#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-cottonmou…
#2952: GRHayLET/IllinoisGRMHD convert_*_to_*.c magnetic sector subleties
Reporter: Maxwell Rizzo
Status: new
Milestone:
Version:
Type: bug
Priority: minor
Component: EinsteinToolkit thorn
Comment (by Leonardo Rosa Werneck):
Hi Maxwell! Thanks for pointing these out!
* Regarding `phitilde = Aphi`, I agree that this is misleading. Although I don't see anything in the `HydroBase` documentation that states that `Aphi` is defined without the `sqrt{gamma}` factor in it, intuitively that's what I would expect. This has been likely not been a problem because most of our ID thorns set `Aphi` to zero initially. This can definitely be fixed.
* Regarding the factor of `1/sqrt(4pi)`, I would point to the discussion surrounding Eqs. (A9) and (A10) of the [GRHayL paper](https://arxiv.org/pdf/2512.15846). Since this affects only the definition of the magnetic field itself, I think rescaling only `Avec` is correct, but let me know if you find a reasoning to also rescale `Aphi`.
* Regarding `Ax`/`Ay`/`Az` being staggered, I don't know of a way to remedy this. Our initial data thorns (such as `Seed_Magnetic_Fields`) "borrow" `Avec` as auxiliary storage. Although one can argue this is incorrect and that they could initialize `Ax`/`Ay`/`Az` directly instead, doing that would make it difficult for users to utilize our initial data in their own evolution thorns. Our "solution" was to add a parameter to the ID thorns that allows users to initialize `Avec` with either staggered or vertex-centered data. We'd be happy to discuss alternatives, if you'd like to propose any.
* Regarding repopulating `Avec`/`Aphi`, this can be added, but the usefulness is less clear to me. While having them in the `HydroBase -> GRHayLET` conversion enables users to more easily utilize our initial data thorns, having them in the `GRHayLET -> HydroBase` seems less useful/redundant, as users can output `Ax`/`Ay`/`Az`/`phitilde` instead of the `HydroBase` quantities. The centering mismatch also makes things a bit trickier, as you pointed out. That being said, we'd be happy to add the copy if folks think it would be useful.
Let me know what you think. I'd be happy to create a PR to address issues we agree on.
--
Ticket URL: https://bitbucket.org/einsteintoolkit/tickets/issues/2952/grhaylet-illinois…
#2952: GRHayLET/IllinoisGRMHD convert_*_to_*.c magnetic sector subleties
Reporter: Maxwell Rizzo
Status: new
Milestone:
Version:
Type: bug
Priority: minor
Component: EinsteinToolkit thorn
convert_HydroBase_to_IllinoisGRMHD.c
1. HydroBase Aphi is directly copied into IllinoisGRMHD phitilde on line [29](https://github.com/GRHayL/GRHayLET/blob/9b01f8839d44db2ba928d2af7d1bc6b…. A comment in the interface says "# phitilde (=Phi*psi^6)", and another "sqrt{gamma} Phi", so I believe the direct copy is missing a factor of Psi^6 or sqrt(det gamma). It is also missing a `mag_factor` scaling of 1/4pi when compared to the Avec copying, I am not sure if it is correct without it or it should have it given `mag_factor`/`rescale_magnetics` is on by default.
2. IllinoisGRMHD Ax/Ay/Az are face centered staggered, Aphi is vertex centered. They are populated directly from HydroBase cell centered quantities. This is a bit subtle for Initial Data thorns to interface with properly, it is somewhat documented but could be a bit clearer.
convert_IllinoisGRMHD_to_HydroBase.c
1. Only HydroBase Bvec is repopulated. HydroBase Avec and Aphi are not refilled (probably due to centering mismatch). This is probably fine as the magnetic field is the more interesting quantity for diagnostics, but it could be useful for debugging or consistency.
--
Ticket URL: https://bitbucket.org/einsteintoolkit/tickets/issues/2952/grhaylet-illinois…
#2929: GRHayLET/IllinoisGRMHD convert_IllinoisGRMHD_to_HydroBase schedule compatibility with VolumeIntegrals_*
Reporter: Maxwell Rizzo
Status: open
Milestone: ET_2026_05
Version: ET_2025_05
Type: bug
Priority: major
Component: EinsteinToolkit thorn
Changes (by Maxwell Rizzo):
status: open (was resolved)
Comment (by Maxwell Rizzo):
GRHayLET/GRHayLHD has the same issue, on line [152](https://github.com/GRHayL/GRHayLET/blob/9b01f8839d44db2ba928d2af7d1bc6… of `schedule.ccl`,
```
schedule convert_GRHayLHD_to_HydroBase at CCTK_ANALYSIS before (compute_bi_b2_Poyn_fluxET convert_to_MHD_3velocity particle_tracerET VolumeIntegralGroup) after ML_BSSN_evolCalcGroup
```
--
Ticket URL: https://bitbucket.org/einsteintoolkit/tickets/issues/2929/grhaylet-illinois…
#2951: Cottonmouth uses `max()` unqualified without a using declaration
Reporter: Maxwell Rizzo
Status: new
Milestone:
Version:
Type: bug
Priority: major
Component: EinsteinToolkit thorn
CottonmouthZ4c4m/src/CottonmouthZ4c4m_z4c_enforce_pt1.cpp
CottonmouthBSSNOK4m/src/CottonmouthBSSNOK4m_enforce_pt1.cpp
both files have unqualified calls of the `max()` function. Both have the same using statement on line 41,
```
using std::cbrt,std::fmax,std::fmin,std::sqrt;
```
`fmax` being unused in both files maybe suggests that this `std::fmax` should be `std::max`, as it is generated code it isn't clear if this is a human error or an issue with the generation.
Cottonmouth fails to compile on Anvil with error
```
error: 'max' was not declared in this scope
```
--
Ticket URL: https://bitbucket.org/einsteintoolkit/tickets/issues/2951/cottonmouth-uses-…
#2950: CarpetX/ADMBaseX: Missing ADMBase features (Conditional storage, evolution parameters)
Reporter: Maxwell Rizzo
Status: new
Milestone:
Version:
Type: bug
Priority: minor
Component: CarpetX
Very similar to #2948, but smaller in scope. Mostly consistency and future proofing in mind. All changes are following the previous Carpet ADMBase conventions.
1. Missing conditional storage on the extra fields (shift, dtlapse, dtshift).
2. Missng `*_evolution_method` parameters specifying which thorn evolves which fields.
3. ADMBaseX defaults to an initial dtlapse/dtshift of `zero` instead of `none` (previously in Carpet ADMBase). With conditional storage it probably makes sense to return to the `none` default to only enable the extra fields if the user needs them. Changing this default here may break some test parameter files.
Addressed by CarpetX [PR #386](https://github.com/EinsteinToolkit/CarpetX/pull/386)
--
Ticket URL: https://bitbucket.org/einsteintoolkit/tickets/issues/2950/carpetx-admbasex-…