Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ __pycache__/
source/bind/excel/~$*.xlsm
source/bind/excel/*.lib
source/bind/python/samples
source/bind/python/cea/samples/intro.py
source/bind/python/cea/samples/intro.py
AGENTS.local.md
13 changes: 10 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,27 @@ All notable user-visible changes to this project are documented here.

## [Unreleased]

### Changed

### Fixed

### Added

## [3.2.0] - 2026-05-12

### Changed
- Python `cea.Reactant` now supports explicit `enthalpy_units` for custom reactant enthalpy input (`J/kg`, `kJ/kg`, `cal/kg`, `kcal/kg`, `J/mol`, `kJ/mol`, `cal/mol`, `kcal/mol`; `/mol` and `/mole` spellings accepted).
- Backward-compatible behavior is preserved when `Reactant.enthalpy` is provided without `enthalpy_units`: the legacy default (`J/kg`) is still used, but this now emits a `FutureWarning` directing users to pass `enthalpy_units` explicitly.
- Python `cea.Reactant` now requires explicit `enthalpy_units` whenever `enthalpy` is provided; omitting the units now raises `ValueError` instead of falling back to the legacy implicit `J/kg` default.
- Weight-based enthalpy units (`*/kg`) now require `Reactant.molecular_weight` for conversion, while molar units (`*/mol`) do not require molecular weight for enthalpy interpretation.
- Python docs and examples were updated to pass explicit `enthalpy_units` for custom reactants (including RP-1311 Example 5) instead of relying on implicit defaults.
- **Compatibility notice:** a future **minor** version increment will require `enthalpy_units` whenever `Reactant.enthalpy` is defined. Code that currently relies on implicit enthalpy units may break until updated.
- Smooth-truncation derivative assembly in the equilibrium solver now uses an effective Jacobian/chain-rule formulation across TP/HP/SP/TV/UV/SV solves so reported sensitivities track the smoothed species-activity model more consistently near truncation thresholds.

### Fixed
- Smooth-truncation total derivatives for reported gas species and derived thermodynamic outputs were aligned with the final reported-species mapping, improving behavior for trace-species sensitivities near truncation cutoffs.
- Corrected user-facing viscosity unit labels in the equilibrium core comments and Python bindings to report millipoise consistently.

### Added
- Added Python regression coverage for `Reactant` enthalpy-unit handling, including omitted-units warning behavior, explicit molar/weight units, molecular-weight requirements for weight units, and accepted unit spellings.
- Added Python regression coverage for `Reactant` enthalpy-unit handling, including required explicit units, explicit molar/weight units, molecular-weight requirements for weight units, and accepted unit spellings.
- Added pfunit regression coverage for smooth-truncation derivative chain-rule behavior, targeted finite-difference agreement across TP/HP/SP/TV/UV/SV constraints, and reported-entropy consistency checks.

## [3.1.2] - 2026-03-23
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set(CMAKE_DISABLE_SOURCE_CHANGES ON)
set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
project(CEA
VERSION 3.1.2
VERSION 3.2.0
LANGUAGES Fortran
)

Expand Down
4 changes: 2 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
project = 'CEA'
copyright = ''
author = 'Mark Leader'
version = '3.1'
release = '3.1.2'
version = '3.2'
release = '3.2.0'

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand Down
3 changes: 1 addition & 2 deletions docs/source/examples/equilibrium/example5.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ Define the pressure schedule and reactant composition by weight fraction:
T_reac = np.array([298.15, 298.15, 298.15, 298.15, 298.15], dtype=np.float64)

Create a :class:`~cea.Reactant` for ``CHOS-Binder`` using explicit enthalpy units.
For backward compatibility, omitting ``enthalpy_units`` still defaults to ``J/kg`` for now,
but that implicit behavior is deprecated and emits a warning.
When ``enthalpy`` is provided, ``enthalpy_units`` is required.

.. code-block:: python

Expand Down
6 changes: 2 additions & 4 deletions docs/source/interfaces/python_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ The :class:`~cea.Mixture` class is used to define a mixture of product or reacta
The instances of this class are then passed as inputs to the available solver classes (e.g., :class:`~cea.EqSolver`, :class:`~cea.RocketSolver`, :class:`~cea.ShockSolver`, or :class:`~cea.DetonationSolver`).
Custom reactants can be provided through :class:`~cea.Reactant` objects (including mixed lists of strings and Reactant objects).
For :class:`~cea.Reactant`, ``temperature`` is in K, and ``enthalpy`` must be paired with explicit
``enthalpy_units`` (for example ``"J/kg"`` or ``"kJ/mol"``). Omitting ``enthalpy_units`` while
providing ``enthalpy`` preserves the legacy default (``J/kg``) for backward compatibility, but this
is deprecated and emits a ``FutureWarning``. A future minor version will require explicit
``enthalpy_units``.
``enthalpy_units`` (for example ``"J/kg"`` or ``"kJ/mol"``). Weight-based enthalpy units require
``molecular_weight`` for conversion, while molar enthalpy units do not.

.. autoclass:: cea.Reactant
:members:
Expand Down
16 changes: 4 additions & 12 deletions source/bind/python/CEA.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -546,9 +546,7 @@ cdef class Reactant:
Units for ``enthalpy``. Supported values (case-insensitive):
``J/kg``, ``kJ/kg``, ``cal/kg``, ``kcal/kg``, ``J/mol``, ``kJ/mol``,
``cal/mol``, ``kcal/mol`` (``/mole`` spellings are also accepted).
If omitted while ``enthalpy`` is provided, the legacy default ``J/kg``
is used for backward compatibility and a ``FutureWarning`` is emitted.
Future minor versions will require explicit ``enthalpy_units``.
Required whenever ``enthalpy`` is provided.
temperature : float, optional
Reference temperature in SI units (K).

Expand Down Expand Up @@ -612,16 +610,10 @@ cdef class Reactant:

if enthalpy is not None:
if enthalpy_units is None:
warnings.warn(
"Reactant enthalpy without explicit enthalpy_units is deprecated; "
"using legacy default J/kg for backward compatibility. "
"Future minor versions will require explicit enthalpy_units. "
"Pass enthalpy_units='J/kg' or enthalpy_units='kJ/mol'.",
FutureWarning,
raise ValueError(
"Reactant enthalpy requires explicit enthalpy_units; "
"pass enthalpy_units='J/kg' or enthalpy_units='kJ/mol'"
)
normalized_enthalpy_units = "j/kg"
enthalpy_core_units = "j/mole"
enthalpy_is_weight_units = True
else:
(
normalized_enthalpy_units,
Expand Down
2 changes: 1 addition & 1 deletion source/bind/python/cea/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "3.1.2"
__version__ = "3.2.0"

# initialize libcea, loading in the default data files
from cea.lib.libcea import init as libcea_init
Expand Down
21 changes: 16 additions & 5 deletions source/bind/python/cea/lib/libcea.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,20 @@ class Reactant:
self,
name: str,
formula: Mapping[str, SupportsFloat] | None = None,
molecular_weight: SupportsFloat = ...,
enthalpy: SupportsFloat = ...,
enthalpy_units: WeightEnthalpyUnits = ...,
molecular_weight: SupportsFloat | None = None,
enthalpy: None = None,
enthalpy_units: None = None,
temperature: SupportsFloat | None = None,
) -> None: ...

@overload
def __init__(
self,
name: str,
formula: Mapping[str, SupportsFloat] | None = None,
molecular_weight: SupportsFloat,
enthalpy: SupportsFloat,
enthalpy_units: WeightEnthalpyUnits,
temperature: SupportsFloat | None = None,
) -> None: ...

Expand All @@ -210,8 +221,8 @@ class Reactant:
name: str,
formula: Mapping[str, SupportsFloat] | None = None,
molecular_weight: SupportsFloat | None = None,
enthalpy: SupportsFloat | None = None,
enthalpy_units: MolarEnthalpyUnits | None = None,
enthalpy: SupportsFloat,
enthalpy_units: MolarEnthalpyUnits,
temperature: SupportsFloat | None = None,
) -> None: ...

Expand Down
25 changes: 3 additions & 22 deletions source/bind/python/tests/test_rp1311_samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,40 +240,21 @@ def test_example5_hp_custom_reactant(cea_module):
assert soln.mole_fractions["CO"] == pytest.approx(0.25896, rel=1e-4)


def test_reactant_enthalpy_implicit_units_warn_and_match_explicit(cea_module):
def test_reactant_enthalpy_requires_explicit_units(cea_module):
cea = cea_module
mw = 14.6652984484
h_cal_per_mol = -2999.082
h_j_per_kg = cea.units.cal_to_joule(h_cal_per_mol) / (mw * 1.0e-3)
formula = {"C": 1.0, "H": 1.86955, "O": 0.031256, "S": 0.008415}

with pytest.warns(FutureWarning, match="legacy default J/kg"):
legacy = cea.Reactant(
with pytest.raises(ValueError, match="requires explicit enthalpy_units"):
cea.Reactant(
name="CHOS-Binder",
formula=formula,
molecular_weight=mw,
enthalpy=h_j_per_kg,
temperature=298.15,
)
explicit = cea.Reactant(
name="CHOS-Binder",
formula=formula,
molecular_weight=mw,
enthalpy=h_j_per_kg,
enthalpy_units="J/kg",
temperature=298.15,
)

assert legacy.enthalpy_units == "j/kg"
assert explicit.enthalpy_units == "j/kg"

mix_legacy = cea.Mixture([legacy])
mix_explicit = cea.Mixture([explicit])
weights = np.array([1.0], dtype=np.float64)
T_reac = np.array([298.15], dtype=np.float64)
h_legacy = mix_legacy.calc_property(cea.ENTHALPY, weights, T_reac)
h_explicit = mix_explicit.calc_property(cea.ENTHALPY, weights, T_reac)
assert h_legacy == pytest.approx(h_explicit, rel=1e-12)


def test_reactant_molar_enthalpy_units_do_not_require_molecular_weight(cea_module):
Expand Down
Loading