mirror of
https://github.com/Cantera/cantera.git
synced 2025-02-25 18:55:29 -06:00
1155 lines
44 KiB
Python
1155 lines
44 KiB
Python
import sys
|
|
|
|
import numpy as np
|
|
from collections import OrderedDict
|
|
import pickle
|
|
|
|
import pytest
|
|
from .utilities import allow_deprecated
|
|
|
|
import cantera as ct
|
|
|
|
try:
|
|
ct.composite._import_pandas()
|
|
except ImportError:
|
|
pass
|
|
|
|
try:
|
|
ct.composite._import_h5py()
|
|
except ImportError:
|
|
pass
|
|
|
|
from cantera.composite import _pandas, _h5py
|
|
from . import utilities
|
|
|
|
|
|
class TestModels(utilities.CanteraTest):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
utilities.CanteraTest.setUpClass()
|
|
cls.yml_file = cls.test_data_path / "thermo-models.yaml"
|
|
cls.yml = utilities.load_yaml(cls.yml_file)
|
|
|
|
def test_load_thermo_models(self):
|
|
for ph in self.yml['phases']:
|
|
ph_name = ph['name']
|
|
try:
|
|
sol = ct.Solution(self.yml_file, ph_name)
|
|
|
|
T0, p0 = sol.TP
|
|
TD = sol.TD
|
|
z = sol.state # calls Phase::saveState
|
|
sol.TP = 300, 2*ct.one_atm
|
|
sol.state = z # calls Phase::restoreState
|
|
self.assertEqual(sol.T, T0)
|
|
self.assertEqual(sol.P, p0)
|
|
|
|
if sol.thermo_model in ('pure-fluid',):
|
|
self.assertTrue(sol.has_phase_transition)
|
|
else:
|
|
self.assertFalse(sol.has_phase_transition)
|
|
|
|
if not sol.is_compressible:
|
|
with self.assertRaisesRegex(ct.CanteraError,
|
|
'Density is not an independent'):
|
|
sol.TD = TD
|
|
|
|
self.assertEqual(len(z), sol.state_size)
|
|
if sol.is_pure:
|
|
# stoich phase (fixed composition)
|
|
self.assertEqual(sol.n_species, 1)
|
|
self.assertEqual(len(z), 2)
|
|
else:
|
|
self.assertEqual(len(z), 2 + sol.n_species)
|
|
|
|
except Exception as inst:
|
|
|
|
# raise meaningful error message without breaking test suite
|
|
# ignore deprecation warnings originating in C++ layer
|
|
# (converted to errors in test suite)
|
|
if 'Deprecated' not in str(inst):
|
|
|
|
msg = ("Error in processing of phase '{}' with type '{}'\n"
|
|
"TPX = {}")
|
|
msg = msg.format(ph['name'], ph['thermo'], sol.TPX)
|
|
raise TypeError(msg) from inst
|
|
|
|
def test_restore_thermo_models(self):
|
|
|
|
def check(a, b):
|
|
self.assertArrayNear(a.T, b.T)
|
|
self.assertArrayNear(a.P, b.P)
|
|
self.assertArrayNear(a.X, b.X)
|
|
|
|
for ph in self.yml['phases']:
|
|
|
|
skipped = ['pure-fluid']
|
|
if ph['thermo'] in skipped:
|
|
continue
|
|
|
|
ph_name = ph['name']
|
|
|
|
try:
|
|
sol = ct.Solution(self.yml_file, ph_name)
|
|
a = ct.SolutionArray(sol, 10)
|
|
if ph['thermo'] == 'liquid-water-IAPWS95':
|
|
# ensure that phase remains liquid
|
|
a.TP = sol.T, sol.critical_pressure
|
|
|
|
# assign some state
|
|
T = 373.15 + 100*np.random.rand(10)
|
|
P = a.P * (1 + np.random.rand(10))
|
|
if sol.is_pure:
|
|
a.TP = T, P
|
|
else:
|
|
X = a.X
|
|
xmin = np.min(X[X>0])
|
|
ix = np.where(xmin)
|
|
X[ix] = .5 * X[ix]
|
|
X = np.diag(X.sum(axis=1)).dot(X)
|
|
self.assertFalse(sol.is_pure)
|
|
self.assertIn('TPX', sol._full_states.values())
|
|
a.TPX = T, P, X
|
|
|
|
# default columns
|
|
data = a.collect_data()
|
|
b = ct.SolutionArray(sol)
|
|
b.restore_data(data)
|
|
check(a, b)
|
|
|
|
except Exception as inst:
|
|
|
|
# raise meaningful error message without breaking test suite
|
|
# ignore deprecation warnings originating in C++ layer
|
|
# (converted to errors in test suite)
|
|
if 'Deprecated' not in str(inst):
|
|
|
|
msg = ("Error in processing of phase '{}' with type '{}'\n"
|
|
"TPX = {}")
|
|
msg = msg.format(ph['name'], ph['thermo'], sol.TPX)
|
|
raise TypeError(msg) from inst
|
|
|
|
|
|
class TestPickle(utilities.CanteraTest):
|
|
|
|
def test_pickle_gas(self):
|
|
gas = ct.Solution("h2o2.yaml", transport_model=None)
|
|
gas.TPX = 500, 500000, "H2:.75,O2:.25"
|
|
with open(self.test_work_path / "gas.pkl", "wb") as pkl:
|
|
pickle.dump(gas, pkl)
|
|
|
|
with open(self.test_work_path / "gas.pkl", "rb") as pkl:
|
|
gas2 = pickle.load(pkl)
|
|
self.assertNear(gas.T, gas2.T)
|
|
self.assertNear(gas.P, gas2.P)
|
|
self.assertArrayNear(gas.X, gas2.X)
|
|
|
|
self.assertEqual(gas2.transport_model, "none")
|
|
|
|
def test_pickle_gas_with_transport(self):
|
|
gas = ct.Solution("h2o2.yaml")
|
|
gas.TPX = 500, 500000, "H2:.75,O2:.25"
|
|
gas.transport_model = "multicomponent"
|
|
with open(self.test_work_path / "gas.pkl", "wb") as pkl:
|
|
pickle.dump(gas, pkl)
|
|
|
|
with open(self.test_work_path / "gas.pkl", "rb") as pkl:
|
|
gas2 = pickle.load(pkl)
|
|
self.assertNear(gas.T, gas2.T)
|
|
self.assertNear(gas.P, gas2.P)
|
|
self.assertArrayNear(gas.X, gas2.X)
|
|
|
|
self.assertEqual(gas2.transport_model, "multicomponent")
|
|
|
|
def test_pickle_interface(self):
|
|
gas = ct.Solution("diamond.yaml", "gas")
|
|
solid = ct.Solution("diamond.yaml", "diamond")
|
|
interface = ct.Interface("diamond.yaml", "diamond_100", (gas, solid))
|
|
|
|
with self.assertRaises(NotImplementedError):
|
|
with open(self.test_work_path / "interface.pkl", "wb") as pkl:
|
|
pickle.dump(interface, pkl)
|
|
|
|
|
|
class TestEmptyThermoPhase(utilities.CanteraTest):
|
|
""" Test empty Solution object """
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
utilities.CanteraTest.setUpClass()
|
|
cls.gas = ct.ThermoPhase()
|
|
|
|
def test_empty_report(self):
|
|
with pytest.raises(NotImplementedError):
|
|
self.gas()
|
|
|
|
def test_empty_TP(self):
|
|
with pytest.raises(NotImplementedError):
|
|
self.gas.TP = 300, ct.one_atm
|
|
|
|
def test_empty_equilibrate(self):
|
|
with pytest.raises(NotImplementedError):
|
|
self.gas.equilibrate("TP")
|
|
|
|
|
|
class TestEmptySolution(TestEmptyThermoPhase):
|
|
""" Test empty Solution object """
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
utilities.CanteraTest.setUpClass()
|
|
cls.gas = ct.Solution()
|
|
|
|
def test_empty_composite(self):
|
|
self.assertEqual(self.gas.thermo_model, "none")
|
|
self.assertEqual(self.gas.composite, ("none", "none", "none"))
|
|
|
|
|
|
class TestEmptyEdgeCases(utilities.CanteraTest):
|
|
""" Test for edge cases where constructors are not allowed """
|
|
def test_empty_phase(self):
|
|
with self.assertRaisesRegex(ValueError, "Arguments are insufficient to define a phase"):
|
|
ct.ThermoPhase(thermo="ideal-gas")
|
|
|
|
def test_empty_kinetics(self):
|
|
with self.assertRaisesRegex(ValueError, "Cannot instantiate"):
|
|
ct.Kinetics()
|
|
|
|
def test_empty_transport(self):
|
|
with self.assertRaisesRegex(ValueError, "Cannot instantiate"):
|
|
ct.Transport()
|
|
|
|
|
|
class TestSolutionArray(utilities.CanteraTest):
|
|
""" Test SolutionArray file basics """
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
utilities.CanteraTest.setUpClass()
|
|
cls.gas = ct.Solution('h2o2.yaml', transport_model=None)
|
|
|
|
def test_from_state_scalar(self):
|
|
state = list(self.gas.state)
|
|
arr = ct.SolutionArray(self.gas, states=[state])
|
|
assert arr.shape == (1,)
|
|
|
|
def test_from_state_list(self):
|
|
states = [list(self.gas.state)] * 5
|
|
arr = ct.SolutionArray(self.gas, states=states)
|
|
assert arr.shape == (5,)
|
|
|
|
def test_from_state_array(self):
|
|
states = [[list(self.gas.state)] * 5] * 3
|
|
arr = ct.SolutionArray(self.gas, states=states)
|
|
assert arr.shape == (3, 5) # shape is based on numpy conversion
|
|
|
|
def test_from_state_numpy(self):
|
|
states = np.array([[list(self.gas.state)] * 5] * 3)
|
|
arr = ct.SolutionArray(self.gas, states=states)
|
|
assert arr.shape == (3, 5)
|
|
|
|
def test_missing_attribute(self):
|
|
arr = ct.SolutionArray(self.gas, 5, extra={"spam": 0})
|
|
assert len(arr.spam) == 5
|
|
with pytest.raises(AttributeError, match="no attribute"):
|
|
arr.eggs
|
|
|
|
def test_auxiliary(self):
|
|
arr = ct.SolutionArray(self.gas, 5, extra={"spam": 0})
|
|
arr.spam = np.arange(5)
|
|
assert len(arr.spam) == 5
|
|
assert arr.get_auxiliary(4) == {"spam": 4}
|
|
arr.set_auxiliary(0, {"spam": 42})
|
|
assert arr.spam[0] == 42
|
|
|
|
|
|
class TestSolutionArrayIO(utilities.CanteraTest):
|
|
""" Test SolutionArray file IO """
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
utilities.CanteraTest.setUpClass()
|
|
cls.gas = ct.Solution('h2o2.yaml', transport_model=None)
|
|
|
|
def test_collect_data(self):
|
|
states = ct.SolutionArray(self.gas)
|
|
collected = states.collect_data(tabular=True)
|
|
self.assertIsInstance(collected, dict)
|
|
self.assertIn('Y_H2', collected)
|
|
self.assertEqual(len(collected['Y_H2']), 0)
|
|
|
|
states = ct.SolutionArray(self.gas)
|
|
collected = states.collect_data(tabular=False, species='X')
|
|
self.assertIn('X', collected)
|
|
self.assertEqual(collected['X'].shape, (0, self.gas.n_species))
|
|
|
|
def test_getitem(self):
|
|
states = ct.SolutionArray(self.gas, 10, extra={"index": range(10)})
|
|
for ix, state in enumerate(states):
|
|
assert state.index == ix
|
|
|
|
assert list(states[:2].index) == [0, 1]
|
|
assert list(states[100:102].index) == [] # outside of range
|
|
|
|
def test_append_state(self):
|
|
gas = ct.Solution("h2o2.yaml")
|
|
gas.TPX = 300, ct.one_atm, 'H2:0.5, O2:0.4'
|
|
states = ct.SolutionArray(gas)
|
|
states.append(gas.state)
|
|
self.assertEqual(states[0].T, gas.T)
|
|
self.assertEqual(states[0].P, gas.P)
|
|
self.assertArrayNear(states[0].X, gas.X)
|
|
self.assertEqual(len(states), 1)
|
|
self.assertEqual(states.shape, (1,))
|
|
self.assertEqual(states.ndim, 1)
|
|
self.assertEqual(states.size, 1)
|
|
|
|
def test_append_no_norm_data(self):
|
|
gas = ct.Solution("h2o2.yaml")
|
|
gas.TP = 300, ct.one_atm
|
|
gas.set_unnormalized_mass_fractions(np.full(gas.n_species, 0.3))
|
|
states = ct.SolutionArray(gas)
|
|
states.append(T=gas.T, P=gas.P, Y=gas.Y, normalize=False)
|
|
self.assertEqual(states[0].T, gas.T)
|
|
self.assertEqual(states[0].P, gas.P)
|
|
self.assertArrayNear(states[0].Y, gas.Y)
|
|
|
|
@pytest.mark.skipif("native" not in ct.hdf_support(),
|
|
reason="Cantera compiled without HDF support")
|
|
def test_import_no_norm_data(self):
|
|
outfile = self.test_work_path / "solutionarray_no_norm.h5"
|
|
outfile.unlink(missing_ok=True)
|
|
|
|
gas = ct.Solution("h2o2.yaml")
|
|
gas.set_unnormalized_mole_fractions(np.full(gas.n_species, 0.3))
|
|
states = ct.SolutionArray(gas, 5)
|
|
states.save(outfile, "group0")
|
|
|
|
gas_new = ct.Solution("h2o2.yaml")
|
|
b = ct.SolutionArray(gas_new)
|
|
b.restore(outfile, "group0") #, normalize=False)
|
|
self.assertArrayNear(states.T, b.T)
|
|
self.assertArrayNear(states.P, b.P)
|
|
self.assertArrayNear(states.X, b.X)
|
|
|
|
def test_write_csv(self):
|
|
states = ct.SolutionArray(self.gas, 7)
|
|
states.TPX = np.linspace(300, 1000, 7), 2e5, 'H2:0.5, O2:0.4'
|
|
states.equilibrate('HP')
|
|
|
|
outfile = self.test_work_path / "solutionarray.csv"
|
|
states.write_csv(outfile)
|
|
|
|
data = np.genfromtxt(outfile, names=True, delimiter=',')
|
|
self.assertEqual(len(data), 7)
|
|
self.assertEqual(len(data.dtype), self.gas.n_species + 2)
|
|
self.assertIn('Y_H2', data.dtype.fields)
|
|
|
|
b = ct.SolutionArray(self.gas)
|
|
b.read_csv(outfile)
|
|
self.assertArrayNear(states.T, b.T)
|
|
self.assertArrayNear(states.P, b.P)
|
|
self.assertArrayNear(states.X, b.X)
|
|
|
|
def test_write_csv_single_row(self):
|
|
gas = ct.Solution("gri30.yaml")
|
|
states = ct.SolutionArray(gas)
|
|
states.append(T=300., P=ct.one_atm, X="CH4:0.5, O2:0.4")
|
|
states.equilibrate("HP")
|
|
|
|
outfile = self.test_work_path / "solutionarray.csv"
|
|
states.write_csv(outfile)
|
|
|
|
b = ct.SolutionArray(gas)
|
|
b.read_csv(outfile)
|
|
self.assertArrayNear(states.T, b.T)
|
|
self.assertArrayNear(states.P, b.P)
|
|
self.assertArrayNear(states.X, b.X)
|
|
|
|
def test_write_csv_str_column(self):
|
|
states = ct.SolutionArray(self.gas, 3, extra={'spam': 'eggs'})
|
|
|
|
outfile = self.test_work_path / "solutionarray.csv"
|
|
states.write_csv(outfile)
|
|
|
|
b = ct.SolutionArray(self.gas, extra={'spam'})
|
|
b.read_csv(outfile)
|
|
self.assertEqual(list(states.spam), list(b.spam))
|
|
|
|
def test_write_csv_multidim_column(self):
|
|
states = ct.SolutionArray(self.gas, 3, extra={'spam': np.zeros((3, 5,))})
|
|
|
|
outfile = self.test_work_path / "solutionarray.csv"
|
|
with self.assertRaisesRegex(NotImplementedError, 'not supported'):
|
|
states.write_csv(outfile)
|
|
|
|
@utilities.unittest.skipIf(_pandas is None, "pandas is not installed")
|
|
def test_to_pandas(self):
|
|
states = ct.SolutionArray(self.gas, 7, extra={"props": range(7)})
|
|
states.TPX = np.linspace(300, 1000, 7), 2e5, 'H2:0.5, O2:0.4'
|
|
df = states.to_pandas()
|
|
self.assertEqual(df.shape[0], 7)
|
|
states.props = np.zeros((7,2,))
|
|
with self.assertRaisesRegex(NotImplementedError, 'not supported'):
|
|
states.to_pandas()
|
|
|
|
@pytest.mark.skipif("native" not in ct.hdf_support(),
|
|
reason="Cantera compiled without HDF support")
|
|
@utilities.unittest.skipIf(_h5py is None, "h5py is not installed")
|
|
def test_write_hdf(self):
|
|
outfile = self.test_work_path / "solutionarray_fancy.h5"
|
|
outfile.unlink(missing_ok=True)
|
|
|
|
extra = {'foo': range(7), 'bar': range(7)}
|
|
meta = {'spam': 'eggs', 'hello': 'world'}
|
|
states = ct.SolutionArray(self.gas, 7, extra=extra, meta=meta)
|
|
states.TPX = np.linspace(300, 1000, 7), 2e5, 'H2:0.5, O2:0.4'
|
|
states.equilibrate('HP')
|
|
|
|
states.save(outfile, "group0")
|
|
|
|
b = ct.SolutionArray(self.gas)
|
|
attr = b.restore(outfile, "group0")
|
|
self.assertArrayNear(states.T, b.T)
|
|
self.assertArrayNear(states.P, b.P)
|
|
self.assertArrayNear(states.X, b.X)
|
|
self.assertArrayNear(states.foo, b.foo)
|
|
self.assertArrayNear(states.bar, b.bar)
|
|
self.assertEqual(b.meta['spam'], 'eggs')
|
|
self.assertEqual(b.meta['hello'], 'world')
|
|
|
|
@pytest.mark.skipif("native" not in ct.hdf_support(),
|
|
reason="Cantera compiled without HDF support")
|
|
def test_write_hdf_str_column(self):
|
|
self.run_write_str_column("h5")
|
|
|
|
def test_write_yaml_str_column(self):
|
|
self.run_write_str_column("yaml")
|
|
|
|
def run_write_str_column(self, mode):
|
|
outfile = self.test_work_path / f"solutionarray_str.{mode}"
|
|
outfile.unlink(missing_ok=True)
|
|
|
|
states = ct.SolutionArray(self.gas, 3, extra={'spam': 'eggs'})
|
|
states.save(outfile, "arr")
|
|
|
|
b = ct.SolutionArray(self.gas, extra={'spam'})
|
|
b.restore(outfile, "arr")
|
|
self.assertEqual(list(states.spam), list(b.spam))
|
|
|
|
@pytest.mark.skipif("native" not in ct.hdf_support(),
|
|
reason="Cantera compiled without HDF support")
|
|
def test_write_hdf_multidim_column(self):
|
|
self.run_write_multidim_column("h5")
|
|
|
|
def test_write_yaml_multidim_column(self):
|
|
self.run_write_multidim_column("yaml")
|
|
|
|
def run_write_multidim_column(self, mode):
|
|
outfile = self.test_work_path / f"solutionarray_multi.{mode}"
|
|
outfile.unlink(missing_ok=True)
|
|
|
|
states = ct.SolutionArray(self.gas, 3, extra={'spam': [[1, 2], [3, 4], [5, 6]]})
|
|
states.save(outfile, "arr")
|
|
|
|
b = ct.SolutionArray(self.gas, extra={'spam'})
|
|
b.restore(outfile, "arr")
|
|
self.assertArrayNear(states.spam, b.spam)
|
|
|
|
@pytest.mark.skipif("native" not in ct.hdf_support(),
|
|
reason="Cantera compiled without HDF support")
|
|
def test_write_hdf_2d(self):
|
|
self.run_write_2d("h5")
|
|
|
|
def test_write_yaml_2d(self):
|
|
self.run_write_2d("yaml")
|
|
|
|
def run_write_2d(self, mode):
|
|
outfile = self.test_work_path / f"solutionarray_2d.{mode}"
|
|
outfile.unlink(missing_ok=True)
|
|
|
|
states = ct.SolutionArray(self.gas, (2, 5))
|
|
states.save(outfile, "arr")
|
|
|
|
b = ct.SolutionArray(self.gas)
|
|
b.restore(outfile, "arr")
|
|
assert b.shape == states.shape
|
|
|
|
class TestLegacyHDF(utilities.CanteraTest):
|
|
# Test SolutionArray legacy HDF file input
|
|
#
|
|
# All input files were created using the Cantera 2.6 Python test suite:
|
|
# - solutionarray_fancy_legacy.h5
|
|
# -> test_composite.py::TestSolutionArrayIO::test_write_hdf
|
|
# - solutionarray_str_legacy.h5
|
|
# -> test_composite.py::TestSolutionArrayIO::test_write_hdf_str_column
|
|
# - solutionarray_multi_legacy.h5
|
|
# -> test_composite.py::TestSolutionArrayIO::test_write_hdf_multi_column
|
|
# - solutionarray_no_norm_legacy.h5
|
|
# -> test_composite.py::TestSolutionArrayIO::test_import_no_norm_data
|
|
# - solutionarray_water_legacy.h5
|
|
# -> test_composite.py::TestRestorePureFluid::test_import_no_norm_water
|
|
|
|
def setUp(self):
|
|
self.gas = ct.Solution('h2o2.yaml', transport_model=None)
|
|
|
|
@pytest.mark.usefixtures("allow_deprecated")
|
|
@pytest.mark.skipif("h5py" not in ct.hdf_support(), reason="h5py is not installed")
|
|
def test_legacy_hdf_str_column_h5py(self):
|
|
self.run_read_legacy_hdf_str_column(legacy=True)
|
|
|
|
@pytest.mark.xfail(reason="Unable to read fixed length strings from HDF")
|
|
@pytest.mark.skipif("native" not in ct.hdf_support(),
|
|
reason="Cantera compiled without HDF support")
|
|
def test_legacy_hdf_str_column(self):
|
|
# h5py writes strings with fixed length, which require a priori knowledge of
|
|
# length in order to be read with HighFive (which currently only supports
|
|
# fixed string lengths based on compile-time templates)
|
|
self.run_read_legacy_hdf_str_column()
|
|
|
|
def run_read_legacy_hdf_str_column(self, legacy=False):
|
|
# recreate states used to create legacy HDF file
|
|
arr = ct.SolutionArray(self.gas, 3, extra={'spam': 'eggs'})
|
|
|
|
b = ct.SolutionArray(self.gas, extra={'spam'})
|
|
infile = self.test_data_path / f"solutionarray_str_legacy.h5"
|
|
|
|
if legacy:
|
|
b.read_hdf(infile)
|
|
else:
|
|
b.restore(infile, "group0")
|
|
assert all(arr.spam == b.spam)
|
|
|
|
@pytest.mark.usefixtures("allow_deprecated")
|
|
@pytest.mark.skipif("h5py" not in ct.hdf_support(), reason="h5py is not installed")
|
|
def test_legacy_hdf_multidim_h5py(self):
|
|
self.run_read_legacy_hdf_multidim(legacy=True)
|
|
|
|
@pytest.mark.skipif("native" not in ct.hdf_support(),
|
|
reason="Cantera compiled without HDF support")
|
|
def test_legacy_hdf_multidim(self):
|
|
self.run_read_legacy_hdf_multidim()
|
|
|
|
def run_read_legacy_hdf_multidim(self, legacy=False):
|
|
# recreate states used to create legacy HDF file
|
|
arr = ct.SolutionArray(self.gas, 3, extra={'spam': [[1, 2], [3, 4], [5, 6]]})
|
|
b = ct.SolutionArray(self.gas, extra={'spam'})
|
|
infile = self.test_data_path / f"solutionarray_multi_legacy.h5"
|
|
|
|
if legacy:
|
|
b.read_hdf(infile)
|
|
else:
|
|
b.restore(infile, "group0")
|
|
self.assertArrayNear(arr.spam, b.spam)
|
|
|
|
@pytest.mark.usefixtures("allow_deprecated")
|
|
@pytest.mark.skipif(ct.hdf_support() != {"native", "h5py"},
|
|
reason="Both HDF support modes needed")
|
|
def test_deprecated_write_read_hdf(self):
|
|
# recreate states used to create legacy HDF file
|
|
arr = ct.SolutionArray(self.gas, 3, extra={'spam': [[1, 2], [3, 4], [5, 6]]})
|
|
outfile = self.test_work_path / "solutionarray_deprecated.h5"
|
|
outfile.unlink(missing_ok=True)
|
|
|
|
with pytest.raises(KeyError, match="Missing required parameter 'group'"):
|
|
arr.write_hdf(outfile, "group0")
|
|
|
|
with pytest.warns(DeprecationWarning, match="use 'save' instead"):
|
|
# New HDF format is written regardless via 'save'
|
|
arr.write_hdf(outfile, group="group0")
|
|
|
|
b = ct.SolutionArray(self.gas)
|
|
with pytest.raises(IOError, match="use 'restore' instead"):
|
|
# New HDF format should not be read with 'read_hdf'
|
|
with pytest.warns(DeprecationWarning, match="use 'restore' instead"):
|
|
# DeprecationWarning is triggered before IOError is raised
|
|
b.read_hdf(outfile, group="group0")
|
|
|
|
meta = b.restore(outfile, "group0")
|
|
assert meta["generator"] == "Cantera SolutionArray"
|
|
self.assertArrayNear(arr.spam, b.spam)
|
|
|
|
@pytest.mark.usefixtures("allow_deprecated")
|
|
@pytest.mark.skipif("h5py" not in ct.hdf_support(), reason="h5py is not installed")
|
|
def test_legacy_hdf_h5py(self):
|
|
self.run_legacy_hdf(legacy=True)
|
|
|
|
@pytest.mark.skipif("native" not in ct.hdf_support(),
|
|
reason="Cantera compiled without HDF support")
|
|
def test_legacy_hdf(self):
|
|
self.run_legacy_hdf()
|
|
|
|
def run_legacy_hdf(self, legacy=False):
|
|
# recreate states used to create legacy HDF file (valid portion)
|
|
extra = {'foo': range(7), 'bar': range(7)}
|
|
meta = {'spam': 'eggs', 'hello': 'world'}
|
|
states = ct.SolutionArray(self.gas, 7, extra=extra, meta=meta)
|
|
states.TPX = np.linspace(300, 1000, 7), 2e5, 'H2:0.5, O2:0.4'
|
|
states.equilibrate('HP')
|
|
|
|
infile = self.test_data_path / f"solutionarray_fancy_legacy.h5"
|
|
b = ct.SolutionArray(self.gas)
|
|
if legacy:
|
|
attr = b.read_hdf(infile)
|
|
else:
|
|
attr = b.restore(infile, "group0")
|
|
self.assertArrayNear(states.T, b.T)
|
|
self.assertArrayNear(states.P, b.P)
|
|
self.assertArrayNear(states.X, b.X)
|
|
self.assertArrayNear(states.foo, b.foo)
|
|
self.assertArrayNear(states.bar, b.bar)
|
|
self.assertEqual(b.meta['spam'], 'eggs')
|
|
self.assertEqual(b.meta['hello'], 'world')
|
|
self.assertEqual(attr['foobar'], 'spam and eggs')
|
|
|
|
@pytest.mark.usefixtures("allow_deprecated")
|
|
@pytest.mark.skipif("h5py" not in ct.hdf_support(), reason="h5py is not installed")
|
|
def test_read_legacy_hdf_no_norm_h5py(self):
|
|
self.run_read_legacy_hdf_no_norm(legacy=True)
|
|
|
|
@pytest.mark.skipif("native" not in ct.hdf_support(),
|
|
reason="Cantera compiled without HDF support")
|
|
def test_read_legacy_hdf_no_norm(self):
|
|
self.run_read_legacy_hdf_no_norm()
|
|
|
|
def run_read_legacy_hdf_no_norm(self, legacy=False):
|
|
# recreate states used to create legacy HDF file
|
|
self.gas.set_unnormalized_mole_fractions(np.full(self.gas.n_species, 0.3))
|
|
states = ct.SolutionArray(self.gas, 5)
|
|
|
|
infile = self.test_data_path / "solutionarray_no_norm_legacy.h5"
|
|
|
|
b = ct.SolutionArray(self.gas)
|
|
if legacy:
|
|
b.read_hdf(infile, normalize=False)
|
|
else:
|
|
b.restore(infile, "group0")
|
|
self.assertArrayNear(states.T, b.T, rtol=1e-7)
|
|
self.assertArrayNear(states.P, b.P, rtol=1e-7)
|
|
self.assertArrayNear(states.X, b.X, rtol=1e-7)
|
|
|
|
@pytest.mark.usefixtures("allow_deprecated")
|
|
@pytest.mark.skipif("h5py" not in ct.hdf_support(), reason="h5py is not installed")
|
|
def test_import_no_norm_water_h5py(self):
|
|
self.run_import_no_norm_water(legacy=True)
|
|
|
|
@pytest.mark.skipif("native" not in ct.hdf_support(),
|
|
reason="Cantera compiled without HDF support")
|
|
def test_import_no_norm_water(self):
|
|
self.run_import_no_norm_water()
|
|
|
|
def run_import_no_norm_water(self, legacy=False):
|
|
# recreate states used to create legacy HDF file
|
|
w = ct.Water()
|
|
w.TQ = 300, 0.5
|
|
states = ct.SolutionArray(w, 5)
|
|
|
|
w_new = ct.Water()
|
|
infile = self.test_data_path / "solutionarray_water_legacy.h5"
|
|
c = ct.SolutionArray(w_new)
|
|
if legacy:
|
|
c.read_hdf(infile, normalize=False)
|
|
else:
|
|
c.restore(infile, "group0")
|
|
self.assertArrayNear(states.T, c.T, rtol=1e-7)
|
|
self.assertArrayNear(states.P, c.P, rtol=1e-7)
|
|
self.assertArrayNear(states.Q, c.Q, rtol=1e-7)
|
|
|
|
@pytest.mark.usefixtures("allow_deprecated")
|
|
@pytest.mark.skipif(ct.hdf_support() != {"native", "h5py"},
|
|
reason="Both HDF support modes needed")
|
|
def test_new_hdf_h5py_exception(self):
|
|
outfile = self.test_work_path / f"solutionarray_new.h5"
|
|
outfile.unlink(missing_ok=True)
|
|
|
|
states = ct.SolutionArray(self.gas, 3, extra={'spam': [[1, 2], [3, 4], [5, 6]]})
|
|
states.save(outfile, "arr")
|
|
|
|
b = ct.SolutionArray(self.gas, extra={'spam'})
|
|
with pytest.raises(IOError, match="Cantera 3.0 HDF format"):
|
|
b.read_hdf(outfile, "arr") # h5py file should not read new format
|
|
|
|
|
|
class TestRestoreIdealGas(utilities.CanteraTest):
|
|
""" Test restoring of the IdealGas class """
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
utilities.CanteraTest.setUpClass()
|
|
cls.gas = ct.Solution('h2o2.yaml', transport_model=None)
|
|
|
|
def test_restore_gas(self):
|
|
|
|
def check(a, b, atol=None):
|
|
if atol is None:
|
|
self.assertArrayNear(a.T, b.T)
|
|
self.assertArrayNear(a.P, b.P)
|
|
self.assertArrayNear(a.X, b.X)
|
|
else:
|
|
self.assertArrayNear(a.T, b.T, atol=atol)
|
|
self.assertArrayNear(a.P, b.P, atol=atol)
|
|
self.assertArrayNear(a.X, b.X, atol=atol)
|
|
|
|
# test ThermoPhase
|
|
a = ct.SolutionArray(self.gas)
|
|
for i in range(10):
|
|
T = 300 + 1800*np.random.random()
|
|
P = ct.one_atm*(1 + 10*np.random.random())
|
|
X = np.random.random(self.gas.n_species)
|
|
X[-1] = 0.
|
|
X /= X.sum()
|
|
a.append(T=T, P=P, X=X)
|
|
|
|
data = a.collect_data()
|
|
|
|
# basic restore
|
|
b = ct.SolutionArray(self.gas)
|
|
b.restore_data(data, normalize=True)
|
|
check(a, b)
|
|
|
|
# skip concentrations
|
|
b = ct.SolutionArray(self.gas)
|
|
b.restore_data({'T': data['T'], 'density': data['density']})
|
|
self.assertArrayNear(a.T, b.T)
|
|
self.assertArrayNear(a.density, b.density)
|
|
self.assertFalse(np.allclose(a.X, b.X))
|
|
|
|
# wrong data shape
|
|
b = ct.SolutionArray(self.gas)
|
|
with self.assertRaises(ValueError):
|
|
b.restore_data(OrderedDict([(k, v[np.newaxis, :])
|
|
for k, v in data.items()]))
|
|
|
|
# inconsistent shape of receiving SolutionArray
|
|
b = ct.SolutionArray(self.gas, 9)
|
|
with self.assertRaises(ValueError):
|
|
b.restore_data(data)
|
|
|
|
# incomplete state
|
|
b = ct.SolutionArray(self.gas)
|
|
with self.assertRaises(ValueError):
|
|
b.restore_data(OrderedDict([tup for i, tup in enumerate(data.items())
|
|
if i]))
|
|
|
|
# add extra column
|
|
t = np.arange(10, dtype=float)
|
|
|
|
# auto-detection of extra
|
|
b = ct.SolutionArray(self.gas)
|
|
data_mod = OrderedDict(data)
|
|
data_mod['time'] = t
|
|
b.restore_data(data_mod)
|
|
check(a, b)
|
|
|
|
# explicit extra
|
|
b = ct.SolutionArray(self.gas, extra=('time',))
|
|
b.restore_data(data_mod)
|
|
check(a, b)
|
|
self.assertArrayNear(b.time, t)
|
|
|
|
# wrong extra
|
|
b = ct.SolutionArray(self.gas, extra=('xyz',))
|
|
with self.assertRaises(KeyError):
|
|
b.restore_data(data_mod)
|
|
|
|
# missing extra
|
|
b = ct.SolutionArray(self.gas, extra=('time'))
|
|
with self.assertRaises(KeyError):
|
|
b.restore_data(data)
|
|
|
|
# inconsistent species
|
|
data_mod = a.collect_data(tabular=True)
|
|
val = data_mod.pop('Y_AR')
|
|
data_mod['Y_invalid'] = val
|
|
b = ct.SolutionArray(self.gas)
|
|
with self.assertRaises(ValueError):
|
|
b.restore_data(data_mod)
|
|
|
|
# incomplete species info (using threshold)
|
|
data = a.collect_data(threshold=1e-6)
|
|
|
|
# basic restore
|
|
b = ct.SolutionArray(self.gas)
|
|
b.restore_data(data)
|
|
check(a, b, atol=1e-6)
|
|
|
|
# skip calculated properties
|
|
cols = ('T', 'P', 'X', 'gibbs_mass', 'forward_rates_of_progress')
|
|
data = a.collect_data(cols=cols, threshold=1e-6)
|
|
|
|
b = ct.SolutionArray(self.gas)
|
|
b.restore_data(data)
|
|
check(a, b)
|
|
self.assertEqual(len(b.extra), 0)
|
|
|
|
|
|
class TestRestorePureFluid(utilities.CanteraTest):
|
|
""" Test restoring of the PureFluid class """
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
utilities.CanteraTest.setUpClass()
|
|
cls.water = ct.Water()
|
|
|
|
def test_restore_water(self):
|
|
|
|
def check(a, b):
|
|
self.assertArrayNear(a.T, b.T)
|
|
self.assertArrayNear(a.P, b.P)
|
|
self.assertArrayNear(a.Q, b.Q)
|
|
|
|
self.assertTrue(self.water.has_phase_transition)
|
|
|
|
# benchmark
|
|
a = ct.SolutionArray(self.water, 10)
|
|
a.TQ = 373.15, np.linspace(0., 1., 10)
|
|
|
|
# complete data
|
|
cols = ('T', 'P', 'Q')
|
|
data = a.collect_data(cols=cols)
|
|
b = ct.SolutionArray(self.water)
|
|
b.restore_data(data)
|
|
check(a, b)
|
|
|
|
# partial data
|
|
cols = ('T', 'Q')
|
|
data = a.collect_data(cols=cols)
|
|
b = ct.SolutionArray(self.water)
|
|
b.restore_data(data)
|
|
check(a, b)
|
|
|
|
# default columns
|
|
data = a.collect_data()
|
|
self.assertEqual(list(data.keys()), ['T', 'density'])
|
|
b = ct.SolutionArray(self.water)
|
|
b.restore_data(data)
|
|
check(a, b)
|
|
|
|
# default state plus Y
|
|
cols = ('T', 'D', 'Y')
|
|
data = a.collect_data(cols=cols)
|
|
b = ct.SolutionArray(self.water)
|
|
b.restore_data(data)
|
|
check(a, b)
|
|
|
|
@pytest.mark.skipif("native" not in ct.hdf_support(),
|
|
reason="Cantera compiled without HDF support")
|
|
def test_import_no_norm_water(self):
|
|
outfile = self.test_work_path / "solutionarray_water.h5"
|
|
outfile.unlink(missing_ok=True)
|
|
|
|
w = ct.Water()
|
|
w.TQ = 300, 0.5
|
|
states = ct.SolutionArray(w, 5)
|
|
states.save(outfile, "group0")
|
|
|
|
w_new = ct.Water()
|
|
c = ct.SolutionArray(w_new)
|
|
c.restore(outfile, "group0") # normalize=False)
|
|
self.assertArrayNear(states.T, c.T)
|
|
self.assertArrayNear(states.P, c.P)
|
|
self.assertArrayNear(states.Q, c.Q)
|
|
|
|
def test_append_no_norm_water(self):
|
|
w = ct.Water()
|
|
states = ct.SolutionArray(w)
|
|
w.TQ = 300, 0.5
|
|
states.append(w.state)
|
|
self.assertEqual(states[0].T, w.T)
|
|
self.assertEqual(states[0].P, w.P)
|
|
self.assertEqual(states[0].Q, w.Q)
|
|
|
|
def test_phase_of_matter(self):
|
|
# based on test_thermo.py::TestSolutionArray::test_phase_of_matter
|
|
outfile = self.test_work_path / "solutionarray_pom.yaml"
|
|
outfile.unlink(missing_ok=True)
|
|
|
|
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)
|
|
states.save(outfile, "group0")
|
|
|
|
saved = ct.SolutionArray(water)
|
|
saved.restore(outfile, "group0") # normalize=False)
|
|
self.assertArrayNear(states.T, saved.T)
|
|
self.assertArrayNear(states.P, saved.P)
|
|
self.assertArrayNear(states.Q, saved.Q)
|
|
self.assertEqual(list(saved.phase_of_matter), pom)
|
|
|
|
|
|
class TestSolutionSerialization(utilities.CanteraTest):
|
|
def test_input_data_simple(self):
|
|
gas = ct.Solution('h2o2.yaml')
|
|
data = gas.input_data
|
|
self.assertEqual(data['name'], 'ohmech')
|
|
self.assertEqual(data['thermo'], 'ideal-gas')
|
|
self.assertEqual(data['kinetics'], 'gas')
|
|
self.assertEqual(data['transport'], 'mixture-averaged')
|
|
|
|
def test_input_data_user_modifications(self):
|
|
gas = ct.Solution("h2o2.yaml")
|
|
data1 = gas.input_data
|
|
gas.update_user_data({"foo": True}) # should get overwritten
|
|
extra = {"foo": [1.2, 3.4], "bar": [[1, 2], [3, 4]]}
|
|
gas.update_user_data(extra)
|
|
data2 = gas.input_data
|
|
self.assertEqual(extra["foo"], data2["foo"])
|
|
self.assertEqual(extra["bar"], data2["bar"])
|
|
gas.clear_user_data()
|
|
data3 = gas.input_data
|
|
self.assertEqual(data1, data3)
|
|
|
|
def test_input_data_state(self):
|
|
gas = ct.Solution('h2o2.yaml', transport_model=None)
|
|
data = gas.input_data
|
|
self.assertEqual(gas.T, data['state']['T'])
|
|
self.assertEqual(gas.density, data['state']['density'])
|
|
|
|
gas.TP = 500, 3.14e5
|
|
data = gas.input_data
|
|
self.assertEqual(gas.T, data['state']['T'])
|
|
self.assertEqual(gas.density, data['state']['density'])
|
|
|
|
def test_input_data_custom(self):
|
|
gas = ct.Solution('ideal-gas.yaml')
|
|
data = gas.input_data
|
|
self.assertEqual(data['custom-field']['first'], True)
|
|
self.assertEqual(data['custom-field']['last'], [100, 200, 300])
|
|
|
|
if sys.version_info >= (3,7):
|
|
# Check that items are ordered as expected
|
|
self.assertEqual(
|
|
list(data),
|
|
["name", "thermo", "elements", "species", "state",
|
|
"custom-field", "literal-string"]
|
|
)
|
|
self.assertEqual(list(data["custom-field"]), ["first", "second", "last"])
|
|
self.assertEqual(data["literal-string"], "spam\nand\neggs\n")
|
|
|
|
def test_input_data_debye_huckel(self):
|
|
soln = ct.Solution('thermo-models.yaml', 'debye-huckel-B-dot-ak')
|
|
data = soln.input_data
|
|
self.assertEqual(data['thermo'], 'Debye-Huckel')
|
|
act_data = data['activity-data']
|
|
self.assertEqual(act_data['model'], 'B-dot-with-variable-a')
|
|
self.assertEqual(act_data['default-ionic-radius'], 4e-10)
|
|
self.assertNotIn('kinetics', data)
|
|
self.assertNotIn('transport', data)
|
|
|
|
def test_yaml_simple(self):
|
|
gas = ct.Solution('h2o2.yaml')
|
|
gas.TPX = 500, ct.one_atm, 'H2: 1.0, O2: 1.0'
|
|
gas.equilibrate('HP')
|
|
gas.TP = 1500, ct.one_atm
|
|
gas.write_yaml(self.test_work_path / "h2o2-generated.yaml")
|
|
generated = utilities.load_yaml(self.test_work_path / "h2o2-generated.yaml")
|
|
for key in ('generator', 'date', 'phases', 'species', 'reactions'):
|
|
self.assertIn(key, generated)
|
|
self.assertEqual(generated['phases'][0]['transport'], 'mixture-averaged')
|
|
for i, species in enumerate(generated['species']):
|
|
self.assertEqual(species['composition'], gas.species(i).composition)
|
|
for blessed, generated in zip(gas.reactions(), generated["reactions"]):
|
|
assert blessed.equation == generated["equation"]
|
|
|
|
gas2 = ct.Solution(self.test_work_path / "h2o2-generated.yaml")
|
|
self.assertArrayNear(gas.concentrations, gas2.concentrations)
|
|
self.assertArrayNear(gas.partial_molar_enthalpies,
|
|
gas2.partial_molar_enthalpies)
|
|
self.assertArrayNear(gas.forward_rate_constants,
|
|
gas2.forward_rate_constants)
|
|
self.assertArrayNear(gas.mix_diff_coeffs, gas2.mix_diff_coeffs)
|
|
|
|
def test_yaml_outunits1(self):
|
|
gas = ct.Solution('h2o2.yaml')
|
|
gas.TPX = 500, ct.one_atm, 'H2: 1.0, O2: 1.0'
|
|
gas.equilibrate('HP')
|
|
gas.TP = 1500, ct.one_atm
|
|
units = {'length': 'cm', 'quantity': 'mol', 'energy': 'cal'}
|
|
gas.write_yaml(self.test_work_path / "h2o2-generated.yaml", units=units)
|
|
generated = utilities.load_yaml(self.test_work_path / "h2o2-generated.yaml")
|
|
original = utilities.load_yaml(self.cantera_data_path / "h2o2.yaml")
|
|
self.assertEqual(generated['units'], units)
|
|
|
|
for r1, r2 in zip(original['reactions'], generated['reactions']):
|
|
if 'rate-constant' in r1:
|
|
self.assertNear(r1['rate-constant']['A'], r2['rate-constant']['A'])
|
|
self.assertNear(r1['rate-constant']['Ea'], r2['rate-constant']['Ea'])
|
|
|
|
gas2 = ct.Solution(self.test_work_path / "h2o2-generated.yaml")
|
|
self.assertArrayNear(gas.concentrations, gas2.concentrations)
|
|
self.assertArrayNear(gas.partial_molar_enthalpies,
|
|
gas2.partial_molar_enthalpies)
|
|
self.assertArrayNear(gas.forward_rate_constants,
|
|
gas2.forward_rate_constants)
|
|
self.assertArrayNear(gas.mix_diff_coeffs, gas2.mix_diff_coeffs)
|
|
|
|
def test_yaml_outunits2(self):
|
|
gas = ct.Solution('h2o2.yaml')
|
|
gas.TPX = 500, ct.one_atm, 'H2: 1.0, O2: 1.0'
|
|
gas.equilibrate('HP')
|
|
gas.TP = 1500, ct.one_atm
|
|
units = {'length': 'cm', 'quantity': 'mol', 'energy': 'cal'}
|
|
system = ct.UnitSystem(units)
|
|
gas.write_yaml(self.test_work_path / "h2o2-generated.yaml", units=system)
|
|
generated = utilities.load_yaml(self.test_work_path / "h2o2-generated.yaml")
|
|
original = utilities.load_yaml(self.cantera_data_path / "h2o2.yaml")
|
|
|
|
for r1, r2 in zip(original['reactions'], generated['reactions']):
|
|
if 'rate-constant' in r1:
|
|
self.assertNear(r1['rate-constant']['A'], r2['rate-constant']['A'])
|
|
self.assertNear(r1['rate-constant']['Ea'], r2['rate-constant']['Ea'])
|
|
|
|
gas2 = ct.Solution(self.test_work_path / "h2o2-generated.yaml")
|
|
self.assertArrayNear(gas.concentrations, gas2.concentrations)
|
|
self.assertArrayNear(gas.partial_molar_enthalpies,
|
|
gas2.partial_molar_enthalpies)
|
|
self.assertArrayNear(gas.forward_rate_constants,
|
|
gas2.forward_rate_constants)
|
|
self.assertArrayNear(gas.mix_diff_coeffs, gas2.mix_diff_coeffs)
|
|
|
|
def check_ptcombust(self, gas, surf):
|
|
generated = utilities.load_yaml(self.test_work_path / "ptcombust-generated.yaml")
|
|
for key in ("phases", "species", "gas-reactions", "Pt_surf-reactions"):
|
|
self.assertIn(key, generated)
|
|
self.assertEqual(len(generated["gas-reactions"]), gas.n_reactions)
|
|
self.assertEqual(len(generated["Pt_surf-reactions"]), surf.n_reactions)
|
|
self.assertEqual(len(generated["species"]), surf.n_total_species)
|
|
|
|
surf2 = ct.Solution(self.test_work_path / "ptcombust-generated.yaml", "Pt_surf")
|
|
self.assertArrayNear(surf.concentrations, surf2.concentrations)
|
|
self.assertArrayNear(surf.partial_molar_enthalpies,
|
|
surf2.partial_molar_enthalpies)
|
|
self.assertArrayNear(surf.forward_rate_constants,
|
|
surf2.forward_rate_constants)
|
|
|
|
def test_yaml_surface_explicit(self):
|
|
gas = ct.Solution("ptcombust.yaml", "gas")
|
|
surf = ct.Interface("ptcombust.yaml", "Pt_surf", [gas])
|
|
gas.TPY = 900, ct.one_atm, np.ones(gas.n_species)
|
|
surf.coverages = np.ones(surf.n_species)
|
|
surf.write_yaml(self.test_work_path / "ptcombust-generated.yaml")
|
|
self.check_ptcombust(gas, surf)
|
|
|
|
def test_yaml_surface_adjacent(self):
|
|
surf = ct.Interface("ptcombust.yaml", "Pt_surf")
|
|
gas = surf.adjacent["gas"]
|
|
gas.TPY = 900, ct.one_atm, np.ones(gas.n_species)
|
|
surf.coverages = np.ones(surf.n_species)
|
|
surf.write_yaml(self.test_work_path / "ptcombust-generated.yaml")
|
|
self.check_ptcombust(gas, surf)
|
|
|
|
def test_yaml_eos(self):
|
|
ice = ct.Solution('water.yaml', 'ice')
|
|
ice.TP = 270, 2 * ct.one_atm
|
|
ice.write_yaml(self.test_work_path / "ice-generated.yaml", units={'length': 'mm', 'mass': 'g'})
|
|
|
|
ice2 = ct.Solution(self.test_work_path / "ice-generated.yaml")
|
|
self.assertNear(ice.density, ice2.density)
|
|
self.assertNear(ice.entropy_mole, ice2.entropy_mole)
|
|
|
|
def test_yaml_inconsistent_species(self):
|
|
gas = ct.Solution('h2o2.yaml', transport_model=None)
|
|
gas2 = ct.Solution('h2o2.yaml', transport_model=None)
|
|
gas2.name = 'modified'
|
|
# modify the NASA coefficients for one species
|
|
h2 = gas2.species('H2')
|
|
nasa_coeffs = h2.thermo.coeffs
|
|
nasa_coeffs[1] += 0.1
|
|
nasa_coeffs[8] += 0.1
|
|
h2.thermo = ct.NasaPoly2(h2.thermo.min_temp, h2.thermo.max_temp,
|
|
h2.thermo.reference_pressure, nasa_coeffs)
|
|
gas2.modify_species(gas2.species_index('H2'), h2)
|
|
with self.assertRaisesRegex(ct.CanteraError, "different definitions"):
|
|
gas.write_yaml(self.test_work_path / "h2o2-error.yaml", phases=gas2)
|
|
|
|
def test_yaml_user_data(self):
|
|
gas = ct.Solution("h2o2.yaml")
|
|
extra = {"spam": {"A": 1, "B": 2}, "eggs": [1, 2.3, 4.5]}
|
|
gas.update_user_data(extra)
|
|
S = gas.species(2)
|
|
S.update_user_data({"foo": "bar"})
|
|
S.transport.update_user_data({"baz": 1234.5})
|
|
S.thermo.update_user_data({"something": (False, True)})
|
|
gas.reaction(5).update_user_data({"baked-beans": True})
|
|
|
|
gas.write_yaml(self.test_work_path / "h2o2-generated-user-data.yaml")
|
|
gas2 = ct.Solution(self.test_work_path / "h2o2-generated-user-data.yaml")
|
|
data2 = gas2.species(2).input_data
|
|
|
|
self.assertEqual(gas2.input_data["spam"], extra["spam"])
|
|
self.assertEqual(gas2.input_data["eggs"], extra["eggs"])
|
|
self.assertEqual(data2["foo"], "bar")
|
|
self.assertEqual(data2["transport"]["baz"], 1234.5)
|
|
self.assertEqual(data2["thermo"]["something"], [False, True])
|
|
self.assertTrue(gas2.reaction(5).input_data["baked-beans"])
|
|
|
|
|
|
class TestSpeciesSerialization(utilities.CanteraTest):
|
|
def test_species_simple(self):
|
|
gas = ct.Solution('h2o2.yaml', transport_model=None)
|
|
data = gas.species('H2O').input_data
|
|
self.assertEqual(data['name'], 'H2O')
|
|
self.assertEqual(data['composition'], {'H': 2, 'O': 1})
|
|
|
|
def test_species_thermo(self):
|
|
gas = ct.Solution('h2o2.yaml', transport_model=None)
|
|
data = gas.species('H2O').input_data['thermo']
|
|
self.assertEqual(data['model'], 'NASA7')
|
|
self.assertEqual(data['temperature-ranges'], [200, 1000, 3500])
|
|
self.assertEqual(data['note'], 'L8/89')
|
|
|
|
def test_species_transport(self):
|
|
gas = ct.Solution('h2o2.yaml')
|
|
data = gas.species('H2O').input_data['transport']
|
|
self.assertEqual(data['model'], 'gas')
|
|
self.assertEqual(data['geometry'], 'nonlinear')
|
|
self.assertNear(data['dipole'], 1.844)
|
|
|
|
|
|
class TestInterfaceAdjacent(utilities.CanteraTest):
|
|
def test_surface(self):
|
|
surf = ct.Interface("ptcombust.yaml", "Pt_surf")
|
|
self.assertEqual(list(surf.adjacent), ["gas"])
|
|
self.assertEqual(surf.phase_index(surf), 0)
|
|
self.assertEqual(surf.phase_index("gas"), 1)
|
|
self.assertEqual(surf.phase_index(surf.adjacent["gas"]), 1)
|
|
|
|
def test_named_adjacent(self):
|
|
# override the adjacent-phases to change the order
|
|
surf = ct.Interface("surface-phases.yaml", "anode-surface",
|
|
adjacent=["electrolyte", "graphite"])
|
|
self.assertEqual(list(surf.adjacent), ["electrolyte", "graphite"])
|
|
|
|
def test_edge(self):
|
|
tpb = ct.Interface("sofc.yaml", "tpb")
|
|
self.assertEqual(set(tpb.adjacent), {"metal_surface", "oxide_surface", "metal"})
|
|
self.assertIsInstance(tpb.adjacent["metal_surface"], ct.Interface)
|
|
self.assertNotIsInstance(tpb.adjacent["metal"], ct.Interface)
|
|
gas1 = tpb.adjacent["metal_surface"].adjacent["gas"]
|
|
gas2 = tpb.adjacent["oxide_surface"].adjacent["gas"]
|
|
gas1.X = [0.1, 0.4, 0.3, 0.2]
|
|
self.assertArrayNear(gas1.X, gas2.X)
|
|
|
|
def test_invalid(self):
|
|
with self.assertRaisesRegex(ct.CanteraError, "does not contain"):
|
|
surf = ct.Interface("ptcombust.yaml", "Pt_surf", ["foo"])
|
|
|
|
with self.assertRaises(TypeError):
|
|
surf = ct.Interface("ptcombust.yaml", "Pt_surf", [2])
|
|
|
|
def test_remote_file(self):
|
|
yaml = """
|
|
phases:
|
|
- name: Pt_surf
|
|
thermo: ideal-surface
|
|
adjacent-phases: [{ptcombust.yaml/phases: [gas]}]
|
|
species: [{ptcombust.yaml/species: all}]
|
|
kinetics: surface
|
|
reactions: [{ptcombust.yaml/reactions: all}]
|
|
site-density: 2.7063e-09
|
|
"""
|
|
|
|
surf = ct.Interface(yaml=yaml)
|
|
self.assertEqual(surf.adjacent["gas"].n_species, 32)
|
|
self.assertEqual(surf.n_reactions, 24)
|