From trac-noreply at einsteintoolkit.org Thu Jun 4 09:42:19 2026 From: trac-noreply at einsteintoolkit.org (Zach Etienne) Date: Thu, 04 Jun 2026 14:42:19 +0000 Subject: [ET Trac] #2916: Include Cottonmouth Message-ID: #2916: Include Cottonmouth Reporter: Beyhan Karaka? Status: open Milestone: ET_2026_05 Version: Type: enhancement Priority: major Component: EinsteinToolkit thorn Comment (by Zach Etienne): Here is an updated list of issues, focusing on EinsteinEngine (the generator for Cottonmouth & Friends): ``` Only issues with confidence greater than 90% are included. The list is ordered by confidence, highest first. * Issue 1 - Severity critical - Description of the error: The checked-in 4D Kerr recipe uses `dimensionality=4`, but derivative handling is hard-coded to three axes. Running the recipe reaches `l3` and fails with `Exception: Bad index passed to derivative: x: idx=l3`. - Filename + line number(s): `recipes/compute-kerr.py:26-73`, `EinsteinEngine/frontend/dsl/finite_difference.py:108-143`, `EinsteinEngine/frontend/dsl/finite_difference.py:337-405` - Original code snippet ```python elif idx == L2: if expr == z: return one elif expr in [x, y]: return zero elif expr in self.params: return zero else: raise Exception(f"Bad index passed to derivative: {expr}: idx={idx}") ``` - Fixed code snippet. ```python # DivMakerVisitor def set_coords(self, coords: list[Symbol]) -> None: self.coords = coords self.idx_map = {mk_idx(f"l{i}"): coord for i, coord in enumerate(coords)} @visit.register def _(self, expr: sy.Symbol, idx: sy.Idx) -> Expr: if idx is no_idx: return expr if idx in self.idx_map: coord = self.idx_map[idx] if expr == coord: return one if expr in self.coords or expr in self.params: return zero return _mk_div(self.div_func, expr, idx) raise DslException(f"Unsupported derivative index {idx}") # DslFrontend.mk_coords(), after self.coords is finalized: for dmv in self.div_makers.values(): dmv.set_coords(self.coords) ``` - Confidence: 99.8% * Issue 2 - Severity high - Description of the error: `sech()` and `csch()` derivatives use the global symbol `x` instead of the differentiated function argument, silently producing wrong symbolic equations for non-`x` operands. - Filename + line number(s): `EinsteinEngine/frontend/dsl/finite_difference.py:63-78` - Original code snippet ```python sy.sech: lambda self, r, idx: -sech(r) * tanh(x) * self.visit(r, idx), sy.csch: lambda self, r, idx: -csch(r) * coth(x) * self.visit(r, idx), ``` - Fixed code snippet. ```python sy.sech: lambda self, r, idx: -sech(r) * tanh(r) * self.visit(r, idx), sy.csch: lambda self, r, idx: -csch(r) * coth(r) * self.visit(r, idx), ``` - Confidence: 99.8% * Issue 3 - Severity high - Description of the error: Cactus keyword and explicit numeric range parameters fail because runtime values are compared to parameterized generic aliases such as `set[str]` and `tuple[int, int]`. - Filename + line number(s): `EinsteinEngine/generators/cpp_carpetx_generator.py:535-580` - Original code snippet ```python assert type(py_param_range) is set[str] ... assert type(py_param_range) is tuple[int, int] ... assert type(py_param_range) is tuple[float, float] ``` - Fixed code snippet. ```python if py_param_type is set: if not isinstance(py_param_range, set) or not all(isinstance(s, str) for s in py_param_range): raise GeneratorException(f"Bad keyword range for parameter {param_name}") param_range = KeywordParamRange([String(s) for s in sorted(py_param_range)], String("")) elif py_param_type is int: if py_param_range is None: param_range = IntParamRange(IntParamDescWildcard(), String("")) elif ( isinstance(py_param_range, tuple) and len(py_param_range) == 2 and all(type(v) is int for v in py_param_range) ): lo_i, hi_i = py_param_range param_range = IntParamRange(IntParamDescRange( IntParamOpenLowerBound(Integer(lo_i)), IntParamOpenUpperBound(Integer(hi_i)) ), String("")) else: raise GeneratorException(f"Bad integer range for parameter {param_name}") elif py_param_type is float: if py_param_range is None: param_range = RealParamRange(RealParamDescWildcard(), String("")) elif ( isinstance(py_param_range, tuple) and len(py_param_range) == 2 and all(isinstance(v, (int, float)) for v in py_param_range) ): lo_f, hi_f = map(float, py_param_range) param_range = RealParamRange(RealParamDescRange( RealParamOpenLowerBound(Float(lo_f)), RealParamOpenUpperBound(Float(hi_f)) ), String("")) else: raise GeneratorException(f"Bad real range for parameter {param_name}") ``` - Confidence: 99.5% * Issue 4 - Severity high - Description of the error: Vanilla F90 generation emits empty declarations and empty deallocation calls for functions without local temporaries or tile temporaries, producing invalid Fortran such as `DOUBLE PRECISION ::` and `DEALLOCATE()`. - Filename + line number(s): `EinsteinEngine/generators/vanilla_f90_generator.py:289-302`, `EinsteinEngine/generators/vanilla_f90_generator.py:373-415`, `EinsteinEngine/emit/code/f90/f90_visitor.py:149-152`, `EinsteinEngine/emit/code/f90/f90_visitor.py:207-208` - Original code snippet ```python loop_body.append(VarDecl( type=TypeSpecifier( type=PrimitiveType.Double, attributes=[] ), names=[Identifier(temp_name) for temp_name in local_temporaries] )) deallocate_stmt = ExprStmt(FunctionCall(Identifier('DEALLOCATE'), list(grid_temp_deallocs), [])) ... destructs=[ deallocate_stmt ] ``` - Fixed code snippet. ```python if local_temporaries: loop_body.append(VarDecl( type=TypeSpecifier(type=PrimitiveType.Double, attributes=[]), names=[Identifier(temp_name) for temp_name in local_temporaries] )) destructs: list[F90StmtNode] = [] if grid_temp_deallocs: destructs.append(ExprStmt(FunctionCall( Identifier("DEALLOCATE"), list(grid_temp_deallocs), [] ))) ... destructs=destructs ``` - Confidence: 99.0% * Issue 5 - Severity medium - Description of the error: `ScheduleBlock.if_var` renders as `IN ` instead of `IF `, producing malformed or semantically wrong Cactus schedule CCL for direct `if_var` users. - Filename + line number(s): `EinsteinEngine/emit/ccl/schedule/schedule_visitor.py:83-87` - Original code snippet ```python if n.if_var is not None: s += f' IN {self.visit(n.if_var)}' ``` - Fixed code snippet. ```python if n.if_var is not None: s += f' IF {self.visit(n.if_var)}' ``` - Confidence: 99.0% * Issue 6 - Severity medium - Description of the error: Non-integer stencil offsets are silently truncated by `int(expr.evalf())`, so malformed offsets like `1/2` become `0` instead of being rejected. - Filename + line number(s): `EinsteinEngine/emit/code/sympy_visitor.py:150-156` - Original code snippet ```python @staticmethod def _to_stencil_offset(v: sy.Basic) -> int: if isinstance(v, sy.Integer): return int(v) # noinspection PyTypeChecker return int(cast(sy.Expr, v).evalf()) # type: ignore[no-untyped-call] ``` - Fixed code snippet. ```python @staticmethod def _to_stencil_offset(v: sy.Basic) -> int: if isinstance(v, sy.Integer): return int(v) raise DslException(f"Stencil offset must be an integer, got {v!s}") ``` - Confidence: 99.0% * Issue 7 - Severity medium - Description of the error: Ghost-zone limit discovery misses equations whose RHS is exactly a stencil call because `_stencil_limits()` checks only child arguments and not the root expression. - Filename + line number(s): `EinsteinEngine/intermediate/eqnlist.py:1215-1230` - Original code snippet ```python def _stencil_limits(self, result: List[int], expr: Expr) -> None: for arg in expr.args: if str(type(arg)) == "stencil": for i in range(3): ivar = arg.args[i + 1] assert isinstance(ivar, Integer), f"ivar={ivar}, type={type(ivar)}" result[i] = max(result[i], abs(int(ivar))) else: if isinstance(arg, Expr): self._stencil_limits(result, arg) ``` - Fixed code snippet. ```python def _stencil_limits(self, result: List[int], expr: Expr) -> None: if getattr(expr, "func", None) is not None and self.is_stencil.get(expr.func, False): if len(expr.args) != 4: raise DslException(f"Stencil call should have 4 arguments: {expr}") for i in range(3): ivar = expr.args[i + 1] if not isinstance(ivar, Integer): raise DslException(f"Stencil offset must be an integer: {expr}") result[i] = max(result[i], abs(int(ivar))) return for arg in expr.args: if isinstance(arg, Expr): self._stencil_limits(result, arg) ``` - Confidence: 98.5% * Issue 8 - Severity medium - Description of the error: Duplicate equation detection uses `assert`, so `python -O` disables the guard and lets a second equation silently overwrite the first. - Filename + line number(s): `EinsteinEngine/intermediate/eqnlist.py:668-672` - Original code snippet ```python def add_eqn(self, lhs: Symbol, rhs: Expr) -> None: assert lhs not in self.eqns, f"Equation for '{lhs}' is already defined" # Ensure we only have symbols in eqnlist self.eqns[lhs := symbify(lhs)] = symbify(rhs) self.eqn_insertion_order[lhs] = len(self.eqns) - 1 ``` - Fixed code snippet. ```python def add_eqn(self, lhs: Symbol, rhs: Expr) -> None: lhs = symbify(lhs) if lhs in self.eqns: raise IntermediateException(f"Equation for '{lhs}' is already defined") self.eqns[lhs] = symbify(rhs) self.eqn_insertion_order[lhs] = len(self.eqns) - 1 ``` - Confidence: 98.5% * Issue 9 - Severity medium - Description of the error: Invalid tensor symmetry declarations are not reliably rejected. The second missing-index check compares `i2` against `-2` even though the sentinel is `-1`, and all validation is assert-based. - Filename + line number(s): `EinsteinEngine/frontend/dsl/dsl_frontend.py:802-815` - Original code snippet ```python assert i1 != -1, f"Index {ix1} not in {tens}" assert i2 != -2, f"Index {ix2} not in {tens}" assert i1 != i2, f"Index {ix1} cannot be symmetric with itself in {tens}" ``` - Fixed code snippet. ```python if i1 == -1: raise DslException(f"Index {ix1} not in {tens}") if i2 == -1: raise DslException(f"Index {ix2} not in {tens}") if i1 == i2: raise DslException(f"Index {ix1} cannot be symmetric with itself in {tens}") ``` - Confidence: 98.0% * Issue 10 - Severity high - Description of the error: The overwrite/stencil safety guard never detects stencil calls because it checks for the string `"stencil"` inside `rhs.free_symbols`, which contains SymPy symbols, not function names. - Filename + line number(s): `EinsteinEngine/intermediate/eqnlist.py:963-971`, `EinsteinEngine/generators/cpp_carpetx_generator.py:932-935`, `EinsteinEngine/generators/vanilla_f90_generator.py:284-287` - Original code snippet ```python for rhs in self.eqns.values(): if "stencil" in rhs.free_symbols: raise DslException(f"Overwrite source symbol {rd} cannot be used inside a stencil") ``` - Fixed code snippet. ```python for rd in rd_overwrites: for rhs in self.eqns.values(): for call in rhs.find(lambda e: hasattr(e, "func") and self.is_stencil.get(e.func, False)): if len(call.args) > 0 and call.args[0] == rd: raise DslException( f"Overwrite source symbol {rd} cannot be used inside a stencil" ) ``` - Confidence: 97.0% * Issue 11 - Severity medium - Description of the error: Package metadata does not enforce the documented Python 3.13 minimum, so unsupported interpreters can install the package and fail later. - Filename + line number(s): `README.md:5-7`, `setup.py:20-40` - Original code snippet ```python setup( name='EinsteinEngine', version='0.1.0', ... install_requires=[ ... ] ) ``` - Fixed code snippet. ```python setup( name='EinsteinEngine', version='0.1.0', python_requires='>=3.13', ... install_requires=[ ... ] ) ``` - Confidence: 97.0% * Issue 12 - Severity medium - Description of the error: Generated CarpetX thorns always require NewRadX and include `newradx.hxx`, even when no NewRadX boundary functions are used. - Filename + line number(s): `EinsteinEngine/wizards/thorn_wizards.py:90-99`, `EinsteinEngine/generators/cpp_carpetx_generator.py:510-518` - Original code snippet ```python configuration_ccl = f""" REQUIRES Arith Loop {self.thorn_def.name}_gen AMReX NewRadX ... """.strip() ``` ```python IncludeSection([ UsesInclude(Verbatim('newradx.hxx')) ]), ``` - Fixed code snippet. ```python requires = f"REQUIRES Arith Loop {self.thorn_def.name}_gen AMReX" if self.generator.options.get("new_rad_x_boundary_fns"): requires += " NewRadX" configuration_ccl = f""" {requires} PROVIDES {self.thorn_def.name}_gen {{ # SCRIPT bin/generate.py # LANG python3 }} """.strip() ``` ```python includes = [] if self.options.get("new_rad_x_boundary_fns"): includes.append(UsesInclude(Verbatim("newradx.hxx"))) IncludeSection(includes), ``` - Confidence: 96.0% * Issue 13 - Severity medium - Description of the error: Vanilla F90 module parameter declarations are flattened from per-function maps without deduplicating by parameter name, so multiple functions using the same parameter can declare it more than once. - Filename + line number(s): `EinsteinEngine/generators/vanilla_f90_generator.py:446-458` - Original code snippet ```python param_decls: list[VarDecl] = list( VarDecl( type=TypeSpecifier( type=param.get_type(), attributes=[Public()] ), names=[Identifier(var)], ) for var, param in flatten(self.params[fn_name].items() for fn_name in fn_names) ) ``` - Fixed code snippet. ```python module_params: OrderedDict[str, CactusParam] = OrderedDict() for fn_name in fn_names: for var, param in self.params[fn_name].items(): module_params.setdefault(var, param) param_decls: list[VarDecl] = [ VarDecl( type=TypeSpecifier(type=param.get_type(), attributes=[Public()]), names=[Identifier(var)], ) for var, param in module_params.items() ] ``` - Confidence: 95.0% * Issue 14 - Severity medium - Description of the error: Reciprocal trig/hyperbolic functions are accepted by the SymPy visitor and emitted directly as `cot`, `sec`, `csc`, `coth`, `sech`, and `csch`, which are not standard C++ functions and are not Fortran intrinsics in the emitted form. - Filename + line number(s): `EinsteinEngine/emit/code/sympy_visitor.py:38-50`, `EinsteinEngine/emit/code/cpp_carpetx/cpp_carpetx_visitor.py:42-54`, `EinsteinEngine/emit/code/f90/f90_visitor.py:39-51`, `EinsteinEngine/generators/cpp_carpetx_generator.py:111-112` - Original code snippet ```python sy.cot: StandardizedFunctionCallType.Cot, sy.sec: StandardizedFunctionCallType.Sec, sy.csc: StandardizedFunctionCallType.Csc, ... StandardizedFunctionCallType.Cot: 'cot', StandardizedFunctionCallType.Sec: 'sec', StandardizedFunctionCallType.Csc: 'csc', ``` - Fixed code snippet. ```python reciprocal_rewrites: dict[sy.Function, Callable[ -- Ticket URL: https://bitbucket.org/einsteintoolkit/tickets/issues/2916/include-cottonmouth -------------- next part -------------- An HTML attachment was scrubbed... URL: