[ET Trac] #2916: Include Cottonmouth

Zach Etienne trac-noreply at einsteintoolkit.org
Thu Jun 4 09:42:19 CDT 2026


#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 <var>` instead of `IF <var>`, 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: <http://lists.einsteintoolkit.org/pipermail/trac/attachments/20260604/49a1ebe3/attachment.htm>


More information about the Trac mailing list