Skip to content
4 changes: 2 additions & 2 deletions examples/rmg/MR_test/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,8 @@
minimumNumberOfGrains=250,
#the conditions for the rate to be output over
#parameter order is: low_value, high_value, units, internal points
temperatures=(300,2200,'K',2),
pressures=(0.01,100.01,'bar',3),
temperatures=(300,2200,'K',10),
pressures=(0.01,100.01,'bar',10),
#The two options for interpolation are 'PDepArrhenius' (no extra arguments) and
#'Chebyshev' which is followed by the number of basis sets in
#Temperature and Pressure. These values must be less than the number of
Expand Down
2 changes: 1 addition & 1 deletion examples/rmg/TEOS/input.py
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might want !electrochem and !surface_development as well as !surface ?
(possibly some other things in the recommended.py file)

Copy link
Contributor Author

@jonwzheng jonwzheng Sep 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this example crashed previously, which is why !surface was added. AFAIK these others aren't problematic as their inclusion does not lead to a crash. but we can add this if you want to be preventative

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
reactionLibraries = [],
seedMechanisms = [],
kineticsDepositories = ['training'],
kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'],
kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O', '!surface'],
kineticsEstimator = 'rate rules',
)

Expand Down
2 changes: 1 addition & 1 deletion examples/rmg/catalysis/methane_steam/input.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Data sources
database(
thermoLibraries=['surfaceThermoNi111', 'surfaceThermoPt111', 'primaryThermoLibrary', 'thermo_DFT_CCSDTF12_BAC'],
reactionLibraries = [('Surface/Deutschmann_Ni', True)], # when Pt is used change the library to Surface/CPOX_Pt/Deutschmann2006
reactionLibraries = [('Surface/Methane/Deutschmann_Ni', True)], # when Pt is used change the library to Surface/CPOX_Pt/Deutschmann2006
seedMechanisms = [],
kineticsDepositories = ['training'],
kineticsFamilies = ['surface','default'],
Expand Down
4 changes: 2 additions & 2 deletions examples/rmg/commented/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,8 @@
minimumNumberOfGrains=250,
# the conditions for the rate to be output over
# parameter order is: low_value, high_value, units, internal points
temperatures=(300, 2200, 'K', 2),
pressures=(0.01, 100, 'bar', 3),
temperatures=(300, 2200, 'K', 10),
pressures=(0.01, 100, 'bar', 10),
# The two options for interpolation are 'PDepArrhenius' (no extra arguments) and
# 'Chebyshev' which is followed by the number of basis sets in
# Temperature and Pressure. These values must be less than the number of
Expand Down
2 changes: 1 addition & 1 deletion examples/rmg/e85/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
reactionLibraries = [],
seedMechanisms = ['GRI-Mech3.0'],
kineticsDepositories = ['training'],
kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'],
kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O', '!surface'],
kineticsEstimator = 'rate rules',
)

Expand Down
4 changes: 4 additions & 0 deletions examples/rmg/gri_mech_rxn_lib/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
maximumEdgeSpecies=100000
)

generatedSpeciesConstraints(
maximumCarbeneRadicals=2,
)

options(
units='si',
generateOutputHTML=False,
Expand Down
2 changes: 1 addition & 1 deletion examples/rmg/methylformate/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
reactionLibraries = [('Methylformate',False),('Glarborg/highP',False)],
seedMechanisms = ['Glarborg/C2'],
kineticsDepositories = ['training'],
kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'],
kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O', '!surface'],
kineticsEstimator = 'rate rules',
)

Expand Down
2 changes: 1 addition & 1 deletion examples/rmg/minimal_sensitivity/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
reactionLibraries = [],
seedMechanisms = [],
kineticsDepositories = ['training'],
kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O'],
kineticsFamilies = ['!Intra_Disproportionation','!Substitution_O', '!surface'],
kineticsEstimator = 'rate rules',
)

Expand Down
3 changes: 3 additions & 0 deletions examples/rmg/nox_transitory_edge/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
reactive=True,
structure=SMILES("CC"),
)

species(
label='O2',
structure=SMILES("[O][O]"),
)

species(
label='N2',
reactive=False,
Expand Down Expand Up @@ -76,6 +78,7 @@

generatedSpeciesConstraints(
allowed=['input species','seed mechanisms','reaction libraries'],
allowSingletO2=True,
#maximumCarbonAtoms=5,
#maximumOxygenAtoms=8,
#maximumNitrogenAtoms=0,
Expand Down
30 changes: 16 additions & 14 deletions rmgpy/constraints.py
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like having more details, but it's a bit annoying that the False is no longer False but is a tuple which would evaluate to True when cast to a boolean. Now you can't use the function so easily inside if blocks etc.

How about returning the reason (as a string) if it's true, and False if it's false?

As long as someone doesn't do something like if fails_species_constraints(species) == True: then that should be safe?

But maybe this is ok.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can refactor it to "passes_species_constraint" which evaluates to either True or the reason for failing (str). Then we need to check for passes_species_constraint == True, and if not, the reason is provided.

It's a safer coding practice to explicitly state the equality check (for example if passes = True: rather than if passes).

Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ def pass_cutting_threshold(species):
def fails_species_constraints(species):
"""
Pass in either a `Species` or `Molecule` object and checks whether it passes
the speciesConstraints set by the user. If not, returns `True` for failing speciesConstraints.
the speciesConstraints set by the user. If the species fails constraints, returns
a string `reason` describing which constraint failed. If all constraints pass, returns `False`.
"""

from rmgpy.rmg.input import get_input
Expand All @@ -86,57 +87,58 @@ def fails_species_constraints(species):
max_carbon_atoms = species_constraints.get('maximumCarbonAtoms', -1)
if max_carbon_atoms != -1:
if struct.get_num_atoms('C') > max_carbon_atoms:
return True
return f"Exceeded maximumCarbonAtoms: {struct.get_num_atoms('C')} > {max_carbon_atoms}"

max_oxygen_atoms = species_constraints.get('maximumOxygenAtoms', -1)
if max_oxygen_atoms != -1:
if struct.get_num_atoms('O') > max_oxygen_atoms:
return True
return f"Exceeded maximumOxygenAtoms: {struct.get_num_atoms('O')} > {max_oxygen_atoms}"

max_nitrogen_atoms = species_constraints.get('maximumNitrogenAtoms', -1)
if max_nitrogen_atoms != -1:
if struct.get_num_atoms('N') > max_nitrogen_atoms:
return True
return f"Exceeded maximumNitrogenAtoms: {struct.get_num_atoms('N')} > {max_nitrogen_atoms}"

max_silicon_atoms = species_constraints.get('maximumSiliconAtoms', -1)
if max_silicon_atoms != -1:
if struct.get_num_atoms('Si') > max_silicon_atoms:
return True
return f"Exceeded maximumSiliconAtoms: {struct.get_num_atoms('Si')} > {max_silicon_atoms}"

max_sulfur_atoms = species_constraints.get('maximumSulfurAtoms', -1)
if max_sulfur_atoms != -1:
if struct.get_num_atoms('S') > max_sulfur_atoms:
return True
return f"Exceeded maximumSulfurAtoms: {struct.get_num_atoms('S')} > {max_sulfur_atoms}"

max_heavy_atoms = species_constraints.get('maximumHeavyAtoms', -1)
if max_heavy_atoms != -1:
if struct.get_num_atoms() - struct.get_num_atoms('H') > max_heavy_atoms:
return True
heavy_atoms = struct.get_num_atoms() - struct.get_num_atoms('H')
if heavy_atoms > max_heavy_atoms:
return f"Exceeded maximumHeavyAtoms: {heavy_atoms} > {max_heavy_atoms}"

max_surface_sites = species_constraints.get('maximumSurfaceSites', -1)
if max_surface_sites != -1:
if struct.get_num_atoms('X') > max_surface_sites:
return True
return f"Exceeded maximumSurfaceSites: {struct.get_num_atoms('X')} > {max_surface_sites}"

max_surface_bond_order = species_constraints.get('maximumSurfaceBondOrder', -1)
if max_surface_bond_order != -1:
for site in struct.get_surface_sites():
if site.get_total_bond_order() > max_surface_bond_order:
return True
return f"Exceeded maximumSurfaceBondOrder at site: {site.get_total_bond_order()} > {max_surface_bond_order}"

max_radicals = species_constraints.get('maximumRadicalElectrons', -1)
if max_radicals != -1:
if struct.get_radical_count() > max_radicals:
return True
return f"Exceeded maximumRadicalElectrons: {struct.get_radical_count()} > {max_radicals}"

max_carbenes = species_constraints.get('maximumSingletCarbenes', 1)
if max_radicals != -1:
if max_carbenes != -1:
if struct.get_singlet_carbene_count() > max_carbenes:
return True
return f"Exceeded maximumSingletCarbenes: {struct.get_singlet_carbene_count()} > {max_carbenes}"

max_carbene_radicals = species_constraints.get('maximumCarbeneRadicals', 0)
if max_carbene_radicals != -1:
if struct.get_singlet_carbene_count() > 0 and struct.get_radical_count() > max_carbene_radicals:
return True
return f"Exceeded maximumCarbeneRadicals: {struct.get_radical_count()} > {max_carbene_radicals}"

return False
7 changes: 6 additions & 1 deletion rmgpy/data/kinetics/family.py
Original file line number Diff line number Diff line change
Expand Up @@ -1655,7 +1655,12 @@ def _generate_product_structures(self, reactant_structures, maps, forward, relab
if self.is_molecule_forbidden(struct):
raise ForbiddenStructureException()
if fails_species_constraints(struct):
raise ForbiddenStructureException()
reason = fails_species_constraints(struct)
raise ForbiddenStructureException(
"Species constraints forbids product species {0}. Please "
"reformulate constraints, or explicitly "
"allow it. Reason: {1}".format(struct, reason)
)

return product_structures

Expand Down
2 changes: 1 addition & 1 deletion rmgpy/kinetics/chebyshev.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ cdef class Chebyshev(PDepKineticsModel):
raise KineticsError("The master equation data needs more temperature and pressure data "
"points than are placed into Chebyshev polynomial. Currently, the "
"data has {0} temperatures and the polynomial is set to have {1}. "
"The data has {2} pressures and the polynomial is set ot have {3}"
"The data has {2} pressures and the polynomial is set to have {3}"
"".format(nT, degreeT, nP, degreeP))
elif nT < 1.25 * degreeT or nP < 1.25 * degreeP:
logging.warning('This Chebyshev fitting has few degrees of freedom and may not be '
Expand Down
7 changes: 4 additions & 3 deletions rmgpy/rmg/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,9 +587,9 @@ def initialize(self, **kwargs):
shutil.copyfile(self.species_map_path, os.path.join(filters_restart, "species_map.yml"))

# Load the seed mechanism to get the core and edge species
self.database.kinetics.load_libraries(restart_dir, libraries=["restart", "restart_edge"])
self.database.kinetics.load_libraries(restart_dir)#, libraries=["restart", "restart_edge"])
self.seed_mechanisms.append("restart")
self.reaction_libraries.append(("restart_edge", False))
# self.reaction_libraries.append(("restart_edge", False))

# Set trimolecular reactant flags of reaction systems
if self.trimolecular:
Expand Down Expand Up @@ -693,10 +693,11 @@ def initialize(self, **kwargs):
if "allowed" in self.species_constraints and "input species" in self.species_constraints["allowed"]:
self.species_constraints["explicitlyAllowedMolecules"].append(spec.molecule[0])
else:
reason = fails_species_constraints(spec)
raise ForbiddenStructureException(
"Species constraints forbids input species {0}. Please "
"reformulate constraints, remove the species, or explicitly "
"allow it.".format(spec.label)
"allow it. Reason: {1}".format(spec.label, reason)
)

# For liquidReactor, checks whether the solvent is listed as one of the initial species.
Expand Down
6 changes: 4 additions & 2 deletions rmgpy/rmg/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1677,10 +1677,11 @@ def add_seed_mechanism_to_core(self, seed_mechanism, react=False, requires_rms=F
if "allowed" in rmg.species_constraints and "seed mechanisms" in rmg.species_constraints["allowed"]:
rmg.species_constraints["explicitlyAllowedMolecules"].extend(spec.molecule)
else:
reason = fails_species_constraints(spec)
raise ForbiddenStructureException(
"Species constraints forbids species {0} from seed mechanism {1}."
" Please reformulate constraints, remove the species, or"
" explicitly allow it.".format(spec.label, seed_mechanism.label)
" explicitly allow it. Reason: {2}".format(spec.label, seed_mechanism.label, reason)
)

for spec in edge_species_to_move+self.new_species_list:
Expand Down Expand Up @@ -1804,10 +1805,11 @@ def add_reaction_library_to_edge(self, reaction_library, requires_rms=False):
if "allowed" in rmg.species_constraints and "reaction libraries" in rmg.species_constraints["allowed"]:
rmg.species_constraints["explicitlyAllowedMolecules"].extend(spec.molecule)
else:
reason = fails_species_constraints(spec)
raise ForbiddenStructureException(
"Species constraints forbids species {0} from reaction library "
"{1}. Please reformulate constraints, remove the species, or "
"explicitly allow it.".format(spec.label, reaction_library.label)
"explicitly allow it. Reason: {2}".format(spec.label, reaction_library.label, reason)
)

for spec in self.new_species_list:
Expand Down
3 changes: 2 additions & 1 deletion test/rmgpy/constraintsTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ def test_constraints_not_loaded(self, mock_logging):
rmgpy.rmg.input.rmg = None

mol = Molecule(smiles="C")

assert not fails_species_constraints(mol)

mock_logging.debug.assert_called_with("Species constraints could not be found.")
Expand Down Expand Up @@ -210,9 +209,11 @@ def test_surface_site_constraint(self):
self.rmg.species_constraints["maximumHeavyAtoms"] = 6

assert not fails_species_constraints(mol_1site)

assert not fails_species_constraints(mol_2site)

assert fails_species_constraints(mol_3site_vdW)

assert fails_species_constraints(mol_3site)

self.rmg.species_constraints["maximumCarbonAtoms"] = max_carbon
Expand Down
Loading