From 5256ab99c155b3a75d32f575b472b37e1b04a613 Mon Sep 17 00:00:00 2001 From: Sevy Harris Date: Tue, 10 Feb 2026 16:07:29 -0500 Subject: [PATCH 01/13] save user input concentration as quantity We want to preserve the units specified in the input file as much as possible to avoid the ambiguity of saving just floats in memory --- rmgpy/rmg/input.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index de2ddf0c331..c3ce02a75ee 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -656,10 +656,7 @@ def liquid_cat_reactor(temperature, constantSpecies=[]): for spec, conc in initialConcentrations.items(): if not isinstance(conc, list): - concentration = Quantity(conc) - # check the dimensions are ok - # convert to mol/m^3 (or something numerically nice? or must it be SI) - initialConcentrations[spec] = concentration.value_si + initialConcentrations[spec] = Quantity(conc) else: if len(conc) != 2: raise InputError("Concentration values must either be in the form of (number,units) or a list with 2 " @@ -715,14 +712,14 @@ def liquid_cat_reactor(temperature, initialCondLiq = dict() V = 1.0 - A = V*Quantity(surfaceVolumeRatio).value_si - for key,item in initialConcentrations.items(): - initialCondLiq[key] = item*V + A = V * Quantity(surfaceVolumeRatio).value_si + for key, conc in initialConcentrations.items(): + initialCondLiq[key] = conc.value_si * V initialCondLiq["T"] = T initialCondLiq["V"] = V initialCondSurf = dict() - for key,item in initialSurfaceCoverages.items(): - initialCondSurf[key] = item*rmg.surface_site_density.value_si*A + for key, surf_cov in initialSurfaceCoverages.items(): + initialCondSurf[key] = surf_cov * rmg.surface_site_density.value_si * A initialCondSurf["T"] = T initialCondSurf["A"] = A initialCondSurf["d"] = 0.0 @@ -771,8 +768,7 @@ def constant_T_V_liquid_reactor(temperature, for spec, conc in initialConcentrations.items(): if not isinstance(conc, list): - concentration = Quantity(conc) - initialConcentrations[spec] = concentration.value_si + initialConcentrations[spec] = Quantity(conc) else: raise InputError("Condition ranges not supported for this reaction type") if len(conc) != 2: @@ -880,16 +876,16 @@ def constant_T_V_liquid_reactor(temperature, ############################################### process inputs ############################################## initial_conditions = dict() - for key, item in initialConcentrations.items(): - initial_conditions[key] = item*V + for key, conc in initialConcentrations.items(): + initial_conditions[key] = conc.value_si * V initial_conditions["T"] = T initial_conditions["V"] = V inlet_conditions = dict() if inletConcentrations: total_molar_flow_rate = 0 - for key, item in inletConcentrations.items(): - inlet_conditions[key] = item*inlet_volumetric_flow_rate + for key, inlet_conc in inletConcentrations.items(): + inlet_conditions[key] = inlet_conc.value_si * inlet_volumetric_flow_rate total_molar_flow_rate += inlet_conditions[key] for key, item in inlet_conditions.items(): inlet_conditions[key] = item/total_molar_flow_rate #molar fraction for each species @@ -946,10 +942,7 @@ def liquid_reactor(temperature, for spec, conc in initialConcentrations.items(): if not isinstance(conc, list): - concentration = Quantity(conc) - # check the dimensions are ok - # convert to mol/m^3 (or something numerically nice? or must it be SI) - initialConcentrations[spec] = concentration.value_si + initialConcentrations[spec] = Quantity(conc) else: if len(conc) != 2: raise InputError("Concentration values must either be in the form of (number,units) or a list with 2 " From b542589aaf89f2715802f2890d2fbbaa7ac3932b Mon Sep 17 00:00:00 2001 From: Sevy Harris Date: Wed, 11 Feb 2026 11:16:37 -0500 Subject: [PATCH 02/13] implement writing of input files with liquidSurfaceReactor --- rmgpy/rmg/input.py | 77 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index c3ce02a75ee..89cb33983c3 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -58,6 +58,7 @@ from rmgpy.solver.mbSampled import MBSampledReactor from rmgpy.solver.simple import SimpleReactor from rmgpy.solver.surface import SurfaceReactor +from rmgpy.solver.base import ReactionSystem from rmgpy.solver.termination import ( TerminationConversion, TerminationRateRatio, @@ -656,7 +657,10 @@ def liquid_cat_reactor(temperature, constantSpecies=[]): for spec, conc in initialConcentrations.items(): if not isinstance(conc, list): - initialConcentrations[spec] = Quantity(conc) + concentration = Quantity(conc) + # check the dimensions are ok + # convert to mol/m^3 (or something numerically nice? or must it be SI) + initialConcentrations[spec] = concentration.value_si else: if len(conc) != 2: raise InputError("Concentration values must either be in the form of (number,units) or a list with 2 " @@ -712,14 +716,14 @@ def liquid_cat_reactor(temperature, initialCondLiq = dict() V = 1.0 - A = V * Quantity(surfaceVolumeRatio).value_si - for key, conc in initialConcentrations.items(): - initialCondLiq[key] = conc.value_si * V + A = V*Quantity(surfaceVolumeRatio).value_si + for key,item in initialConcentrations.items(): + initialCondLiq[key] = item*V initialCondLiq["T"] = T initialCondLiq["V"] = V initialCondSurf = dict() - for key, surf_cov in initialSurfaceCoverages.items(): - initialCondSurf[key] = surf_cov * rmg.surface_site_density.value_si * A + for key,item in initialSurfaceCoverages.items(): + initialCondSurf[key] = item*rmg.surface_site_density.value_si*A initialCondSurf["T"] = T initialCondSurf["A"] = A initialCondSurf["d"] = 0.0 @@ -768,7 +772,8 @@ def constant_T_V_liquid_reactor(temperature, for spec, conc in initialConcentrations.items(): if not isinstance(conc, list): - initialConcentrations[spec] = Quantity(conc) + concentration = Quantity(conc) + initialConcentrations[spec] = concentration.value_si else: raise InputError("Condition ranges not supported for this reaction type") if len(conc) != 2: @@ -876,16 +881,16 @@ def constant_T_V_liquid_reactor(temperature, ############################################### process inputs ############################################## initial_conditions = dict() - for key, conc in initialConcentrations.items(): - initial_conditions[key] = conc.value_si * V + for key, item in initialConcentrations.items(): + initial_conditions[key] = item*V initial_conditions["T"] = T initial_conditions["V"] = V inlet_conditions = dict() if inletConcentrations: total_molar_flow_rate = 0 - for key, inlet_conc in inletConcentrations.items(): - inlet_conditions[key] = inlet_conc.value_si * inlet_volumetric_flow_rate + for key, item in inletConcentrations.items(): + inlet_conditions[key] = item*inlet_volumetric_flow_rate total_molar_flow_rate += inlet_conditions[key] for key, item in inlet_conditions.items(): inlet_conditions[key] = item/total_molar_flow_rate #molar fraction for each species @@ -942,7 +947,10 @@ def liquid_reactor(temperature, for spec, conc in initialConcentrations.items(): if not isinstance(conc, list): - initialConcentrations[spec] = Quantity(conc) + concentration = Quantity(conc) + # check the dimensions are ok + # convert to mol/m^3 (or something numerically nice? or must it be SI) + initialConcentrations[spec] = concentration.value_si else: if len(conc) != 2: raise InputError("Concentration values must either be in the form of (number,units) or a list with 2 " @@ -983,7 +991,7 @@ def liquid_reactor(temperature, if sensitivityConcentrations is None or sensitivityTemperature is None: sens_conditions = None else: - sens_conditions = sensitivityConcentrations + sens_conditions = deepcopy(sensitivityConcentrations) sens_conditions['T'] = Quantity(sensitivityTemperature).value_si system = LiquidReactor(T, initialConcentrations, nSims, termination, sensitive_species, sensitivityThreshold, @@ -1749,7 +1757,29 @@ def format_initial_mole_fractions(system): # Reaction systems for system in rmg.reaction_systems: - if rmg.solvent: + if isinstance(system, ConstantTLiquidSurfaceReactor): + f.write('liquidSurfaceReactor(\n') + f.write(' temperature = ' + format_temperature(system) + '\n') + f.write(' initialConcentrations={\n') + for spcs, conc in system.initial_conditions['liquid'].items(): + if spcs in ['T', 'V']: + continue + f.write(' "{0!s}": ({1:g},"{2!s}"),\n'.format(spcs, conc, 'mol/m^3')) + f.write(' initialSurfaceCoverages={\n') + for spcs, conc_mols in system.initial_conditions['surface'].items(): + if spcs in ['T', 'A', 'd']: + continue + # surf conc here is in mols, need to convert back into unitless coverage fraction + coverage = conc_mols / (rmg.surface_site_density.value_si * system.initial_conditions['surface']['A']) + f.write(' "{0!s}": ({1:g}),\n'.format(spcs, coverage)) + f.write(' },\n') + + # write the list of constant species + f.write(f' constantSpecies = {system.const_spc_names},\n') + + # write the surface Volume ratio, where ratio = A/V and A was originally constructed by assuming V=1 m^3 + f.write(' surfaceVolumeRatio = ({0:g}, "{1!s}"),\n'.format(system.initial_conditions['surface']['A'], 'm^-1')) + elif isinstance(system, LiquidReactor): f.write('liquidReactor(\n') f.write(' temperature = ' + format_temperature(system) + '\n') f.write(' initialConcentrations={\n') @@ -1778,8 +1808,25 @@ def format_initial_mole_fractions(system): f.write(' },\n') # Termination criteria + if isinstance(system, ReactionSystem): + terminations = system.termination + elif isinstance(system, Reactor): # RMS reactor terminations need to be converted back + terminations = [] + for term in system.terminations: + if hasattr(term, 'time'): + terminations.append(TerminationTime(time=(term.time, 's'))) + elif hasattr(term, 'ratio'): + terminations.append(TerminationRateRatio(ratio=term.ratio)) + elif isinstance(term, tuple): + species, conversion = term + terminations.append(TerminationConversion(spec=species, conv=conversion)) + else: + raise NotImplementedError('Termination criterion of type {0} is not currently supported for RMS reactors. Please convert this criterion to a time-based criterion or remove it from the input file.'.format(type(term))) + else: + raise NotImplementedError('Termination criteria for reaction system of type {0} not supported'.format(type(system))) + conversions = '' - for term in system.termination: + for term in terminations: if isinstance(term, TerminationTime): f.write(' terminationTime = ({0:g},"{1!s}"),\n'.format(term.time.value, term.time.units)) elif isinstance(term, TerminationRateRatio): From b57e44c6e070dc41aa01adbca071bec5c8df839f Mon Sep 17 00:00:00 2001 From: Sevy Harris Date: Sun, 15 Feb 2026 20:30:14 -0500 Subject: [PATCH 03/13] Remove unnecessary check for T in concentrations Now that sensitive conditions uses deepcopy to copy from initial_concentrations, there is no need to worry about T showing up in initial_concentrations --- rmgpy/solver/liquid.pyx | 2 -- 1 file changed, 2 deletions(-) diff --git a/rmgpy/solver/liquid.pyx b/rmgpy/solver/liquid.pyx index 9ebc9889b6b..fef047f2153 100644 --- a/rmgpy/solver/liquid.pyx +++ b/rmgpy/solver/liquid.pyx @@ -91,8 +91,6 @@ cdef class LiquidReactor(ReactionSystem): """ initial_concentrations = {} for label, moleFrac in self.initial_concentrations.items(): - if label == 'T': - continue initial_concentrations[species_dict[label]] = moleFrac self.initial_concentrations = initial_concentrations From 7b20c296eb23b662a43e77dfa7f3b0add27e3ad6 Mon Sep 17 00:00:00 2001 From: Sevy Harris Date: Thu, 19 Feb 2026 11:43:11 -0500 Subject: [PATCH 04/13] set output directory for initializing quantum mechanics --- rmgpy/rmg/input.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index 89cb33983c3..447bdaf7e80 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -1618,6 +1618,8 @@ def read_input_file(path, rmg0): if not isinstance(reaction_system, Reactor): reaction_system.convert_initial_keys_to_species_objects(species_dict) + if rmg.output_directory is None: + rmg.output_directory = os.path.dirname(full_path) if rmg.quantum_mechanics: rmg.quantum_mechanics.set_default_output_directory(rmg.output_directory) rmg.quantum_mechanics.initialize() From cc783a2e08ffaacff34e1defbe3fe4a26489d744 Mon Sep 17 00:00:00 2001 From: Sevy Harris Date: Mon, 6 Apr 2026 10:44:24 -0400 Subject: [PATCH 05/13] fix liquid cat reactor writing --- rmgpy/rmg/input.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index 447bdaf7e80..0c776369c2c 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -689,7 +689,7 @@ def liquid_cat_reactor(temperature, raise InputError('Species {0} not found in the input file'.format(const_spc)) if not isinstance(temperature, list): - T = Quantity(temperature).value_si + T = Quantity(temperature) else: raise InputError("Condition ranges not supported for this reaction type") if len(temperature) != 2: @@ -719,12 +719,12 @@ def liquid_cat_reactor(temperature, A = V*Quantity(surfaceVolumeRatio).value_si for key,item in initialConcentrations.items(): initialCondLiq[key] = item*V - initialCondLiq["T"] = T + initialCondLiq["T"] = T.value_si initialCondLiq["V"] = V initialCondSurf = dict() for key,item in initialSurfaceCoverages.items(): initialCondSurf[key] = item*rmg.surface_site_density.value_si*A - initialCondSurf["T"] = T + initialCondSurf["T"] = T.value_si initialCondSurf["A"] = A initialCondSurf["d"] = 0.0 if surfPotential: @@ -734,12 +734,12 @@ def liquid_cat_reactor(temperature, if distance: initialCondLiq["d"] = Quantity(distance).value_si if viscosity: - initialCondLiq["mu"] = Quantity(distance).value_si + initialCondLiq["mu"] = Quantity(viscosity).value_si system = ConstantTLiquidSurfaceReactor(rmg.reaction_model.core.phase_system, rmg.reaction_model.edge.phase_system, {"liquid":initialCondLiq,"surface":initialCondSurf}, termination,constantSpecies) - system.T = Quantity(T) + system.T = T system.Trange = None system.sensitive_species = [] rmg.reaction_systems.append(system) @@ -762,7 +762,7 @@ def constant_T_V_liquid_reactor(temperature, ################################################# check input ######################################################## if not isinstance(temperature, list): - T = Quantity(temperature).value_si + T = Quantity(temperature) else: raise InputError("Condition ranges not supported for this reaction type") if len(temperature) != 2: @@ -883,7 +883,7 @@ def constant_T_V_liquid_reactor(temperature, initial_conditions = dict() for key, item in initialConcentrations.items(): initial_conditions[key] = item*V - initial_conditions["T"] = T + initial_conditions["T"] = T.value_si initial_conditions["V"] = V inlet_conditions = dict() @@ -918,7 +918,7 @@ def constant_T_V_liquid_reactor(temperature, outlet_conditions, evap_cond_conditions, ) - system.T = Quantity(T) + system.T = T system.Trange = None system.sensitive_species = [] rmg.reaction_systems.append(system) @@ -1410,6 +1410,7 @@ def options(name='Seed', generateSeedEachIteration=True, saveSeedToDatabase=Fals def generated_species_constraints(**kwargs): valid_constraints = [ 'allowed', + 'explicitlyAllowedMolecules', 'maximumCarbonAtoms', 'maximumOxygenAtoms', 'maximumNitrogenAtoms', @@ -1704,7 +1705,7 @@ def save_input_file(path, rmg): f.write(' thermoLibraries = {0!r},\n'.format(rmg.thermo_libraries)) f.write(' reactionLibraries = {0!r},\n'.format(rmg.reaction_libraries)) f.write(' seedMechanisms = {0!r},\n'.format(rmg.seed_mechanisms)) - f.write(' kinetics_depositories = {0!r},\n'.format(rmg.kinetics_depositories)) + f.write(' kineticsDepositories = {0!r},\n'.format(rmg.kinetics_depositories)) f.write(' kineticsFamilies = {0!r},\n'.format(rmg.kinetics_families)) f.write(' kineticsEstimator = {0!r},\n'.format(rmg.kinetics_estimator)) f.write(')\n\n') @@ -1735,14 +1736,14 @@ def save_input_file(path, rmg): def format_temperature(system): """Get temperature string format for reaction system, whether single value or range""" if system.T is not None: - return '({0:g},"{1!s}")'.format(system.T.value, system.T.units) + return '({0:g},"{1!s}"),'.format(system.T.value, system.T.units) return f'[({system.Trange[0].value:g}, "{system.Trange[0].units}"), ({system.Trange[1].value:g}, "{system.Trange[1].units}")],' def format_pressure(system): """Get pressure string format for reaction system, whether single value or range""" if system.P is not None: - return '({0:g},"{1!s}")'.format(system.P.value, system.P.units) + return '({0:g},"{1!s}"),'.format(system.P.value, system.P.units) return f'[({system.Prange[0].value:g}, "{system.Prange[0].units}"), ({system.Prange[1].value:g}, "{system.Prange[1].units}")],' @@ -1767,13 +1768,14 @@ def format_initial_mole_fractions(system): if spcs in ['T', 'V']: continue f.write(' "{0!s}": ({1:g},"{2!s}"),\n'.format(spcs, conc, 'mol/m^3')) + f.write(' },\n') f.write(' initialSurfaceCoverages={\n') for spcs, conc_mols in system.initial_conditions['surface'].items(): if spcs in ['T', 'A', 'd']: continue # surf conc here is in mols, need to convert back into unitless coverage fraction coverage = conc_mols / (rmg.surface_site_density.value_si * system.initial_conditions['surface']['A']) - f.write(' "{0!s}": ({1:g}),\n'.format(spcs, coverage)) + f.write(' "{0!s}": {1:g},\n'.format(spcs, coverage)) f.write(' },\n') # write the list of constant species @@ -1790,6 +1792,7 @@ def format_initial_mole_fractions(system): if type(conc) == float: conc = Quantity(conc, Concentration.units) f.write(' "{0!s}": ({1:g},"{2!s}"),\n'.format(spcs.label, conc.value, conc.units)) + f.write(' },\n') elif isinstance(system, SurfaceReactor): f.write('surfaceReactor(\n') f.write(' temperature = ' + format_temperature(system) + '\n') @@ -1801,13 +1804,14 @@ def format_initial_mole_fractions(system): f.write(' initialSurfaceCoverages={\n') for spcs, cov in system.initial_surface_coverages.items(): f.write(' "{0!s}": {1:g},\n'.format(spcs.label, cov)) + f.write(' },\n') else: f.write('simpleReactor(\n') f.write(' temperature = ' + format_temperature(system) + '\n') f.write(' pressure = ' + format_pressure(system) + '\n') f.write(' initialMoleFractions={\n') f.write(format_initial_mole_fractions(system)) - f.write(' },\n') + f.write(' },\n') # Termination criteria if isinstance(system, ReactionSystem): @@ -1871,7 +1875,7 @@ def format_initial_mole_fractions(system): f.write(' maximumEdgeSpecies = {0:d},\n'.format(rmg.model_settings_list[0].maximum_edge_species)) f.write(' minCoreSizeForPrune = {0:d},\n'.format(rmg.model_settings_list[0].min_core_size_for_prune)) f.write(' minSpeciesExistIterationsForPrune = {0:d},\n'.format(rmg.model_settings_list[0].min_species_exist_iterations_for_prune)) - f.write(' filterReactions = {0:d},\n'.format(rmg.model_settings_list[0].filter_reactions)) + f.write(' filterReactions = {0},\n'.format(bool(rmg.model_settings_list[0].filter_reactions))) f.write(' filterThreshold = {0:g},\n'.format(rmg.model_settings_list[0].filter_threshold)) f.write(')\n\n') @@ -1941,7 +1945,7 @@ def formula(elements): f.write(' keepIrreversible = {0},\n'.format(rmg.keep_irreversible)) f.write(' trimolecularProductReversible = {0},\n'.format(rmg.trimolecular_product_reversible)) f.write(' verboseComments = {0},\n'.format(rmg.verbose_comments)) - f.write(' wallTime = {0},\n'.format(rmg.walltime)) + f.write(' wallTime = "{0}",\n'.format(rmg.walltime)) f.write(')\n\n') f.close() From 535ca63fc1cc57333640a267c1a4ef1f0d69b317 Mon Sep 17 00:00:00 2001 From: Sevy Harris Date: Mon, 6 Apr 2026 11:04:44 -0400 Subject: [PATCH 06/13] Add test for writing superminimal input file --- test/rmgpy/rmg/inputTest.py | 55 +++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/test/rmgpy/rmg/inputTest.py b/test/rmgpy/rmg/inputTest.py index 00160d974b9..05cf4d867cb 100644 --- a/test/rmgpy/rmg/inputTest.py +++ b/test/rmgpy/rmg/inputTest.py @@ -447,3 +447,58 @@ def test_completed_networks_none(self): # Check that no networks were added assert len(rmg.reaction_model.completed_pdep_networks) == 0 + +class TestWriteInputFile: + """ + Contains unit test for writing input files + """ + def setup_method(self): + """This method is run before every test in this class""" + global rmg + rmg.reaction_systems = [] + + + def test_write_superminimal_input(self): + """ + Test that we can write superminimal input file and read it back in with the same values. + """ + + superminimal_input_file = '../../../examples/rmg/superminimal/input.py' + superminimal_output_file = 'temp_superminimal_input.py' + + + rmg = RMG() + inp.read_input_file(superminimal_input_file, rmg) + + # read a bunch of values in from input file to check they are the same after writing + T = rmg.reaction_systems[0].T.value_si + P = rmg.reaction_systems[0].P.value_si + initialMoleFractions = {k.label: v for k, v in rmg.reaction_systems[0].initial_mole_fractions.items()} + for term in rmg.reaction_systems[0].termination: + if hasattr(term, 'time'): + termination_time = term.time.value_si + elif hasattr(term, 'conversion'): + termination_conversion = term.conversion + termination_converstion_species = term.species.label + + + + inp.save_input_file(superminimal_output_file, rmg) + # read it back in and confirm all the values match + rmg1 = RMG() + inp.read_input_file(superminimal_output_file, rmg1) + assert rmg1.reaction_systems[0].T.value_si == T + assert rmg1.reaction_systems[0].P.value_si == P + output_mol_fractions = {k.label: v for k, v in rmg1.reaction_systems[0].initial_mole_fractions.items()} + assert output_mol_fractions== initialMoleFractions + for term in rmg1.reaction_systems[0].termination: + if hasattr(term, 'time'): + assert term.time.value_si == termination_time + elif hasattr(term, 'conversion'): + assert term.conversion == termination_conversion + assert term.species.label == termination_converstion_species + + # clean up + import os + os.remove(superminimal_output_file) + From 3a8c5b0fea80f842ba6908452cee5508b59c178b Mon Sep 17 00:00:00 2001 From: Sevy Harris Date: Mon, 6 Apr 2026 11:54:54 -0400 Subject: [PATCH 07/13] Add unit test for writing surface reactor input file --- rmgpy/rmg/input.py | 1 + test/rmgpy/rmg/inputTest.py | 100 +++++++++++++++++++++++++++++++++++- 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index 0c776369c2c..fa1a5e64762 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -1805,6 +1805,7 @@ def format_initial_mole_fractions(system): for spcs, cov in system.initial_surface_coverages.items(): f.write(' "{0!s}": {1:g},\n'.format(spcs.label, cov)) f.write(' },\n') + f.write(' surfaceVolumeRatio = ({0:g}, "{1!s}"),\n'.format(system.surface_volume_ratio.value, system.surface_volume_ratio.units)) else: f.write('simpleReactor(\n') f.write(' temperature = ' + format_temperature(system) + '\n') diff --git a/test/rmgpy/rmg/inputTest.py b/test/rmgpy/rmg/inputTest.py index 05cf4d867cb..e546d2a0f0a 100644 --- a/test/rmgpy/rmg/inputTest.py +++ b/test/rmgpy/rmg/inputTest.py @@ -448,9 +448,20 @@ def test_completed_networks_none(self): # Check that no networks were added assert len(rmg.reaction_model.completed_pdep_networks) == 0 + class TestWriteInputFile: """ - Contains unit test for writing input files + Contains unit test for writing input files for each of the reactor types: + + 'simpleReactor': simple_reactor, ✅ + 'constantVIdealGasReactor' : constant_V_ideal_gas_reactor, + 'constantTPIdealGasReactor' : constant_TP_ideal_gas_reactor, + 'liquidSurfaceReactor' : liquid_cat_reactor, + 'constantTVLiquidReactor': constant_T_V_liquid_reactor, + 'liquidReactor': liquid_reactor, + 'surfaceReactor': surface_reactor, ✅ + 'mbsampledReactor': mb_sampled_reactor, + """ def setup_method(self): """This method is run before every test in this class""" @@ -502,3 +513,90 @@ def test_write_superminimal_input(self): import os os.remove(superminimal_output_file) + @pytest.mark.skip(reason="Slow test that runs a full RMG job") + def test_write_superminimal_and_run(self): + """ + Test that we can write superminimal input file and then run RMG without errors + """ + import os + import shutil + + + superminimal_input_file = '../../../examples/rmg/superminimal/input.py' + new_run_dir = 'temp_superminimal_run' + os.makedirs(new_run_dir, exist_ok=True) + superminimal_output_file = os.path.join(new_run_dir, 'temp_superminimal_input.py') + + + rmg = RMG() + inp.read_input_file(superminimal_input_file, rmg) + inp.save_input_file(superminimal_output_file, rmg) + + # run RMG with the new input file + import subprocess + subprocess.run(['python', '../../../rmg.py', superminimal_output_file], check=True) + + + # clean up + shutil.rmtree(new_run_dir) + + + def test_write_min_surf_input(self): + """ + Test that we can write the minimal surface input file and read it back in with the same values. + """ + + min_surf_input_file = '../../../examples/rmg/minimal_surface/input.py' + min_surf_output_file = 'temp_min_surf_input.py' + + + rmg = RMG() + inp.read_input_file(min_surf_input_file, rmg) + + # read a bunch of values in from input file to check they are the same after writing + T = rmg.reaction_systems[0].T.value_si + P = rmg.reaction_systems[0].P_initial.value_si + initialMoleFractions = {k.label: v for k, v in rmg.reaction_systems[0].initial_gas_mole_fractions.items()} + initialSurfaceCoverages = {k.label: v for k, v in rmg.reaction_systems[0].initial_surface_coverages.items()} + for term in rmg.reaction_systems[0].termination: + if hasattr(term, 'time'): + termination_time = term.time.value_si + elif hasattr(term, 'conversion'): + termination_conversion = term.conversion + termination_converstion_species = term.species.label + elif hasattr(term, 'ratio'): + termination_ratio = term.ratio + + binding_energies = {k: v.value_si for k, v in rmg.binding_energies.items()} + surface_site_density = rmg.surface_site_density.value_si + + + inp.save_input_file(min_surf_output_file, rmg) + # read it back in and confirm all the values match + rmg1 = RMG() + inp.read_input_file(min_surf_output_file, rmg1) + assert rmg1.reaction_systems[0].T.value_si == T + assert rmg1.reaction_systems[0].P_initial.value_si == P + output_mol_fractions = {k.label: v for k, v in rmg1.reaction_systems[0].initial_gas_mole_fractions.items()} + assert output_mol_fractions== initialMoleFractions + output_surface_coverages = {k.label: v for k, v in rmg1.reaction_systems[0].initial_surface_coverages.items()} + assert output_surface_coverages == initialSurfaceCoverages + + output_binding_energies = {k: v.value_si for k, v in rmg1.binding_energies.items()} + assert output_binding_energies == binding_energies + + assert rmg1.surface_site_density.value_si == surface_site_density + + for term in rmg1.reaction_systems[0].termination: + if hasattr(term, 'time'): + assert term.time.value_si == termination_time + elif hasattr(term, 'conversion'): + assert term.conversion == termination_conversion + assert term.species.label == termination_converstion_species + elif hasattr(term, 'ratio'): + assert term.ratio == termination_ratio + + # clean up + import os + os.remove(min_surf_output_file) + From 0f3e5c56c82c3e898280cb14bd8d7dfa3cdbd91e Mon Sep 17 00:00:00 2001 From: Sevy Harris Date: Mon, 6 Apr 2026 12:43:29 -0400 Subject: [PATCH 08/13] Add test for writing liquid cat reactor input file --- test/rmgpy/rmg/inputTest.py | 103 +++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 13 deletions(-) diff --git a/test/rmgpy/rmg/inputTest.py b/test/rmgpy/rmg/inputTest.py index e546d2a0f0a..244a7d35c58 100644 --- a/test/rmgpy/rmg/inputTest.py +++ b/test/rmgpy/rmg/inputTest.py @@ -456,7 +456,7 @@ class TestWriteInputFile: 'simpleReactor': simple_reactor, ✅ 'constantVIdealGasReactor' : constant_V_ideal_gas_reactor, 'constantTPIdealGasReactor' : constant_TP_ideal_gas_reactor, - 'liquidSurfaceReactor' : liquid_cat_reactor, + 'liquidSurfaceReactor' : liquid_cat_reactor, ✅ 'constantTVLiquidReactor': constant_T_V_liquid_reactor, 'liquidReactor': liquid_reactor, 'surfaceReactor': surface_reactor, ✅ @@ -468,7 +468,6 @@ def setup_method(self): global rmg rmg.reaction_systems = [] - def test_write_superminimal_input(self): """ Test that we can write superminimal input file and read it back in with the same values. @@ -477,7 +476,6 @@ def test_write_superminimal_input(self): superminimal_input_file = '../../../examples/rmg/superminimal/input.py' superminimal_output_file = 'temp_superminimal_input.py' - rmg = RMG() inp.read_input_file(superminimal_input_file, rmg) @@ -492,8 +490,6 @@ def test_write_superminimal_input(self): termination_conversion = term.conversion termination_converstion_species = term.species.label - - inp.save_input_file(superminimal_output_file, rmg) # read it back in and confirm all the values match rmg1 = RMG() @@ -501,7 +497,7 @@ def test_write_superminimal_input(self): assert rmg1.reaction_systems[0].T.value_si == T assert rmg1.reaction_systems[0].P.value_si == P output_mol_fractions = {k.label: v for k, v in rmg1.reaction_systems[0].initial_mole_fractions.items()} - assert output_mol_fractions== initialMoleFractions + assert output_mol_fractions == initialMoleFractions for term in rmg1.reaction_systems[0].termination: if hasattr(term, 'time'): assert term.time.value_si == termination_time @@ -521,12 +517,10 @@ def test_write_superminimal_and_run(self): import os import shutil - superminimal_input_file = '../../../examples/rmg/superminimal/input.py' new_run_dir = 'temp_superminimal_run' os.makedirs(new_run_dir, exist_ok=True) superminimal_output_file = os.path.join(new_run_dir, 'temp_superminimal_input.py') - rmg = RMG() inp.read_input_file(superminimal_input_file, rmg) @@ -536,11 +530,9 @@ def test_write_superminimal_and_run(self): import subprocess subprocess.run(['python', '../../../rmg.py', superminimal_output_file], check=True) - # clean up shutil.rmtree(new_run_dir) - def test_write_min_surf_input(self): """ Test that we can write the minimal surface input file and read it back in with the same values. @@ -549,7 +541,6 @@ def test_write_min_surf_input(self): min_surf_input_file = '../../../examples/rmg/minimal_surface/input.py' min_surf_output_file = 'temp_min_surf_input.py' - rmg = RMG() inp.read_input_file(min_surf_input_file, rmg) @@ -570,7 +561,6 @@ def test_write_min_surf_input(self): binding_energies = {k: v.value_si for k, v in rmg.binding_energies.items()} surface_site_density = rmg.surface_site_density.value_si - inp.save_input_file(min_surf_output_file, rmg) # read it back in and confirm all the values match rmg1 = RMG() @@ -578,7 +568,7 @@ def test_write_min_surf_input(self): assert rmg1.reaction_systems[0].T.value_si == T assert rmg1.reaction_systems[0].P_initial.value_si == P output_mol_fractions = {k.label: v for k, v in rmg1.reaction_systems[0].initial_gas_mole_fractions.items()} - assert output_mol_fractions== initialMoleFractions + assert output_mol_fractions == initialMoleFractions output_surface_coverages = {k.label: v for k, v in rmg1.reaction_systems[0].initial_surface_coverages.items()} assert output_surface_coverages == initialSurfaceCoverages @@ -600,3 +590,90 @@ def test_write_min_surf_input(self): import os os.remove(min_surf_output_file) + @pytest.mark.skip(reason="Slow test that runs a full RMG job") + def test_write_min_surf_and_run(self): + """ + Test that we can write minimal surface input file and then run RMG without errors + """ + import os + import shutil + + min_surf_input_file = '../../../examples/rmg/minimal_surface/input.py' + new_run_dir = 'temp_min_surf_run' + os.makedirs(new_run_dir, exist_ok=True) + min_surf_output_file = os.path.join(new_run_dir, 'temp_min_surf_input.py') + + rmg = RMG() + inp.read_input_file(min_surf_input_file, rmg) + inp.save_input_file(min_surf_output_file, rmg) + + # run RMG with the new input file + import subprocess + subprocess.run(['python', '../../../rmg.py', min_surf_output_file], check=True) + + # clean up + shutil.rmtree(new_run_dir) + + @pytest.mark.skip(reason="Slow because it has to compile Julia") + def test_write_liquid_cat_input(self): + """ + Test that we can write liquid catalyst input file and read it back in with the same values. + """ + + liquid_cat_input_file = '../../../examples/rmg/liquid_cat/input.py' + liquid_cat_output_file = 'temp_liquid_cat_input.py' + + rmg = RMG() + inp.read_input_file(liquid_cat_input_file, rmg) + + # read a bunch of values in from input file to check they are the same after writing + T = rmg.reaction_systems[0].T.value_si + tf = rmg.reaction_systems[0].tf + liquid_init_conditions = {k: v for k, v in rmg.reaction_systems[0].initial_conditions['liquid'].items()} + surf_init_conditions = {k: v for k, v in rmg.reaction_systems[0].initial_conditions['surface'].items()} + + termination_species = rmg.reaction_systems[0].terminations[0][0].label + termination_conversion = rmg.reaction_systems[0].terminations[0][1] + + inp.save_input_file(liquid_cat_output_file, rmg) + # read it back in and confirm all the values match + rmg1 = RMG() + inp.read_input_file(liquid_cat_output_file, rmg1) + assert rmg1.reaction_systems[0].T.value_si == T + assert rmg1.reaction_systems[0].tf == tf + + liquid_init_conditions_output = {k: v for k, v in rmg1.reaction_systems[0].initial_conditions['liquid'].items()} + assert pytest.approx(liquid_init_conditions_output) == liquid_init_conditions + surf_init_conditions_output = {k: v for k, v in rmg1.reaction_systems[0].initial_conditions['surface'].items()} + assert pytest.approx(surf_init_conditions_output) == surf_init_conditions + + assert rmg1.reaction_systems[0].terminations[0][0].label == termination_species + assert rmg1.reaction_systems[0].terminations[0][1] == termination_conversion + + # clean up + import os + os.remove(liquid_cat_output_file) + + @pytest.mark.skip(reason="Slow test that runs a full RMG job") + def test_write_liquid_cat_and_run(self): + """ + Test that we can write liquid catalyst input file and then run RMG without errors + """ + import os + import shutil + + liquid_cat_input_file = '../../../examples/rmg/liquid_cat/input.py' + new_run_dir = 'temp_liquid_cat_run' + os.makedirs(new_run_dir, exist_ok=True) + liquid_cat_output_file = os.path.join(new_run_dir, 'temp_liquid_cat_input.py') + + rmg = RMG() + inp.read_input_file(liquid_cat_input_file, rmg) + inp.save_input_file(liquid_cat_output_file, rmg) + + # run RMG with the new input file + import subprocess + subprocess.run(['python', '../../../rmg.py', '-t', '00:00:01:30', liquid_cat_output_file], check=True) + + # clean up + shutil.rmtree(new_run_dir) From aa61ea9e778644f2bf20950be0c4654635e28a64 Mon Sep 17 00:00:00 2001 From: Sevy Harris Date: Mon, 6 Apr 2026 12:52:50 -0400 Subject: [PATCH 09/13] add test for writing liquid reactor input file --- test/rmgpy/rmg/inputTest.py | 63 ++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/test/rmgpy/rmg/inputTest.py b/test/rmgpy/rmg/inputTest.py index 244a7d35c58..e84e3c7a38b 100644 --- a/test/rmgpy/rmg/inputTest.py +++ b/test/rmgpy/rmg/inputTest.py @@ -458,7 +458,7 @@ class TestWriteInputFile: 'constantTPIdealGasReactor' : constant_TP_ideal_gas_reactor, 'liquidSurfaceReactor' : liquid_cat_reactor, ✅ 'constantTVLiquidReactor': constant_T_V_liquid_reactor, - 'liquidReactor': liquid_reactor, + 'liquidReactor': liquid_reactor, ✅ 'surfaceReactor': surface_reactor, ✅ 'mbsampledReactor': mb_sampled_reactor, @@ -677,3 +677,64 @@ def test_write_liquid_cat_and_run(self): # clean up shutil.rmtree(new_run_dir) + + def test_write_liquid_input(self): + """ + Test that we can write the liquid reactor input file and read it back in with the same values. + """ + + liquid_input_file = '../../../examples/rmg/liquid_phase/input.py' + liquid_output_file = 'temp_liquid_input.py' + + rmg = RMG() + inp.read_input_file(liquid_input_file, rmg) + + # read a bunch of values in from input file to check they are the same after writing + T = rmg.reaction_systems[0].T.value_si + P = rmg.reaction_systems[0].P.value_si + initialConcentrations = {k.label: v for k, v in rmg.reaction_systems[0].initial_concentrations.items()} + for term in rmg.reaction_systems[0].termination: + if hasattr(term, 'time'): + termination_time = term.time.value_si + solvent = rmg.solvent + + inp.save_input_file(liquid_output_file, rmg) + # read it back in and confirm all the values match + rmg1 = RMG() + inp.read_input_file(liquid_output_file, rmg1) + assert rmg1.reaction_systems[0].T.value_si == T + assert rmg1.reaction_systems[0].P.value_si == P + output_concentrations = {k.label: v for k, v in rmg1.reaction_systems[0].initial_concentrations.items()} + assert pytest.approx(output_concentrations) == initialConcentrations + for term in rmg1.reaction_systems[0].termination: + if hasattr(term, 'time'): + assert term.time.value_si == termination_time + assert rmg1.solvent == solvent + + # clean up + import os + os.remove(liquid_output_file) + + @pytest.mark.skip(reason="Slow test that runs a full RMG job") + def test_write_liquid_and_run(self): + """ + Test that we can write liquid reactor input file and then run RMG without errors + """ + import os + import shutil + + liquid_input_file = '../../../examples/rmg/liquid_phase/input.py' + new_run_dir = 'temp_liquid_run' + os.makedirs(new_run_dir, exist_ok=True) + liquid_output_file = os.path.join(new_run_dir, 'temp_liquid_input.py') + + rmg = RMG() + inp.read_input_file(liquid_input_file, rmg) + inp.save_input_file(liquid_output_file, rmg) + + # run RMG with the new input file + import subprocess + subprocess.run(['python', '../../../rmg.py', '-t', '00:00:01:30', liquid_output_file], check=True) + + # clean up + shutil.rmtree(new_run_dir) From ec6c958dcaf1a5a4d28dc9f8b8eb018447b40c88 Mon Sep 17 00:00:00 2001 From: Sevy Harris Date: Mon, 6 Apr 2026 13:21:56 -0400 Subject: [PATCH 10/13] Add test for rms constant V reactor write input file --- rmgpy/rmg/input.py | 31 +++++++++++------ test/rmgpy/rmg/inputTest.py | 67 ++++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 12 deletions(-) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index fa1a5e64762..5e5c472b411 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -465,7 +465,7 @@ def constant_V_ideal_gas_reactor(temperature, raise InputError('Initial mole fraction range out of order: {0}'.format(key)) if not isinstance(temperature, list): - T = Quantity(temperature).value_si + T = Quantity(temperature) else: raise InputError("Condition ranges not supported for this reaction type") if len(temperature) != 2: @@ -513,11 +513,11 @@ def constant_V_ideal_gas_reactor(temperature, termination.append(TerminationRateRatio(terminationRateRatio)) if len(termination) == 0: raise InputError('No termination conditions specified for reaction system #{0}.'.format(len(rmg.reaction_systems) + 2)) - + initial_cond = initialMoleFractions - initial_cond["T"] = T + initial_cond["T"] = T.value_si initial_cond["P"] = P - system = ConstantVIdealGasReactor(rmg.reaction_model.core.phase_system,rmg.reaction_model.edge.phase_system,initial_cond,termination) + system = ConstantVIdealGasReactor(rmg.reaction_model.core.phase_system, rmg.reaction_model.edge.phase_system, initial_cond,termination) system.T = Quantity(T) system.P = Quantity(P) system.Trange = None @@ -1573,9 +1573,9 @@ def read_input_file(path, rmg0): 'adjacencyListGroup': adjacency_list_group, 'react': react, 'simpleReactor': simple_reactor, - 'constantVIdealGasReactor' : constant_V_ideal_gas_reactor, - 'constantTPIdealGasReactor' : constant_TP_ideal_gas_reactor, - 'liquidSurfaceReactor' : liquid_cat_reactor, + 'constantVIdealGasReactor': constant_V_ideal_gas_reactor, + 'constantTPIdealGasReactor': constant_TP_ideal_gas_reactor, + 'liquidSurfaceReactor': liquid_cat_reactor, 'constantTVLiquidReactor': constant_T_V_liquid_reactor, 'liquidReactor': liquid_reactor, 'surfaceReactor': surface_reactor, @@ -1737,14 +1737,14 @@ def format_temperature(system): """Get temperature string format for reaction system, whether single value or range""" if system.T is not None: return '({0:g},"{1!s}"),'.format(system.T.value, system.T.units) - + return f'[({system.Trange[0].value:g}, "{system.Trange[0].units}"), ({system.Trange[1].value:g}, "{system.Trange[1].units}")],' def format_pressure(system): """Get pressure string format for reaction system, whether single value or range""" if system.P is not None: return '({0:g},"{1!s}"),'.format(system.P.value, system.P.units) - + return f'[({system.Prange[0].value:g}, "{system.Prange[0].units}"), ({system.Prange[1].value:g}, "{system.Prange[1].units}")],' def format_initial_mole_fractions(system): @@ -1757,7 +1757,6 @@ def format_initial_mole_fractions(system): mole_fractions += ' "{0!s}": {1:g},\n'.format(spcs.label, molfrac) return mole_fractions - # Reaction systems for system in rmg.reaction_systems: if isinstance(system, ConstantTLiquidSurfaceReactor): @@ -1777,7 +1776,7 @@ def format_initial_mole_fractions(system): coverage = conc_mols / (rmg.surface_site_density.value_si * system.initial_conditions['surface']['A']) f.write(' "{0!s}": {1:g},\n'.format(spcs, coverage)) f.write(' },\n') - + # write the list of constant species f.write(f' constantSpecies = {system.const_spc_names},\n') @@ -1806,6 +1805,16 @@ def format_initial_mole_fractions(system): f.write(' "{0!s}": {1:g},\n'.format(spcs.label, cov)) f.write(' },\n') f.write(' surfaceVolumeRatio = ({0:g}, "{1!s}"),\n'.format(system.surface_volume_ratio.value, system.surface_volume_ratio.units)) + elif isinstance(system, ConstantVIdealGasReactor): + f.write('constantVIdealGasReactor(\n') + f.write(' temperature = ' + format_temperature(system) + '\n') + f.write(' pressure = ' + format_pressure(system) + '\n') + f.write(' initialMoleFractions={\n') + for spcs, conc in system.initial_conditions.items(): + if spcs in ['T', 'P']: + continue + f.write(' "{0!s}": {1:g},\n'.format(spcs, conc)) + f.write(' },\n') else: f.write('simpleReactor(\n') f.write(' temperature = ' + format_temperature(system) + '\n') diff --git a/test/rmgpy/rmg/inputTest.py b/test/rmgpy/rmg/inputTest.py index e84e3c7a38b..9cc9bcbe51c 100644 --- a/test/rmgpy/rmg/inputTest.py +++ b/test/rmgpy/rmg/inputTest.py @@ -454,7 +454,7 @@ class TestWriteInputFile: Contains unit test for writing input files for each of the reactor types: 'simpleReactor': simple_reactor, ✅ - 'constantVIdealGasReactor' : constant_V_ideal_gas_reactor, + 'constantVIdealGasReactor' : constant_V_ideal_gas_reactor, ✅ 'constantTPIdealGasReactor' : constant_TP_ideal_gas_reactor, 'liquidSurfaceReactor' : liquid_cat_reactor, ✅ 'constantTVLiquidReactor': constant_T_V_liquid_reactor, @@ -738,3 +738,68 @@ def test_write_liquid_and_run(self): # clean up shutil.rmtree(new_run_dir) + + @pytest.mark.skip(reason="Slow because it has to compile Julia") + def test_write_constantVIdealGasReactor(self): + """ + Test that we can write constant volume ideal gas reactor input file and read it back in with the same values. + """ + + rms_constant_V_input_file = '../../../examples/rmg/rms_constant_V/input.py' + rms_constant_V_output_file = 'temp_rms_constant_V_input.py' + + rmg = RMG() + inp.read_input_file(rms_constant_V_input_file, rmg) + + # read a bunch of values in from input file to check they are the same after writing + T = rmg.reaction_systems[0].T.value_si + P = rmg.reaction_systems[0].P.value_si + tf = rmg.reaction_systems[0].tf + init_conditions = {k: v for k, v in rmg.reaction_systems[0].initial_conditions.items()} + + termination_species = rmg.reaction_systems[0].terminations[0][0].label + termination_conversion = rmg.reaction_systems[0].terminations[0][1] + termination_time = rmg.reaction_systems[0].terminations[1].time + + inp.save_input_file(rms_constant_V_output_file, rmg) + # read it back in and confirm all the values match + rmg1 = RMG() + inp.read_input_file(rms_constant_V_output_file, rmg1) + assert rmg1.reaction_systems[0].T.value_si == T + assert rmg1.reaction_systems[0].P.value_si == P + assert rmg1.reaction_systems[0].tf == tf + + new_init_conditions = {k: v for k, v in rmg1.reaction_systems[0].initial_conditions.items()} + assert pytest.approx(new_init_conditions) == init_conditions + + assert rmg1.reaction_systems[0].terminations[0][0].label == termination_species + assert rmg1.reaction_systems[0].terminations[0][1] == termination_conversion + assert rmg1.reaction_systems[0].terminations[1].time == termination_time + + # clean up + import os + os.remove(rms_constant_V_output_file) + + @pytest.mark.skip(reason="Slow test that runs a full RMG job") + def test_write_constantVIdealGasReactor_and_run(self): + """ + Test that we can write constant volume ideal gas reactor input file and then run RMG without errors + """ + import os + import shutil + + constant_V_input_file = '../../../examples/rmg/rms_constant_V/input.py' + new_run_dir = 'temp_constant_V_run' + os.makedirs(new_run_dir, exist_ok=True) + constant_V_output_file = os.path.join(new_run_dir, 'temp_constant_V_input.py') + + rmg = RMG() + inp.read_input_file(constant_V_input_file, rmg) + inp.save_input_file(constant_V_output_file, rmg) + + # run RMG with the new input file + import subprocess + subprocess.run(['python', '../../../rmg.py', '-t', '00:00:01:30', constant_V_output_file], check=True) + + # clean up + shutil.rmtree(new_run_dir) From 9b807f1429dfd9ee587ce7c659b069072d657f61 Mon Sep 17 00:00:00 2001 From: Sevy Harris Date: Mon, 6 Apr 2026 13:41:24 -0400 Subject: [PATCH 11/13] Add test for writing constant TP reactor input file --- rmgpy/rmg/input.py | 2 +- test/rmgpy/rmg/inputTest.py | 67 ++++++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index 5e5c472b411..f43e07decd2 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -1805,7 +1805,7 @@ def format_initial_mole_fractions(system): f.write(' "{0!s}": {1:g},\n'.format(spcs.label, cov)) f.write(' },\n') f.write(' surfaceVolumeRatio = ({0:g}, "{1!s}"),\n'.format(system.surface_volume_ratio.value, system.surface_volume_ratio.units)) - elif isinstance(system, ConstantVIdealGasReactor): + elif isinstance(system, ConstantVIdealGasReactor) or isinstance(system, ConstantTPIdealGasReactor): f.write('constantVIdealGasReactor(\n') f.write(' temperature = ' + format_temperature(system) + '\n') f.write(' pressure = ' + format_pressure(system) + '\n') diff --git a/test/rmgpy/rmg/inputTest.py b/test/rmgpy/rmg/inputTest.py index 9cc9bcbe51c..e27e5532298 100644 --- a/test/rmgpy/rmg/inputTest.py +++ b/test/rmgpy/rmg/inputTest.py @@ -455,7 +455,7 @@ class TestWriteInputFile: 'simpleReactor': simple_reactor, ✅ 'constantVIdealGasReactor' : constant_V_ideal_gas_reactor, ✅ - 'constantTPIdealGasReactor' : constant_TP_ideal_gas_reactor, + 'constantTPIdealGasReactor' : constant_TP_ideal_gas_reactor, ✅ 'liquidSurfaceReactor' : liquid_cat_reactor, ✅ 'constantTVLiquidReactor': constant_T_V_liquid_reactor, 'liquidReactor': liquid_reactor, ✅ @@ -803,3 +803,68 @@ def test_write_constantVIdealGasReactor_and_run(self): # clean up shutil.rmtree(new_run_dir) + + @pytest.mark.skip(reason="Slow because it has to compile Julia") + def test_write_constantTPdealGasReactor(self): + """ + Test that we can write constant TP ideal gas reactor input file and read it back in with the same values. + """ + + rms_constant_TP_input_file = '../../../examples/rmg/nox_transitory_edge/input.py' + rms_constant_TP_output_file = 'temp_constant_TP_input.py' + + rmg = RMG() + inp.read_input_file(rms_constant_TP_input_file, rmg) + + # read a bunch of values in from input file to check they are the same after writing + T = rmg.reaction_systems[0].T.value_si + P = rmg.reaction_systems[0].P.value_si + tf = rmg.reaction_systems[0].tf + init_conditions = {k: v for k, v in rmg.reaction_systems[0].initial_conditions.items()} + + termination_species = rmg.reaction_systems[0].terminations[0][0].label + termination_conversion = rmg.reaction_systems[0].terminations[0][1] + termination_time = rmg.reaction_systems[0].terminations[1].time + + inp.save_input_file(rms_constant_TP_output_file, rmg) + # read it back in and confirm all the values match + rmg1 = RMG() + inp.read_input_file(rms_constant_TP_output_file, rmg1) + assert rmg1.reaction_systems[0].T.value_si == T + assert rmg1.reaction_systems[0].P.value_si == P + assert rmg1.reaction_systems[0].tf == tf + + new_init_conditions = {k: v for k, v in rmg1.reaction_systems[0].initial_conditions.items()} + assert pytest.approx(new_init_conditions, rel=1e-4) == init_conditions + + assert rmg1.reaction_systems[0].terminations[0][0].label == termination_species + assert rmg1.reaction_systems[0].terminations[0][1] == termination_conversion + assert rmg1.reaction_systems[0].terminations[1].time == termination_time + + # clean up + import os + os.remove(rms_constant_TP_output_file) + + @pytest.mark.skip(reason="Slow test that runs a full RMG job") + def test_write_constantTPIdealGasReactor_and_run(self): + """ + Test that we can write constant TP ideal gas reactor input file and then run RMG without errors + """ + import os + import shutil + + constant_TP_input_file = '../../../examples/rmg/nox_transitory_edge/input.py' + new_run_dir = 'temp_constant_TP_run' + os.makedirs(new_run_dir, exist_ok=True) + constant_TP_output_file = os.path.join(new_run_dir, 'temp_constant_TP_input.py') + + rmg = RMG() + inp.read_input_file(constant_TP_input_file, rmg) + inp.save_input_file(constant_TP_output_file, rmg) + + # run RMG with the new input file + import subprocess + subprocess.run(['python', '../../../rmg.py', '-t', '00:00:01:30', constant_TP_output_file], check=True) + + # clean up + shutil.rmtree(new_run_dir) From c38d947d00bedc788f631e3af883cd70f0abf667 Mon Sep 17 00:00:00 2001 From: Sevy Harris Date: Mon, 6 Apr 2026 13:56:53 -0400 Subject: [PATCH 12/13] add test for constant TV liquid reactor input file --- rmgpy/rmg/input.py | 9 +++++ test/rmgpy/rmg/inputTest.py | 65 +++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index f43e07decd2..8bbb30115b8 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -1815,6 +1815,15 @@ def format_initial_mole_fractions(system): continue f.write(' "{0!s}": {1:g},\n'.format(spcs, conc)) f.write(' },\n') + elif isinstance(system, ConstantTVLiquidReactor): + f.write('constantTVLiquidReactor(\n') + f.write(' temperature = ' + format_temperature(system) + '\n') + f.write(' initialConcentrations={\n') + for spcs, conc in system.initial_conditions.items(): + if spcs in ['T', 'V']: + continue + f.write(' "{0!s}": ({1:g},"{2!s}"),\n'.format(spcs, conc, 'mol/m^3')) + f.write(' },\n') else: f.write('simpleReactor(\n') f.write(' temperature = ' + format_temperature(system) + '\n') diff --git a/test/rmgpy/rmg/inputTest.py b/test/rmgpy/rmg/inputTest.py index e27e5532298..6a9f0cbcb15 100644 --- a/test/rmgpy/rmg/inputTest.py +++ b/test/rmgpy/rmg/inputTest.py @@ -868,3 +868,68 @@ def test_write_constantTPIdealGasReactor_and_run(self): # clean up shutil.rmtree(new_run_dir) + + @pytest.mark.skip(reason="Slow because it has to compile Julia") + def test_write_constantTVLiquidReactor(self): + """ + Test that we can write constant TV liquid reactor input file and read it back in with the same values. + """ + + rms_constant_TV_input_file = '../../../test/regression/RMS_CSTR_liquid_oxidation/input.py' + rms_constant_TV_output_file = 'temp_constant_TV_liquid_input.py' + + rmg = RMG() + inp.read_input_file(rms_constant_TV_input_file, rmg) + + # read a bunch of values in from input file to check they are the same after writing + T = rmg.reaction_systems[0].T.value_si + V = rmg.reaction_systems[0].initial_conditions['V'] + tf = rmg.reaction_systems[0].tf + init_conditions = {k: v for k, v in rmg.reaction_systems[0].initial_conditions.items()} + + termination_species = rmg.reaction_systems[0].terminations[0][0].label + termination_conversion = rmg.reaction_systems[0].terminations[0][1] + termination_time = rmg.reaction_systems[0].terminations[1].time + + inp.save_input_file(rms_constant_TV_output_file, rmg) + # read it back in and confirm all the values match + rmg1 = RMG() + inp.read_input_file(rms_constant_TV_output_file, rmg1) + assert rmg1.reaction_systems[0].T.value_si == T + assert rmg1.reaction_systems[0].initial_conditions['V'] == V + assert rmg1.reaction_systems[0].tf == tf + + new_init_conditions = {k: v for k, v in rmg1.reaction_systems[0].initial_conditions.items()} + assert pytest.approx(new_init_conditions, rel=1e-4) == init_conditions + + assert rmg1.reaction_systems[0].terminations[0][0].label == termination_species + assert rmg1.reaction_systems[0].terminations[0][1] == termination_conversion + assert rmg1.reaction_systems[0].terminations[1].time == termination_time + + # clean up + import os + os.remove(rms_constant_TV_output_file) + + @pytest.mark.skip(reason="Slow test that runs a full RMG job") + def test_write_constantTVLiquidReactor_and_run(self): + """ + Test that we can write constant TV liquid reactor input file and then run RMG without errors + """ + import os + import shutil + + constant_TV_input_file = '../../../test/regression/RMS_CSTR_liquid_oxidation/input.py' + new_run_dir = 'temp_constant_TV_run' + os.makedirs(new_run_dir, exist_ok=True) + constant_TV_output_file = os.path.join(new_run_dir, 'temp_constant_TV_input.py') + + rmg = RMG() + inp.read_input_file(constant_TV_input_file, rmg) + inp.save_input_file(constant_TV_output_file, rmg) + + # run RMG with the new input file + import subprocess + subprocess.run(['python', '../../../rmg.py', '-t', '00:00:01:30', constant_TV_output_file], check=True) + + # clean up + shutil.rmtree(new_run_dir) From fb8b428d5bc877cf30309238831ea2bff0327985 Mon Sep 17 00:00:00 2001 From: Sevy Harris Date: Mon, 6 Apr 2026 14:26:01 -0400 Subject: [PATCH 13/13] add MBSampledReactor write input file test --- rmgpy/rmg/input.py | 9 ++++++++ rmgpy/rmg/main.py | 4 ++-- test/rmgpy/rmg/inputTest.py | 42 +++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index 8bbb30115b8..7941248155a 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -1824,6 +1824,15 @@ def format_initial_mole_fractions(system): continue f.write(' "{0!s}": ({1:g},"{2!s}"),\n'.format(spcs, conc, 'mol/m^3')) f.write(' },\n') + elif isinstance(system, MBSampledReactor): + f.write('mbsampledReactor(\n') + f.write(' temperature = ' + format_temperature(system) + '\n') + f.write(' pressure = ' + format_pressure(system) + '\n') + f.write(' initialMoleFractions={\n') + f.write(format_initial_mole_fractions(system)) + f.write(' },\n') + f.write(' mbsamplingRate = ' + str(system.k_sampling.value_si) + ',\n') + f.write(' constantSpecies = ' + str([x.label for x in system.constantSpeciesList]) + ',\n') else: f.write('simpleReactor(\n') f.write(' temperature = ' + format_temperature(system) + '\n') diff --git a/rmgpy/rmg/main.py b/rmgpy/rmg/main.py index 66a827e8890..cbfa6fe0364 100644 --- a/rmgpy/rmg/main.py +++ b/rmgpy/rmg/main.py @@ -804,8 +804,8 @@ def execute(self, initialize=True, **kwargs): self.done = False # determine min and max values for T and P (don't determine P values for liquid reactors) - self.Tmin = min([x.Trange[0].value_si if x.Trange else x.T.value_si for x in self.reaction_systems]) - self.Tmax = max([x.Trange[1].value_si if x.Trange else x.T.value_si for x in self.reaction_systems]) + self.Tmin = min([x.Trange[0].value_si if hasattr(x, "Trange") and x.Trange else x.T.value_si for x in self.reaction_systems]) + self.Tmax = max([x.Trange[1].value_si if hasattr(x, "Trange") and x.Trange else x.T.value_si for x in self.reaction_systems]) try: self.Pmin = min([x.Prange[0].value_si if hasattr(x, "Prange") and x.Prange else x.P.value_si for x in self.reaction_systems]) self.Pmax = max([x.Prange[1].value_si if hasattr(x, "Prange") and x.Prange else x.P.value_si for x in self.reaction_systems]) diff --git a/test/rmgpy/rmg/inputTest.py b/test/rmgpy/rmg/inputTest.py index 6a9f0cbcb15..378528538d5 100644 --- a/test/rmgpy/rmg/inputTest.py +++ b/test/rmgpy/rmg/inputTest.py @@ -933,3 +933,45 @@ def test_write_constantTVLiquidReactor_and_run(self): # clean up shutil.rmtree(new_run_dir) + + def test_MBSampledReactor_write(self): + """ + Test that we can write MB sampled reactor input file and read it back in with the same values. + Note that the MBSampledReactor is not intended to be used with a standard RMG job, so there's no point in running it as a test + """ + + mbsampled_input_file = '../../../rmgpy/tools/data/sim/mbSampled/input.py' + mbsampled_output_file = 'temp_mbsampled_input.py' + + rmg = RMG() + inp.read_input_file(mbsampled_input_file, rmg) + + # read a bunch of values in from input file to check they are the same after writing + T = rmg.reaction_systems[0].T.value_si + P = rmg.reaction_systems[0].P.value_si + sampling_rate = rmg.reaction_systems[0].k_sampling.value_si + + initialMoleFractions = {k.label: v for k, v in rmg.reaction_systems[0].initial_mole_fractions.items()} + + for term in rmg.reaction_systems[0].termination: + if hasattr(term, 'time'): + termination_time = term.time.value_si + + inp.save_input_file(mbsampled_output_file, rmg) + # read it back in and confirm all the values match + rmg1 = RMG() + inp.read_input_file(mbsampled_output_file, rmg1) + assert rmg1.reaction_systems[0].T.value_si == T + assert rmg1.reaction_systems[0].P.value_si == P + assert rmg1.reaction_systems[0].k_sampling.value_si == sampling_rate + + new_initialMoleFractions = {k.label: v for k, v in rmg1.reaction_systems[0].initial_mole_fractions.items()} + assert pytest.approx(new_initialMoleFractions, rel=1e-4) == initialMoleFractions + + for term in rmg1.reaction_systems[0].termination: + if hasattr(term, 'time'): + assert term.time.value_si == termination_time + + # clean up + import os + os.remove(mbsampled_output_file)