Files
cantera/test/python/test_thermo.py
2024-03-11 19:43:32 -04:00

2473 lines
96 KiB
Python

import numpy as np
import gc
import cantera as ct
from . import utilities
from .utilities import allow_deprecated
import pytest
from pytest import approx
class TestThermoPhase(utilities.CanteraTest):
def setUp(self):
self.phase = ct.Solution('h2o2.yaml', transport_model=None)
def test_source(self):
self.assertEqual(self.phase.source, 'h2o2.yaml')
def test_input_header(self):
extra = self.phase.input_header
self.assertTrue(extra["description"].startswith("Hydrogen-Oxygen submechanism"))
self.assertEqual(extra["cantera-version"], "2.5.0")
self.assertEqual(extra["generator"], "ck2yaml")
def test_missing_phases_key(self):
yaml = '''
species:
- name: H
composition: {H: 1}
thermo:
model: NASA7
temperature-ranges: [200.0, 1000.0, 6000.0]
data:
- [2.5, 0.0, 0.0, 0.0, 0.0, 2.547366e+04, -0.44668285]
- [2.5, 0.0, 0.0, 0.0, 0.0, 2.547366e+04, -0.44668285]
note: L6/94
'''
with self.assertRaisesRegex(ct.CanteraError, "Key 'phases' not found"):
_ = ct.Solution(yaml=yaml)
def test_deprecated_phase(self):
yaml = """
phases:
- name: phasename
thermo: ideal-gas
species: [{h2o2.yaml/species: all}]
deprecated: This phase is deprecated because I said so.
"""
with self.assertRaisesRegex(ct.CanteraError, "(?s)phasename.*said so"):
ct.Solution(yaml=yaml)
def test_base_attributes(self):
self.assertIsInstance(self.phase.name, str)
self.assertIsInstance(self.phase.phase_of_matter, str)
self.assertIsInstance(self.phase.thermo_model, str)
self.assertIsInstance(self.phase.kinetics_model, str)
self.assertIsInstance(self.phase.transport_model, str)
self.assertIsInstance(self.phase.composite, tuple)
self.assertEqual(len(self.phase.composite), 3)
self.assertEqual(self.phase.composite,
(self.phase.thermo_model,
self.phase.kinetics_model,
self.phase.transport_model))
self.phase.name = 'spam'
self.assertEqual(self.phase.name, 'spam')
with self.assertRaises(AttributeError):
self.phase.type = 'eggs'
def test_phases(self):
self.assertEqual(self.phase.n_phases, 1)
self.assertEqual(self.phase.phase_of_matter, "gas")
def test_states(self):
self.assertEqual(self.phase._native_state, ('T', 'D', 'Y'))
self.assertIn('TPY', self.phase._full_states.values())
self.assertIn('TD', self.phase._partial_states.values())
assert self.phase._native_mode == "TDY"
def test_species(self):
self.assertEqual(self.phase.n_species, 10)
for i,name in enumerate(self.phase.species_names):
self.assertEqual(name, self.phase.species_name(i))
self.assertEqual(i, self.phase.species_index(name))
self.assertEqual(i, self.phase.species_index(i))
with self.assertRaisesRegex(ct.CanteraError, 'IndexError thrown by Phase::checkSpeciesIndex'):
self.phase.species(self.phase.n_species)
def test_elements(self):
self.assertEqual(self.phase.n_elements, 4)
for i,symbol in enumerate(self.phase.element_names):
self.assertEqual(symbol, self.phase.element_name(i))
self.assertEqual(i, self.phase.element_index(symbol))
self.assertEqual(i, self.phase.element_index(i))
def test_n_atoms(self):
data = [(1, 'O', 'O'), (2, 'O', 'O2'), (1, b'H', b'OH'),
(2, 'H', 'H2O'), (2, 'O', 'H2O2'), (1, 'Ar', 'AR'),
(0, 'O', 'H'), (0, 'H', 'AR'), (0, 'Ar', 'HO2')]
for (n, elem, species) in data:
self.assertEqual(self.phase.n_atoms(species, elem), n)
mElem = self.phase.element_index(elem)
kSpec = self.phase.species_index(species)
self.assertEqual(self.phase.n_atoms(kSpec, mElem), n)
with self.assertRaisesRegex(ValueError, 'No such species'):
self.phase.n_atoms('C', 'H2')
with self.assertRaisesRegex(ValueError, 'No such element'):
self.phase.n_atoms('H', 'CH4')
def test_elemental_mass_fraction(self):
self.phase.Y = 'H2O:0.5, O2:0.5'
Zo = self.phase.elemental_mass_fraction('O')
Zh = self.phase.elemental_mass_fraction('H')
Zar = self.phase.elemental_mass_fraction('Ar')
mO = self.phase.element_index('O')
self.assertEqual(Zo, self.phase.elemental_mass_fraction(mO))
self.assertNear(Zo, 0.5 + 0.5 * (15.999 / 18.015))
self.assertNear(Zh, 0.5 * (2.016 / 18.015))
self.assertEqual(Zar, 0.0)
with self.assertRaisesRegex(ValueError, 'No such element'):
self.phase.elemental_mass_fraction('C')
with self.assertRaisesRegex(ValueError, 'No such element'):
self.phase.elemental_mass_fraction(5)
def test_elemental_mole_fraction(self):
self.phase.X = 'H2O:0.5, O2:0.5'
Zo = self.phase.elemental_mole_fraction('O')
Zh = self.phase.elemental_mole_fraction('H')
Zar = self.phase.elemental_mole_fraction('Ar')
mO = self.phase.element_index('O')
self.assertEqual(Zo, self.phase.elemental_mole_fraction(mO))
self.assertNear(Zo, (0.5 + 1) / (0.5*3 + 0.5*2))
self.assertNear(Zh, (2*0.5) / (0.5*3 + 0.5*2))
self.assertEqual(Zar, 0.0)
with self.assertRaisesRegex(ValueError, 'No such element'):
self.phase.elemental_mole_fraction('C')
with self.assertRaisesRegex(ValueError, 'No such element'):
self.phase.elemental_mole_fraction(5)
def test_elemental_mass_mole_fraction(self):
# expected relationship between elemental mass and mole fractions
comps = ['H2O:0.5, O2:0.5', 'H2:0.1, O2:0.4, H2O2:0.3, AR:0.2',
'O2:0.1, H2:0.9']
for comp in comps:
self.phase.X = comp
denom = sum(self.phase.elemental_mole_fraction(i)
* self.phase.atomic_weight(i)
for i in range(self.phase.n_elements))
for i in range(self.phase.n_elements):
self.assertNear(self.phase.elemental_mass_fraction(i),
self.phase.elemental_mole_fraction(i)
* self.phase.atomic_weight(i) / denom)
def test_weights(self):
atomic_weights = self.phase.atomic_weights
molecular_weights = self.phase.molecular_weights
self.assertEqual(self.phase.n_elements, len(atomic_weights))
self.assertEqual(self.phase.n_species, len(molecular_weights))
for i,mw in enumerate(molecular_weights):
test_weight = 0.0
for j,aw in enumerate(atomic_weights):
test_weight += aw * self.phase.n_atoms(i,j)
self.assertNear(test_weight, mw)
def test_charges(self):
gas = ct.Solution('ch4_ion.yaml')
charges = gas.charges
test = {'E': -1., 'N2': 0., 'H3O+': 1.}
for species, charge in test.items():
self.assertIn(species, gas.species_names)
index = gas.species_index(species)
self.assertEqual(charges[index], charge)
def test_setComposition(self):
X = np.zeros(self.phase.n_species)
X[2] = 1.0
self.phase.X = X
Y = self.phase.Y
self.assertEqual(list(X), list(Y))
def test_setComposition_singleton(self):
X = np.zeros((1,self.phase.n_species,1))
X[0,2,0] = 1.0
self.phase.X = X
Y = self.phase.Y
self.assertEqual(list(X[0,:,0]), list(Y))
def test_setCompositionString(self):
self.phase.X = 'h2:1.0, o2:1.0'
X = self.phase.X
self.assertNear(X[0], 0.5)
self.assertNear(X[3], 0.5)
with self.assertRaisesRegex(ct.CanteraError, 'Unknown species'):
self.phase.X = 'H2:1.0, CO2:1.5'
def test_setCompositionStringBad(self):
X0 = self.phase.X
with self.assertRaisesRegex(ct.CanteraError, 'Trouble processing'):
self.phase.X = 'H2:1.0, O2:asdf'
self.assertArrayNear(X0, self.phase.X)
with self.assertRaisesRegex(ct.CanteraError, 'Trouble processing'):
self.phase.X = 'H2:1e-x4'
self.assertArrayNear(X0, self.phase.X)
with self.assertRaisesRegex(ct.CanteraError, 'decimal point in exponent'):
self.phase.X = 'H2:1e-1.4'
self.assertArrayNear(X0, self.phase.X)
with self.assertRaisesRegex(ct.CanteraError, 'Duplicate key'):
self.phase.X = 'H2:0.5, O2:1.0, H2:0.1'
self.assertArrayNear(X0, self.phase.X)
def test_setCompositionDict(self):
self.phase.X = {b'H2':1.0, b'O2':3.0}
X = self.phase.X
self.assertNear(X[0], 0.25)
self.assertNear(X[3], 0.75)
self.phase.Y = {'H2':1.0, 'O2':3.0}
Y = self.phase.Y
self.assertNear(Y[0], 0.25)
self.assertNear(Y[3], 0.75)
def test_getCompositionDict(self):
self.phase.X = 'oh:1e-9, O2:0.4, AR:0.6'
self.assertEqual(len(self.phase.mole_fraction_dict(1e-7)), 2)
self.assertEqual(len(self.phase.mole_fraction_dict()), 3)
self.phase.Y = 'O2:0.4, AR:0.6'
Y1 = self.phase.mass_fraction_dict()
self.assertNear(Y1['O2'], 0.4)
self.assertNear(Y1['AR'], 0.6)
def test_setCompositionNoNorm(self):
X = np.zeros(self.phase.n_species)
X[2] = 1.0
X[0] = 0.01
self.phase.set_unnormalized_mole_fractions(X)
self.assertArrayNear(self.phase.X, X)
self.assertNear(sum(X), 1.01)
Y = np.zeros(self.phase.n_species)
Y[2] = 1.0
Y[0] = 0.01
self.phase.set_unnormalized_mass_fractions(Y)
self.assertArrayNear(self.phase.Y, Y)
self.assertNear(sum(Y), 1.01)
def test_setCompositionNoNormBad(self):
X = np.zeros(self.phase.n_species - 1)
with self.assertRaisesRegex(ValueError, 'incorrect length'):
self.phase.set_unnormalized_mole_fractions(X)
with self.assertRaisesRegex(ValueError, 'incorrect length'):
self.phase.set_unnormalized_mass_fractions([1,2,3])
def test_setCompositionDict_bad1(self):
with self.assertRaisesRegex(ct.CanteraError, 'Unknown species'):
self.phase.X = {'H2':1.0, 'HCl':3.0}
def test_setCompositionDict_bad2(self):
with self.assertRaises(TypeError):
self.phase.Y = {'H2':1.0, 'O2':'xx'}
def test_setCompositionSlice(self):
self.phase['h2', 'o2'].X = 0.1, 0.9
X = self.phase.X
self.assertNear(X[0], 0.1)
self.assertNear(X[3], 0.9)
def test_setCompositionSingleSpecies(self):
yaml_def = """
phases:
- name: gas
species: [{h2o2.yaml/species: [AR]}]
thermo: ideal-gas
"""
gas = ct.Solution(yaml=yaml_def)
gas.X = [1]
gas.Y = np.array([[1.001]])
self.assertEqual(gas.Y[0], 1.0)
def test_setCompositionSlice_bad(self):
X0 = self.phase.X
with self.assertRaisesRegex(ValueError, 'incorrect length'):
self.phase['H2','O2'].Y = [0.1, 0.2, 0.3]
self.assertArrayNear(self.phase.X, X0)
def test_setCompositionEmpty_bad(self):
X0 = self.phase.X
with self.assertRaisesRegex(ValueError, 'incorrect length'):
self.phase.Y = np.array([])
self.assertArrayNear(self.phase.X, X0)
@utilities.slow_test
def test_set_equivalence_ratio_stoichiometric(self):
gas = ct.Solution('gri30.yaml', transport_model=None)
for fuel in ('C2H6', 'H2:0.7, CO:0.3', 'NH3:0.4, CH3OH:0.6'):
for oxidizer in ('O2:1.0, N2:3.76', 'H2O2:1.0'):
gas.set_equivalence_ratio(1.0, fuel, oxidizer)
gas.equilibrate('TP')
# Almost everything should end up as CO2, H2O and N2
self.assertGreater(sum(gas['H2O','CO2','N2'].X), 0.999999)
def test_set_equivalence_ratio_lean(self):
gas = ct.Solution('gri30.yaml', transport_model=None)
excess = 0
for phi in np.linspace(0.9, 0, 5):
gas.set_equivalence_ratio(phi, 'CH4:0.8, CH3OH:0.2', 'O2:1.0, N2:3.76')
gas.equilibrate('TP')
self.assertGreater(gas['O2'].X[0], excess)
excess = gas['O2'].X[0]
self.assertNear(sum(gas['O2','N2'].X), 1.0)
def test_set_equivalence_ratio_sulfur(self):
sulfur_species = [k for k in ct.Species.list_from_file("nasa_gas.yaml")
if k.name in ("SO", "SO2")]
gas = ct.Solution(thermo="ideal-gas",
species=ct.Species.list_from_file("gri30.yaml") + sulfur_species)
fuel = "CH3:0.5, SO:0.25, OH:0.125, N2:0.125"
ox = "O2:0.5, SO2:0.25, CO2:0.125, CH:0.125"
def test_sulfur_results(gas, fuel, ox, basis):
gas.set_equivalence_ratio(2.0, fuel, ox, basis)
Z = gas.mixture_fraction(fuel, ox, basis)
self.assertNear(gas.stoich_air_fuel_ratio(fuel, ox, basis)/((1.0-Z)/Z), 2.0)
gas.set_mixture_fraction(Z, fuel, ox, basis)
self.assertNear(gas['SO2'].X[0], 31.0/212.0)
self.assertNear(gas['O2'].X[0], 31.0/106.0)
self.assertNear(gas['SO'].X[0], 11.0/106.0)
self.assertNear(gas['CO2'].X[0], 31.0/424.0)
self.assertNear(gas['CH3'].X[0], 11.0/53.0)
self.assertNear(gas['N2'].X[0], 11.0/212.0)
self.assertNear(gas['CH'].X[0], 31.0/424.0)
self.assertNear(gas['OH'].X[0], 11.0/212.0)
self.assertNear(gas.equivalence_ratio(fuel, ox, basis), 2.0)
test_sulfur_results(gas, fuel, ox, 'mole')
gas.TPX = None, None, fuel
fuel = gas.Y
gas.TPX = None, None, ox
ox = gas.Y
test_sulfur_results(gas, fuel, ox, 'mass')
def test_equivalence_ratio(self):
gas = ct.Solution('gri30.yaml', transport_model=None)
for phi in np.linspace(0.5, 2.0, 5):
gas.set_equivalence_ratio(phi, 'CH4:0.8, CH3OH:0.2', 'O2:1.0, N2:3.76')
self.assertNear(phi, gas.equivalence_ratio('CH4:0.8, CH3OH:0.2', 'O2:1.0, N2:3.76'))
# Check sulfur species
sulfur_species = [k for k in ct.Species.list_from_file("nasa_gas.yaml")
if k.name in ("SO", "SO2")]
gas = ct.Solution(thermo="ideal-gas", kinetics="gas",
species=ct.Species.list_from_file("gri30.yaml") + sulfur_species)
for phi in np.linspace(0.5, 2.0, 5):
gas.set_equivalence_ratio(phi, 'CH3:0.5, SO:0.25, OH:0.125, N2:0.125', 'O2:0.5, SO2:0.25, CO2:0.125')
self.assertNear(phi, gas.equivalence_ratio('CH3:0.5, SO:0.25, OH:0.125, N2:0.125', 'O2:0.5, SO2:0.25, CO2:0.125'))
gas.X = 'CH4:1' # pure fuel
self.assertEqual(gas.equivalence_ratio(), np.inf)
def test_get_set_equivalence_ratio_functions(self):
fuel = "CH4:0.2,O2:0.02,N2:0.1,CO:0.05,CO2:0.02"
ox = "O2:0.21,N2:0.79,CO:0.04,CH4:0.01,CO2:0.03"
gas = ct.Solution('gri30.yaml', transport_model=None)
gas.TPX = 300, 1e5, fuel
Y_Cf = gas.elemental_mass_fraction("C")
Y_Of = gas.elemental_mass_fraction("O")
gas.TPX = 300, 1e5, ox
Y_Co = gas.elemental_mass_fraction("C")
Y_Oo = gas.elemental_mass_fraction("O")
def test_equil_results(gas, fuel, ox, Y_Cf, Y_Of, Y_Co, Y_Oo, basis):
gas.TP = 300, 1e5
gas.set_equivalence_ratio(1.3, fuel, ox, basis)
T = gas.T
# set mixture to burnt state to make sure that equivalence ratio and
# mixture fraction are independent of reaction progress
gas.equilibrate("HP")
phi = gas.equivalence_ratio(fuel, ox, basis)
phi_loc = gas.equivalence_ratio()
mf = gas.mixture_fraction(fuel, ox, basis)
mf_C = gas.mixture_fraction(fuel, ox, basis, element="C")
mf_O = gas.mixture_fraction(fuel, ox, basis, element="O")
l = gas.stoich_air_fuel_ratio(fuel, ox, basis)
gas.set_mixture_fraction(mf, fuel,ox, basis)
phi2 = gas.equivalence_ratio(fuel, ox, basis)
self.assertNear(phi, 1.3)
self.assertNear(phi2, 1.3)
self.assertNear(phi_loc, 1.1726068608195617)
self.assertNear(mf, 0.13415725911057605)
self.assertNear(mf_C, (gas.elemental_mass_fraction("C")-Y_Co)/(Y_Cf-Y_Co))
self.assertNear(mf_O, (gas.elemental_mass_fraction("O")-Y_Oo)/(Y_Of-Y_Oo))
self.assertNear(l, 8.3901204498353561)
self.assertNear(gas.P, 1e5)
self.assertNear(T, 300.0)
test_equil_results(gas, fuel, ox, Y_Cf, Y_Of, Y_Co, Y_Oo, 'mole')
# do the same for mass-based functions
gas.TPX = None, None, fuel
fuel = gas.Y
Y_Cf = gas.elemental_mass_fraction("C")
Y_Of = gas.elemental_mass_fraction("O")
gas.TPX = None, None, ox
ox = gas.Y
Y_Co = gas.elemental_mass_fraction("C")
Y_Oo = gas.elemental_mass_fraction("O")
test_equil_results(gas, fuel, ox, Y_Cf, Y_Of, Y_Co, Y_Oo, 'mass')
def test_equivalence_ratio_simple_dilution(self):
gas = ct.Solution("gri30.yaml")
phi = 2
inv_afr = 2 * phi # inverse molar AFR for H2/O2
X = "H2:4,O2:1,CO:3,CO2:4,N2:5,CH4:6"
T, P = 300, 1e5
gas.TPX = T, P, X
original_X = gas.X
self.assertNear(gas.equivalence_ratio(include_species=["H2", "O2"]), 2)
self.assertNear(gas.equivalence_ratio("H2", "O2",
include_species=["H2", "O2"]), 2)
self.assertNear(gas.T, T)
self.assertNear(gas.P, P)
self.assertArrayNear(gas.X, original_X)
def test_simple_dilution(fraction, basis):
if isinstance(fraction, str):
fraction_dict = {fraction[:fraction.find(":")]:
float(fraction[fraction.find(":")+1:])}
else:
fraction_dict = fraction
fraction_type = list(fraction_dict.keys())[0]
fraction_value = float(list(fraction_dict.values())[0])
M_H2 = gas.molecular_weights[gas.species_index("H2")]
M_O2 = gas.molecular_weights[gas.species_index("O2")]
gas.TP = T, P
gas.set_equivalence_ratio(phi, "H2", "O2", fraction=fraction,
diluent="CO2", basis=basis)
if basis == "mole" and fraction_type == "diluent":
self.assertNear(gas["H2"].X[0], (1 - fraction_value)
* inv_afr / (inv_afr + 1))
self.assertNear(gas["O2"].X[0], (1 - fraction_value) / (inv_afr + 1))
self.assertNear(gas["CO2"].X[0], fraction_value)
elif basis == "mass" and fraction_type == "diluent":
self.assertNear(gas["H2"].Y[0] / gas["O2"].Y[0], inv_afr * M_H2 / M_O2)
self.assertNear(gas["CO2"].Y[0], fraction_value)
elif basis == "mole" and fraction_type == "fuel":
self.assertNear(gas["H2"].X[0], fraction_value)
self.assertNear(gas["O2"].X[0], fraction_value / inv_afr)
self.assertNear(gas["CO2"].X[0], 1 - fraction_value * (1 + 1 / inv_afr))
elif basis == "mass" and fraction_type == "fuel":
self.assertNear(gas["H2"].Y[0], fraction_value)
self.assertNear(gas["H2"].Y[0] / gas["O2"].Y[0], inv_afr * M_H2 / M_O2)
elif basis == "mole" and fraction_type == "oxidizer":
self.assertNear(gas["H2"].X[0], fraction_value * inv_afr)
self.assertNear(gas["O2"].X[0], fraction_value)
self.assertNear(gas["CO2"].X[0], 1 - fraction_value * (1 + inv_afr))
elif basis == "mass" and fraction_type == "oxidizer":
self.assertNear(gas["O2"].Y[0], fraction_value)
self.assertNear(gas["H2"].Y[0] / gas["O2"].Y[0], inv_afr * M_H2 / M_O2)
Y = gas.Y
self.assertNear(gas.equivalence_ratio("H2", "O2",
include_species=["H2", "O2"],
basis=basis), phi)
self.assertNear(gas.equivalence_ratio(include_species=["H2", "O2"],
basis=basis), phi)
self.assertArrayNear(Y, gas.Y)
self.assertNear(gas.T, T)
self.assertNear(gas.P, P)
# brute force all possible input combinations
test_simple_dilution("diluent:0.3", "mole")
test_simple_dilution({"diluent": 0.3}, "mole")
test_simple_dilution("diluent:0.3", "mass")
test_simple_dilution({"diluent": 0.3}, "mass")
test_simple_dilution("fuel:0.1", "mole")
test_simple_dilution({"fuel": 0.1}, "mole")
test_simple_dilution("fuel:0.1", "mass")
test_simple_dilution({"fuel": 0.1}, "mass")
test_simple_dilution("oxidizer:0.1", "mole")
test_simple_dilution({"oxidizer": 0.1}, "mole")
test_simple_dilution("oxidizer:0.1", "mass")
test_simple_dilution({"oxidizer": 0.1}, "mass")
def test_equivalence_ratio_arbitrary_dilution(self):
fuel = "CH4:1,O2:0.01,N2:0.1,CO:0.05,CO2:0.02"
oxidizer = "O2:0.8,N2:0.2,CO:0.01,CH4:0.005,CO2:0.03"
diluent = "CO2:1,CO:0.025,N2:0.3,CH4:0.07,O2:0.09"
gas = ct.Solution("gri30.yaml")
gas.X = fuel
X_fuel = gas.X
gas.Y = fuel
Y_fuel = gas.Y
gas.X = oxidizer
X_oxidizer = gas.X
gas.Y = oxidizer
Y_oxidizer = gas.Y
gas.X = diluent
X_diluent = gas.X
gas.Y = diluent
Y_diluent = gas.Y
phi = 2
fraction = 0.6
gas.set_equivalence_ratio(phi, fuel, oxidizer)
X_Mix = gas.X
gas.set_equivalence_ratio(phi, fuel, oxidizer, fraction={"diluent": fraction},
diluent=diluent)
X_expected = X_Mix * (1 - fraction) + fraction * X_diluent
self.assertArrayNear(gas.X, X_expected)
gas.set_equivalence_ratio(phi, fuel, oxidizer, basis="mass")
Y_Mix = gas.Y
gas.set_equivalence_ratio(phi, fuel, oxidizer, fraction={"diluent": fraction},
diluent=diluent, basis="mass")
Y_expected = Y_Mix * (1 - fraction) + fraction * Y_diluent
self.assertArrayNear(gas.Y, Y_expected)
phi = 0.8
fraction = 0.05
gas.set_equivalence_ratio(phi, fuel, oxidizer, basis="mass")
AFR = gas.stoich_air_fuel_ratio(fuel, oxidizer, basis="mass") / phi
gas.set_equivalence_ratio(phi, fuel, oxidizer, fraction={"fuel": fraction},
diluent=diluent, basis="mass")
Y_expected = fraction * (Y_fuel + AFR * Y_oxidizer) \
+ (1 - fraction * (1 + AFR)) * Y_diluent
self.assertArrayNear(gas.Y, Y_expected)
gas.set_equivalence_ratio(phi, fuel, oxidizer, fraction={"oxidizer": fraction},
diluent=diluent, basis="mass")
Y_expected = fraction * (Y_fuel / AFR + Y_oxidizer) \
+ (1 - fraction * (1 + 1 / AFR)) * Y_diluent
self.assertArrayNear(gas.Y, Y_expected)
gas.X = fuel
M_fuel = gas.mean_molecular_weight
gas.X = oxidizer
M_oxidizer = gas.mean_molecular_weight
gas.set_equivalence_ratio(phi, fuel, oxidizer)
AFR = M_fuel / M_oxidizer * gas.stoich_air_fuel_ratio(fuel, oxidizer) / phi
gas.set_equivalence_ratio(phi, fuel, oxidizer, fraction={"fuel": fraction},
diluent=diluent)
X_expected = fraction * (X_fuel + AFR * X_oxidizer) \
+ (1 - fraction * (1 + AFR)) * X_diluent
self.assertArrayNear(gas.X, X_expected)
gas.set_equivalence_ratio(phi, fuel, oxidizer, fraction={"oxidizer": fraction},
diluent=diluent)
X_expected = fraction * (X_fuel / AFR + X_oxidizer) \
+ (1 - fraction * (1 + 1 / AFR)) * X_diluent
self.assertArrayNear(gas.X, X_expected)
def test_full_report(self):
report = self.phase.report(threshold=0.0)
self.assertIn(self.phase.name, report)
self.assertIn('temperature', report)
self.assertNotIn('minor', report)
for name in self.phase.species_names:
self.assertIn(name, report)
def test_default_report(self):
self.phase.X = 'H2:0.1, O2:0.9, HO2:1e-10, H2O2:1e-20'
report = self.phase.report()
self.assertIn('minor', report)
for name in (' H2 ', ' O2 ', ' HO2 '):
self.assertIn(name, report)
for name in (' H2O2 ', ' OH ', ' AR '):
self.assertNotIn(name, report)
def test_name(self):
self.phase.name = 'something'
self.assertEqual(self.phase.name, 'something')
self.assertIn('something', self.phase.report())
def test_badLength(self):
X = np.zeros(5)
with self.assertRaisesRegex(ValueError, 'incorrect length'):
self.phase.X = X
with self.assertRaisesRegex(ValueError, 'incorrect length'):
self.phase.Y = X
def test_mass_basis(self):
self.assertEqual(self.phase.basis, 'mass')
self.assertNear(self.phase.density_mass, self.phase.density)
self.assertNear(self.phase.enthalpy_mass, self.phase.h)
self.assertNear(self.phase.entropy_mass, self.phase.s)
self.assertNear(self.phase.int_energy_mass, self.phase.u)
self.assertNear(self.phase.volume_mass, self.phase.v)
self.assertNear(self.phase.cv_mass, self.phase.cv)
self.assertNear(self.phase.cp_mass, self.phase.cp)
def test_molar_basis(self):
self.phase.basis = 'molar'
self.assertEqual(self.phase.basis, 'molar')
self.assertNear(self.phase.density_mole, self.phase.density)
self.assertNear(self.phase.enthalpy_mole, self.phase.h)
self.assertNear(self.phase.entropy_mole, self.phase.s)
self.assertNear(self.phase.int_energy_mole, self.phase.u)
self.assertNear(self.phase.volume_mole, self.phase.v)
self.assertNear(self.phase.cv_mole, self.phase.cv)
self.assertNear(self.phase.cp_mole, self.phase.cp)
def check_setters(self, T1, rho1, Y1):
T0, rho0, Y0 = self.phase.TDY
self.phase.TDY = T1, rho1, Y1
X1 = self.phase.X
P1 = self.phase.P
h1 = self.phase.h
s1 = self.phase.s
u1 = self.phase.u
v1 = self.phase.v
def check_state(T, rho, Y):
self.assertNear(self.phase.T, T)
self.assertNear(self.phase.Te, T)
self.assertNear(self.phase.density, rho)
self.assertArrayNear(self.phase.Y, Y)
self.phase.TDY = T0, rho0, Y0
self.phase.TPY = T1, P1, Y1
check_state(T1, rho1, Y1)
self.phase.TDY = T0, rho0, Y0
self.phase.UVY = u1, v1, Y1
check_state(T1, rho1, Y1)
self.phase.TDY = T0, rho0, Y0
self.phase.HPY = h1, P1, Y1
check_state(T1, rho1, Y1)
self.phase.TDY = T0, rho0, Y0
self.phase.SPY = s1, P1, Y1
check_state(T1, rho1, Y1)
self.phase.TDY = T0, rho0, Y0
self.phase.TPX = T1, P1, X1
check_state(T1, rho1, Y1)
self.phase.TDY = T0, rho0, Y0
self.phase.UVX = u1, v1, X1
check_state(T1, rho1, Y1)
self.phase.TDY = T0, rho0, Y0
self.phase.HPX = h1, P1, X1
check_state(T1, rho1, Y1)
self.phase.TDY = T0, rho0, Y0
self.phase.SPX = s1, P1, X1
check_state(T1, rho1, Y1)
self.phase.TDY = T0, rho0, Y0
self.phase.SVX = s1, v1, X1
check_state(T1, rho1, Y1)
self.phase.TDY = T0, rho0, Y0
self.phase.SVY = s1, v1, Y1
check_state(T1, rho1, Y1)
self.phase.TDY = T0, rho0, Y0
self.phase.DPX = rho1, P1, X1
check_state(T1, rho1, Y1)
self.phase.TDY = T0, rho0, Y0
self.phase.DPY = rho1, P1, Y1
check_state(T1, rho1, Y1)
def test_setState_mass(self):
self.check_setters(T1 = 500.0, rho1 = 1.5,
Y1 = [0.1, 0.0, 0.0, 0.1, 0.4, 0.2, 0.0, 0.0, 0.2, 0.0])
def test_setState_mole(self):
self.phase.basis = 'molar'
self.check_setters(T1 = 750.0, rho1 = 0.02,
Y1 = [0.2, 0.1, 0.0, 0.3, 0.1, 0.0, 0.0, 0.2, 0.1, 0.0])
def test_setters_hold_constant(self):
props = ('T','P','s','h','u','v','X','Y')
pairs = [('TP', 'T', 'P'), ('SP', 's', 'P'),
('UV', 'u', 'v')]
self.phase.TDX = 1000, 1.5, 'H2O:0.1, O2:0.95, AR:3.0'
values = {}
for p in props:
values[p] = getattr(self.phase, p)
for pair, first, second in pairs:
self.phase.TDX = 500, 2.5, 'H2:0.1, O2:1.0, AR:3.0'
first_val = getattr(self.phase, first)
second_val = getattr(self.phase, second)
setattr(self.phase, pair, (values[first], None))
self.assertNear(getattr(self.phase, first), values[first])
self.assertNear(getattr(self.phase, second), second_val)
self.phase.TDX = 500, 2.5, 'H2:0.1, O2:1.0, AR:3.0'
setattr(self.phase, pair, (None, values[second]))
self.assertNear(getattr(self.phase, first), first_val)
self.assertNear(getattr(self.phase, second), values[second])
self.phase.TDX = 500, 2.5, 'H2:0.1, O2:1.0, AR:3.0'
setattr(self.phase, pair + 'X', (None, None, values['X']))
self.assertNear(getattr(self.phase, first), first_val)
self.assertNear(getattr(self.phase, second), second_val)
self.phase.TDX = 500, 2.5, 'H2:0.1, O2:1.0, AR:3.0'
setattr(self.phase, pair + 'Y', (None, None, values['Y']))
self.assertNear(getattr(self.phase, first), first_val)
self.assertNear(getattr(self.phase, second), second_val)
def test_setter_errors(self):
with self.assertRaises(TypeError):
self.phase.TD = 400
with self.assertRaisesRegex(AssertionError, 'incorrect number'):
self.phase.TP = 300, 101325, 'CH4:1.0'
with self.assertRaisesRegex(AssertionError, 'incorrect number'):
self.phase.HPY = 1.2e6, 101325
with self.assertRaisesRegex(AssertionError, 'incorrect number'):
self.phase.UVX = -4e5, 4.4, 'H2:1.0', -1
def test_invalid_property(self):
x = self.phase
with self.assertRaises(AttributeError):
x.foobar = 300
with self.assertRaises(AttributeError):
x.foobar
def check_getters(self):
T,D,X = self.phase.TDX
self.assertNear(T, self.phase.T)
self.assertNear(D, self.phase.density)
self.assertArrayNear(X, self.phase.X)
T,D,Y = self.phase.TDY
self.assertNear(T, self.phase.T)
self.assertNear(D, self.phase.density)
self.assertArrayNear(Y, self.phase.Y)
T,D = self.phase.TD
self.assertNear(T, self.phase.T)
self.assertNear(D, self.phase.density)
T,P,X = self.phase.TPX
self.assertNear(T, self.phase.T)
self.assertNear(P, self.phase.P)
self.assertArrayNear(X, self.phase.X)
T,P,Y = self.phase.TPY
self.assertNear(T, self.phase.T)
self.assertNear(P, self.phase.P)
self.assertArrayNear(Y, self.phase.Y)
T,P = self.phase.TP
self.assertNear(T, self.phase.T)
self.assertNear(P, self.phase.P)
H,P,X = self.phase.HPX
self.assertNear(H, self.phase.h)
self.assertNear(P, self.phase.P)
self.assertArrayNear(X, self.phase.X)
H,P,Y = self.phase.HPY
self.assertNear(H, self.phase.h)
self.assertNear(P, self.phase.P)
self.assertArrayNear(Y, self.phase.Y)
H,P = self.phase.HP
self.assertNear(H, self.phase.h)
self.assertNear(P, self.phase.P)
U,V,X = self.phase.UVX
self.assertNear(U, self.phase.u)
self.assertNear(V, self.phase.v)
self.assertArrayNear(X, self.phase.X)
U,V,Y = self.phase.UVY
self.assertNear(U, self.phase.u)
self.assertNear(V, self.phase.v)
self.assertArrayNear(Y, self.phase.Y)
U,V = self.phase.UV
self.assertNear(U, self.phase.u)
self.assertNear(V, self.phase.v)
S,P,X = self.phase.SPX
self.assertNear(S, self.phase.s)
self.assertNear(P, self.phase.P)
self.assertArrayNear(X, self.phase.X)
S,P,Y = self.phase.SPY
self.assertNear(S, self.phase.s)
self.assertNear(P, self.phase.P)
self.assertArrayNear(Y, self.phase.Y)
S,P = self.phase.SP
self.assertNear(S, self.phase.s)
self.assertNear(P, self.phase.P)
S,V,X = self.phase.SVX
self.assertNear(S, self.phase.s)
self.assertNear(V, self.phase.v)
self.assertArrayNear(X, self.phase.X)
S,V,Y = self.phase.SVY
self.assertNear(S, self.phase.s)
self.assertNear(V, self.phase.v)
self.assertArrayNear(Y, self.phase.Y)
S,V = self.phase.SV
self.assertNear(S, self.phase.s)
self.assertNear(V, self.phase.v)
D,P,X = self.phase.DPX
self.assertNear(D, self.phase.density)
self.assertNear(P, self.phase.P)
self.assertArrayNear(X, self.phase.X)
D,P,Y = self.phase.DPY
self.assertNear(D, self.phase.density)
self.assertNear(P, self.phase.P)
self.assertArrayNear(Y, self.phase.Y)
D,P = self.phase.DP
self.assertNear(D, self.phase.density)
self.assertNear(P, self.phase.P)
Te = self.phase.Te
self.assertNear(Te, self.phase.Te)
def test_getState_mass(self):
self.phase.TDY = 350.0, 0.7, 'H2:0.1, H2O2:0.1, AR:0.8'
self.check_getters()
def test_getState_mole(self):
self.phase.basis = 'molar'
self.phase.TDX = 350.0, 0.01, 'H2:0.1, O2:0.3, AR:0.6'
self.check_getters()
def test_getState(self):
self.assertNear(self.phase.P, ct.one_atm)
self.assertNear(self.phase.T, 300)
def test_partial_molar(self):
self.phase.TDY = 350.0, 0.6, 'H2:0.1, H2O2:0.1, AR:0.8'
self.assertNear(sum(self.phase.partial_molar_enthalpies * self.phase.X),
self.phase.enthalpy_mole)
self.assertNear(sum(self.phase.partial_molar_entropies * self.phase.X),
self.phase.entropy_mole)
self.assertNear(sum(self.phase.partial_molar_int_energies * self.phase.X),
self.phase.int_energy_mole)
self.assertNear(sum(self.phase.chemical_potentials * self.phase.X),
self.phase.gibbs_mole)
self.assertNear(sum(self.phase.partial_molar_cp * self.phase.X),
self.phase.cp_mole)
def test_nondimensional(self):
self.phase.TDY = 850.0, 0.2, 'H2:0.1, H2O:0.6, AR:0.3'
H = (sum(self.phase.standard_enthalpies_RT * self.phase.X) *
ct.gas_constant * self.phase.T)
self.assertNear(H, self.phase.enthalpy_mole)
U = (sum(self.phase.standard_int_energies_RT * self.phase.X) *
ct.gas_constant * self.phase.T)
self.assertNear(U, self.phase.int_energy_mole)
cp = sum(self.phase.standard_cp_R * self.phase.X) * ct.gas_constant
self.assertNear(cp, self.phase.cp_mole)
def test_activities(self):
self.phase.TDY = 850.0, 0.2, 'H2:0.1, H2O:0.6, AR:0.3'
self.assertArrayNear(self.phase.X, self.phase.activities)
self.assertArrayNear(self.phase.activity_coefficients,
np.ones(self.phase.n_species))
def test_isothermal_compressibility(self):
self.assertNear(self.phase.isothermal_compressibility, 1.0/self.phase.P)
def test_thermal_expansion_coeff(self):
self.assertNear(self.phase.thermal_expansion_coeff, 1.0/self.phase.T)
def test_ref_info(self):
self.assertNear(self.phase.reference_pressure, ct.one_atm)
self.assertNear(self.phase.min_temp, 300.0)
self.assertNear(self.phase.max_temp, 3500.0)
def test_uncopyable(self):
import copy
with self.assertRaises(NotImplementedError):
copy.copy(self.phase)
def test_add_species(self):
ref = ct.Solution('gri30.yaml', transport_model=None)
n_orig = self.phase.n_species
self.phase.add_species(ref.species('CO2'))
self.phase.add_species(ref.species('CO'))
self.assertEqual(self.phase.n_species, n_orig + 2)
self.assertIn('CO2', self.phase.species_names)
self.assertIn('CO', self.phase.species_names)
state = 400, 2e5, 'H2:0.7, CO2:0.2, CO:0.1'
ref.TPY = state
self.phase.TPY = state
self.assertNear(self.phase.enthalpy_mass, ref.enthalpy_mass)
self.assertNear(self.phase.entropy_mole, ref.entropy_mole)
self.assertArrayNear(ref[self.phase.species_names].partial_molar_cp,
self.phase.partial_molar_cp)
def test_add_species_disabled(self):
ref = ct.Solution('gri30.yaml', transport_model=None)
self.phase.transport_model = "unity-Lewis-number"
reactor = ct.IdealGasReactor(self.phase)
with self.assertRaisesRegex(ct.CanteraError, 'Cannot add species'):
self.phase.add_species(ref.species('CH4'))
del reactor
gc.collect()
self.phase.add_species(ref.species('CH4'))
flame = ct.FreeFlame(self.phase, width=0.1)
with self.assertRaisesRegex(ct.CanteraError, 'Cannot add species'):
self.phase.add_species(ref.species('CO'))
del flame
gc.collect()
self.phase.add_species(ref.species('CO'))
mix = ct.Mixture([(self.phase, 2.0)])
with self.assertRaisesRegex(ct.CanteraError, 'Cannot add species'):
self.phase.add_species(ref.species('CH2O'))
del mix
gc.collect()
self.phase.add_species(ref.species('CH2O'))
def test_add_species_duplicate(self):
species = self.phase.species('H2O2')
with self.assertRaisesRegex(ct.CanteraError, 'already contains'):
self.phase.add_species(species)
def test_auxiliary_data(self):
"""
This test checks that the values of `aAlpha_mix` and `b_mix` should, when
used in the Peng-Robinson equation of state expression, yield the same
pressure as the pressure that was set for the thermodynamic state.
"""
gas = ct.Solution('co2_PR_example.yaml', 'CO2-PR')
gas.TPX = 300, 101325, 'H2:1.0'
params = gas.auxiliary_data
# Get the Peng-Robinson equation of state parameters
aAlpha_mix = params['aAlpha_mix']
b_mix = params['b_mix']
molar_volume = 1.0 / gas.density_mole
# Evaluate the pressure using the Peng-Robinson equation of state
# parameters
evaluated_pressure = (
ct.gas_constant * gas.T / (molar_volume - b_mix) -
aAlpha_mix / (molar_volume ** 2 + 2 * b_mix * molar_volume - b_mix ** 2)
)
assert abs(gas.P - evaluated_pressure) < 1e-6
class TestThermo(utilities.CanteraTest):
def setUp(self):
self.gas = ct.ThermoPhase("h2o2.yaml")
self.gas.TPX = 450, 2e5, 'H2:1.0, O2:0.4, AR:3, H2O:0.1'
def test_setSV_lowT(self):
"""
Set state in terms of (s,v) when the end temperature is below the
phase's nominal temperature limit.
"""
self.gas.TPX = 450, 1e5, 'H2:1.0, O2:0.4, AR:3'
s1, v1 = self.gas.SV
self.gas.SV = s1, 3 * v1
self.assertNear(self.gas.s, s1)
self.assertNear(self.gas.v, 3 * v1)
self.assertTrue(self.gas.T < self.gas.min_temp)
def test_setSV_low_invalid(self):
self.gas.TPX = 450, 1e5, 'H2:1.0, O2:0.4, AR:3'
self.gas.SV = 4600, None
with self.assertRaises(ct.CanteraError):
self.gas.SV = -1000, None
def test_setSV_highT(self):
"""
Set state in terms of (s,v) when the end temperature is above the
phase's nominal temperature limit.
"""
self.gas.TPX = 2900, 1e5, 'H2:1.0, O2:0.4, AR:3'
s1, v1 = self.gas.SV
self.gas.SV = s1, 0.3 * v1
self.assertNear(self.gas.s, s1)
self.assertNear(self.gas.v, 0.3 * v1)
self.assertTrue(self.gas.T > self.gas.max_temp)
def test_setHP_lowT(self):
"""
Set state in terms of (s,v) when the end temperature is below the
phase's nominal temperature limit.
"""
self.gas.TPX = 450, 1e5, 'H2:1.0, O2:0.4, AR:3'
deltaH = 1.25e5
h1, p1 = self.gas.HP
self.gas.HP = h1 - deltaH, None
self.assertNear(self.gas.h, h1 - deltaH)
self.assertNear(self.gas.P, p1)
self.assertTrue(self.gas.T < self.gas.min_temp)
def test_setHP_low_invalid(self):
"""
Set state in terms of (h,p) when the enthalpy would imply a negative
temperature
"""
self.gas.TPX = 300, 101325, 'H2:1.0'
with self.assertRaises(ct.CanteraError):
self.gas.HP = -4e6, 101325
def test_setHP_highT(self):
"""
Set state in terms of (s,v) when the end temperature is above the
phase's nominal temperature limit.
"""
self.gas.TPX = 2800, 1e5, 'H2:1.0, O2:0.4, AR:3'
deltaH = 8.25e5
h1, p1 = self.gas.HP
self.gas.HP = h1 + deltaH, None
self.assertNear(self.gas.h, h1 + deltaH)
self.assertNear(self.gas.P, p1)
self.assertTrue(self.gas.T > self.gas.max_temp)
def test_volume(self):
"""
This phase should follow the ideal gas law
"""
g = self.gas
self.assertNear(g.P, g.density_mole * ct.gas_constant * g.T)
self.assertNear(
g.P / g.density,
ct.gas_constant / g.mean_molecular_weight * g.T)
self.assertNear(g.density, 1.0 / g.volume_mass)
def test_energy(self):
g = self.gas
mmw = g.mean_molecular_weight
self.assertNear(g.enthalpy_mass, g.enthalpy_mole / mmw)
self.assertNear(g.int_energy_mass, g.int_energy_mole / mmw)
self.assertNear(g.gibbs_mass, g.gibbs_mole / mmw)
self.assertNear(g.entropy_mass, g.entropy_mole / mmw)
self.assertNear(g.cv_mass, g.cv_mole / mmw)
self.assertNear(g.cp_mass, g.cp_mole / mmw)
self.assertNear(g.cv_mole + ct.gas_constant, g.cp_mole)
def test_nondimensional(self):
g = self.gas
R = ct.gas_constant
self.assertNear(np.dot(g.standard_cp_R, g.X), g.cp_mole / R)
self.assertNear(np.dot(g.standard_enthalpies_RT, g.X),
g.enthalpy_mole / (R*g.T))
Smix_R = - np.dot(g.X, np.log(g.X+1e-20))
self.assertNear(np.dot(g.standard_entropies_R, g.X) + Smix_R,
g.entropy_mole / R)
self.assertNear(np.dot(g.standard_gibbs_RT, g.X) - Smix_R,
g.gibbs_mole / (R*g.T))
class TestInterfacePhase(utilities.CanteraTest):
def setUp(self):
self.gas = ct.Solution("diamond.yaml", "gas")
self.solid = ct.Solution("diamond.yaml", "diamond")
self.interface = ct.Interface("diamond.yaml", "diamond_100",
(self.gas, self.solid))
def test_properties(self):
self.interface.site_density = 100
self.assertNear(self.interface.site_density, 100)
def test_coverages_array(self):
C = np.zeros(self.interface.n_species)
C[1] = 0.25
C[3] = 0.125
C[4] = 0.125
self.interface.coverages = C
C = self.interface.coverages
# should now be normalized
self.assertNear(C[1], 0.5)
self.assertNear(C[3], 0.25)
self.assertNear(C[4], 0.25)
self.assertNear(sum(C), 1.0)
def test_mole_fractions(self):
self.interface.X = 'c6HM:0.3, c6H*:0.7'
self.assertNear(sum(self.interface.concentrations), self.interface.site_density)
def test_coverages_string(self):
self.interface.coverages = 'c6HM:0.2, c6H*:0.8'
C = self.interface.coverages
self.assertNear(C[self.interface.species_index('c6HM')], 0.2)
self.assertNear(C[self.interface.species_index('c6H*')], 0.8)
def test_coverages_dict(self):
self.interface.coverages = {'c6**':1.0, 'c6*M':3.0}
C = self.interface.coverages
self.assertNear(C[self.interface.species_index('c6**')], 0.25)
self.assertNear(C[self.interface.species_index('c6*M')], 0.75)
class TestInterfacePhase2(utilities.CanteraTest):
""" Test special cases of interface phases """
def test_multi_site_species(self):
surf = ct.Interface("surface-phases.yaml", "Pt-multi-sites")
# O2(s) consumes two surface sites
surf.coverages = {"Pt(s)": 0.5, "H(s)": 0.1, "O2(s)": 0.4}
X = surf.mole_fraction_dict()
moles = 0.5 + 0.1 + 0.4 / 2
assert np.isclose(X["Pt(s)"], 0.5 / moles)
assert np.isclose(X["H(s)"], 0.1 / moles)
assert np.isclose(X["O2(s)"], 0.2 / moles)
def test_multi_site_unnormalized(self):
surf = ct.Interface("surface-phases.yaml", "Pt-multi-sites")
theta = [0.5, 0.1, 0.1, 0.32]
surf.set_unnormalized_coverages(theta)
self.assertArrayNear(surf.coverages, theta)
X_unnormalized = surf.X
assert np.isclose(sum(theta), sum(surf.X))
surf.coverages = theta
X_normalized = surf.X
self.assertArrayNear(X_normalized * sum(theta), X_unnormalized)
class TestPlasmaPhase(utilities.CanteraTest):
def setUp(self):
self.phase = ct.Solution('oxygen-plasma.yaml',
'isotropic-electron-energy-plasma',
transport_model=None)
def test_converting_electron_energy_to_temperature(self):
self.phase.mean_electron_energy = 1.0
Te = 2.0 / 3.0 * ct.electron_charge / ct.boltzmann
self.assertNear(self.phase.Te, Te)
def test_converting_electron_temperature_to_energy(self):
self.phase.Te = 10000
energy = self.phase.Te * 3.0 / 2.0 / ct.electron_charge * ct.boltzmann
self.assertNear(self.phase.mean_electron_energy, energy)
def test_set_get_electron_energy_levels(self):
levels = np.linspace(0.01, 10, num=9)
self.phase.electron_energy_levels = levels
self.assertArrayNear(levels, self.phase.electron_energy_levels)
def test_isotropic_velocity_electron_energy_distribution(self):
levels = np.linspace(0.01, 10, num=9)
self.phase.electron_energy_levels = levels
self.phase.Te = 2e5
mean_electron_energy = 3.0 / 2.0 * (self.phase.Te * ct.gas_constant /
(ct.avogadro * ct.electron_charge))
self.assertNear(mean_electron_energy, self.phase.mean_electron_energy)
def test_discretized_electron_energy_distribution(self):
levels = np.array([0.0, 1.0, 10.0])
dist = np.array([0.0, 0.9, 0.01])
self.phase.normalize_electron_energy_distribution_enabled = False
self.phase.quadrature_method = "trapezoidal"
self.phase.set_discretized_electron_energy_distribution(levels, dist)
self.assertArrayNear(levels, self.phase.electron_energy_levels)
self.assertArrayNear(dist, self.phase.electron_energy_distribution)
mean_energy = 2.0 / 5.0 * np.trapz(dist, np.power(levels, 5./2.))
self.assertNear(self.phase.mean_electron_energy, mean_energy, 1e-4)
electron_temp = 2.0 / 3.0 * (self.phase.mean_electron_energy *
ct.avogadro * ct.electron_charge / ct.gas_constant)
self.assertNear(self.phase.Te, electron_temp)
def test_electron_thermodynamic_properties(self):
self.assertNear(self.phase.standard_gibbs_RT[0],
self.phase.standard_enthalpies_RT[0] -
self.phase.standard_entropies_R[0])
def test_add_multiple_electron_species(self):
electron = ct.Species('Electron', 'E:1')
electron.thermo = ct.ConstantCp(100, 200, 101325, coeffs=(300, 1, 1, 1))
with self.assertRaisesRegex(ct.CanteraError,
'Only one electron species is allowed'):
self.phase.add_species(electron)
class ImportTest(utilities.CanteraTest):
"""
Test the various ways of creating a Solution object
"""
def check(self, gas, phase, T, P, nSpec, nElem):
self.assertEqual(gas.name, phase)
self.assertNear(gas.T, T)
self.assertNear(gas.P, P)
self.assertEqual(gas.n_species, nSpec)
self.assertEqual(gas.n_elements, nElem)
def test_import_from_species(self):
gas1 = ct.Solution('h2o2.yaml', transport_model=None)
gas1.TPX = 350, 101325, 'H2:0.3, O2:0.7'
gas1.equilibrate('HP')
species = ct.Species.list_from_file("h2o2.yaml")
gas2 = ct.ThermoPhase(thermo='ideal-gas', species=species)
gas2.TPX = 350, 101325, 'H2:0.3, O2:0.7'
gas2.equilibrate('HP')
self.assertEqual(gas1.n_elements, gas2.n_elements)
self.assertEqual(gas1.species_names, gas2.species_names)
self.assertNear(gas1.T, gas2.T)
self.assertArrayNear(gas1.X, gas2.X)
def test_yaml_ideal_gas_simple(self):
gas = ct.ThermoPhase('ideal-gas.yaml', 'simple')
self.check(gas, 'simple', 500, 10 * ct.one_atm, 3, 2)
def test_yaml_ideal_gas_remote_species(self):
gas = ct.ThermoPhase('ideal-gas.yaml', 'species-remote')
self.check(gas, 'species-remote', 300, ct.one_atm, 4, 2)
def test_yaml_duplicate(self):
with self.assertRaisesRegex(ct.CanteraError, 'duplicate'):
gas = ct.ThermoPhase('ideal-gas.yaml', 'duplicate-species')
class TestSpecies(utilities.CanteraTest):
def setUp(self):
self.gas = ct.Solution('h2o2.yaml', transport_model=None)
def test_standalone(self):
s = ct.Species('CH4', {'C':1, 'H':4})
self.assertEqual(s.name, 'CH4')
c = s.composition
self.assertEqual(len(c), 2)
self.assertEqual(c['C'], 1)
self.assertEqual(c['H'], 4)
self.assertNear(s.molecular_weight, 16.043)
def test_molecular_weight_with_unstable_element(self):
s = ct.Species("CPo", {"C": 1, "Po": 1})
with pytest.raises(ct.CanteraError, match="has no stable isotopes"):
s.molecular_weight
def test_molecular_weight_with_electron(self):
yaml = """
name: Li+[elyt]
composition: {Li: 1, E: -1}
"""
s = ct.Species.from_yaml(yaml)
assert np.isclose(s.molecular_weight, 6.939451420091127)
def test_custom_element_weight_is_set_on_species(self):
gas = ct.Solution("ideal-gas.yaml", "element-override")
# Check that the molecular weight stored in the phase definition is the same
# as the one on the Species instance
self.assertNear(
gas["AR"].molecular_weights[0], gas.species("AR").molecular_weight
)
# Check that the custom value is actually used
self.assertNear(gas.species("AR").molecular_weight, 36.0)
def test_species_can_be_added_to_phase(self):
s = ct.Species.from_dict({
"name": "AR2",
"composition": {"Ar": 2},
"thermo": {"model": "constant-cp", "h0": 100}
})
# Access the molecular weight to make sure it's been computed by the Species
self.assertNear(s.molecular_weight, 39.95 * 2)
# This should not cause a warning because the Ar element definition in
# self.gas is the same as the default
self.gas.add_species(s)
self.assertNear(
self.gas["AR2"].molecular_weights[0],
self.gas.species("AR2").molecular_weight
)
def test_species_warns_when_changing_molecular_weight(self):
s = ct.Species.from_dict({
"name": "AR2",
"composition": {"Ar": 2},
"thermo": {"model": "constant-cp", "h0": 100}
})
# Access the molecular weight to make sure it's been computed by the Species
self.assertNear(s.molecular_weight, 39.95 * 2)
gas = ct.Solution("ideal-gas.yaml", "element-override")
# The warning here is because the weight of the Argon element has been changed in
# the phase definition, but the molecular weight of the species has already been
# computed, so loading the phase definition changes the molecular weight on the
# species.
with pytest.warns(UserWarning, match="Molecular weight.*changing"):
gas.add_species(s)
def test_species_can_be_added_to_phase_custom_element(self):
s = ct.Species.from_dict({
"name": "AR2",
"composition": {"Ar": 2},
"thermo": {"model": "constant-cp", "h0": 100}
})
# DO NOT access the molecular weight on the Species instance before adding it
# to the phase to make sure the weight has not been computed by the Species
gas = ct.Solution("ideal-gas.yaml", "element-override")
gas.add_species(s)
self.assertNear(
gas["AR2"].molecular_weights[0],
gas.species("AR2").molecular_weight
)
self.assertNear(s.molecular_weight, gas.species("AR2").molecular_weight)
def test_defaults(self):
s = ct.Species('H2')
self.assertEqual(s.size, 1.0)
self.assertEqual(s.charge, 0.0)
self.assertIsNone(s.thermo)
self.assertIsNone(s.transport)
def test_index_accessor(self):
for k in range(self.gas.n_species):
s = self.gas.species(k)
self.assertEqual(s.name, self.gas.species_name(k))
for m,n in s.composition.items():
self.assertEqual(n, self.gas.n_atoms(k,m))
def test_species_noargs(self):
for k,s in enumerate(self.gas.species()):
self.assertEqual(s.name, self.gas.species_name(k))
def test_name_accessor(self):
for name in self.gas.species_names:
s = self.gas.species(name)
self.assertEqual(s.name, name)
def test_listfromFile_yaml(self):
S = ct.Species.list_from_file("h2o2.yaml")
self.assertEqual({sp.name for sp in S}, set(self.gas.species_names))
def test_list_from_yaml(self):
yaml = '''
- name: H2O
composition: {H: 2, O: 1}
thermo: {model: constant-cp, h0: 100}
- name: HO2
composition: {H: 1, O: 2}
thermo: {model: constant-cp, h0: 200}
'''
species = ct.Species.list_from_yaml(yaml)
self.assertEqual(species[0].name, 'H2O')
self.assertEqual(species[1].composition, {'H': 1, 'O': 2})
self.assertNear(species[0].thermo.h(300), 100)
def test_list_from_yaml_section(self):
species = ct.Species.list_from_yaml(
(self.test_data_path / "ideal-gas.yaml").read_text(),
'species')
self.assertEqual(species[0].name, 'O2')
self.assertEqual(species[1].composition, {'N': 1, 'O': 1})
def test_from_yaml(self):
yaml = """
name: H2O
composition: {H: 2, O: 1}
thermo: {model: constant-cp, h0: 100}
"""
species = ct.Species.from_yaml(yaml)
self.assertEqual(species.name, 'H2O')
self.assertEqual(species.composition, {'H': 2, 'O': 1})
self.assertNear(species.thermo.h(300), 100)
def test_from_dict(self):
data = {
"name": "H2O",
"composition": {"H": 2, "O": 1},
"thermo": {"model": "constant-cp", "h0": 100},
}
species = ct.Species.from_dict(data)
self.assertEqual(species.name, 'H2O')
self.assertEqual(species.composition, {'H': 2, 'O': 1})
self.assertNear(species.thermo.h(300), 100)
def test_modify_thermo(self):
S = {sp.name: sp for sp in ct.Species.list_from_file("h2o2.yaml")}
self.gas.TPX = 400, 2*ct.one_atm, 'H2:1.0'
g0 = self.gas.gibbs_mole
self.gas.TPX = None, None, 'O2:1.0'
self.assertNotAlmostEqual(g0, self.gas.gibbs_mole)
# Replace O2 thermo with the data from H2
S['O2'].thermo = S['H2'].thermo
self.gas.modify_species(self.gas.species_index('O2'), S['O2'])
self.assertNear(g0, self.gas.gibbs_mole)
def test_modify_thermo_invalid(self):
S = {sp.name: sp for sp in ct.Species.list_from_file("h2o2.yaml")}
orig = S['H2']
thermo = orig.thermo
copy = ct.Species('foobar', orig.composition)
copy.thermo = thermo
with self.assertRaisesRegex(ct.CanteraError, 'modifySpecies'):
self.gas.modify_species(self.gas.species_index('H2'), copy)
copy = ct.Species('H2', {'H': 3})
copy.thermo = thermo
with self.assertRaisesRegex(ct.CanteraError, 'modifySpecies'):
self.gas.modify_species(self.gas.species_index('H2'), copy)
copy = ct.Species('H2', orig.composition)
copy.thermo = ct.ConstantCp(thermo.min_temp, thermo.max_temp,
thermo.reference_pressure, [300, 123, 456, 789])
with self.assertRaisesRegex(ct.CanteraError, 'modifySpecies'):
self.gas.modify_species(self.gas.species_index('H2'), copy)
copy = ct.Species('H2', orig.composition)
copy.thermo = ct.NasaPoly2(thermo.min_temp+200, thermo.max_temp,
thermo.reference_pressure, thermo.coeffs)
with self.assertRaisesRegex(ct.CanteraError, 'modifySpecies'):
self.gas.modify_species(self.gas.species_index('H2'), copy)
def test_alias(self):
self.gas.add_species_alias('H2', 'hydrogen')
self.assertEqual(self.gas.species_index('hydrogen'), 0)
self.gas.X = 'hydrogen:.5, O2:.5'
self.assertNear(self.gas.X[0], 0.5)
with self.assertRaisesRegex(ct.CanteraError, 'Invalid alias'):
self.gas.add_species_alias('H2', 'O2')
with self.assertRaisesRegex(ct.CanteraError, 'Unable to add alias'):
self.gas.add_species_alias('spam', 'eggs')
def test_isomers(self):
gas = ct.Solution('nDodecane_Reitz.yaml')
iso = gas.find_isomers({'C':4, 'H':9, 'O':2})
self.assertEqual(len(iso), 2)
iso = gas.find_isomers('C:4, H:9, O:2')
self.assertEqual(len(iso), 2)
iso = gas.find_isomers({'C':7, 'H':15})
self.assertEqual(len(iso), 1)
iso = gas.find_isomers({'C':7, 'H':16})
self.assertEqual(len(iso), 0)
class TestSpeciesThermo(utilities.CanteraTest):
h2o_coeffs = [
1000.0, 3.03399249E+00, 2.17691804E-03, -1.64072518E-07,
-9.70419870E-11, 1.68200992E-14, -3.00042971E+04, 4.96677010E+00,
4.19864056E+00, -2.03643410E-03, 6.52040211E-06, -5.48797062E-09,
1.77197817E-12, -3.02937267E+04, -8.49032208E-01
]
def setUp(self):
self.gas = ct.Solution('h2o2.yaml', transport_model=None)
self.gas.X = 'H2O:1.0'
def test_create(self):
st = ct.NasaPoly2(300, 3500, 101325, self.h2o_coeffs)
for T in [300, 500, 900, 1200, 2000]:
self.gas.TP = T, 101325
self.assertNear(st.cp(T), self.gas.cp_mole)
self.assertNear(st.h(T), self.gas.enthalpy_mole)
self.assertNear(st.s(T), self.gas.entropy_mole)
def test_invalid(self):
with self.assertRaisesRegex(ValueError, 'incorrect length'):
# not enough coefficients
st = ct.NasaPoly2(300, 3500, 101325,
[1000.0, 3.03399249E+00, 2.17691804E-03])
def test_wrap(self):
st = self.gas.species('H2O').thermo
self.assertIsInstance(st, ct.NasaPoly2)
for T in [300, 500, 900, 1200, 2000]:
self.gas.TP = T, 101325
self.assertNear(st.cp(T), self.gas.cp_mole)
self.assertNear(st.h(T), self.gas.enthalpy_mole)
self.assertNear(st.s(T), self.gas.entropy_mole)
def test_coeffs(self):
st = ct.NasaPoly2(300, 3500, 101325, self.h2o_coeffs)
self.assertEqual(st.min_temp, 300)
self.assertEqual(st.max_temp, 3500)
self.assertEqual(st.reference_pressure, 101325)
self.assertArrayNear(self.h2o_coeffs, st.coeffs)
self.assertEqual(st.n_coeffs, len(st.coeffs))
self.assertTrue(st._check_n_coeffs(st.n_coeffs))
def test_nasa9_load(self):
gas = ct.Solution("airNASA9.yaml")
st = gas.species(3).thermo
self.assertIsInstance(st, ct.Nasa9PolyMultiTempRegion)
self.assertEqual(st.n_coeffs, len(st.coeffs))
self.assertTrue(st._check_n_coeffs(st.n_coeffs))
def test_nasa9_create(self):
gas = ct.Solution("airNASA9.yaml")
st = gas.species(3).thermo
t_min = st.min_temp
t_max = st.max_temp
p_ref = st.reference_pressure
coeffs = st.coeffs
st2 = ct.Nasa9PolyMultiTempRegion(t_min, t_max, p_ref, coeffs)
self.assertIsInstance(st2, ct.Nasa9PolyMultiTempRegion)
self.assertEqual(st.min_temp, t_min)
self.assertEqual(st.max_temp, t_max)
self.assertEqual(st.reference_pressure, p_ref)
for T in range(300, 20000, 1000):
self.assertNear(st.cp(T), st2.cp(T))
self.assertNear(st.h(T), st2.h(T))
self.assertNear(st.s(T), st2.s(T))
def test_shomate_load(self):
sol = ct.Solution('thermo-models.yaml', 'molten-salt-Margules')
st = sol.species(0).thermo
self.assertIsInstance(st, ct.ShomatePoly2)
self.assertEqual(st.n_coeffs, len(st.coeffs))
self.assertTrue(st._check_n_coeffs(st.n_coeffs))
def test_shomate_create(self):
sol = ct.Solution('thermo-models.yaml', 'molten-salt-Margules')
st = sol.species(0).thermo
t_min = st.min_temp
t_max = st.max_temp
p_ref = st.reference_pressure
coeffs = st.coeffs
st2 = ct.ShomatePoly2(t_min, t_max, p_ref, coeffs)
self.assertIsInstance(st2, ct.ShomatePoly2)
self.assertEqual(st.min_temp, t_min)
self.assertEqual(st.max_temp, t_max)
self.assertEqual(st.reference_pressure, p_ref)
for T in [300, 500, 700, 900]:
self.assertNear(st.cp(T), st2.cp(T))
self.assertNear(st.h(T), st2.h(T))
self.assertNear(st.s(T), st2.s(T))
def test_piecewise_gibbs_load(self):
sol = ct.Solution('thermo-models.yaml', 'HMW-NaCl-electrolyte')
st = sol.species(1).thermo
self.assertIsInstance(st, ct.Mu0Poly)
self.assertEqual(st.n_coeffs, len(st.coeffs))
self.assertTrue(st._check_n_coeffs(st.n_coeffs))
def test_piecewise_gibbs_create1(self):
# use OH- ion data from test/thermo/phaseConstructors.cpp
h298 = -230.015e6
T1 = 298.15
mu1 = -91.50963
T2 = 333.15
mu2 = -85
pref = 101325
coeffs = [2, h298, T1, mu1*ct.gas_constant*T1, T2, mu2*ct.gas_constant*T2]
st2 = ct.Mu0Poly(200, 3500, pref, coeffs)
self.assertIsInstance(st2, ct.Mu0Poly)
self.assertEqual(st2.n_coeffs, len(coeffs))
self.assertEqual(st2.n_coeffs, len(st2.coeffs))
def test_piecewise_gibbs_create2(self):
sol = ct.Solution('thermo-models.yaml', 'HMW-NaCl-electrolyte')
st = sol.species(1).thermo
t_min = st.min_temp
t_max = st.max_temp
p_ref = st.reference_pressure
coeffs = st.coeffs
st2 = ct.Mu0Poly(t_min, t_max, p_ref, coeffs)
self.assertIsInstance(st2, ct.Mu0Poly)
self.assertEqual(st.min_temp, t_min)
self.assertEqual(st.max_temp, t_max)
self.assertEqual(st.reference_pressure, p_ref)
for T in [300, 500, 700, 900]:
self.assertNear(st.cp(T), st2.cp(T))
self.assertNear(st.h(T), st2.h(T))
self.assertNear(st.s(T), st2.s(T))
class TestQuantity(utilities.CanteraTest):
@classmethod
def setUpClass(cls):
utilities.CanteraTest.setUpClass()
cls.gas = ct.Solution('gri30.yaml', transport_model=None)
def setUp(self):
self.gas.TPX = 300, 101325, 'O2:1.0, N2:3.76'
self.gas.basis = 'mass'
def test_mass_moles(self):
q1 = ct.Quantity(self.gas, mass=5)
self.assertNear(q1.mass, 5)
self.assertNear(q1.moles, 5 / q1.mean_molecular_weight)
q1.mass = 7
self.assertNear(q1.moles, 7 / q1.mean_molecular_weight)
q1.moles = 9
self.assertNear(q1.moles, 9)
self.assertNear(q1.mass, 9 * q1.mean_molecular_weight)
def test_extensive(self):
q1 = ct.Quantity(self.gas, mass=5)
self.assertNear(q1.mass, 5)
self.assertNear(q1.volume * q1.density, q1.mass)
self.assertNear(q1.V * q1.density, q1.mass)
self.assertNear(q1.int_energy, q1.moles * q1.int_energy_mole)
self.assertNear(q1.enthalpy, q1.moles * q1.enthalpy_mole)
self.assertNear(q1.entropy, q1.moles * q1.entropy_mole)
self.assertNear(q1.gibbs, q1.moles * q1.gibbs_mole)
self.assertNear(q1.int_energy, q1.U)
self.assertNear(q1.enthalpy, q1.H)
self.assertNear(q1.entropy, q1.S)
self.assertNear(q1.gibbs, q1.G)
def test_set_equivalence_ratio(self):
q1 = ct.Quantity(self.gas, mass=3)
T1, P1 = q1.TP
q1.set_equivalence_ratio(2.0, 'CH4:1.0', 'O2:1.0, N2:3.76')
assert q1.T == approx(T1)
assert q1.P == approx(P1)
assert q1.X[q1.species_index('CH4')] == approx(1.0 / (1 + 4.76))
def test_set_mixture_fraction(self):
q1 = ct.Quantity(self.gas, mass=3)
T1, P1 = q1.TP
q1.set_mixture_fraction(1.0, 'CH3OH:1.0', 'O2:1.0, N2:3.76')
assert q1.T == approx(T1)
assert q1.P == approx(P1)
assert q1.mass == 3
assert q1.X[q1.species_index('CH3OH')] == approx(1.0)
def test_basis(self):
q1 = ct.Quantity(self.gas, mass=5)
T1, P1 = q1.TP
h1 = q1.h # J/kg
q1.basis = 'molar'
assert q1.T == approx(T1)
assert q1.P == approx(P1)
assert q1.h == approx(h1 * q1.mean_molecular_weight)
def test_multiply(self):
q1 = ct.Quantity(self.gas, mass=5)
q2 = q1 * 2.5
self.assertNear(q1.mass * 2.5, q2.mass)
self.assertNear(q1.moles * 2.5, q2.moles)
self.assertNear(q1.entropy * 2.5, q2.entropy)
self.assertArrayNear(q1.X, q2.X)
def test_multiply_HP(self):
self.gas.TPX = 500, 101325, 'CH4:1.0, O2:0.4'
q1 = ct.Quantity(self.gas, mass=2, constant='HP')
q2 = ct.Quantity(self.gas, mass=1, constant='HP')
q2.equilibrate('HP')
q3 = 0.2 * q1 + q2 * 0.4
self.assertNear(q1.P, q3.P)
self.assertNear(q1.enthalpy_mass, q3.enthalpy_mass)
self.assertNear(q2.enthalpy_mass, q3.enthalpy_mass)
def test_iadd(self):
q0 = ct.Quantity(self.gas, mass=5)
q1 = ct.Quantity(self.gas, mass=5)
q2 = ct.Quantity(self.gas, mass=5)
q2.TPX = 500, 101325, 'CH4:1.0'
q1 += q2
self.assertNear(q0.mass + q2.mass, q1.mass)
# addition is at constant UV
self.assertNear(q0.U + q2.U, q1.U)
self.assertNear(q0.V + q2.V, q1.V)
self.assertArrayNear(q0.X*q0.moles + q2.X*q2.moles, q1.X*q1.moles)
def test_add(self):
q1 = ct.Quantity(self.gas, mass=5)
q2 = ct.Quantity(self.gas, mass=5)
q2.TPX = 500, 101325, 'CH4:1.0'
q3 = q1 + q2
self.assertNear(q1.mass + q2.mass, q3.mass)
# addition is at constant UV
self.assertNear(q1.U + q2.U, q3.U)
self.assertNear(q1.V + q2.V, q3.V)
self.assertArrayNear(q1.X*q1.moles + q2.X*q2.moles, q3.X*q3.moles)
def test_add_molar(self):
q1 = ct.Quantity(self.gas, mass=5)
q2 = ct.Quantity(self.gas, mass=5)
q2.TPX = 900, 101325, 'CH4:1.0'
q1.basis = 'molar'
q2.basis = 'molar'
# addition at constant UV
q3 = q1 + q2
assert q1.mass + q2.mass == approx(q3.mass)
assert q1.U + q2.U == approx(q3.U)
assert q1.V + q2.V == approx(q3.V)
# addition at constant HP
q1.constant = q2.constant = 'HP'
q4 = q1 + q2
assert q1.mass + q2.mass == approx(q4.mass)
assert q1.H + q2.H == approx(q4.H)
assert q4.P == approx(q1.P)
self.assertArrayNear(q1.X*q1.moles + q2.X*q2.moles, q4.X*q4.moles)
def test_add_errors(self):
q1 = ct.Quantity(self.gas, mass=5)
q2 = ct.Quantity(self.gas, mass=5)
q1.constant = q2.constant = 'HP'
q2.TP = q1.T, 1.2 * q1.P
with pytest.raises(ValueError, match="pressure is not equal"):
q3 = q1 + q2
def test_equilibrate(self):
self.gas.TPX = 300, 101325, 'CH4:1.0, O2:0.2, N2:1.0'
q1 = ct.Quantity(self.gas)
self.gas.equilibrate('HP')
T2 = self.gas.T
self.assertNear(q1.T, 300)
q1.equilibrate('HP')
self.assertNear(q1.T, T2)
def test_invalid_setter(self):
q1 = ct.Quantity(self.gas, mass =3)
with self.assertRaises(AttributeError):
q1.HPQ = self.gas.H, self.gas.P, 1
with pytest.raises(AttributeError):
q1.set_unnormalized_mass_fractions(np.ones(q1.n_species))
with pytest.raises(AttributeError):
q1.set_unnormalized_mole_fractions(np.ones(q1.n_species))
def test_incompatible(self):
gas2 = ct.Solution('h2o2.yaml', transport_model=None)
q1 = ct.Quantity(self.gas)
q2 = ct.Quantity(gas2)
with self.assertRaisesRegex(ValueError, 'different phase definitions'):
q1+q2
def test_disables_add_species(self):
gas = ct.Solution('h2o2.yaml', transport_model=None)
q1 = ct.Quantity(gas)
species_x = ct.Species("X", {"H": 3})
species_x.thermo = ct.ConstantCp(200, 5000, ct.one_atm, coeffs=(0,0,0,0))
N = gas.n_species
with pytest.raises(ct.CanteraError, match="is being used"):
gas.add_species(species_x)
assert gas.n_species == N
# Adding species works again after the Solution is no longer in use
del q1
gas.add_species(species_x)
assert gas.n_species == N + 1
class TestMisc(utilities.CanteraTest):
def test_stringify_bad(self):
with self.assertRaises(AttributeError):
ct.Solution(3)
def test_case_sensitive_names(self):
gas = ct.Solution('h2o2.yaml', transport_model=None)
self.assertFalse(gas.case_sensitive_species_names)
self.assertEqual(gas.species_index('h2'), 0)
gas.X = 'h2:.5, o2:.5'
self.assertNear(gas.X[0], 0.5)
gas.Y = 'h2:.5, o2:.5'
self.assertNear(gas.Y[0], 0.5)
gas.case_sensitive_species_names = True
self.assertTrue(gas.case_sensitive_species_names)
with self.assertRaises(ValueError):
gas.species_index('h2')
with self.assertRaisesRegex(ct.CanteraError, 'Unknown species'):
gas.X = 'h2:1.0, o2:1.0'
with self.assertRaisesRegex(ct.CanteraError, 'Unknown species'):
gas.Y = 'h2:1.0, o2:1.0'
gas_yaml = """
phases:
- name: gas
thermo: ideal-gas
elements: [S, C, Cs]
species: [{nasa_gas.yaml/species: all}]
skip-undeclared-elements: true
"""
ct.suppress_thermo_warnings(True)
gas = ct.Solution(yaml=gas_yaml)
with self.assertRaisesRegex(ct.CanteraError, 'is not unique'):
gas.species_index('cs')
gas.case_sensitive_species_names = True
with self.assertRaises(ValueError):
gas.species_index('cs')
def test_unstable_element_in_phase(self):
gas_yaml = """
phases:
- name: gas
thermo: ideal-gas
elements: [Po]
species:
- name: Po
composition: {Po: 1}
"""
with pytest.raises(ct.CanteraError, match="has no stable isotopes"):
ct.Solution(yaml=gas_yaml)
class TestElement(utilities.CanteraTest):
@classmethod
def setUpClass(cls):
utilities.CanteraTest.setUpClass()
cls.ar_sym = ct.Element('Ar')
cls.ar_name = ct.Element('argon')
cls.ar_num = ct.Element(18)
def test_element_multiple_possibilities(self):
# Carbon starts with Ca, the symbol for calcium.
carbon = ct.Element('Carbon')
self.assertEqual(carbon.name, 'carbon')
self.assertEqual(carbon.symbol, 'C')
def test_element_weight(self):
self.assertNear(self.ar_sym.weight, 39.95)
self.assertNear(self.ar_name.weight, 39.95)
self.assertNear(self.ar_num.weight, 39.95)
def test_element_symbol(self):
self.assertEqual(self.ar_sym.symbol, 'Ar')
self.assertEqual(self.ar_name.symbol, 'Ar')
self.assertEqual(self.ar_num.symbol, 'Ar')
def test_element_name(self):
self.assertEqual(self.ar_sym.name, 'argon')
self.assertEqual(self.ar_name.name, 'argon')
self.assertEqual(self.ar_num.name, 'argon')
def test_element_atomic_number(self):
self.assertEqual(self.ar_sym.atomic_number, 18)
self.assertEqual(self.ar_name.atomic_number, 18)
self.assertEqual(self.ar_num.atomic_number, 18)
def test_element_name_not_present(self):
with self.assertRaisesRegex(ct.CanteraError, 'element not found'):
ct.Element('I am not an element')
def test_element_atomic_number_small(self):
with self.assertRaisesRegex(ct.CanteraError, 'IndexError'):
ct.Element(0)
def test_element_atomic_number_big(self):
num_elements = ct.Element.num_elements_defined
with self.assertRaisesRegex(ct.CanteraError, 'IndexError'):
ct.Element(num_elements + 1)
def test_element_no_weight(self):
with self.assertRaisesRegex(ct.CanteraError, 'no stable isotopes'):
ct.Element('Tc')
def test_element_bad_input(self):
with self.assertRaisesRegex(TypeError, 'input argument to Element'):
ct.Element(1.2345)
def test_get_isotope(self):
d_sym = ct.Element('D')
self.assertEqual(d_sym.atomic_number, 1)
self.assertNear(d_sym.weight, 2.0141017781)
self.assertEqual(d_sym.name, 'deuterium')
self.assertEqual(d_sym.symbol, 'D')
d_name = ct.Element('deuterium')
self.assertEqual(d_name.atomic_number, 1)
self.assertNear(d_name.weight, 2.0141017781)
self.assertEqual(d_name.name, 'deuterium')
self.assertEqual(d_name.symbol, 'D')
def test_elements_lists(self):
syms = ct.Element.element_symbols
names = ct.Element.element_names
num_elements = ct.Element.num_elements_defined
self.assertEqual(len(syms), num_elements)
self.assertEqual(len(names), num_elements)
class TestSolutionArray(utilities.CanteraTest):
@classmethod
def setUpClass(cls):
utilities.CanteraTest.setUpClass()
cls.gas = ct.Solution('h2o2.yaml')
def test_passthrough(self):
states = ct.SolutionArray(self.gas, 3)
self.assertEqual(states.n_species, self.gas.n_species)
self.assertEqual(states.reaction(10).equation,
self.gas.reaction(10).equation)
def test_meta(self):
meta = {'foo': 'bar', 'spam': 'eggs'}
states = ct.SolutionArray(self.gas, 3, meta=meta)
self.assertEqual(states.meta['foo'], 'bar')
self.assertEqual(states.meta['spam'], 'eggs')
def test_get_state(self):
states = ct.SolutionArray(self.gas, 4)
H, P = states.HP
self.assertEqual(H.shape, (4,))
self.assertEqual(P.shape, (4,))
S, P, Y = states.SPY
self.assertEqual(S.shape, (4,))
self.assertEqual(P.shape, (4,))
self.assertEqual(Y.shape, (4, self.gas.n_species))
def test_idealgas_getters(self):
N = 11
states = ct.SolutionArray(self.gas, N)
getters = 'TDPUVHSXY' # omit getters that contain Q
# obtain setters from thermo objects
all_getters = [k for k in dir(self.gas)
if not set(k) - set(getters) and len(k)>1]
# ensure that getters do not raise attribute errors
for g in all_getters:
out = getattr(states, g)
self.assertEqual(len(out), len(g))
def test_properties_onedim(self):
N = 11
states = ct.SolutionArray(self.gas, N)
T = np.linspace(300, 2200, N)
P = np.logspace(3, 8, N)
X = 'H2:0.5, O2:0.4, AR:0.1, H2O2:0.01, OH:0.001'
states.TPX = T, P, X
self.assertArrayNear(states.T, T)
self.assertArrayNear(states.P, P)
h = states.enthalpy_mass
ropr = states.reverse_rates_of_progress
Dkm = states.mix_diff_coeffs
for i in range(N):
self.gas.TPX = T[i], P[i], X
self.assertNear(self.gas.enthalpy_mass, h[i])
self.assertArrayNear(self.gas.reverse_rates_of_progress, ropr[i])
self.assertArrayNear(self.gas.mix_diff_coeffs, Dkm[i])
def test_properties_ndim(self):
states = ct.SolutionArray(self.gas, (2,3,5))
T = np.linspace(300, 2200, 5)
P = np.logspace(3, 8, 2)[:,np.newaxis, np.newaxis]
X = np.random.random((3,1,self.gas.n_species))
states.TPX = T, P, X
TT, PP = states.TP
h = states.enthalpy_mass
ropr = states.reverse_rates_of_progress
Dkm = states.mix_diff_coeffs
self.assertEqual(h.shape, (2,3,5))
self.assertEqual(ropr.shape, (2,3,5,self.gas.n_reactions))
self.assertEqual(Dkm.shape, (2,3,5,self.gas.n_species))
for i,j,k in np.ndindex(TT.shape):
self.gas.TPX = T[k], P[i][0][0], X[j]
self.assertNear(self.gas.enthalpy_mass, h[i,j,k])
self.assertArrayNear(self.gas.reverse_rates_of_progress, ropr[i,j,k])
self.assertArrayNear(self.gas.mix_diff_coeffs, Dkm[i,j,k])
def test_array_properties_exist(self):
grid_shape = (7, 3)
states = ct.SolutionArray(self.gas, grid_shape)
skip = {
# Skipped because they are complicated (conversion not implemented)
"forward_rates_of_progress_ddX", "net_rates_of_progress_ddX",
"reverse_rates_of_progress_ddX", "state", "net_rates_of_progress_ddCi",
"forward_rates_of_progress_ddCi", "reverse_rates_of_progress_ddCi"
}
skip.update(ct.SolutionArray._passthrough)
for attr in dir(self.gas):
if attr.startswith("_") or attr in skip:
continue
try:
soln_value = getattr(self.gas, attr)
except (ct.CanteraError, ct.ThermoModelMethodError, NotImplementedError):
continue
if not isinstance(soln_value, (float, np.ndarray)):
continue
assert hasattr(states, attr), attr
array_value = getattr(states, attr)
assert array_value.shape == grid_shape + np.asarray(soln_value).shape, attr
def test_slicing_onedim(self):
states = ct.SolutionArray(self.gas, 5)
states.TPX = np.linspace(500, 1000, 5), 2e5, 'H2:0.5, O2:0.4'
T0 = states.T
H0 = states.enthalpy_mass
# Verify that original object is updated when slices change
state = states[1]
state.TD = 300, 0.5
self.assertNear(states.T[0], 500)
self.assertNear(states.T[1], 300)
self.assertNear(states.P[2], 2e5)
self.assertNear(states.density[1], 0.5)
# Verify that the slices are updated when the original object changes
states.TD = 900, None
self.assertNear(state.T, 900)
self.assertNear(states.density[1], 0.5)
def test_slicing_ndim(self):
states = ct.SolutionArray(self.gas, (2,5))
states.TPX = np.linspace(500, 1000, 5), 2e5, 'H2:0.5, O2:0.4'
T0 = states.T
H0 = states.enthalpy_mass
# Verify that original object is updated when slices change
row2 = states[1]
row2.TD = 300, 0.5
T = states.T
D = states.density
self.assertArrayNear(T[0], T0[0])
self.assertArrayNear(T[1], 300*np.ones(5))
self.assertArrayNear(D[1], 0.5*np.ones(5))
col3 = states[:,2]
col3.TD = 400, 2.5
T = states.T
D = states.density
self.assertArrayNear(T[:,2], 400*np.ones(2))
self.assertArrayNear(D[:,2], 2.5*np.ones(2))
# Verify that the slices are updated when the original object changes
states.TP = 900, None
self.assertArrayNear(col3.T, 900*np.ones(2))
self.assertArrayNear(row2.T, 900*np.ones(5))
def test_extra_create_by_dict(self):
extra = {"grid": np.arange(10), "velocity": np.random.rand(10)}
states = ct.SolutionArray(self.gas, 10, extra=extra)
keys = states.extra
self.assertEqual(keys[0], 'grid')
self.assertArrayNear(states.grid, np.arange(10))
def test_extra_no_shape(self):
# The shape of the value for "prop" here is (), which is falsey
# and causes the use of np.full()
states = ct.SolutionArray(self.gas, 3, extra={"prop": 1})
self.assertEqual(states.prop.shape, (3,))
self.assertArrayNear(states.prop, np.array((1, 1, 1)))
# Check a multidimensional SolutionArray
states = ct.SolutionArray(self.gas, (2, 2), extra={"prop": 3})
self.assertEqual(states.prop.shape, (2, 2))
self.assertArrayNear(states.prop, np.array(((3, 3), (3, 3))))
def test_extra_not_empty(self):
"""Test that a non-empty SolutionArray raises a ValueError if
initial values for properties are not supplied.
"""
with self.assertRaisesRegex(ValueError, "Initial values for extra components"):
ct.SolutionArray(self.gas, 3, extra=["prop"])
with self.assertRaisesRegex(ValueError, "Initial values for extra components"):
ct.SolutionArray(self.gas, 3, extra=np.array(["prop", "prop2"]))
def test_extra_create_multidim(self):
# requires matching first dimensions
extra_val = [[1, 2, 3] for i in range(5)]
states = ct.SolutionArray(self.gas, 5, extra={"prop": extra_val})
self.assertEqual(states.prop.shape, (5, 3,))
states = ct.SolutionArray(self.gas, 5, extra={"prop": np.array(extra_val)})
self.assertEqual(states.prop.shape, (5, 3,))
states = ct.SolutionArray(self.gas, (3, 4,), extra={"prop": np.ones((3, 4, 5,))})
self.assertEqual(states.prop.shape, (3, 4, 5,))
states = ct.SolutionArray(self.gas, 1, extra={"prop": extra_val[0]})
self.assertEqual(states.prop.shape, (1, 3,))
states = ct.SolutionArray(self.gas, 1, extra={"prop": [2]})
self.assertEqual(states.prop.shape, (1,))
with self.assertRaisesRegex(ValueError, "Unable to map"):
ct.SolutionArray(self.gas, (3, 3), extra={"prop": np.arange(3)})
def test_extra_create_by_iterable(self):
states = ct.SolutionArray(self.gas, extra=("prop1"))
self.assertEqual(states.prop1.shape, (0,))
# An integer is not an iterable, and only bare strings are
# turned into iterables
with self.assertRaisesRegex(ValueError, "Extra components"):
ct.SolutionArray(self.gas, extra=2)
def test_extra_not_string(self):
with self.assertRaisesRegex(TypeError, "is not a string"):
ct.SolutionArray(self.gas, extra=[1])
def test_extra_no_objects(self):
with self.assertRaisesRegex(ValueError, "not supported"):
prop = np.array([0, [1, 2], (3, 4)], dtype=object)
states = ct.SolutionArray(self.gas, 3, extra={"prop": prop})
def test_extra_reserved_names(self):
with self.assertRaisesRegex(ValueError, "name is already used"):
ct.SolutionArray(self.gas, extra=["creation_rates"])
with self.assertRaisesRegex(ValueError, "name is already used"):
ct.SolutionArray(self.gas, extra={"creation_rates": 0})
def test_extra_create_by_string(self):
states = ct.SolutionArray(self.gas, extra="prop")
self.assertEqual(states.prop.shape, (0,))
def test_extra_setattr(self):
states = ct.SolutionArray(self.gas, 7, extra={'prop': range(7)})
states.prop = 0
self.assertArrayNear(states.prop, np.zeros((7,)))
mod_array = np.linspace(0, 10, 7).astype(np.int64)
states.prop = mod_array
self.assertArrayNear(states.prop, mod_array)
with self.assertRaisesRegex(ValueError, "Incompatible shapes"):
states.prop = [1, 2]
def test_assign_to_slice(self):
# assign to slices does not work after Cantera 3.0
# as Python and C++ representations do not reference shared memory
states = ct.SolutionArray(self.gas, 7, extra={'prop': range(7)})
array = np.arange(7)
self.assertArrayNear(states.prop, array)
states[1].prop = -5
assert states.prop[1] == -5
with pytest.raises(ValueError, match="read-only"):
states.prop[1] = -10
# assign to multi-dimensional extra
extra_val = [[1, 2, 3] for i in range(5)]
states = ct.SolutionArray(self.gas, 5, extra={"prop": extra_val})
states[1].prop = [-1, -1, -1]
assert (states[1].prop == np.array([-1, -1, -1])).all()
with pytest.raises(ValueError, match="read-only"):
states.prop[:, 1] = -2
def test_extra_create_by_ndarray(self):
properties_array = np.array(["prop1", "prop2", "prop3"])
states = ct.SolutionArray(self.gas, shape=(0,), extra=properties_array)
self.assertEqual(states.prop1.shape, (0,))
self.assertEqual(states.prop2.shape, (0,))
self.assertEqual(states.prop3.shape, (0,))
# Ensure that a 2-dimensional array is flattened
properties_array = np.array((["prop1"], ["prop2"]))
states = ct.SolutionArray(self.gas, extra=properties_array)
self.assertEqual(states.prop1.shape, (0,))
self.assertEqual(states.prop2.shape, (0,))
def test_append(self):
states = ct.SolutionArray(self.gas, 5)
states.TPX = np.linspace(500, 1000, 5), 2e5, 'H2:0.5, O2:0.4'
self.assertEqual(states.cp_mass.shape, (5,))
states.append(T=1100, P=3e5, X='AR:1.0')
self.assertEqual(states.cp_mass.shape, (6,))
self.assertNear(states.P[-1], 3e5)
self.assertNear(states.T[-1], 1100)
self.gas.TPX = 1200, 5e5, 'O2:0.3, AR:0.7'
states.append(self.gas.state)
self.assertEqual(states.cp_mass.shape, (7,))
self.assertNear(states.P[-1], 5e5)
self.assertNear(states.X[-1, self.gas.species_index('AR')], 0.7)
self.gas.TPX = 300, 1e4, 'O2:0.5, AR:0.5'
HPY = self.gas.HPY
self.gas.TPX = 1200, 5e5, 'O2:0.3, AR:0.7' # to make sure it gets changed
states.append(HPY=HPY)
self.assertEqual(states.cp_mass.shape, (8,))
self.assertNear(states.P[-1], 1e4)
self.assertNear(states.T[-1], 300)
def test_append_with_extra(self):
states = ct.SolutionArray(self.gas, 5, extra={"prop": "value"})
states.TPX = np.linspace(500, 1000, 5), 2e5, 'H2:0.5, O2:0.4'
self.assertEqual(states.shape, (5,))
states.append(T=1100, P=3e5, X="AR:1.0", prop="value2")
self.assertEqual(states.prop[-1], "value2")
self.assertEqual(states.prop.shape, (6,))
states.append(T=1100, P=3e5, X="AR:1.0", prop=100)
# NumPy converts to the existing type of the array
self.assertEqual(states.prop[-1], "100")
self.assertEqual(states.prop.shape, (7,))
# two-dimensional input array
states = ct.SolutionArray(self.gas, 1, extra={"prop": [1, 2, 3]})
states.append(T=1100, P=3e5, X="AR:1.0", prop=[4, 5, 6])
self.assertEqual(states.shape, (2,))
def test_append_failures(self):
states = ct.SolutionArray(self.gas, 5, extra={"prop": "value"})
states.TPX = np.linspace(500, 1000, 5), 2e5, 'H2:0.5, O2:0.4'
self.assertEqual(states.shape, (5,))
with self.assertRaisesRegex(TypeError, "Missing keyword arguments for extra"):
states.append(T=1100, P=3e5, X="AR:1.0")
# Failing to append a state shouldn't change the size
self.assertEqual(states.shape, (5,))
with self.assertRaisesRegex(KeyError, "does not specify"):
# I is not a valid property
states.append(TPI=(1100, 3e5, "AR:1.0"), prop="value2")
# Failing to append a state shouldn't change the size
self.assertEqual(states.shape, (5,))
with self.assertRaisesRegex(KeyError, "is not a valid"):
# I is not a valid property
states.append(T=1100, P=3e5, I="AR:1.0", prop="value2")
# Failing to append a state shouldn't change the size
self.assertEqual(states.shape, (5,))
with self.assertRaisesRegex(ct.CanteraError, "incompatible value"):
# prop has incompatible dimensions
states.append(T=1100, P=3e5, X="AR:1.0", prop=[1, 2, 3])
# Failing to append a state shouldn't change the size
self.assertEqual(states.shape, (5,))
states = ct.SolutionArray(self.gas, 1, extra={"prop": [1, 2, 3]})
with self.assertRaisesRegex(ct.CanteraError, "incompatible value"):
# prop has incorrect type (no implicit conversion; changed from Cantera 2.6)
states.append(T=1100, P=3e5, X="AR:1.0", prop=['a', 'b', 'c'])
# Failing to append a state shouldn't change the size
self.assertEqual(states.shape, (1,))
states = ct.SolutionArray(self.gas, 1, extra={"prop": [1, 2, 3]})
with self.assertRaisesRegex(ct.CanteraError, "does not match"):
# prop has incorrect dimensions
states.append(T=1100, P=3e5, X="AR:1.0", prop=[1, 2])
# Failing to append a state shouldn't change the size
self.assertEqual(states.shape, (1,))
def test_purefluid(self):
water = ct.Water()
states = ct.SolutionArray(water, 5)
states.TQ = 400, np.linspace(0, 1, 5)
P = states.P
for i in range(1, 5):
self.assertNear(P[0], P[i])
states.TP = np.linspace(400, 500, 5), 101325
self.assertArrayNear(states.Q.squeeze(), np.ones(5))
def test_phase_of_matter(self):
water = ct.Water()
states = ct.SolutionArray(water, 5)
T = [300, 500, water.critical_temperature*2, 300]
P = [101325, 101325, 101325, water.critical_pressure*2]
states[:4].TP = T, P
states[4].TQ = 300, .4
pom = ['liquid', 'gas', 'supercritical', 'supercritical', 'liquid-gas-mix']
self.assertEqual(list(states.phase_of_matter), pom)
def test_purefluid_getters(self):
N = 11
water = ct.Water()
states = ct.SolutionArray(water, N)
getters = 'TDPUVHSQ' # omit getters that contain X or Y
# obtain setters from thermo objects
all_getters = [k for k in dir(water)
if not set(k) - set(getters) and len(k)>1]
# ensure that getters do not raise attribute errors
for g in all_getters:
out = getattr(states, g)
self.assertEqual(len(out), len(g))
def test_purefluid_yaml_state(self):
yaml = """
phases:
- name: water
thermo: pure-fluid
species: [{{liquidvapor.yaml/species: [H2O]}}]
state: {{T: {T}, P: 101325, Q: {Q}}}
pure-fluid-name: water
"""
w = ct.PureFluid(yaml=yaml.format(T=373.177233, Q=0.5))
self.assertNear(w.Q, 0.5)
with self.assertRaisesRegex(ct.CanteraError, "setState"):
ct.PureFluid(yaml=yaml.format(T=373, Q=0.5))
w = ct.PureFluid(yaml=yaml.format(T=370, Q=0.0))
self.assertNear(w.P, 101325)
with self.assertRaisesRegex(ct.CanteraError, "setState"):
ct.PureFluid(yaml=yaml.format(T=370, Q=1.0))
def test_sort(self):
np.random.seed(0)
t = np.random.random(101)
T = np.linspace(300., 1000., 101)
P = ct.one_atm * (1. + 10.*np.random.random(101))
states = ct.SolutionArray(self.gas, 101, extra={'t': t})
states.TP = T, P
states.sort('t')
self.assertTrue((states.t[1:] - states.t[:-1] > 0).all())
self.assertFalse((states.T[1:] - states.T[:-1] > 0).all())
self.assertFalse(np.allclose(states.P, P))
states.sort('T')
self.assertFalse((states.t[1:] - states.t[:-1] > 0).all())
self.assertTrue((states.T[1:] - states.T[:-1] > 0).all())
self.assertArrayNear(states.P, P)
states.sort('T', reverse=True)
self.assertTrue((states.T[1:] - states.T[:-1] < 0).all())
def test_set_equivalence_ratio(self):
states = ct.SolutionArray(self.gas, 8)
phi = np.linspace(0.5, 2, 8)
fuel, oxidizer = "H2:1.0", "O2:1.0"
# The mole fraction arrays need to be squeezed here to reduce their
# dimensionality from a 2-d column array to a vector for comparison
# with phi.
states.set_equivalence_ratio(phi, fuel, oxidizer)
comp = (states("H2").X / (2 * states("O2").X)).squeeze(1)
self.assertArrayNear(comp, phi)
states.set_equivalence_ratio(phi[0], fuel, oxidizer)
comp = (states("H2").X / (2 * states("O2").X)).squeeze(1)
self.assertArrayNear(comp, np.full_like(phi, phi[0]))
states.set_equivalence_ratio(phi.tolist(), fuel, oxidizer)
comp = (states("H2").X / (2 * states("O2").X)).squeeze(1)
self.assertArrayNear(comp, phi)
def test_set_equivalence_ratio_wrong_shape_raises(self):
states = ct.SolutionArray(self.gas, 8)
phi = np.linspace(0.5, 2, 7)
fuel, oxidizer = "H2:1.0", "O2:1.0"
with self.assertRaisesRegex(ValueError, r"shape mismatch"):
states.set_equivalence_ratio(phi, fuel, oxidizer)
def test_set_equivalence_ratio_2d(self):
states = ct.SolutionArray(self.gas, (2, 4))
phi = np.linspace(0.5, 2, 8).reshape((2, 4))
fuel, oxidizer = "H2:1.0", "O2:1.0"
# The mole fraction arrays need to be squeezed here to reduce their
# dimensionality from a 2-d column array to a vector for comparison
# with phi.
states.set_equivalence_ratio(phi, fuel, oxidizer)
comp = (states("H2").X / (2 * states("O2").X)).squeeze(2)
self.assertArrayNear(comp, phi)
def test_set_mixture_fraction(self):
states = ct.SolutionArray(self.gas, 8)
mixture_fraction = np.linspace(0.5, 1, 8)
fuel, oxidizer = "H2:1.0", "O2:1.0"
# The mass fraction arrays need to be squeezed here to reduce their
# dimensionality from a 2-d column array to a vector for comparison
# with mixture_fraction.
states.set_mixture_fraction(mixture_fraction, fuel, oxidizer)
self.assertArrayNear(states("H2").Y.squeeze(1), mixture_fraction)
states.set_mixture_fraction(mixture_fraction[0], fuel, oxidizer)
self.assertArrayNear(
states("H2").Y.squeeze(1),
np.full_like(mixture_fraction, mixture_fraction[0]),
)
states.set_mixture_fraction(mixture_fraction.tolist(), fuel, oxidizer)
self.assertArrayNear(states("H2").Y.squeeze(1), mixture_fraction)
def test_set_mixture_fraction_wrong_shape_raises(self):
states = ct.SolutionArray(self.gas, 8)
mixture_fraction = np.linspace(0.5, 1, 7)
fuel, oxidizer = "H2:1.0", "O2:1.0"
with self.assertRaisesRegex(ValueError, r"shape mismatch"):
states.set_mixture_fraction(mixture_fraction, fuel, oxidizer)
def test_set_mixture_fraction_2D(self):
states = ct.SolutionArray(self.gas, (2, 4))
mixture_fraction = np.linspace(0.5, 1, 8).reshape((2, 4))
fuel, oxidizer = "H2:1.0", "O2:1.0"
states.set_mixture_fraction(mixture_fraction, fuel, oxidizer)
# The mass fraction array needs to be squeezed here to reduce its
# dimensionality from a 3-d array to a 2-d array for comparison
# with mixture_fraction.
self.assertArrayNear(states("H2").Y.squeeze(2), mixture_fraction)
def test_species_slicing(self):
states = ct.SolutionArray(self.gas, (2,5))
states.TPX = np.linspace(500, 1000, 5), 2e5, 'H2:0.5, O2:0.4'
states.equilibrate('HP')
self.assertArrayNear(states('H2').X.squeeze(),
states.X[...,self.gas.species_index('H2')])
kk = (self.gas.species_index('OH'), self.gas.species_index('O'))
self.assertArrayNear(states('OH','O').partial_molar_cp,
states.partial_molar_cp[...,kk])
def test_slice_SolutionArray(self):
soln = ct.SolutionArray(self.gas, 10)
arr = soln[2:9:3]
self.assertEqual(len(arr.T), 3)
def test_zero_length_slice_SolutionArray(self):
states = ct.SolutionArray(self.gas, 4)
arr1 = states[3:3]
self.assertEqual(len(arr1.T), 0)
self.assertEqual(arr1.X.shape, (0,10))
self.assertEqual(arr1.n_reactions, 29)
states.TP = [100,300,900,323.23], ct.one_atm
arr2 = states[slice(0)]
self.assertEqual(len(arr2.T), 0)