Skip to content
Open
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
37 changes: 24 additions & 13 deletions source/bind/python/CEA.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1224,7 +1224,7 @@ cdef class Mixture:

return weights

def calc_property(self, cea_property_type prop_type, np.ndarray weights,
def calc_property(self, int prop_type, np.ndarray weights,
temperature: float | list | np.ndarray, pressure: Optional[float] = None):
"""
Calculate thermodynamic property for mixture at specified conditions.
Expand Down Expand Up @@ -1253,12 +1253,6 @@ cdef class Mixture:
cdef cea_err ierr
cdef cea_real value
cdef int nspecies = <int>len(weights)
cdef cea_real *reac_weights = <cea_real *>malloc(nspecies * sizeof(cea_real))
if reac_weights == NULL:
raise MemoryError("Failed to allocate weights buffer")

for i in range(nspecies):
reac_weights[i] = weights[i]

if prop_type not in [VOLUME, DENSITY, ENTHALPY, ENERGY, FROZEN_CP, FROZEN_CV, ENTROPY, GIBBS_ENERGY]:
raise ValueError("Property type not supported for mixture calculations")
Expand All @@ -1267,6 +1261,13 @@ cdef class Mixture:
if pressure is None:
raise ValueError("Pressure must be provided for enthalpy and Gibbs energy calculations")

cdef cea_real *reac_weights = <cea_real *>malloc(nspecies * sizeof(cea_real))
if reac_weights == NULL:
raise MemoryError("Failed to allocate weights buffer")

for i in range(nspecies):
reac_weights[i] = weights[i]

# Handle the case where temperature is a list of numpy array
cdef cea_real *reac_temps = <cea_real *>malloc(nspecies * sizeof(cea_real))
if reac_temps == NULL:
Expand All @@ -1275,21 +1276,31 @@ cdef class Mixture:

try:
if prop_type in [ENTROPY, GIBBS_ENERGY, VOLUME, DENSITY]:
if isinstance(temperature, float):
ierr = cea_mixture_calc_property_tp(self.ptr, prop_type, nspecies, reac_weights, temperature, pressure, &value)
if isinstance(temperature, (float, int, np.floating)):
ierr = cea_mixture_calc_property_tp(self.ptr, <cea_property_type>prop_type, nspecies, reac_weights, temperature, pressure, &value)
elif type(temperature) in [list, np.ndarray]:
if len(temperature) != nspecies:
raise ValueError(
f"Mixture.calc_property: temperature has {len(temperature)} elements, "
f"expected {nspecies} (one per species in weights)"
)
for i in range(nspecies):
reac_temps[i] = temperature[i]
ierr = cea_mixture_calc_property_tp_multitemp(self.ptr, prop_type, nspecies, reac_weights, nspecies, reac_temps, pressure, &value)
ierr = cea_mixture_calc_property_tp_multitemp(self.ptr, <cea_property_type>prop_type, nspecies, reac_weights, nspecies, reac_temps, pressure, &value)
else:
raise ValueError("Mixiture.calc_property: temperature must be a float, list, or np.ndarray")
else:
if isinstance(temperature, float):
ierr = cea_mixture_calc_property(self.ptr, prop_type, nspecies, reac_weights, temperature, &value)
if isinstance(temperature, (float, int, np.floating)):
ierr = cea_mixture_calc_property(self.ptr, <cea_property_type>prop_type, nspecies, reac_weights, temperature, &value)
elif type(temperature) in [list, np.ndarray]:
if len(temperature) != nspecies:
raise ValueError(
f"Mixture.calc_property: temperature has {len(temperature)} elements, "
f"expected {nspecies} (one per species in weights)"
)
for i in range(nspecies):
reac_temps[i] = temperature[i]
ierr = cea_mixture_calc_property_multitemp(self.ptr, prop_type, nspecies, reac_weights, nspecies, reac_temps, &value)
ierr = cea_mixture_calc_property_multitemp(self.ptr, <cea_property_type>prop_type, nspecies, reac_weights, nspecies, reac_temps, &value)
else:
raise ValueError("Mixiture.calc_property: temperature must be a float, list, or np.ndarray")
_check_ierr(ierr, "Mixture.calc_property")
Expand Down
138 changes: 136 additions & 2 deletions source/bind/python/tests/test_mixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ def test_calc_property_requires_pressure(cea_module):
mix.calc_property(cea_module.ENTROPY, weights, temperature=3000.0)


def test_calc_property_rejects_unknown_type(cea_module):
@pytest.mark.parametrize("prop_type", [999999, -5])
def test_calc_property_rejects_unknown_type(cea_module, prop_type):
mix = cea_module.Mixture(["H2", "O2"])
weights = np.array([0.4, 0.6], dtype=np.float64)
with pytest.raises(ValueError):
mix.calc_property(999999, weights, temperature=3000.0)
mix.calc_property(prop_type, weights, temperature=3000.0)


@pytest.mark.parametrize(
Expand Down Expand Up @@ -76,3 +77,136 @@ def test_multitemperature_density_matches_single_temperature(cea_module):
)

assert density_multi == pytest.approx(density_single)


def test_multitemperature_density_matches_single_temperature_list(cea_module):
"""Same as above, but passing temperature as a plain Python list instead of an ndarray."""
mix = cea_module.Mixture(["O2(L)"])
weights = np.array([1.0])

density_single = mix.calc_property(
cea_module.DENSITY, weights, temperature=90.0, pressure=68.0
)
density_multi = mix.calc_property(
cea_module.DENSITY, weights, temperature=[90.0], pressure=68.0
)

assert density_multi == pytest.approx(density_single)


@pytest.mark.parametrize("temperature_dtype", [np.float32, np.float64])
def test_calc_property_accepts_numpy_scalar_temperature(cea_module, temperature_dtype):
mix = cea_module.Mixture(["O2(L)"])
weights = np.array([1.0])

density_python_float = mix.calc_property(
cea_module.DENSITY, weights, temperature=90.0, pressure=68.0
)
numpy_scalar_temperature = np.array([90.0], dtype=temperature_dtype)[0]
density_numpy_scalar = mix.calc_property(
cea_module.DENSITY, weights, temperature=numpy_scalar_temperature, pressure=68.0
)

assert density_numpy_scalar == pytest.approx(density_python_float)


def test_calc_property_rejects_invalid_temperature_type(cea_module):
mix = cea_module.Mixture(["O2(L)"])
weights = np.array([1.0])

with pytest.raises(ValueError):
mix.calc_property(
cea_module.DENSITY, weights, temperature="not a number", pressure=68.0
)


def test_calc_property_accepts_plain_int_temperature(cea_module):
mix = cea_module.Mixture(["O2(L)"])
weights = np.array([1.0])

density_python_float = mix.calc_property(
cea_module.DENSITY, weights, temperature=90.0, pressure=68.0
)
density_python_int = mix.calc_property(
cea_module.DENSITY, weights, temperature=90, pressure=68.0
)

assert density_python_int == pytest.approx(density_python_float)


def test_calc_property_rejects_numpy_integer_temperature(cea_module):
mix = cea_module.Mixture(["O2(L)"])
weights = np.array([1.0])

with pytest.raises(ValueError):
mix.calc_property(
cea_module.DENSITY, weights, temperature=np.int64(90), pressure=68.0
)


@pytest.mark.parametrize(
"temperatures",
[np.array([90.0]), [90.0]],
ids=["ndarray", "list"],
)
def test_calc_property_rejects_too_few_multitemperatures_pressure_dependent(
cea_module, temperatures
):
"""2 species but only 1 temperature given (DENSITY, which needs pressure) -> should error, not crash or guess."""
mix = cea_module.Mixture(["O2(L)", "CH4(L)"])
weights = np.array([0.5, 0.5])

with pytest.raises(ValueError):
mix.calc_property(
cea_module.DENSITY, weights, temperature=temperatures, pressure=68.0
)


@pytest.mark.parametrize(
"temperatures",
[np.array([90.0, 200.0, 300.0]), [90.0, 200.0, 300.0]],
ids=["ndarray", "list"],
)
def test_calc_property_rejects_too_many_multitemperatures_pressure_dependent(
cea_module, temperatures
):
"""1 species but 3 temperatures given (DENSITY, which needs pressure) -> should error, not silently ignore the extras."""
mix = cea_module.Mixture(["O2(L)"])
weights = np.array([1.0])

with pytest.raises(ValueError):
mix.calc_property(
cea_module.DENSITY, weights, temperature=temperatures, pressure=68.0
)


@pytest.mark.parametrize(
"temperatures",
[np.array([90.0]), [90.0]],
ids=["ndarray", "list"],
)
def test_calc_property_rejects_too_few_multitemperatures_pressure_independent(
cea_module, temperatures
):
"""Same too-few-temperatures check, but for FROZEN_CP (a property type that does NOT need pressure) -> should also error."""
mix = cea_module.Mixture(["O2(L)", "CH4(L)"])
weights = np.array([0.5, 0.5])

with pytest.raises(ValueError):
mix.calc_property(cea_module.FROZEN_CP, weights, temperature=temperatures)


@pytest.mark.parametrize(
"temperatures",
[np.array([90.0, 200.0, 300.0]), [90.0, 200.0, 300.0]],
ids=["ndarray", "list"],
)
def test_calc_property_rejects_too_many_multitemperatures_pressure_independent(
cea_module, temperatures
):
"""Same too-many-temperatures check, but for FROZEN_CP (a property type that does NOT need pressure) -> should also error."""
mix = cea_module.Mixture(["O2(L)"])
weights = np.array([1.0])

with pytest.raises(ValueError):
mix.calc_property(cea_module.FROZEN_CP, weights, temperature=temperatures)
Loading