from __future__ import annotations import itertools from pathlib import Path import logging import io import pytest from . import utilities from .utilities import allow_deprecated import cantera as ct from cantera import ck2yaml, cti2yaml, ctml2yaml, yaml2ck class ck2yamlTest(utilities.CanteraTest): def convert(self, inputFile, thermo=None, transport=None, surface=None, output=None, extra=None, **kwargs): if output is None: output = Path(inputFile).stem # strip '.inp' if inputFile is not None: inputFile = self.test_data_path / inputFile if thermo is not None: thermo = self.test_data_path / thermo if transport is not None: transport = self.test_data_path / transport if surface is not None: surface = self.test_data_path / surface if extra is not None: extra = self.test_data_path / extra output = self.test_work_path / (output + "-from-ck.yaml") # In Python >= 3.8, this can be replaced by the missing_ok argument if output.is_file(): output.unlink() ck2yaml.convert(inputFile, thermo_file=thermo, transport_file=transport, surface_file=surface, out_name=output, extra_file=extra, quiet=True, **kwargs) return output def checkConversion(self, refFile, testFile): ref = ct.Solution(refFile) gas = ct.Solution(testFile) self.assertEqual(ref.element_names, gas.element_names) self.assertEqual(ref.species_names, gas.species_names) coeffs_ref = ref.reactant_stoich_coeffs coeffs_gas = gas.reactant_stoich_coeffs self.assertEqual(coeffs_gas.shape, coeffs_ref.shape) self.assertTrue((coeffs_gas == coeffs_ref).all()) compositionA = [[ref.n_atoms(i,j) for j in range(ref.n_elements)] for i in range(ref.n_species)] compositionB = [[gas.n_atoms(i,j) for j in range(gas.n_elements)] for i in range(gas.n_species)] self.assertEqual(compositionA, compositionB) return ref, gas def checkThermo(self, ref, gas, temperatures): for T in temperatures: ref.TP = T, ct.one_atm gas.TP = T, ct.one_atm ref_cp = ref.standard_cp_R gas_cp = gas.standard_cp_R ref_h = ref.standard_enthalpies_RT gas_h = gas.standard_enthalpies_RT ref_s = ref.standard_entropies_R gas_s = gas.standard_entropies_R for i in range(gas.n_species): message = ' for species {0} at T = {1}'.format(i, T) self.assertNear(ref_cp[i], gas_cp[i], 1e-7, msg='cp'+message) self.assertNear(ref_h[i], gas_h[i], 1e-7, msg='h'+message) self.assertNear(ref_s[i], gas_s[i], 1e-7, msg='s'+message) def checkKinetics(self, ref, gas, temperatures, pressures, tol=1e-8): for T,P in itertools.product(temperatures, pressures): ref.TP = T, P gas.TP = T, P ref_kf = ref.forward_rate_constants ref_kr = ref.reverse_rate_constants gas_kf = gas.forward_rate_constants gas_kr = gas.reverse_rate_constants for i in range(gas.n_reactions): message = ' for reaction {0} at T = {1}, P = {2}'.format(i, T, P) self.assertNear(ref_kf[i], gas_kf[i], rtol=tol, msg='kf' + message) self.assertNear(ref_kr[i], gas_kr[i], rtol=tol, msg='kr' + message) @utilities.slow_test def test_gri30(self): output = self.convert('gri30.inp', thermo='gri30_thermo.dat', transport='gri30_tran.dat', output='gri30_test') ref, gas = self.checkConversion("gri30.yaml", output) self.checkKinetics(ref, gas, [300, 1500], [5e3, 1e5, 2e6]) def test_soot(self): output = self.convert("soot.inp", thermo="soot-therm.dat", output="soot_test") ref, gas = self.checkConversion("soot.yaml", output) self.checkThermo(ref, gas, [300, 1100]) self.checkKinetics(ref, gas, [300, 1100], [5e3, 1e5, 2e6]) def test_pdep(self): output = self.convert('pdep-test.inp') ref, gas = self.checkConversion('pdep-test.yaml', output) self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6]) def test_species_only(self): self.convert(None, thermo='dummy-thermo.dat', output='dummy-thermo') yaml = ("{phases: [{name: gas, species: " "[{dummy-thermo-from-ck.yaml/species: [R1A, R1B, P1]}], " "thermo: ideal-gas}]}") gas = ct.Solution(yaml=yaml) self.assertEqual(gas.n_species, 3) self.assertEqual(gas.n_reactions, 0) def test_missingThermo(self): with self.assertRaisesRegex(ck2yaml.InputError, 'No thermo data'): self.convert('h2o2_missingThermo.inp') def test_duplicate_thermo(self): with self.assertRaisesRegex(ck2yaml.InputError, 'additional thermo'): self.convert('duplicate-thermo.inp') output = self.convert('duplicate-thermo.inp', permissive=True) gas = ct.Solution(output) self.assertEqual(gas.n_species, 3) self.assertEqual(gas.n_reactions, 2) def test_duplicate_species(self): with self.assertRaisesRegex(ck2yaml.InputError, 'additional declaration'): self.convert('duplicate-species.inp') output = self.convert('duplicate-species.inp', permissive=True) gas = ct.Solution(output) self.assertEqual(gas.species_names, ['foo','bar','baz']) def test_pathologicalSpeciesNames(self): output = self.convert('species-names.inp') gas = ct.Solution(output) self.assertEqual(gas.n_species, 10) self.assertEqual(gas.species_name(0), '(Parens)') self.assertEqual(gas.species_name(1), '@#$%^-2') self.assertEqual(gas.species_index('co:lons:'), 2) self.assertEqual(gas.species_name(3), '[xy2]*{.}') self.assertEqual(gas.species_name(4), 'plus+') self.assertEqual(gas.species_name(5), 'eq=uals') self.assertEqual(gas.species_name(6), 'plus') self.assertEqual(gas.species_name(7), 'trans_butene') self.assertEqual(gas.species_name(8), 'co') self.assertEqual(gas.species_name(9), "amp&ersand") self.assertEqual(gas.n_reactions, 13) nu = gas.product_stoich_coeffs - gas.reactant_stoich_coeffs self.assertEqual(list(nu[:,0]), [-1, -1, 0, 2, 0, 0, 0, 0, 0, 0]) self.assertEqual(list(nu[:,1]), [-2, 3, 0, -1, 0, 0, 0, 0, 0, 0]) self.assertEqual(list(nu[:,2]), [-1, 0, 0, 0, 1, 0, 0, 0, 0, 0]) self.assertEqual(list(nu[:,3]), [3, 0, 0, 0, -2, -1, 0, 0, 0, 0]) self.assertEqual(list(nu[:,4]), [2, 0, 0, 0, -1, 0, -1, 0, 0, 0]) self.assertEqual(list(nu[:,5]), [1, 0, 0, 0, 1, -1, -1, 0, 0, 0]) self.assertEqual(list(nu[:,6]), [2, 0, -1, 0, 0, -1, 0, 0, 0, 0]) self.assertEqual(list(nu[:,7]), [0, 0, 0, 0, -1, 1, 0, 0, 0, 0]) self.assertEqual(list(nu[:,8]), [0, 0, 0, 0, -1, 1, 0, 0, 0, 0]) self.assertEqual(list(nu[:,9]), [0, 0, 0, 0, -1, 1, 0, 0, 0, 0]) self.assertEqual(list(nu[:,10]), [0, 0, -1, 0, 2, 0, 0, -1, 0, 0]) self.assertEqual(list(nu[:,11]), [0, 0, -1, 0, 2, 0, 0, 0, -1, 0]) self.assertEqual(list(nu[:,12]), [0, 0, 0, 0, 1, 0, 0, 0, 0, -1]) def test_unterminatedSections(self): with self.assertRaisesRegex(ck2yaml.InputError, 'implicitly ended'): self.convert('unterminated-sections.inp') output = self.convert('unterminated-sections.inp', permissive=True) gas = ct.Solution(output) self.assertEqual(gas.n_species, 3) self.assertEqual(gas.n_reactions, 2) def test_unterminatedSections2(self): with self.assertRaisesRegex(ck2yaml.InputError, 'implicitly ended'): self.convert('unterminated-sections2.inp') output = self.convert('unterminated-sections2.inp', permissive=True) gas = ct.Solution(output) self.assertEqual(gas.n_species, 3) self.assertEqual(gas.n_reactions, 2) def test_unrecognized_section(self): with self.assertRaisesRegex(ck2yaml.InputError, 'SPAM'): self.convert('unrecognized-section.inp', thermo='dummy-thermo.dat', permissive=True) def test_nasa9(self): output = self.convert("nasa9-test.inp", thermo="nasa9-test-therm.dat") ref, gas = self.checkConversion("nasa9-test.yaml", output) self.checkThermo(ref, gas, [300, 500, 1200, 5000]) def test_nasa9_subset(self): output = self.convert("nasa9-test-subset.inp", thermo="nasa9-test-therm.dat") ref, gas = self.checkConversion("nasa9-test-subset.yaml", output) self.checkThermo(ref, gas, [300, 500, 1200, 5000]) def test_sri_falloff(self): output = self.convert("sri-falloff.inp", thermo="dummy-thermo.dat") ref, gas = self.checkConversion("sri-falloff.yaml", output) self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6]) def test_chemically_activated(self): output = self.convert("chemically-activated-reaction.inp") ref, gas = self.checkConversion("chemically-activated-reaction.yaml", output) self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6, 1e7]) def test_explicit_third_bodies(self): output = self.convert("explicit-third-bodies.inp", thermo="dummy-thermo.dat") ref, gas = self.checkConversion("explicit-third-bodies.yaml", output) self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6]) def test_explicit_reverse_rate(self): output = self.convert("explicit-reverse-rate.inp", thermo="dummy-thermo.dat") ref, gas = self.checkConversion("explicit-reverse-rate.yaml", output) self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6]) # Reactions with explicit reverse rate constants are transformed into # two irreversible reactions with reactants and products swapped, unless # the explicit reverse rate is zero so only the forward reaction is used. Rr = gas.reverse_rate_constants self.assertEqual(Rr[0], 0.0) self.assertEqual(Rr[1], 0.0) self.assertEqual(Rr[2], 0.0) self.assertEqual(Rr[3], 0.0) self.assertEqual(Rr[4], 0.0) Rstoich = gas.reactant_stoich_coeffs Pstoich = gas.product_stoich_coeffs self.assertEqual(list(Rstoich[:, 0]), list(Pstoich[:, 1])) self.assertEqual(list(Rstoich[:, 1]), list(Pstoich[:, 0])) self.assertEqual(list(Rstoich[:, 2]), list(Pstoich[:, 3])) self.assertEqual(list(Rstoich[:, 3]), list(Pstoich[:, 2])) self.assertEqual(gas.n_reactions, 5) def test_explicit_forward_order(self): output = self.convert("explicit-forward-order.inp", thermo="dummy-thermo.dat") ref, gas = self.checkConversion("explicit-forward-order.yaml", output) self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6]) def test_negative_order(self): with self.assertRaisesRegex(ck2yaml.InputError, 'Negative reaction order'): self.convert('negative-order.inp', thermo='dummy-thermo.dat') def test_negative_order_permissive(self): output = self.convert('negative-order.inp', thermo='dummy-thermo.dat', permissive=True) ref, gas = self.checkConversion("negative-order.yaml", output) self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6]) def test_negative_A_factor(self): output = self.convert('negative-rate.inp', thermo='dummy-thermo.dat') gas = ct.Solution(output) # Validate the mechanism self.assertLess(gas.reaction(4).rate.pre_exponential_factor, 0) self.assertLess(gas.reaction(1).rate.pre_exponential_factor, 0) self.assertLess(gas.reaction(2).rate.pre_exponential_factor, 0) self.assertLess(gas.forward_rate_constants[5], 0) def test_bad_troe_value(self): with self.assertRaises(ValueError): self.convert('bad-troe.inp', thermo='dummy-thermo.dat') def test_invalid_reaction_equation(self): with self.assertRaisesRegex(ck2yaml.InputError, 'Unparsable'): self.convert('invalid-equation.inp', thermo='dummy-thermo.dat') @utilities.slow_test def test_reaction_units(self): out_def = self.convert('units-default.inp', thermo='dummy-thermo.dat') out_cus = self.convert('units-custom.inp', thermo='dummy-thermo.dat') default, custom = self.checkConversion(out_def, out_cus) self.checkKinetics(default, custom, [300, 800, 1450, 2800], [5e0, 5e3, 1e5, 2e6, 1e8], 1e-7) def test_float_stoich_coeffs(self): output = self.convert('float-stoich.inp', thermo='dummy-thermo.dat') gas = ct.Solution(output) R = gas.reactant_stoich_coeffs P = gas.product_stoich_coeffs self.assertArrayNear(R[:,0], [0, 1.5, 0.5, 0]) self.assertArrayNear(P[:,0], [1, 0, 0, 1]) self.assertArrayNear(R[:,1], [1, 0, 0, 1]) self.assertArrayNear(P[:,1], [0, 0.33, 1.67, 0]) def test_photon(self): output = self.convert('photo-reaction.inp', thermo='dummy-thermo.dat', permissive=True) ref, gas = self.checkConversion("photo-reaction.yaml", output) self.checkKinetics(ref, gas, [300, 800, 1450, 2800], [5e3, 1e5, 2e6]) def test_transport_normal(self): output = self.convert('h2o2.inp', transport='gri30_tran.dat', output='h2o2_transport_normal') gas = ct.Solution(output) gas.TPX = 300, 101325, 'H2:1.0, O2:1.0' self.assertAlmostEqual(gas.thermal_conductivity, 0.07663, 4) def test_transport_embedded(self): output = self.convert('with-transport.inp') gas = ct.Solution(output) gas.X = [0.2, 0.3, 0.5] D = gas.mix_diff_coeffs for d in D: self.assertTrue(d > 0.0) def test_transport_missing_species(self): with self.assertRaisesRegex(ck2yaml.InputError, 'No transport data'): self.convert('h2o2.inp', transport='h2o2-missing-species-tran.dat', output='h2o2_transport_missing_species') def test_transport_extra_column_entries(self): with self.assertRaisesRegex(ck2yaml.InputError, '572.400'): self.convert('h2o2.inp', transport='h2o2-extra-column-entries-tran.dat', output='h2o2_extra-column-entries-tran') def test_transport_duplicate_species(self): with self.assertRaisesRegex(ck2yaml.InputError, 'duplicate transport'): self.convert('h2o2.inp', transport='h2o2-duplicate-species-tran.dat', output='h2o2_transport_duplicate_species') self.convert('h2o2.inp', transport='h2o2-duplicate-species-tran.dat', output='h2o2_transport_duplicate_species', permissive=True) def test_transport_bad_geometry(self): with self.assertRaisesRegex(ck2yaml.InputError, 'Invalid geometry flag value'): self.convert('h2o2.inp', transport='h2o2-bad-geometry-tran.dat', output='h2o2_transport_bad_geometry') with self.assertRaisesRegex(ck2yaml.InputError, 'Invalid geometry flag \''): self.convert('h2o2.inp', transport='h2o2-character-geometry-tran.dat', output='h2o2_transport_character_geometry') def test_transport_float_geometry(self): with self.assertRaisesRegex(ck2yaml.InputError, 'Incorrect geometry flag syntax'): self.convert('h2o2.inp', transport='h2o2-float-geometry-tran.dat', output='h2o2_transport_float_geometry') output = self.convert('h2o2.inp', transport='h2o2-float-geometry-tran.dat', output='h2o2_transport_float_geometry', permissive=True) gas = ct.Solution(output) self.assertTrue(gas.species("H").transport.geometry == 'atom') self.assertTrue(gas.species("H2").transport.geometry == 'linear') self.assertTrue(gas.species("H2O").transport.geometry == 'nonlinear') with self.assertRaisesRegex(ck2yaml.InputError, 'Invalid float geometry flag'): self.convert('h2o2.inp', transport='h2o2-float-arithmetic-error-geometry-tran.dat', output='h2o2_transport_float_geometry', permissive=True) def test_empty_reaction_section(self): output = self.convert('h2o2_emptyReactions.inp') gas = ct.Solution(output) self.assertEqual(gas.n_species, 9) self.assertEqual(gas.n_reactions, 0) def test_reaction_comments1(self): output = self.convert('pdep-test.inp') text = output.read_text() self.assertIn('Generic mechanism header', text) self.assertIn('Single PLOG reaction', text) self.assertIn('Multiple PLOG expressions at the same pressure', text) def test_reaction_comments2(self): output = self.convert('explicit-third-bodies.inp', thermo='dummy-thermo.dat') text = output.read_text() self.assertIn('An end of line comment', text) self.assertIn('A comment after the last reaction', text) def test_custom_element(self): output = self.convert('custom-elements.inp') gas = ct.Solution(output) self.assertEqual(gas.n_elements, 4) self.assertNear(gas.atomic_weight(2), 13.003) self.assertEqual(gas.n_atoms('ethane', 'C'), 2) self.assertEqual(gas.n_atoms('CC', 'C'), 1) self.assertEqual(gas.n_atoms('CC', 'Ci'), 1) def test_surface_mech(self): output = self.convert('surface1-gas.inp', surface='surface1.inp', output='surface1') surf = ct.Interface(output, 'PT_SURFACE') gas = surf.adjacent["gas"] self.assertEqual(gas.n_reactions, 11) self.assertEqual(surf.n_reactions, 15) self.assertEqual(surf.species('O2_Pt').size, 3) # Different units for rate constants in each input file # 62.1 kJ/gmol = 6.21e7 J/kmol self.assertNear(gas.reaction(0).rate.activation_energy, 6.21e7) # 67400 J/mol = 6.74e7 J/kmol self.assertNear(surf.reaction(1).rate.activation_energy, 6.74e7) # Sticking coefficients self.assertTrue(surf.reaction(4).duplicate) self.assertNotIsInstance(surf.reaction(1).rate, ct.StickingArrheniusRate) self.assertIsInstance(surf.reaction(2).rate, ct.StickingArrheniusRate) self.assertTrue(surf.reaction(2).rate.motz_wise_correction) self.assertIsInstance(surf.reaction(4).rate, ct.StickingArrheniusRate) self.assertFalse(surf.reaction(4).rate.motz_wise_correction) self.assertTrue(surf.reaction(6).rate.motz_wise_correction) # Coverage dependencies covdeps = surf.reaction(1).rate.coverage_dependencies self.assertEqual(len(covdeps), 2) self.assertIn("H_Pt", covdeps) self.assertEqual(covdeps["OH_Pt"]["m"], 1.0) self.assertNear(covdeps["H_Pt"]["E"], -6e6) # 6000 J/gmol = 6e6 J/kmol def test_surface_mech2(self): output = self.convert('surface1-gas-noreac.inp', surface='surface1.inp', output='surface1-nogasreac') gas = ct.Solution(output, 'gas') surf = ct.Interface(output, 'PT_SURFACE', [gas]) self.assertEqual(gas.n_reactions, 0) self.assertEqual(surf.n_reactions, 15) covdeps = surf.reaction(1).rate.coverage_dependencies self.assertIn("H_Pt", covdeps) self.assertEqual(covdeps["OH_Pt"]["m"], 1.0) self.assertNear(covdeps["H_Pt"]["E"], -6e6) def test_surface_mech3(self): # This tests the case where the thermo data for both the gas and surface are # combined in a file separate from the gas and surface definitions. output = self.convert('surface2-gas.inp', thermo='surface2-thermo.dat', surface='surface2.inp', output='surface2') surf = ct.Interface(output, 'PT_SURFACE') assert surf.n_species == 6 assert surf.n_reactions == 15 assert surf.reaction(4).duplicate is True def test_third_body_plus_falloff_reactions(self): output = self.convert("third_body_plus_falloff_reaction.inp") gas = ct.Solution(output) self.assertEqual(gas.n_reactions, 2) def test_blank_line_in_header(self): output = self.convert("blank_line_in_header.inp") gas = ct.Solution(output) self.assertEqual(gas.n_reactions, 1) @utilities.slow_test def test_extra(self): output = self.convert("gri30.inp", thermo="gri30_thermo.dat", transport="gri30_tran.dat", output="gri30_extra", extra="extra.yaml") yml = utilities.load_yaml(output) desc = yml['description'].split('\n')[-1] self.assertEqual(desc, 'This is an alternative description.') for key in ['foo', 'bar']: self.assertIn(key, yml.keys()) def test_sri_zero(self): # This test tests it can convert the SRI parameters when D or E equal to 0 output = self.convert('sri_convert_test.txt') mech = utilities.load_yaml(output) D = mech['reactions'][0]['SRI']['D'] E = mech['reactions'][0]['SRI']['E'] self.assertEqual(D, 0) self.assertEqual(E, 0) def test_duplicate_reactions(self): # Running a test this way instead of using the convertMech function # tests the behavior of the ck2yaml.main function and the mechanism # validation step. # Replace the ck2yaml logger with our own in order to capture the output log_stream = io.StringIO() logger = logging.getLogger('cantera.ck2yaml') original_handler = logger.handlers.pop() logformatter = logging.Formatter('%(message)s') handler = logging.StreamHandler(log_stream) handler.setFormatter(logformatter) logger.addHandler(handler) with self.assertRaises(SystemExit): ck2yaml.main([ f"--input={self.test_data_path}/undeclared-duplicate-reactions.inp", f"--thermo={self.test_data_path}/dummy-thermo.dat", f"--output={self.test_work_path}/undeclared-duplicate-reactions.yaml"]) # Put the original logger back in place logger.handlers.clear() logger.addHandler(original_handler) message = log_stream.getvalue() for token in ('FAILED', 'lines 12 and 14', 'R1A', 'R1B'): self.assertIn(token, message) def test_single_Tint(self): output = self.convert(None, thermo="thermo_single_Tint.dat", output="thermo_single_Tint", single_intermediate_temperature=True) mech = utilities.load_yaml(output) # Al(cr) thermo = mech["species"][0]["thermo"] assert thermo["temperature-ranges"] == [200.0, 933.61] assert len(thermo["data"]) == 1 assert thermo["data"][0][0] == 1.01040191 # AlBr3(L) thermo = mech["species"][1]["thermo"] assert thermo["temperature-ranges"] == [370.6, 5000.0] assert len(thermo["data"]) == 1 assert thermo["data"][0][0] == 15.02975 # AlF3(b) thermo = mech["species"][2]["thermo"] assert thermo["temperature-ranges"] == [728.0, 1000.0, 2523.0] assert len(thermo["data"]) == 2 assert thermo["data"][1][0] == 10.41947 # AlF3(L) thermo = mech["species"][3]["thermo"] assert thermo["temperature-ranges"] == [2523.0, 5000.0] assert len(thermo["data"]) == 1 assert thermo["data"][0][0] == 15.096679 def test_error_for_big_element_number(self): with self.assertRaisesRegex(ck2yaml.InputError, 'Element amounts can have no more than 3 digits.'): self.convert('big_element_num_err.inp') class yaml2ckTest(utilities.CanteraTest): """Test yaml2ck by converting to CK then back to YAML to read with Cantera.""" ext: str = "-from-yaml2ck.yaml" def _convert_to_ck( self, input_file: Path, phase_name: str = "", output: tuple[str, str, str] | tuple = (), ) -> tuple[Path | None, Path | None, Path | None]: mechanism_path: Path | str if not output: stem = Path(input_file).stem # strip '.inp' mechanism_path = self.test_work_path / (stem + "-from-yaml.ck") thermo_path = transport_path = None else: if len(output) != 3: raise ValueError( "convert_to_ck output must be a tuple of length three " "containing the mechanism, thermo, and transport file names." ) mechanism_path, thermo_path, transport_path = output mech, thermo, transport = yaml2ck.convert( input_file, phase_name=phase_name, mechanism_path=mechanism_path, thermo_path=thermo_path, transport_path=transport_path, overwrite=True, sort_elements=None, sort_species=None ) return mech, thermo, transport def convert( self, input_file: Path, phase_name: str = "", mech: str | Path | None = None, thermo: str | Path | None = None, transport: str | Path | None = None, permissive: bool = False, ) -> str: if mech is not None: mech, thermo, transport = self._convert_to_ck( input_file, phase_name, (mech, thermo, transport), ) else: mech, thermo, transport = self._convert_to_ck(input_file, phase_name) output = self.test_work_path / (Path(input_file).stem + self.ext) ck2yaml.convert( mech, thermo_file=thermo, transport_file=transport, out_name=output, quiet=True, permissive=permissive, ) return mech def check_conversion(self, basename, cls=ct.Solution, **kwargs): # The round-trip YAML->CK->YAML will always have the single phase name 'gas' # even if the input YAML phase has a different name if "name" in kwargs: phase_name = kwargs.pop("name") else: phase_name = "" ckname = self.test_work_path / (basename.stem + self.ext) ck_phase = cls(ckname, **kwargs) yaml_phase = cls(basename, phase_name, **kwargs) self.assertEqual(set(ck_phase.element_names), set(yaml_phase.element_names)) self.assertEqual(set(ck_phase.species_names), set(yaml_phase.species_names)) yamlSpecies = [yaml_phase.species(s) for s in ck_phase.species_names] for C, Y in zip(ck_phase.species(), yamlSpecies): self.assertEqual(C.composition, Y.composition) self.assertEqual(ck_phase.n_reactions, yaml_phase.n_reactions) for C, Y in zip(ck_phase.reactions(), yaml_phase.reactions()): self.assertEqual(C.__class__, Y.__class__) self.assertEqual(C.reactants, Y.reactants) self.assertEqual(C.products, Y.products) self.assertEqual(C.duplicate, Y.duplicate) for i, sp in zip(range(ck_phase.n_reactions), ck_phase.kinetics_species_names): self.assertEqual(ck_phase.reactant_stoich_coeff(sp, i), yaml_phase.reactant_stoich_coeff(sp, i)) return ck_phase, yaml_phase def check_thermo(self, ck_phase, yaml_phase, temperatures, tol=1e-7): yaml_idx = {ck_phase.species_index(s): yaml_phase.species_index(s) for s in ck_phase.species_names} for T in temperatures: ck_phase.TP = T, ct.one_atm yaml_phase.TP = T, ct.one_atm cp_ck = ck_phase.partial_molar_cp cp_yaml = yaml_phase.partial_molar_cp h_ck = ck_phase.partial_molar_enthalpies h_yaml = yaml_phase.partial_molar_enthalpies s_ck = ck_phase.partial_molar_entropies s_yaml = yaml_phase.partial_molar_entropies self.assertNear(ck_phase.density, yaml_phase.density) for i in range(ck_phase.n_species): message = ' for species {0} at T = {1}'.format(i, T) self.assertNear(cp_ck[i], cp_yaml[yaml_idx[i]], tol, msg='cp'+message) self.assertNear(h_ck[i], h_yaml[yaml_idx[i]], tol, msg='h'+message) self.assertNear(s_ck[i], s_yaml[yaml_idx[i]], tol, msg='s'+message) def check_kinetics(self, ck_phase, yaml_phase, temperatures, pressures, tol=1e-7): for T, P in itertools.product(temperatures, pressures): ck_phase.TP = T, P yaml_phase.TP = T, P kf_ck = ck_phase.forward_rate_constants kr_ck = ck_phase.reverse_rate_constants kf_yaml = yaml_phase.forward_rate_constants kr_yaml = yaml_phase.reverse_rate_constants for i in range(yaml_phase.n_reactions): message = f"for reaction {i+1}: {yaml_phase.reaction(i)} at T = {T}, P = {P}" self.assertNear(kf_ck[i], kf_yaml[i], rtol=tol, msg="kf " + message) self.assertNear(kr_ck[i], kr_yaml[i], rtol=tol, msg="kr " + message) def check_transport(self, ck_phase, yaml_phase, temperatures, model="mixture-averaged"): yaml_idx = {ck_phase.species_index(s): yaml_phase.species_index(s) for s in ck_phase.species_names} ck_phase.transport_model = model yaml_phase.transport_model = model for T in temperatures: ck_phase.TP = T, ct.one_atm yaml_phase.TP = T, ct.one_atm self.assertNear(ck_phase.viscosity, yaml_phase.viscosity) self.assertNear(ck_phase.thermal_conductivity, yaml_phase.thermal_conductivity) Dkm_ck = ck_phase.mix_diff_coeffs Dkm_yaml = yaml_phase.mix_diff_coeffs for i in range(ck_phase.n_species): message = 'dkm for species {0} at T = {1}'.format(i, T) self.assertNear(Dkm_ck[i], Dkm_yaml[yaml_idx[i]], msg=message) @utilities.slow_test def test_gri30(self): input_file = self.cantera_data_path / "gri30.yaml" self.convert(input_file) X = {'O2': 0.3, 'H2': 0.1, 'CH4': 0.2, 'CO2': 0.4} ck_phase, yaml_phase = self.check_conversion(input_file) ck_phase.X = X yaml_phase.X = X self.check_thermo(ck_phase, yaml_phase, [300, 500, 1300, 2000]) self.check_kinetics(ck_phase, yaml_phase, [900, 1800], [2e5, 20e5]) self.check_transport(ck_phase, yaml_phase, [298, 1001, 2400]) def test_nonreactant_orders(self): input_file = self.test_data_path / "reaction-orders.yaml" self.convert(input_file, permissive=True) ck_phase, yaml_phase = self.check_conversion(input_file) self.check_thermo(ck_phase, yaml_phase, [300, 500]) self.check_kinetics(ck_phase, yaml_phase, [300, 1001, 2500], [1e5, 10e5]) def test_phase_id(self): input_file = self.cantera_data_path / "nDodecane_Reitz.yaml" self.convert(input_file, "nDodecane_IG") ck_phase, yaml_phase = self.check_conversion(input_file, name="nDodecane_IG") ck_phase.X = "h2:1" yaml_phase.X = "h2:1" self.check_kinetics( ck_phase, yaml_phase, [300, 800, 1450, 2800], [5e3, 1e5, 2e6], tol=4e-6 ) def test_third_body_reactions(self): input_file = self.test_data_path / "explicit-third-bodies.yaml" mech = self.convert(input_file) with open(mech) as fid: lines = fid.readlines() for i, line in enumerate(lines): if line.startswith("R1A + R1B"): next = lines[i + 1] assert next.startswith("LOW") or next.strip() == "DUPLICATE" ck_phase, yaml_phase = self.check_conversion(input_file) self.check_kinetics( ck_phase, yaml_phase, [300, 800, 1450, 2800], [5e3, 1e5, 2e6] ) def test_pdep(self): input_file = self.test_data_path / "pdep-test.yaml" self.convert(input_file) ck_phase, yaml_phase = self.check_conversion(input_file) # Chebyshev coefficients in XML are truncated to 6 digits, limiting accuracy self.check_kinetics(ck_phase, yaml_phase, [300, 1000, 2200], [100, ct.one_atm, 2e5, 2e6, 9.9e6], tol=2e-4) def test_sri_falloff(self): input_file = self.test_data_path / "sri-falloff.yaml" self.convert(input_file) ck_phase, yaml_phase = self.check_conversion(input_file) self.check_kinetics(ck_phase, yaml_phase, [300, 800, 1450, 2800], [5e3, 1e5, 2e6]) def test_chemically_activated(self): input_file = self.test_data_path / "chemically-activated-reaction.yaml" self.convert(input_file) ck_phase, yaml_phase = self.check_conversion(input_file) # pre-exponential factor in XML is truncated to 7 sig figs, limiting accuracy self.check_kinetics( ck_phase, yaml_phase, [300, 800, 1450, 2800], [5e3, 1e5, 2e6, 1e7], tol=1e-7 ) def test_yaml_2_ck_reactions(self): input_file = self.test_data_path / "yaml-ck-reactions.yaml" self.convert(input_file) ck_phase, yaml_phase = self.check_conversion(input_file) X = {'O2': 0.3, 'H': 0.1, 'H2': 0.2, 'AR': 0.4} ck_phase.X = X yaml_phase.X = X self.check_thermo(ck_phase, yaml_phase, [300, 500, 1300, 2000]) self.check_kinetics(ck_phase, yaml_phase, [900, 1800], [2e5, 20e5], tol=2e-7) self.check_transport(ck_phase, yaml_phase, [298, 1001, 2400]) def test_write_chemkin(self): # test alternative converter yaml_phase = ct.Solution('h2o2.yaml') ck_file = self.test_work_path / 'test.ck' ck_file.unlink(missing_ok=True) yaml_phase.write_chemkin(ck_file, quiet=True) yaml_phase.write_chemkin( ck_file, sort_species='alphabetical', overwrite=True, quiet=True) assert ck_file.exists() yaml_file = self.test_work_path / 'test.yaml' yaml_file.unlink(missing_ok=True) ck2yaml.convert(ck_file, out_name=yaml_file, quiet=True) assert yaml_file.exists() ck_phase = ct.Solution(yaml_file) X = {'O2': 0.3, 'H': 0.1, 'H2': 0.2, 'AR': 0.4} ck_phase.X = X yaml_phase.X = X self.check_thermo(ck_phase, yaml_phase, [300, 500, 1300, 2000]) self.check_kinetics(ck_phase, yaml_phase, [900, 1800], [2e5, 20e5], tol=2e-7) self.check_transport(ck_phase, yaml_phase, [298, 1001, 2400]) def test_write_notes(self): input_file = self.test_data_path / 'species-names.yaml' yaml_phase = ct.Solution(input_file) assert yaml_phase.species("eq=uals").input_data["thermo"]["note"] == 120521 assert yaml_phase.species("plus").input_data["thermo"]["note"] == 12.05 ck_file = self.test_work_path / 'species-names.ck' ck_file.unlink(missing_ok=True) yaml_phase.write_chemkin(ck_file, quiet=True) yaml_file = self.test_work_path / 'species-names.yaml' yaml_file.unlink(missing_ok=True) ck2yaml.convert(ck_file, out_name=yaml_file, quiet=True) assert yaml_file.exists() ck_phase = ct.Solution(yaml_file) assert ck_phase.species("eq=uals").input_data["thermo"]["note"] == "120521" assert ck_phase.species("plus").input_data["thermo"]["note"] == "12.05" class cti2yamlTest(utilities.CanteraTest): def convert(self, basename, src_dir=None, encoding=None): if src_dir is None: src_dir = self.test_data_path cti2yaml.convert( filename=Path(src_dir) / f"{basename}.cti", output_name=self.test_work_path / f"{basename}-from-cti.yaml", encoding=encoding, ) def checkConversion(self, basename, cls=ct.Solution, ctiphases=(), yamlphases=(), **kwargs): ctiPhase = cls(f"{basename}-from-cti.yaml", adjacent=ctiphases, **kwargs) yamlPhase = cls(f"{basename}.yaml", adjacent=yamlphases, **kwargs) self.assertEqual(ctiPhase.element_names, yamlPhase.element_names) self.assertEqual(ctiPhase.species_names, yamlPhase.species_names) self.assertEqual(ctiPhase.n_reactions, yamlPhase.n_reactions) for C, Y in zip(ctiPhase.species(), yamlPhase.species()): self.assertEqual(C.composition, Y.composition) for C, Y in zip(ctiPhase.reactions(), yamlPhase.reactions()): self.assertEqual(C.__class__, Y.__class__) self.assertEqual(C.reactants, Y.reactants) self.assertEqual(C.products, Y.products) self.assertEqual(C.duplicate, Y.duplicate) for i, sp in zip(range(ctiPhase.n_reactions), ctiPhase.kinetics_species_names): self.assertEqual(ctiPhase.reactant_stoich_coeff(sp, i), yamlPhase.reactant_stoich_coeff(sp, i)) return ctiPhase, yamlPhase def checkThermo(self, ctiPhase, yamlPhase, temperatures, tol=1e-7, check_cp=True): for T in temperatures: ctiPhase.TP = T, ct.one_atm yamlPhase.TP = T, ct.one_atm if check_cp: cp_cti = ctiPhase.partial_molar_cp cp_yaml = yamlPhase.partial_molar_cp else: with pytest.raises(NotImplementedError): yamlPhase.partial_molar_cp h_cti = ctiPhase.partial_molar_enthalpies h_yaml = yamlPhase.partial_molar_enthalpies s_cti = ctiPhase.partial_molar_entropies s_yaml = yamlPhase.partial_molar_entropies self.assertNear(ctiPhase.density, yamlPhase.density) for i in range(ctiPhase.n_species): message = ' for species {0} at T = {1}'.format(i, T) if check_cp: self.assertNear(cp_cti[i], cp_yaml[i], tol, msg='cp'+message) self.assertNear(h_cti[i], h_yaml[i], tol, msg='h'+message) self.assertNear(s_cti[i], s_yaml[i], tol, msg='s'+message) def checkKinetics(self, ctiPhase, yamlPhase, temperatures, pressures, tol=1e-7): for T,P in itertools.product(temperatures, pressures): ctiPhase.TP = T, P yamlPhase.TP = T, P kf_cti = ctiPhase.forward_rate_constants kr_cti = ctiPhase.reverse_rate_constants kf_yaml = yamlPhase.forward_rate_constants kr_yaml = yamlPhase.reverse_rate_constants for i in range(yamlPhase.n_reactions): message = ' for reaction {0} at T = {1}, P = {2}'.format(i, T, P) self.assertNear(kf_cti[i], kf_yaml[i], rtol=tol, msg='kf '+message) self.assertNear(kr_cti[i], kr_yaml[i], rtol=tol, msg='kr '+message) def checkTransport(self, ctiPhase, yamlPhase, temperatures, model='mixture-averaged'): ctiPhase.transport_model = model yamlPhase.transport_model = model for T in temperatures: ctiPhase.TP = T, ct.one_atm yamlPhase.TP = T, ct.one_atm self.assertNear(ctiPhase.viscosity, yamlPhase.viscosity) self.assertNear(ctiPhase.thermal_conductivity, yamlPhase.thermal_conductivity) Dkm_cti = ctiPhase.mix_diff_coeffs Dkm_yaml = yamlPhase.mix_diff_coeffs for i in range(ctiPhase.n_species): message = 'dkm for species {0} at T = {1}'.format(i, T) self.assertNear(Dkm_cti[i], Dkm_yaml[i], msg=message) @utilities.slow_test def test_gri30(self): self.convert("gri30") ctiPhase, yamlPhase = self.checkConversion('gri30') X = {'O2': 0.3, 'H2': 0.1, 'CH4': 0.2, 'CO2': 0.4} ctiPhase.X = X yamlPhase.X = X self.checkThermo(ctiPhase, yamlPhase, [300, 500, 1300, 2000]) self.checkKinetics(ctiPhase, yamlPhase, [900, 1800], [2e5, 20e5]) self.checkTransport(ctiPhase, yamlPhase, [298, 1001, 2400]) def test_pdep(self): self.convert("pdep-test") ctiPhase, yamlPhase = self.checkConversion('pdep-test') # Agreement limited by low precision used by ck2cti for Chebyshev coeffs self.checkKinetics(ctiPhase, yamlPhase, [300, 1000, 2200], [100, ct.one_atm, 2e5, 2e6, 9.9e6], tol=2e-4) def test_ptcombust(self): self.convert("ptcombust") ctiSurf, yamlSurf = self.checkConversion("ptcombust", ct.Interface, name="Pt_surf") yamlGas = yamlSurf.adjacent["gas"] ctiGas = ctiSurf.adjacent["gas"] self.checkKinetics(ctiGas, yamlGas, [500, 1200], [1e4, 3e5]) self.checkThermo(ctiSurf, yamlSurf, [400, 800, 1600]) self.checkKinetics(ctiSurf, yamlSurf, [500, 1200], [1e4, 3e5]) @utilities.slow_test def test_ptcombust_motzwise(self): self.convert("ptcombust-motzwise") ctiSurf, yamlSurf = self.checkConversion("ptcombust-motzwise", ct.Interface, name="Pt_surf") yamlGas = yamlSurf.adjacent["gas"] ctiGas = ctiSurf.adjacent["gas"] self.checkKinetics(ctiGas, yamlGas, [500, 1200], [1e4, 3e5]) self.checkThermo(ctiSurf, yamlSurf, [400, 800, 1600]) self.checkKinetics(ctiSurf, yamlSurf, [900], [101325]) def test_sofc(self): self.convert("sofc") cti_tpb, yaml_tpb = self.checkConversion("sofc", ct.Interface, name="tpb") ctiMetal, ctiMSurf, ctiOSurf = cti_tpb.adjacent.values() yamlMetal, yamlMSurf, yamlOSurf = yaml_tpb.adjacent.values() self.assertIn("oxide_bulk", ctiOSurf.adjacent) self.assertIn("gas", ctiOSurf.adjacent) self.checkThermo(ctiMSurf, yamlMSurf, [900, 1000, 1100]) self.checkThermo(ctiOSurf, yamlOSurf, [900, 1000, 1100]) ctiMetal.electric_potential = yamlMetal.electric_potential = 2 self.checkKinetics(cti_tpb, yaml_tpb, [900, 1000, 1100], [1e5]) ctiMetal.electric_potential = yamlMetal.electric_potential = 4 self.checkKinetics(cti_tpb, yaml_tpb, [900, 1000, 1100], [1e5]) @utilities.slow_test def test_liquidvapor(self): self.convert("liquidvapor") for name in ["water", "nitrogen", "methane", "hydrogen", "oxygen", "heptane"]: ctiPhase, yamlPhase = self.checkConversion("liquidvapor", name=name) self.checkThermo(ctiPhase, yamlPhase, [1.3 * ctiPhase.min_temp, 0.7 * ctiPhase.max_temp]) def test_Redlich_Kwong_CO2(self): self.convert("co2_RK_example") ctiGas, yamlGas = self.checkConversion('co2_RK_example') for P in [1e5, 2e6, 1.3e7]: yamlGas.TP = ctiGas.TP = 300, P self.checkThermo(ctiGas, yamlGas, [300, 400, 500], check_cp=False) def test_diamond(self): self.convert("diamond") ctiSurf, yamlSurf = self.checkConversion("diamond", ct.Interface, name="diamond_100") ctiSolid = ctiSurf.adjacent["diamond"] yamlSolid = yamlSurf.adjacent["diamond"] self.checkThermo(ctiSolid, yamlSolid, [300, 500]) self.checkThermo(ctiSurf, yamlSurf, [330, 490]) self.checkKinetics(ctiSurf, yamlSurf, [400, 800], [2e5]) def test_lithium_ion_battery(self): name = 'lithium_ion_battery' self.convert(name, encoding="utf-8") ctiAnode, yamlAnode = self.checkConversion(name, name='anode') ctiCathode, yamlCathode = self.checkConversion(name, name='cathode') ctiMetal, yamlMetal = self.checkConversion(name, name='electron') ctiElyt, yamlElyt = self.checkConversion(name, name='electrolyte') ctiAnodeInt, yamlAnodeInt = self.checkConversion(name, name='edge_anode_electrolyte', ctiphases=[ctiAnode, ctiMetal, ctiElyt], yamlphases=[yamlAnode, yamlMetal, yamlElyt]) ctiCathodeInt, yamlCathodeInt = self.checkConversion(name, name='edge_cathode_electrolyte', ctiphases=[ctiCathode, ctiMetal, ctiElyt], yamlphases=[yamlCathode, yamlMetal, yamlElyt]) self.checkThermo(ctiAnode, yamlAnode, [300, 330]) self.checkThermo(ctiCathode, yamlCathode, [300, 330]) ctiAnode.X = yamlAnode.X = [0.7, 0.3] self.checkThermo(ctiAnode, yamlAnode, [300, 330]) ctiCathode.X = yamlCathode.X = [0.2, 0.8] self.checkThermo(ctiCathode, yamlCathode, [300, 330]) for phase in [ctiAnode, yamlAnode, ctiCathode, yamlCathode, ctiMetal, yamlMetal, ctiElyt, yamlElyt, ctiAnodeInt, yamlAnodeInt, ctiCathodeInt, yamlCathodeInt]: phase.TP = 300, 1e5 ctiMetal.electric_potential = yamlMetal.electric_potential = 0 ctiElyt.electric_potential = yamlElyt.electric_potential = 1.9 self.checkKinetics(ctiAnodeInt, yamlAnodeInt, [300], [1e5]) ctiMetal.electric_potential = yamlMetal.electric_potential = 2.2 ctiElyt.electric_potential = yamlElyt.electric_potential = 0 self.checkKinetics(ctiCathodeInt, yamlCathodeInt, [300], [1e5]) def test_ch4_ion(self): self.convert("ch4_ion") ctiGas, yamlGas = self.checkConversion("ch4_ion") self.checkThermo(ctiGas, yamlGas, [300, 500, 1300, 2000]) self.checkKinetics(ctiGas, yamlGas, [900, 1800], [2e5, 20e5]) self.checkTransport(ctiGas, yamlGas, [298, 1001, 2400]) def test_description(self): self.convert("haca2") ctiGas, yamlGas = self.checkConversion("haca2") assert ctiGas.input_header["description"].startswith("HACA Mechanism") assert yamlGas.input_header["description"].startswith("HACA Mechanism") def test_nonreactant_orders(self): self.convert("reaction-orders") ctiGas, yamlGas = self.checkConversion("reaction-orders") assert ctiGas.input_header["description"].startswith("Input file to test") self.checkThermo(ctiGas, yamlGas, [300, 500]) self.checkKinetics(ctiGas, yamlGas, [300, 1001, 2500], [1e5, 10e5]) class ctml2yamlTest(utilities.CanteraTest): def convert(self, basename, src_dir=None): if src_dir is None: src_dir = self.test_data_path ctml2yaml.convert( Path(src_dir) / f"{basename}.xml", self.test_work_path / f"{basename}-from-xml.yaml", ) def checkConversion(self, basename, cls=ct.Solution, ctmlphases=(), yamlphases=(), **kwargs): ctmlPhase = cls(f"{basename}-from-xml.yaml", adjacent=ctmlphases, **kwargs) yamlPhase = cls(f"{basename}.yaml", adjacent=yamlphases, **kwargs) self.assertEqual(ctmlPhase.element_names, yamlPhase.element_names) self.assertEqual(ctmlPhase.species_names, yamlPhase.species_names) self.assertEqual(ctmlPhase.n_reactions, yamlPhase.n_reactions) for C, Y in zip(ctmlPhase.species(), yamlPhase.species()): self.assertEqual(C.composition, Y.composition) for C, Y in zip(ctmlPhase.reactions(), yamlPhase.reactions()): self.assertEqual(C.__class__, Y.__class__) self.assertEqual(C.reactants, Y.reactants) self.assertEqual(C.products, Y.products) self.assertEqual(C.duplicate, Y.duplicate) for i, sp in zip(range(ctmlPhase.n_reactions), ctmlPhase.kinetics_species_names): self.assertEqual(ctmlPhase.reactant_stoich_coeff(sp, i), yamlPhase.reactant_stoich_coeff(sp, i)) return ctmlPhase, yamlPhase def checkThermo(self, ctmlPhase, yamlPhase, temperatures, pressure=ct.one_atm, tol=1e-7, check_cp=True): for T in temperatures: ctmlPhase.TP = T, pressure yamlPhase.TP = T, pressure if check_cp: cp_ctml = ctmlPhase.partial_molar_cp cp_yaml = yamlPhase.partial_molar_cp else: with pytest.raises(NotImplementedError): yamlPhase.partial_molar_cp h_ctml = ctmlPhase.partial_molar_enthalpies h_yaml = yamlPhase.partial_molar_enthalpies s_ctml = ctmlPhase.partial_molar_entropies s_yaml = yamlPhase.partial_molar_entropies self.assertNear(ctmlPhase.density, yamlPhase.density) for i in range(ctmlPhase.n_species): message = ' for species {0} at T = {1}'.format(ctmlPhase.species_names[i], T) if check_cp: self.assertNear(cp_ctml[i], cp_yaml[i], tol, msg='cp'+message) self.assertNear(h_ctml[i], h_yaml[i], tol, msg='h'+message) self.assertNear(s_ctml[i], s_yaml[i], tol, msg='s'+message) def checkKinetics(self, ctmlPhase, yamlPhase, temperatures, pressures, tol=1e-7): for T,P in itertools.product(temperatures, pressures): ctmlPhase.TP = T, P yamlPhase.TP = T, P kf_ctml = ctmlPhase.forward_rate_constants kr_ctml = ctmlPhase.reverse_rate_constants kf_yaml = yamlPhase.forward_rate_constants kr_yaml = yamlPhase.reverse_rate_constants for i in range(yamlPhase.n_reactions): message = ' for reaction {0} at T = {1}, P = {2}'.format(i, T, P) self.assertNear(kf_ctml[i], kf_yaml[i], rtol=tol, msg='kf '+message) self.assertNear(kr_ctml[i], kr_yaml[i], rtol=tol, msg='kr '+message) def checkTransport(self, ctmlPhase, yamlPhase, temperatures, model='mixture-averaged'): ctmlPhase.transport_model = model yamlPhase.transport_model = model for T in temperatures: ctmlPhase.TP = T, ct.one_atm yamlPhase.TP = T, ct.one_atm self.assertNear(ctmlPhase.viscosity, yamlPhase.viscosity) self.assertNear(ctmlPhase.thermal_conductivity, yamlPhase.thermal_conductivity) Dkm_ctml = ctmlPhase.mix_diff_coeffs Dkm_yaml = yamlPhase.mix_diff_coeffs for i in range(ctmlPhase.n_species): message = 'dkm for species {0} at T = {1}'.format(i, T) self.assertNear(Dkm_ctml[i], Dkm_yaml[i], msg=message) @utilities.slow_test def test_gri30(self): self.convert("gri30") ctmlPhase, yamlPhase = self.checkConversion('gri30') X = {'O2': 0.3, 'H2': 0.1, 'CH4': 0.2, 'CO2': 0.4} ctmlPhase.X = X yamlPhase.X = X self.checkThermo(ctmlPhase, yamlPhase, [300, 500, 1300, 2000]) self.checkKinetics(ctmlPhase, yamlPhase, [900, 1800], [2e5, 20e5]) self.checkTransport(ctmlPhase, yamlPhase, [298, 1001, 2400]) def test_pdep(self): self.convert("pdep-test") ctmlPhase, yamlPhase = self.checkConversion('pdep-test') # Chebyshev coefficients in XML are truncated to 6 digits, limiting accuracy self.checkKinetics(ctmlPhase, yamlPhase, [300, 1000, 2200], [100, ct.one_atm, 2e5, 2e6, 9.9e6], tol=2e-4) def test_ptcombust(self): self.convert("ptcombust") ctmlSurf, yamlSurf = self.checkConversion("ptcombust", ct.Interface, name="Pt_surf") ctmlGas = ctmlSurf.adjacent["gas"] yamlGas = yamlSurf.adjacent["gas"] self.checkKinetics(ctmlGas, yamlGas, [500, 1200], [1e4, 3e5]) self.checkThermo(ctmlSurf, yamlSurf, [400, 800, 1600]) self.checkKinetics(ctmlSurf, yamlSurf, [500, 1200], [1e4, 3e5]) def test_ptcombust_motzwise(self): self.convert("ptcombust-motzwise") ctmlGas, yamlGas = self.checkConversion('ptcombust-motzwise') ctmlSurf, yamlSurf = self.checkConversion('ptcombust-motzwise', ct.Interface, name='Pt_surf', ctmlphases=[ctmlGas], yamlphases=[yamlGas]) self.checkKinetics(ctmlGas, yamlGas, [500, 1200], [1e4, 3e5]) self.checkThermo(ctmlSurf, yamlSurf, [400, 800, 1600]) self.checkKinetics(ctmlSurf, yamlSurf, [500, 1200], [1e4, 3e5]) def test_sofc(self): self.convert("sofc") ctml_tpb, yaml_tpb = self.checkConversion("sofc", ct.Interface, name="tpb") ctmlMetal, ctmlMSurf, ctmlOSurf = ctml_tpb.adjacent.values() yamlMetal, yamlMSurf, yamlOSurf = yaml_tpb.adjacent.values() self.assertIn("oxide_bulk", ctmlOSurf.adjacent) self.assertIn("gas", ctmlOSurf.adjacent) self.checkThermo(ctmlMSurf, yamlMSurf, [900, 1000, 1100]) self.checkThermo(ctmlOSurf, yamlOSurf, [900, 1000, 1100]) ctmlMetal.electric_potential = yamlMetal.electric_potential = 2 self.checkKinetics(ctml_tpb, yaml_tpb, [900, 1000, 1100], [1e5]) ctmlMetal.electric_potential = yamlMetal.electric_potential = 4 self.checkKinetics(ctml_tpb, yaml_tpb, [900, 1000, 1100], [1e5]) def test_liquidvapor(self): self.convert("liquidvapor") for name in ["water", "nitrogen", "methane", "hydrogen", "oxygen", "heptane"]: ctmlPhase, yamlPhase = self.checkConversion("liquidvapor", name=name) self.checkThermo(ctmlPhase, yamlPhase, [1.3 * ctmlPhase.min_temp, 0.7 * ctmlPhase.max_temp]) def test_Redlich_Kwong_CO2(self): self.convert("co2_RK_example") ctmlGas, yamlGas = self.checkConversion('co2_RK_example') for P in [1e5, 2e6, 1.3e7]: yamlGas.TP = ctmlGas.TP = 300, P self.checkThermo(ctmlGas, yamlGas, [300, 400, 500], check_cp=False) def test_diamond(self): self.convert("diamond") ctmlGas, yamlGas = self.checkConversion('diamond', name='gas') ctmlSolid, yamlSolid = self.checkConversion('diamond', name='diamond') ctmlSurf, yamlSurf = self.checkConversion('diamond', ct.Interface, name='diamond_100', ctmlphases=[ctmlGas, ctmlSolid], yamlphases=[yamlGas, yamlSolid]) self.checkThermo(ctmlSolid, yamlSolid, [300, 500]) self.checkThermo(ctmlSurf, yamlSurf, [330, 490]) self.checkKinetics(ctmlSurf, yamlSurf, [400, 800], [2e5]) def test_lithium_ion_battery(self): name = 'lithium_ion_battery' self.convert(name) ctmlAnode, yamlAnode = self.checkConversion(name, name='anode') ctmlCathode, yamlCathode = self.checkConversion(name, name='cathode') ctmlMetal, yamlMetal = self.checkConversion(name, name='electron') ctmlElyt, yamlElyt = self.checkConversion(name, name='electrolyte') ctmlAnodeInt, yamlAnodeInt = self.checkConversion(name, name='edge_anode_electrolyte', ctmlphases=[ctmlAnode, ctmlMetal, ctmlElyt], yamlphases=[yamlAnode, yamlMetal, yamlElyt]) ctmlCathodeInt, yamlCathodeInt = self.checkConversion(name, name='edge_cathode_electrolyte', ctmlphases=[ctmlCathode, ctmlMetal, ctmlElyt], yamlphases=[yamlCathode, yamlMetal, yamlElyt]) self.checkThermo(ctmlAnode, yamlAnode, [300, 330]) self.checkThermo(ctmlCathode, yamlCathode, [300, 330]) ctmlAnode.X = yamlAnode.X = [0.7, 0.3] self.checkThermo(ctmlAnode, yamlAnode, [300, 330]) ctmlCathode.X = yamlCathode.X = [0.2, 0.8] self.checkThermo(ctmlCathode, yamlCathode, [300, 330]) for phase in [ctmlAnode, yamlAnode, ctmlCathode, yamlCathode, ctmlMetal, yamlMetal, ctmlElyt, yamlElyt, ctmlAnodeInt, yamlAnodeInt, ctmlCathodeInt, yamlCathodeInt]: phase.TP = 300, 1e5 ctmlMetal.electric_potential = yamlMetal.electric_potential = 0 ctmlElyt.electric_potential = yamlElyt.electric_potential = 1.9 self.checkKinetics(ctmlAnodeInt, yamlAnodeInt, [300], [1e5]) ctmlMetal.electric_potential = yamlMetal.electric_potential = 2.2 ctmlElyt.electric_potential = yamlElyt.electric_potential = 0 self.checkKinetics(ctmlCathodeInt, yamlCathodeInt, [300], [1e5]) def test_noxNeg(self): self.convert("noxNeg") ctmlGas, yamlGas = self.checkConversion('noxNeg') self.checkThermo(ctmlGas, yamlGas, [300, 1000]) self.checkKinetics(ctmlGas, yamlGas, [300, 1000], [1e5]) def test_ch4_ion(self): self.convert("ch4_ion") ctmlGas, yamlGas = self.checkConversion("ch4_ion") self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) self.checkKinetics(ctmlGas, yamlGas, [900, 1800], [2e5, 20e5]) self.checkTransport(ctmlGas, yamlGas, [298, 1001, 2400]) def test_nasa9(self): self.convert("nasa9-test") ctmlGas, yamlGas = self.checkConversion("nasa9-test") self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) def test_chemically_activated(self): self.convert("chemically-activated-reaction") ctmlGas, yamlGas = self.checkConversion("chemically-activated-reaction") self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) self.checkKinetics(ctmlGas, yamlGas, [900, 1800], [2e5, 20e5]) def test_explicit_forward_order(self): self.convert("explicit-forward-order") ctmlGas, yamlGas = self.checkConversion("explicit-forward-order") self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) # Accuracy limited by precision of ck2cti self.checkKinetics(ctmlGas, yamlGas, [900, 1800], [2e5, 20e5], tol=2e-7) def test_explicit_reverse_rate(self): self.convert("explicit-reverse-rate") ctmlGas, yamlGas = self.checkConversion("explicit-reverse-rate") self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) self.checkKinetics(ctmlGas, yamlGas, [900, 1800], [2e5, 20e5]) def test_explicit_third_bodies(self): self.convert("explicit-third-bodies") ctmlGas, yamlGas = self.checkConversion("explicit-third-bodies") self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) self.checkKinetics(ctmlGas, yamlGas, [900, 1800], [2e5, 20e5]) def test_fractional_stoich_coeffs(self): self.convert("frac") ctmlGas, yamlGas = self.checkConversion("frac") self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) self.checkKinetics(ctmlGas, yamlGas, [900, 1800], [2e5, 20e5]) def test_water_IAPWS95_thermo(self): self.convert("liquid-water") ctmlWater, yamlWater = self.checkConversion("liquid-water") self.checkThermo(ctmlWater, yamlWater, [300, 500, 1300, 2000], pressure=22064000.0) self.assertEqual(ctmlWater.transport_model, yamlWater.transport_model) ctmlWater.TP = yamlWater.TP = 300, 22064000.0 dens = ctmlWater.density for T in [298, 1001, 2400]: ctmlWater.TD = T, dens yamlWater.TD = T, dens self.assertNear(ctmlWater.viscosity, yamlWater.viscosity) self.assertNear(ctmlWater.thermal_conductivity, yamlWater.thermal_conductivity) def test_hmw_nacl_phase(self): basename = "HMW_NaCl_sp1977_alt" self.convert(basename) ctmlPhase, yamlPhase = self.checkConversion(basename) self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) def test_NaCl_solid_phase(self): self.convert("NaCl_Solid") ctmlPhase, yamlPhase = self.checkConversion("NaCl_Solid") self.checkThermo(ctmlPhase, yamlPhase, [300, 500, 1300, 2000]) def test_DH_NaCl_phase(self): self.convert("debye-huckel-all") for name in [ "debye-huckel-dilute", "debye-huckel-B-dot-ak", "debye-huckel-B-dot-a", "debye-huckel-pitzer-beta_ij", "debye-huckel-beta_ij", ]: ctmlPhase, yamlPhase = self.checkConversion("debye-huckel-all", name=name) self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) def test_Redlich_Kister(self): self.convert("RedlichKisterVPSSTP_valid") ctmlPhase, yamlPhase = self.checkConversion("RedlichKisterVPSSTP_valid") self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) def test_species_names(self): self.convert("species-names") ctmlGas, yamlGas = self.checkConversion('species-names') self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) def test_sri_falloff_reaction(self): self.convert("sri-falloff") ctmlGas, yamlGas = self.checkConversion("sri-falloff") self.checkThermo(ctmlGas, yamlGas, [300, 500, 1300, 2000]) self.checkKinetics(ctmlGas, yamlGas, [900, 1800], [2e5, 20e5]) def test_vpss_and_hkft(self): self.convert("pdss_hkft") ctmlPhase, yamlPhase = self.checkConversion("pdss_hkft") self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) def test_lattice_solid(self): self.convert("Li7Si3_ls") ctmlPhase, yamlPhase = self.checkConversion("Li7Si3_ls", name="Li7Si3_and_Interstitials(S)") self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) def test_margules(self): self.convert("LiKCl_liquid") ctmlPhase, yamlPhase = self.checkConversion("LiKCl_liquid") self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) def test_idealsolidsoln(self): with self.assertWarnsRegex(UserWarning, "SolidKinetics type is not implemented"): self.convert("IdealSolidSolnPhaseExample") # SolidKinetics is not implemented, so can't create a Kinetics class instance. basename = "IdealSolidSolnPhaseExample" ctmlPhase = ct.ThermoPhase(basename + "-from-xml.yaml") yamlPhase = ct.ThermoPhase(basename + ".yaml") self.assertEqual(ctmlPhase.element_names, yamlPhase.element_names) self.assertEqual(ctmlPhase.species_names, yamlPhase.species_names) self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) def test_idealmolalsoln(self): self.convert("IdealMolalSolnPhaseExample") ctmlPhase, yamlPhase = self.checkConversion("IdealMolalSolnPhaseExample") self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) def test_transport_models(self): self.convert("transport_models_test") for name in ["UnityLewis", "CK_Mix", "CK_Multi", "HighP"]: ctmlPhase, yamlPhase = self.checkConversion("transport_models_test", name=name) self.checkTransport(ctmlPhase, yamlPhase, [298, 1001, 2500]) def test_nonreactant_orders(self): self.convert("reaction-orders") ctmlPhase, yamlPhase = self.checkConversion("reaction-orders") self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) self.checkKinetics(ctmlPhase, yamlPhase, [300, 1001, 2500], [1e5, 10e5]) def test_species_ss_temperature_polynomials(self): self.convert("Li_Liquid") ctmlPhase, yamlPhase = self.checkConversion("Li_Liquid") self.checkThermo(ctmlPhase, yamlPhase, [300, 500]) def test_duplicate_section_ids(self): with self.assertWarnsRegex(UserWarning, "Duplicate 'speciesData' id"): self.convert("duplicate-speciesData-ids") with self.assertWarnsRegex(UserWarning, "Duplicate 'reactionData' id"): self.convert("duplicate-reactionData-ids")