Skip to content

Commit 215eb1b

Browse files
authored
Merge pull request #2491 from rwest/matt
A collection of Matt and David's commits from #2316, with updates and fixes added by Richard That pull request is too large to review, so some parts are being merged first.
2 parents ef83a1c + cfe970f commit 215eb1b

File tree

13 files changed

+127
-13
lines changed

13 files changed

+127
-13
lines changed

arkane/common.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,11 @@ def get_element_mass(input_element, isotope=None):
390390
number = input_element
391391
elif isinstance(input_element, str):
392392
symbol = input_element
393-
number = next(key for key, value in symbol_by_number.items() if value == input_element)
393+
try:
394+
number = number_by_symbol[symbol]
395+
except KeyError:
396+
symbol = input_element.capitalize()
397+
number = number_by_symbol[symbol]
394398

395399
if symbol is None or number is None:
396400
raise ValueError('Could not identify element {0}'.format(input_element))
@@ -434,6 +438,7 @@ def get_element_mass(input_element, isotope=None):
434438
92: 'U', 93: 'Np', 94: 'Pu', 95: 'Am', 96: 'Cm', 97: 'Bk', 98: 'Cf', 99: 'Es', 100: 'Fm', 101: 'Md',
435439
102: 'No', 103: 'Lr', 104: 'Rf', 105: 'Db', 106: 'Sg', 107: 'Bh', 108: 'Hs', 109: 'Mt', 110: 'Ds',
436440
111: 'Rg', 112: 'Cn', 113: 'Nh', 114: 'Fl', 115: 'Mc', 116: 'Lv', 117: 'Ts', 118: 'Og'}
441+
number_by_symbol = {value: key for key, value in symbol_by_number.items()}
437442

438443
# Structure of mass_by_symbol items: list(list(isotope1, mass1, weight1), list(isotope2, mass2, weight2), ...)
439444
mass_by_symbol = {

arkane/commonTest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ def test_get_mass(self):
459459
"""Test that the correct mass/number/isotope is returned from get_element_mass"""
460460
self.assertEqual(get_element_mass(1), (1.00782503224, 1)) # test input by integer
461461
self.assertEqual(get_element_mass('Si'), (27.97692653465, 14)) # test string input and most common isotope
462+
self.assertEqual(get_element_mass('SI'), (27.97692653465, 14)) # test string in all caps
462463
self.assertEqual(get_element_mass('C', 13), (13.00335483507, 6)) # test specific isotope
463464
self.assertEqual(get_element_mass('Bk'), (247.0703073, 97)) # test a two-element array (no isotope data)
464465

documentation/source/users/rmg/input.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,7 @@ all of RMG's reaction families. ::
989989
maximumSulfurAtoms=2,
990990
maximumHeavyAtoms=10,
991991
maximumSurfaceSites=2,
992+
maximumSurfaceBondOrder=4,
992993
maximumRadicalElectrons=2,
993994
maximumSingletCarbenes=1,
994995
maximumCarbeneRadicals=0,

examples/rmg/commented/input.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@
271271
maximumNitrogenAtoms=0,
272272
maximumSiliconAtoms=0,
273273
maximumSulfurAtoms=0,
274+
maximumSurfaceSites=2, # maximum number of surface sites (for heterogeneous catalysis)
275+
maximumSurfaceBondOrder=2, # maximum bond order of each surface sites (for heterogeneous catalysis)
274276
# max number of non-hydrogen atoms
275277
# maximumHeavyAtoms=20,
276278
# maximum radicals on a molecule

rmgpy/constraints.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,21 @@ def fails_species_constraints(species):
108108
if struct.get_num_atoms('S') > max_sulfur_atoms:
109109
return True
110110

111+
max_heavy_atoms = species_constraints.get('maximumHeavyAtoms', -1)
112+
if max_heavy_atoms != -1:
113+
if struct.get_num_atoms() - struct.get_num_atoms('H') > max_heavy_atoms:
114+
return True
115+
111116
max_surface_sites = species_constraints.get('maximumSurfaceSites', -1)
112117
if max_surface_sites != -1:
113118
if struct.get_num_atoms('X') > max_surface_sites:
114119
return True
115120

116-
max_heavy_atoms = species_constraints.get('maximumHeavyAtoms', -1)
117-
if max_heavy_atoms != -1:
118-
if struct.get_num_atoms() - struct.get_num_atoms('H') > max_heavy_atoms:
119-
return True
121+
max_surface_bond_order = species_constraints.get('maximumSurfaceBondOrder', -1)
122+
if max_surface_bond_order != -1:
123+
for site in struct.get_surface_sites():
124+
if site.get_total_bond_order() > max_surface_bond_order:
125+
return True
120126

121127
max_radicals = species_constraints.get('maximumRadicalElectrons', -1)
122128
if max_radicals != -1:

rmgpy/constraintsTest.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def setUpClass(cls):
6262
maximumSiliconAtoms=1,
6363
maximumSulfurAtoms=1,
6464
maximumSurfaceSites=2,
65+
maximumSurfaceBondOrder=3,
6566
maximumHeavyAtoms=3,
6667
maximumRadicalElectrons=2,
6768
maximumSingletCarbenes=1,
@@ -211,6 +212,16 @@ def test_surface_site_constraint(self):
211212
self.rmg.species_constraints['maximumCarbonAtoms'] = max_carbon
212213
self.rmg.species_constraints['maximumHeavyAtoms'] = max_heavy_atoms
213214

215+
def test_surface_bond_order_constraint(self):
216+
"""
217+
Test that we can constrain the max bond order of surface sites.
218+
"""
219+
mol_1site = Molecule().from_adjacency_list("""
220+
1 C u0 p0 c0 {2,Q}
221+
2 X u0 p0 c0 {1,Q}
222+
""")
223+
self.assertTrue(fails_species_constraints(mol_1site))
224+
214225
def test_heavy_constraint(self):
215226
"""
216227
Test that we can constrain the max number of heavy atoms.

rmgpy/data/kinetics/family.py

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3621,9 +3621,12 @@ def make_bm_rules_from_template_rxn_map(self, template_rxn_map, nprocs=1, Tref=1
36213621
inds = inds.tolist()
36223622
revinds = [inds.index(x) for x in np.arange(len(inputs))]
36233623

3624-
pool = mp.Pool(nprocs)
3624+
if nprocs > 1:
3625+
pool = mp.Pool(nprocs)
3626+
kinetics_list = np.array(pool.map(_make_rule, inputs[inds]))
3627+
else:
3628+
kinetics_list = np.array(list(map(_make_rule, inputs[inds])))
36253629

3626-
kinetics_list = np.array(pool.map(_make_rule, inputs[inds]))
36273630
kinetics_list = kinetics_list[revinds] # fix order
36283631

36293632
for i, kinetics in enumerate(kinetics_list):
@@ -4670,3 +4673,61 @@ def _child_make_tree_nodes(family, child_conn, template_rxn_map, obj, T, nprocs,
46704673
extension_iter_max=extension_iter_max, extension_iter_item_cap=extension_iter_item_cap)
46714674

46724675
child_conn.send(list(family.groups.entries.values()))
4676+
4677+
def average_kinetics(kinetics_list):
4678+
"""
4679+
Based on averaging log k.
4680+
Hence we average n, Ea, arithmetically, but we
4681+
average log A (geometric average)
4682+
"""
4683+
logA = 0.0
4684+
n = 0.0
4685+
Ea = 0.0
4686+
count = 0
4687+
for kinetics in kinetics_list:
4688+
count += 1
4689+
logA += np.log10(kinetics.A.value_si)
4690+
n += kinetics.n.value_si
4691+
Ea += kinetics.Ea.value_si
4692+
4693+
logA /= count
4694+
n /= count
4695+
Ea /= count
4696+
4697+
## The above could be replaced with something like:
4698+
# logA, n, Ea = np.mean([[np.log10(k.A.value_si),
4699+
# k.n.value_si,
4700+
# k.Ea.value_si] for k in kinetics_list], axis=1)
4701+
4702+
Aunits = kinetics_list[0].A.units
4703+
if Aunits in {'cm^3/(mol*s)', 'cm^3/(molecule*s)', 'm^3/(molecule*s)'}:
4704+
Aunits = 'm^3/(mol*s)'
4705+
elif Aunits in {'cm^6/(mol^2*s)', 'cm^6/(molecule^2*s)', 'm^6/(molecule^2*s)'}:
4706+
Aunits = 'm^6/(mol^2*s)'
4707+
elif Aunits in {'s^-1', 'm^3/(mol*s)', 'm^6/(mol^2*s)'}:
4708+
# they were already in SI
4709+
pass
4710+
elif Aunits in {'m^2/(mol*s)', 'cm^2/(mol*s)', 'm^2/(molecule*s)', 'cm^2/(molecule*s)'}:
4711+
# surface: bimolecular (Langmuir-Hinshelwood)
4712+
Aunits = 'm^2/(mol*s)'
4713+
elif Aunits in {'m^5/(mol^2*s)', 'cm^5/(mol^2*s)', 'm^5/(molecule^2*s)', 'cm^5/(molecule^2*s)'}:
4714+
# surface: dissociative adsorption
4715+
Aunits = 'm^5/(mol^2*s)'
4716+
elif Aunits == '':
4717+
# surface: sticking coefficient
4718+
pass
4719+
else:
4720+
raise Exception(f'Invalid units {Aunits} for averaging kinetics.')
4721+
4722+
if type(kinetics) not in [Arrhenius,]:
4723+
raise Exception(f'Invalid kinetics type {type(kinetics)!r} for {self!r}.')
4724+
4725+
if False:
4726+
pass
4727+
else:
4728+
averaged_kinetics = Arrhenius(
4729+
A=(10 ** logA, Aunits),
4730+
n=n,
4731+
Ea=(Ea * 0.001, "kJ/mol"),
4732+
)
4733+
return averaged_kinetics

rmgpy/data/kinetics/familyTest.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,15 @@
3737
import numpy as np
3838

3939
from rmgpy import settings
40+
import rmgpy.data.kinetics.family
4041
from rmgpy.data.kinetics.database import KineticsDatabase
4142
from rmgpy.data.kinetics.family import TemplateReaction
4243
from rmgpy.data.rmg import RMGDatabase
4344
from rmgpy.data.thermo import ThermoDatabase
4445
from rmgpy.molecule import Molecule
4546
from rmgpy.species import Species
47+
from rmgpy.kinetics import Arrhenius
48+
4649

4750

4851
###################################################
@@ -1090,6 +1093,23 @@ def test_retaining_atom_labels_in_template_reaction(self):
10901093
self.assertEqual([(label, str(atom)) for label, atom in
10911094
reaction_list_2[0].labeled_atoms['products'].items()],
10921095
[('*1', 'C'), ('*2', 'C.'), ('*3', 'H')])
1096+
1097+
def test_average_kinetics(self):
1098+
"""
1099+
Test that the average kinetics are calculated correctly
1100+
"""
1101+
k1 = Arrhenius(A=(1e+13, 'cm^3/(mol*s)'), n=0, Ea=(0, 'kJ/mol'), T0=(1, 'K'))
1102+
k2 = Arrhenius(A=(4e+13, 'cm^3/(mol*s)'), n=1, Ea=(10, 'kJ/mol'), T0=(1, 'K'))
1103+
kav = rmgpy.data.kinetics.family.average_kinetics([k1, k2])
1104+
self.assertAlmostEqual(kav.A.value_si, 2.0e7, 2) # m3/mol/s
1105+
self.assertAlmostEqual(kav.n.value_si, 0.5, 6)
1106+
self.assertAlmostEqual(kav.Ea.value_si, 5.0e3, 2)
1107+
self.assertAlmostEqual(kav.T0.value_si, 1.0, 6)
1108+
self.assertEqual(kav.A.units, 'm^3/(mol*s)')
1109+
self.assertAlmostEqual(np.log(kav.get_rate_coefficient(300)),
1110+
np.average([np.log(k1.get_rate_coefficient(300)),
1111+
np.log(k2.get_rate_coefficient(300))]), 6)
1112+
10931113

10941114

10951115
################################################################################

rmgpy/data/solvation.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,6 +1198,9 @@ def get_solute_data_from_groups(self, species):
11981198
by gas-phase thermo estimate.
11991199
"""
12001200
molecule = species.molecule[0]
1201+
if molecule.contains_surface_site():
1202+
molecule = molecule.get_desorbed_molecules()[0]
1203+
molecule.saturate_unfilled_valence()
12011204
molecule.clear_labeled_atoms()
12021205
molecule.update_atomtypes()
12031206
solute_data = self.estimate_solute_via_group_additivity(molecule)

rmgpy/rmg/input.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,6 +1374,7 @@ def generated_species_constraints(**kwargs):
13741374
'maximumSiliconAtoms',
13751375
'maximumSulfurAtoms',
13761376
'maximumSurfaceSites',
1377+
'maximumSurfaceBondOrder',
13771378
'maximumHeavyAtoms',
13781379
'maximumRadicalElectrons',
13791380
'maximumSingletCarbenes',

0 commit comments

Comments
 (0)