diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 5183002095..4e2cc7a77d 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -175,7 +175,7 @@ jobs:
timeout-minutes: 120 # this usually takes 20-45 minutes (or hangs for 6+ hours).
run: |
python -c "import julia; julia.install(); import diffeqpy; diffeqpy.install()"
- julia -e 'using Pkg; Pkg.add(PackageSpec(name="ReactionMechanismSimulator",rev="main")); using ReactionMechanismSimulator'
+ julia -e 'using Pkg; Pkg.add(PackageSpec(name="ReactionMechanismSimulator",rev="for_rmg")); using ReactionMechanismSimulator'
- name: Install Q2DTor
run: echo "" | make q2dtor
@@ -296,7 +296,7 @@ jobs:
export FAILED=Yes
fi
echo "" # blank line so next block is interpreted as markdown
- cat "$regr_test-core.log"
+ cat "$regr_test-core.log" || (echo "Dumping the whole log failed, please download it from GitHub actions. Here are the first 100 lines:" && head -n100 "$regr_test-core.log")
echo ""
echo ""
if python-jl scripts/checkModels.py \
@@ -313,7 +313,7 @@ jobs:
export FAILED=Yes
fi
echo "" # blank line so next block is interpreted as markdown
- cat "$regr_test-edge.log"
+ cat "$regr_test-edge.log" || (echo "Dumping the whole log failed, please download it from GitHub actions. Here are the first 100 lines:" && head -n100 "$regr_test-core.log")
echo " "
# Check for Regression between Reference and Dynamic (skip superminimal)
@@ -405,3 +405,5 @@ jobs:
with:
push: true
tags: reactionmechanismgenerator/rmg:latest
+ build-args: |
+ RMS_Branch=for_rmg
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 43ab6b0ab1..ef3509d78f 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -64,7 +64,7 @@ jobs:
timeout-minutes: 120 # this usually takes 20-45 minutes (or hangs for 6+ hours).
run: |
python -c "import julia; julia.install(); import diffeqpy; diffeqpy.install()"
- julia -e 'using Pkg; Pkg.add(PackageSpec(name="ReactionMechanismSimulator",rev="main")); using ReactionMechanismSimulator'
+ julia -e 'using Pkg; Pkg.add(PackageSpec(name="ReactionMechanismSimulator",rev="for_rmg")); using ReactionMechanismSimulator'
- name: Checkout gh-pages Branch
uses: actions/checkout@v2
diff --git a/arkane/encorr/ae.py b/arkane/encorr/ae.py
index b06bd025c9..ecef89ae37 100644
--- a/arkane/encorr/ae.py
+++ b/arkane/encorr/ae.py
@@ -73,7 +73,10 @@
'Methane',
'Methyl',
'Ammonia',
- 'Chloromethane'
+ 'Chloromethane',
+ # Lithium species shall be uncommented after we reconcile the difference in AECs and BACs
+ # 'Lithium Hydride',
+ # 'Lithium Fluoride'
]
diff --git a/arkane/encorr/bac.py b/arkane/encorr/bac.py
index 564cadeb15..c511a9f47b 100644
--- a/arkane/encorr/bac.py
+++ b/arkane/encorr/bac.py
@@ -241,7 +241,7 @@ class BAC:
ref_databases = {}
atom_spins = {
'H': 0.5, 'C': 1.0, 'N': 1.5, 'O': 1.0, 'F': 0.5,
- 'Si': 1.0, 'P': 1.5, 'S': 1.0, 'Cl': 0.5, 'Br': 0.5, 'I': 0.5
+ 'Si': 1.0, 'P': 1.5, 'S': 1.0, 'Cl': 0.5, 'Br': 0.5, 'I': 0.5, 'Li': 0.5,
}
exp_coeff = 3.0 # Melius-type parameter (Angstrom^-1)
diff --git a/documentation/source/users/rmg/installation/anacondaDeveloper.rst b/documentation/source/users/rmg/installation/anacondaDeveloper.rst
index 7dbcfb6677..e1aed5f7db 100644
--- a/documentation/source/users/rmg/installation/anacondaDeveloper.rst
+++ b/documentation/source/users/rmg/installation/anacondaDeveloper.rst
@@ -117,7 +117,7 @@ Installation by Source Using Anaconda Environment for Unix-based Systems: Linux
#. Install and Link Julia dependencies: ::
- julia -e 'using Pkg; Pkg.add("PyCall");Pkg.build("PyCall");Pkg.add(PackageSpec(name="ReactionMechanismSimulator",rev="main")); using ReactionMechanismSimulator;'
+ julia -e 'using Pkg; Pkg.add("PyCall");Pkg.build("PyCall");Pkg.add(PackageSpec(name="ReactionMechanismSimulator",rev="for_rmg")); using ReactionMechanismSimulator;'
python -c "import julia; julia.install(); import diffeqpy; diffeqpy.install()"
diff --git a/examples/rmg/CO2RR/input.py b/examples/rmg/CO2RR/input.py
new file mode 100644
index 0000000000..182f878588
--- /dev/null
+++ b/examples/rmg/CO2RR/input.py
@@ -0,0 +1,317 @@
+# Data sources
+database(
+ thermoLibraries=['surfaceThermoPt111', 'primaryThermoLibrary', 'thermo_DFT_CCSDTF12_BAC','DFT_QCI_thermo', 'electrocatThermo',
+ # 'CO2RR_Adsorbates_Ag111'
+ ],
+ reactionLibraries = [('Surface/CPOX_Pt/Deutschmann2006_adjusted', False)],
+ seedMechanisms = [],
+ kineticsDepositories = ['training'],
+ kineticsFamilies = ['electrochem',
+ # 'surface',
+ 'Surface_Abstraction',
+ 'Surface_Abstraction_vdW',
+ 'Surface_Abstraction_Single_vdW',
+ 'Surface_Abstraction_Beta_double_vdW',
+ 'Surface_Adsorption_Dissociative',
+ 'Surface_Adsorption_Dissociative_Double',
+ 'Surface_Adsorption_vdW',
+ 'Surface_Dissociation',
+ 'Surface_Dissociation_Double_vdW',
+ 'Surface_Dissociation_vdW',
+ 'Surface_EleyRideal_Addition_Multiple_Bond',
+ 'Surface_Migration',
+ ],
+ kineticsEstimator = 'rate rules',
+
+)
+
+catalystProperties(
+ metal = 'Ag111'
+)
+
+# List of species
+species(
+ label='CO2',
+ reactive=True,
+ structure=adjacencyList(
+ """
+1 O u0 p2 c0 {2,D}
+2 C u0 p0 c0 {1,D} {3,D}
+3 O u0 p2 c0 {2,D}
+"""),
+)
+
+
+species(
+ label='proton',
+ reactive=True,
+ structure=adjacencyList(
+ """
+1 H u0 p0 c+1
+"""),
+)
+
+species(
+ label='vacantX',
+ reactive=True,
+ structure=adjacencyList("1 X u0"),
+)
+
+species(
+ label='H',
+ reactive=True,
+ structure=adjacencyList(
+ """
+1 H u1 p0 c0
+"""),
+)
+
+species(
+ label='CO2X',
+ reactive=True,
+ structure=adjacencyList("""
+1 O u0 p2 c0 {3,D}
+2 O u0 p2 c0 {3,D}
+3 C u0 p0 c0 {1,D} {2,D}
+4 X u0 p0 c0
+"""),
+)
+
+species(
+ label='CHO2X',
+ reactive=True,
+ structure=adjacencyList("""
+1 O u0 p2 c0 {3,S} {5,S}
+2 O u0 p2 c0 {3,D}
+3 C u0 p0 c0 {1,S} {2,D} {4,S}
+4 H u0 p0 c0 {3,S}
+5 X u0 p0 c0 {1,S}
+"""),
+)
+
+species(
+ label='CO2HX',
+ reactive=True,
+ structure=adjacencyList("""
+1 O u0 p2 c0 {2,S} {4,S}
+2 C u0 p0 c0 {1,S} {3,D} {5,S}
+3 O u0 p2 c0 {2,D}
+4 H u0 p0 c0 {1,S}
+5 X u0 p0 c0 {2,S}
+
+"""),
+)
+
+species(
+ label='OCX',
+ reactive=True,
+ structure=adjacencyList("""
+1 O u0 p2 c0 {2,D}
+2 C u0 p0 c0 {1,D} {3,D}
+3 X u0 p0 c0 {2,D}
+"""),
+)
+
+species(
+ label='OX',
+ reactive=True,
+ structure=adjacencyList("""
+1 O u0 p2 c0 {2,D}
+2 X u0 p0 c0 {1,D}
+"""),
+)
+
+species(
+ label='CH2O2X',
+ reactive=True,
+ structure=adjacencyList("""
+1 O u0 p2 c0 {3,S} {5,S}
+2 O u0 p2 c0 {3,D}
+3 C u0 p0 c0 {1,S} {2,D} {4,S}
+4 H u0 p0 c0 {3,S}
+5 H u0 p0 c0 {1,S}
+6 X u0 p0 c0
+"""),
+)
+
+species(
+ label='CHOX',
+ reactive=True,
+ structure=adjacencyList("""
+1 O u0 p2 c0 {2,D}
+2 C u0 p0 c0 {1,D} {3,S} {4,S}
+3 H u0 p0 c0 {2,S}
+4 X u0 p0 c0 {2,S}
+"""),
+)
+
+species(
+ label='CH2OX',
+ reactive=True,
+ structure=adjacencyList("""
+1 O u0 p2 c0 {2,D}
+2 C u0 p0 c0 {1,D} {3,S} {4,S}
+3 H u0 p0 c0 {2,S}
+4 H u0 p0 c0 {2,S}
+5 X u0 p0 c0
+"""),
+)
+
+
+forbidden(
+ label='CO2-bidentate',
+ structure=adjacencyList(
+ """
+ 1 O u0 p2 c0 {2,D}
+ 2 C u0 p0 c0 {1,D} {3,S} {4,S}
+ 3 X u0 p0 c0 {2,S}
+ 4 O u0 p2 c0 {2,S} {5,S}
+ 5 X u0 p0 c0 {4,S}
+ """
+ )
+)
+
+liquidSurfaceReactor(
+ temperature=(300,'K'),
+ liqPotential=(0,'V'),
+ surfPotential=(-2.0,'V'),
+ initialConcentrations={
+ "CO2": (1e-3,'mol/cm^3'),
+ "proton": (1e-4,'mol/m^3'),
+ },
+ initialSurfaceCoverages={
+ # "HX": 0.5,
+ # # "CXO2": 0.0,
+ "CHO2X": 0.1,
+ "CO2HX": 0.1,
+ "vacantX": 0.1,
+ "CO2X": 0.4,
+ 'OX': 0.1,
+ 'OCX': 0.1,
+ 'CH2O2X': 0.05,
+ 'CHOX': 0.04,
+ 'CH2OX': 0.01
+ },
+ surfaceVolumeRatio=(1.0e5, 'm^-1'),
+ terminationTime=(1.0e3,'sec'),
+ # terminationConversion={'CO2': 0.90},
+ # constantSpecies=["proton"],
+ )
+
+liquidSurfaceReactor(
+ temperature=(300,'K'),
+ liqPotential=(0,'V'),
+ surfPotential=(-1.5,'V'),
+ initialConcentrations={
+ "CO2": (1e-3,'mol/cm^3'),
+ "proton": (1e-4,'mol/m^3'),
+ },
+ initialSurfaceCoverages={
+ # "HX": 0.5,
+ # # "CXO2": 0.0,
+ "CHO2X": 0.1,
+ "CO2HX": 0.1,
+ "vacantX": 0.1,
+ "CO2X": 0.4,
+ 'OX': 0.1,
+ 'OCX': 0.1,
+ 'CH2O2X': 0.05,
+ 'CHOX': 0.04,
+ 'CH2OX': 0.01
+ },
+ surfaceVolumeRatio=(1.0e5, 'm^-1'),
+ terminationTime=(1.0e3,'sec'),
+ # terminationConversion={'CO2': 0.90},
+ # constantSpecies=["proton"],
+ )
+
+liquidSurfaceReactor(
+ temperature=(300,'K'),
+ liqPotential=(0,'V'),
+ surfPotential=(-1.0,'V'),
+ initialConcentrations={
+ "CO2": (1e-3,'mol/cm^3'),
+ "proton": (1e-4,'mol/m^3'),
+ },
+ initialSurfaceCoverages={
+ # "HX": 0.5,
+ # # "CXO2": 0.0,
+ "CHO2X": 0.1,
+ "CO2HX": 0.1,
+ "vacantX": 0.1,
+ "CO2X": 0.4,
+ 'OX': 0.1,
+ 'OCX': 0.1,
+ 'CH2O2X': 0.05,
+ 'CHOX': 0.04,
+ 'CH2OX': 0.01
+ },
+ surfaceVolumeRatio=(1.0e5, 'm^-1'),
+ terminationTime=(1.0e3,'sec'),
+ # terminationConversion={'CO2': 0.90},
+ # constantSpecies=["proton"],
+ )
+
+# liquidSurfaceReactor(
+# temperature=(300,'K'),
+# liqPotential=(0,'V'),
+# surfPotential=(-0.5,'V'),
+# initialConcentrations={
+# "CO2": (1e-3,'mol/cm^3'),
+# "proton": (1e-4,'mol/m^3'),
+# },
+# initialSurfaceCoverages={
+# # "HX": 0.5,
+# # # "CXO2": 0.0,
+# "CHO2X": 0.1,
+# "CO2HX": 0.1,
+# "vacantX": 0.1,
+# "CO2X": 0.4,
+# 'OX': 0.1,
+# 'OCX': 0.1,
+# 'CH2O2X': 0.05,
+# 'CHOX': 0.04,
+# 'CH2OX': 0.01
+# },
+# surfaceVolumeRatio=(1.0e5, 'm^-1'),
+# terminationTime=(1.0e3,'sec'),
+# # terminationConversion={'CO2': 0.90},
+# # constantSpecies=["proton"],
+# )
+
+solvation(
+ solvent='water'
+)
+
+simulator(
+ atol=1e-16,
+ rtol=1e-8,
+)
+
+model(
+ toleranceKeepInEdge=1E-16,
+ toleranceMoveToCore=1E-3,
+ toleranceRadMoveToCore=1E-6,
+ toleranceInterruptSimulation=1E6,
+ filterReactions=False,
+ maximumEdgeSpecies=5000,
+ toleranceBranchReactionToCore=1E-6,
+ branchingIndex=0.5,
+ branchingRatioMax=1.0,
+)
+
+options(
+ units='si',
+ generateOutputHTML=True,
+ generatePlots=True,
+ saveEdgeSpecies=True,
+ saveSimulationProfiles=False,
+)
+
+generatedSpeciesConstraints(
+ allowed=['input species','reaction libraries'],
+ maximumSurfaceSites=2,
+ maximumCarbonAtoms=3,
+ maximumOxygenAtoms=2,
+ maximumRadicalElectrons=1,
+)
diff --git a/examples/rmg/SEI_pure_ACN/input.py b/examples/rmg/SEI_pure_ACN/input.py
new file mode 100644
index 0000000000..2a4136c59c
--- /dev/null
+++ b/examples/rmg/SEI_pure_ACN/input.py
@@ -0,0 +1,181 @@
+# Data sources
+database(
+ thermoLibraries=['LithiumSurface','electrocatLiThermo','primaryThermoLibrary', 'LithiumPrimaryThermo', 'LithiumAdditionalThermo', 'thermo_DFT_CCSDTF12_BAC','DFT_QCI_thermo'], # 'surfaceThermoPt' is the default. Thermo data is derived using bindingEnergies for other metals
+ reactionLibraries = ['LithiumPrimaryKinetics',"LithiumSurface"], # when Ni is used change the library to Surface/Deutschmann_Ni
+ seedMechanisms = [],
+ kineticsDepositories = ['training'],
+ kineticsFamilies = ['surface',
+ '1+2_Cycloaddition',
+ 'Surface_Carbonate_Deposition',
+ 'Surface_Carbonate_F_CO_Decomposition',
+ 'Surface_Carbonate_2F_Decomposition',
+ 'Surface_Carbonate_CO_Decomposition',
+ '1,2_Elimination_LiR',
+ '1,2_Intra_Elimination_LiR',
+ 'Li_Addition_MultipleBond',
+ 'Li_NO_Substitution',
+ 'Li_NO_Ring_Opening',
+ 'Li_Abstraction',
+ 'R_Addition_MultipleBond_Disprop',
+ 'Cation_R_Recombination',
+ 'Cation_Addition_MultipleBond',
+ '1,2-Birad_to_alkene',
+ '1,2_Insertion_CO',
+ '1,2_Insertion_carbene',
+ '1,2_shiftS',
+ '1,3_Insertion_CO2',
+ '1,3_Insertion_ROR',
+ '1,3_Insertion_RSR',
+ '1,4_Cyclic_birad_scission',
+ '1,4_Linear_birad_scission',
+ '2+2_cycloaddition',
+ 'Birad_recombination',
+ 'CO_Disproportionation',
+ 'Birad_R_Recombination',
+ 'Cyclic_Ether_Formation',
+ 'Cyclic_Thioether_Formation',
+ 'Diels_alder_addition',
+ 'Diels_alder_addition_Aromatic',
+ #'Disproportionation',
+ 'HO2_Elimination_from_PeroxyRadical',
+ 'H_Abstraction',
+ 'Intra_Retro_Diels_alder_bicyclic',
+ 'Intra_Disproportionation',
+ 'Intra_R_Add_Endocyclic',
+ 'Intra_R_Add_Exocyclic',
+ 'R_Addition_COm',
+ 'R_Addition_MultipleBond',
+ 'R_Recombination',
+ 'intra_H_migration',
+ 'intra_NO2_ONO_conversion',
+ 'intra_OH_migration',
+ 'intra_substitutionCS_cyclization',
+ 'intra_substitutionCS_isomerization',
+ 'intra_substitutionS_cyclization',
+ 'intra_substitutionS_isomerization',
+ #'ketoenol',
+ 'Singlet_Carbene_Intra_Disproportionation',
+ 'Singlet_Val6_to_triplet',
+ 'Intra_5_membered_conjugated_C=C_C=C_addition',
+ 'Intra_Diels_alder_monocyclic',
+ 'Concerted_Intra_Diels_alder_monocyclic_1,2_shiftH',
+ 'Intra_2+2_cycloaddition_Cd',
+ 'Intra_ene_reaction',
+ 'Cyclopentadiene_scission',
+ '6_membered_central_C-C_shift',
+ 'Intra_R_Add_Exo_scission',
+ '1,2_shiftC',
+ '1,2_NH3_elimination',
+ '1,3_NH3_elimination',
+ 'Retroene',],
+ kineticsEstimator = 'rate rules',
+ adsorptionGroups='adsorptionLi',
+)
+
+catalystProperties(
+ metal = 'Li110',
+)
+
+# List of species
+species(
+ label="Lip",
+ reactive=True,
+ structure=SMILES("[Li+]"),
+)
+
+species(
+ label='ACN',
+ reactive=True,
+ structure=SMILES("CC#N"),
+)
+
+species(
+ label='vacantX',
+ reactive=True,
+ structure=adjacencyList("1 X u0"),
+)
+
+liquidSurfaceReactor(
+ temperature=(298.15,'K'),
+ distance=(10.0e-10,"m"),
+ viscosity=(5e7,"Pa*s"),
+ liqPotential=(0.3,'V'),
+ surfPotential=(0.0,'V'),
+ initialConcentrations={
+ "ACN": (0.019146,'mol/cm^3'),
+ "Lip": (15.0,'mol/m^3'),
+ },
+ initialSurfaceCoverages={
+ "vacantX": 1.0,
+ },
+ surfaceVolumeRatio=(1.0e-5, 'm^-1'),
+ terminationTime=(1e3,'sec'),
+ constantSpecies=["ACN","Lip"],
+)
+
+liquidSurfaceReactor(
+ temperature=(298.15,'K'),
+ distance=(0.0,"m"),
+ liqPotential=(0.0,'V'),
+ surfPotential=(0.0,'V'),
+ initialConcentrations={
+ "ACN": (0.019146,'mol/cm^3'),
+ "Lip": (15.0,'mol/m^3'),
+ },
+ initialSurfaceCoverages={
+ "vacantX": 1.0,
+ },
+ surfaceVolumeRatio=(1.0e5, 'm^-1'),
+ terminationTime=(1e3,'sec'),
+ constantSpecies=["ACN","Lip"],
+)
+
+solvation(
+ solvent='acetonitrile'
+)
+
+simulator(
+ atol=1e-16,
+ rtol=1e-6,
+)
+
+model(
+ toleranceKeepInEdge=1E-20,
+ toleranceMoveToCore=0.1,
+ toleranceRadMoveToCore=0.1,
+ toleranceInterruptSimulation=1e10,
+ maximumEdgeSpecies=100000,
+ filterReactions=False,
+ maxNumObjsPerIter=1,
+ terminateAtMaxObjects=True,
+ toleranceBranchReactionToCore=0.001,
+ branchingIndex=0.5,
+ branchingRatioMax=1.0,
+)
+
+options(
+ units='si',
+ saveEdgeSpecies=False,
+)
+
+forbidden(
+ label='vacancies',
+ structure=adjacencyListGroup("""
+1 Xv u0 p0 c0
+"""),
+)
+
+forbidden(
+ label='Li2',
+ structure=adjacencyList("""
+1 Li u0 p0 c0 {2,S}
+2 Li u0 p0 c0 {1,S}"""),
+)
+
+generatedSpeciesConstraints(
+ allowed=['input species','reaction libraries'],
+ maximumSurfaceSites=1,
+ maximumCarbonAtoms=7,
+ maximumOxygenAtoms=4,
+ maximumRadicalElectrons=1,
+)
diff --git a/examples/rmg/SEI_pure_EC/input.py b/examples/rmg/SEI_pure_EC/input.py
new file mode 100644
index 0000000000..2912c7f2ba
--- /dev/null
+++ b/examples/rmg/SEI_pure_EC/input.py
@@ -0,0 +1,291 @@
+# Data sources
+database(
+ thermoLibraries=['electrocatLiThermo','primaryThermoLibrary', 'LithiumPrimaryThermo', 'LithiumAdditionalThermo', 'thermo_DFT_CCSDTF12_BAC','DFT_QCI_thermo'], # 'surfaceThermoPt' is the default. Thermo data is derived using bindingEnergies for other metals
+ reactionLibraries = ['LithiumPrimaryKinetics','LithiumAnalogyKinetics'], # when Ni is used change the library to Surface/Deutschmann_Ni
+ seedMechanisms = [],
+ kineticsDepositories = ['training'],
+ kineticsFamilies = ['surface','electrochem',
+ '1+2_Cycloaddition',
+ '1,2-Birad_to_alkene',
+ '1,2_Insertion_CO',
+ '1,2_Insertion_carbene',
+ '1,2_shiftS',
+ '1,3_Insertion_CO2',
+ '1,3_Insertion_ROR',
+ '1,3_Insertion_RSR',
+ '1,4_Cyclic_birad_scission',
+ '1,4_Linear_birad_scission',
+ '2+2_cycloaddition',
+ 'Birad_recombination',
+ 'CO_Disproportionation',
+ 'Birad_R_Recombination',
+ 'Cyclic_Ether_Formation',
+ 'Cyclic_Thioether_Formation',
+ 'Diels_alder_addition',
+ 'Diels_alder_addition_Aromatic',
+ #'Disproportionation',
+ 'HO2_Elimination_from_PeroxyRadical',
+ 'H_Abstraction',
+ 'Intra_Retro_Diels_alder_bicyclic',
+ 'Intra_Disproportionation',
+ 'Intra_R_Add_Endocyclic',
+ 'Intra_R_Add_Exocyclic',
+ 'R_Addition_COm',
+ 'R_Addition_MultipleBond',
+ 'R_Recombination',
+ 'intra_H_migration',
+ 'intra_NO2_ONO_conversion',
+ 'intra_OH_migration',
+ 'intra_substitutionCS_cyclization',
+ 'intra_substitutionCS_isomerization',
+ 'intra_substitutionS_cyclization',
+ 'intra_substitutionS_isomerization',
+ #'ketoenol',
+ 'Singlet_Carbene_Intra_Disproportionation',
+ 'Singlet_Val6_to_triplet',
+ 'Intra_5_membered_conjugated_C=C_C=C_addition',
+ 'Intra_Diels_alder_monocyclic',
+ 'Concerted_Intra_Diels_alder_monocyclic_1,2_shiftH',
+ 'Intra_2+2_cycloaddition_Cd',
+ 'Intra_ene_reaction',
+ 'Cyclopentadiene_scission',
+ '6_membered_central_C-C_shift',
+ 'Intra_R_Add_Exo_scission',
+ '1,2_shiftC',
+ '1,2_NH3_elimination',
+ '1,3_NH3_elimination',
+ 'Retroene',],
+ kineticsEstimator = 'rate rules',
+ adsorptionGroups='adsorptionLi',
+)
+
+catalystProperties(
+ metal = 'Li110'
+)
+
+# List of species
+species(
+ label="Lip",
+ reactive=True,
+ structure=SMILES("[Li+]"),
+)
+
+species(
+ label='ethylene-carbonate',
+ reactive=True,
+ structure=SMILES("C1COC(=O)O1"),
+)
+
+species(
+ label='vacantX',
+ reactive=True,
+ structure=adjacencyList("1 X u0"),
+)
+
+species(
+ label="Li",
+ reactive=True,
+ structure=SMILES("[Li]"),
+)
+
+species(
+ label='[Li]O[C]1OCCO1',
+ reactive=True,
+ structure=SMILES("[Li]O[C]1OCCO1"),
+)
+
+species(
+ label='[Li]OC(=O)OC[CH2]',
+ reactive=True,
+ structure=SMILES("[Li]OC(=O)OC[CH2]"),
+)
+
+#species(
+# label='[Li]OC(=O)O[Li]',
+# reactive=True,
+# structure=SMILES("[Li]OC(=O)O[Li]"),
+# )
+
+#species(
+# label='[Li]OC(=O)OCCOC(=O)OC[CH2]',
+# reactive=True,
+# structure=SMILES("[Li]OC(=O)OCCOC(=O)OC[CH2]"),
+# )
+
+#species(
+# label='[Li]OC(=O)OCCOC(=O)O[Li]',
+# reactive=True,
+# structure=SMILES("[Li]OC(=O)OCCOC(=O)O[Li]"),
+# )
+
+#species(
+# label='[Li]OC(=O)OCCCCOC(=O)O[Li]',
+# reactive=True,
+# structure=SMILES("[Li]OC(=O)OCCCCOC(=O)O[Li]"),)
+
+#species(
+# label='[Li]OCCOC(=O)CCOC(=O)O[Li]',
+# reactive=True,
+# structure=SMILES("[Li]OCCOC(=O)CCOC(=O)O[Li]"),
+# )
+
+#species(
+# label='[Li]OCCOC(=O)OC(=O)O[Li]',
+# reactive=True,
+# structure=SMILES("[Li]OCCOC(=O)OC(=O)O[Li]"),
+#)
+
+#species(
+# label='C2H4',
+# reactive=True,
+# structure=SMILES("C=C"),
+#)
+
+#species(
+# label='O=[C]OCCO[Li]',
+# reactive=True,
+# structure=SMILES("O=[C]OCCO[Li]"),
+#)
+
+#species(
+# label='CO2',
+# reactive=True,
+# structure=SMILES("O=C=O"),
+#)
+
+#species(
+# label='[Li]OC[CH2]',
+# reactive=True,
+# structure=SMILES("[Li]OC[CH2]"),
+#)
+
+#species(
+# label='O1CCO[C]1OC2(O[Li])OCCO2',
+# reactive=True,
+# structure=SMILES("O1CCO[C]1OC2(O[Li])OCCO2"),
+#)
+
+#species(
+# label='O1CCOC1(O[Li])OC(=O)OC[CH2]',
+# reactive=True,
+# structure=SMILES("O1CCOC1(O[Li])OC(=O)OC[CH2]"),
+#)
+
+#species(
+# label='O1CCOC1(O[Li])OC(=O)OCCOC(=O)O[Li]',
+# reactive=True,
+# structure=SMILES("O1CCOC1(O[Li])OC(=O)OCCOC(=O)O[Li]"),
+#)
+
+species(
+ label='CO3X2',
+ reactive=True,
+ structure=adjacencyList("""1 O u0 p2 {2,D}
+2 C u0 p0 {1,D} {3,S} {4,S}
+3 O u0 p2 {2,S} {5,S}
+4 O u0 p2 {2,S} {6,S}
+5 X u0 p0 c0 {3,S}
+6 X u0 p0 c0 {4,S}
+"""),
+)
+
+
+#species(
+# label="CO",
+# reactive=True,
+# structure=SMILES("[C-]#[O+]"),
+#)
+
+#species(
+# label='[Li]OC(=O)OCCX',
+# reactive=True,
+# structure=adjacencyList("""1 O u0 p2 c0 {2,S} {7,S}
+# 2 C u0 p0 c0 {1,S} {3,D} {4,S}
+# 3 O u0 p2 c0 {2,D}
+# 4 O u0 p2 c0 {2,S} {5,S}
+# 5 C u0 p0 c0 {4,S} {6,S} {8,S} {9,S}
+# 6 C u0 p0 c0 {5,S} {10,S} {11,S} {12,S}
+# 7 Li u0 p0 c0 {1,S}
+# 8 H u0 p0 c0 {5,S}
+# 9 H u0 p0 c0 {5,S}
+# 10 H u0 p0 c0 {6,S}
+# 11 H u0 p0 c0 {6,S}
+# 12 X u0 p0 c0 {6,S}
+# """),
+#)
+#species(
+# label='O=C(X)OCCO[Li]',
+# reactive=True,
+# structure=adjacencyList("""1 O u0 p2 c0 {2,D}
+# 2 C u0 p0 c0 {1,D} {3,S} {12,S}
+# 3 O u0 p2 c0 {2,S} {4,S}
+# 4 C u0 p0 c0 {3,S} {5,S} {7,S} {8,S}
+# 5 C u0 p0 c0 {4,S} {6,S} {9,S} {10,S}
+# 6 O u0 p2 c0 {5,S} {11,S}
+# 7 H u0 p0 c0 {4,S}
+# 8 H u0 p0 c0 {4,S}
+# 9 H u0 p0 c0 {5,S}
+# 10 H u0 p0 c0 {5,S}
+# 11 Li u0 p0 c0 {6,S}
+# 12 X u0 p0 c0 {2,S}
+# """),
+#)
+
+liquidSurfaceReactor(
+ temperature=(298.15,'K'),
+ liqPotential=(-1.0,'V'),
+ surfPotential=(0.0,'V'),
+ initialConcentrations={
+ "ethylene-carbonate": (7.585e-3*2.0,'mol/cm^3'),
+ "Lip": (15.0,'mol/m^3'),
+ },
+ initialSurfaceCoverages={
+ "vacantX": 1.0,
+ },
+ surfaceVolumeRatio=(1.0e5, 'm^-1'),
+ terminationTime=(1.0e3,'sec'),
+ constantSpecies=["ethylene-carbonate","Lip"],
+)
+
+solvation(
+ solvent='ethylene carbonate'
+)
+
+simulator(
+ atol=1e-16,
+ rtol=1e-6,
+)
+
+model(
+ toleranceKeepInEdge=1E-20,
+ toleranceMoveToCore=0.000001,
+ toleranceRadMoveToCore=0.00000000001,
+ toleranceInterruptSimulation=1e10,
+ maximumEdgeSpecies=100000,
+ filterReactions=False,
+ maxNumObjsPerIter=1,
+ terminateAtMaxObjects=True,
+ toleranceBranchReactionToCore=0.000001,
+ branchingIndex=0.3,
+ branchingRatioMax=1.0,
+)
+
+options(
+ units='si',
+ saveEdgeSpecies=False,
+)
+
+forbidden(
+ label='vacancies',
+ structure=adjacencyListGroup("""
+1 Xv u0 p0 c0
+"""),
+)
+
+generatedSpeciesConstraints(
+ allowed=['input species','reaction libraries'],
+ maximumSurfaceSites=1,
+ maximumCarbonAtoms=8,
+ maximumOxygenAtoms=8,
+ maximumRadicalElectrons=1,
+)
diff --git a/examples/rmg/liquid_cat/input.py b/examples/rmg/liquid_cat/input.py
index de70d0f47b..36ffd61991 100644
--- a/examples/rmg/liquid_cat/input.py
+++ b/examples/rmg/liquid_cat/input.py
@@ -1,7 +1,7 @@
# Data sources
database(
thermoLibraries=['surfaceThermoPt111', 'primaryThermoLibrary', 'thermo_DFT_CCSDTF12_BAC','DFT_QCI_thermo'], # 'surfaceThermoPt' is the default. Thermo data is derived using bindingEnergies for other metals
- reactionLibraries = [],
+ reactionLibraries = [('Surface/CPOX_Pt/Deutschmann2006_adjusted', False)], # when Ni is used change the library to Surface/Deutschmann_Ni
seedMechanisms = [],
kineticsDepositories = ['training'],
kineticsFamilies = ['surface','default'],
diff --git a/ipython/kinetics_library_to_training_tools.py b/ipython/kinetics_library_to_training_tools.py
index 286b0bd32f..1d2865f242 100644
--- a/ipython/kinetics_library_to_training_tools.py
+++ b/ipython/kinetics_library_to_training_tools.py
@@ -185,8 +185,9 @@ def process_reactions(database, libraries, families, compare_kinetics=True, show
units = 'cm^3/(mol*s)'
elif len(lib_rxn.reactants) == 3:
units = 'cm^6/(mol^2*s)'
- A = lib_rxn.kinetics.A
- lib_rxn.kinetics.A = ScalarQuantity(value=A.value_si*A.get_conversion_factor_from_si_to_cm_mol_s(),units=units,uncertainty_type=A.uncertainty_type,uncertainty=A.uncertainty_si*A.get_conversion_factor_from_si_to_cm_mol_s())
+ if hasattr(lib_rxn.kinetics,'A'):
+ A = lib_rxn.kinetics.A
+ lib_rxn.kinetics.A = ScalarQuantity(value=A.value_si*A.get_conversion_factor_from_si_to_cm_mol_s(),units=units,uncertainty_type=A.uncertainty_type,uncertainty=A.uncertainty_si*A.get_conversion_factor_from_si_to_cm_mol_s())
if fam_rxn.family in reaction_dict:
reaction_dict[fam_rxn.family].append(lib_rxn)
@@ -436,4 +437,3 @@ def manual_selection(master_dict, multiple_dict, database):
print('================================================================================')
print('Manual selection of reactions completed.')
print('================================================================================')
-
diff --git a/rmgpy/chemkin.pyx b/rmgpy/chemkin.pyx
index d28ee47ce0..068410dacf 100644
--- a/rmgpy/chemkin.pyx
+++ b/rmgpy/chemkin.pyx
@@ -758,7 +758,7 @@ def read_reaction_comments(reaction, comments, read=True):
raise ChemkinError('Unexpected species identifier {0} encountered in flux pairs '
'for reaction {1}.'.format(prod_str, reaction))
reaction.pairs.append((reactant, product))
- assert len(reaction.pairs) == max(len(reaction.reactants), len(reaction.products))
+ #assert len(reaction.pairs) == max(len(reaction.reactants), len(reaction.products))
elif isinstance(reaction, TemplateReaction) and 'rate rule ' in line:
bracketed_rule = tokens[-1]
diff --git a/rmgpy/constants.pxd b/rmgpy/constants.pxd
index c6e7594c08..50edea8ad8 100644
--- a/rmgpy/constants.pxd
+++ b/rmgpy/constants.pxd
@@ -25,4 +25,4 @@
# #
###############################################################################
-cdef double pi, Na, kB, R, h, hbar, c, e, m_e, m_p, m_n, amu, a0, E_h
+cdef double pi, Na, kB, R, h, hbar, c, e, m_e, m_p, m_n, amu, a0, E_h, F
diff --git a/rmgpy/constants.py b/rmgpy/constants.py
index 43e16b4410..3b546d97d6 100644
--- a/rmgpy/constants.py
+++ b/rmgpy/constants.py
@@ -110,6 +110,12 @@
#: :math:`\pi = 3.14159 \ldots`
pi = float(math.pi)
+#: Faradays Constant F in C/mol
+F = 96485.3321233100184
+
+#: Vacuum permittivity
+epsilon_0 = 8.8541878128
+
################################################################################
# Cython does not automatically place module-level variables into the module
@@ -130,4 +136,6 @@
'm_n': m_n,
'm_p': m_p,
'pi': pi,
+ 'F': F,
+ 'epsilon_0': epsilon_0,
})
diff --git a/rmgpy/data/base.py b/rmgpy/data/base.py
index 54c0e6c070..e6d0bc249e 100644
--- a/rmgpy/data/base.py
+++ b/rmgpy/data/base.py
@@ -42,6 +42,7 @@
from rmgpy.data.reference import Reference, Article, Book, Thesis
from rmgpy.exceptions import DatabaseError, InvalidAdjacencyListError
from rmgpy.kinetics.uncertainties import RateUncertainty
+from rmgpy.kinetics.arrhenius import ArrheniusChargeTransfer, ArrheniusChargeTransferBM
from rmgpy.molecule import Molecule, Group
@@ -228,6 +229,8 @@ def load(self, path, local_context=None, global_context=None):
local_context['shortDesc'] = self.short_desc
local_context['longDesc'] = self.long_desc
local_context['RateUncertainty'] = RateUncertainty
+ local_context['ArrheniusChargeTransfer'] = ArrheniusChargeTransfer
+ local_context['ArrheniusChargeTransferBM'] = ArrheniusChargeTransferBM
local_context['metal'] = self.metal
local_context['site'] = self.site
local_context['facet'] = self.facet
@@ -1354,8 +1357,8 @@ def is_molecule_forbidden(self, molecule):
raise NotImplementedError('Checking is only implemented for forbidden Groups, Molecule, and Species.')
# Until we have more thermodynamic data of molecular ions we will forbid them
- if molecule.get_net_charge() != 0:
- return True
+ # if molecule.get_net_charge() != 0:
+ # return True
return False
diff --git a/rmgpy/data/kinetics/database.py b/rmgpy/data/kinetics/database.py
index 91b13e3972..6650a532cc 100644
--- a/rmgpy/data/kinetics/database.py
+++ b/rmgpy/data/kinetics/database.py
@@ -45,11 +45,12 @@
from rmgpy.kinetics import Arrhenius, ArrheniusEP, ThirdBody, Lindemann, Troe, \
PDepArrhenius, MultiArrhenius, MultiPDepArrhenius, \
Chebyshev, KineticsData, StickingCoefficient, \
- StickingCoefficientBEP, SurfaceArrhenius, SurfaceArrheniusBEP, ArrheniusBM
+ StickingCoefficientBEP, SurfaceArrhenius, SurfaceArrheniusBEP, \
+ ArrheniusBM, SurfaceChargeTransfer, KineticsModel, Marcus
from rmgpy.molecule import Molecule, Group
from rmgpy.reaction import Reaction, same_species_lists
from rmgpy.species import Species
-
+from rmgpy.data.solvation import SoluteData, SoluteTSData, SoluteTSDiffData
################################################################################
@@ -80,8 +81,14 @@ def __init__(self):
'StickingCoefficientBEP': StickingCoefficientBEP,
'SurfaceArrhenius': SurfaceArrhenius,
'SurfaceArrheniusBEP': SurfaceArrheniusBEP,
+ 'SurfaceChargeTransfer': SurfaceChargeTransfer,
'R': constants.R,
- 'ArrheniusBM': ArrheniusBM
+ 'ArrheniusBM': ArrheniusBM,
+ 'SoluteData': SoluteData,
+ 'SoluteTSData': SoluteTSData,
+ 'SoluteTSDiffData': SoluteTSDiffData,
+ 'KineticsModel': KineticsModel,
+ 'Marcus': Marcus,
}
self.global_context = {}
diff --git a/rmgpy/data/kinetics/depository.py b/rmgpy/data/kinetics/depository.py
index f3765860a9..b299a81152 100644
--- a/rmgpy/data/kinetics/depository.py
+++ b/rmgpy/data/kinetics/depository.py
@@ -35,6 +35,7 @@
from rmgpy.data.base import Database, Entry, DatabaseError
from rmgpy.data.kinetics.common import save_entry
+from rmgpy.kinetics import SurfaceChargeTransfer, SurfaceArrheniusBEP
from rmgpy.reaction import Reaction
@@ -60,7 +61,8 @@ def __init__(self,
pairs=None,
depository=None,
family=None,
- entry=None
+ entry=None,
+ electrons=None,
):
Reaction.__init__(self,
index=index,
@@ -72,7 +74,8 @@ def __init__(self,
transition_state=transition_state,
duplicate=duplicate,
degeneracy=degeneracy,
- pairs=pairs
+ pairs=pairs,
+ electrons=electrons,
)
self.depository = depository
self.family = family
@@ -104,12 +107,34 @@ def get_source(self):
"""
return self.depository.label
+ def apply_solvent_correction(self, solvent):
+ """
+ apply kinetic solvent correction
+ """
+ from rmgpy.data.rmg import get_db
+ solvation_database = get_db('solvation')
+ solvent_data = solvation_database.get_solvent_data(solvent)
+ solute_data = self.kinetics.solute
+ correction = solvation_database.get_solvation_correction(solute_data, solvent_data)
+ dHR = 0.0
+ dSR = 0.0
+ for spc in self.reactants:
+ spc_solute_data = solvation_database.get_solute_data(spc)
+ spc_correction = solvation_database.get_solvation_correction(spc_solute_data, solvent_data)
+ dHR += spc_correction.enthalpy
+ dSR += spc_correction.entropy
+
+ dH = correction.enthalpy-dHR
+ dA = np.exp((correction.entropy-dSR)/constants.R)
+ self.kinetics.Ea.value_si += dH
+ self.kinetics.A.value_si *= dA
+ self.kinetics.comment += "\nsolvation correction raised barrier by {0} kcal/mol and prefactor by factor of {1}".format(dH/4184.0,dA)
################################################################################
class KineticsDepository(Database):
"""
- A class for working with an RMG kinetics depository. Each depository
+ A class for working with an RMG kinetics depository. Each depository
corresponds to a reaction family (a :class:`KineticsFamily` object). Each
entry in a kinetics depository involves a reaction defined either by a
real reactant and product species (as in a kinetics library).
@@ -187,6 +212,9 @@ def load(self, path, local_context=None, global_context=None):
''.format(product, self.label))
# Same comment about molecule vs species objects as above.
rxn.products.append(species_dict[product])
+
+ if isinstance(entry.data, (SurfaceChargeTransfer, SurfaceArrheniusBEP)):
+ rxn.electrons = entry.data.electrons.value
if not rxn.is_balanced():
raise DatabaseError('Reaction {0} in kinetics depository {1} was not balanced! Please reformulate.'
diff --git a/rmgpy/data/kinetics/family.py b/rmgpy/data/kinetics/family.py
index f8818c78ac..43e3836169 100644
--- a/rmgpy/data/kinetics/family.py
+++ b/rmgpy/data/kinetics/family.py
@@ -36,6 +36,7 @@
import multiprocessing as mp
import os.path
import random
+import math
import re
import warnings
from collections import OrderedDict
@@ -55,7 +56,8 @@
from rmgpy.exceptions import ActionError, DatabaseError, InvalidActionError, KekulizationError, KineticsError, \
ForbiddenStructureException, UndeterminableKineticsError
from rmgpy.kinetics import Arrhenius, SurfaceArrhenius, SurfaceArrheniusBEP, StickingCoefficient, \
- StickingCoefficientBEP, ArrheniusBM
+ StickingCoefficientBEP, ArrheniusBM, SurfaceChargeTransfer, ArrheniusChargeTransfer, \
+ ArrheniusChargeTransferBM, KineticsModel, Marcus
from rmgpy.kinetics.uncertainties import RateUncertainty, rank_accuracy_map
from rmgpy.molecule import Bond, GroupBond, Group, Molecule
from rmgpy.molecule.atomtype import ATOMTYPES
@@ -63,6 +65,8 @@
from rmgpy.species import Species
from rmgpy.tools.uncertainty import KineticParameterUncertainty
from rmgpy.molecule.fragment import Fragment
+import rmgpy.constants as constants
+from rmgpy.data.solvation import SoluteData, add_solute_data, SoluteTSData, to_soluteTSdata
################################################################################
@@ -102,6 +106,7 @@ def __init__(self,
estimator=None,
reverse=None,
is_forward=None,
+ electrons=0,
):
Reaction.__init__(self,
index=index,
@@ -115,6 +120,7 @@ def __init__(self,
degeneracy=degeneracy,
pairs=pairs,
is_forward=is_forward,
+ electrons=electrons
)
self.family = family
self.template = template
@@ -140,7 +146,8 @@ def __reduce__(self):
self.template,
self.estimator,
self.reverse,
- self.is_forward
+ self.is_forward,
+ self.electrons
))
def __repr__(self):
@@ -162,6 +169,7 @@ def __repr__(self):
if self.pairs is not None: string += 'pairs={0}, '.format(self.pairs)
if self.family: string += "family='{}', ".format(self.family)
if self.template: string += "template={}, ".format(self.template)
+ if self.electrons: string += "electrons={}, ".format(self.electrons)
if self.comment != '': string += 'comment={0!r}, '.format(self.comment)
string = string[:-2] + ')'
return string
@@ -195,6 +203,7 @@ def copy(self):
other.transition_state = deepcopy(self.transition_state)
other.duplicate = self.duplicate
other.pairs = deepcopy(self.pairs)
+ other.electrons = self.electrons
# added for TemplateReaction information
other.family = self.family
@@ -205,6 +214,86 @@ def copy(self):
return other
+ def apply_solvent_correction(self, solvent):
+ """
+ apply kinetic solvent correction in this case the parameters are dGTSsite instead of GTS
+ """
+ from rmgpy.data.rmg import get_db
+ solvation_database = get_db('solvation')
+ solvent_data = solvation_database.get_solvent_data(solvent)
+
+
+ if isinstance(self.kinetics, Marcus):
+ solvent_struct = solvation_database.get_solvent_structure(solvent)[0]
+ solv_solute_data = solvation_database.get_solute_data(solvent_struct.copy(deep=True))
+ Rsolv = math.pow((75 * solv_solute_data.V / constants.pi / constants.Na),
+ (1.0 / 3.0)) / 100
+ Rtot = 0.0
+ Ner = 0
+ Nep = 0
+ for spc in self.reactants:
+ spc_solute_data = solvation_database.get_solute_data(spc.copy(deep=True))
+ spc_solute_data.set_mcgowan_volume(spc)
+ R = math.pow((75 * spc_solute_data.V / constants.pi / constants.Na),
+ (1.0 / 3.0)) / 100
+ Rtot += R
+ Ner += spc.get_net_charge()
+ for spc in self.products:
+ Nep += spc.get_net_charge()
+
+ Rtot += Rsolv #radius of reactants plus first solvation shell
+ self.lmbd_o = constants.Na*(constants.e*(Nep-Ner))**2/(8.0*constants.pi*constants.epsilon_0*Rtot)*(1.0/solvent_data.n**2 - 1.0/solvent_data.eps)
+ return
+
+ site_data = to_soluteTSdata(self.kinetics.solute)
+
+ #compute x from gas phase
+ GR = 0.0
+ GP = 0.0
+ for reactant in self.reactants:
+ try:
+ GR += reactant.get_free_energy(298.0)
+ except Exception:
+ logging.error("Problem with reactant {!r} in reaction {!s}".format(reactant, self))
+ raise
+ for product in self.products:
+ try:
+ GP += product.get_free_energy(298.0)
+ except Exception:
+ logging.error("Problem with product {!r} in reaction {!s}".format(reactant, self))
+ raise
+
+ GTS = self.kinetics.Ea.value_si + GR
+
+ #x = abs(GTS - GR) / (abs(GP - GTS) + abs(GR - GTS))
+ dGrxn = GP-GR
+ if dGrxn > 0:
+ x = 1.0
+ else:
+ x = 0.0
+
+ dHR = 0.0
+ dSR = 0.0
+ for spc in self.reactants:
+ spc_solute_data = solvation_database.get_solute_data(spc.copy(deep=True))
+ spc_soluteTS_data = to_soluteTSdata(spc_solute_data)
+ site_data += spc_soluteTS_data*(1.0-x)
+ spc_correction = solvation_database.get_solvation_correction(spc_solute_data, solvent_data)
+ dHR += spc_correction.enthalpy
+ dSR += spc_correction.entropy
+
+ for spc in self.products:
+ spc_solute_data = to_soluteTSdata(solvation_database.get_solute_data(spc.copy(deep=True)))
+ site_data += spc_solute_data*x
+
+ dGTS,dHTS = site_data.calculate_corrections(solvent_data)
+ dSTS = (dHTS - dGTS)/298.0
+
+ dH = dHTS-dHR
+ dA = np.exp((dSTS-dSR)/constants.R)
+ self.kinetics.Ea.value_si += dH
+ self.kinetics.A.value_si *= dA
+ self.kinetics.comment += "\nsolvation correction raised barrier by {0} kcal/mol and prefactor by factor of {1}".format(dH/4184.0,dA)
################################################################################
@@ -260,6 +349,10 @@ def get_reverse(self):
other.add_action(['GAIN_RADICAL', action[1], action[2]])
elif action[0] == 'GAIN_RADICAL':
other.add_action(['LOSE_RADICAL', action[1], action[2]])
+ elif action[0] == 'GAIN_CHARGE':
+ other.add_action(['LOSE_CHARGE', action[1], action[2]])
+ elif action[0] == 'LOSE_CHARGE':
+ other.add_action(['GAIN_CHARGE', action[1], action[2]])
elif action[0] == 'LOSE_PAIR':
other.add_action(['GAIN_PAIR', action[1], action[2]])
elif action[0] == 'GAIN_PAIR':
@@ -309,7 +402,7 @@ def _apply(self, struct, forward, unique):
if info < 1:
raise InvalidActionError('Attempted to change a nonexistent bond.')
# If we do not have a bond, it might be because we are trying to change a vdW bond
- # Lets see if one of that atoms is a surface site,
+ # Lets see if one of that atoms is a surface site,
# If we have a surface site, we will make a single bond, then change it by info - 1
is_vdW_bond = False
for atom in (atom1, atom2):
@@ -383,7 +476,7 @@ def _apply(self, struct, forward, unique):
atom1.apply_action(['BREAK_BOND', label1, info, label2])
atom2.apply_action(['BREAK_BOND', label1, info, label2])
- elif action[0] in ['LOSE_RADICAL', 'GAIN_RADICAL']:
+ elif action[0] in ['LOSE_RADICAL', 'GAIN_RADICAL', 'LOSE_CHARGE', 'GAIN_CHARGE']:
label, change = action[1:]
change = int(change)
@@ -401,6 +494,10 @@ def _apply(self, struct, forward, unique):
atom.apply_action(['GAIN_RADICAL', label, 1])
elif (action[0] == 'LOSE_RADICAL' and forward) or (action[0] == 'GAIN_RADICAL' and not forward):
atom.apply_action(['LOSE_RADICAL', label, 1])
+ elif (action[0] == 'LOSE_CHARGE' and forward) or (action[0] == 'GAIN_CHARGE' and not forward):
+ atom.apply_action(['LOSE_CHARGE', label, 1])
+ elif (action[0] == 'GAIN_CHARGE' and forward) or (action[0] == 'LOSE_CHARGE' and not forward):
+ atom.apply_action(['GAIN_CHARGE', label, 1])
elif action[0] in ['LOSE_PAIR', 'GAIN_PAIR']:
@@ -445,8 +542,8 @@ def apply_reverse(self, struct, unique=True):
class KineticsFamily(Database):
"""
- A class for working with an RMG kinetics family: a set of reactions with
- similar chemistry, and therefore similar reaction rates. The attributes
+ A class for working with an RMG kinetics family: a set of reactions with
+ similar chemistry, and therefore similar reaction rates. The attributes
are:
=================== =============================== ========================
@@ -537,11 +634,11 @@ def distribute_tree_distances(self):
def load(self, path, local_context=None, global_context=None, depository_labels=None):
"""
Load a kinetics database from a file located at `path` on disk.
-
+
If `depository_labels` is a list, eg. ['training','PrIMe'], then only those
depositories are loaded, and they are searched in that order when
generating kinetics.
-
+
If depository_labels is None then load 'training' first then everything else.
If depository_labels is not None then load in the order specified in depository_labels.
"""
@@ -558,6 +655,8 @@ def load(self, path, local_context=None, global_context=None, depository_labels=
local_context['reactantNum'] = None
local_context['productNum'] = None
local_context['autoGenerated'] = False
+ local_context['allowChargedSpecies'] = False
+ local_context['electrons'] = 0
self.groups = KineticsGroups(label='{0}/groups'.format(self.label))
logging.debug("Loading kinetics family groups from {0}".format(os.path.join(path, 'groups.py')))
Database.load(self.groups, os.path.join(path, 'groups.py'), local_context, global_context)
@@ -570,6 +669,8 @@ def load(self, path, local_context=None, global_context=None, depository_labels=
self.product_num = local_context.get('productNum', None)
self.auto_generated = local_context.get('autoGenerated', False)
+ self.allow_charged_species = local_context.get('allowChargedSpecies', False)
+ self.electrons = local_context.get('electrons', 0)
if self.reactant_num:
self.groups.reactant_num = self.reactant_num
@@ -586,6 +687,9 @@ def load(self, path, local_context=None, global_context=None, depository_labels=
self.reverse = local_context.get('reverse', None)
self.reversible = True if local_context.get('reversible', None) is None else local_context.get('reversible', None)
self.forward_template.products = self.generate_product_template(self.forward_template.reactants)
+ for entry in self.forward_template.products:
+ if isinstance(entry.item,Group):
+ entry.item.update()
if self.reversible:
self.reverse_template = Reaction(reactants=self.forward_template.products,
products=self.forward_template.reactants)
@@ -634,7 +738,7 @@ def load(self, path, local_context=None, global_context=None, depository_labels=
# depository and add them to the RMG rate rules by default:
depository_labels = ['training']
if depository_labels:
- # If there are depository labels, load them in the order specified, but
+ # If there are depository labels, load them in the order specified, but
# append the training reactions unless the user specifically declares it not
# to be included with a '!training' flag
if '!training' not in depository_labels:
@@ -673,7 +777,8 @@ def load_recipe(self, actions):
for action in actions:
action[0] = action[0].upper()
valid_actions = [
- 'CHANGE_BOND', 'FORM_BOND', 'BREAK_BOND', 'GAIN_RADICAL', 'LOSE_RADICAL', 'GAIN_PAIR', 'LOSE_PAIR'
+ 'CHANGE_BOND', 'FORM_BOND', 'BREAK_BOND', 'GAIN_RADICAL', 'LOSE_RADICAL',
+ 'GAIN_CHARGE', 'LOSE_CHARGE', 'GAIN_PAIR', 'LOSE_PAIR'
]
if action[0] not in valid_actions:
raise InvalidActionError('Action {0} is not a recognized action. '
@@ -700,12 +805,12 @@ def save_training_reactions(self, reactions, reference=None, reference_type='',
rank=3):
"""
This function takes a list of reactions appends it to the training reactions file. It ignores the existence of
- duplicate reactions.
-
- The rank for each new reaction's kinetics is set to a default value of 3 unless the user specifies differently
+ duplicate reactions.
+
+ The rank for each new reaction's kinetics is set to a default value of 3 unless the user specifies differently
for those reactions.
-
- For each entry, the long description is imported from the kinetics comment.
+
+ For each entry, the long description is imported from the kinetics comment.
"""
if not isinstance(reference, list):
@@ -808,7 +913,7 @@ def save_training_reactions(self, reactions, reference=None, reference_type='',
def save(self, path):
"""
- Save the current database to the file at location `path` on disk.
+ Save the current database to the file at location `path` on disk.
"""
self.save_groups(os.path.join(path, 'groups.py'))
self.rules.save(os.path.join(path, 'rules.py'))
@@ -825,7 +930,7 @@ def save_depository(self, depository, path):
def save_groups(self, path):
"""
- Save the current database to the file at location `path` on disk.
+ Save the current database to the file at location `path` on disk.
"""
entries = self.groups.get_entries_to_save()
@@ -861,10 +966,16 @@ def save_groups(self, path):
f.write('reactantNum = {0}\n\n'.format(self.reactant_num))
if self.product_num is not None:
f.write('productNum = {0}\n\n'.format(self.product_num))
-
+
if self.auto_generated is not None:
f.write('autoGenerated = {0}\n\n'.format(self.auto_generated))
+ if self.allow_charged_species:
+ f.write('allowChargedSpecies = {0}\n\n'.format(self.allow_charged_species))
+
+ if self.electrons != 0:
+ f.write('electrons = {0}\n\n'.format(self.electrons))
+
# Write the recipe
f.write('recipe(actions=[\n')
for action in self.forward_recipe.actions:
@@ -985,14 +1096,14 @@ def generate_product_template(self, reactants0):
def has_rate_rule(self, template):
"""
- Return ``True`` if a rate rule with the given `template` currently
+ Return ``True`` if a rate rule with the given `template` currently
exists, or ``False`` otherwise.
"""
return self.rules.has_rule(template)
def get_rate_rule(self, template):
"""
- Return the rate rule with the given `template`. Raises a
+ Return the rate rule with the given `template`. Raises a
:class:`ValueError` if no corresponding entry exists.
"""
entry = self.rules.get_rule(template)
@@ -1076,6 +1187,21 @@ def add_rules_from_training(self, thermo_database=None, train_indices=None):
Tmax=deepcopy(data.Tmax),
coverage_dependence=deepcopy(data.coverage_dependence),
)
+ elif isinstance(data, SurfaceChargeTransfer):
+ for reactant in entry.item.reactants:
+ # Clear atom labels to avoid effects on thermo generation, ok because this is a deepcopy
+ reactant_copy = reactant.copy(deep=True)
+ reactant_copy.molecule[0].clear_labeled_atoms()
+ reactant_copy.generate_resonance_structures()
+ reactant.thermo = thermo_database.get_thermo_data(reactant_copy, training_set=True)
+ for product in entry.item.products:
+ product_copy = product.copy(deep=True)
+ product_copy.molecule[0].clear_labeled_atoms()
+ product_copy.generate_resonance_structures()
+ product.thermo = thermo_database.get_thermo_data(product_copy, training_set=True)
+ V = data.V0.value_si
+ dGrxn = entry.item._get_free_energy_of_charge_transfer_reaction(298,V)
+ data = data.to_surface_charge_transfer_bep(dGrxn,0.0)
else:
raise NotImplementedError("Unexpected training kinetics type {} for {}".format(type(data), entry))
@@ -1104,7 +1230,9 @@ def add_rules_from_training(self, thermo_database=None, train_indices=None):
for entry in reverse_entries:
tentries[entry.index].item.is_forward = False
- assert isinstance(entry.data, Arrhenius)
+ if not isinstance(entry.data, Arrhenius):
+ print(self.label)
+ assert False
data = deepcopy(entry.data)
data.change_t0(1)
# Estimate the thermo for the reactants and products
@@ -1185,7 +1313,7 @@ def add_rules_from_training(self, thermo_database=None, train_indices=None):
def get_root_template(self):
"""
Return the root template for the reaction family. Most of the time this
- is the top-level nodes of the tree (as stored in the
+ is the top-level nodes of the tree (as stored in the
:class:`KineticsGroups` object), but there are a few exceptions (e.g.
R_Recombination).
"""
@@ -1350,22 +1478,23 @@ def apply_recipe(self, reactant_structures, forward=True, unique=True, relabel_a
product_num = self.product_num or len(template.products)
# Split product structure into multiple species if necessary
- if (isinstance(product_structure, Group) and self.auto_generated and self.label in ["Intra_R_Add_Endocyclic","Intra_R_Add_Exocyclic"]):
+ if self.auto_generated and isinstance(reactant_structures[0],Group) and self.product_num == 1:
product_structures = [product_structure]
else:
product_structures = product_structure.split()
- # Make sure we've made the expected number of products
- if product_num != len(product_structures):
- # We have a different number of products than expected by the template.
- # By definition this means that the template is not a match, so
- # we return None to indicate that we could not generate the product
- # structures
- # We need to think this way in order to distinguish between
- # intermolecular and intramolecular versions of reaction families,
- # which will have very different kinetics
- # Unfortunately this may also squash actual errors with malformed
- # reaction templates
- return None
+
+ # Make sure we've made the expected number of products
+ if product_num != len(product_structures):
+ # We have a different number of products than expected by the template.
+ # By definition this means that the template is not a match, so
+ # we return None to indicate that we could not generate the product
+ # structures
+ # We need to think this way in order to distinguish between
+ # intermolecular and intramolecular versions of reaction families,
+ # which will have very different kinetics
+ # Unfortunately this may also squash actual errors with malformed
+ # reaction templates
+ return None
# Remove vdW bonds
for struct in product_structures:
@@ -1383,15 +1512,20 @@ def apply_recipe(self, reactant_structures, forward=True, unique=True, relabel_a
struc.update()
reactant_net_charge += struc.get_net_charge()
+
+ is_molecule = True
for struct in product_structures:
# If product structures are Molecule objects, update their atom types
# If product structures are Group objects and the reaction is in certain families
# (families with charged substances), the charge of structures will be updated
if isinstance(struct, Molecule):
- struct.update(sort_atoms=not self.save_order)
- elif isinstance(struct, Fragment):
- struct.update()
+ struct.update_charge()
+ if isinstance(struct, Fragment):
+ struct.update()
+ else:
+ struct.update(sort_atoms=not self.save_order)
elif isinstance(struct, Group):
+ is_molecule = False
struct.reset_ring_membership()
if label in ['1,2_insertion_co', 'r_addition_com', 'co_disproportionation',
'intra_no2_ono_conversion', 'lone_electron_pair_bond',
@@ -1399,20 +1533,25 @@ def apply_recipe(self, reactant_structures, forward=True, unique=True, relabel_a
struct.update_charge()
else:
raise TypeError('Expecting Molecule or Group object, not {0}'.format(struct.__class__.__name__))
- product_net_charge += struc.get_net_charge()
- if reactant_net_charge != product_net_charge:
+ product_net_charge += struct.get_net_charge()
+
+
+ if self.electrons < 0:
+ if forward:
+ reactant_net_charge += self.electrons
+ else:
+ product_net_charge += self.electrons
+ elif self.electrons > 0:
+ if forward:
+ product_net_charge -= self.electrons
+ else:
+ reactant_net_charge -= self.electrons
+
+ if reactant_net_charge != product_net_charge and is_molecule:
logging.debug(
'The net charge of the reactants {0} differs from the net charge of the products {1} in reaction '
'family {2}. Not generating this reaction.'.format(reactant_net_charge, product_net_charge, self.label))
return None
- # The following check should be removed once RMG can process charged species
- # This is applied only for :class:Molecule (not for :class:Group which is allowed to have a nonzero net charge)
- if any([structure.get_net_charge() for structure in reactant_structures + product_structures]) \
- and isinstance(struc, Molecule):
- logging.debug(
- 'A net charged species was formed when reacting {0} to form {1} in reaction family {2}. Not generating '
- 'this reaction.'.format(reactant_net_charge, product_net_charge, self.label))
- return None
# If there are two product structures, place the one containing '*1' first
if len(product_structures) == 2:
@@ -1523,10 +1662,10 @@ def _generate_product_structures(self, reactant_structures, maps, forward, relab
def is_molecule_forbidden(self, molecule):
"""
Return ``True`` if the molecule is forbidden in this family, or
- ``False`` otherwise.
+ ``False`` otherwise.
"""
- # check family-specific forbidden structures
+ # check family-specific forbidden structures
if self.forbidden is not None and self.forbidden.is_molecule_forbidden(molecule):
return True
@@ -1558,8 +1697,17 @@ def _create_reaction(self, reactants, products, is_forward):
reversible=self.reversible,
family=self.label,
is_forward=is_forward,
+ electrons = self.electrons
)
+ if not self.allow_charged_species:
+ for spc in (reaction.reactants + reaction.products):
+ if spc.get_net_charge() != 0:
+ return None
+
+ if not reaction.is_balanced():
+ return None
+
# Store the labeled atoms so we can recover them later
# (e.g. for generating reaction pairs and templates)
for key, species_list in zip(['reactants', 'products'], [reaction.reactants, reaction.products]):
@@ -1570,7 +1718,7 @@ def _create_reaction(self, reactants, products, is_forward):
def _match_reactant_to_template(self, reactant, template_reactant):
"""
- Return a complete list of the mappings if the provided reactant
+ Return a complete list of the mappings if the provided reactant
matches the provided template reactant, or an empty list if not.
"""
@@ -1746,9 +1894,17 @@ def calculate_degeneracy(self, reaction, resonance=True):
For a `reaction` with `Molecule` or `Species` objects given in the direction in which
the kinetics are defined, compute the reaction-path degeneracy. Can specify whether to consider resonance.
- This method by default adjusts for double counting of identical reactants.
- This should only be adjusted once per reaction.
- """
+ This method by default adjusts for double counting of identical reactants.
+ This should only be adjusted once per reaction. To not adjust for
+ identical reactants (since you will be reducing them later in the algorithm), add
+ `ignoreSameReactants= True` to this method.
+ """
+ # Check if the reactants are the same
+ # If they refer to the same memory address, then make a deep copy so
+ # they can be manipulated independently
+ if reaction.is_charge_transfer_reaction():
+ # Not implemented yet for charge transfer reactions
+ return 1
reactants = reaction.reactants
reactants, same_reactants = check_for_same_reactants(reactants)
@@ -1810,7 +1966,7 @@ def _generate_reactions(self, reactants, products=None, forward=True, prod_reson
rxn_list = []
- # Wrap each reactant in a list if not already done (this is done to
+ # Wrap each reactant in a list if not already done (this is done to
# allow for passing multiple resonance structures for each molecule)
# This also makes a copy of the reactants list so we don't modify the
# original
@@ -2124,7 +2280,7 @@ def generate_products_and_reactions(order):
if not forward and ('adsorption' in self.label.lower() or 'eleyrideal' in self.label.lower()):
# Desorption should have desorbed something (else it was probably bidentate)
# so delete reactions that don't make a gas-phase desorbed product
- # Eley-Rideal reactions should have one gas-phase product in the reverse direction
+ # Eley-Rideal reactions should have one gas-phase product in the reverse direction
# Determine how many surf reactants we expect based on the template
n_surf_expected = len([r for r in self.forward_template.reactants if r.item.contains_surface_site()])
@@ -2194,7 +2350,7 @@ def get_reaction_pairs(self, reaction):
"""
pairs = []
if len(reaction.reactants) == 1 or len(reaction.products) == 1:
- # When there is only one reactant (or one product), it is paired
+ # When there is only one reactant (or one product), it is paired
# with each of the products (reactants)
for reactant in reaction.reactants:
for product in reaction.products:
@@ -2378,7 +2534,7 @@ def get_kinetics_for_template(self, template, degeneracy=1, method='rate rules')
def get_kinetics_from_depository(self, depository, reaction, template, degeneracy):
"""
Search the given `depository` in this kinetics family for kinetics
- for the given `reaction`. Returns a list of all of the matching
+ for the given `reaction`. Returns a list of all of the matching
kinetics, the corresponding entries, and ``True`` if the kinetics
match the forward direction or ``False`` if they match the reverse
direction.
@@ -2484,7 +2640,7 @@ def estimate_kinetics_using_rate_rules(self, template, degeneracy=1):
"""
Determine the appropriate kinetics for a reaction with the given
`template` using rate rules.
-
+
Returns a tuple (kinetics, entry) where `entry` is the database
entry used to determine the kinetics only if it is an exact match,
and is None if some averaging or use of a parent node took place.
@@ -2495,8 +2651,8 @@ def estimate_kinetics_using_rate_rules(self, template, degeneracy=1):
def get_reaction_template_labels(self, reaction):
"""
- Retrieve the template for the reaction and
- return the corresponding labels for each of the
+ Retrieve the template for the reaction and
+ return the corresponding labels for each of the
groups in the template.
"""
template = self.get_reaction_template(reaction)
@@ -2509,8 +2665,8 @@ def get_reaction_template_labels(self, reaction):
def retrieve_template(self, template_labels):
"""
- Reconstruct the groups associated with the
- labels of the reaction template and
+ Reconstruct the groups associated with the
+ labels of the reaction template and
return a list.
"""
template = []
@@ -2521,9 +2677,9 @@ def retrieve_template(self, template_labels):
def get_labeled_reactants_and_products(self, reactants, products, relabel_atoms=True):
"""
- Given `reactants`, a list of :class:`Molecule` objects, and products, a list of
- :class:`Molecule` objects, return two new lists of :class:`Molecule` objects with
- atoms labeled: one for reactants, one for products. Returned molecules are totally
+ Given `reactants`, a list of :class:`Molecule` objects, and products, a list of
+ :class:`Molecule` objects, return two new lists of :class:`Molecule` objects with
+ atoms labeled: one for reactants, one for products. Returned molecules are totally
new entities in memory so input molecules `reactants` and `products` won't be affected.
If RMG cannot find appropriate labels, (None, None) will be returned.
If ``relabel_atoms`` is ``True``, product atom labels of reversible families
@@ -2702,7 +2858,7 @@ def add_entry(self, parent, grp, name):
def _split_reactions(self, rxns, newgrp):
"""
divides the reactions in rxns between the new
- group structure newgrp and the old structure with
+ group structure newgrp and the old structure with
label oldlabel
returns a list of reactions associated with the new group
the list of reactions associated with the old group
@@ -2727,14 +2883,14 @@ def _split_reactions(self, rxns, newgrp):
comp.append(rxn)
return new, comp, new_inds
-
+
def reaction_matches(self, rxn, grp):
rmol = rxn.reactants[0].molecule[0]
for r in rxn.reactants[1:]:
rmol = rmol.merge(r.molecule[0])
rmol.identify_ring_membership()
return rmol.is_subgraph_isomorphic(grp, generate_initial_map=True, save_order=True)
-
+
def eval_ext(self, parent, ext, extname, template_rxn_map, obj=None, T=1000.0):
"""
evaluates the objective function obj
@@ -2758,15 +2914,15 @@ def get_extension_edge(self, parent, template_rxn_map, obj, T, iter_max=np.inf,
finds the set of all extension groups to parent such that
1) the extension group divides the set of reactions under parent
2) No generalization of the extension group divides the set of reactions under parent
-
+
We find this by generating all possible extensions of the initial group. Extensions that split reactions are added
- to the list. All extensions that do not split reactions and do not create bonds are ignored
+ to the list. All extensions that do not split reactions and do not create bonds are ignored
(although those that match every reaction are labeled so we don't search them twice). Those that match
- all reactions and involve bond creation undergo this process again.
-
- Principle: Say you have two elementary changes to a group ext1 and ext2 if applying ext1 and ext2 results in a
+ all reactions and involve bond creation undergo this process again.
+
+ Principle: Say you have two elementary changes to a group ext1 and ext2 if applying ext1 and ext2 results in a
split at least one of ext1 and ext2 must result in a split
-
+
Speed of this algorithm relies heavily on searching non bond creation dimensions once.
"""
out_exts = [[]]
@@ -2777,7 +2933,7 @@ def get_extension_edge(self, parent, template_rxn_map, obj, T, iter_max=np.inf,
n_splits = len(template_rxn_map[parent.label][0].reactants)
iter = 0
-
+
while grps[iter] != []:
grp = grps[iter][-1]
@@ -2896,7 +3052,7 @@ def get_extension_edge(self, parent, template_rxn_map, obj, T, iter_max=np.inf,
out_exts.append([])
grps[iter].pop()
names.pop()
-
+
for ind in ext_inds: # collect the groups to be expanded
grpr, grpcr, namer, typr, indcr = exts[ind]
if len(grps) == iter+1:
@@ -2906,17 +3062,17 @@ def get_extension_edge(self, parent, template_rxn_map, obj, T, iter_max=np.inf,
if first_time:
first_time = False
-
+
if grps[iter] == [] and len(grps) != iter+1 and (not (any([len(x)>0 for x in out_exts]) and iter+1 > iter_max)):
iter += 1
if len(grps[iter]) > iter_item_cap:
logging.error("Recursion item cap hit not splitting {0} reactions at iter {1} with {2} items".format(len(template_rxn_map[parent.label]),iter,len(grps[iter])))
iter -= 1
gave_up_split = True
-
+
elif grps[iter] == [] and len(grps) != iter+1 and (any([len(x)>0 for x in out_exts]) and iter+1 > iter_max):
logging.error("iter_max achieved terminating early")
-
+
out = []
# compile all of the valid extensions together
# may be some duplicates here, but I don't think it's currently worth identifying them
@@ -2927,7 +3083,7 @@ def get_extension_edge(self, parent, template_rxn_map, obj, T, iter_max=np.inf,
def extend_node(self, parent, template_rxn_map, obj=None, T=1000.0, iter_max=np.inf, iter_item_cap=np.inf):
"""
- Constructs an extension to the group parent based on evaluation
+ Constructs an extension to the group parent based on evaluation
of the objective function obj
"""
exts, gave_up_split = self.get_extension_edge(parent, template_rxn_map, obj=obj, T=T, iter_max=iter_max, iter_item_cap=iter_item_cap)
@@ -2983,10 +3139,10 @@ def extend_node(self, parent, template_rxn_map, obj=None, T=1000.0, iter_max=np.
parent.item.clear_reg_dims() # this almost always solves the problem
return True
return False
-
+
if gave_up_split:
return False
-
+
vals = []
for grp, grpc, name, typ, einds in exts:
val, boo = self.eval_ext(parent, grp, name, template_rxn_map, obj, T)
@@ -3082,7 +3238,7 @@ def extend_node(self, parent, template_rxn_map, obj=None, T=1000.0, iter_max=np.
logging.error(prod.label)
logging.error(prod.to_adjacency_list())
raise ValueError
-
+
template_rxn_map[extname] = new_entries
if complement:
@@ -3098,21 +3254,21 @@ def generate_tree(self, rxns=None, obj=None, thermo_database=None, T=1000.0, npr
"""
Generate a tree by greedy optimization based on the objective function obj
the optimization is done by iterating through every group and if the group has
- more than one training reaction associated with it a set of potential more specific extensions
- are generated and the extension that optimizing the objective function combination is chosen
+ more than one training reaction associated with it a set of potential more specific extensions
+ are generated and the extension that optimizing the objective function combination is chosen
and the iteration starts over at the beginning
-
+
additionally the tree structure is simplified on the fly by removing groups that have no kinetics data
associated if their parent has no kinetics data associated and they either have only one child or
have two children one of which has no kinetics data and no children
(its parent becomes the parent of its only relevant child node)
-
+
Args:
rxns: List of reactions to generate tree from (if None pull the whole training set)
obj: Object to expand tree from (if None uses top node)
thermo_database: Thermodynamic database used for reversing training reactions
T: Temperature the tree is optimized for
- nprocs: Number of process for parallel tree generation
+ nprocs: Number of process for parallel tree generation
min_splitable_entry_num: the minimum number of splitable reactions at a node in order to spawn
a new process solving that node
min_rxns_to_spawn: the minimum number of reactions at a node to spawn a new process solving that node
@@ -3125,7 +3281,7 @@ def generate_tree(self, rxns=None, obj=None, thermo_database=None, T=1000.0, npr
"""
if rxns is None:
rxns = self.get_training_set(thermo_database=thermo_database, remove_degeneracy=True, estimate_thermo=True,
- fix_labels=True, get_reverse=True)
+ fix_labels=True, get_reverse=True, rxns_with_kinetics_only=True)
if len(rxns) <= max_batch_size:
template_rxn_map = self.get_reaction_matches(rxns=rxns, thermo_database=thermo_database, remove_degeneracy=True,
@@ -3156,9 +3312,9 @@ def rxnkey(rxn):
min_splitable_entry_num=min_splitable_entry_num, min_rxns_to_spawn=min_rxns_to_spawn, extension_iter_max=extension_iter_max,
extension_iter_item_cap=extension_iter_item_cap)
logging.error("built tree with {} nodes".format(len(list(self.groups.entries))))
-
+
self.auto_generated = True
-
+
def get_rxn_batches(self, rxns, T=1000.0, max_batch_size=800, outlier_fraction=0.02, stratum_num=8):
"""
Breaks reactions into batches based on a modified stratified sampling scheme
@@ -3261,7 +3417,7 @@ def make_tree_nodes(self, template_rxn_map=None, obj=None, T=1000.0, nprocs=0, d
entries.remove(entry)
else:
psize = float(len(template_rxn_map[root.label]))
-
+
logging.error(psize)
mult_completed_nodes = [] # nodes containing multiple identical training reactions
boo = True # if the for loop doesn't break becomes false and the while loop terminates
@@ -3396,7 +3552,25 @@ def make_bm_rules_from_template_rxn_map(self, template_rxn_map, nprocs=1, Tref=1
kinetics_list = kinetics_list[revinds] # fix order
for i, kinetics in enumerate(kinetics_list):
- if kinetics is not None:
+ if isinstance(kinetics, Marcus):
+ entry = entries[i]
+ st = "Marcus rule fitted to {0} training reactions at node {1}".format(len(rxnlists[i][0]), entry.label)
+ new_entry = Entry(
+ index=index,
+ label=entry.label,
+ item=self.forward_template,
+ data=kinetics,
+ rank=11,
+ reference=None,
+ short_desc=st,
+ long_desc=st,
+ )
+ new_entry.data.comment = st
+
+ self.rules.entries[entry.label].append(new_entry)
+
+ index += 1
+ elif kinetics is not None:
entry = entries[i]
std = kinetics.uncertainty.get_expected_log_uncertainty() / 0.398 # expected uncertainty is std * 0.398
st = "BM rule fitted to {0} training reactions at node {1}".format(len(rxnlists[i][0]), entry.label)
@@ -3417,11 +3591,21 @@ def make_bm_rules_from_template_rxn_map(self, template_rxn_map, nprocs=1, Tref=1
index += 1
- def cross_validate(self, folds=5, template_rxn_map=None, test_rxn_inds=None, T=1000.0, iters=0, random_state=1, ascend=False):
+ for label,entry in self.rules.entries.items(): #pull solute data from further up the tree as needed
+ if len(entry) == 0:
+ continue
+ entry = entry[0]
+ if not entry.data.solute:
+ ent = self.groups.entries[label]
+ while not self.rules.entries[ent.label][0].data.solute and ent.parent:
+ ent = ent.parent
+ entry.data.solute = self.rules.entries[ent.label][0].data.solute
+
+ def cross_validate(self, folds=5, template_rxn_map=None, test_rxn_inds=None, T=1000.0, iters=0, random_state=1):
"""
Perform K-fold cross validation on an automatically generated tree at temperature T
after finding an appropriate node for kinetics estimation it will move up the tree
- iters times.
+ iters times.
Returns a dictionary mapping {rxn:Ln(k_Est/k_Train)}
"""
@@ -3473,44 +3657,44 @@ def cross_validate(self, folds=5, template_rxn_map=None, test_rxn_inds=None, T=1
if entry.parent:
entry = entry.parent
+ boo = True
+
+ while boo:
+ if entry.parent is None:
+ break
+ kin = self.rules.entries[entry.label][0].data
+ kinparent = self.rules.entries[entry.parent.label][0].data
+ err_parent = abs(kinparent.uncertainty.data_mean + kinparent.uncertainty.mu - kin.uncertainty.data_mean) + np.sqrt(2.0*kinparent.uncertainty.var/np.pi)
+ err_entry = abs(kin.uncertainty.mu) + np.sqrt(2.0*kin.uncertainty.var/np.pi)
+ if err_entry <= err_parent:
+ break
+ else:
+ entry = entry.parent
+
uncertainties[rxn] = self.rules.entries[entry.label][0].data.uncertainty
-
- if not ascend:
- L = list(set(template_rxn_map[entry.label]) - set(rxns_test))
- if L != []:
+
+ L = list(set(template_rxn_map[entry.label]) - set(rxns_test))
+
+ if L != []:
+ if isinstance(L[0].kinetics, Arrhenius):
kinetics = ArrheniusBM().fit_to_reactions(L, recipe=self.forward_recipe.actions)
- kinetics = kinetics.to_arrhenius(rxn.get_enthalpy_of_reaction(T))
- k = kinetics.get_rate_coefficient(T)
- errors[rxn] = np.log(k / krxn)
+ if kinetics.E0.value_si < 0.0 or len(L) == 1:
+ kinetics = average_kinetics([r.kinetics for r in L])
+ else:
+ kinetics = kinetics.to_arrhenius(rxn.get_enthalpy_of_reaction(298.))
else:
- raise ValueError('only one piece of kinetics information in the tree?')
- else:
- boo = True
- rlist = list(set(template_rxn_map[entry.label]) - set(rxns_test))
- kinetics = _make_rule((self.forward_recipe.actions,rlist,T,1.0e3,"",[rxn.rank for rxn in rlist]))
- logging.error("determining fold rate")
- c = 1
- while boo:
- parent = entry.parent
- if parent is None:
- break
- rlistparent = list(set(template_rxn_map[parent.label]) - set(rxns_test))
- kineticsparent = _make_rule((self.forward_recipe.actions,rlistparent,T,1.0e3,"",[rxn.rank for rxn in rlistparent]))
- err_parent = abs(kineticsparent.uncertainty.data_mean + kineticsparent.uncertainty.mu - kinetics.uncertainty.data_mean) + np.sqrt(2.0*kineticsparent.uncertainty.var/np.pi)
- err_entry = abs(kinetics.uncertainty.mu) + np.sqrt(2.0*kinetics.uncertainty.var/np.pi)
- if err_entry > err_parent:
- entry = entry.parent
- kinetics = kineticsparent
- logging.error("recursing {}".format(c))
- c += 1
+ kinetics = ArrheniusChargeTransferBM().fit_to_reactions(L, recipe=self.forward_recipe.actions)
+ if kinetics.E0.value_si < 0.0 or len(L) == 1:
+ kinetics = average_kinetics([r.kinetics for r in L])
else:
- boo = False
-
- kinetics = kinetics.to_arrhenius(rxn.get_enthalpy_of_reaction(T))
+ kinetics = kinetics.to_arrhenius_charge_transfer(rxn.get_enthalpy_of_reaction(298.))
+
k = kinetics.get_rate_coefficient(T)
errors[rxn] = np.log(k / krxn)
-
+ else:
+ raise ValueError('only one piece of kinetics information in the tree?')
+
return errors, uncertainties
def cross_validate_old(self, folds=5, T=1000.0, random_state=1, estimator='rate rules', thermo_database=None, get_reverse=False, uncertainties=True):
@@ -3520,7 +3704,7 @@ def cross_validate_old(self, folds=5, T=1000.0, random_state=1, estimator='rate
"""
errors = {}
uncs = {}
-
+
kpu = KineticParameterUncertainty()
rxns = np.array(self.get_training_set(remove_degeneracy=True,get_reverse=get_reverse))
@@ -3564,7 +3748,7 @@ def cross_validate_old(self, folds=5, T=1000.0, random_state=1, estimator='rate
boo,source = self.extract_source_from_comments(testrxn)
sdict = {"Rate Rules":source}
uncs[rxn] = kpu.get_uncertainty_value(sdict)
-
+
if uncertainties:
return errors, uncs
else:
@@ -3574,19 +3758,19 @@ def simple_regularization(self, node, template_rxn_map, test=True):
"""
Simplest regularization algorithm
All nodes are made as specific as their descendant reactions
- Training reactions are assumed to not generalize
+ Training reactions are assumed to not generalize
For example if an particular atom at a node is Oxygen for all of its
descendent reactions a reaction where it is Sulfur will never hit that node
- unless it is the top node even if the tree did not split on the identity
+ unless it is the top node even if the tree did not split on the identity
of that atom
-
- The test option to this function determines whether or not the reactions
- under a node match the extended group before adding an extension.
- If the test fails the extension is skipped.
-
- In general test=True is needed if the cascade algorithm was used
+
+ The test option to this function determines whether or not the reactions
+ under a node match the extended group before adding an extension.
+ If the test fails the extension is skipped.
+
+ In general test=True is needed if the cascade algorithm was used
to generate the tree and test=False is ok if the cascade algorithm
- wasn't used.
+ wasn't used.
"""
for child in node.children:
@@ -3703,7 +3887,7 @@ def regularize(self, regularization=simple_regularization, keep_root=True, therm
if template_rxn_map is None:
if rxns is None:
template_rxn_map = self.get_reaction_matches(thermo_database=thermo_database, remove_degeneracy=True,
- get_reverse=True, exact_matches_only=False, fix_labels=True)
+ get_reverse=True, exact_matches_only=False, fix_labels=True, rxns_with_kinetics_only=False)
else:
template_rxn_map = self.get_reaction_matches(rxns=rxns, thermo_database=thermo_database,
remove_degeneracy=True, get_reverse=True, exact_matches_only=False,
@@ -3792,7 +3976,7 @@ def clean_tree(self):
def save_generated_tree(self, path=None):
"""
- clears the rules and saves the family to its
+ clears the rules and saves the family to its
current location in database
"""
if path is None:
@@ -3802,11 +3986,11 @@ def save_generated_tree(self, path=None):
self.save(path)
def get_training_set(self, thermo_database=None, remove_degeneracy=False, estimate_thermo=True, fix_labels=False,
- get_reverse=False):
+ get_reverse=False, rxns_with_kinetics_only=False):
"""
retrieves all reactions in the training set, assigns thermo to the species objects
reverses reactions as necessary so that all reactions are in the forward direction
- and returns the resulting list of reactions in the forward direction with thermo
+ and returns the resulting list of reactions in the forward direction with thermo
assigned
"""
@@ -3863,8 +4047,8 @@ def get_reactant_thermo(reactant,metal):
logging.info('Must be because you turned off the training depository.')
return
- rxns = deepcopy([i.item for i in dep.entries.values()])
- entries = deepcopy([i for i in dep.entries.values()])
+ rxns = deepcopy([i.item for i in dep.entries.values() if (not rxns_with_kinetics_only) or type(i.data) != KineticsModel])
+ entries = deepcopy([i for i in dep.entries.values() if (not rxns_with_kinetics_only) or type(i.data) != KineticsModel])
roots = [x.item for x in self.get_root_template()]
root = None
@@ -3876,7 +4060,7 @@ def get_reactant_thermo(reactant,metal):
root_labels = [x.label for x in root.atoms if x.label != '']
root_label_set = set(root_labels)
-
+
for i, entry in enumerate(entries):
if estimate_thermo:
# parse out the metal to scale to
@@ -3894,7 +4078,7 @@ def get_reactant_thermo(reactant,metal):
rxns[i].kinetics = entry.data
rxns[i].rank = entry.rank
- if remove_degeneracy: # adjust for degeneracy
+ if remove_degeneracy and type(rxns[i].kinetics) != KineticsModel: # adjust for degeneracy
rxns[i].kinetics.A.value_si /= rxns[i].degeneracy
mol = None
@@ -3906,6 +4090,8 @@ def get_reactant_thermo(reactant,metal):
else:
mol = deepcopy(react.molecule[0])
+ mol.update_atomtypes()
+
if fix_labels:
for prod in rxns[i].products:
fix_labels_mol(prod.molecule[0], root_labels)
@@ -3924,12 +4110,12 @@ def get_reactant_thermo(reactant,metal):
mol = mol.merge(react.molecule[0])
else:
mol = deepcopy(react.molecule[0])
-
+
if fix_labels:
mol_label_set = set([x.label for x in get_label_fixed_mol(mol, root_labels).atoms if x.label != ''])
else:
mol_label_set = set([x.label for x in mol.atoms if x.label != ''])
-
+
if mol_label_set == root_label_set and ((mol.is_subgraph_isomorphic(root, generate_initial_map=True) or
(not fix_labels and
get_label_fixed_mol(mol, root_labels).is_subgraph_isomorphic(root, generate_initial_map=True)))):
@@ -3962,7 +4148,10 @@ def get_reactant_thermo(reactant,metal):
reacts = [Species(molecule=[get_label_fixed_mol(x.molecule[0], root_labels)], thermo=x.thermo)
for x in rxns[i].reactants]
- rrev = Reaction(reactants=products, products=reacts,
+ if type(rxns[i].kinetics) != KineticsModel:
+ if rxns[i].kinetics.solute:
+ rxns[i].kinetics.solute = to_soluteTSdata(rxns[i].kinetics.solute,reactants=rxns[i].reactants)
+ rrev = Reaction(reactants=products, products=reacts,
kinetics=rxns[i].generate_reverse_rate_coefficient(), rank=rxns[i].rank)
rrev.is_forward = False
@@ -3991,6 +4180,8 @@ def get_reactant_thermo(reactant,metal):
else:
mol = deepcopy(react.molecule[0])
+ mol.update_atomtypes()
+
if (mol.is_subgraph_isomorphic(root, generate_initial_map=True) or
(not fix_labels and
get_label_fixed_mol(mol, root_labels).is_subgraph_isomorphic(root, generate_initial_map=True))): # try product structures
@@ -4017,15 +4208,15 @@ def get_reactant_thermo(reactant,metal):
return rxns
def get_reaction_matches(self, rxns=None, thermo_database=None, remove_degeneracy=False, estimate_thermo=True,
- fix_labels=False, exact_matches_only=False, get_reverse=False):
+ fix_labels=False, exact_matches_only=False, get_reverse=False, rxns_with_kinetics_only=False):
"""
- returns a dictionary mapping for each entry in the tree:
+ returns a dictionary mapping for each entry in the tree:
(entry.label,entry.item) : list of all training reactions (or the list given) that match that entry
"""
if rxns is None:
rxns = self.get_training_set(thermo_database=thermo_database, remove_degeneracy=remove_degeneracy,
estimate_thermo=estimate_thermo, fix_labels=fix_labels,
- get_reverse=get_reverse)
+ get_reverse=get_reverse,rxns_with_kinetics_only=rxns_with_kinetics_only)
entries = self.groups.entries
@@ -4112,10 +4303,10 @@ def retrieve_original_entry(self, template_label):
"""
Retrieves the original entry, be it a rule or training reaction, given
the template label in the form 'group1;group2' or 'group1;group2;group3'
-
+
Returns tuple in the form
(RateRuleEntry, TrainingReactionEntry)
-
+
Where the TrainingReactionEntry is only present if it comes from a training reaction
"""
template_labels = template_label.split()[-1].split(';')
@@ -4132,13 +4323,13 @@ def get_sources_for_template(self, template):
"""
Returns the set of rate rules and training reactions used to average this `template`. Note that the tree must be
averaged with verbose=True for this to work.
-
+
Returns a tuple of
rules, training
-
- where rules are a list of tuples containing
+
+ where rules are a list of tuples containing
the [(original_entry, weight_used_in_average), ... ]
-
+
and training is a list of tuples containing
the [(rate_rule_entry, training_reaction_entry, weight_used_in_average),...]
"""
@@ -4146,7 +4337,7 @@ def get_sources_for_template(self, template):
def assign_weights_to_entries(entry_nested_list, weighted_entries, n=1):
"""
Assign weights to an average of average nested list. Where n is the
- number of values being averaged recursively.
+ number of values being averaged recursively.
"""
n = len(entry_nested_list) * n
for entry in entry_nested_list:
@@ -4220,7 +4411,7 @@ def assign_weights_to_entries(entry_nested_list, weighted_entries, n=1):
rules[rule_entry] += weight
else:
rules[rule_entry] = weight
- # Each entry should now only appear once
+ # Each entry should now only appear once
training = [(k[0], k[1], v) for k, v in training.items()]
rules = list(rules.items())
@@ -4231,11 +4422,11 @@ def extract_source_from_comments(self, reaction):
Returns the rate rule associated with the kinetics of a reaction by parsing the comments.
Will return the template associated with the matched rate rule.
Returns a tuple containing (Boolean_Is_Kinetics_From_Training_reaction, Source_Data)
-
+
For a training reaction, the Source_Data returns::
[Family_Label, Training_Reaction_Entry, Kinetics_In_Reverse?]
-
+
For a reaction from rate rules, the Source_Data is a tuple containing::
[Family_Label, {'template': originalTemplate,
@@ -4270,7 +4461,7 @@ def extract_source_from_comments(self, reaction):
'but does not match the training reaction {1} from the '
'{2} family.'.format(reaction, training_reaction_index, self.label))
- # Sometimes the matched kinetics could be in the reverse direction.....
+ # Sometimes the matched kinetics could be in the reverse direction.....
if reaction.is_isomorphic(training_entry.item, either_direction=False, save_order=self.save_order):
reverse = False
else:
@@ -4284,7 +4475,7 @@ def extract_source_from_comments(self, reaction):
elif line.startswith('Multiplied by'):
degeneracy = float(line.split()[-1])
- # Extract the rate rule information
+ # Extract the rate rule information
full_comment_string = reaction.kinetics.comment.replace('\n', ' ')
# The rate rule string is right after the phrase 'for rate rule'
@@ -4366,8 +4557,12 @@ def get_objective_function(kinetics1, kinetics2, obj=information_gain, T=1000.0)
Error using mean: Err_1 + Err_2
Split: abs(N1-N2)
"""
- ks1 = np.array([np.log(k.get_rate_coefficient(T)) for k in kinetics1])
- ks2 = np.array([np.log(k.get_rate_coefficient(T)) for k in kinetics2])
+ if not isinstance(kinetics1[0], Marcus):
+ ks1 = np.array([np.log(k.get_rate_coefficient(T)) for k in kinetics1])
+ ks2 = np.array([np.log(k.get_rate_coefficient(T)) for k in kinetics2])
+ else:
+ ks1 = np.array([k.get_lmbd_i(T) for k in kinetics1])
+ ks2 = np.array([k.get_lmbd_i(T) for k in kinetics2])
N1 = len(ks1)
return obj(ks1, ks2), N1 == 0
@@ -4375,29 +4570,115 @@ def get_objective_function(kinetics1, kinetics2, obj=information_gain, T=1000.0)
def _make_rule(rr):
"""
- function for parallelization of rule and uncertainty calculation
+ Function for parallelization of rule and uncertainty calculation
+
+ Input: rr - tuple of (recipe, rxns, Tref, fmax, label, ranks)
+ rxns and ranks are lists of equal length.
+ Output: kinetics object, with uncertainty and comment attached.
+ If Blowers-Masel fitting is successful it will be ArrheniusBM or ArrheniusChargeTransferBM,
+ else Arrhenius, SurfaceChargeTransfer, or ArrheniusChargeTransfer.
+
Errors in Ln(k) at each reaction are treated as samples from a weighted normal distribution
weights are inverse variance weights based on estimates of the error in Ln(k) for each individual reaction
"""
recipe, rxns, Tref, fmax, label, ranks = rr
- n = len(rxns)
for i, rxn in enumerate(rxns):
rxn.rank = ranks[i]
rxns = np.array(rxns)
- data_mean = np.mean(np.log([r.kinetics.get_rate_coefficient(Tref) for r in rxns]))
+ rs = np.array([r for r in rxns if type(r.kinetics) != KineticsModel])
+ n = len(rs)
+ if n > 0 and isinstance(rs[0].kinetics, Marcus):
+ kin = average_kinetics([r.kinetics for r in rs])
+ return kin
+ data_mean = np.mean(np.log([r.kinetics.get_rate_coefficient(Tref) for r in rs]))
if n > 0:
- kin = ArrheniusBM().fit_to_reactions(rxns, recipe=recipe)
+ if isinstance(rs[0].kinetics, Arrhenius):
+ arr = ArrheniusBM
+ else:
+ arr = ArrheniusChargeTransferBM
+ if n > 1:
+ kin = arr().fit_to_reactions(rs, recipe=recipe)
+ if n == 1 or kin.E0.value_si < 0.0:
+ kin = average_kinetics([r.kinetics for r in rs])
+ #kin.comment = "Only one reaction or Arrhenius BM fit bad. Instead averaged from {} reactions.".format(n)
+ if n == 1:
+ kin.uncertainty = RateUncertainty(mu=0.0, var=(np.log(fmax) / 2.0) ** 2, N=1, Tref=Tref, data_mean=data_mean, correlation=label)
+ else:
+ dlnks = np.array([
+ np.log(
+ average_kinetics([r.kinetics for r in rs[list(set(range(len(rs))) - {i})]]).get_rate_coefficient(T=Tref) / rxn.get_rate_coefficient(T=Tref)
+ ) for i, rxn in enumerate(rs)
+ ]) # 1) fit to set of reactions without the current reaction (k) 2) compute log(kfit/kactual) at Tref
+ varis = (np.array([rank_accuracy_map[rxn.rank].value_si for rxn in rs]) / (2.0 * 8.314 * Tref)) ** 2
+ # weighted average calculations
+ ws = 1.0 / varis
+ V1 = ws.sum()
+ V2 = (ws ** 2).sum()
+ mu = np.dot(ws, dlnks) / V1
+ s = np.sqrt(np.dot(ws, (dlnks - mu) ** 2) / (V1 - V2 / V1))
+ kin.uncertainty = RateUncertainty(mu=mu, var=s ** 2, N=n, Tref=Tref, data_mean=data_mean, correlation=label)
+ else:
+ if n == 1:
+ kin.uncertainty = RateUncertainty(mu=0.0, var=(np.log(fmax) / 2.0) ** 2, N=1, Tref=Tref, data_mean=data_mean, correlation=label)
+ else:
+ if isinstance(rs[0].kinetics, Arrhenius):
+ dlnks = np.array([
+ np.log(
+ arr().fit_to_reactions(rs[list(set(range(len(rs))) - {i})], recipe=recipe)
+ .to_arrhenius(rxn.get_enthalpy_of_reaction(Tref))
+ .get_rate_coefficient(T=Tref) / rxn.get_rate_coefficient(T=Tref)
+ ) for i, rxn in enumerate(rs)
+ ]) # 1) fit to set of reactions without the current reaction (k) 2) compute log(kfit/kactual) at Tref
+ else:
+ dlnks = np.array([
+ np.log(
+ arr().fit_to_reactions(rs[list(set(range(len(rs))) - {i})], recipe=recipe)
+ .to_arrhenius_charge_transfer(rxn.get_enthalpy_of_reaction(Tref))
+ .get_rate_coefficient(T=Tref) / rxn.get_rate_coefficient(T=Tref)
+ ) for i, rxn in enumerate(rs)
+ ]) # 1) fit to set of reactions without the current reaction (k) 2) compute log(kfit/kactual) at Tref
+ varis = (np.array([rank_accuracy_map[rxn.rank].value_si for rxn in rs]) / (2.0 * 8.314 * Tref)) ** 2
+ # weighted average calculations
+ ws = 1.0 / varis
+ V1 = ws.sum()
+ V2 = (ws ** 2).sum()
+ mu = np.dot(ws, dlnks) / V1
+ s = np.sqrt(np.dot(ws, (dlnks - mu) ** 2) / (V1 - V2 / V1))
+ kin.uncertainty = RateUncertainty(mu=mu, var=s ** 2, N=n, Tref=Tref, data_mean=data_mean, correlation=label)
+
+ #site solute parameters
+ site_datas = [get_site_solute_data(rxn) for rxn in rxns]
+ site_datas = [sdata for sdata in site_datas if sdata is not None]
+ if len(site_datas) > 0:
+ site_data = SoluteTSData()
+ for sdata in site_datas:
+ site_data += sdata
+ site_data = site_data * (1.0/len(site_datas))
+ kin.solute = site_data
+ return kin
+ else:
+ return None
+
+ if isinstance(rs[0].kinetics, Arrhenius):
+ arr = ArrheniusBM
+ else:
+ arr = ArrheniusChargeTransferBM
+ if n > 1:
+ kin = arr().fit_to_reactions(rs, recipe=recipe)
+ if n == 1 or kin.E0.value_si < 0.0:
+ # still run it through the averaging function when n=1 to standardize the units and run checks
+ kin = average_kinetics([r.kinetics for r in rs])
if n == 1:
kin.uncertainty = RateUncertainty(mu=0.0, var=(np.log(fmax) / 2.0) ** 2, N=1, Tref=Tref, data_mean=data_mean, correlation=label)
+ kin.comment = f"Only one reaction rate: {rs[0]!s}"
else:
+ kin.comment = f"Blowers-Masel fit was bad (E0<0) so instead averaged from {n} reactions."
dlnks = np.array([
np.log(
- ArrheniusBM().fit_to_reactions(rxns[list(set(range(len(rxns))) - {i})], recipe=recipe)
- .to_arrhenius(rxn.get_enthalpy_of_reaction(Tref))
- .get_rate_coefficient(T=Tref) / rxn.get_rate_coefficient(T=Tref)
- ) for i, rxn in enumerate(rxns)
- ]) # 1) fit to set of reactions without the current reaction (k) 2) compute log(kfit/kactual) at Tref
- varis = (np.array([rank_accuracy_map[rxn.rank].value_si for rxn in rxns]) / (2.0 * 8.314 * Tref)) ** 2
+ average_kinetics([r.kinetics for r in rs[list(set(range(len(rs))) - {i})]]).get_rate_coefficient(T=Tref) / rxn.get_rate_coefficient(T=Tref)
+ ) for i, rxn in enumerate(rs)
+ ]) # 1) fit to set of reactions without the current reaction (k) 2) compute log(kfit/kactual) at Tref
+ varis = (np.array([rank_accuracy_map[rxn.rank].value_si for rxn in rs]) / (2.0 * 8.314 * Tref)) ** 2
# weighted average calculations
ws = 1.0 / varis
V1 = ws.sum()
@@ -4405,10 +4686,42 @@ def _make_rule(rr):
mu = np.dot(ws, dlnks) / V1
s = np.sqrt(np.dot(ws, (dlnks - mu) ** 2) / (V1 - V2 / V1))
kin.uncertainty = RateUncertainty(mu=mu, var=s ** 2, N=n, Tref=Tref, data_mean=data_mean, correlation=label)
- return kin
- else:
- return None
-
+ else: # Blowers-Masel fit was good
+ if isinstance(rs[0].kinetics, Arrhenius):
+ dlnks = np.array([
+ np.log(
+ arr().fit_to_reactions(rs[list(set(range(len(rs))) - {i})], recipe=recipe)
+ .to_arrhenius(rxn.get_enthalpy_of_reaction(298.))
+ .get_rate_coefficient(T=Tref) / rxn.get_rate_coefficient(T=Tref)
+ ) for i, rxn in enumerate(rs)
+ ]) # 1) fit to set of reactions without the current reaction (k) 2) compute log(kfit/kactual) at Tref
+ else: # SurfaceChargeTransfer or ArrheniusChargeTransfer
+ dlnks = np.array([
+ np.log(
+ arr().fit_to_reactions(rs[list(set(range(len(rs))) - {i})], recipe=recipe)
+ .to_arrhenius_charge_transfer(rxn.get_enthalpy_of_reaction(298.))
+ .get_rate_coefficient(T=Tref) / rxn.get_rate_coefficient(T=Tref)
+ ) for i, rxn in enumerate(rs)
+ ]) # 1) fit to set of reactions without the current reaction (k) 2) compute log(kfit/kactual) at Tref
+ varis = (np.array([rank_accuracy_map[rxn.rank].value_si for rxn in rs]) / (2.0 * 8.314 * Tref)) ** 2
+ # weighted average calculations
+ ws = 1.0 / varis
+ V1 = ws.sum()
+ V2 = (ws ** 2).sum()
+ mu = np.dot(ws, dlnks) / V1
+ s = np.sqrt(np.dot(ws, (dlnks - mu) ** 2) / (V1 - V2 / V1))
+ kin.uncertainty = RateUncertainty(mu=mu, var=s ** 2, N=n, Tref=Tref, data_mean=data_mean, correlation=label)
+
+ #site solute parameters
+ site_datas = [get_site_solute_data(rxn) for rxn in rxns]
+ site_datas = [sdata for sdata in site_datas if sdata is not None]
+ if len(site_datas) > 0:
+ site_data = SoluteTSData()
+ for sdata in site_datas:
+ site_data += sdata
+ site_data = site_data * (1.0/len(site_datas))
+ kin.solute = site_data
+ return kin
def _spawn_tree_process(family, template_rxn_map, obj, T, nprocs, depth, min_splitable_entry_num, min_rxns_to_spawn, extension_iter_max, extension_iter_item_cap):
parent_conn, child_conn = mp.Pipe()
@@ -4433,7 +4746,7 @@ def _child_make_tree_nodes(family, child_conn, template_rxn_map, obj, T, nprocs,
family.groups.entries[root_label].parent = None
family.make_tree_nodes(template_rxn_map=template_rxn_map, obj=obj, T=T, nprocs=nprocs, depth=depth + 1,
- min_splitable_entry_num=min_splitable_entry_num, min_rxns_to_spawn=min_rxns_to_spawn,
+ min_splitable_entry_num=min_splitable_entry_num, min_rxns_to_spawn=min_rxns_to_spawn,
extension_iter_max=extension_iter_max, extension_iter_item_cap=extension_iter_item_cap)
child_conn.send(list(family.groups.entries.values()))
@@ -4444,25 +4757,9 @@ def average_kinetics(kinetics_list):
Hence we average n, Ea, arithmetically, but we
average log A (geometric average)
"""
- logA = 0.0
- n = 0.0
- Ea = 0.0
- count = 0
- for kinetics in kinetics_list:
- count += 1
- logA += np.log10(kinetics.A.value_si)
- n += kinetics.n.value_si
- Ea += kinetics.Ea.value_si
-
- logA /= count
- n /= count
- Ea /= count
-
- ## The above could be replaced with something like:
- # logA, n, Ea = np.mean([[np.log10(k.A.value_si),
- # k.n.value_si,
- # k.Ea.value_si] for k in kinetics_list], axis=1)
-
+ if type(kinetics_list[0]) not in [Arrhenius,SurfaceChargeTransfer,ArrheniusChargeTransfer,Marcus]:
+ raise Exception('Invalid kinetics type {0!r} for {1!r}.'.format(type(kinetics), self))
+
Aunits = kinetics_list[0].A.units
if Aunits in {'cm^3/(mol*s)', 'cm^3/(molecule*s)', 'm^3/(molecule*s)'}:
Aunits = 'm^3/(mol*s)'
@@ -4481,17 +4778,122 @@ def average_kinetics(kinetics_list):
# surface: sticking coefficient
pass
else:
- raise Exception(f'Invalid units {Aunits} for averaging kinetics.')
+ raise Exception('Invalid units {0} for averaging kinetics.'.format(Aunits))
+
+ logA = 0.0
+ n = 0.0
+ Ea = 0.0
+ alpha = 0.5
+ lmbd_i_coefs = np.zeros(4)
+ beta = 0.0
+ wr = 0.0
+ wp = 0.0
+ electrons = None
+ if isinstance(kinetics_list[0], SurfaceChargeTransfer) or isinstance(kinetics_list[0], ArrheniusChargeTransfer):
+ if electrons is None:
+ electrons = kinetics_list[0].electrons.value_si
+ assert all(np.abs(k.V0.value_si) < 0.0001 for k in kinetics_list), [k.V0.value_si for k in kinetics_list]
+ assert all(np.abs(k.alpha.value_si - 0.5) < 0.001 for k in kinetics_list), [k.alpha for k in kinetics_list]
+ V0 = 0.0
+ count = 0
+ for kinetics in kinetics_list:
+ count += 1
+ logA += np.log10(kinetics.A.value_si)
+ n += kinetics.n.value_si
+ if hasattr(kinetics,"Ea"):
+ Ea += kinetics.Ea.value_si
+ if hasattr(kinetics,"lmbd_i_coefs"):
+ lmbd_i_coefs += kinetics.lmbd_i_coefs.value_si
+ beta += kinetics.beta.value_si
+ wr += kinetics.wr.value_si
+ wp += kinetics.wp.value_si
- if type(kinetics) not in [Arrhenius,]:
- raise Exception(f'Invalid kinetics type {type(kinetics)!r} for {self!r}.')
+ logA /= count
+ n /= count
+ Ea /= count
+ lmbd_i_coefs /= count
+ beta /= count
+ wr /= count
+ wp /= count
- if False:
- pass
+ if isinstance(kinetics, Marcus):
+ averaged_kinetics = Marcus(
+ A=(10 ** logA, Aunits),
+ n=n,
+ lmbd_i_coefs=lmbd_i_coefs,
+ beta=(beta,"1/m"),
+ wr=(wr * 0.001, "kJ/mol"),
+ wp=(wp * 0.001, "kJ/mol"),
+ comment="Averaged from {} reactions.".format(len(kinetics_list)),
+ )
+ elif isinstance(kinetics, SurfaceChargeTransfer):
+ averaged_kinetics = SurfaceChargeTransfer(
+ A=(10 ** logA, Aunits),
+ n=n,
+ electrons=electrons,
+ alpha=alpha,
+ V0=(V0,'V'),
+ Ea=(Ea * 0.001, "kJ/mol"),
+ )
+ elif isinstance(kinetics, ArrheniusChargeTransfer):
+ averaged_kinetics = ArrheniusChargeTransfer(
+ A=(10 ** logA, Aunits),
+ n=n,
+ electrons=electrons,
+ alpha=alpha,
+ V0=(V0,'V'),
+ Ea=(Ea * 0.001, "kJ/mol"),
+ )
else:
averaged_kinetics = Arrhenius(
A=(10 ** logA, Aunits),
n=n,
Ea=(Ea * 0.001, "kJ/mol"),
+ comment=f"Averaged from {len(kinetics_list)} rate expressions.",
)
return averaged_kinetics
+
+def get_site_solute_data(rxn):
+ """
+ apply kinetic solvent correction in this case the parameters are dGTSsite instead of GTS
+ """
+ from rmgpy.data.rmg import get_db
+ solvation_database = get_db('solvation')
+ ts_data = rxn.kinetics.solute
+ if ts_data:
+ site_data = to_soluteTSdata(ts_data,reactants=rxn.reactants)
+
+ #compute x from gas phase
+ GR = 0.0
+ GP = 0.0
+
+ for reactant in rxn.reactants:
+ try:
+ GR += reactant.thermo.get_free_energy(298.0)
+ except Exception:
+ logging.error("Problem with reactant {!r} in reaction {!s}".format(reactant, rxn))
+ raise
+ for product in rxn.products:
+ try:
+ GP += product.thermo.get_free_energy(298.0)
+ except Exception:
+ logging.error("Problem with product {!r} in reaction {!s}".format(reactant, rxn))
+ raise
+
+ dGrxn = GP-GR
+ if dGrxn > 0:
+ x = 1.0
+ else:
+ x = 0.0
+
+ for spc in rxn.reactants:
+ spc_solute_data = to_soluteTSdata(solvation_database.get_solute_data(spc.copy(deep=True)))
+ site_data -= spc_solute_data*(1.0-x)
+
+ for spc in rxn.products:
+ spc_solute_data = to_soluteTSdata(solvation_database.get_solute_data(spc.copy(deep=True)))
+ site_data -= spc_solute_data*x
+
+ return site_data
+ else:
+ return None
diff --git a/rmgpy/data/kinetics/library.py b/rmgpy/data/kinetics/library.py
index 747c37d746..4298084a8c 100644
--- a/rmgpy/data/kinetics/library.py
+++ b/rmgpy/data/kinetics/library.py
@@ -43,12 +43,13 @@
from rmgpy.data.kinetics.common import save_entry
from rmgpy.data.kinetics.family import TemplateReaction
from rmgpy.kinetics import Arrhenius, ThirdBody, Lindemann, Troe, \
- PDepArrhenius, MultiArrhenius, MultiPDepArrhenius, Chebyshev
+ PDepArrhenius, MultiArrhenius, MultiPDepArrhenius, Chebyshev, KineticsModel, Marcus
from rmgpy.kinetics.surface import StickingCoefficient
from rmgpy.molecule import Molecule
from rmgpy.reaction import Reaction
from rmgpy.species import Species
-
+from rmgpy.data.solvation import to_soluteTSdata
+import rmgpy.constants as constants
################################################################################
@@ -219,6 +220,52 @@ def get_sticking_coefficient(self, T):
return False
+ def apply_solvent_correction(self, solvent):
+ """
+ apply kinetic solvent correction
+ """
+ from rmgpy.data.rmg import get_db
+ solvation_database = get_db('solvation')
+ solvent_data = solvation_database.get_solvent_data(solvent)
+
+ if isinstance(self.kinetics, Marcus):
+ solvent_struct = solvation_database.get_solvent_structure(solvent)
+ solv_solute_data = solvation_database.get_solute_data(solvent_struct.copy(deep=True))
+ Rsolv = math.pow((75 * solv_solute_data.V / constants.pi / constants.Na) * (1.0 / 3.0)) / 100
+ Rtot = 0.0
+ Ner = 0
+ Nep = 0
+ for spc in self.reactants:
+ spc_solute_data = solvation_database.get_solute_data(spc.copy(deep=True))
+ spc_solute_data.set_mcgowan_volume(spc)
+ R = math.pow((75 * spc_solute_data.V / constants.pi / constants.Na),
+ (1.0 / 3.0)) / 100
+ Rtot += R
+ Ner += spc.get_net_charge()
+ for spc in self.products:
+ Nep += spc.get_net_charge()
+
+ Rtot += Rsolv #radius of reactants plus first solvation shell
+ self.lmbd_o = constants.Na*(constants.e*(Nep-Ner))**2/(8.0*constants.pi*constants.epsilon_0*Rtot)*(1.0/solvent_data.n**2 - 1.0/solvent_data.eps)
+ return
+
+ solute_data = to_soluteTSdata(self.kinetics.solute,reactants=self.reactants)
+ dGTS,dHTS = solute_data.calculate_corrections(solvent_data)
+ dSTS = (dHTS - dGTS)/298.0
+
+ dHR = 0.0
+ dSR = 0.0
+ for spc in self.reactants:
+ spc_solute_data = solvation_database.get_solute_data(spc)
+ spc_correction = solvation_database.get_solvation_correction(spc_solute_data, solvent_data)
+ dHR += spc_correction.enthalpy
+ dSR += spc_correction.entropy
+
+ dH = dHTS-dHR
+ dA = np.exp((dSTS-dSR)/constants.R)
+ self.kinetics.Ea.value_si += dH
+ self.kinetics.A.value_si *= dA
+ self.kinetics.comment += "solvation correction raised barrier by {0} kcal/mol and prefactor by factor of {1}".format(dH/4184.0,dA)
################################################################################
diff --git a/rmgpy/data/kinetics/rules.py b/rmgpy/data/kinetics/rules.py
index 6f20e5460a..d20a4a6ce4 100644
--- a/rmgpy/data/kinetics/rules.py
+++ b/rmgpy/data/kinetics/rules.py
@@ -44,7 +44,8 @@
from rmgpy.data.base import Database, Entry, get_all_combinations
from rmgpy.data.kinetics.common import save_entry
from rmgpy.exceptions import KineticsError, DatabaseError
-from rmgpy.kinetics import ArrheniusEP, Arrhenius, StickingCoefficientBEP, SurfaceArrheniusBEP
+from rmgpy.kinetics import ArrheniusEP, Arrhenius, StickingCoefficientBEP, SurfaceArrheniusBEP, \
+ SurfaceChargeTransfer, SurfaceChargeTransferBEP, Marcus
from rmgpy.quantity import Quantity, ScalarQuantity
from rmgpy.reaction import Reaction
@@ -282,12 +283,23 @@ def _get_average_kinetics(self, kinetics_list):
n = 0.0
E0 = 0.0
alpha = 0.0
- count = len(kinetics_list)
+ electrons = None
+ V0 = None
+ count = 0
for kinetics in kinetics_list:
+ if isinstance(kinetics, SurfaceChargeTransfer):
+ continue
+ count += 1
logA += math.log10(kinetics.A.value_si)
n += kinetics.n.value_si
alpha += kinetics.alpha.value_si
E0 += kinetics.E0.value_si
+ if isinstance(kinetics, SurfaceChargeTransferBEP):
+ if electrons is None:
+ electrons = kinetics.electrons.value_si
+ if V0 is None:
+ V0 = kinetics.V0.value_si
+
logA /= count
n /= count
alpha /= count
@@ -312,15 +324,25 @@ def _get_average_kinetics(self, kinetics_list):
else:
raise Exception('Invalid units {0} for averaging kinetics.'.format(Aunits))
- if type(kinetics) not in [ArrheniusEP, SurfaceArrheniusBEP, StickingCoefficientBEP]:
+ if type(kinetics) not in [ArrheniusEP, SurfaceArrheniusBEP, StickingCoefficientBEP, SurfaceChargeTransferBEP]:
raise Exception('Invalid kinetics type {0!r} for {1!r}.'.format(type(kinetics), self))
- averaged_kinetics = type(kinetics)(
- A=(10 ** logA, Aunits),
- n=n,
- alpha=alpha,
- E0=(E0 * 0.001, "kJ/mol"),
- )
+ if isinstance(kinetics, SurfaceChargeTransferBEP):
+ averaged_kinetics = SurfaceChargeTransferBEP(
+ A=(10 ** logA, Aunits),
+ n=n,
+ electrons=electrons,
+ alpha=alpha,
+ V0=(V0,'V'),
+ E0=(E0 * 0.001, "kJ/mol"),
+ )
+ else:
+ averaged_kinetics = type(kinetics)(
+ A=(10 ** logA, Aunits),
+ n=n,
+ alpha=alpha,
+ E0=(E0 * 0.001, "kJ/mol"),
+ )
return averaged_kinetics
def estimate_kinetics(self, template, degeneracy=1):
diff --git a/rmgpy/data/rmg.py b/rmgpy/data/rmg.py
index 9dd230c51a..1bf8ba4eea 100644
--- a/rmgpy/data/rmg.py
+++ b/rmgpy/data/rmg.py
@@ -80,6 +80,7 @@ def load(self,
kinetics_families=None,
kinetics_depositories=None,
statmech_libraries=None,
+ adsorption_groups='adsorptionPt111',
depository=True,
solvation=True,
surface=True, # on by default, because solvation is also on by default
@@ -109,16 +110,17 @@ def load(self,
self.load_solvation(os.path.join(path, 'solvation'))
if surface:
- self.load_thermo(os.path.join(path, 'thermo'), thermo_libraries, depository, surface)
+ self.load_thermo(os.path.join(path, 'thermo'), thermo_libraries, depository, surface, adsorption_groups)
- def load_thermo(self, path, thermo_libraries=None, depository=True, surface=False):
+ def load_thermo(self, path, thermo_libraries=None, depository=True, surface=False, adsorption_groups='adsorptionPt111'):
"""
Load the RMG thermo database from the given `path` on disk, where
`path` points to the top-level folder of the RMG thermo database.
"""
self.thermo = ThermoDatabase()
+ self.thermo.adsorption_groups = adsorption_groups
self.thermo.load(path, thermo_libraries, depository, surface)
def load_transport(self, path, transport_libraries=None):
diff --git a/rmgpy/data/solvation.py b/rmgpy/data/solvation.py
index 45a133de01..b95f708f02 100644
--- a/rmgpy/data/solvation.py
+++ b/rmgpy/data/solvation.py
@@ -138,6 +138,7 @@ def save_entry(f, entry):
f.write(' alpha = {0!r},\n'.format(entry.data.alpha))
f.write(' beta = {0!r},\n'.format(entry.data.beta))
f.write(' eps = {0!r},\n'.format(entry.data.eps))
+ f.write(' n = {0!r},\n'.format(entry.data.n))
f.write(' name_in_coolprop = "{0!s}",\n'.format(entry.data.name_in_coolprop))
f.write(' ),\n')
elif entry.data is None:
@@ -361,7 +362,7 @@ class SolventData(object):
def __init__(self, s_h=None, b_h=None, e_h=None, l_h=None, a_h=None,
c_h=None, s_g=None, b_g=None, e_g=None, l_g=None, a_g=None, c_g=None, A=None, B=None,
- C=None, D=None, E=None, alpha=None, beta=None, eps=None, name_in_coolprop=None):
+ C=None, D=None, E=None, alpha=None, beta=None, eps=None, name_in_coolprop=None, n=None):
self.s_h = s_h
self.b_h = b_h
self.e_h = e_h
@@ -385,6 +386,8 @@ def __init__(self, s_h=None, b_h=None, e_h=None, l_h=None, a_h=None,
self.beta = beta
# This is the dielectric constant
self.eps = eps
+ #This is the index of refraction
+ self.n = n
# This corresponds to the solvent's name in CoolProp. CoolProp is an external package used for
# fluid property calculation. If the solvent is not available in CoolProp, this is set to None
self.name_in_coolprop = name_in_coolprop
@@ -470,7 +473,7 @@ class SoluteData(object):
"""
# Set class variable with McGowan volumes
mcgowan_volumes = {
- 1: 8.71, 2: 6.75,
+ 1: 8.71, 2: 6.75, 3: 22.23,
6: 16.35, 7: 14.39, 8: 12.43, 9: 10.47, 10: 8.51,
14: 26.83, 15: 24.87, 16: 22.91, 17: 20.95, 18: 18.99,
35: 26.21, 53: 34.53,
@@ -491,7 +494,7 @@ def __repr__(self):
def get_stokes_diffusivity(self, T, solvent_viscosity):
"""
- Get diffusivity of solute using the Stokes-Einstein sphere relation.
+ Get diffusivity of solute using the Stokes-Einstein sphere relation.
Radius is found from the McGowan volume.
solvent_viscosity should be given in kg/s/m which equals Pa.s
(water is about 9e-4 Pa.s at 25C, propanol is 2e-3 Pa.s)
@@ -510,7 +513,7 @@ def set_mcgowan_volume(self, species):
doi: 10.1007/BF02311772
Also see Table 1 in Zhao et al., J. Chem. Inf. Comput. Sci. Vol. 43, p.1848. 2003
doi: 10.1021/ci0341114
-
+
"V is scaled to have similar values to the other
descriptors by division by 100 and has units of (cm3mol−1/100)."
the contibutions in this function are in cm3/mol, and the division by 100 is done at the very end.
@@ -532,6 +535,299 @@ def set_mcgowan_volume(self, species):
self.V = Vtot / 100 # division by 100 to get units correct.
+class SoluteTSData(object):
+ """
+ Stores Abraham parameters to characterize a solute
+ """
+ # Set class variable with McGowan volumes
+ mcgowan_volumes = {
+ 1: 8.71, 2: 6.75, 3: 22.23,
+ 6: 16.35, 7: 14.39, 8: 12.43, 9: 10.47, 10: 8.51,
+ 14: 26.83, 15: 24.87, 16: 22.91, 17: 20.95, 18: 18.99,
+ 35: 26.21, 53: 34.53,
+ }
+
+ def __init__(self, Sg_g=0.0, Bg_g=0.0, Eg_g=0.0, Lg_g=0.0, Ag_g=0.0, Cg_g=0.0, Sh_g=0.0, Bh_g=0.0, Eh_g=0.0, Lh_g=0.0, Ah_g=0.0, Ch_g=0.0,
+ K_g=0.0, Sg_h=0.0, Bg_h=0.0, Eg_h=0.0, Lg_h=0.0, Ag_h=0.0, Cg_h=0.0, Sh_h=0.0, Bh_h=0.0, Eh_h=0.0, Lh_h=0.0, Ah_h=0.0, Ch_h=0.0, K_h=0.0, comment=None):
+ """
+ Xi_j correction is associated with calculating j (Gibbs or enthalpy) using solvent parameters for i (abraharm=g, mintz=h)
+ """
+ self.Sg_g = Sg_g
+ self.Bg_g = Bg_g
+ self.Eg_g = Eg_g
+ self.Lg_g = Lg_g
+ self.Ag_g = Ag_g
+ self.Cg_g = Cg_g
+ self.Sh_g = Sh_g
+ self.Bh_g = Bh_g
+ self.Eh_g = Eh_g
+ self.Lh_g = Lh_g
+ self.Ah_g = Ah_g
+ self.Ch_g = Ch_g
+ self.K_g = K_g
+
+ self.Sg_h = Sg_h
+ self.Bg_h = Bg_h
+ self.Eg_h = Eg_h
+ self.Lg_h = Lg_h
+ self.Ag_h = Ag_h
+ self.Cg_h = Cg_h
+ self.Sh_h = Sh_h
+ self.Bh_h = Bh_h
+ self.Eh_h = Eh_h
+ self.Lh_h = Lh_h
+ self.Ah_h = Ah_h
+ self.Ch_h = Ch_h
+ self.K_h = K_h
+
+ self.comment = comment
+
+ def __repr__(self):
+ return "SoluteTSData(Sg_g={0},Bg_g={1},Eg_g={2},Lg_g={3},Ag_g={4},Cg_g={5},Sh_g={6},Bh_g={7},Eh_g={8},Lh_g={9},Ah_g={10},Ch_g={11},K_g={12},Sg_h={13},Bg_h={14},Eg_h={15},Lg_h={16},Ag_h={17},Cg_h={18},Sh_h={19},Bh_h={20},Eh_h={21},Lh_h={22},Ah_h={23},Ch_h={24},K_h={25},comment={26!r})".format(
+ self.Sg_g,
+ self.Bg_g,
+ self.Eg_g,
+ self.Lg_g,
+ self.Ag_g,
+ self.Cg_g,
+ self.Sh_g,
+ self.Bh_g,
+ self.Eh_g,
+ self.Lh_g,
+ self.Ah_g,
+ self.Ch_g,
+ self.K_g,
+ self.Sg_h,
+ self.Bg_h,
+ self.Eg_h,
+ self.Lg_h,
+ self.Ag_h,
+ self.Cg_h,
+ self.Sh_h,
+ self.Bh_h,
+ self.Eh_h,
+ self.Lh_h,
+ self.Ah_h,
+ self.Ch_h,
+ self.K_h, self.comment)
+
+ def __add__(self,sol):
+ return SoluteTSData(
+ Sg_g = self.Sg_g+sol.Sg_g,
+ Bg_g = self.Bg_g+sol.Bg_g,
+ Eg_g = self.Eg_g+sol.Eg_g,
+ Lg_g = self.Lg_g+sol.Lg_g,
+ Ag_g = self.Ag_g+sol.Ag_g,
+ Cg_g = self.Cg_g+sol.Cg_g,
+ Sh_g = self.Sh_g+sol.Sh_g,
+ Bh_g = self.Bh_g+sol.Bh_g,
+ Eh_g = self.Eh_g+sol.Eh_g,
+ Lh_g = self.Lh_g+sol.Lh_g,
+ Ah_g = self.Ah_g+sol.Ah_g,
+ Ch_g = self.Ch_g+sol.Ch_g,
+ K_g = self.K_g+sol.K_g,
+ Sg_h = self.Sg_h+sol.Sg_h,
+ Bg_h = self.Bg_h+sol.Bg_h,
+ Eg_h = self.Eg_h+sol.Eg_h,
+ Lg_h = self.Lg_h+sol.Lg_h,
+ Ag_h = self.Ag_h+sol.Ag_h,
+ Cg_h = self.Cg_h+sol.Cg_h,
+ Sh_h = self.Sh_h+sol.Sh_h,
+ Bh_h = self.Bh_h+sol.Bh_h,
+ Eh_h = self.Eh_h+sol.Eh_h,
+ Lh_h = self.Lh_h+sol.Lh_h,
+ Ah_h = self.Ah_h+sol.Ah_h,
+ Ch_h = self.Ch_h+sol.Ch_h,
+ K_h = self.K_h+sol.K_h,
+ )
+
+ def __sub__(self,sol):
+ return SoluteTSData(
+ Sg_g = self.Sg_g-sol.Sg_g,
+ Bg_g = self.Bg_g-sol.Bg_g,
+ Eg_g = self.Eg_g-sol.Eg_g,
+ Lg_g = self.Lg_g-sol.Lg_g,
+ Ag_g = self.Ag_g-sol.Ag_g,
+ Cg_g = self.Cg_g-sol.Cg_g,
+ Sh_g = self.Sh_g-sol.Sh_g,
+ Bh_g = self.Bh_g-sol.Bh_g,
+ Eh_g = self.Eh_g-sol.Eh_g,
+ Lh_g = self.Lh_g-sol.Lh_g,
+ Ah_g = self.Ah_g-sol.Ah_g,
+ Ch_g = self.Ch_g-sol.Ch_g,
+ K_g = self.K_g-sol.K_g,
+ Sg_h = self.Sg_h-sol.Sg_h,
+ Bg_h = self.Bg_h-sol.Bg_h,
+ Eg_h = self.Eg_h-sol.Eg_h,
+ Lg_h = self.Lg_h-sol.Lg_h,
+ Ag_h = self.Ag_h-sol.Ag_h,
+ Cg_h = self.Cg_h-sol.Cg_h,
+ Sh_h = self.Sh_h-sol.Sh_h,
+ Bh_h = self.Bh_h-sol.Bh_h,
+ Eh_h = self.Eh_h-sol.Eh_h,
+ Lh_h = self.Lh_h-sol.Lh_h,
+ Ah_h = self.Ah_h-sol.Ah_h,
+ Ch_h = self.Ch_h-sol.Ch_h,
+ K_h = self.K_h-sol.K_h,
+ )
+
+ def __eq__(self,sol):
+ if self.Sg_g != sol.Sg_g:
+ return False
+ elif self.Bg_g != sol.Bg_g:
+ return False
+ elif self.Eg_g != sol.Eg_g:
+ return False
+ elif self.Lg_g != sol.Lg_g:
+ return False
+ elif self.Ag_g != sol.Ag_g:
+ return False
+ elif self.Cg_g != sol.Cg_g:
+ return False
+ elif self.Sh_g != sol.Sh_g:
+ return False
+ elif self.Bh_g != sol.Bh_g:
+ return False
+ elif self.Eh_g != sol.Eh_g:
+ return False
+ elif self.Lh_g != sol.Lh_g:
+ return False
+ elif self.Ah_g != sol.Ah_g:
+ return False
+ elif self.Ch_g != sol.Ch_g:
+ return False
+ elif self.K_g != sol.K_g:
+ return False
+ elif self.Sg_h != sol.Sg_h:
+ return False
+ elif self.Bg_h != sol.Bg_h:
+ return False
+ elif self.Eg_h != sol.Eg_h:
+ return False
+ elif self.Lg_h != sol.Lg_h:
+ return False
+ elif self.Ag_h != sol.Ag_h:
+ return False
+ elif self.Cg_h != sol.Cg_h:
+ return False
+ elif self.Sh_h != sol.Sh_h:
+ return False
+ elif self.Bh_h != sol.Bh_h:
+ return False
+ elif self.Eh_h != sol.Eh_h:
+ return False
+ elif self.Lh_h != sol.Lh_h:
+ return False
+ elif self.Ah_h != sol.Ah_h:
+ return False
+ elif self.Ch_h != sol.Ch_h:
+ return False
+ elif self.K_h != sol.K_h:
+ return False
+ else:
+ return True
+
+ def __mul__(self,num):
+ return SoluteTSData(
+ Sg_g = self.Sg_g*num,
+ Bg_g = self.Bg_g*num,
+ Eg_g = self.Eg_g*num,
+ Lg_g = self.Lg_g*num,
+ Ag_g = self.Ag_g*num,
+ Cg_g = self.Cg_g*num,
+ Sh_g = self.Sh_g*num,
+ Bh_g = self.Bh_g*num,
+ Eh_g = self.Eh_g*num,
+ Lh_g = self.Lh_g*num,
+ Ah_g = self.Ah_g*num,
+ Ch_g = self.Ch_g*num,
+ K_g = self.K_g*num,
+ Sg_h = self.Sg_h*num,
+ Bg_h = self.Bg_h*num,
+ Eg_h = self.Eg_h*num,
+ Lg_h = self.Lg_h*num,
+ Ag_h = self.Ag_h*num,
+ Cg_h = self.Cg_h*num,
+ Sh_h = self.Sh_h*num,
+ Bh_h = self.Bh_h*num,
+ Eh_h = self.Eh_h*num,
+ Lh_h = self.Lh_h*num,
+ Ah_h = self.Ah_h*num,
+ Ch_h = self.Ch_h*num,
+ K_h = self.K_h*num,
+ )
+
+ def calculate_corrections(self,solv):
+ dG298 = 0.0
+ dG298 += -(np.log(10)*8.314*298.15)*(self.Sg_g*solv.s_g+self.Bg_g*solv.b_g+self.Eg_g*solv.e_g+self.Lg_g*solv.l_g+self.Ag_g*solv.a_g+self.Cg_g*solv.c_g+self.K_g)
+ dG298 += 1000.0*(self.Sh_g*solv.s_h+self.Bh_g*solv.b_h+self.Eh_g*solv.e_h+self.Lh_g*solv.l_h+self.Ah_g*solv.a_h+self.Ch_g*solv.c_h)
+ dH298 = 0.0
+ dH298 += -(np.log(10)*8.314*298.15)*(self.Sg_h*solv.s_g+self.Bg_h*solv.b_g+self.Eg_h*solv.e_g+self.Lg_h*solv.l_g+self.Ag_h*solv.a_g+self.Cg_h*solv.c_g+self.K_h)
+ dH298 += 1000.0*(self.Sh_h*solv.s_h+self.Bh_h*solv.b_h+self.Eh_h*solv.e_h+self.Lh_h*solv.l_h+self.Ah_h*solv.a_h+self.Ch_h*solv.c_h)
+ return dG298,dH298
+
+class SoluteTSDiffData(object):
+ """
+ Stores Abraham parameters to characterize a solute
+ """
+ # Set class variable with McGowan volumes
+ mcgowan_volumes = {
+ 1: 8.71, 2: 6.75, 3: 22.23,
+ 6: 16.35, 7: 14.39, 8: 12.43, 9: 10.47, 10: 8.51,
+ 14: 26.83, 15: 24.87, 16: 22.91, 17: 20.95, 18: 18.99,
+ 35: 26.21, 53: 34.53,
+ }
+
+ def __init__(self, S_g=None, B_g=None, E_g=None, L_g=None, A_g=None,
+ K_g=None, S_h=None, B_h=None, E_h=None, L_h=None, A_h=None, K_h=None, comment=None):
+ self.S_g = S_g
+ self.B_g = B_g
+ self.E_g = E_g
+ self.L_g = L_g
+ self.A_g = A_g
+ self.K_g = K_g
+ self.S_h = S_h
+ self.B_h = B_h
+ self.E_h = E_h
+ self.L_h = L_h
+ self.A_h = A_h
+ self.K_h = K_h
+
+ self.comment=comment
+
+ def __repr__(self):
+ return "SoluteTSDiffData(S_g={0},B_g={1},E_g={2},L_g={3},A_g={4},K_g={5},S_h={6},B_h={7},E_h={8},L_h={9},A_h={10},K_h={11},comment={12!r})".format(
+ self.S_g, self.B_g, self.E_g, self.L_g, self.A_g, self.K_g, self.S_h, self.B_h, self.E_h,
+ self.L_h, self.A_h, self.K_h, self.comment)
+
+def to_soluteTSdata(data,reactants=None):
+ if isinstance(data,SoluteTSData):
+ return data
+ elif isinstance(data,SoluteData):
+ return SoluteTSData(Sg_g=data.S,Bg_g=data.B,Eg_g=data.E,Lg_g=data.L,Ag_g=data.A,Cg_g=1.0,
+ Sh_h=data.S,Bh_h=data.B,Eh_h=data.E,Lh_h=data.L,Ah_h=data.A,Ch_h=1.0,comment=data.comment)
+ elif isinstance(data,SoluteTSDiffData):
+ from rmgpy.data.rmg import get_db
+ solvation_database = get_db('solvation')
+ react_data = [solvation_database.get_solute_data(spc.copy(deep=True)) for spc in reactants]
+ return SoluteTSData(Sg_g=data.S_g+sum([x.S for x in react_data]),
+ Bg_g=data.B_g+sum([x.B for x in react_data]),
+ Eg_g=data.E_g+sum([x.E for x in react_data]),
+ Lg_g=data.L_g+sum([x.L for x in react_data]),
+ Ag_g=data.A_g+sum([x.A for x in react_data]),
+ K_g=data.K_g,
+ Sg_h=data.S_h,
+ Bg_h=data.B_h,
+ Eg_h=data.E_h,
+ Lg_h=data.L_h,
+ Ag_h=data.A_h,
+ Sh_h=sum([x.S for x in react_data]),
+ Bh_h=sum([x.B for x in react_data]),
+ Eh_h=sum([x.E for x in react_data]),
+ Lh_h=sum([x.L for x in react_data]),
+ Ah_h=sum([x.A for x in react_data]),
+ K_h=data.K_h,comment=data.comment)
+
class DataCountGAV(object):
"""
A class for storing the number of data used to fit each solute parameter group value in the solute group additivity.
@@ -859,7 +1155,7 @@ def load(self, path, libraries=None, depository=True):
"""
Load the solvation database from the given `path` on disk, where `path`
points to the top-level folder of the solvation database.
-
+
Load the solvent and solute libraries, then the solute groups.
"""
@@ -1154,8 +1450,8 @@ def get_all_solute_data(self, species):
Return all possible sets of Abraham solute descriptors for a given
:class:`Species` object `species`. The hits from the library come
first, then the group additivity estimate. This method is useful
- for a generic search job. Right now, there should either be 1 or
- 2 sets of descriptors, depending on whether or not we have a
+ for a generic search job. Right now, there should either be 1 or
+ 2 sets of descriptors, depending on whether or not we have a
library entry.
"""
solute_data_list = []
@@ -1192,7 +1488,7 @@ def get_solute_data_from_groups(self, species):
:class:`Species` object `species` by estimation using the group
additivity method. If no group additivity values are loaded, a
:class:`DatabaseError` is raised.
-
+
It estimates the solute data for the first item in the species's
molecule list because it is the most stable resonance structure found
by gas-phase thermo estimate.
@@ -1311,6 +1607,7 @@ def estimate_radical_solute_data_via_hbi(self, molecule, stable_solute_data_esti
saturated_struct.remove_bond(bond)
saturated_struct.remove_atom(H)
atom.increment_radical()
+ saturated_struct.update_charge() # we need to update charges before updating lone pairs
saturated_struct.update()
try:
self._add_group_solute_data(solute_data, self.groups['radical'], saturated_struct, {'*': atom})
@@ -1921,7 +2218,7 @@ def calc_s(self, delG, delH):
return delS
def get_solvation_correction(self, solute_data, solvent_data):
- """
+ """
Given a solute_data and solvent_data object, calculates the enthalpy, entropy,
and Gibbs free energy of solvation at 298 K. Returns a SolvationCorrection
object
@@ -2132,7 +2429,7 @@ def get_Kfactor_parameters(self, delG298, delH298, delS298, solvent_name, T_tran
kfactor_parameters.T_transition = T_transition
return kfactor_parameters
-
+
def check_solvent_in_initial_species(self, rmg, solvent_structure):
"""
Given the instance of RMG class and the solvent_structure, it checks whether the solvent is listed as one
@@ -2150,7 +2447,7 @@ def check_solvent_in_initial_species(self, rmg, solvent_structure):
if not any([spec.is_solvent for spec in rmg.initial_species]):
if solvent_structure is not None:
logging.info('One of the initial species must be the solvent')
- raise ValueError('One of the initial species must be the solvent')
+ logging.warning("Solvent is not an initial species")
else:
logging.info('One of the initial species must be the solvent with the same string name')
- raise ValueError('One of the initial species must be the solvent with the same string name')
+ logging.warning("Solvent is not an initial species with the same string name")
diff --git a/rmgpy/data/thermo.py b/rmgpy/data/thermo.py
index 7816d7ed2f..34f9630dac 100644
--- a/rmgpy/data/thermo.py
+++ b/rmgpy/data/thermo.py
@@ -608,10 +608,12 @@ def load_entry(self, index, label, molecule, thermo, reference=None, referenceTy
Method for parsing entries in database files.
Note that these argument names are retained for backward compatibility.
"""
+ mol = Molecule().from_adjacency_list(molecule)
+ mol.update_atomtypes()
entry = Entry(
index=index,
label=label,
- item=Molecule().from_adjacency_list(molecule),
+ item=mol,
data=thermo,
reference=reference,
reference_type=referenceType,
@@ -666,6 +668,7 @@ def load_entry(self,
molecule = Molecule().from_adjacency_list(molecule)
except TypeError:
molecule = Fragment().from_adjacency_list(molecule)
+ molecule.update_atomtypes()
# Internal checks for adding entry to the thermo library
if label in list(self.entries.keys()):
@@ -854,6 +857,7 @@ def __init__(self):
self.libraries = {}
self.surface = {}
self.groups = {}
+ self.adsorption_groups = "adsorptionPt111"
self.library_order = []
self.local_context = {
'ThermoData': ThermoData,
@@ -989,7 +993,9 @@ def load_groups(self, path):
'longDistanceInteraction_cyclic',
'longDistanceInteraction_noncyclic',
'adsorptionPt111',
+ 'adsorptionLi'
]
+ # categories.append(self.adsorption_groups)
self.groups = {
category: ThermoGroups(label=category).load(os.path.join(path, category + '.py'),
self.local_context, self.global_context)
@@ -1287,7 +1293,9 @@ def get_thermo_data(self, species, metal_to_scale_to=None, training_set=None):
if species.contains_surface_site():
try:
thermo0 = self.get_thermo_data_for_surface_species(species)
- thermo0 = self.correct_binding_energy(thermo0, species, metal_to_scale_from="Pt111", metal_to_scale_to=metal_to_scale_to) # group adsorption values come from Pt111
+ metal_to_scale_from = self.adsorption_groups.split('adsorption')[-1]
+ if metal_to_scale_from != metal_to_scale_to:
+ thermo0 = self.correct_binding_energy(thermo0, species, metal_to_scale_from=metal_to_scale_from, metal_to_scale_to=metal_to_scale_to) # group adsorption values come from Pt111
return thermo0
except:
logging.error("Error attempting to get thermo for species %s with structure \n%s",
@@ -1481,10 +1489,14 @@ def correct_binding_energy(self, thermo, species, metal_to_scale_from=None, meta
'H': rmgpy.quantity.Energy(0.0, 'eV/molecule'),
'O': rmgpy.quantity.Energy(0.0, 'eV/molecule'),
'N': rmgpy.quantity.Energy(0.0, 'eV/molecule'),
+ 'F': rmgpy.quantity.Energy(0.0, 'eV/molecule'),
}
for element, delta_energy in delta_atomic_adsorption_energy.items():
- delta_energy.value_si = metal_to_scale_to_binding_energies[element].value_si - metal_to_scale_from_binding_energies[element].value_si
+ try:
+ delta_energy.value_si = metal_to_scale_to_binding_energies[element].value_si - metal_to_scale_from_binding_energies[element].value_si
+ except KeyError:
+ pass
if all(-0.01 < v.value_si < 0.01 for v in delta_atomic_adsorption_energy.values()):
return thermo
@@ -1495,8 +1507,8 @@ def correct_binding_energy(self, thermo, species, metal_to_scale_from=None, meta
for atom in molecule.atoms:
if atom.is_surface_site():
surface_sites.append(atom)
- normalized_bonds = {'C': 0., 'O': 0., 'N': 0., 'H': 0.}
- max_bond_order = {'C': 4., 'O': 2., 'N': 3., 'H': 1.}
+ normalized_bonds = {'C': 0., 'O': 0., 'N': 0., 'H': 0., 'F': 0., 'Li': 0.}
+ max_bond_order = {'C': 4., 'O': 2., 'N': 3., 'H': 1., 'F': 1, 'Li': 1.}
for site in surface_sites:
numbonds = len(site.bonds)
if numbonds == 0:
@@ -1526,11 +1538,14 @@ def correct_binding_energy(self, thermo, species, metal_to_scale_from=None, meta
# now edit the adsorptionThermo using LSR
comments = []
- for element in 'CHON':
- if normalized_bonds[element]:
- change_in_binding_energy = delta_atomic_adsorption_energy[element].value_si * normalized_bonds[element]
+ for element,bond in normalized_bonds.items():
+ if bond:
+ try:
+ change_in_binding_energy = delta_atomic_adsorption_energy[element].value_si * bond
+ except KeyError:
+ continue
thermo.H298.value_si += change_in_binding_energy
- comments.append(f'{normalized_bonds[element]:.2f}{element}')
+ comments.append(f'{bond:.2f}{element}')
thermo.comment += " Binding energy corrected by LSR ({}) from {}".format('+'.join(comments), metal_to_scale_from)
return thermo
@@ -1607,7 +1622,7 @@ def lowest_energy(species):
surface_sites = molecule.get_surface_sites()
try:
- self._add_adsorption_correction(adsorption_thermo, self.groups['adsorptionPt111'], molecule, surface_sites)
+ self._add_adsorption_correction(adsorption_thermo, self.groups[self.adsorption_groups], molecule, surface_sites)
except (KeyError, DatabaseError):
logging.error("Couldn't find in adsorption thermo database:")
logging.error(molecule)
@@ -2049,8 +2064,12 @@ def estimate_radical_thermo_via_hbi(self, molecule, stable_thermo_estimator):
"not {0}".format(thermo_data_sat))
thermo_data_sat = thermo_data_sat[0]
else:
- thermo_data_sat = stable_thermo_estimator(saturated_struct)
-
+ try:
+ thermo_data_sat = stable_thermo_estimator(saturated_struct)
+ except DatabaseError as e:
+ logging.error(f"Trouble finding thermo data for saturated structure {saturated_struct.to_adjacency_list()}"
+ f"when trying to evaluate radical {molecule.to_adjacency_list()} via HBI.")
+ raise e
if thermo_data_sat is None:
# We couldn't get thermo for the saturated species from libraries, ml, or qm
# However, if we were trying group additivity, this could be a problem
@@ -2555,9 +2574,10 @@ def _add_group_thermo_data(self, thermo_data, database, molecule, atom):
node = node0
while node is not None and node.data is None:
node = node.parent
- if node is None:
+ if node is None:
raise DatabaseError(f'Unable to determine thermo parameters for atom {atom} in molecule {molecule}: '
- f'no data for node {node0} or any of its ancestors in database {database.label}.')
+ f'no data for node {node0} or any of its ancestors in database {database.label}.\n' +
+ molecule.to_adjacency_list())
data = node.data
comment = node.label
diff --git a/rmgpy/kinetics/__init__.py b/rmgpy/kinetics/__init__.py
index 0816d655f7..b3f8eec12e 100644
--- a/rmgpy/kinetics/__init__.py
+++ b/rmgpy/kinetics/__init__.py
@@ -29,10 +29,12 @@
from rmgpy.kinetics.model import KineticsModel, PDepKineticsModel, TunnelingModel, \
get_rate_coefficient_units_from_reaction_order, get_reaction_order_from_rate_coefficient_units
-from rmgpy.kinetics.arrhenius import Arrhenius, ArrheniusEP, PDepArrhenius, MultiArrhenius, MultiPDepArrhenius, ArrheniusBM
+from rmgpy.kinetics.arrhenius import Arrhenius, ArrheniusEP, PDepArrhenius, MultiArrhenius, MultiPDepArrhenius, \
+ ArrheniusBM, ArrheniusChargeTransfer, ArrheniusChargeTransferBM, Marcus
from rmgpy.kinetics.chebyshev import Chebyshev
from rmgpy.kinetics.falloff import ThirdBody, Lindemann, Troe
from rmgpy.kinetics.kineticsdata import KineticsData, PDepKineticsData
from rmgpy.kinetics.tunneling import Wigner, Eckart
from rmgpy.kinetics.surface import SurfaceArrhenius, SurfaceArrheniusBEP, \
- StickingCoefficient, StickingCoefficientBEP
+ StickingCoefficient, StickingCoefficientBEP, \
+ SurfaceChargeTransfer, SurfaceChargeTransferBEP
diff --git a/rmgpy/kinetics/arrhenius.pxd b/rmgpy/kinetics/arrhenius.pxd
index 21e44a3be3..2d39ac48d4 100644
--- a/rmgpy/kinetics/arrhenius.pxd
+++ b/rmgpy/kinetics/arrhenius.pxd
@@ -128,4 +128,74 @@ cdef class MultiPDepArrhenius(PDepKineticsModel):
cpdef bint is_identical_to(self, KineticsModel other_kinetics) except -2
+
+ cpdef change_rate(self, double factor)
+
+################################################################################
+cdef class ArrheniusChargeTransfer(KineticsModel):
+
+ cdef public ScalarQuantity _A
+ cdef public ScalarQuantity _n
+ cdef public ScalarQuantity _Ea
+ cdef public ScalarQuantity _T0
+ cdef public ScalarQuantity _V0
+ cdef public ScalarQuantity _alpha
+ cdef public ScalarQuantity _electrons
+
+ cpdef double get_activation_energy_from_potential(self, double V=?, bint non_negative=?)
+
+ cpdef double get_rate_coefficient(self, double T, double V=?) except -1
+
cpdef change_rate(self, double factor)
+
+ cpdef change_t0(self, double T0)
+
+ cpdef change_v0(self, double V0)
+
+ cpdef fit_to_data(self, np.ndarray Tlist, np.ndarray klist, str kunits, double T0=?, np.ndarray weights=?, bint three_params=?)
+
+ cpdef bint is_identical_to(self, KineticsModel other_kinetics) except -2
+
+
+################################################################################
+
+cdef class ArrheniusChargeTransferBM(KineticsModel):
+
+ cdef public ScalarQuantity _A
+ cdef public ScalarQuantity _n
+ cdef public ScalarQuantity _E0
+ cdef public ScalarQuantity _w0
+ cdef public ScalarQuantity _V0
+ cdef public ScalarQuantity _alpha
+ cdef public ScalarQuantity _electrons
+
+ cpdef change_v0(self, double V0)
+
+ cpdef double get_activation_energy(self, double dGrxn) except -1
+
+ cpdef double get_rate_coefficient_from_potential(self, double T, double V, double dGrxn) except -1
+
+ cpdef ArrheniusChargeTransfer to_arrhenius_charge_transfer(self, double dGrxn)
+
+ cpdef bint is_identical_to(self, KineticsModel other_kinetics) except -2
+
+ cpdef change_rate(self, double factor)
+
+cdef class Marcus(KineticsModel):
+
+ cdef public ScalarQuantity _A
+ cdef public ScalarQuantity _n
+ cdef public ArrayQuantity _lmbd_i_coefs
+ cdef public ScalarQuantity _V0
+ cdef public ScalarQuantity _beta
+ cdef public ScalarQuantity _wr
+ cdef public ScalarQuantity _wp
+ cdef public ScalarQuantity _lmbd_o
+
+ cpdef double get_lmbd_i(self, double T)
+
+ cpdef double get_gibbs_activation_energy(self, double T, double dGrxn) except -1
+
+ cpdef bint is_identical_to(self, KineticsModel other_kinetics) except -2
+
+ cpdef change_rate(self, double factor)
\ No newline at end of file
diff --git a/rmgpy/kinetics/arrhenius.pyx b/rmgpy/kinetics/arrhenius.pyx
index f24defb8cc..f4b69179ed 100644
--- a/rmgpy/kinetics/arrhenius.pyx
+++ b/rmgpy/kinetics/arrhenius.pyx
@@ -37,6 +37,8 @@ import rmgpy.quantity as quantity
from rmgpy.exceptions import KineticsError
from rmgpy.kinetics.uncertainties import rank_accuracy_map
from rmgpy.molecule.molecule import Bond
+from rmgpy.kinetics.model import KineticsModel, PDepKineticsModel
+import logging
# Prior to numpy 1.14, `numpy.linalg.lstsq` does not accept None as a value
RCOND = -1 if int(np.__version__.split('.')[1]) < 14 else None
@@ -58,15 +60,16 @@ cdef class Arrhenius(KineticsModel):
`Tmax` The maximum temperature at which the model is valid, or zero if unknown or undefined
`Pmin` The minimum pressure at which the model is valid, or zero if unknown or undefined
`Pmax` The maximum pressure at which the model is valid, or zero if unknown or undefined
+ `solute` Transition state solute data
`comment` Information about the model (e.g. its source)
=============== =============================================================
"""
def __init__(self, A=None, n=0.0, Ea=None, T0=(1.0, "K"), Tmin=None, Tmax=None, Pmin=None, Pmax=None,
- uncertainty=None, comment=''):
+ uncertainty=None, solute=None, comment=''):
KineticsModel.__init__(self, Tmin=Tmin, Tmax=Tmax, Pmin=Pmin, Pmax=Pmax, uncertainty=uncertainty,
- comment=comment)
+ solute=solute, comment=comment)
self.A = A
self.n = n
self.Ea = Ea
@@ -83,6 +86,7 @@ cdef class Arrhenius(KineticsModel):
if self.Pmin is not None: string += ', Pmin={0!r}'.format(self.Pmin)
if self.Pmax is not None: string += ', Pmax={0!r}'.format(self.Pmax)
if self.uncertainty: string += ', uncertainty={0!r}'.format(self.uncertainty)
+ if self.solute: string += ', solute={0!r}'.format(self.solute)
if self.comment != '': string += ', comment="""{0}"""'.format(self.comment)
string += ')'
return string
@@ -92,7 +96,7 @@ cdef class Arrhenius(KineticsModel):
A helper function used when pickling an Arrhenius object.
"""
return (Arrhenius, (self.A, self.n, self.Ea, self.T0, self.Tmin, self.Tmax, self.Pmin, self.Pmax,
- self.uncertainty, self.comment))
+ self.uncertainty, self.solute, self.comment))
property A:
"""The preexponential factor."""
@@ -196,6 +200,7 @@ cdef class Arrhenius(KineticsModel):
self.T0 = (T0, "K")
self.Tmin = (np.min(Tlist), "K")
self.Tmax = (np.max(Tlist), "K")
+ self.solute = None
self.comment = 'Fitted to {0:d} data points; dA = *|/ {1:g}, dn = +|- {2:g}, dEa = +|- {3:g} kJ/mol'.format(
len(Tlist),
exp(sqrt(cov[0, 0])),
@@ -301,6 +306,7 @@ cdef class Arrhenius(KineticsModel):
Pmin=self.Pmin,
Pmax=self.Pmax,
uncertainty=self.uncertainty,
+ solute=self.solute,
comment=self.comment)
return aep
################################################################################
@@ -323,15 +329,16 @@ cdef class ArrheniusEP(KineticsModel):
`Tmax` The maximum temperature at which the model is valid, or zero if unknown or undefined
`Pmin` The minimum pressure at which the model is valid, or zero if unknown or undefined
`Pmax` The maximum pressure at which the model is valid, or zero if unknown or undefined
+ `solute` Transition state solute data
`comment` Information about the model (e.g. its source)
=============== =============================================================
"""
def __init__(self, A=None, n=0.0, alpha=0.0, E0=None, Tmin=None, Tmax=None, Pmin=None, Pmax=None, uncertainty=None,
- comment=''):
+ solute=None, comment=''):
KineticsModel.__init__(self, Tmin=Tmin, Tmax=Tmax, Pmin=Pmin, Pmax=Pmax, uncertainty=uncertainty,
- comment=comment)
+ solute=solute, comment=comment)
self.A = A
self.n = n
self.alpha = alpha
@@ -348,6 +355,7 @@ cdef class ArrheniusEP(KineticsModel):
if self.Pmin is not None: string += ', Pmin={0!r}'.format(self.Pmin)
if self.Pmax is not None: string += ', Pmax={0!r}'.format(self.Pmax)
if self.uncertainty is not None: string += ', uncertainty={0!r}'.format(self.uncertainty)
+ if self.solute is not None: string += ', solute={0!r}'.format(self.solute)
if self.comment != '': string += ', comment="""{0}"""'.format(self.comment)
string += ')'
return string
@@ -357,7 +365,7 @@ cdef class ArrheniusEP(KineticsModel):
A helper function used when pickling an ArrheniusEP object.
"""
return (ArrheniusEP, (self.A, self.n, self.alpha, self.E0, self.Tmin, self.Tmax, self.Pmin, self.Pmax,
- self.uncertainty, self.comment))
+ self.uncertainty, self.solute, self.comment))
property A:
"""The preexponential factor."""
@@ -428,6 +436,7 @@ cdef class ArrheniusEP(KineticsModel):
Pmin=self.Pmin,
Pmax=self.Pmax,
uncertainty=self.uncertainty,
+ solute=self.solute,
comment=self.comment,
)
@@ -481,15 +490,16 @@ cdef class ArrheniusBM(KineticsModel):
`Tmax` The maximum temperature at which the model is valid, or zero if unknown or undefined
`Pmin` The minimum pressure at which the model is valid, or zero if unknown or undefined
`Pmax` The maximum pressure at which the model is valid, or zero if unknown or undefined
+ `solute` Transition state solute data
`comment` Information about the model (e.g. its source)
=============== =============================================================
"""
def __init__(self, A=None, n=0.0, w0=(0.0, 'J/mol'), E0=None, Tmin=None, Tmax=None, Pmin=None, Pmax=None,
- uncertainty=None, comment=''):
+ uncertainty=None, solute=None, comment=''):
KineticsModel.__init__(self, Tmin=Tmin, Tmax=Tmax, Pmin=Pmin, Pmax=Pmax, uncertainty=uncertainty,
- comment=comment)
+ solute=solute, comment=comment)
self.A = A
self.n = n
self.w0 = w0
@@ -506,6 +516,7 @@ cdef class ArrheniusBM(KineticsModel):
if self.Pmin is not None: string += ', Pmin={0!r}'.format(self.Pmin)
if self.Pmax is not None: string += ', Pmax={0!r}'.format(self.Pmax)
if self.uncertainty is not None: string += ', uncertainty={0!r}'.format(self.uncertainty)
+ if self.solute is not None: string += ', solute={0!r}'.format(self.solute)
if self.comment != '': string += ', comment="""{0}"""'.format(self.comment)
string += ')'
return string
@@ -515,7 +526,7 @@ cdef class ArrheniusBM(KineticsModel):
A helper function used when pickling an ArrheniusEP object.
"""
return (ArrheniusBM, (self.A, self.n, self.w0, self.E0, self.Tmin, self.Tmax, self.Pmin, self.Pmax,
- self.uncertainty, self.comment))
+ self.uncertainty, self.solute, self.comment))
property A:
"""The preexponential factor."""
@@ -549,7 +560,7 @@ cdef class ArrheniusBM(KineticsModel):
"""
Return the rate coefficient in the appropriate combination of m^3,
mol, and s at temperature `T` in K and enthalpy of reaction `dHrxn`
- in J/mol.
+ in J/mol, evaluated at 298 K.
"""
cdef double A, n, Ea
Ea = self.get_activation_energy(dHrxn)
@@ -560,7 +571,7 @@ cdef class ArrheniusBM(KineticsModel):
cpdef double get_activation_energy(self, double dHrxn) except -1:
"""
Return the activation energy in J/mol corresponding to the given
- enthalpy of reaction `dHrxn` in J/mol.
+ enthalpy of reaction `dHrxn` in J/mol, evaluated at 298 K.
"""
cdef double w0, E0
E0 = self._E0.value_si
@@ -576,7 +587,8 @@ cdef class ArrheniusBM(KineticsModel):
cpdef Arrhenius to_arrhenius(self, double dHrxn):
"""
Return an :class:`Arrhenius` instance of the kinetics model using the
- given enthalpy of reaction `dHrxn` to determine the activation energy.
+ given enthalpy of reaction `dHrxn` (in J/mol, evaluated at 298 K)
+ to determine the activation energy.
"""
return Arrhenius(
A=self.A,
@@ -586,6 +598,7 @@ cdef class ArrheniusBM(KineticsModel):
Tmin=self.Tmin,
Tmax=self.Tmax,
uncertainty=self.uncertainty,
+ solute=self.solute,
comment=self.comment,
)
@@ -593,6 +606,9 @@ cdef class ArrheniusBM(KineticsModel):
"""
Fit an ArrheniusBM model to a list of reactions at the given temperatures,
w0 must be either given or estimated using the family object
+
+ WARNING: there's a lot of code duplication with ArrheniusChargeTransferBM.fit_to_reactions
+ so anything you change here you should probably change there too and vice versa!
"""
assert w0 is not None or recipe is not None, 'either w0 or recipe must be specified'
@@ -604,28 +620,25 @@ cdef class ArrheniusBM(KineticsModel):
w0 = sum(w0s) / len(w0s)
if len(rxns) == 1:
- T = 1000.0
rxn = rxns[0]
- dHrxn = rxn.get_enthalpy_of_reaction(T)
+ dHrxn = rxn.get_enthalpy_of_reaction(298.0)
A = rxn.kinetics.A.value_si
n = rxn.kinetics.n.value_si
Ea = rxn.kinetics.Ea.value_si
-
+
def kfcn(E0):
Vp = 2 * w0 * (2 * w0 + 2 * E0) / (2 * w0 - 2 * E0)
out = Ea - (w0 + dHrxn / 2.0) * (Vp - 2 * w0 + dHrxn) * (Vp - 2 * w0 + dHrxn) / (Vp * Vp - (2 * w0) * (2 * w0) + dHrxn * dHrxn)
return out
- if abs(dHrxn) > 4 * w0 / 10.0:
- E0 = w0 / 10.0
- else:
- E0 = fsolve(kfcn, w0 / 10.0)[0]
+ E0 = fsolve(kfcn, w0 / 10.0)[0]
self.Tmin = rxn.kinetics.Tmin
self.Tmax = rxn.kinetics.Tmax
- self.comment = 'Fitted to {0} reaction at temperature: {1} K'.format(len(rxns), T)
+ self.solute = None
+ self.comment = 'Fitted to 1 reaction.'
else:
- # define optimization function
+ # define optimization function
def kfcn(xs, lnA, n, E0):
T = xs[:,0]
dHrxn = xs[:,1]
@@ -634,7 +647,7 @@ cdef class ArrheniusBM(KineticsModel):
Ea = np.where(dHrxn< -4.0*E0, 0.0, Ea)
Ea = np.where(dHrxn > 4.0*E0, dHrxn, Ea)
return lnA + np.log(T ** n * np.exp(-Ea / (8.314 * T)))
-
+
# get (T,dHrxn(T)) -> (Ln(k) mappings
xdata = []
ydata = []
@@ -643,25 +656,24 @@ cdef class ArrheniusBM(KineticsModel):
# approximately correct the overall uncertainties to std deviations
s = rank_accuracy_map[rxn.rank].value_si/2.0
for T in Ts:
- xdata.append([T, rxn.get_enthalpy_of_reaction(T)])
+ xdata.append([T, rxn.get_enthalpy_of_reaction(298.0)])
ydata.append(np.log(rxn.get_rate_coefficient(T)))
-
sigmas.append(s / (8.314 * T))
xdata = np.array(xdata)
ydata = np.array(ydata)
# fit parameters
- boo = True
+ keep_trying = True
xtol = 1e-8
ftol = 1e-8
- while boo:
- boo = False
+ while keep_trying:
+ keep_trying = False
try:
params = curve_fit(kfcn, xdata, ydata, sigma=sigmas, p0=[1.0, 1.0, w0 / 10.0], xtol=xtol, ftol=ftol)
except RuntimeError:
if xtol < 1.0:
- boo = True
+ keep_trying = True
xtol *= 10.0
ftol *= 10.0
else:
@@ -672,11 +684,14 @@ cdef class ArrheniusBM(KineticsModel):
self.Tmin = (np.min(Ts), "K")
self.Tmax = (np.max(Ts), "K")
+ self.solute = None
self.comment = 'Fitted to {0} reactions at temperatures: {1}'.format(len(rxns), Ts)
# fill in parameters
A_units = ['', 's^-1', 'm^3/(mol*s)', 'm^6/(mol^2*s)']
order = len(rxns[0].reactants)
+ if order != 1 and rxn.is_surface_reaction():
+ raise NotImplementedError("Units not implemented for surface reactions.")
self.A = (A, A_units[order])
self.n = n
@@ -1097,6 +1112,759 @@ cdef class MultiPDepArrhenius(PDepKineticsModel):
for i, arr in enumerate(self.arrhenius):
arr.set_cantera_kinetics(ct_reaction[i], species_list)
+################################################################################
+
+cdef class ArrheniusChargeTransfer(KineticsModel):
+
+ """
+ A kinetics model for surface charge transfer reactions
+
+ It is very similar to the :class:`SurfaceArrhenius`, but the Ea is potential-dependent
+
+
+ The attributes are:
+
+ =============== =============================================================
+ Attribute Description
+ =============== =============================================================
+ `A` The preexponential factor
+ `T0` The reference temperature
+ `n` The temperature exponent
+ `Ea` The activation energy
+ `electrons` The stochiometry coeff for electrons (negative if reactant, positive if product)
+ `V0` The reference potential
+ `alpha` The charge transfer coefficient
+ `Tmin` The minimum temperature at which the model is valid, or zero if unknown or undefined
+ `Tmax` The maximum temperature at which the model is valid, or zero if unknown or undefined
+ `Pmin` The minimum pressure at which the model is valid, or zero if unknown or undefined
+ `Pmax` The maximum pressure at which the model is valid, or zero if unknown or undefined
+ `solute` The transition state solute data
+ `comment` Information about the model (e.g. its source)
+ =============== =============================================================
+
+ """
+
+ def __init__(self, A=None, n=0.0, Ea=None, V0=None, alpha=0.5, electrons=-1, T0=(1.0, "K"), Tmin=None, Tmax=None,
+ Pmin=None, Pmax=None, solute=None, uncertainty=None, comment=''):
+
+ KineticsModel.__init__(self, Tmin=Tmin, Tmax=Tmax, Pmin=Pmin, Pmax=Pmax, solute=solute, uncertainty=uncertainty,
+ comment=comment)
+
+ self.alpha = alpha
+ self.A = A
+ self.n = n
+ self.Ea = Ea
+ self.T0 = T0
+ self.electrons = electrons
+ self.V0 = V0
+
+ def __repr__(self):
+ """
+ Return a string representation that can be used to reconstruct the
+ Arrhenius object.
+ """
+ string = 'ArrheniusChargeTransfer(A={0!r}, n={1!r}, Ea={2!r}, V0={3!r}, alpha={4!r}, electrons={5!r}, T0={6!r}'.format(
+ self.A, self.n, self.Ea, self.V0, self.alpha, self.electrons, self.T0)
+ if self.Tmin is not None: string += ', Tmin={0!r}'.format(self.Tmin)
+ if self.Tmax is not None: string += ', Tmax={0!r}'.format(self.Tmax)
+ if self.Pmin is not None: string += ', Pmin={0!r}'.format(self.Pmin)
+ if self.Pmax is not None: string += ', Pmax={0!r}'.format(self.Pmax)
+ if self.solute: string += ', solute={0!r}'.format(self.solute)
+ if self.uncertainty: string += ', uncertainty={0!r}'.format(self.uncertainty)
+ if self.comment != '': string += ', comment="""{0}"""'.format(self.comment)
+ string += ')'
+ return string
+
+ def __reduce__(self):
+ """
+ A helper function used when pickling a ArrheniusChargeTransfer object.
+ """
+ return (ArrheniusChargeTransfer, (self.A, self.n, self.Ea, self.V0, self.alpha, self.electrons, self.T0, self.Tmin, self.Tmax, self.Pmin, self.Pmax,
+ self.solute, self.uncertainty, self.comment))
+
+ property A:
+ """The preexponential factor."""
+ def __get__(self):
+ return self._A
+ def __set__(self, value):
+ self._A = quantity.SurfaceRateCoefficient(value)
+
+ property n:
+ """The temperature exponent."""
+ def __get__(self):
+ return self._n
+ def __set__(self, value):
+ self._n = quantity.Dimensionless(value)
+
+ property Ea:
+ """The activation energy."""
+ def __get__(self):
+ return self._Ea
+ def __set__(self, value):
+ self._Ea = quantity.Energy(value)
+
+ property T0:
+ """The reference temperature."""
+ def __get__(self):
+ return self._T0
+ def __set__(self, value):
+ self._T0 = quantity.Temperature(value)
+
+ property V0:
+ """The reference potential."""
+ def __get__(self):
+ return self._V0
+ def __set__(self, value):
+ self._V0 = quantity.Potential(value)
+
+ property electrons:
+ """The number of electrons transferred."""
+ def __get__(self):
+ return self._electrons
+ def __set__(self, value):
+ self._electrons = quantity.Dimensionless(value)
+
+ property alpha:
+ """The charge transfer coefficient."""
+ def __get__(self):
+ return self._alpha
+ def __set__(self, value):
+ self._alpha = quantity.Dimensionless(value)
+
+ cpdef double get_activation_energy_from_potential(self, double V=0.0, bint non_negative=True):
+ """
+ Return the effective activation energy (in J/mol) at specificed potential (in Volts).
+ """
+ cdef double electrons, alpha, Ea, V0
+
+ electrons = self._electrons.value_si
+ alpha = self._alpha.value_si
+ Ea = self._Ea.value_si
+ V0 = self._V0.value_si
+
+ Ea -= alpha * electrons * constants.F * (V-V0)
+
+ if non_negative is True:
+ if Ea < 0:
+ Ea = 0.0
+
+ return Ea
+
+ cpdef double get_rate_coefficient(self, double T, double V=0.0) except -1:
+ """
+ Return the rate coefficient in the appropriate combination of m^2,
+ mol, and s at temperature `T` in K.
+ """
+ cdef double A, n, V0, T0, Ea
+
+ A = self._A.value_si
+ n = self._n.value_si
+ V0 = self._V0.value_si
+ T0 = self._T0.value_si
+
+ if V != V0:
+ Ea = self.get_activation_energy_from_potential(V)
+ else:
+ Ea = self._Ea.value_si
+
+ return A * (T / T0) ** n * exp(-Ea / (constants.R * T))
+
+ cpdef change_t0(self, double T0):
+ """
+ Changes the reference temperature used in the exponent to `T0` in K,
+ and adjusts the preexponential factor accordingly.
+ """
+ self._A.value_si /= (self._T0.value_si / T0) ** self._n.value_si
+ self._T0.value_si = T0
+
+ cpdef change_v0(self, double V0):
+ """
+ Changes the reference potential to `V0` in volts, and adjusts the
+ activation energy `Ea` accordingly.
+ """
+
+ self._Ea.value_si = self.get_activation_energy_from_potential(V0)
+ self._V0.value_si = V0
+
+ cpdef fit_to_data(self, np.ndarray Tlist, np.ndarray klist, str kunits, double T0=1,
+ np.ndarray weights=None, bint three_params=False):
+ """
+ Fit the Arrhenius parameters to a set of rate coefficient data `klist`
+ in units of `kunits` corresponding to a set of temperatures `Tlist` in
+ K. A linear least-squares fit is used, which guarantees that the
+ resulting parameters provide the best possible approximation to the
+ data.
+ """
+ import scipy.stats
+ if not all(np.isfinite(klist)):
+ raise ValueError("Rates must all be finite, not inf or NaN")
+ if any(klist<0):
+ if not all(klist<0):
+ raise ValueError("Rates must all be positive or all be negative.")
+ rate_sign_multiplier = -1
+ klist = -1 * klist
+ else:
+ rate_sign_multiplier = 1
+
+ assert len(Tlist) == len(klist), "length of temperatures and rates must be the same"
+ if len(Tlist) < 3 + three_params:
+ raise KineticsError('Not enough degrees of freedom to fit this Arrhenius expression')
+ if three_params:
+ A = np.zeros((len(Tlist), 3), np.float64)
+ A[:, 0] = np.ones_like(Tlist)
+ A[:, 1] = np.log(Tlist / T0)
+ A[:, 2] = -1.0 / constants.R / Tlist
+ else:
+ A = np.zeros((len(Tlist), 2), np.float64)
+ A[:, 0] = np.ones_like(Tlist)
+ A[:, 1] = -1.0 / constants.R / Tlist
+ b = np.log(klist)
+ if weights is not None:
+ for n in range(b.size):
+ A[n, :] *= weights[n]
+ b[n] *= weights[n]
+ x, residues, rank, s = np.linalg.lstsq(A, b, rcond=RCOND)
+
+ # Determine covarianace matrix to obtain parameter uncertainties
+ count = klist.size
+ cov = residues[0] / (count - 3) * np.linalg.inv(np.dot(A.T, A))
+ t = scipy.stats.t.ppf(0.975, count - 3)
+
+ if not three_params:
+ x = np.array([x[0], 0, x[1]])
+ cov = np.array([[cov[0, 0], 0, cov[0, 1]], [0, 0, 0], [cov[1, 0], 0, cov[1, 1]]])
+
+ self.A = (rate_sign_multiplier * exp(x[0]), kunits)
+ self.n = x[1]
+ self.Ea = (x[2] * 0.001, "kJ/mol")
+ self.T0 = (T0, "K")
+ self.Tmin = (np.min(Tlist), "K")
+ self.Tmax = (np.max(Tlist), "K")
+ self.solute = None,
+ self.comment = 'Fitted to {0:d} data points; dA = *|/ {1:g}, dn = +|- {2:g}, dEa = +|- {3:g} kJ/mol'.format(
+ len(Tlist),
+ exp(sqrt(cov[0, 0])),
+ sqrt(cov[1, 1]),
+ sqrt(cov[2, 2]) * 0.001,
+ )
+
+ return self
+
+ cpdef bint is_identical_to(self, KineticsModel other_kinetics) except -2:
+ """
+ Returns ``True`` if kinetics matches that of another kinetics model. Must match temperature
+ and pressure range of kinetics model, as well as parameters: A, n, Ea, T0. (Shouldn't have pressure
+ range if it's Arrhenius.) Otherwise returns ``False``.
+ """
+ if not isinstance(other_kinetics, ArrheniusChargeTransfer):
+ return False
+ if not KineticsModel.is_identical_to(self, other_kinetics):
+ return False
+ if (not self.A.equals(other_kinetics.A) or not self.n.equals(other_kinetics.n)
+ or not self.Ea.equals(other_kinetics.Ea) or not self.T0.equals(other_kinetics.T0)
+ or not self.alpha.equals(other_kinetics.alpha) or not self.electrons.equals(other_kinetics.electrons)
+ or not self.V0.equals(other_kinetics.V0)):
+ return False
+
+ return True
+
+ cpdef change_rate(self, double factor):
+ """
+ Changes A factor in Arrhenius expression by multiplying it by a ``factor``.
+ """
+ self._A.value_si *= factor
+
+cdef class ArrheniusChargeTransferBM(KineticsModel):
+ """
+ A kinetics model based on the (modified) Arrhenius equation, using the
+ Evans-Polanyi equation to determine the activation energy. The attributes
+ are:
+
+ =============== =============================================================
+ Attribute Description
+ =============== =============================================================
+ `A` The preexponential factor
+ `n` The temperature exponent
+ `w0` The average of the bond dissociation energies of the bond formed and the bond broken
+ `E0` The activation energy for a thermoneutral reaction
+ `electrons` The stochiometry coeff for electrons (negative if reactant, positive if product)
+ `V0` The reference potential
+ `alpha` The charge transfer coefficient
+ `Tmin` The minimum temperature at which the model is valid, or zero if unknown or undefined
+ `Tmax` The maximum temperature at which the model is valid, or zero if unknown or undefined
+ `Pmin` The minimum pressure at which the model is valid, or zero if unknown or undefined
+ `Pmax` The maximum pressure at which the model is valid, or zero if unknown or undefined
+ `solute` Transition state solute data
+ `comment` Information about the model (e.g. its source)
+ =============== =============================================================
+
+ """
+
+ def __init__(self, A=None, n=0.0, w0=(0.0, 'J/mol'), E0=None, V0=(0.0,'V'), alpha=0.5, electrons=-1, Tmin=None, Tmax=None,
+ Pmin=None, Pmax=None, solute=None, uncertainty=None, comment=''):
+
+ KineticsModel.__init__(self, Tmin=Tmin, Tmax=Tmax, Pmin=Pmin, Pmax=Pmax, solute=solute, uncertainty=uncertainty,
+ comment=comment)
+
+ self.alpha = alpha
+ self.A = A
+ self.n = n
+ self.w0 = w0
+ self.E0 = E0
+ self.electrons = electrons
+ self.V0 = V0
+
+ def __repr__(self):
+ """
+ Return a string representation that can be used to reconstruct the
+ Arrhenius object.
+ """
+ string = 'ArrheniusChargeTransferBM(A={0!r}, n={1!r}, w0={2!r}, E0={3!r}, V0={4!r}, alpha={5!r}, electrons={6!r}'.format(
+ self.A, self.n, self.w0, self.E0, self.V0, self.alpha, self.electrons)
+ if self.Tmin is not None: string += ', Tmin={0!r}'.format(self.Tmin)
+ if self.Tmax is not None: string += ', Tmax={0!r}'.format(self.Tmax)
+ if self.Pmin is not None: string += ', Pmin={0!r}'.format(self.Pmin)
+ if self.Pmax is not None: string += ', Pmax={0!r}'.format(self.Pmax)
+ if self.solute: string += ', solute={0!r}'.format(self.solute)
+ if self.uncertainty: string += ', uncertainty={0!r}'.format(self.uncertainty)
+ if self.comment != '': string += ', comment="""{0}"""'.format(self.comment)
+ string += ')'
+ return string
+
+ def __reduce__(self):
+ """
+ A helper function used when pickling a ArrheniusChargeTransfer object.
+ """
+ return (ArrheniusChargeTransferBM, (self.A, self.n, self.w0, self.E0, self.V0, self.alpha, self.electrons, self.Tmin, self.Tmax, self.Pmin, self.Pmax,
+ self.solute, self.uncertainty, self.comment))
+
+ property A:
+ """The preexponential factor."""
+ def __get__(self):
+ return self._A
+ def __set__(self, value):
+ self._A = quantity.SurfaceRateCoefficient(value)
+
+ property n:
+ """The temperature exponent."""
+ def __get__(self):
+ return self._n
+ def __set__(self, value):
+ self._n = quantity.Dimensionless(value)
+
+ property w0:
+ """The average of the bond dissociation energies of the bond formed and the bond broken."""
+ def __get__(self):
+ return self._w0
+ def __set__(self, value):
+ self._w0 = quantity.Energy(value)
+
+ property E0:
+ """The activation energy."""
+ def __get__(self):
+ return self._E0
+ def __set__(self, value):
+ self._E0 = quantity.Energy(value)
+
+ property V0:
+ """The reference potential."""
+ def __get__(self):
+ return self._V0
+ def __set__(self, value):
+ self._V0 = quantity.Potential(value)
+
+ property electrons:
+ """The number of electrons transferred."""
+ def __get__(self):
+ return self._electrons
+ def __set__(self, value):
+ self._electrons = quantity.Dimensionless(value)
+
+ property alpha:
+ """The charge transfer coefficient."""
+ def __get__(self):
+ return self._alpha
+ def __set__(self, value):
+ self._alpha = quantity.Dimensionless(value)
+
+ cpdef change_v0(self, double V0):
+ """
+ Changes the reference potential to `V0` in volts, and adjusts the
+ activation energy `E0` accordingly.
+ """
+
+ self._E0.value_si = self.get_activation_energy_from_potential(V0,0.0)
+ self._V0.value_si = V0
+
+ cpdef double get_rate_coefficient(self, double T, double dHrxn=0.0) except -1:
+ """
+ Return the rate coefficient in the appropriate combination of m^3,
+ mol, and s at temperature `T` in K and enthalpy of reaction `dHrxn`
+ in J/mol.
+ """
+ cdef double A, n, Ea
+ Ea = self.get_activation_energy(dHrxn)
+ A = self._A.value_si
+ n = self._n.value_si
+ return A * T ** n * exp(-Ea / (constants.R * T))
+
+ cpdef double get_activation_energy(self, double dHrxn) except -1:
+ """
+ Return the activation energy in J/mol corresponding to the given
+ enthalpy of reaction `dHrxn` in J/mol.
+ """
+ cdef double w0, E0
+ E0 = self._E0.value_si
+ if dHrxn < -4 * self._E0.value_si:
+ return 0.0
+ elif dHrxn > 4 * self._E0.value_si:
+ return dHrxn
+ else:
+ w0 = self._w0.value_si
+ Vp = 2 * w0 * (2 * w0 + 2 * E0) / (2 * w0 - 2 * E0)
+ return (w0 + dHrxn / 2.0) * (Vp - 2 * w0 + dHrxn) ** 2 / (Vp ** 2 - (2 * w0) ** 2 + dHrxn ** 2)
+
+ cpdef double get_rate_coefficient_from_potential(self, double T, double V, double dHrxn) except -1:
+ """
+ Return the rate coefficient in the appropriate combination of m^3,
+ mol, and s at temperature `T` in K, potential `V` in volts, and
+ heat of reaction `dHrxn` in J/mol.
+ """
+ cdef double A, n, Ea
+ Ea = self.get_activation_energy_from_potential(V,dHrxn)
+ Ea -= self._alpha.value_si * self._electrons.value_si * constants.F * (V-self._V0.value_si)
+ A = self._A.value_si
+ n = self._n.value_si
+ return A * T ** n * exp(-Ea / (constants.R * T))
+
+ def fit_to_reactions(self, rxns, w0=None, recipe=None, Ts=None):
+ """
+ Fit an ArrheniusChargeTransferBM model to a list of reactions at the given temperatures,
+ w0 must be either given or estimated using the family object
+
+ WARNING: there's a lot of code duplication with ArrheniusBM.fit_to_reactions
+ so anything you change here you should probably change there too and vice versa!
+ """
+ assert w0 is not None or recipe is not None, 'either w0 or recipe must be specified'
+
+ for rxn in rxns:
+ if rxn.kinetics._V0.value_si != 0.0:
+ rxn.kinetics.change_v0(0.0)
+
+ if Ts is None:
+ Ts = [300.0, 500.0, 600.0, 700.0, 800.0, 900.0, 1000.0, 1100.0, 1200.0, 1500.0]
+ if w0 is None:
+ #estimate w0
+ w0s = get_w0s(recipe, rxns)
+ w0 = sum(w0s) / len(w0s)
+
+ if len(rxns) == 1:
+ rxn = rxns[0]
+ dHrxn = rxn.get_enthalpy_of_reaction(298.0)
+ A = rxn.kinetics.A.value_si
+ n = rxn.kinetics.n.value_si
+ Ea = rxn.kinetics.Ea.value_si
+
+ def kfcn(E0):
+ Vp = 2 * w0 * (2 * w0 + 2 * E0) / (2 * w0 - 2 * E0)
+ out = Ea - (w0 + dHrxn / 2.0) * (Vp - 2 * w0 + dHrxn) * (Vp - 2 * w0 + dHrxn) / (Vp * Vp - (2 * w0) * (2 * w0) + dHrxn * dHrxn)
+ return out
+
+ E0 = fsolve(kfcn, w0 / 10.0)[0]
+
+ self.Tmin = rxn.kinetics.Tmin
+ self.Tmax = rxn.kinetics.Tmax
+ self.comment = 'Fitted to 1 reaction'
+ else:
+ # define optimization function
+ def kfcn(xs, lnA, n, E0):
+ T = xs[:,0]
+ dHrxn = xs[:,1]
+ Vp = 2 * w0 * (2 * w0 + 2 * E0) / (2 * w0 - 2 * E0)
+ Ea = (w0 + dHrxn / 2.0) * (Vp - 2 * w0 + dHrxn) * (Vp - 2 * w0 + dHrxn) / (Vp * Vp - (2 * w0) * (2 * w0) + dHrxn * dHrxn)
+ Ea = np.where(dHrxn< -4.0*E0, 0.0, Ea)
+ Ea = np.where(dHrxn > 4.0*E0, dHrxn, Ea)
+ return lnA + np.log(T ** n * np.exp(-Ea / (8.314 * T)))
+
+ # get (T,dHrxn(T)) -> (Ln(k) mappings
+ xdata = []
+ ydata = []
+ sigmas = []
+ for rxn in rxns:
+ # approximately correct the overall uncertainties to std deviations
+ s = rank_accuracy_map[rxn.rank].value_si/2.0
+ for T in Ts:
+ xdata.append([T, rxn.get_enthalpy_of_reaction(298.0)])
+ ydata.append(np.log(rxn.get_rate_coefficient(T)))
+ sigmas.append(s / (8.314 * T))
+
+ xdata = np.array(xdata)
+ ydata = np.array(ydata)
+
+ # fit parameters
+ keep_trying = True
+ xtol = 1e-8
+ ftol = 1e-8
+ while keep_trying:
+ keep_trying = False
+ try:
+ params = curve_fit(kfcn, xdata, ydata, sigma=sigmas, p0=[1.0, 1.0, w0 / 10.0], xtol=xtol, ftol=ftol)
+ except RuntimeError:
+ if xtol < 1.0:
+ keep_trying = True
+ xtol *= 10.0
+ ftol *= 10.0
+ else:
+ raise ValueError("Could not fit BM arrhenius to reactions with xtol<1.0")
+
+ lnA, n, E0 = params[0].tolist()
+ A = np.exp(lnA)
+
+ self.Tmin = (np.min(Ts), "K")
+ self.Tmax = (np.max(Ts), "K")
+ self.comment = 'Fitted to {0} reactions at temperatures: {1}'.format(len(rxns), Ts)
+
+ # fill in parameters
+ A_units = ['', 's^-1', 'm^3/(mol*s)', 'm^6/(mol^2*s)']
+ order = len(rxns[0].reactants)
+ if order != 1 and rxn.is_surface_reaction():
+ raise NotImplementedError("Units not implemented for surface reactions")
+ self.A = (A, A_units[order])
+
+ self.n = n
+ self.w0 = (w0, 'J/mol')
+ self.E0 = (E0, 'J/mol')
+ self._V0.value_si = 0.0
+ self.electrons = rxns[0].electrons
+
+ return self
+
+ cpdef ArrheniusChargeTransfer to_arrhenius_charge_transfer(self, double dHrxn):
+ """
+ Return an :class:`ArrheniusChargeTransfer` instance of the kinetics model using the
+ given heat of reaction `dHrxn` to determine the activation energy.
+ """
+ return ArrheniusChargeTransfer(
+ A=self.A,
+ n=self.n,
+ electrons=self.electrons,
+ Ea=(self.get_activation_energy(dHrxn) * 0.001, "kJ/mol"),
+ V0=self.V0,
+ T0=(1, "K"),
+ Tmin=self.Tmin,
+ Tmax=self.Tmax,
+ Pmin=self.Pmin,
+ Pmax=self.Pmax,
+ uncertainty=self.uncertainty,
+ solute=self.solute,
+ comment=self.comment,
+ )
+
+ cpdef bint is_identical_to(self, KineticsModel other_kinetics) except -2:
+ """
+ Returns ``True`` if kinetics matches that of another kinetics model. Must match temperature
+ and pressure range of kinetics model, as well as parameters: A, n, Ea, T0. (Shouldn't have pressure
+ range if it's Arrhenius.) Otherwise returns ``False``.
+ """
+ if not isinstance(other_kinetics, ArrheniusChargeTransferBM):
+ return False
+ if not KineticsModel.is_identical_to(self, other_kinetics):
+ return False
+ if (not self.A.equals(other_kinetics.A) or not self.n.equals(other_kinetics.n)
+ or not self.E0.equals(other_kinetics.E0) or not self.w0.equals(other_kinetics.w0)
+ or not self.alpha.equals(other_kinetics.alpha)
+ or not self.electrons.equals(other_kinetics.electrons) or not self.V0.equals(other_kinetics.V0)):
+ return False
+
+ return True
+
+ cpdef change_rate(self, double factor):
+ """
+ Changes A factor by multiplying it by a ``factor``.
+ """
+ self._A.value_si *= factor
+
+ def set_cantera_kinetics(self, ct_reaction, species_list):
+ """
+ Sets a cantera ElementaryReaction() object with the modified Arrhenius object
+ converted to an Arrhenius form.
+ """
+ raise NotImplementedError('set_cantera_kinetics() is not implemented for ArrheniusEP class kinetics.')
+
+cdef class Marcus(KineticsModel):
+ """
+ A kinetics model based on the (modified) Arrhenius equation, using the
+ Evans-Polanyi equation to determine the activation energy. The attributes
+ are:
+
+ =============== =============================================================
+ Attribute Description
+ =============== =============================================================
+ `A` The preexponential factor
+ `n` The temperature exponent
+ `lmbd_i_coefs` Coefficients for inner sphere reorganization energy
+ `V0` The reference potential
+ `beta` Transmission decay coefficient
+ `wr` Work to bring reactants together
+ `wp` Work to bring products together
+ `lmbd_o` Outer sphere reorganization energy (solvent)
+ `Tmin` The minimum temperature at which the model is valid, or zero if unknown or undefined
+ `Tmax` The maximum temperature at which the model is valid, or zero if unknown or undefined
+ `Pmin` The minimum pressure at which the model is valid, or zero if unknown or undefined
+ `Pmax` The maximum pressure at which the model is valid, or zero if unknown or undefined
+ `solute` Transition state solute data
+ `comment` Information about the model (e.g. its source)
+ =============== =============================================================
+
+ """
+
+ def __init__(self, A=None, n=0.0, lmbd_i_coefs=np.array([0.0,0.0,0.0,0.0]), beta=(1.2e-10,"1/m"),
+ wr=(0,"J/mol"), wp=(0,"J/mol"), lmbd_o=(0,"J/mol"), Tmin=None, Tmax=None,
+ Pmin=None, Pmax=None, solute=None, uncertainty=None, comment=''):
+
+ KineticsModel.__init__(self, Tmin=Tmin, Tmax=Tmax, Pmin=Pmin, Pmax=Pmax, solute=solute, uncertainty=uncertainty,
+ comment=comment)
+
+ self.A = A
+ self.n = n
+ self.lmbd_i_coefs = lmbd_i_coefs
+ self.beta = beta
+ self.wr = wr
+ self.wp = wp
+ self.lmbd_o = lmbd_o
+
+ def __repr__(self):
+ """
+ Return a string representation that can be used to reconstruct the
+ Marcus object.
+ """
+ string = 'Marcus(A={0!r}, n={1!r}, lmbd_i_coefs={2!r}, beta={3!r}, wr={4!r}, wp={5!r}, lmbd_o={6!r}'.format(
+ self.A, self.n, self.lmbd_i_coefs, self.beta, self.wr, self.wp, self.lmbd_o)
+ if self.Tmin is not None: string += ', Tmin={0!r}'.format(self.Tmin)
+ if self.Tmax is not None: string += ', Tmax={0!r}'.format(self.Tmax)
+ if self.Pmin is not None: string += ', Pmin={0!r}'.format(self.Pmin)
+ if self.Pmax is not None: string += ', Pmax={0!r}'.format(self.Pmax)
+ if self.solute: string += ', solute={0!r}'.format(self.solute)
+ if self.uncertainty: string += ', uncertainty={0!r}'.format(self.uncertainty)
+ if self.comment != '': string += ', comment="""{0}"""'.format(self.comment)
+ string += ')'
+ return string
+
+ def __reduce__(self):
+ """
+ A helper function used when pickling a Marcus object.
+ """
+ return (Marcus, (self.A, self.n, self.lmbd_i_coefs, self.beta, self.wr, self.wp, self.lmbd_o,
+ self.Tmin, self.Tmax, self.Pmin, self.Pmax,
+ self.solute, self.uncertainty, self.comment))
+
+ property A:
+ """The preexponential factor."""
+ def __get__(self):
+ return self._A
+ def __set__(self, value):
+ self._A = quantity.RateCoefficient(value)
+
+ property n:
+ """The temperature exponent."""
+ def __get__(self):
+ return self._n
+ def __set__(self, value):
+ self._n = quantity.Dimensionless(value)
+
+ property lmbd_i_coefs:
+ """Temperature polynomial coefficients for inner sphere reogranization energy"""
+ def __get__(self):
+ return self._lmbd_i_coefs
+ def __set__(self, value):
+ self._lmbd_i_coefs = quantity.Dimensionless(value)
+
+ property beta:
+ """transmission coefficient"""
+ def __get__(self):
+ return self._beta
+ def __set__(self, value):
+ self._beta = quantity.UnitType('m^-1')(value)
+
+ property lmbd_o:
+ """outer sphere reorganization energy"""
+ def __get__(self):
+ return self._lmbd_o
+ def __set__(self, value):
+ self._lmbd_o = quantity.Energy(value)
+
+ property wr:
+ """outer sphere reorganization energy"""
+ def __get__(self):
+ return self._wr
+ def __set__(self, value):
+ self._wr = quantity.Energy(value)
+
+ property wp:
+ """outer sphere reorganization energy"""
+ def __get__(self):
+ return self._wp
+ def __set__(self, value):
+ self._wp = quantity.Energy(value)
+
+ cpdef double get_rate_coefficient(self, double T, double dGrxn=0.0) except -1:
+ """
+ Return the rate coefficient in the appropriate combination of m^3,
+ mol, and s at temperature `T` in K and enthalpy of reaction `dHrxn`
+ in J/mol.
+ """
+ cdef double A, n, dG
+ dG = self.get_gibbs_activation_energy(T, dGrxn)
+ A = self._A.value_si
+ n = self._n.value_si
+ return A * T ** n * exp(-dG / (constants.R * T))
+
+ cpdef double get_lmbd_i(self, double T):
+ """
+ Return lmbd_i in J/mol
+ """
+ return self.lmbd_i_coefs.value_si[0]+self.lmbd_i_coefs.value_si[1]*T+self.lmbd_i_coefs.value_si[2]*T**2+self.lmbd_i_coefs.value_si[3]*T**3
+
+ cpdef double get_gibbs_activation_energy(self, double T, double dGrxn) except -1:
+ """
+ Return the activation energy in J/mol corresponding to the given
+ enthalpy of reaction `dHrxn` in J/mol.
+ """
+ cdef double lmbd_i
+ lmbd_i = self.get_lmbd_i(T)
+ return (lmbd_i+self.lmbd_o.value_si)/4.0*(1.0+dGrxn/(lmbd_i+self.lmbd_o.value_si))**2
+
+ cpdef bint is_identical_to(self, KineticsModel other_kinetics) except -2:
+ """
+ Returns ``True`` if kinetics matches that of another kinetics model. Must match temperature
+ and pressure range of kinetics model, as well as parameters: A, n, Ea, T0. (Shouldn't have pressure
+ range if it's Arrhenius.) Otherwise returns ``False``.
+ """
+ if not isinstance(other_kinetics, Marcus):
+ return False
+ if not KineticsModel.is_identical_to(self, other_kinetics):
+ return False
+ if (not self.A.equals(other_kinetics.A) or not self.n.equals(other_kinetics.n)
+ or not self.lmbd_i_coefs.equals(other_kinetics.lmbd_i_coefs) or not self.lmbd_o.equals(other_kinetics.lmbd_o)
+ or not self.beta.equals(other_kinetics.beta)
+ or not self.electrons.equals(other_kinetics.electrons)):
+ return False
+
+ return True
+
+ cpdef change_rate(self, double factor):
+ """
+ Changes A factor by multiplying it by a ``factor``.
+ """
+ self._A.value_si *= factor
+
+ def set_cantera_kinetics(self, ct_reaction, species_list):
+ """
+ Sets a cantera ElementaryReaction() object with the modified Arrhenius object
+ converted to an Arrhenius form.
+ """
+ raise NotImplementedError('set_cantera_kinetics() is not implemented for Marcus class kinetics.')
+
def get_w0(actions, rxn):
"""
calculates the w0 for Blower Masel kinetics by calculating wf (total bond energy of bonds formed)
@@ -1167,4 +1935,4 @@ def get_w0(actions, rxn):
return (wf + wb) / 2.0
def get_w0s(actions, rxns):
- return [get_w0(actions, rxn) for rxn in rxns]
+ return [get_w0(actions, rxn) for rxn in rxns]
\ No newline at end of file
diff --git a/rmgpy/kinetics/model.pxd b/rmgpy/kinetics/model.pxd
index 9fa24cb767..9f2fa3f28c 100644
--- a/rmgpy/kinetics/model.pxd
+++ b/rmgpy/kinetics/model.pxd
@@ -29,7 +29,7 @@ cimport numpy as np
from rmgpy.quantity cimport ScalarQuantity, ArrayQuantity
from rmgpy.kinetics.uncertainties cimport RateUncertainty
-
+from rmgpy.data.solvation import SoluteData
################################################################################
cpdef str get_rate_coefficient_units_from_reaction_order(n_gas, n_surf=?)
@@ -43,6 +43,7 @@ cdef class KineticsModel:
cdef public ScalarQuantity _Tmin, _Tmax
cdef public ScalarQuantity _Pmin, _Pmax
cdef public RateUncertainty uncertainty
+ cdef public object solute
cdef public str comment
diff --git a/rmgpy/kinetics/model.pyx b/rmgpy/kinetics/model.pyx
index 85760d1892..262b33142b 100644
--- a/rmgpy/kinetics/model.pyx
+++ b/rmgpy/kinetics/model.pyx
@@ -119,16 +119,18 @@ cdef class KineticsModel:
`Tmax` The maximum temperature at which the model is valid, or zero if unknown or undefined
`Pmin` The minimum pressure at which the model is valid, or zero if unknown or undefined
`Pmax` The maximum pressure at which the model is valid, or zero if unknown or undefined
+ `solute` Solute data for the transition state
`comment` Information about the model (e.g. its source)
=============== ============================================================
"""
- def __init__(self, Tmin=None, Tmax=None, Pmin=None, Pmax=None, uncertainty=None, comment=''):
+ def __init__(self, Tmin=None, Tmax=None, Pmin=None, Pmax=None, uncertainty=None, solute=None, comment=''):
self.Tmin = Tmin
self.Tmax = Tmax
self.Pmin = Pmin
self.Pmax = Pmax
+ self.solute = solute
self.uncertainty = uncertainty
self.comment = comment
@@ -137,14 +139,14 @@ cdef class KineticsModel:
Return a string representation that can be used to reconstruct the
KineticsModel object.
"""
- return 'KineticsModel(Tmin={0!r}, Tmax={1!r}, Pmin={2!r}, Pmax={3!r}, uncertainty={4!r}, comment="""{5}""")'.format(
- self.Tmin, self.Tmax, self.Pmin, self.Pmax, self.uncertainty, self.comment)
+ return 'KineticsModel(Tmin={0!r}, Tmax={1!r}, Pmin={2!r}, Pmax={3!r}, uncertainty={4!r}, solute={5!r}, comment="""{6}""")'.format(
+ self.Tmin, self.Tmax, self.Pmin, self.Pmax, self.uncertainty, self.solute, self.comment)
def __reduce__(self):
"""
A helper function used when pickling a KineticsModel object.
"""
- return (KineticsModel, (self.Tmin, self.Tmax, self.Pmin, self.Pmax, self.uncertainty, self.comment))
+ return (KineticsModel, (self.Tmin, self.Tmax, self.Pmin, self.Pmax, self.uncertainty, self.solute, self.comment))
property Tmin:
"""The minimum temperature at which the model is valid, or ``None`` if not defined."""
diff --git a/rmgpy/kinetics/surface.pxd b/rmgpy/kinetics/surface.pxd
index 8b43084d2a..0a834ea5a9 100644
--- a/rmgpy/kinetics/surface.pxd
+++ b/rmgpy/kinetics/surface.pxd
@@ -34,7 +34,7 @@ from rmgpy.quantity cimport ScalarQuantity, ArrayQuantity
################################################################################
cdef class StickingCoefficient(KineticsModel):
-
+
cdef public ScalarQuantity _A
cdef public ScalarQuantity _n
cdef public ScalarQuantity _Ea
@@ -50,7 +50,7 @@ cdef class StickingCoefficient(KineticsModel):
cpdef bint is_similar_to(self, KineticsModel other_kinetics) except -2
cpdef bint is_identical_to(self, KineticsModel other_kinetics) except -2
-
+
cpdef change_rate(self, double factor)
cpdef to_html(self)
@@ -71,10 +71,64 @@ cdef class StickingCoefficientBEP(KineticsModel):
################################################################################
cdef class SurfaceArrhenius(Arrhenius):
+
cdef public dict _coverage_dependence
- pass
+ cpdef SurfaceChargeTransfer to_surface_charge_transfer(self, double V0, double electrons=?)
+
################################################################################
cdef class SurfaceArrheniusBEP(ArrheniusEP):
cdef public dict _coverage_dependence
pass
+################################################################################
+cdef class SurfaceChargeTransfer(KineticsModel):
+
+ cdef public ScalarQuantity _A
+ cdef public ScalarQuantity _n
+ cdef public ScalarQuantity _Ea
+ cdef public ScalarQuantity _T0
+ cdef public ScalarQuantity _V0
+ cdef public ScalarQuantity _alpha
+ cdef public ScalarQuantity _electrons
+
+ cpdef double get_activation_energy_from_potential(self, double V=?, bint non_negative=?)
+
+ cpdef double get_rate_coefficient(self, double T, double V=?) except -1
+
+ cpdef change_rate(self, double factor)
+
+ cpdef change_t0(self, double T0)
+
+ cpdef change_v0(self, double V0)
+
+ cpdef fit_to_data(self, np.ndarray Tlist, np.ndarray klist, str kunits, double T0=?, np.ndarray weights=?, bint three_params=?)
+
+ cpdef bint is_identical_to(self, KineticsModel other_kinetics) except -2
+
+ cpdef SurfaceArrhenius to_surface_arrhenius(self)
+
+ cpdef SurfaceChargeTransferBEP to_surface_charge_transfer_bep(self, double dGrxn, double V0=?)
+
+################################################################################
+cdef class SurfaceChargeTransferBEP(KineticsModel):
+
+ cdef public ScalarQuantity _A
+ cdef public ScalarQuantity _n
+ cdef public ScalarQuantity _E0
+ cdef public ScalarQuantity _V0
+ cdef public ScalarQuantity _alpha
+ cdef public ScalarQuantity _electrons
+
+ cpdef change_v0(self, double V0)
+
+ cpdef double get_activation_energy(self, double dGrxn) except -1
+
+ cpdef double get_activation_energy_from_potential(self, double V, double dGrxn) except -1
+
+ cpdef double get_rate_coefficient_from_potential(self, double T, double V, double dGrxn) except -1
+
+ cpdef SurfaceChargeTransfer to_surface_charge_transfer(self, double dGrxn)
+
+ cpdef bint is_identical_to(self, KineticsModel other_kinetics) except -2
+
+ cpdef change_rate(self, double factor)
diff --git a/rmgpy/kinetics/surface.pyx b/rmgpy/kinetics/surface.pyx
index 120cfc006d..679f7bb338 100644
--- a/rmgpy/kinetics/surface.pyx
+++ b/rmgpy/kinetics/surface.pyx
@@ -570,6 +570,25 @@ cdef class SurfaceArrhenius(Arrhenius):
return (SurfaceArrhenius, (self.A, self.n, self.Ea, self.T0, self.Tmin, self.Tmax, self.Pmin, self.Pmax,
self.coverage_dependence, self.uncertainty, self.comment))
+ cpdef SurfaceChargeTransfer to_surface_charge_transfer(self, double V0, double electrons=-1):
+ """
+ Return an :class:`SurfaceChargeTransfer` instance of the kinetics model with reversible
+ potential `V0` in Volts and electron stochiometric coeff ` electrons`
+ """
+ return SurfaceChargeTransfer(
+ A=self.A,
+ n=self.n,
+ electrons= electrons,
+ Ea=self.Ea,
+ V0=(V0,'V'),
+ T0=(1, "K"),
+ Tmin=self.Tmin,
+ Tmax=self.Tmax,
+ uncertainty = self.uncertainty,
+ comment=self.comment,
+ )
+
+
################################################################################
cdef class SurfaceArrheniusBEP(ArrheniusEP):
@@ -684,3 +703,504 @@ cdef class SurfaceArrheniusBEP(ArrheniusEP):
coverage_dependence=self.coverage_dependence,
comment=self.comment,
)
+
+################################################################################
+
+cdef class SurfaceChargeTransfer(KineticsModel):
+
+ """
+ A kinetics model for surface charge transfer reactions
+
+ It is very similar to the :class:`SurfaceArrhenius`, but the Ea is potential-dependent
+
+
+ The attributes are:
+
+ =============== =============================================================
+ Attribute Description
+ =============== =============================================================
+ `A` The preexponential factor
+ `T0` The reference temperature
+ `n` The temperature exponent
+ `Ea` The activation energy
+ `electrons` The stochiometry coeff for electrons (negative if reactant, positive if product)
+ `V0` The reference potential
+ `alpha` The charge transfer coefficient
+ `Tmin` The minimum temperature at which the model is valid, or zero if unknown or undefined
+ `Tmax` The maximum temperature at which the model is valid, or zero if unknown or undefined
+ `Pmin` The minimum pressure at which the model is valid, or zero if unknown or undefined
+ `Pmax` The maximum pressure at which the model is valid, or zero if unknown or undefined
+ `comment` Information about the model (e.g. its source)
+ =============== =============================================================
+
+ """
+
+ def __init__(self, A=None, n=0.0, Ea=None, V0=None, alpha=0.5, electrons=-1, T0=(1.0, "K"), Tmin=None, Tmax=None,
+ Pmin=None, Pmax=None, uncertainty=None, comment=''):
+
+ KineticsModel.__init__(self, Tmin=Tmin, Tmax=Tmax, Pmin=Pmin, Pmax=Pmax, uncertainty=uncertainty,
+ comment=comment)
+
+ self.alpha = alpha
+ self.A = A
+ self.n = n
+ self.Ea = Ea
+ self.T0 = T0
+ self.electrons = electrons
+ self.V0 = V0
+
+ def __repr__(self):
+ """
+ Return a string representation that can be used to reconstruct the
+ Arrhenius object.
+ """
+ string = 'SurfaceChargeTransfer(A={0!r}, n={1!r}, Ea={2!r}, V0={3!r}, alpha={4!r}, electrons={5!r}, T0={6!r}'.format(
+ self.A, self.n, self.Ea, self.V0, self.alpha, self.electrons, self.T0)
+ if self.Tmin is not None: string += ', Tmin={0!r}'.format(self.Tmin)
+ if self.Tmax is not None: string += ', Tmax={0!r}'.format(self.Tmax)
+ if self.Pmin is not None: string += ', Pmin={0!r}'.format(self.Pmin)
+ if self.Pmax is not None: string += ', Pmax={0!r}'.format(self.Pmax)
+ if self.uncertainty: string += ', uncertainty={0!r}'.format(self.uncertainty)
+ if self.comment != '': string += ', comment="""{0}"""'.format(self.comment)
+ string += ')'
+ return string
+
+ def __reduce__(self):
+ """
+ A helper function used when pickling a SurfaceChargeTransfer object.
+ """
+ return (SurfaceChargeTransfer, (self.A, self.n, self.Ea, self.V0, self.alpha, self.electrons, self.T0, self.Tmin, self.Tmax, self.Pmin, self.Pmax,
+ self.uncertainty, self.comment))
+
+ property A:
+ """The preexponential factor."""
+ def __get__(self):
+ return self._A
+ def __set__(self, value):
+ self._A = quantity.SurfaceRateCoefficient(value)
+
+ property n:
+ """The temperature exponent."""
+ def __get__(self):
+ return self._n
+ def __set__(self, value):
+ self._n = quantity.Dimensionless(value)
+
+ property Ea:
+ """The activation energy."""
+ def __get__(self):
+ return self._Ea
+ def __set__(self, value):
+ self._Ea = quantity.Energy(value)
+
+ property T0:
+ """The reference temperature."""
+ def __get__(self):
+ return self._T0
+ def __set__(self, value):
+ self._T0 = quantity.Temperature(value)
+
+ property V0:
+ """The reference potential."""
+ def __get__(self):
+ return self._V0
+ def __set__(self, value):
+ self._V0 = quantity.Potential(value)
+
+ property electrons:
+ """The number of electrons transferred."""
+ def __get__(self):
+ return self._electrons
+ def __set__(self, value):
+ self._electrons = quantity.Dimensionless(value)
+
+ property alpha:
+ """The charge transfer coefficient."""
+ def __get__(self):
+ return self._alpha
+ def __set__(self, value):
+ self._alpha = quantity.Dimensionless(value)
+
+ cpdef double get_activation_energy_from_potential(self, double V=0.0, bint non_negative=True):
+ """
+ Return the effective activation energy (in J/mol) at specificed potential (in Volts).
+ """
+ cdef double electrons, alpha, Ea, V0
+
+ electrons = self._electrons.value_si
+ alpha = self._alpha.value_si
+ Ea = self._Ea.value_si
+ V0 = self._V0.value_si
+
+ Ea -= alpha * electrons * constants.F * (V-V0)
+
+ if non_negative is True:
+ if Ea < 0:
+ Ea = 0.0
+
+ return Ea
+
+ cpdef double get_rate_coefficient(self, double T, double V=0.0) except -1:
+ """
+ Return the rate coefficient in the appropriate combination of m^2,
+ mol, and s at temperature `T` in K.
+ """
+ cdef double A, n, V0, T0, Ea
+
+ A = self._A.value_si
+ n = self._n.value_si
+ V0 = self._V0.value_si
+ T0 = self._T0.value_si
+
+ if V != V0:
+ Ea = self.get_activation_energy_from_potential(V)
+ else:
+ Ea = self._Ea.value_si
+
+ return A * (T / T0) ** n * exp(-Ea / (constants.R * T))
+
+ cpdef change_t0(self, double T0):
+ """
+ Changes the reference temperature used in the exponent to `T0` in K,
+ and adjusts the preexponential factor accordingly.
+ """
+ self._A.value_si /= (self._T0.value_si / T0) ** self._n.value_si
+ self._T0.value_si = T0
+
+ cpdef change_v0(self, double V0):
+ """
+ Changes the reference potential to `V0` in volts, and adjusts the
+ activation energy `Ea` accordingly.
+ """
+
+ self._Ea.value_si = self.get_activation_energy_from_potential(V0)
+ self._V0.value_si = V0
+
+ cpdef fit_to_data(self, np.ndarray Tlist, np.ndarray klist, str kunits, double T0=1,
+ np.ndarray weights=None, bint three_params=False):
+ """
+ Fit the Arrhenius parameters to a set of rate coefficient data `klist`
+ in units of `kunits` corresponding to a set of temperatures `Tlist` in
+ K. A linear least-squares fit is used, which guarantees that the
+ resulting parameters provide the best possible approximation to the
+ data.
+ """
+ import scipy.stats
+ if not all(np.isfinite(klist)):
+ raise ValueError("Rates must all be finite, not inf or NaN")
+ if any(klist<0):
+ if not all(klist<0):
+ raise ValueError("Rates must all be positive or all be negative.")
+ rate_sign_multiplier = -1
+ klist = -1 * klist
+ else:
+ rate_sign_multiplier = 1
+
+ assert len(Tlist) == len(klist), "length of temperatures and rates must be the same"
+ if len(Tlist) < 3 + three_params:
+ raise KineticsError('Not enough degrees of freedom to fit this Arrhenius expression')
+ if three_params:
+ A = np.zeros((len(Tlist), 3), np.float64)
+ A[:, 0] = np.ones_like(Tlist)
+ A[:, 1] = np.log(Tlist / T0)
+ A[:, 2] = -1.0 / constants.R / Tlist
+ else:
+ A = np.zeros((len(Tlist), 2), np.float64)
+ A[:, 0] = np.ones_like(Tlist)
+ A[:, 1] = -1.0 / constants.R / Tlist
+ b = np.log(klist)
+ if weights is not None:
+ for n in range(b.size):
+ A[n, :] *= weights[n]
+ b[n] *= weights[n]
+ x, residues, rank, s = np.linalg.lstsq(A, b, rcond=RCOND)
+
+ # Determine covarianace matrix to obtain parameter uncertainties
+ count = klist.size
+ cov = residues[0] / (count - 3) * np.linalg.inv(np.dot(A.T, A))
+ t = scipy.stats.t.ppf(0.975, count - 3)
+
+ if not three_params:
+ x = np.array([x[0], 0, x[1]])
+ cov = np.array([[cov[0, 0], 0, cov[0, 1]], [0, 0, 0], [cov[1, 0], 0, cov[1, 1]]])
+
+ self.A = (rate_sign_multiplier * exp(x[0]), kunits)
+ self.n = x[1]
+ self.Ea = (x[2] * 0.001, "kJ/mol")
+ self.T0 = (T0, "K")
+ self.Tmin = (np.min(Tlist), "K")
+ self.Tmax = (np.max(Tlist), "K")
+ self.comment = 'Fitted to {0:d} data points; dA = *|/ {1:g}, dn = +|- {2:g}, dEa = +|- {3:g} kJ/mol'.format(
+ len(Tlist),
+ exp(sqrt(cov[0, 0])),
+ sqrt(cov[1, 1]),
+ sqrt(cov[2, 2]) * 0.001,
+ )
+
+ return self
+
+ cpdef bint is_identical_to(self, KineticsModel other_kinetics) except -2:
+ """
+ Returns ``True`` if kinetics matches that of another kinetics model. Must match temperature
+ and pressure range of kinetics model, as well as parameters: A, n, Ea, T0. (Shouldn't have pressure
+ range if it's Arrhenius.) Otherwise returns ``False``.
+ """
+ if not isinstance(other_kinetics, SurfaceChargeTransfer):
+ return False
+ if not KineticsModel.is_identical_to(self, other_kinetics):
+ return False
+ if (not self.A.equals(other_kinetics.A) or not self.n.equals(other_kinetics.n)
+ or not self.Ea.equals(other_kinetics.Ea) or not self.T0.equals(other_kinetics.T0)
+ or not self.alpha.equals(other_kinetics.alpha) or not self.electrons.equals(other_kinetics.electrons)
+ or not self.V0.equals(other_kinetics.V0)):
+ return False
+
+ return True
+
+ cpdef change_rate(self, double factor):
+ """
+ Changes A factor in Arrhenius expression by multiplying it by a ``factor``.
+ """
+ self._A.value_si *= factor
+
+ cpdef SurfaceArrhenius to_surface_arrhenius(self):
+ """
+ Return an :class:`SurfaceArrhenius` instance of the kinetics model
+ """
+ return SurfaceArrhenius(
+ A=self.A,
+ n=self.n,
+ Ea=self.Ea,
+ T0=(1, "K"),
+ Tmin=self.Tmin,
+ Tmax=self.Tmax,
+ uncertainty = self.uncertainty,
+ comment=self.comment,
+ )
+
+ cpdef SurfaceChargeTransferBEP to_surface_charge_transfer_bep(self, double dGrxn, double V0=0.0):
+ """
+ Converts an SurfaceChargeTransfer object to SurfaceChargeTransferBEP
+ """
+ cdef double E0
+
+ self.change_t0(1)
+ self.change_v0(V0)
+
+ E0 = self.Ea.value_si - self._alpha.value_si * dGrxn
+ if E0 < 0:
+ E0 = 0.0
+
+ aep = SurfaceChargeTransferBEP(
+ A=self.A,
+ electrons=self.electrons,
+ n=self.n,
+ alpha=self.alpha,
+ V0=self.V0,
+ E0=(E0, 'J/mol'),
+ Tmin=self.Tmin,
+ Tmax=self.Tmax,
+ Pmin=self.Pmin,
+ Pmax=self.Pmax,
+ uncertainty=self.uncertainty,
+ comment=self.comment)
+ return aep
+
+cdef class SurfaceChargeTransferBEP(KineticsModel):
+ """
+ A kinetics model based on the (modified) Arrhenius equation, using the
+ Evans-Polanyi equation to determine the activation energy. The attributes
+ are:
+
+ =============== =============================================================
+ Attribute Description
+ =============== =============================================================
+ `A` The preexponential factor
+ `n` The temperature exponent
+ `E0` The activation energy at equilibiurm
+ ` electrons` The stochiometry coeff for electrons (negative if reactant, positive if product)
+ `V0` The reference potential
+ `alpha` The charge transfer coefficient
+ `Tmin` The minimum temperature at which the model is valid, or zero if unknown or undefined
+ `Tmax` The maximum temperature at which the model is valid, or zero if unknown or undefined
+ `Pmin` The minimum pressure at which the model is valid, or zero if unknown or undefined
+ `Pmax` The maximum pressure at which the model is valid, or zero if unknown or undefined
+ `comment` Information about the model (e.g. its source)
+ =============== =============================================================
+
+ """
+
+ def __init__(self, A=None, n=0.0, E0=None, V0=(0.0,'V'), alpha=0.5, electrons=-1, Tmin=None, Tmax=None,
+ Pmin=None, Pmax=None, uncertainty=None, comment=''):
+
+ KineticsModel.__init__(self, Tmin=Tmin, Tmax=Tmax, Pmin=Pmin, Pmax=Pmax, uncertainty=uncertainty,
+ comment=comment)
+
+ self.alpha = alpha
+ self.A = A
+ self.n = n
+ self.E0 = E0
+ self.electrons = electrons
+ self.V0 = V0
+
+ def __repr__(self):
+ """
+ Return a string representation that can be used to reconstruct the
+ Arrhenius object.
+ """
+ string = 'SurfaceChargeTransferBEP(A={0!r}, n={1!r}, E0={2!r}, V0={3!r}, alpha={4!r}, electrons={5!r}'.format(
+ self.A, self.n, self.E0, self.V0, self.alpha, self.electrons)
+ if self.Tmin is not None: string += ', Tmin={0!r}'.format(self.Tmin)
+ if self.Tmax is not None: string += ', Tmax={0!r}'.format(self.Tmax)
+ if self.Pmin is not None: string += ', Pmin={0!r}'.format(self.Pmin)
+ if self.Pmax is not None: string += ', Pmax={0!r}'.format(self.Pmax)
+ if self.uncertainty: string += ', uncertainty={0!r}'.format(self.uncertainty)
+ if self.comment != '': string += ', comment="""{0}"""'.format(self.comment)
+ string += ')'
+ return string
+
+ def __reduce__(self):
+ """
+ A helper function used when pickling a SurfaceChargeTransfer object.
+ """
+ return (SurfaceChargeTransferBEP, (self.A, self.n, self.E0, self.V0, self.alpha, self.electrons, self.Tmin, self.Tmax, self.Pmin, self.Pmax,
+ self.uncertainty, self.comment))
+
+ property A:
+ """The preexponential factor."""
+ def __get__(self):
+ return self._A
+ def __set__(self, value):
+ self._A = quantity.SurfaceRateCoefficient(value)
+
+ property n:
+ """The temperature exponent."""
+ def __get__(self):
+ return self._n
+ def __set__(self, value):
+ self._n = quantity.Dimensionless(value)
+
+ property E0:
+ """The activation energy."""
+ def __get__(self):
+ return self._E0
+ def __set__(self, value):
+ self._E0 = quantity.Energy(value)
+
+ property V0:
+ """The reference potential."""
+ def __get__(self):
+ return self._V0
+ def __set__(self, value):
+ self._V0 = quantity.Potential(value)
+
+ property electrons:
+ """The number of electrons transferred."""
+ def __get__(self):
+ return self._electrons
+ def __set__(self, value):
+ self._electrons = quantity.Dimensionless(value)
+
+ property alpha:
+ """The charge transfer coefficient."""
+ def __get__(self):
+ return self._alpha
+ def __set__(self, value):
+ self._alpha = quantity.Dimensionless(value)
+
+ cpdef change_v0(self, double V0):
+ """
+ Changes the reference potential to `V0` in volts, and adjusts the
+ activation energy `E0` accordingly.
+ """
+
+ self._E0.value_si = self.get_activation_energy_from_potential(V0,0.0)
+ self._V0.value_si = V0
+
+ cpdef double get_activation_energy(self, double dGrxn) except -1:
+ """
+ Return the activation energy in J/mol corresponding to the given
+ free energy of reaction `dGrxn` in J/mol at the reference potential.
+ """
+ cdef double Ea
+ Ea = self._alpha.value_si * dGrxn + self._E0.value_si
+
+ if Ea < 0.0:
+ Ea = 0.0
+ elif dGrxn > 0.0 and Ea < dGrxn:
+ Ea = dGrxn
+
+ return Ea
+
+ cpdef double get_activation_energy_from_potential(self, double V, double dGrxn) except -1:
+ """
+ Return the activation energy in J/mol corresponding to the given
+ free energy of reaction `dGrxn` in J/mol.
+ """
+ cdef double Ea
+ Ea = self.get_activation_energy(dGrxn)
+ Ea -= self._alpha.value_si * self._electrons.value_si * constants.F * (V-self._V0.value_si)
+
+ return Ea
+
+ cpdef double get_rate_coefficient_from_potential(self, double T, double V, double dGrxn) except -1:
+ """
+ Return the rate coefficient in the appropriate combination of m^3,
+ mol, and s at temperature `T` in K, potential `V` in volts, and
+ free of reaction `dGrxn` in J/mol.
+ """
+ cdef double A, n, Ea
+ Ea = self.get_activation_energy_from_potential(V,dGrxn)
+ A = self._A.value_si
+ n = self._n.value_si
+ return A * T ** n * exp(-Ea / (constants.R * T))
+
+ cpdef SurfaceChargeTransfer to_surface_charge_transfer(self, double dGrxn):
+ """
+ Return an :class:`SurfaceChargeTransfer` instance of the kinetics model using the
+ given free energy of reaction `dGrxn` to determine the activation energy.
+ """
+ return SurfaceChargeTransfer(
+ A=self.A,
+ n=self.n,
+ electrons=self.electrons,
+ Ea=(self.get_activation_energy(dGrxn) * 0.001, "kJ/mol"),
+ V0=self.V0,
+ T0=(1, "K"),
+ Tmin=self.Tmin,
+ Tmax=self.Tmax,
+ Pmin=self.Pmin,
+ Pmax=self.Pmax,
+ uncertainty=self.uncertainty,
+ comment=self.comment,
+ )
+
+ cpdef bint is_identical_to(self, KineticsModel other_kinetics) except -2:
+ """
+ Returns ``True`` if kinetics matches that of another kinetics model. Must match temperature
+ and pressure range of kinetics model, as well as parameters: A, n, Ea, T0. (Shouldn't have pressure
+ range if it's Arrhenius.) Otherwise returns ``False``.
+ """
+ if not isinstance(other_kinetics, SurfaceChargeTransferBEP):
+ return False
+ if not KineticsModel.is_identical_to(self, other_kinetics):
+ return False
+ if (not self.A.equals(other_kinetics.A) or not self.n.equals(other_kinetics.n)
+ or not self.E0.equals(other_kinetics.E0) or not self.alpha.equals(other_kinetics.alpha)
+ or not self.electrons.equals(other_kinetics.electrons) or not self.V0.equals(other_kinetics.V0)):
+ return False
+
+ return True
+
+ cpdef change_rate(self, double factor):
+ """
+ Changes A factor by multiplying it by a ``factor``.
+ """
+ self._A.value_si *= factor
+
+ def set_cantera_kinetics(self, ct_reaction, species_list):
+ """
+ Sets a cantera ElementaryReaction() object with the modified Arrhenius object
+ converted to an Arrhenius form.
+ """
+ raise NotImplementedError('set_cantera_kinetics() is not implemented for ArrheniusEP class kinetics.')
diff --git a/rmgpy/molecule/adjlist.py b/rmgpy/molecule/adjlist.py
index 1ebe0ff110..852d7bc9d0 100644
--- a/rmgpy/molecule/adjlist.py
+++ b/rmgpy/molecule/adjlist.py
@@ -92,7 +92,7 @@ def check_partial_charge(atom):
the theoretical one:
"""
- if atom.symbol in {'X','L','R'}:
+ if atom.symbol in {'X','L','R','e','H+','Li'}:
return # because we can't check it.
valence = PeriodicSystem.valence_electrons[atom.symbol]
diff --git a/rmgpy/molecule/atomtype.pxd b/rmgpy/molecule/atomtype.pxd
index 573e761645..dbd168c4a0 100644
--- a/rmgpy/molecule/atomtype.pxd
+++ b/rmgpy/molecule/atomtype.pxd
@@ -39,6 +39,8 @@ cdef class AtomType:
cdef public list decrement_radical
cdef public list increment_lone_pair
cdef public list decrement_lone_pair
+ cdef public list increment_charge
+ cdef public list decrement_charge
cdef public list single
cdef public list all_double
diff --git a/rmgpy/molecule/atomtype.py b/rmgpy/molecule/atomtype.py
index 7ee6a1fe98..960648c6ee 100644
--- a/rmgpy/molecule/atomtype.py
+++ b/rmgpy/molecule/atomtype.py
@@ -30,7 +30,7 @@
"""
This module defines the atom types that are available for representing
molecular functional groups and substructure patterns. Each available atom type
-is defined as an instance of the :class:`AtomType` class. The atom types
+is defined as an instance of the :class:`AtomType` class. The atom types
themselves are available in the ``ATOMTYPES`` module-level variable, or as
the return value from the :meth:`get_atomtype()` method.
@@ -65,6 +65,8 @@ class AtomType:
`break_bond` ``list`` The atom type(s) that result when an existing single bond to this atom type is broken
`increment_radical` ``list`` The atom type(s) that result when the number of radical electrons is incremented
`decrement_radical` ``list`` The atom type(s) that result when the number of radical electrons is decremented
+ `increment_charge` ``list`` The atom type(s) that result when the number of radical electrons is decremented and charge is incremented
+ `decrement_charge` ``list`` The atom type(s) that result when the number of radical electrons is incremented and charge is decremented
`increment_lone_pair` ``list`` The atom type(s) that result when the number of lone electron pairs is incremented
`decrement_lone_pair` ``list`` The atom type(s) that result when the number of lone electron pairs is decremented
@@ -106,6 +108,8 @@ def __init__(self, label='',
self.break_bond = []
self.increment_radical = []
self.decrement_radical = []
+ self.increment_charge = []
+ self.decrement_charge = []
self.increment_lone_pair = []
self.decrement_lone_pair = []
self.single = single or []
@@ -136,6 +140,8 @@ def __reduce__(self):
'break_bond': self.break_bond,
'increment_radical': self.increment_radical,
'decrement_radical': self.decrement_radical,
+ 'increment_charge': self.increment_charge,
+ 'decrement_charge': self.decrement_charge,
'increment_lone_pair': self.increment_lone_pair,
'decrement_lone_pair': self.decrement_lone_pair,
'single': self.single,
@@ -164,6 +170,8 @@ def __setstate__(self, d):
self.break_bond = d['break_bond']
self.increment_radical = d['increment_radical']
self.decrement_radical = d['decrement_radical']
+ self.increment_charge = d['increment_charge']
+ self.decrement_charge = d['decrement_charge']
self.increment_lone_pair = d['increment_lone_pair']
self.decrement_lone_pair = d['decrement_lone_pair']
self.single = d['single']
@@ -178,7 +186,7 @@ def __setstate__(self, d):
self.charge = d['charge']
def set_actions(self, increment_bond, decrement_bond, form_bond, break_bond, increment_radical, decrement_radical,
- increment_lone_pair, decrement_lone_pair):
+ increment_lone_pair, decrement_lone_pair, increment_charge, decrement_charge):
self.increment_bond = increment_bond
self.decrement_bond = decrement_bond
self.form_bond = form_bond
@@ -187,6 +195,8 @@ def set_actions(self, increment_bond, decrement_bond, form_bond, break_bond, inc
self.decrement_radical = decrement_radical
self.increment_lone_pair = increment_lone_pair
self.decrement_lone_pair = decrement_lone_pair
+ self.increment_charge = increment_charge
+ self.decrement_charge = decrement_charge
def equivalent(self, other):
"""
@@ -202,7 +212,7 @@ def is_specific_case_of(self, other):
atom type `atomType2` or ``False`` otherwise.
"""
return self is other or self in other.specific
-
+
def get_features(self):
"""
Returns a list of the features that are checked to determine atomtype
@@ -240,6 +250,9 @@ def get_features(self):
ATOMTYPES = {}
+# Electron
+ATOMTYPES['e'] = AtomType(label='e', generic=[], specific=[], lone_pairs=[0], charge=[-1])
+
ATOMTYPES['Rx'] = AtomType(label='Rx', generic=[], specific=[
'H',
'R',
@@ -290,7 +303,8 @@ def get_features(self):
# Non-surface atomTypes, R being the most generic:
ATOMTYPES['R'] = AtomType(label='R', generic=['Rx'], specific=[
- 'H',
+ 'H','H0','H+',
+ 'Li','Li0','Li+',
'R!H',
'R!H!Val7',
'Val4','Val5','Val6','Val7',
@@ -309,6 +323,7 @@ def get_features(self):
ATOMTYPES['R!H'] = AtomType(label='R!H', generic=['R', 'Rx', 'Rx!H'], specific=[
'Val4','Val5','Val6','Val7',
'He','Ne','Ar',
+ 'Li','Li0','Li+',
'C','Ca','Cs','Csc','Cd','CO','CS','Cdd','Cdc','Ctc','Ct','Cb','Cbf','Cq','C2s','C2sc','C2d','C2dc','C2tc',
'N','N0sc','N1s','N1sc','N1dc','N3s','N3sc','N3d','N3t','N3b','N5sc','N5dc','N5ddc','N5dddc','N5tc','N5b','N5bd',
'O','Oa','O0sc','O2s','O2sc','O2d','O4sc','O4dc','O4tc','O4b',
@@ -323,6 +338,7 @@ def get_features(self):
ATOMTYPES['R!H!Val7'] = AtomType(label='R!H!Val7', generic=['R', 'Rx', 'Rx!H'], specific=[
'Val4','Val5','Val6',
'He','Ne','Ar',
+ 'Li','Li0','Li+',
'C','Ca','Cs','Csc','Cd','CO','CS','Cdd','Cdc','Ctc','Ct','Cb','Cbf','Cq','C2s','C2sc','C2d','C2dc','C2tc',
'N','N0sc','N1s','N1sc','N1dc','N3s','N3sc','N3d','N3t','N3b','N5sc','N5dc','N5ddc','N5dddc','N5tc','N5b','N5bd',
'O','Oa','O0sc','O2s','O2sc','O2d','O4sc','O4dc','O4tc','O4b',
@@ -349,7 +365,19 @@ def get_features(self):
'I','I1s',
'F','F1s'])
-ATOMTYPES['H'] = AtomType('H', generic=['Rx','R'], specific=[])
+
+ATOMTYPES['H'] = AtomType('H', generic=['Rx','R'], specific=['H0','H+'], charge=[0,+1])
+ATOMTYPES['H0'] = AtomType('H0', generic=['R','H'], specific=[], single=[0,1], all_double=[0], r_double=[0], o_double=[0], s_double=[0], triple=[0],
+ quadruple=[0], benzene=[0], lone_pairs=[0], charge=[0])
+ATOMTYPES['H+'] = AtomType('H+', generic=['R','H'], specific=[], single=[0], all_double=[0], r_double=[0], o_double=[0], s_double=[0], triple=[0],
+ quadruple=[0], benzene=[0], lone_pairs=[0], charge=[+1])
+
+ATOMTYPES['Li'] = AtomType('Li', generic=['R', 'R!H', 'R!H!Val7'], specific=['Li0','Li+'],
+ single=[0,1], all_double=[0], r_double=[0], o_double=[0], s_double=[0], triple=[0], quadruple=[0], benzene=[0], lone_pairs=[0], charge=[0,1])
+ATOMTYPES['Li0'] = AtomType('Li', generic=['Li','R', 'R!H', 'R!H!Val7'], specific=[],
+ single=[0,1], all_double=[0], r_double=[0], o_double=[0], s_double=[0], triple=[0], quadruple=[0], benzene=[0], lone_pairs=[0], charge=[0])
+ATOMTYPES['Li+'] = AtomType('Li+', generic=['Li','R', 'R!H', 'R!H!Val7'], specific=[],
+ single=[0], all_double=[0], r_double=[0], o_double=[0], s_double=[0], triple=[0], quadruple=[0], benzene=[0], lone_pairs=[0], charge=[1])
ATOMTYPES['He'] = AtomType('He', generic=['R', 'R!H', 'R!H!Val7', 'Rx', 'Rx!H'], specific=[])
ATOMTYPES['Ne'] = AtomType('Ne', generic=['R', 'R!H', 'R!H!Val7', 'Rx', 'Rx!H'], specific=[])
@@ -670,161 +698,168 @@ def get_features(self):
single=[0,1], all_double=[0], r_double=[], o_double=[], s_double=[], triple=[0], quadruple=[0], benzene=[0], lone_pairs=[3], charge=[0])
# examples for F1s: HF, [F], FO, CH3F, F2
-ATOMTYPES['Rx'].set_actions(increment_bond=['Rx'], decrement_bond=['Rx'], form_bond=['Rx'], break_bond=['Rx'], increment_radical=['Rx'], decrement_radical=['Rx'], increment_lone_pair=['Rx'], decrement_lone_pair=['Rx'])
-ATOMTYPES['Rx!H'].set_actions(increment_bond=['Rx!H'], decrement_bond=['Rx!H'], form_bond=['Rx!H'], break_bond=['Rx!H'], increment_radical=['Rx!H'], decrement_radical=['Rx!H'], increment_lone_pair=['Rx!H'], decrement_lone_pair=['Rx!H'])
-ATOMTYPES['X'].set_actions(increment_bond=['X'], decrement_bond=['X'], form_bond=['X'], break_bond=['X'], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Xv'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['Xo'], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Xo'].set_actions(increment_bond=['Xo'], decrement_bond=['Xo'], form_bond=[], break_bond=['Xv'], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-
-ATOMTYPES['R'].set_actions(increment_bond=['R'], decrement_bond=['R'], form_bond=['R'], break_bond=['R'], increment_radical=['R'], decrement_radical=['R'], increment_lone_pair=['R'], decrement_lone_pair=['R'])
-ATOMTYPES['R!H'].set_actions(increment_bond=['R!H'], decrement_bond=['R!H'], form_bond=['R!H'], break_bond=['R!H'], increment_radical=['R!H'], decrement_radical=['R!H'], increment_lone_pair=['R!H'], decrement_lone_pair=['R!H'])
-ATOMTYPES['R!H!Val7'].set_actions(increment_bond=['R!H!Val7'], decrement_bond=['R!H!Val7'], form_bond=['R!H!Val7'], break_bond=['R!H!Val7'], increment_radical=['R!H!Val7'], decrement_radical=['R!H!Val7'], increment_lone_pair=['R!H!Val7'], decrement_lone_pair=['R!H!Val7'])
-ATOMTYPES['Val4'].set_actions(increment_bond=['Val4'], decrement_bond=['Val4'], form_bond=['Val4'], break_bond=['Val4'], increment_radical=['Val4'], decrement_radical=['Val4'], increment_lone_pair=['Val4'], decrement_lone_pair=['Val4'])
-ATOMTYPES['Val5'].set_actions(increment_bond=['Val5'], decrement_bond=['Val5'], form_bond=['Val5'], break_bond=['Val5'], increment_radical=['Val5'], decrement_radical=['Val5'], increment_lone_pair=['Val5'], decrement_lone_pair=['Val5'])
-ATOMTYPES['Val6'].set_actions(increment_bond=['Val6'], decrement_bond=['Val6'], form_bond=['Val6'], break_bond=['Val6'], increment_radical=['Val6'], decrement_radical=['Val6'], increment_lone_pair=['Val6'], decrement_lone_pair=['Val6'])
-ATOMTYPES['Val7'].set_actions(increment_bond=['Val7'], decrement_bond=['Val7'], form_bond=['Val7'], break_bond=['Val7'], increment_radical=['Val7'], decrement_radical=['Val7'], increment_lone_pair=['Val7'], decrement_lone_pair=['Val7'])
-
-ATOMTYPES['H'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['H'], break_bond=['H'], increment_radical=['H'], decrement_radical=['H'], increment_lone_pair=[], decrement_lone_pair=[])
-
-ATOMTYPES['He'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=['He'], decrement_radical=['He'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Ne'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=['Ne'], decrement_radical=['Ne'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Ar'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-
-ATOMTYPES['C'].set_actions(increment_bond=['C'], decrement_bond=['C'], form_bond=['C'], break_bond=['C'], increment_radical=['C'], decrement_radical=['C'], increment_lone_pair=['C'], decrement_lone_pair=['C'])
-ATOMTYPES['Ca'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['C2s'])
-ATOMTYPES['Cs'].set_actions(increment_bond=['Cd', 'CO', 'CS'], decrement_bond=[], form_bond=['Cs', 'Csc'], break_bond=['Cs'], increment_radical=['Cs'], decrement_radical=['Cs'], increment_lone_pair=['C2s'], decrement_lone_pair=['C2s'])
-ATOMTYPES['Csc'].set_actions(increment_bond=['Cdc'], decrement_bond=[], form_bond=['Csc'], break_bond=['Csc', 'Cs'], increment_radical=['Csc'], decrement_radical=['Csc'], increment_lone_pair=['C2sc'], decrement_lone_pair=['C2sc'])
-ATOMTYPES['Cd'].set_actions(increment_bond=['Cdd', 'Ct', 'C2tc'], decrement_bond=['Cs'], form_bond=['Cd', 'Cdc'], break_bond=['Cd'], increment_radical=['Cd'], decrement_radical=['Cd'], increment_lone_pair=['C2d'], decrement_lone_pair=[])
-ATOMTYPES['Cdc'].set_actions(increment_bond=['Ctc'], decrement_bond=['Csc'], form_bond=['Cdc'], break_bond=['Cdc', 'Cd', 'CO', 'CS'], increment_radical=['Cdc'], decrement_radical=['Cdc'], increment_lone_pair=['C2dc'], decrement_lone_pair=[])
-ATOMTYPES['CO'].set_actions(increment_bond=['Cdd', 'C2tc'], decrement_bond=['Cs'], form_bond=['CO', 'Cdc'], break_bond=['CO'], increment_radical=['CO'], decrement_radical=['CO'], increment_lone_pair=['C2d'], decrement_lone_pair=[])
-ATOMTYPES['CS'].set_actions(increment_bond=['Cdd', 'C2tc'], decrement_bond=['Cs'], form_bond=['CS', 'Cdc'], break_bond=['CS'], increment_radical=['CS'], decrement_radical=['CS'], increment_lone_pair=['C2d'], decrement_lone_pair=[])
-ATOMTYPES['Cdd'].set_actions(increment_bond=[], decrement_bond=['Cd', 'CO', 'CS'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Ct'].set_actions(increment_bond=['Cq'], decrement_bond=['Cd', 'CO', 'CS'], form_bond=['Ct'], break_bond=['Ct'], increment_radical=['Ct'], decrement_radical=['Ct'], increment_lone_pair=['C2tc'], decrement_lone_pair=[])
-ATOMTYPES['Ctc'].set_actions(increment_bond=[], decrement_bond=['Cdc'], form_bond=['Ct'], break_bond=[], increment_radical=['Ctc'], decrement_radical=['Ctc'], increment_lone_pair=['C2tc'], decrement_lone_pair=[])
-ATOMTYPES['Cb'].set_actions(increment_bond=['Cbf'], decrement_bond=[], form_bond=['Cb'], break_bond=['Cb'], increment_radical=['Cb'], decrement_radical=['Cb'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Cbf'].set_actions(increment_bond=[], decrement_bond=['Cb'], form_bond=[], break_bond=['Cb'], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['C2s'].set_actions(increment_bond=['C2d'], decrement_bond=[], form_bond=['C2s'], break_bond=['C2s'], increment_radical=['C2s'], decrement_radical=['C2s'], increment_lone_pair=['Ca'], decrement_lone_pair=['Cs'])
-ATOMTYPES['C2sc'].set_actions(increment_bond=['C2dc'], decrement_bond=[], form_bond=['C2sc'], break_bond=['C2sc'], increment_radical=['C2sc'], decrement_radical=['C2sc'], increment_lone_pair=[], decrement_lone_pair=['Cs'])
-ATOMTYPES['C2d'].set_actions(increment_bond=['C2tc'], decrement_bond=['C2s'], form_bond=['C2dc'], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['Cd', 'CO', 'CS'])
-ATOMTYPES['C2dc'].set_actions(increment_bond=[], decrement_bond=['C2sc'], form_bond=[], break_bond=['C2d'], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['Cdc'])
-ATOMTYPES['C2tc'].set_actions(increment_bond=[], decrement_bond=['C2d'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['Ct','Ctc'])
-ATOMTYPES['Cq'].set_actions(increment_bond=[], decrement_bond=['Ct'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-
-ATOMTYPES['N'].set_actions(increment_bond=['N'], decrement_bond=['N'], form_bond=['N'], break_bond=['N'], increment_radical=['N'], decrement_radical=['N'], increment_lone_pair=['N'], decrement_lone_pair=['N'])
-ATOMTYPES['N0sc'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['N0sc'], break_bond=['N0sc'], increment_radical=['N0sc'], decrement_radical=['N0sc'], increment_lone_pair=[], decrement_lone_pair=['N1s', 'N1sc'])
-ATOMTYPES['N1s'].set_actions(increment_bond=['N1dc'], decrement_bond=[], form_bond=['N1s'], break_bond=['N1s'], increment_radical=['N1s'], decrement_radical=['N1s'], increment_lone_pair=['N0sc'], decrement_lone_pair=['N3s', 'N3sc'])
-ATOMTYPES['N1sc'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['N1sc'], break_bond=['N1sc'], increment_radical=['N1sc'], decrement_radical=['N1sc'], increment_lone_pair=[], decrement_lone_pair=['N3s', 'N3sc'])
-ATOMTYPES['N1dc'].set_actions(increment_bond=['N1dc'], decrement_bond=['N1s', 'N1dc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['N3d'])
-ATOMTYPES['N3s'].set_actions(increment_bond=['N3d'], decrement_bond=[], form_bond=['N3s'], break_bond=['N3s'], increment_radical=['N3s'], decrement_radical=['N3s'], increment_lone_pair=['N1s', 'N1sc'], decrement_lone_pair=['N5sc'])
-ATOMTYPES['N3sc'].set_actions(increment_bond=['N3d'], decrement_bond=[], form_bond=['N3sc'], break_bond=['N3sc'], increment_radical=['N3sc'], decrement_radical=['N3sc'], increment_lone_pair=['N1s', 'N1sc'], decrement_lone_pair=['N5sc'])
-ATOMTYPES['N3d'].set_actions(increment_bond=['N3t'], decrement_bond=['N3s', 'N3sc'], form_bond=['N3d'], break_bond=['N3d'], increment_radical=['N3d'], decrement_radical=['N3d'], increment_lone_pair=['N1dc'], decrement_lone_pair=['N5dc'])
-ATOMTYPES['N3t'].set_actions(increment_bond=[], decrement_bond=['N3d'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['N3b'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['N5sc'].set_actions(increment_bond=['N5dc'], decrement_bond=[], form_bond=['N5sc'], break_bond=['N5sc'], increment_radical=['N5sc'], decrement_radical=['N5sc'], increment_lone_pair=['N3s', 'N3sc'], decrement_lone_pair=[])
-ATOMTYPES['N5dc'].set_actions(increment_bond=['N5ddc', 'N5tc'], decrement_bond=['N5sc'], form_bond=['N5dc'], break_bond=['N5dc'], increment_radical=['N5dc'], decrement_radical=['N5dc'], increment_lone_pair=['N3d'], decrement_lone_pair=[])
-ATOMTYPES['N5ddc'].set_actions(increment_bond=['N5dddc'], decrement_bond=['N5dc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['N5dddc'].set_actions(increment_bond=[], decrement_bond=['N5ddc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['N5tc'].set_actions(increment_bond=[], decrement_bond=['N5dc'], form_bond=['N5tc'], break_bond=['N5tc'], increment_radical=['N5tc'], decrement_radical=['N5tc'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['N5b'].set_actions(increment_bond=['N5bd'], decrement_bond=[], form_bond=['N5b'], break_bond=['N5b'], increment_radical=['N5b'], decrement_radical=['N5b'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['N5bd'].set_actions(increment_bond=[], decrement_bond=['N5b'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-
-ATOMTYPES['O'].set_actions(increment_bond=['O'], decrement_bond=['O'], form_bond=['O'], break_bond=['O'], increment_radical=['O'], decrement_radical=['O'], increment_lone_pair=['O'], decrement_lone_pair=['O'])
-ATOMTYPES['Oa'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['O0sc'], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['O2s', 'O2sc'])
-ATOMTYPES['O0sc'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['O0sc'], break_bond=['Oa', 'O0sc'], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['O2s', 'O2sc'])
-ATOMTYPES['O2s'].set_actions(increment_bond=['O2d'], decrement_bond=[], form_bond=['O2s', 'O2sc'], break_bond=['O2s'], increment_radical=['O2s'], decrement_radical=['O2s'], increment_lone_pair=['Oa', 'O0sc'], decrement_lone_pair=['O4sc'])
-ATOMTYPES['O2sc'].set_actions(increment_bond=['O2d'], decrement_bond=[], form_bond=[], break_bond=['O2s'], increment_radical=['O2sc'], decrement_radical=['O2sc'], increment_lone_pair=[], decrement_lone_pair=['O4sc'])
-ATOMTYPES['O2d'].set_actions(increment_bond=[], decrement_bond=['O2s', 'O2sc'], form_bond=[], break_bond=[], increment_radical=['O2d'], decrement_radical=['O2d'], increment_lone_pair=[], decrement_lone_pair=['O4dc', 'O4tc'])
-ATOMTYPES['O4sc'].set_actions(increment_bond=['O4dc'], decrement_bond=[], form_bond=['O4sc'], break_bond=['O4sc'], increment_radical=['O4sc'], decrement_radical=['O4sc'], increment_lone_pair=['O2s', 'O2sc'], decrement_lone_pair=[])
-ATOMTYPES['O4dc'].set_actions(increment_bond=['O4tc'], decrement_bond=['O4sc'], form_bond=['O4dc'], break_bond=['O4dc'], increment_radical=['O4dc'], decrement_radical=['O4dc'], increment_lone_pair=['O2d'], decrement_lone_pair=[])
-ATOMTYPES['O4tc'].set_actions(increment_bond=[], decrement_bond=['O4dc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['O4b'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-
-ATOMTYPES['Ne'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=['Ne'], decrement_radical=['Ne'], increment_lone_pair=[], decrement_lone_pair=[])
-
-ATOMTYPES['Si'].set_actions(increment_bond=['Si'], decrement_bond=['Si'], form_bond=['Si'], break_bond=['Si'], increment_radical=['Si'], decrement_radical=['Si'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Sis'].set_actions(increment_bond=['Sid', 'SiO'], decrement_bond=[], form_bond=['Sis'], break_bond=['Sis'], increment_radical=['Sis'], decrement_radical=['Sis'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Sid'].set_actions(increment_bond=['Sidd', 'Sit'], decrement_bond=['Sis'], form_bond=['Sid'], break_bond=['Sid'], increment_radical=['Sid'], decrement_radical=['Sid'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Sidd'].set_actions(increment_bond=[], decrement_bond=['Sid', 'SiO'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Sit'].set_actions(increment_bond=['Siq'], decrement_bond=['Sid'], form_bond=['Sit'], break_bond=['Sit'], increment_radical=['Sit'], decrement_radical=['Sit'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['SiO'].set_actions(increment_bond=['Sidd'], decrement_bond=['Sis'], form_bond=['SiO'], break_bond=['SiO'], increment_radical=['SiO'], decrement_radical=['SiO'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Sib'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['Sib'], break_bond=['Sib'], increment_radical=['Sib'], decrement_radical=['Sib'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Sibf'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Siq'].set_actions(increment_bond=[], decrement_bond=['Sit'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-
-ATOMTYPES['P'].set_actions(increment_bond=['P'], decrement_bond=['P'], form_bond=['P'], break_bond=['P'], increment_radical=['P'], decrement_radical=['P'], increment_lone_pair=['P'], decrement_lone_pair=['P'])
-ATOMTYPES['P0sc'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['P0sc'], break_bond=['P0sc'], increment_radical=['P0sc'], decrement_radical=['P0sc'], increment_lone_pair=[], decrement_lone_pair=['P1s', 'P1sc'])
-ATOMTYPES['P1s'].set_actions(increment_bond=['P1dc'], decrement_bond=[], form_bond=['P1s'], break_bond=['P1s'], increment_radical=['P1s'], decrement_radical=['P1s'], increment_lone_pair=['P0sc'], decrement_lone_pair=['P3s'])
-ATOMTYPES['P1sc'].set_actions(increment_bond=['P1dc'], decrement_bond=[], form_bond=['P1sc'], break_bond=['P1sc'], increment_radical=['P1sc'], decrement_radical=['P1sc'], increment_lone_pair=['P0sc'], decrement_lone_pair=['P3s'])
-ATOMTYPES['P1dc'].set_actions(increment_bond=[], decrement_bond=['P1s'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['P3d'])
-ATOMTYPES['P3s'].set_actions(increment_bond=['P3d'], decrement_bond=[], form_bond=['P3s'], break_bond=['P3s'], increment_radical=['P3s'], decrement_radical=['P3s'], increment_lone_pair=['P1s', 'P1sc'], decrement_lone_pair=['P5s', 'P5sc'])
-ATOMTYPES['P3d'].set_actions(increment_bond=['P3t'], decrement_bond=['P3s'], form_bond=['P3d'], break_bond=['P3d'], increment_radical=['P3d'], decrement_radical=['P3d'], increment_lone_pair=['P1dc'], decrement_lone_pair=['P5d', 'P5dc'])
-ATOMTYPES['P3t'].set_actions(increment_bond=[], decrement_bond=['P3d'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['P5t', 'P5tc'])
-ATOMTYPES['P3b'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['P5s'].set_actions(increment_bond=['P5d', 'P5dc'], decrement_bond=[], form_bond=['P5s'], break_bond=['P5s'], increment_radical=['P5s'], decrement_radical=['P5s'], increment_lone_pair=['P3s'], decrement_lone_pair=[])
-ATOMTYPES['P5sc'].set_actions(increment_bond=['P5dc'], decrement_bond=[], form_bond=['P5sc'], break_bond=['P5sc'], increment_radical=['P5sc'], decrement_radical=['P5sc'], increment_lone_pair=['P3s'], decrement_lone_pair=[])
-ATOMTYPES['P5d'].set_actions(increment_bond=['P5dd', 'P5ddc', 'P5t', 'P5tc'], decrement_bond=['P5s'], form_bond=['P5d'], break_bond=['P5d'], increment_radical=['P5d'], decrement_radical=['P5d'], increment_lone_pair=['P3d'], decrement_lone_pair=[])
-ATOMTYPES['P5dd'].set_actions(increment_bond=['P5td'], decrement_bond=['P5d', 'P5dc'], form_bond=['P5dd'], break_bond=['P5dd'], increment_radical=['P5dd'], decrement_radical=['P5dd'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['P5dc'].set_actions(increment_bond=['P5dd', 'P5ddc', 'P5tc'], decrement_bond=['P5sc'], form_bond=['P5dc'], break_bond=['P5dc'], increment_radical=['P5dc'], decrement_radical=['P5dc'], increment_lone_pair=['P3d'], decrement_lone_pair=[])
-ATOMTYPES['P5ddc'].set_actions(increment_bond=[], decrement_bond=['P5dc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['P5t'].set_actions(increment_bond=['P5td'], decrement_bond=['P5d'], form_bond=['P5t'], break_bond=['P5t'], increment_radical=['P5t'], decrement_radical=['P5t'], increment_lone_pair=['P3t'], decrement_lone_pair=[])
-ATOMTYPES['P5td'].set_actions(increment_bond=[], decrement_bond=['P5t', 'P5dd'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['P5tc'].set_actions(increment_bond=[], decrement_bond=['P5dc'], form_bond=['P5tc'], break_bond=['P5tc'], increment_radical=['P5tc'], decrement_radical=['P5tc'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['P5b'].set_actions(increment_bond=['P5bd'], decrement_bond=[], form_bond=['P5b'], break_bond=['P5b'], increment_radical=['P5b'], decrement_radical=['P5b'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['P5bd'].set_actions(increment_bond=[], decrement_bond=['P5b'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-
-ATOMTYPES['S'].set_actions(increment_bond=['S'], decrement_bond=['S'], form_bond=['S'], break_bond=['S'], increment_radical=['S'], decrement_radical=['S'], increment_lone_pair=['S'], decrement_lone_pair=['S'])
-ATOMTYPES['S0sc'].set_actions(increment_bond=['S0sc'], decrement_bond=['S0sc'], form_bond=['S0sc'], break_bond=['Sa', 'S0sc'], increment_radical=['S0sc'], decrement_radical=['S0sc'], increment_lone_pair=[], decrement_lone_pair=['S2s', 'S2sc', 'S2dc', 'S2tc'])
-ATOMTYPES['Sa'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['S0sc'], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['S2s'])
-ATOMTYPES['S2s'].set_actions(increment_bond=['S2d', 'S2dc'], decrement_bond=[], form_bond=['S2s', 'S2sc'], break_bond=['S2s'], increment_radical=['S2s'], decrement_radical=['S2s'], increment_lone_pair=['Sa', 'S0sc'], decrement_lone_pair=['S4s', 'S4sc'])
-ATOMTYPES['S2sc'].set_actions(increment_bond=['S2dc'], decrement_bond=[], form_bond=['S2sc'], break_bond=['S2sc', 'S2s'], increment_radical=['S2sc'], decrement_radical=['S2sc'], increment_lone_pair=['S0sc'], decrement_lone_pair=['S4s', 'S4sc'])
-ATOMTYPES['S2d'].set_actions(increment_bond=['S2tc'], decrement_bond=['S2s'], form_bond=['S2d'], break_bond=['S2d'], increment_radical=['S2d'], decrement_radical=['S2d'], increment_lone_pair=[], decrement_lone_pair=['S4dc', 'S4d'])
-ATOMTYPES['S2dc'].set_actions(increment_bond=['S2tc', 'S2dc'], decrement_bond=['S2sc', 'S2s', 'S2dc'], form_bond=['S2dc'], break_bond=['S2dc'], increment_radical=['S2dc'], decrement_radical=['S2dc'], increment_lone_pair=['S0sc'], decrement_lone_pair=['S4d', 'S4dc'])
-ATOMTYPES['S2tc'].set_actions(increment_bond=[], decrement_bond=['S2d', 'S2dc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=['S0sc'], decrement_lone_pair=['S4t'])
-ATOMTYPES['S4s'].set_actions(increment_bond=['S4d', 'S4dc'], decrement_bond=[], form_bond=['S4s'], break_bond=['S4s'], increment_radical=['S4s'], decrement_radical=['S4s'], increment_lone_pair=['S2s', 'S2sc'], decrement_lone_pair=['S6s'])
-ATOMTYPES['S4sc'].set_actions(increment_bond=['S4d', 'S4dc'], decrement_bond=[], form_bond=['S4s', 'S4sc'], break_bond=['S4sc'], increment_radical=['S4sc'], decrement_radical=['S4sc'], increment_lone_pair=['S2s', 'S2sc'], decrement_lone_pair=['S6s'])
-ATOMTYPES['S4d'].set_actions(increment_bond=['S4dd', 'S4dc', 'S4t', 'S4tdc'], decrement_bond=['S4s', 'S4sc'], form_bond=['S4dc', 'S4d'], break_bond=['S4d', 'S4dc'], increment_radical=['S4d'], decrement_radical=['S4d'], increment_lone_pair=['S2d', 'S2dc'], decrement_lone_pair=['S6d', 'S6dc'])
-ATOMTYPES['S4dc'].set_actions(increment_bond=['S4dd', 'S4dc', 'S4tdc'], decrement_bond=['S4sc', 'S4dc'], form_bond=['S4d', 'S4dc'], break_bond=['S4d', 'S4dc'], increment_radical=['S4dc'], decrement_radical=['S4dc'], increment_lone_pair=['S2d', 'S2dc'], decrement_lone_pair=['S6d', 'S6dc'])
-ATOMTYPES['S4b'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['S4dd'].set_actions(increment_bond=['S4dc'], decrement_bond=['S4dc', 'S4d'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['S6dd'])
-ATOMTYPES['S4t'].set_actions(increment_bond=[], decrement_bond=['S4d'], form_bond=['S4t'], break_bond=['S4t'], increment_radical=['S4t'], decrement_radical=['S4t'], increment_lone_pair=['S2tc'], decrement_lone_pair=['S6t', 'S6tdc'])
-ATOMTYPES['S4tdc'].set_actions(increment_bond=['S4tdc'], decrement_bond=['S4d', 'S4tdc'], form_bond=['S4tdc'], break_bond=['S4tdc'], increment_radical=['S4tdc'], decrement_radical=['S4tdc'], increment_lone_pair=['S6tdc'], decrement_lone_pair=['S6td', 'S6tdc'])
-ATOMTYPES['S6s'].set_actions(increment_bond=['S6d', 'S6dc'], decrement_bond=[], form_bond=['S6s'], break_bond=['S6s'], increment_radical=['S6s'], decrement_radical=['S6s'], increment_lone_pair=['S4s', 'S4sc'], decrement_lone_pair=[])
-ATOMTYPES['S6sc'].set_actions(increment_bond=['S6dc'], decrement_bond=[], form_bond=['S6sc'], break_bond=['S6sc'], increment_radical=['S6sc'], decrement_radical=['S6sc'], increment_lone_pair=['S4s', 'S4sc'], decrement_lone_pair=[])
-ATOMTYPES['S6d'].set_actions(increment_bond=['S6dd', 'S6t', 'S6tdc'], decrement_bond=['S6s'], form_bond=['S6d', 'S6dc'], break_bond=['S6d', 'S6dc'], increment_radical=['S6d'], decrement_radical=['S6d'], increment_lone_pair=['S4d', 'S4dc'], decrement_lone_pair=[])
-ATOMTYPES['S6dc'].set_actions(increment_bond=['S6dd', 'S6ddd', 'S6dc', 'S6t', 'S6td', 'S6tdc'], decrement_bond=['S6sc', 'S6dc'], form_bond=['S6d', 'S6dc'], break_bond=['S6d', 'S6dc'], increment_radical=['S6dc'], decrement_radical=['S6dc'], increment_lone_pair=['S4d', 'S4dc'], decrement_lone_pair=[])
-ATOMTYPES['S6dd'].set_actions(increment_bond=['S6ddd', 'S6td'], decrement_bond=['S6d', 'S6dc'], form_bond=['S6dd', 'S6dc'], break_bond=['S6dd'], increment_radical=['S6dd'], decrement_radical=['S6dd'], increment_lone_pair=['S4dd'], decrement_lone_pair=[])
-ATOMTYPES['S6ddd'].set_actions(increment_bond=[], decrement_bond=['S6dd', 'S6dc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['S6t'].set_actions(increment_bond=['S6td'], decrement_bond=['S6d', 'S6dc'], form_bond=['S6t'], break_bond=['S6t'], increment_radical=['S6t'], decrement_radical=['S6t'], increment_lone_pair=['S4t'], decrement_lone_pair=[])
-ATOMTYPES['S6td'].set_actions(increment_bond=['S6tt', 'S6tdc'], decrement_bond=['S6dc', 'S6t', 'S6dd', 'S6tdc'], form_bond=['S6td'], break_bond=['S6td'], increment_radical=['S6td'], decrement_radical=['S6td'], increment_lone_pair=['S4tdc'], decrement_lone_pair=[])
-ATOMTYPES['S6tt'].set_actions(increment_bond=[], decrement_bond=['S6td', 'S6tdc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['S6tdc'].set_actions(increment_bond=['S6td', 'S6tdc', 'S6tt'], decrement_bond=['S6dc', 'S6tdc'], form_bond=['S6tdc'], break_bond=['S6tdc'], increment_radical=['S6tdc'], decrement_radical=['S6tdc'], increment_lone_pair=['S4t', 'S4tdc'], decrement_lone_pair=[])
-
-ATOMTYPES['Cl'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['Cl'], break_bond=['Cl'], increment_radical=['Cl'], decrement_radical=['Cl'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Cl1s'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['Cl1s'], break_bond=['Cl1s'], increment_radical=['Cl1s'], decrement_radical=['Cl1s'], increment_lone_pair=[], decrement_lone_pair=[])
-
-ATOMTYPES['Br'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['Br'], break_bond=['Br'], increment_radical=['Br'], decrement_radical=['Br'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['Br1s'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['Br1s'], break_bond=['Br1s'], increment_radical=['Br1s'], decrement_radical=['Br1s'], increment_lone_pair=[], decrement_lone_pair=[])
-
-ATOMTYPES['I'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['I'], break_bond=['I'], increment_radical=['I'], decrement_radical=['I'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['I1s'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['I1s'], break_bond=['I1s'], increment_radical=['I1s'], decrement_radical=['I1s'], increment_lone_pair=[], decrement_lone_pair=[])
-
-ATOMTYPES['F'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['F'], break_bond=['F'], increment_radical=['F'], decrement_radical=['F'], increment_lone_pair=[], decrement_lone_pair=[])
-ATOMTYPES['F1s'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['F1s'], break_bond=['F1s'], increment_radical=['F1s'], decrement_radical=['F1s'], increment_lone_pair=[], decrement_lone_pair=[])
+ATOMTYPES['Rx'].set_actions(increment_bond=['Rx'], decrement_bond=['Rx'], form_bond=['Rx'], break_bond=['Rx'], increment_radical=['Rx'], decrement_radical=['Rx'], increment_lone_pair=['Rx'], decrement_lone_pair=['Rx'], increment_charge=['Rx'], decrement_charge=['Rx'])
+ATOMTYPES['Rx!H'].set_actions(increment_bond=['Rx!H'], decrement_bond=['Rx!H'], form_bond=['Rx!H'], break_bond=['Rx!H'], increment_radical=['Rx!H'], decrement_radical=['Rx!H'], increment_lone_pair=['Rx!H'], decrement_lone_pair=['Rx!H'], increment_charge=['Rx!H'], decrement_charge=['Rx!H'])
+
+ATOMTYPES['e'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+
+ATOMTYPES['X'].set_actions(increment_bond=['X'], decrement_bond=['X'], form_bond=['X'], break_bond=['X'], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Xv'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['Xo'], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Xo'].set_actions(increment_bond=['Xo'], decrement_bond=['Xo'], form_bond=[], break_bond=['Xv'], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+
+ATOMTYPES['R'].set_actions(increment_bond=['R'], decrement_bond=['R'], form_bond=['R'], break_bond=['R'], increment_radical=['R'], decrement_radical=['R'], increment_lone_pair=['R'], decrement_lone_pair=['R'], increment_charge=['R'], decrement_charge=['R'])
+ATOMTYPES['R!H'].set_actions(increment_bond=['R!H'], decrement_bond=['R!H'], form_bond=['R!H'], break_bond=['R!H'], increment_radical=['R!H'], decrement_radical=['R!H'], increment_lone_pair=['R!H'], decrement_lone_pair=['R!H'], increment_charge=['R!H'], decrement_charge=['R!H'])
+ATOMTYPES['R!H!Val7'].set_actions(increment_bond=['R!H!Val7'], decrement_bond=['R!H!Val7'], form_bond=['R!H!Val7'], break_bond=['R!H!Val7'], increment_radical=['R!H!Val7'], decrement_radical=['R!H!Val7'], increment_lone_pair=['R!H!Val7'], decrement_lone_pair=['R!H!Val7'], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Val4'].set_actions(increment_bond=['Val4'], decrement_bond=['Val4'], form_bond=['Val4'], break_bond=['Val4'], increment_radical=['Val4'], decrement_radical=['Val4'], increment_lone_pair=['Val4'], decrement_lone_pair=['Val4'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['Val5'].set_actions(increment_bond=['Val5'], decrement_bond=['Val5'], form_bond=['Val5'], break_bond=['Val5'], increment_radical=['Val5'], decrement_radical=['Val5'], increment_lone_pair=['Val5'], decrement_lone_pair=['Val5'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['Val6'].set_actions(increment_bond=['Val6'], decrement_bond=['Val6'], form_bond=['Val6'], break_bond=['Val6'], increment_radical=['Val6'], decrement_radical=['Val6'], increment_lone_pair=['Val6'], decrement_lone_pair=['Val6'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['Val7'].set_actions(increment_bond=['Val7'], decrement_bond=['Val7'], form_bond=['Val7'], break_bond=['Val7'], increment_radical=['Val7'], decrement_radical=['Val7'], increment_lone_pair=['Val7'], decrement_lone_pair=['Val7'],increment_charge=[], decrement_charge=[])
+
+ATOMTYPES['H'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['H'], break_bond=['H'], increment_radical=['H'], decrement_radical=['H'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=['H'], decrement_charge=['H'])
+ATOMTYPES['H0'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['H0'], break_bond=['H0'], increment_radical=['H0'], decrement_radical=['H0'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=['H+'], decrement_charge=[])
+ATOMTYPES['H+'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=['H0'])
+
+ATOMTYPES['Li'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['Li'], break_bond=['Li'], increment_radical=['Li'], decrement_radical=['Li'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=['Li'], decrement_charge=['Li'])
+ATOMTYPES['Li0'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['Li0'], break_bond=['Li0'], increment_radical=['Li0'], decrement_radical=['H0'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=['Li+'], decrement_charge=[])
+ATOMTYPES['Li+'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=['Li0'])
+
+ATOMTYPES['He'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=['He'], decrement_radical=['He'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Ne'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=['Ne'], decrement_radical=['Ne'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Ar'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+
+ATOMTYPES['C'].set_actions(increment_bond=['C'], decrement_bond=['C'], form_bond=['C'], break_bond=['C'], increment_radical=['C'], decrement_radical=['C'], increment_lone_pair=['C'], decrement_lone_pair=['C'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['Ca'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['C2s'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['Cs'].set_actions(increment_bond=['Cd', 'CO', 'CS'], decrement_bond=[], form_bond=['Cs', 'Csc'], break_bond=['Cs'], increment_radical=['Cs'], decrement_radical=['Cs'], increment_lone_pair=['C2s'], decrement_lone_pair=['C2s'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['Csc'].set_actions(increment_bond=['Cdc'], decrement_bond=[], form_bond=['Csc'], break_bond=['Csc', 'Cs'], increment_radical=['Csc'], decrement_radical=['Csc'], increment_lone_pair=['C2sc'], decrement_lone_pair=['C2sc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['Cd'].set_actions(increment_bond=['Cdd', 'Ct', 'C2tc'], decrement_bond=['Cs'], form_bond=['Cd', 'Cdc'], break_bond=['Cd'], increment_radical=['Cd'], decrement_radical=['Cd'], increment_lone_pair=['C2d'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Cdc'].set_actions(increment_bond=[], decrement_bond=['Csc'], form_bond=['Cdc'], break_bond=['Cdc', 'Cd', 'CO', 'CS'], increment_radical=['Cdc'], decrement_radical=['Cdc'], increment_lone_pair=['C2dc'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['CO'].set_actions(increment_bond=['Cdd', 'C2tc'], decrement_bond=['Cs'], form_bond=['CO', 'Cdc'], break_bond=['CO'], increment_radical=['CO'], decrement_radical=['CO'], increment_lone_pair=['C2d'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['CS'].set_actions(increment_bond=['Cdd', 'C2tc'], decrement_bond=['Cs'], form_bond=['CS', 'Cdc'], break_bond=['CS'], increment_radical=['CS'], decrement_radical=['CS'], increment_lone_pair=['C2d'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Cdd'].set_actions(increment_bond=[], decrement_bond=['Cd', 'CO', 'CS'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Ct'].set_actions(increment_bond=['Cq'], decrement_bond=['Cd', 'CO', 'CS'], form_bond=['Ct'], break_bond=['Ct'], increment_radical=['Ct'], decrement_radical=['Ct'], increment_lone_pair=['C2tc'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Cb'].set_actions(increment_bond=['Cbf'], decrement_bond=[], form_bond=['Cb'], break_bond=['Cb'], increment_radical=['Cb'], decrement_radical=['Cb'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Cbf'].set_actions(increment_bond=[], decrement_bond=['Cb'], form_bond=[], break_bond=['Cb'], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['C2s'].set_actions(increment_bond=['C2d'], decrement_bond=[], form_bond=['C2s'], break_bond=['C2s'], increment_radical=['C2s'], decrement_radical=['C2s'], increment_lone_pair=['Ca'], decrement_lone_pair=['Cs'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['C2sc'].set_actions(increment_bond=['C2dc'], decrement_bond=[], form_bond=['C2sc'], break_bond=['C2sc'], increment_radical=['C2sc'], decrement_radical=['C2sc'], increment_lone_pair=[], decrement_lone_pair=['Cs'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['C2d'].set_actions(increment_bond=['C2tc'], decrement_bond=['C2s'], form_bond=['C2dc'], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['Cd', 'CO', 'CS'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['C2dc'].set_actions(increment_bond=[], decrement_bond=['C2sc'], form_bond=[], break_bond=['C2d'], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['Cdc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['C2tc'].set_actions(increment_bond=[], decrement_bond=['C2d'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['Ct'], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Cq'].set_actions(increment_bond=[], decrement_bond=['Ct'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+
+ATOMTYPES['N'].set_actions(increment_bond=['N'], decrement_bond=['N'], form_bond=['N'], break_bond=['N'], increment_radical=['N'], decrement_radical=['N'], increment_lone_pair=['N'], decrement_lone_pair=['N'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['N0sc'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['N0sc'], break_bond=['N0sc'], increment_radical=['N0sc'], decrement_radical=['N0sc'], increment_lone_pair=[], decrement_lone_pair=['N1s', 'N1sc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['N1s'].set_actions(increment_bond=['N1dc'], decrement_bond=[], form_bond=['N1s'], break_bond=['N1s'], increment_radical=['N1s'], decrement_radical=['N1s'], increment_lone_pair=['N0sc'], decrement_lone_pair=['N3s', 'N3sc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['N1sc'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['N1sc'], break_bond=['N1sc'], increment_radical=['N1sc'], decrement_radical=['N1sc'], increment_lone_pair=[], decrement_lone_pair=['N3s', 'N3sc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['N1dc'].set_actions(increment_bond=['N1dc'], decrement_bond=['N1s', 'N1dc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['N3d'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['N3s'].set_actions(increment_bond=['N3d'], decrement_bond=[], form_bond=['N3s'], break_bond=['N3s'], increment_radical=['N3s'], decrement_radical=['N3s'], increment_lone_pair=['N1s', 'N1sc'], decrement_lone_pair=['N5sc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['N3sc'].set_actions(increment_bond=['N3d'], decrement_bond=[], form_bond=['N3sc'], break_bond=['N3sc'], increment_radical=['N3sc'], decrement_radical=['N3sc'], increment_lone_pair=['N1s', 'N1sc'], decrement_lone_pair=['N5sc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['N3d'].set_actions(increment_bond=['N3t'], decrement_bond=['N3s', 'N3sc'], form_bond=['N3d'], break_bond=['N3d'], increment_radical=['N3d'], decrement_radical=['N3d'], increment_lone_pair=['N1dc'], decrement_lone_pair=['N5dc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['N3t'].set_actions(increment_bond=[], decrement_bond=['N3d'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['N3b'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['N5sc'].set_actions(increment_bond=['N5dc'], decrement_bond=[], form_bond=['N5sc'], break_bond=['N5sc'], increment_radical=['N5sc'], decrement_radical=['N5sc'], increment_lone_pair=['N3s', 'N3sc'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['N5dc'].set_actions(increment_bond=['N5ddc', 'N5tc'], decrement_bond=['N5sc'], form_bond=['N5dc'], break_bond=['N5dc'], increment_radical=['N5dc'], decrement_radical=['N5dc'], increment_lone_pair=['N3d'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['N5ddc'].set_actions(increment_bond=['N5dddc'], decrement_bond=['N5dc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['N5dddc'].set_actions(increment_bond=[], decrement_bond=['N5ddc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['N5tc'].set_actions(increment_bond=[], decrement_bond=['N5dc'], form_bond=['N5tc'], break_bond=['N5tc'], increment_radical=['N5tc'], decrement_radical=['N5tc'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['N5b'].set_actions(increment_bond=['N5bd'], decrement_bond=[], form_bond=['N5b'], break_bond=['N5b'], increment_radical=['N5b'], decrement_radical=['N5b'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['N5bd'].set_actions(increment_bond=[], decrement_bond=['N5b'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+
+ATOMTYPES['O'].set_actions(increment_bond=['O'], decrement_bond=['O'], form_bond=['O'], break_bond=['O'], increment_radical=['O'], decrement_radical=['O'], increment_lone_pair=['O'], decrement_lone_pair=['O'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['Oa'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['O0sc'], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['O2s', 'O2sc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['O0sc'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['O0sc'], break_bond=['Oa', 'O0sc'], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['O2s', 'O2sc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['O2s'].set_actions(increment_bond=['O2d'], decrement_bond=[], form_bond=['O2s', 'O2sc'], break_bond=['O2s'], increment_radical=['O2s'], decrement_radical=['O2s'], increment_lone_pair=['Oa', 'O0sc'], decrement_lone_pair=['O4sc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['O2sc'].set_actions(increment_bond=['O2d'], decrement_bond=[], form_bond=[], break_bond=['O2s'], increment_radical=['O2sc'], decrement_radical=['O2sc'], increment_lone_pair=[], decrement_lone_pair=['O4sc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['O2d'].set_actions(increment_bond=[], decrement_bond=['O2s', 'O2sc'], form_bond=[], break_bond=[], increment_radical=['O2d'], decrement_radical=['O2d'], increment_lone_pair=[], decrement_lone_pair=['O4dc', 'O4tc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['O4sc'].set_actions(increment_bond=['O4dc'], decrement_bond=[], form_bond=['O4sc'], break_bond=['O4sc'], increment_radical=['O4sc'], decrement_radical=['O4sc'], increment_lone_pair=['O2s', 'O2sc'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['O4dc'].set_actions(increment_bond=['O4tc'], decrement_bond=['O4sc'], form_bond=['O4dc'], break_bond=['O4dc'], increment_radical=['O4dc'], decrement_radical=['O4dc'], increment_lone_pair=['O2d'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['O4tc'].set_actions(increment_bond=[], decrement_bond=['O4dc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['O4b'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+
+ATOMTYPES['Ne'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=['Ne'], decrement_radical=['Ne'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+
+ATOMTYPES['Si'].set_actions(increment_bond=['Si'], decrement_bond=['Si'], form_bond=['Si'], break_bond=['Si'], increment_radical=['Si'], decrement_radical=['Si'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Sis'].set_actions(increment_bond=['Sid', 'SiO'], decrement_bond=[], form_bond=['Sis'], break_bond=['Sis'], increment_radical=['Sis'], decrement_radical=['Sis'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Sid'].set_actions(increment_bond=['Sidd', 'Sit'], decrement_bond=['Sis'], form_bond=['Sid'], break_bond=['Sid'], increment_radical=['Sid'], decrement_radical=['Sid'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Sidd'].set_actions(increment_bond=[], decrement_bond=['Sid', 'SiO'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Sit'].set_actions(increment_bond=['Siq'], decrement_bond=['Sid'], form_bond=['Sit'], break_bond=['Sit'], increment_radical=['Sit'], decrement_radical=['Sit'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['SiO'].set_actions(increment_bond=['Sidd'], decrement_bond=['Sis'], form_bond=['SiO'], break_bond=['SiO'], increment_radical=['SiO'], decrement_radical=['SiO'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Sib'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['Sib'], break_bond=['Sib'], increment_radical=['Sib'], decrement_radical=['Sib'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Sibf'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Siq'].set_actions(increment_bond=[], decrement_bond=['Sit'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+
+ATOMTYPES['P'].set_actions(increment_bond=['P'], decrement_bond=['P'], form_bond=['P'], break_bond=['P'], increment_radical=['P'], decrement_radical=['P'], increment_lone_pair=['P'], decrement_lone_pair=['P'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['P0sc'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['P0sc'], break_bond=['P0sc'], increment_radical=['P0sc'], decrement_radical=['P0sc'], increment_lone_pair=[], decrement_lone_pair=['P1s', 'P1sc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['P1s'].set_actions(increment_bond=['P1dc'], decrement_bond=[], form_bond=['P1s'], break_bond=['P1s'], increment_radical=['P1s'], decrement_radical=['P1s'], increment_lone_pair=['P0sc'], decrement_lone_pair=['P3s'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['P1sc'].set_actions(increment_bond=['P1dc'], decrement_bond=[], form_bond=['P1sc'], break_bond=['P1sc'], increment_radical=['P1sc'], decrement_radical=['P1sc'], increment_lone_pair=['P0sc'], decrement_lone_pair=['P3s'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['P1dc'].set_actions(increment_bond=[], decrement_bond=['P1s'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['P3d'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['P3s'].set_actions(increment_bond=['P3d'], decrement_bond=[], form_bond=['P3s'], break_bond=['P3s'], increment_radical=['P3s'], decrement_radical=['P3s'], increment_lone_pair=['P1s', 'P1sc'], decrement_lone_pair=['P5s', 'P5sc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['P3d'].set_actions(increment_bond=['P3t'], decrement_bond=['P3s'], form_bond=['P3d'], break_bond=['P3d'], increment_radical=['P3d'], decrement_radical=['P3d'], increment_lone_pair=['P1dc'], decrement_lone_pair=['P5d', 'P5dc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['P3t'].set_actions(increment_bond=[], decrement_bond=['P3d'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['P5t', 'P5tc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['P3b'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['P5s'].set_actions(increment_bond=['P5d', 'P5dc'], decrement_bond=[], form_bond=['P5s'], break_bond=['P5s'], increment_radical=['P5s'], decrement_radical=['P5s'], increment_lone_pair=['P3s'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['P5sc'].set_actions(increment_bond=['P5dc'], decrement_bond=[], form_bond=['P5sc'], break_bond=['P5sc'], increment_radical=['P5sc'], decrement_radical=['P5sc'], increment_lone_pair=['P3s'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['P5d'].set_actions(increment_bond=['P5dd', 'P5ddc', 'P5t', 'P5tc'], decrement_bond=['P5s'], form_bond=['P5d'], break_bond=['P5d'], increment_radical=['P5d'], decrement_radical=['P5d'], increment_lone_pair=['P3d'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['P5dd'].set_actions(increment_bond=['P5td'], decrement_bond=['P5d', 'P5dc'], form_bond=['P5dd'], break_bond=['P5dd'], increment_radical=['P5dd'], decrement_radical=['P5dd'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['P5dc'].set_actions(increment_bond=['P5dd', 'P5ddc', 'P5tc'], decrement_bond=['P5sc'], form_bond=['P5dc'], break_bond=['P5dc'], increment_radical=['P5dc'], decrement_radical=['P5dc'], increment_lone_pair=['P3d'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['P5ddc'].set_actions(increment_bond=[], decrement_bond=['P5dc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['P5t'].set_actions(increment_bond=['P5td'], decrement_bond=['P5d'], form_bond=['P5t'], break_bond=['P5t'], increment_radical=['P5t'], decrement_radical=['P5t'], increment_lone_pair=['P3t'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['P5td'].set_actions(increment_bond=[], decrement_bond=['P5t', 'P5dd'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['P5tc'].set_actions(increment_bond=[], decrement_bond=['P5dc'], form_bond=['P5tc'], break_bond=['P5tc'], increment_radical=['P5tc'], decrement_radical=['P5tc'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['P5b'].set_actions(increment_bond=['P5bd'], decrement_bond=[], form_bond=['P5b'], break_bond=['P5b'], increment_radical=['P5b'], decrement_radical=['P5b'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['P5bd'].set_actions(increment_bond=[], decrement_bond=['P5b'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+
+ATOMTYPES['S'].set_actions(increment_bond=['S'], decrement_bond=['S'], form_bond=['S'], break_bond=['S'], increment_radical=['S'], decrement_radical=['S'], increment_lone_pair=['S'], decrement_lone_pair=['S'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['S0sc'].set_actions(increment_bond=['S0sc'], decrement_bond=['S0sc'], form_bond=['S0sc'], break_bond=['Sa', 'S0sc'], increment_radical=['S0sc'], decrement_radical=['S0sc'], increment_lone_pair=[], decrement_lone_pair=['S2s', 'S2sc', 'S2dc', 'S2tc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['Sa'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['S0sc'], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['S2s'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['S2s'].set_actions(increment_bond=['S2d', 'S2dc'], decrement_bond=[], form_bond=['S2s', 'S2sc'], break_bond=['S2s'], increment_radical=['S2s'], decrement_radical=['S2s'], increment_lone_pair=['Sa', 'S0sc'], decrement_lone_pair=['S4s', 'S4sc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['S2sc'].set_actions(increment_bond=['S2dc'], decrement_bond=[], form_bond=['S2sc'], break_bond=['S2sc', 'S2s'], increment_radical=['S2sc'], decrement_radical=['S2sc'], increment_lone_pair=['S0sc'], decrement_lone_pair=['S4s', 'S4sc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['S2d'].set_actions(increment_bond=['S2tc'], decrement_bond=['S2s'], form_bond=['S2d'], break_bond=['S2d'], increment_radical=['S2d'], decrement_radical=['S2d'], increment_lone_pair=[], decrement_lone_pair=['S4dc', 'S4d'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['S2dc'].set_actions(increment_bond=['S2tc', 'S2dc'], decrement_bond=['S2sc', 'S2s', 'S2dc'], form_bond=['S2dc'], break_bond=['S2dc'], increment_radical=['S2dc'], decrement_radical=['S2dc'], increment_lone_pair=['S0sc'], decrement_lone_pair=['S4d', 'S4dc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['S2tc'].set_actions(increment_bond=[], decrement_bond=['S2d', 'S2dc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=['S0sc'], decrement_lone_pair=['S4t'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['S4s'].set_actions(increment_bond=['S4d', 'S4dc'], decrement_bond=[], form_bond=['S4s'], break_bond=['S4s'], increment_radical=['S4s'], decrement_radical=['S4s'], increment_lone_pair=['S2s', 'S2sc'], decrement_lone_pair=['S6s'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['S4sc'].set_actions(increment_bond=['S4d', 'S4dc'], decrement_bond=[], form_bond=['S4s', 'S4sc'], break_bond=['S4sc'], increment_radical=['S4sc'], decrement_radical=['S4sc'], increment_lone_pair=['S2s', 'S2sc'], decrement_lone_pair=['S6s'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['S4d'].set_actions(increment_bond=['S4dd', 'S4dc', 'S4t', 'S4tdc'], decrement_bond=['S4s', 'S4sc'], form_bond=['S4dc', 'S4d'], break_bond=['S4d', 'S4dc'], increment_radical=['S4d'], decrement_radical=['S4d'], increment_lone_pair=['S2d', 'S2dc'], decrement_lone_pair=['S6d', 'S6dc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['S4dc'].set_actions(increment_bond=['S4dd', 'S4dc', 'S4tdc'], decrement_bond=['S4sc', 'S4dc'], form_bond=['S4d', 'S4dc'], break_bond=['S4d', 'S4dc'], increment_radical=['S4dc'], decrement_radical=['S4dc'], increment_lone_pair=['S2d', 'S2dc'], decrement_lone_pair=['S6d', 'S6dc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['S4b'].set_actions(increment_bond=[], decrement_bond=[], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['S4dd'].set_actions(increment_bond=['S4dc'], decrement_bond=['S4dc', 'S4d'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=['S6dd'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['S4t'].set_actions(increment_bond=[], decrement_bond=['S4d'], form_bond=['S4t'], break_bond=['S4t'], increment_radical=['S4t'], decrement_radical=['S4t'], increment_lone_pair=['S2tc'], decrement_lone_pair=['S6t', 'S6tdc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['S4tdc'].set_actions(increment_bond=['S4tdc'], decrement_bond=['S4d', 'S4tdc'], form_bond=['S4tdc'], break_bond=['S4tdc'], increment_radical=['S4tdc'], decrement_radical=['S4tdc'], increment_lone_pair=['S6tdc'], decrement_lone_pair=['S6td', 'S6tdc'],increment_charge=[], decrement_charge=[])
+ATOMTYPES['S6s'].set_actions(increment_bond=['S6d', 'S6dc'], decrement_bond=[], form_bond=['S6s'], break_bond=['S6s'], increment_radical=['S6s'], decrement_radical=['S6s'], increment_lone_pair=['S4s', 'S4sc'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['S6sc'].set_actions(increment_bond=['S6dc'], decrement_bond=[], form_bond=['S6sc'], break_bond=['S6sc'], increment_radical=['S6sc'], decrement_radical=['S6sc'], increment_lone_pair=['S4s', 'S4sc'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['S6d'].set_actions(increment_bond=['S6dd', 'S6t', 'S6tdc'], decrement_bond=['S6s'], form_bond=['S6d', 'S6dc'], break_bond=['S6d', 'S6dc'], increment_radical=['S6d'], decrement_radical=['S6d'], increment_lone_pair=['S4d', 'S4dc'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['S6dc'].set_actions(increment_bond=['S6dd', 'S6ddd', 'S6dc', 'S6t', 'S6td', 'S6tdc'], decrement_bond=['S6sc', 'S6dc'], form_bond=['S6d', 'S6dc'], break_bond=['S6d', 'S6dc'], increment_radical=['S6dc'], decrement_radical=['S6dc'], increment_lone_pair=['S4d', 'S4dc'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['S6dd'].set_actions(increment_bond=['S6ddd', 'S6td'], decrement_bond=['S6d', 'S6dc'], form_bond=['S6dd', 'S6dc'], break_bond=['S6dd'], increment_radical=['S6dd'], decrement_radical=['S6dd'], increment_lone_pair=['S4dd'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['S6ddd'].set_actions(increment_bond=[], decrement_bond=['S6dd', 'S6dc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['S6t'].set_actions(increment_bond=['S6td'], decrement_bond=['S6d', 'S6dc'], form_bond=['S6t'], break_bond=['S6t'], increment_radical=['S6t'], decrement_radical=['S6t'], increment_lone_pair=['S4t'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['S6td'].set_actions(increment_bond=['S6tt', 'S6tdc'], decrement_bond=['S6dc', 'S6t', 'S6dd', 'S6tdc'], form_bond=['S6td'], break_bond=['S6td'], increment_radical=['S6td'], decrement_radical=['S6td'], increment_lone_pair=['S4tdc'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['S6tt'].set_actions(increment_bond=[], decrement_bond=['S6td', 'S6tdc'], form_bond=[], break_bond=[], increment_radical=[], decrement_radical=[], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['S6tdc'].set_actions(increment_bond=['S6td', 'S6tdc', 'S6tt'], decrement_bond=['S6dc', 'S6tdc'], form_bond=['S6tdc'], break_bond=['S6tdc'], increment_radical=['S6tdc'], decrement_radical=['S6tdc'], increment_lone_pair=['S4t', 'S4tdc'], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+
+ATOMTYPES['Cl'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['Cl'], break_bond=['Cl'], increment_radical=['Cl'], decrement_radical=['Cl'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Cl1s'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['Cl1s'], break_bond=['Cl1s'], increment_radical=['Cl1s'], decrement_radical=['Cl1s'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+
+ATOMTYPES['Br'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['Br'], break_bond=['Br'], increment_radical=['Br'], decrement_radical=['Br'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['Br1s'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['Br1s'], break_bond=['Br1s'], increment_radical=['Br1s'], decrement_radical=['Br1s'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+
+ATOMTYPES['I'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['I'], break_bond=['I'], increment_radical=['I'], decrement_radical=['I'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['I1s'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['I1s'], break_bond=['I1s'], increment_radical=['I1s'], decrement_radical=['I1s'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+
+ATOMTYPES['F'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['F'], break_bond=['F'], increment_radical=['F'], decrement_radical=['F'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
+ATOMTYPES['F1s'].set_actions(increment_bond=[], decrement_bond=[], form_bond=['F1s'], break_bond=['F1s'], increment_radical=['F1s'], decrement_radical=['F1s'], increment_lone_pair=[], decrement_lone_pair=[], increment_charge=[], decrement_charge=[])
# these are ordered in priority of picking if a more general atomtype is encountered
-allElements = ['H', 'C', 'O', 'N', 'S', 'P', 'Si', 'F', 'Cl', 'Br', 'I', 'Ne', 'Ar', 'He', 'X']
+allElements = ['H', 'C', 'O', 'N', 'S', 'P', 'Si', 'F', 'Cl', 'Br', 'I', 'Li', 'Ne', 'Ar', 'He', 'X', 'e', ]
# list of elements that do not have more specific atomTypes
-nonSpecifics = ['H', 'He', 'Ne', 'Ar',]
+nonSpecifics = ['He', 'Ne', 'Ar', 'e']
for atomtype in ATOMTYPES.values():
for items in [atomtype.generic, atomtype.specific,
atomtype.increment_bond, atomtype.decrement_bond, atomtype.form_bond,
atomtype.break_bond, atomtype.increment_radical, atomtype.decrement_radical, atomtype.increment_lone_pair,
- atomtype.decrement_lone_pair]:
+ atomtype.decrement_lone_pair, atomtype.increment_charge, atomtype.decrement_charge]:
for index in range(len(items)):
items[index] = ATOMTYPES[items[index]]
-
def get_features(atom, bonds):
"""
Returns a list of features needed to determine atomtype for :class:'Atom'
diff --git a/rmgpy/molecule/draw.py b/rmgpy/molecule/draw.py
index d38c519197..37a3b47e25 100644
--- a/rmgpy/molecule/draw.py
+++ b/rmgpy/molecule/draw.py
@@ -109,9 +109,9 @@ class MoleculeDrawer(object):
This class provides functionality for drawing the skeletal formula of
molecules using the Cairo 2D graphics engine. The most common use case is
simply::
-
+
MoleculeDrawer().draw(molecule, file_format='png', path='molecule.png')
-
+
where ``molecule`` is the :class:`Molecule` object to draw. You can also
pass a dict of options to the constructor to affect how the molecules are
drawn.
@@ -343,7 +343,7 @@ def _find_ring_groups(self):
def _generate_coordinates(self, fix_surface_sites=True):
"""
- Generate the 2D coordinates to be used when drawing the current
+ Generate the 2D coordinates to be used when drawing the current
molecule. The function uses rdKits 2D coordinate generation.
Updates the self.coordinates Array in place.
If `fix_surface_sites` is True, then the surface sites are placed
@@ -406,7 +406,7 @@ def _generate_coordinates(self, fix_surface_sites=True):
[-math.sin(angle), math.cos(angle)]], float)
# need to keep self.coordinates and coordinates referring to the same object
self.coordinates = coordinates = np.dot(coordinates, rot)
-
+
# If two atoms lie on top of each other, push them apart a bit
# This is ugly, but at least the mess you end up with isn't as misleading
# as leaving everything piled on top of each other at the origin
@@ -726,7 +726,7 @@ def _generate_ring_system_coordinates(self, atoms):
def _generate_straight_chain_coordinates(self, atoms):
"""
Update the coordinates for the linear straight chain of `atoms` in
- the current molecule.
+ the current molecule.
"""
coordinates = self.coordinates
@@ -1374,6 +1374,8 @@ def _render_atom(self, symbol, atom, x0, y0, cr, heavy_first=True, draw_lone_pai
cr.set_source_rgba(0.5, 0.0, 0.5, 1.0)
elif heavy_atom == 'X':
cr.set_source_rgba(0.5, 0.25, 0.5, 1.0)
+ elif heavy_atom == 'e':
+ cr.set_source_rgba(1.0, 0.0, 1.0, 1.0)
else:
cr.set_source_rgba(0.0, 0.0, 0.0, 1.0)
@@ -1567,7 +1569,7 @@ def _render_atom(self, symbol, atom, x0, y0, cr, heavy_first=True, draw_lone_pai
cr.set_source_rgba(0.0, 0.0, 0.0, 1.0)
cr.show_text(text)
- # Draw lone electron pairs
+ # Draw lone electron pairs
# Draw them for nitrogen containing molecules only
if draw_lone_pairs:
for i in range(atom.lone_pairs):
@@ -1706,9 +1708,9 @@ class ReactionDrawer(object):
This class provides functionality for drawing chemical reactions using the
skeletal formula of each reactant and product molecule via the Cairo 2D
graphics engine. The most common use case is simply::
-
+
ReactionDrawer().draw(reaction, file_format='png', path='reaction.png')
-
+
where ``reaction`` is the :class:`Reaction` object to draw. You can also
pass a dict of options to the constructor to affect how the molecules are
drawn.
@@ -1728,7 +1730,7 @@ def draw(self, reaction, file_format, path=None):
Draw the given `reaction` using the given image `file_format` - pdf, svg,
ps, or png. If `path` is given, the drawing is saved to that location
on disk.
-
+
This function returns the Cairo surface and context used to create the
drawing, as well as a bounding box for the molecule being drawn as the
tuple (`left`, `top`, `width`, `height`).
diff --git a/rmgpy/molecule/element.py b/rmgpy/molecule/element.py
index ad713d5ce6..6ec975fcf9 100644
--- a/rmgpy/molecule/element.py
+++ b/rmgpy/molecule/element.py
@@ -78,7 +78,7 @@ def __init__(self, number, symbol, name, mass, isotope=-1, chemkin_name=None):
self.mass = mass
self.isotope = isotope
self.chemkin_name = chemkin_name or self.name
- if symbol in {'X','L','R'}:
+ if symbol in {'X','L','R','e'}:
self.cov_radius = 0
else:
try:
@@ -122,14 +122,15 @@ class PeriodicSystem(object):
https://sciencenotes.org/list-of-electronegativity-values-of-the-elements/
isotopes of the same element may have slight different electronegativities, which is not reflected below
"""
- valences = {'H': 1, 'He': 0, 'C': 4, 'N': 3, 'O': 2, 'F': 1, 'Ne': 0,
- 'Si': 4, 'P': 3, 'S': 2, 'Cl': 1, 'Br': 1, 'Ar': 0, 'I': 1, 'X': 4}
- valence_electrons = {'H': 1, 'He': 2, 'C': 4, 'N': 5, 'O': 6, 'F': 7, 'Ne': 8,
- 'Si': 4, 'P': 5, 'S': 6, 'Cl': 7, 'Br': 7, 'Ar': 8, 'I': 7, 'X': 4}
- lone_pairs = {'H': 0, 'He': 1, 'C': 0, 'N': 1, 'O': 2, 'F': 3, 'Ne': 4,
- 'Si': 0, 'P': 1, 'S': 2, 'Cl': 3, 'Br': 3, 'Ar': 4, 'I': 3, 'X': 0}
+ valences = {'H+':0, 'e': 0, 'H': 1, 'He': 0, 'C': 4, 'N': 3, 'O': 2, 'F': 1, 'Ne': 0,
+ 'Si': 4, 'P': 3, 'S': 2, 'Cl': 1, 'Br': 1, 'Ar': 0, 'I': 1, 'X': 4, 'Li': 1}
+ valence_electrons = {'H+':0, 'e': 1, 'H': 1, 'He': 2, 'C': 4, 'N': 5, 'O': 6, 'F': 7, 'Ne': 8,
+ 'Si': 4, 'P': 5, 'S': 6, 'Cl': 7, 'Br': 7, 'Ar': 8, 'I': 7, 'X': 4, 'Li': 1}
+ lone_pairs = {'H+':0, 'e': 0, 'H': 0, 'He': 1, 'C': 0, 'N': 1, 'O': 2, 'F': 3, 'Ne': 4,
+ 'Si': 0, 'P': 1, 'S': 2, 'Cl': 3, 'Br': 3, 'Ar': 4, 'I': 3, 'X': 0, 'Li': 0}
electronegativity = {'H': 2.20, 'D': 2.20, 'T': 2.20, 'C': 2.55, 'C13': 2.55, 'N': 3.04, 'O': 3.44, 'O18': 3.44,
- 'F': 3.98, 'Si': 1.90, 'P': 2.19, 'S': 2.58, 'Cl': 3.16, 'Br': 2.96, 'I': 2.66, 'X': 0.0}
+ 'F': 3.98, 'Si': 1.90, 'P': 2.19, 'S': 2.58, 'Cl': 3.16, 'Br': 2.96, 'I': 2.66, 'X': 0.0,
+ 'Li' : 0.98}
################################################################################
@@ -173,6 +174,8 @@ def get_element(value, isotope=-1):
# Recommended IUPAC nomenclature is used throughout (including 'aluminium' and
# 'caesium')
+# electron
+e = Element(-1, 'e', 'electron' , 5.486e-7)
# Surface site
X = Element(0, 'X', 'surface_site' , 0.0)
@@ -310,6 +313,7 @@ def get_element(value, isotope=-1):
# A list of the elements, sorted by increasing atomic number
element_list = [
+ e,
X,
H, D, T, He,
Li, Be, B, C, C13, N, O, O18, F, Ne,
@@ -332,12 +336,14 @@ def get_element(value, isotope=-1):
# P=P value is from: https://www2.chemistry.msu.edu/faculty/reusch/OrgPage/bndenrgy.htm
# C#S is the value for [C+]#[S-] from 10.1002/chem.201002840 referenced relative to 0 K
# X-O and X-X (X=F,Cl,Br) taken from https://labs.chem.ucsb.edu/zakarian/armen/11---bonddissociationenergy.pdf
+# Li-C and Li-S are taken referenced from 0K from from http://staff.ustc.edu.cn/~luo971/2010-91-CRC-BDEs-Tables.pdf
+# Li-N is a G3 calculation taken from https://doi.org/10.1021/jp050857o
# The reference state is gaseous state at 298 K, but some of the values in the bde_dict might be coming from 0 K.
# The bond dissociation energy at 298 K is greater than the bond dissociation energy at 0 K by 0.6 to 0.9 kcal/mol
# (between RT and 3/2 RT), and this difference is usually much smaller than the uncertainty in the bond dissociation
# energy itself. Therefore, the discrepancy between 0 K and 298 K shouldn't matter too much.
# But for any new entries, try to use the consistent reference state of 298 K.
-bde_elements = ['C', 'N', 'H', 'O', 'S', 'Cl', 'Si', 'P', 'F', 'Br', 'I'] # elements supported by BDE
+bde_elements = ['C', 'N', 'H', 'O', 'S', 'Cl', 'Si', 'P', 'F', 'Br', 'I', 'Li'] # elements supported by BDE
bde_dict = {('H', 'H', 1.0): (432.0, 'kJ/mol'), ('H', 'C', 1): (411.0, 'kJ/mol'),
('H', 'N', 1): (386.0, 'kJ/mol'), ('H', 'O', 1.0): (459.0, 'kJ/mol'),
('H', 'P', 1): (322.0, 'kJ/mol'), ('H', 'S', 1): (363.0, 'kJ/mol'),
@@ -375,7 +381,12 @@ def get_element(value, isotope=-1):
('Br', 'Br', 1): (190.0, 'kJ/mol'), ('I', 'I', 1): (148.0, 'kJ/mol'),
('F', 'O', 1): (222.0, 'kJ/mol'), ('Cl', 'O', 1): (272.0, 'kJ/mol'),
('Br', 'O', 1): (235.1, 'kJ/mol'), ('Cl', 'F', 1): (250.54, 'kJ/mol'),
- ('Br', 'F', 1): (233.8, 'kJ/mol'), ('Br', 'Cl', 1): (218.84, 'kJ/mol')}
+ ('Br', 'F', 1): (233.8, 'kJ/mol'), ('Br', 'Cl', 1): (218.84, 'kJ/mol'),
+ ('Li', 'Br', 1): (423.0, 'kJ/mol'), ('Li', 'F', 1): (577.0, 'kJ/mol'),
+ ('Li', 'Cl', 1): (469.0, 'kJ/mol'), ('Li', 'H', 1): (247.0, 'kJ/mol'),
+ ('Li', 'I', 1): (352.0, 'kJ/mol'), ('Li', 'O', 1): (341.6, 'kJ/mol'),
+ ('Li', 'C', 1): (214.6, 'kJ/mol'), ('Li', 'S', 1): (312.5, 'kJ/mol'),
+ ('Li', 'N', 1): (302.4, 'kJ/mol')}
bdes = {}
for key, value in bde_dict.items():
diff --git a/rmgpy/molecule/filtration.py b/rmgpy/molecule/filtration.py
index 0ccdf991bc..dc310f036f 100644
--- a/rmgpy/molecule/filtration.py
+++ b/rmgpy/molecule/filtration.py
@@ -64,11 +64,14 @@ def filter_structures(mol_list, mark_unreactive=True, allow_expanded_octet=True,
if not all([(mol.multiplicity == mol_list[0].multiplicity) for mol in mol_list]):
raise ValueError("Cannot filter structures with different multiplicities!")
+ #Remove structures that try to put negative charges on metal ions
+ filtered_list = ionic_bond_filteration(mol_list)
+
# Get an octet deviation list
- octet_deviation_list = get_octet_deviation_list(mol_list, allow_expanded_octet=allow_expanded_octet)
+ octet_deviation_list = get_octet_deviation_list(filtered_list, allow_expanded_octet=allow_expanded_octet)
# Filter mol_list using the octet rule and the respective octet deviation list
- filtered_list, charge_span_list = octet_filtration(mol_list, octet_deviation_list)
+ filtered_list, charge_span_list = octet_filtration(filtered_list, octet_deviation_list)
# Filter by charge
filtered_list = charge_filtration(filtered_list, charge_span_list)
@@ -91,6 +94,21 @@ def filter_structures(mol_list, mark_unreactive=True, allow_expanded_octet=True,
return filtered_list
+def ionic_bond_filteration(mol_list):
+ """
+ Returns a filtered list removing structures that put a negative charge on lithium
+ which is ionically bonded and thus cannot donate/recieve electrons covalently
+ """
+ filtered_list = []
+ for mol in mol_list:
+ for atom in mol.atoms:
+ if atom.is_lithium() and atom.charge < 0:
+ break
+ else:
+ filtered_list.append(mol)
+
+ return filtered_list
+
def get_octet_deviation_list(mol_list, allow_expanded_octet=True):
"""
Returns the a list of octet deviations for a respective list of :class:Molecule objects
@@ -113,7 +131,7 @@ def get_octet_deviation(mol, allow_expanded_octet=True):
octet_deviation = 0 # This is the overall "score" for the molecule, summed across all non-H atoms
for atom in mol.vertices:
- if isinstance(atom, CuttingLabel) or atom.is_hydrogen():
+ if isinstance(atom, CuttingLabel) or atom.is_hydrogen() or atom.is_lithium():
continue
val_electrons = 2 * (int(atom.get_total_bond_order()) + atom.lone_pairs) + atom.radical_electrons
if atom.is_carbon() or atom.is_nitrogen() or atom.is_oxygen():
diff --git a/rmgpy/molecule/fragment.py b/rmgpy/molecule/fragment.py
index 113628e8b1..6e5c10652c 100644
--- a/rmgpy/molecule/fragment.py
+++ b/rmgpy/molecule/fragment.py
@@ -165,6 +165,9 @@ def __str__(self):
return "{0}({1:d})".format(self.label, self.index)
# override methods
+ def is_lithium(self):
+ return False
+
def copy(self, deep=False):
"""
Create a copy of the current graph. If `deep` is ``True``, a deep copy
@@ -388,7 +391,7 @@ def is_radical(self):
return True
return False
- def update(self, sort_atoms=True):
+ def update(self, log_species=False, sort_atoms=False, raise_atomtype_exception=False):
# currently sort_atoms does not work for fragments
for v in self.vertices:
if not isinstance(v, CuttingLabel):
@@ -657,9 +660,8 @@ def to_smiles(self):
substi = Atom(
element=get_element("Si"),
radical_electrons=0,
- charge=0,
- lone_pairs=3,
- )
+ charge=-3,
+ lone_pairs=3)
substi.label = element_symbol
for bonded_atom, bond in atom.edges.items():
diff --git a/rmgpy/molecule/group.pxd b/rmgpy/molecule/group.pxd
index afbd02f161..83b4ae83c6 100644
--- a/rmgpy/molecule/group.pxd
+++ b/rmgpy/molecule/group.pxd
@@ -74,6 +74,10 @@ cdef class GroupAtom(Vertex):
cpdef bint is_bonded_to_surface(self) except -2
+ cpdef bint is_proton(self)
+
+ cpdef bint is_electron(self)
+
cpdef bint is_oxygen(self)
cpdef bint is_sulfur(self)
@@ -190,6 +194,10 @@ cdef class Group(Graph):
cpdef bint is_surface_site(self) except -2
+ cpdef bint is_proton(self)
+
+ cpdef bint is_electron(self)
+
cpdef bint contains_surface_site(self) except -2
cpdef list get_surface_sites(self)
diff --git a/rmgpy/molecule/group.py b/rmgpy/molecule/group.py
index f4b0649c0d..1dc56cc7f7 100644
--- a/rmgpy/molecule/group.py
+++ b/rmgpy/molecule/group.py
@@ -44,6 +44,7 @@
from rmgpy.molecule.atomtype import ATOMTYPES, allElements, nonSpecifics, get_features, AtomType
from rmgpy.molecule.element import PeriodicSystem
from rmgpy.molecule.graph import Vertex, Edge, Graph
+from rmgpy.molecule.fragment import CuttingLabel
################################################################################
@@ -80,7 +81,7 @@ class GroupAtom(Vertex):
order to match.
"""
- def __init__(self, atomtype=None, radical_electrons=None, charge=None, label='', lone_pairs=None, site=None, morphology=None,
+ def __init__(self, atomtype=None, radical_electrons=None, charge=None, label='', lone_pairs=None, site=None, morphology=None,
props=None):
Vertex.__init__(self)
self.atomtype = atomtype or []
@@ -115,7 +116,7 @@ def __reduce__(self):
atomtype = self.atomtype
if atomtype is not None:
atomtype = [a.label for a in atomtype]
- return (GroupAtom, (atomtype, self.radical_electrons, self.charge, self.label, self.lone_pairs, self.site,
+ return (GroupAtom, (atomtype, self.radical_electrons, self.charge, self.label, self.lone_pairs, self.site,
self.morphology, self.props), d)
def __setstate__(self, d):
@@ -271,6 +272,54 @@ def _lose_radical(self, radical):
# Set the new radical electron counts
self.radical_electrons = radical_electrons
+ def _gain_charge(self, charge):
+ """
+ Update the atom group as a result of applying a GAIN_CHARGE action,
+ where `charge` specifies the charge gained.
+ """
+ atomtype = []
+
+ for atom in self.atomtype:
+ atomtype.extend(atom.increment_charge)
+
+ if any([len(atom.increment_charge) == 0 for atom in self.atomtype]):
+ raise ActionError('Unable to update GroupAtom due to GAIN_CHARGE action: '
+ 'Unknown atom type produced from set "{0}".'.format(self.atomtype))
+
+ if isinstance(self.charge,list):
+ charges = []
+ for c in self.charge:
+ charges.append(c+charge)
+ self.charge = charges
+ else:
+ self.charge += 1
+
+ self.atomtype = list(set(atomtype))
+
+ def _lose_charge(self, charge):
+ """
+ Update the atom group as a result of applying a LOSE_CHARGE action,
+ where `charge` specifies lost charge.
+ """
+ atomtype = []
+
+ for atom in self.atomtype:
+ atomtype.extend(atom.decrement_charge)
+
+ if any([len(atomtype.decrement_charge) == 0 for atomtype in self.atomtype]):
+ raise ActionError('Unable to update GroupAtom due to LOSE_CHARGE action: '
+ 'Unknown atom type produced from set "{0}".'.format(self.atomtype))
+
+ if isinstance(self.charge,list):
+ charges = []
+ for c in self.charge:
+ charges.append(c-charge)
+ self.charge = charges
+ else:
+ self.charge -= 1
+
+ self.atomtype = list(set(atomtype))
+
def _gain_pair(self, pair):
"""
Update the atom group as a result of applying a GAIN_PAIR action,
@@ -342,8 +391,12 @@ def apply_action(self, action):
self._break_bond(action[2])
elif act == 'GAIN_RADICAL':
self._gain_radical(action[2])
+ elif act == 'GAIN_CHARGE':
+ self._gain_charge(action[2])
elif act == 'LOSE_RADICAL':
self._lose_radical(action[2])
+ elif act == 'LOSE_CHARGE':
+ self._lose_charge(action[2])
elif action[0].upper() == 'GAIN_PAIR':
self._gain_pair(action[2])
elif action[0].upper() == 'LOSE_PAIR':
@@ -357,7 +410,7 @@ def equivalent(self, other, strict=True):
where `other` can be either an :class:`Atom` or an :class:`GroupAtom`
object. When comparing two :class:`GroupAtom` objects, this function
respects wildcards, e.g. ``R!H`` is equivalent to ``C``.
-
+
"""
cython.declare(group=GroupAtom)
if not strict:
@@ -453,7 +506,7 @@ def is_specific_case_of(self, other):
"""
Returns ``True`` if `self` is the same as `other` or is a more
specific case of `other`. Returns ``False`` if some of `self` is not
- included in `other` or they are mutually exclusive.
+ included in `other` or they are mutually exclusive.
"""
cython.declare(group=GroupAtom)
if not isinstance(other, GroupAtom):
@@ -550,6 +603,18 @@ def is_bonded_to_surface(self):
return True
return False
+ def is_electron(self):
+ """
+ Return ``True`` if the atom represents a surface site or ``False`` if not.
+ """
+ return self.atomtype[0] == ATOMTYPES['e']
+
+ def is_proton(self):
+ """
+ Return ``True`` if the atom represents a surface site or ``False`` if not.
+ """
+ return self.atomtype[0] == ATOMTYPES['H+']
+
def is_oxygen(self):
"""
Return ``True`` if the atom represents an oxygen atom or ``False`` if not.
@@ -606,6 +671,14 @@ def is_bromine(self):
check_list = [x in all_bromine for x in self.atomtype]
return all(check_list)
+ def is_lithium(self):
+ """
+ Return ``True`` if the atom represents a bromine atom or ``False`` if not.
+ """
+ all_lithium = [ATOMTYPES['Li']] + ATOMTYPES['Li'].specific
+ check_list = [x in all_lithium for x in self.atomtype]
+ return all(check_list)
+
def has_wildcards(self):
"""
Return ``True`` if the atom has wildcards in any of the attributes:
@@ -686,6 +759,7 @@ def make_sample_atom(self):
'I': 3,
'Ar': 4,
'X': 0,
+ 'e': 0
}
for element_label in allElements:
@@ -863,7 +937,7 @@ def is_single(self, wildcards=False):
not. If `wildcards` is ``False`` we return False anytime there is more
than one bond order, otherwise we return ``True`` if any of the options
are single.
-
+
NOTE: we can replace the absolute value relation with math.isclose when
we swtich to python 3.5+
"""
@@ -940,7 +1014,7 @@ def is_van_der_waals(self, wildcards=False):
else:
return abs(self.order[0]) <= 1e-9 and len(self.order) == 1
-
+
def is_reaction_bond(self, wildcards=False):
"""
Return ``True`` if the bond represents a van der Waals bond or ``False`` if
@@ -988,7 +1062,7 @@ def is_hydrogen_bond(self, wildcards=False):
return False
else:
return abs(self.order[0] - 0.1) <= 1e-9 and len(self.order) == 1
-
+
def is_reaction_bond(self, wildcards=False):
"""
Return ``True`` if the bond represents a reaction bond or ``False`` if
@@ -1114,13 +1188,13 @@ class Group(Graph):
"""
A representation of a molecular substructure group using a graph data
type, extending the :class:`Graph` class. The attributes are:
-
+
=================== =================== ====================================
Attribute Type Description
=================== =================== ====================================
`atoms` ``list`` Aliases for the `vertices` storing :class:`GroupAtom`
`multiplicity` ``list`` Range of multiplicities accepted for the group
- `props` ``dict`` Dictionary of arbitrary properties/flags classifying state of Group object
+ `props` ``dict`` Dictionary of arbitrary properties/flags classifying state of Group object
`metal` ``list`` List of metals accepted for the group
`facet` ``list`` List of facets accepted for the group
=================== =================== ====================================
@@ -1255,6 +1329,14 @@ def get_surface_sites(self):
cython.declare(atom=GroupAtom)
return [atom for atom in self.atoms if atom.is_surface_site()]
+ def is_proton(self):
+ """Returns ``True`` iff the group is a proton"""
+ return len(self.atoms) == 1 and self.atoms[0].is_proton()
+
+ def is_electron(self):
+ """Returns ``True`` iff the group is an electron"""
+ return len(self.atoms) == 1 and self.atoms[0].is_electron()
+
def remove_atom(self, atom):
"""
Remove `atom` and all bonds associated with it from the graph. Does
@@ -1337,6 +1419,8 @@ def update_charge(self):
and radical electrons. This method is used for products of specific families with recipes that modify charges.
"""
for atom in self.atoms:
+ if isinstance(atom, CuttingLabel):
+ continue
if (len(atom.charge) == 1) and (len(atom.lone_pairs) == 1) and (len(atom.radical_electrons) == 1):
# if the charge of the group is not labeled, then no charge update will be
# performed. If there multiple charges are assigned, no update either.
@@ -1565,10 +1649,10 @@ def specify_atom_extensions(self, i, basename, r):
old_atom_type = grp.atoms[i].atomtype
grp.atoms[i].atomtype = [item]
grpc.atoms[i].atomtype = list(Rset - {item})
-
+
if len(grpc.atoms[i].atomtype) == 0:
grpc = None
-
+
if len(old_atom_type) > 1:
labelList = []
old_atom_type_str = ''
@@ -1632,10 +1716,10 @@ def specify_unpaired_extensions(self, i, basename, r_un):
grpc = deepcopy(self)
grp.atoms[i].radical_electrons = [item]
grpc.atoms[i].radical_electrons = list(Rset - {item})
-
+
if len(grpc.atoms[i].radical_electrons) == 0:
grpc = None
-
+
atom_type = grp.atoms[i].atomtype
if len(atom_type) > 1:
@@ -1742,10 +1826,10 @@ def specify_bond_extensions(self, i, j, basename, r_bonds):
grp.atoms[j].bonds[grp.atoms[i]].order = [bd]
grpc.atoms[i].bonds[grpc.atoms[j]].order = list(Rbset - {bd})
grpc.atoms[j].bonds[grpc.atoms[i]].order = list(Rbset - {bd})
-
+
if len(list(Rbset - {bd})) == 0:
grpc = None
-
+
atom_type_i = grp.atoms[i].atomtype
atom_type_j = grp.atoms[j].atomtype
@@ -1770,7 +1854,7 @@ def specify_bond_extensions(self, i, j, basename, r_bonds):
else:
atom_type_j_str = atom_type_j[0].label
- b = None
+ b = None
for v in bdict.keys():
if abs(v - bd) < 1e-4:
b = bdict[v]
@@ -2067,7 +2151,7 @@ def find_subgraph_isomorphisms(self, other, initial_map=None, save_order=False):
else:
if group.facet:
return []
-
+
# Do the isomorphism comparison
return Graph.find_subgraph_isomorphisms(self, other, initial_map, save_order=save_order)
@@ -2083,7 +2167,7 @@ def is_identical(self, other, save_order=False):
if not isinstance(other, Group):
raise TypeError(
'Got a {0} object for parameter "other", when a Group object is required.'.format(other.__class__))
- # An identical group is always a child of itself and
+ # An identical group is always a child of itself and
# is the only case where that is true. Therefore
# if we do both directions of isSubgraphIsmorphic, we need
# to get True twice for it to be identical
@@ -2899,10 +2983,24 @@ def make_sample_molecule(self):
group_atom = mol_to_group[atom]
else:
raise UnexpectedChargeError(graph=new_molecule)
- if atom.charge in group_atom.atomtype[0].charge:
- # declared charge in atomtype is same as new charge
+ # check hardcoded atomtypes
+ positive_charged = ['H+',
+ 'Csc', 'Cdc',
+ 'N3sc', 'N5sc', 'N5dc', 'N5ddc', 'N5tc', 'N5b',
+ 'O2sc', 'O4sc', 'O4dc', 'O4tc',
+ 'P5sc', 'P5dc', 'P5ddc', 'P5tc', 'P5b',
+ 'S2sc', 'S4sc', 'S4dc', 'S4tdc', 'S6sc', 'S6dc', 'S6tdc']
+ negative_charged = ['e',
+ 'C2sc', 'C2dc', 'C2tc',
+ 'N0sc', 'N1sc', 'N1dc', 'N5dddc',
+ 'O0sc',
+ 'P0sc', 'P1sc', 'P1dc', 'P5sc',
+ 'S0sc', 'S2sc', 'S2dc', 'S2tc', 'S4sc', 'S4dc', 'S4tdc', 'S6sc', 'S6dc', 'S6tdc']
+ if atom.charge > 0 and any([group_atom.atomtype[0] is ATOMTYPES[x] or ATOMTYPES[x].is_specific_case_of(group_atom.atomtype[0]) for x in positive_charged]):
+ pass
+ elif atom.charge < 0 and any([group_atom.atomtype[0] is ATOMTYPES[x] or ATOMTYPES[x].is_specific_case_of(group_atom.atomtype[0]) for x in negative_charged]):
pass
- elif atom.charge in group_atom.charge:
+ elif atom.charge in group_atom.atomtype[0].charge:
# declared charge in original group is same as new charge
pass
else:
diff --git a/rmgpy/molecule/molecule.pxd b/rmgpy/molecule/molecule.pxd
index f227e5d533..335486f76f 100644
--- a/rmgpy/molecule/molecule.pxd
+++ b/rmgpy/molecule/molecule.pxd
@@ -56,6 +56,10 @@ cdef class Atom(Vertex):
cpdef Vertex copy(self)
+ cpdef bint is_electron(self)
+
+ cpdef bint is_proton(self)
+
cpdef bint is_hydrogen(self)
cpdef bint is_non_hydrogen(self)
@@ -89,7 +93,11 @@ cdef class Atom(Vertex):
cpdef increment_radical(self)
cpdef decrement_radical(self)
-
+
+ cpdef increment_charge(self)
+
+ cpdef decrement_charge(self)
+
cpdef set_lone_pairs(self, int lone_pairs)
cpdef increment_lone_pairs(self)
@@ -166,6 +174,10 @@ cdef class Molecule(Graph):
cpdef bint has_bond(self, Atom atom1, Atom atom2)
+ cpdef bint is_electron(self)
+
+ cpdef bint is_proton(self)
+
cpdef bint contains_surface_site(self)
cpdef bint is_surface_site(self)
diff --git a/rmgpy/molecule/molecule.py b/rmgpy/molecule/molecule.py
index 03727be792..3f647c336a 100644
--- a/rmgpy/molecule/molecule.py
+++ b/rmgpy/molecule/molecule.py
@@ -58,6 +58,7 @@
from rmgpy.molecule.graph import Vertex, Edge, Graph, get_vertex_connectivity_value
from rmgpy.molecule.kekulize import kekulize
from rmgpy.molecule.pathfinder import find_shortest_path
+from rmgpy.molecule.fragment import CuttingLabel
################################################################################
@@ -354,6 +355,23 @@ def copy(self):
a.props = deepcopy(self.props)
return a
+ def is_electron(self):
+ """
+ Return ``True`` if the atom represents an electron or ``False`` if
+ not.
+ """
+ return self.element.number == -1
+
+ def is_proton(self):
+ """
+ Return ``True`` if the atom represents a proton or ``False`` if
+ not.
+ """
+
+ if self.element.number == 1 and self.charge == 1:
+ return True
+ return False
+
def is_hydrogen(self):
"""
Return ``True`` if the atom represents a hydrogen atom or ``False`` if
@@ -382,6 +400,13 @@ def is_carbon(self):
"""
return self.element.number == 6
+ def is_lithium(self):
+ """
+ Return ``True`` if the atom represents a hydrogen atom or ``False`` if
+ not.
+ """
+ return self.element.number == 3
+
def is_nitrogen(self):
"""
Return ``True`` if the atom represents a nitrogen atom or ``False`` if
@@ -503,6 +528,18 @@ def decrement_radical(self):
raise gr.ActionError('Unable to update Atom due to LOSE_RADICAL action: '
'Invalid radical electron set "{0}".'.format(self.radical_electrons))
+ def increment_charge(self):
+ """
+ Update the atom pattern as a result of applying a GAIN_CHARGE action
+ """
+ self.charge += 1
+
+ def decrement_charge(self):
+ """
+ Update the atom pattern as a result of applying a LOSE_CHARGE action
+ """
+ self.charge -= 1
+
def set_lone_pairs(self, lone_pairs):
"""
Set the number of lone electron pairs.
@@ -544,6 +581,10 @@ def update_charge(self):
if self.is_surface_site():
self.charge = 0
return
+ if self.is_electron():
+ self.charge = -1
+ return
+
valence_electron = elements.PeriodicSystem.valence_electrons[self.symbol]
order = self.get_total_bond_order()
self.charge = valence_electron - order - self.radical_electrons - 2 * self.lone_pairs
@@ -566,6 +607,10 @@ def apply_action(self, action):
for i in range(action[2]): self.increment_radical()
elif act == 'LOSE_RADICAL':
for i in range(abs(action[2])): self.decrement_radical()
+ elif act == 'GAIN_CHARGE':
+ for i in range(action[2]): self.increment_charge()
+ elif act == 'LOSE_CHARGE':
+ for i in range(abs(action[2])): self.decrement_charge()
elif action[0].upper() == 'GAIN_PAIR':
for i in range(action[2]): self.increment_lone_pairs()
elif action[0].upper() == 'LOSE_PAIR':
@@ -1175,6 +1220,14 @@ def is_surface_site(self):
"""Returns ``True`` iff the molecule is nothing but a surface site 'X'."""
return len(self.atoms) == 1 and self.atoms[0].is_surface_site()
+ def is_electron(self):
+ """Returns ``True`` iff the molecule is nothing but an electron 'e'."""
+ return len(self.atoms) == 1 and self.atoms[0].is_electron()
+
+ def is_proton(self):
+ """Returns ``True`` iff the molecule is nothing but a proton 'H+'."""
+ return len(self.atoms) == 1 and self.atoms[0].is_proton()
+
def remove_atom(self, atom):
"""
Remove `atom` and all bonds associated with it from the graph. Does
@@ -1221,17 +1274,22 @@ def sort_atoms(self):
for index, vertex in enumerate(self.vertices):
vertex.sorting_label = index
+ def update_charge(self):
+
+ for atom in self.atoms:
+ if not isinstance(atom, CuttingLabel):
+ atom.update_charge()
+
def update(self, log_species=True, raise_atomtype_exception=True, sort_atoms=True):
"""
- Update the charge and atom types of atoms.
+ Update the lone_pairs, charge, and atom types of atoms.
Update multiplicity, and sort atoms (if ``sort_atoms`` is ``True``)
Does not necessarily update the connectivity values (which are used in isomorphism checks)
If you need that, call update_connectivity_values()
"""
- for atom in self.atoms:
- atom.update_charge()
-
+ self.update_lone_pairs()
+ self.update_charge()
self.update_atomtypes(log_species=log_species, raise_exception=raise_atomtype_exception)
self.update_multiplicity()
if sort_atoms:
@@ -1810,7 +1868,7 @@ def from_smarts(self, smartsstr, raise_atomtype_exception=True):
return self
def from_adjacency_list(self, adjlist, saturate_h=False, raise_atomtype_exception=True,
- raise_charge_exception=True, check_consistency=True):
+ raise_charge_exception=False, check_consistency=True):
"""
Convert a string adjacency list `adjlist` to a molecular structure.
Skips the first line (assuming it's a label) unless `withLabel` is
@@ -2007,7 +2065,7 @@ def find_h_bonds(self):
ONinds = [n for n, a in enumerate(self.atoms) if a.is_oxygen() or a.is_nitrogen()]
for i, atm1 in enumerate(self.atoms):
- if atm1.atomtype.label == 'H':
+ if atm1.atomtype.label == 'H0':
atm_covs = [q for q in atm1.bonds.keys()]
if len(atm_covs) > 1: # H is already H bonded
continue
@@ -2270,10 +2328,15 @@ def is_aryl_radical(self, aromatic_rings=None, save_order=False):
def generate_resonance_structures(self, keep_isomorphic=False, filter_structures=True, save_order=False):
"""Returns a list of resonance structures of the molecule."""
- return resonance.generate_resonance_structures(self, keep_isomorphic=keep_isomorphic,
+
+ try:
+ return resonance.generate_resonance_structures(self, keep_isomorphic=keep_isomorphic,
filter_structures=filter_structures,
save_order=save_order,
)
+ except:
+ logging.warning("Resonance structure generation failed for {}".format(self))
+ return [self.copy(deep=True)]
def get_url(self):
"""
@@ -2303,7 +2366,7 @@ def update_lone_pairs(self):
"""
cython.declare(atom1=Atom, atom2=Atom, bond12=Bond, order=float)
for atom1 in self.vertices:
- if atom1.is_hydrogen() or atom1.is_surface_site():
+ if atom1.is_hydrogen() or atom1.is_surface_site() or atom1.is_electron() or atom1.is_lithium():
atom1.lone_pairs = 0
else:
order = atom1.get_total_bond_order()
diff --git a/rmgpy/molecule/translator.py b/rmgpy/molecule/translator.py
index 731d8d8d8e..fece53b041 100644
--- a/rmgpy/molecule/translator.py
+++ b/rmgpy/molecule/translator.py
@@ -103,7 +103,17 @@
"""
multiplicity 1
1 X u0
+ """,
+ 'e':
+ """
+ multiplicity 1
+ 1 e u0 p0 c-1
+ """,
+ '[H+]':
"""
+ multiplicity 1
+ 1 H u0 p0 c+1
+ """,
}
#: This dictionary is used to shortcut lookups of a molecule's SMILES string from its chemical formula.
@@ -128,6 +138,8 @@
'ClH': 'Cl',
'I2': '[I][I]',
'HI': 'I',
+ 'H': 'H+',
+ 'e': 'e'
}
RADICAL_LOOKUPS = {
@@ -155,7 +167,8 @@
'I': '[I]',
'CF': '[C]F',
'CCl': '[C]Cl',
- 'CBr': '[C]Br'
+ 'CBr': '[C]Br',
+ 'e': 'e'
}
@@ -506,7 +519,7 @@ def _read(mol, identifier, identifier_type, backend, raise_atomtype_exception=Tr
if _lookup(mol, identifier, identifier_type) is not None:
if _check_output(mol, identifier):
- mol.update_atomtypes(log_species=True, raise_exception=raise_atomtype_exception)
+ mol.update(log_species=True, raise_atomtype_exception=raise_atomtype_exception, sort_atoms=False)
return mol
for option in _get_backend_list(backend):
@@ -518,7 +531,7 @@ def _read(mol, identifier, identifier_type, backend, raise_atomtype_exception=Tr
raise NotImplementedError("Unrecognized backend {0}".format(option))
if _check_output(mol, identifier):
- mol.update_atomtypes(log_species=True, raise_exception=raise_atomtype_exception)
+ mol.update(log_species=True, raise_atomtype_exception=raise_atomtype_exception, sort_atoms=False)
return mol
else:
logging.debug('Backend {0} is not able to parse identifier {1}'.format(option, identifier))
diff --git a/rmgpy/quantity.py b/rmgpy/quantity.py
index 149031d13b..5d73546d45 100644
--- a/rmgpy/quantity.py
+++ b/rmgpy/quantity.py
@@ -776,6 +776,8 @@ def __call__(self, *args, **kwargs):
Momentum = UnitType('kg*m/s^2')
+Potential = UnitType('V')
+
Power = UnitType('W')
Pressure = UnitType('Pa', common_units=['bar', 'atm', 'torr', 'psi', 'mbar'])
diff --git a/rmgpy/reaction.pxd b/rmgpy/reaction.pxd
index 7755ba2311..15feecef3f 100644
--- a/rmgpy/reaction.pxd
+++ b/rmgpy/reaction.pxd
@@ -32,7 +32,7 @@ from rmgpy.molecule.graph cimport Vertex, Graph
from rmgpy.molecule.element cimport Element
from rmgpy.kinetics.model cimport KineticsModel
from rmgpy.kinetics.arrhenius cimport Arrhenius
-from rmgpy.kinetics.surface cimport SurfaceArrhenius, StickingCoefficient
+from rmgpy.kinetics.surface cimport SurfaceArrhenius, StickingCoefficient, SurfaceChargeTransfer
cimport numpy as np
@@ -49,8 +49,11 @@ cdef class Reaction:
cdef public KineticsModel kinetics
cdef public Arrhenius network_kinetics
cdef public SurfaceArrhenius
+ cdef public SurfaceChargeTransfer
cdef public bint duplicate
cdef public float _degeneracy
+ cdef public int electrons
+ cdef public int _protons
cdef public list pairs
cdef public bint allow_pdep_route
cdef public bint elementary_high_p
@@ -70,6 +73,10 @@ cdef class Reaction:
cpdef bint is_surface_reaction(self)
+ cpdef bint is_charge_transfer_reaction(self)
+
+ cpdef bint is_surface_charge_transfer_reaction(self)
+
cpdef bint has_template(self, list reactants, list products)
cpdef bint matches_species(self, list reactants, list products=?)
@@ -78,29 +85,37 @@ cdef class Reaction:
bint check_only_label=?, bint check_template_rxn_products=?, bint generate_initial_map=?,
bint strict=?, bint save_order=?) except -2
+ cpdef double _apply_CHE_model(self, double T)
+
cpdef double get_enthalpy_of_reaction(self, double T)
cpdef double get_entropy_of_reaction(self, double T)
- cpdef double get_free_energy_of_reaction(self, double T)
+ cpdef double _get_free_energy_of_charge_transfer_reaction(self, double T, double potential=?)
+
+ cpdef double get_free_energy_of_reaction(self, double T, double potential=?)
- cpdef double get_equilibrium_constant(self, double T, str type=?, double surface_site_density=?)
+ cpdef double get_reversible_potential(self, double T)
+
+ cpdef double set_reference_potential(self, double T)
+
+ cpdef double get_equilibrium_constant(self, double T, double potential=?, str type=?, double surface_site_density=?)
cpdef np.ndarray get_enthalpies_of_reaction(self, np.ndarray Tlist)
cpdef np.ndarray get_entropies_of_reaction(self, np.ndarray Tlist)
- cpdef np.ndarray get_free_energies_of_reaction(self, np.ndarray Tlist)
+ cpdef np.ndarray get_free_energies_of_reaction(self, np.ndarray Tlist, double potential=?)
- cpdef np.ndarray get_equilibrium_constants(self, np.ndarray Tlist, str type=?)
+ cpdef np.ndarray get_equilibrium_constants(self, np.ndarray Tlist, double potential=?, str type=?)
cpdef int get_stoichiometric_coefficient(self, Species spec)
- cpdef double get_rate_coefficient(self, double T, double P=?, double surface_site_density=?)
+ cpdef double get_rate_coefficient(self, double T, double P=?, double surface_site_density=?, double potential=?)
- cpdef double get_surface_rate_coefficient(self, double T, double surface_site_density) except -2
+ cpdef double get_surface_rate_coefficient(self, double T, double surface_site_density, double potential=?) except -2
- cpdef fix_barrier_height(self, bint force_positive=?)
+ cpdef fix_barrier_height(self, bint force_positive=?, str solvent=?, bint apply_solvation_correction=?)
cpdef reverse_arrhenius_rate(self, Arrhenius k_forward, str reverse_units, Tmin=?, Tmax=?)
@@ -108,6 +123,8 @@ cdef class Reaction:
cpdef reverse_sticking_coeff_rate(self, StickingCoefficient k_forward, str reverse_units, double surface_site_density, Tmin=?, Tmax=?)
+ cpdef reverse_surface_charge_transfer_rate(self, SurfaceChargeTransfer k_forward, str reverse_units, Tmin=?, Tmax=?)
+
cpdef generate_reverse_rate_coefficient(self, bint network_kinetics=?, Tmin=?, Tmax=?, double surface_site_density=?)
cpdef np.ndarray calculate_tst_rate_coefficients(self, np.ndarray Tlist)
diff --git a/rmgpy/reaction.py b/rmgpy/reaction.py
index f10e1dcefa..19136c60fa 100644
--- a/rmgpy/reaction.py
+++ b/rmgpy/reaction.py
@@ -30,8 +30,8 @@
"""
This module contains classes and functions for working with chemical reactions.
-From the `IUPAC Compendium of Chemical Terminology
-`_, a chemical reaction is "a process that
+From the `IUPAC Compendium of Chemical Terminology
+`_, a chemical reaction is "a process that
results in the interconversion of chemical species".
In RMG Py, a chemical reaction is represented in memory as a :class:`Reaction`
@@ -53,14 +53,15 @@
from rmgpy.exceptions import ReactionError, KineticsError
from rmgpy.kinetics import KineticsData, ArrheniusBM, ArrheniusEP, ThirdBody, Lindemann, Troe, Chebyshev, \
PDepArrhenius, MultiArrhenius, MultiPDepArrhenius, get_rate_coefficient_units_from_reaction_order, \
- SurfaceArrheniusBEP, StickingCoefficientBEP
+ SurfaceArrheniusBEP, StickingCoefficientBEP, ArrheniusChargeTransfer, ArrheniusChargeTransferBM, Marcus
from rmgpy.kinetics.arrhenius import Arrhenius # Separate because we cimport from rmgpy.kinetics.arrhenius
-from rmgpy.kinetics.surface import SurfaceArrhenius, StickingCoefficient # Separate because we cimport from rmgpy.kinetics.surface
+from rmgpy.kinetics.surface import SurfaceArrhenius, StickingCoefficient, SurfaceChargeTransfer, SurfaceChargeTransferBEP # Separate because we cimport from rmgpy.kinetics.surface
from rmgpy.kinetics.diffusionLimited import diffusion_limiter
from rmgpy.molecule.element import Element, element_list
from rmgpy.molecule.molecule import Molecule, Atom
from rmgpy.pdep.reaction import calculate_microcanonical_rate_coefficient
from rmgpy.species import Species
+from rmgpy.thermo import ThermoData
################################################################################
@@ -68,7 +69,7 @@
class Reaction:
"""
A chemical reaction. The attributes are:
-
+
=================== =========================== ============================
Attribute Type Description
=================== =========================== ============================
@@ -92,7 +93,7 @@ class Reaction:
`is_forward` ``bool`` Indicates if the reaction was generated in the forward (true) or reverse (false)
`rank` ``int`` Integer indicating the accuracy of the kinetics for this reaction
=================== =========================== ============================
-
+
"""
def __init__(self,
@@ -110,10 +111,11 @@ def __init__(self,
pairs=None,
allow_pdep_route=False,
elementary_high_p=False,
- allow_max_rate_violation=False,
rank=None,
+ electrons=0,
comment='',
is_forward=None,
+ allow_max_rate_violation=False,
):
self.index = index
self.label = label
@@ -129,11 +131,12 @@ def __init__(self,
self.pairs = pairs
self.allow_pdep_route = allow_pdep_route
self.elementary_high_p = elementary_high_p
+ self.rank = rank
+ self.electrons = electrons
self.comment = comment
self.k_effective_cache = {}
self.is_forward = is_forward
self.allow_max_rate_violation = allow_max_rate_violation
- self.rank = rank
def __repr__(self):
"""
@@ -157,7 +160,8 @@ def __repr__(self):
if self.elementary_high_p: string += 'elementary_high_p={0}, '.format(self.elementary_high_p)
if self.comment != '': string += 'comment={0!r}, '.format(self.comment)
if self.rank is not None: string += 'rank={0!r},'.format(self.rank)
- string = string[:-2] + ')'
+ if self.electrons != 0: string += 'electrons={0:d},'.format(self.electrons)
+ string = string[:-1] + ')'
return string
def __str__(self):
@@ -169,7 +173,7 @@ def __str__(self):
def to_labeled_str(self, use_index=False):
"""
- the same as __str__ except that the labels are assumed to exist and used for reactant and products rather than
+ the same as __str__ except that the labels are assumed to exist and used for reactant and products rather than
the labels plus the index in parentheses
"""
arrow = ' <=> ' if self.reversible else ' => '
@@ -198,7 +202,8 @@ def __reduce__(self):
self.allow_pdep_route,
self.elementary_high_p,
self.rank,
- self.comment
+ self.electrons,
+ self.comment,
))
@property
@@ -231,10 +236,28 @@ def degeneracy(self, new):
# set new degeneracy
self._degeneracy = new
+ @property
+ def protons(self):
+ """
+ The stochiometric coeff for protons in charge transfer reactions
+ """
+ if self.is_charge_transfer_reaction():
+ self._protons = 0
+ for prod in self.products:
+ if prod.is_proton():
+ self._protons += 1
+ for react in self.reactants:
+ if react.is_proton():
+ self._protons -= 1
+ else:
+ self._protons = 0
+
+ return self._protons
+
def to_chemkin(self, species_list=None, kinetics=True):
"""
Return the chemkin-formatted string for this reaction.
-
+
If `kinetics` is set to True, the chemkin format kinetics will also
be returned (requires the `species_list` to figure out third body colliders.)
Otherwise, only the reaction string will be returned.
@@ -363,9 +386,9 @@ def to_cantera(self, species_list=None, use_chemkin_identifier=False):
if isinstance(ct_reaction, list):
for rxn in ct_reaction:
rxn.reversible = self.reversible
- # Set the duplicate flag to true since this reaction comes from multiarrhenius or multipdeparrhenius
+ # Set the duplicate flag to true since this reaction comes from multiarrhenius or multipdeparrhenius
rxn.duplicate = True
- # Set the ID flag to the original rmg index
+ # Set the ID flag to the original rmg index
rxn.ID = str(self.index)
else:
ct_reaction.reversible = self.reversible
@@ -439,6 +462,22 @@ def is_surface_reaction(self):
return True
return False
+ def is_charge_transfer_reaction(self):
+ """
+ Return ``True`` if one or more reactants or products are electrons
+ """
+ if self.electrons != 0:
+ return True
+ return False
+
+ def is_surface_charge_transfer_reaction(self):
+ """
+ Return ``True`` if one or more reactants or products are electrons
+ """
+ if self.is_surface_reaction() and self.is_charge_transfer_reaction():
+ return True
+ return False
+
def has_template(self, reactants, products):
"""
Return ``True`` if the reaction matches the template of `reactants`
@@ -506,6 +545,10 @@ def is_isomorphic(self, other, either_direction=True, check_identical=False, che
strict=strict,
save_order=save_order)
+ # compare stoichiometry of electrons in reaction
+ if self.electrons != other.electrons:
+ return False
+
# Compare reactants to reactants
forward_reactants_match = same_species_lists(self.reactants, other.reactants,
check_identical=check_identical,
@@ -550,6 +593,33 @@ def is_isomorphic(self, other, either_direction=True, check_identical=False, che
# should have already returned if it matches forwards, or we're not allowed to match backwards
return reverse_reactants_match and reverse_products_match and collider_match
+ def _apply_CHE_model(self, T):
+ """
+ Apply the computational hydrogen electrode (CHE) model at temperature T (in 'K').
+
+ Returns the free energy (in J/mol) of 'N' proton/electron couple(s) in the reaction
+ using the Reversible Hydrogen Electrode (RHE) as referernce so that
+ N * deltaG(H+ + e-) = N * 1/2 deltaG(H2(g)) at 0V.
+ """
+
+ if not self.is_charge_transfer_reaction():
+ raise ReactionError("CHE model is only applicable to charge transfer reactions!")
+
+ if self.electrons != self.protons:
+ raise ReactionError("Number of electrons must equal number of protons! "
+ f"{self} has {self.electrons} protons and {self.electrons} electrons")
+
+
+ H2_thermo = ThermoData(Tdata=([300,400,500,600,800,1000,1500],'K'),
+ Cpdata=([6.895,6.975,6.994,7.009,7.081,7.219,7.72],'cal/(mol*K)'),
+ H298=(0,'kcal/mol'), S298=(31.233,'cal/(mol*K)','+|-',0.0007),
+ Cp0=(29.1007,'J/(mol*K)'), CpInf=(37.4151,'J/(mol*K)'),
+ label="""H2""", comment="""Thermo library: primaryThermoLibrary""")
+ # deltG_H+ + deltaG_e- -> 1/2 deltaG_H2 # only at 298K ???
+
+ return self.electrons * 0.5 * H2_thermo.get_free_energy(T)
+
+
def get_enthalpy_of_reaction(self, T):
"""
Return the enthalpy of reaction in J/mol evaluated at temperature
@@ -576,12 +646,40 @@ def get_entropy_of_reaction(self, T):
dSrxn += product.get_entropy(T)
return dSrxn
- def get_free_energy_of_reaction(self, T):
+ def _get_free_energy_of_charge_transfer_reaction(self, T, potential=0.):
+
+ cython.declare(dGrxn=cython.double, reactant=Species, product=Species)
+
+ dGrxn = 0
+ for reactant in self.reactants:
+ try:
+ dGrxn -= reactant.get_free_energy(T)
+ except Exception:
+ logging.error("Problem with reactant {!r} in reaction {!s}".format(reactant, self))
+ raise
+
+ for product in self.products:
+ try:
+ dGrxn += product.get_free_energy(T)
+ except Exception:
+ logging.error("Problem with product {!r} in reaction {!s}".format(reactant, self))
+ raise
+
+ if potential != 0.:
+ dGrxn -= self.electrons * constants.F * potential
+
+ return dGrxn
+
+ def get_free_energy_of_reaction(self, T, potential=0.):
"""
Return the Gibbs free energy of reaction in J/mol evaluated at
- temperature `T` in K.
+ temperature `T` in K and potential in Volts (if applicable)
"""
cython.declare(dGrxn=cython.double, reactant=Species, product=Species)
+
+ if self.is_charge_transfer_reaction():
+ return self._get_free_energy_of_charge_transfer_reaction(T, potential=potential)
+
dGrxn = 0.0
for reactant in self.reactants:
try:
@@ -595,9 +693,33 @@ def get_free_energy_of_reaction(self, T):
except Exception:
logging.error("Problem with product {!r} in reaction {!s}".format(reactant, self))
raise
+
return dGrxn
- def get_equilibrium_constant(self, T, type='Kc', surface_site_density=2.5e-05):
+ def get_reversible_potential(self, T):
+ """
+ Get the Potential in `V` at T in 'K' at which the charge transfer reaction is at equilibrium
+ """
+ cython.declare(deltaG=cython.double, V0=cython.double)
+ if not self.is_charge_transfer_reaction():
+ raise KineticsError("Cannot get reversible potential for non charge transfer reactions")
+
+ deltaG = self._get_free_energy_of_charge_transfer_reaction(T) #J/mol
+ V0 = deltaG / self.electrons / constants.F # V = deltaG / n / F
+ return V0
+
+ def set_reference_potential(self, T):
+ """
+ Set the reference Potential of the `SurfaceChargeTransfer` kinetics model to the reversible potential
+ of the reaction
+ """
+ if self.kinetics is None:
+ raise KineticsError("Cannot set reference potential for reactions with no kinetics attribute")
+
+ if isinstance(self.kinetics, SurfaceChargeTransfer) and self.kinetics.V0 is None:
+ self.kinetics.V0 = (self.get_reversible_potential(T),'V')
+
+ def get_equilibrium_constant(self, T, potential=0., type='Kc', surface_site_density=2.5e-05):
"""
Return the equilibrium constant for the reaction at the specified
temperature `T` in K and reference `surface_site_density`
@@ -606,16 +728,28 @@ def get_equilibrium_constant(self, T, type='Kc', surface_site_density=2.5e-05):
``Kc`` for concentrations (default), or ``Kp`` for pressures. This
function assumes a reference pressure of 1e5 Pa for gas phases species
and uses the ideal gas law to determine reference concentrations. For
- surface species, the `surface_site_density` is the assumed reference.
- """
- cython.declare(dGrxn=cython.double, K=cython.double, C0=cython.double, P0=cython.double)
- cython.declare(number_of_gas_reactants=cython.int, number_of_gas_products=cython.int)
- cython.declare(number_of_surface_reactants=cython.int, number_of_surface_products=cython.int)
- cython.declare(dN_surf=cython.int, dN_gas=cython.int, sites=cython.int)
- cython.declare(sigma_nu=cython.double)
- cython.declare(rectant=Species, product=Species, spcs=Species)
+ surface species, the `surface_site_density` is the assumed reference. For protons (H+),
+ a reference concentration of 1000 mol/m^3 (1 mol/L) is assumed
+ """
+ cython.declare(
+ dGrxn=cython.double,
+ K=cython.double,
+ C0=cython.double,
+ P0=cython.double,
+ dN_gas=cython.int,
+ dN_surf=cython.int,
+ sites=cython.int,
+ number_of_gas_reactants=cython.int,
+ number_of_gas_products=cython.int,
+ number_of_surface_reactants=cython.int,
+ number_of_surface_products=cython.int,
+ sigma_nu=cython.double,
+ rectant=Species,
+ product=Species,
+ spcs=Species,
+ )
# Use free energy of reaction to calculate Ka
- dGrxn = self.get_free_energy_of_reaction(T)
+ dGrxn = self.get_free_energy_of_reaction(T, potential)
K = np.exp(-dGrxn / constants.R / T)
# Convert Ka to Kc or Kp if specified
# Assume a pressure of 1e5 Pa for gas phase species
@@ -694,14 +828,14 @@ def get_entropies_of_reaction(self, Tlist):
"""
return np.array([self.get_entropy_of_reaction(T) for T in Tlist], float)
- def get_free_energies_of_reaction(self, Tlist):
+ def get_free_energies_of_reaction(self, Tlist, potential=0.):
"""
Return the Gibbs free energies of reaction in J/mol evaluated at
temperatures `Tlist` in K.
"""
- return np.array([self.get_free_energy_of_reaction(T) for T in Tlist], float)
+ return np.array([self.get_free_energy_of_reaction(T, potential=potential) for T in Tlist], float)
- def get_equilibrium_constants(self, Tlist, type='Kc'):
+ def get_equilibrium_constants(self, Tlist, potential=0., type='Kc'):
"""
Return the equilibrium constants for the reaction at the specified
temperatures `Tlist` in K. The `type` parameter lets you specify the
@@ -709,9 +843,7 @@ def get_equilibrium_constants(self, Tlist, type='Kc'):
``Kc`` for concentrations (default), or ``Kp`` for pressures. Note that
this function currently assumes an ideal gas mixture.
"""
- return np.array(
- [self.get_equilibrium_constant(T, type) for T in Tlist], float
- )
+ return np.array([self.get_equilibrium_constant(T, potential=potential, type=type) for T in Tlist], float)
def get_stoichiometric_coefficient(self, spec):
"""
@@ -728,12 +860,12 @@ def get_stoichiometric_coefficient(self, spec):
if product is spec: stoich += 1
return stoich
- def get_rate_coefficient(self, T, P=0, surface_site_density=0):
+ def get_rate_coefficient(self, T, P=0, surface_site_density=0, potential=0.):
"""
Return the overall rate coefficient for the forward reaction at
temperature `T` in K and pressure `P` in Pa, including any reaction
path degeneracies.
-
+
If diffusion_limiter is enabled, the reaction is in the liquid phase and we use
a diffusion limitation to correct the rate. If not, then use the intrinsic rate
coefficient.
@@ -741,9 +873,11 @@ def get_rate_coefficient(self, T, P=0, surface_site_density=0):
If the reaction has sticking coefficient kinetics, a nonzero surface site density
in `mol/m^2` must be provided
"""
- if isinstance(self.kinetics, StickingCoefficient):
+ if isinstance(self.kinetics,SurfaceChargeTransfer):
+ return self.get_surface_rate_coefficient(T, surface_site_density=surface_site_density, potential=potential)
+ elif isinstance(self.kinetics, StickingCoefficient):
if surface_site_density <= 0:
- raise ValueError("Please provide a postive surface site density in mol/m^2 "
+ raise ValueError("Please provide a postive surface site density in mol/m^2 "
f"for calculating the rate coefficient of {StickingCoefficient.__name__} kinetics")
else:
return self.get_surface_rate_coefficient(T, surface_site_density)
@@ -757,14 +891,15 @@ def get_rate_coefficient(self, T, P=0, surface_site_density=0):
else:
return self.kinetics.get_rate_coefficient(T, P)
- def get_surface_rate_coefficient(self, T, surface_site_density):
+ def get_surface_rate_coefficient(self, T, surface_site_density, potential=0.):
"""
Return the overall surface rate coefficient for the forward reaction at
temperature `T` in K with surface site density `surface_site_density` in mol/m2.
Value is returned in combination of [m,mol,s]
"""
cython.declare(rateCoefficient=cython.double,
- molecularWeight_kg=cython.double, )
+ molecularWeight_kg=cython.double,
+ Ea=cython.double, deltaG=cython.double)
if diffusion_limiter.enabled:
raise NotImplementedError()
@@ -808,6 +943,19 @@ def get_surface_rate_coefficient(self, T, surface_site_density):
if isinstance(self.kinetics, SurfaceArrhenius):
return self.kinetics.get_rate_coefficient(T, P=0)
+ if isinstance(self.kinetics, SurfaceChargeTransfer):
+ Ea = self.kinetics.get_activation_energy_from_potential(potential)
+ deltaG = self._get_free_energy_of_charge_transfer_reaction(298,potential)
+ if deltaG > 0 and Ea < deltaG:
+ corrected_kinetics = deepcopy(self.kinetics)
+ corrected_kinetics.V0.value_si = potential
+ corrected_kinetics.Ea.value_si = deltaG
+ logging.info("For reaction {0!s} Ea raised from {1:.1f} to {2:.1f} kJ/mol at {3:.2f} V".format(
+ self, self.kinetics.Ea.value_si / 1000., deltaG / 1000., potential))
+ return corrected_kinetics.get_rate_coefficient(T, potential)
+ else:
+ return self.kinetics.get_rate_coefficient(T, potential)
+
raise NotImplementedError("Can't get_surface_rate_coefficient for kinetics type {!r}".format(type(self.kinetics)))
def fix_diffusion_limited_a_factor(self, T):
@@ -834,26 +982,29 @@ def fix_diffusion_limited_a_factor(self, T):
"diffusion factor {0.2g} evaluated at {1} K.").format(
diffusion_factor, T))
- def fix_barrier_height(self, force_positive=False):
+ def fix_barrier_height(self, force_positive=False, solvent="", apply_solvation_correction=True):
"""
Turns the kinetics into Arrhenius (if they were ArrheniusEP)
and ensures the activation energy is at least the endothermicity
- for endothermic reactions, and is not negative only as a result
+ for endothermic reactions, and is not negative only as a result
of using Evans Polanyi with an exothermic reaction.
If `force_positive` is True, then all reactions
are forced to have a non-negative barrier.
"""
- cython.declare(H0=cython.double, H298=cython.double, Ea=cython.double)
+ cython.declare(H0=cython.double, H298=cython.double, Ea=cython.double, V0=cython.double,
+ deltaG=cython.double)
if self.kinetics is None:
raise KineticsError("Cannot fix barrier height for reactions with no kinetics attribute")
- H298 = self.get_enthalpy_of_reaction(298)
- H0 = sum([spec.get_thermo_data().E0.value_si if spec.get_thermo_data().E0 is not None else spec.get_thermo_data().to_wilhoit().E0.value_si for spec in self.products]) \
- - sum([spec.get_thermo_data().E0.value_si if spec.get_thermo_data().E0 is not None else spec.get_thermo_data().to_wilhoit().E0.value_si for spec in self.reactants])
- if isinstance(self.kinetics, (ArrheniusEP, SurfaceArrheniusBEP, StickingCoefficientBEP, ArrheniusBM)):
+ if isinstance(self.kinetics, Marcus):
+ if apply_solvation_correction and solvent:
+ self.apply_solvent_correction(solvent)
+ elif isinstance(self.kinetics, SurfaceChargeTransferBEP):
Ea = self.kinetics.E0.value_si # temporarily using Ea to store the intrinsic barrier height E0
- self.kinetics = self.kinetics.to_arrhenius(H298)
+ V0 = self.kinetics.V0.value_si
+ deltaG = self._get_free_energy_of_charge_transfer_reaction(298,V0)
+ self.kinetics = self.kinetics.to_surface_charge_transfer(deltaG)
if self.kinetics.Ea.value_si < 0.0 and self.kinetics.Ea.value_si < Ea:
# Calculated Ea (from Evans-Polanyi) is negative AND below than the intrinsic E0
Ea = min(0.0, Ea) # (the lowest we want it to be)
@@ -862,33 +1013,67 @@ def fix_barrier_height(self, force_positive=False):
logging.info("For reaction {0!s} Ea raised from {1:.1f} to {2:.1f} kJ/mol.".format(
self, self.kinetics.Ea.value_si / 1000., Ea / 1000.))
self.kinetics.Ea.value_si = Ea
- if isinstance(self.kinetics, (Arrhenius, StickingCoefficient)): # SurfaceArrhenius is a subclass of Arrhenius
- Ea = self.kinetics.Ea.value_si
- if H0 >= 0 and Ea < H0:
- self.kinetics.Ea.value_si = H0
- self.kinetics.comment += "\nEa raised from {0:.1f} to {1:.1f} kJ/mol to match endothermicity of " \
- "reaction.".format( Ea / 1000., H0 / 1000.)
- logging.info("For reaction {2!s}, Ea raised from {0:.1f} to {1:.1f} kJ/mol to match "
- "endothermicity of reaction.".format( Ea / 1000., H0 / 1000., self))
- if force_positive and isinstance(self.kinetics, (Arrhenius, StickingCoefficient)) and self.kinetics.Ea.value_si < 0:
- self.kinetics.comment += "\nEa raised from {0:.1f} to 0 kJ/mol.".format(self.kinetics.Ea.value_si / 1000.)
- logging.info("For reaction {1!s} Ea raised from {0:.1f} to 0 kJ/mol.".format(
- self.kinetics.Ea.value_si / 1000., self))
- self.kinetics.Ea.value_si = 0
- if self.kinetics.is_pressure_dependent() and self.network_kinetics is not None:
- Ea = self.network_kinetics.Ea.value_si
- if H0 >= 0 and Ea < H0:
- self.network_kinetics.Ea.value_si = H0
- self.network_kinetics.comment += "\nEa raised from {0:.1f} to {1:.1f} kJ/mol to match endothermicity of" \
- " reaction.".format(Ea / 1000., H0 / 1000.)
- logging.info("For reaction {2!s}, Ea of the high pressure limit kinetics raised from {0:.1f} to {1:.1f}"
- " kJ/mol to match endothermicity of reaction.".format(Ea / 1000., H0 / 1000., self))
- if force_positive and isinstance(self.kinetics, Arrhenius) and self.kinetics.Ea.value_si < 0:
- self.network_kinetics.comment += "\nEa raised from {0:.1f} to 0 kJ/mol.".format(
- self.kinetics.Ea.value_si / 1000.)
- logging.info("For reaction {1!s} Ea of the high pressure limit kinetics raised from {0:.1f} to 0"
- " kJ/mol.".format(self.kinetics.Ea.value_si / 1000., self))
+ else:
+ H298 = self.get_enthalpy_of_reaction(298)
+ H0 = sum([spec.get_thermo_data().E0.value_si if spec.get_thermo_data().E0 is not None else spec.get_thermo_data().to_wilhoit().E0.value_si for spec in self.products]) \
+ - sum([spec.get_thermo_data().E0.value_si if spec.get_thermo_data().E0 is not None else spec.get_thermo_data().to_wilhoit().E0.value_si for spec in self.reactants])
+ if isinstance(self.kinetics, (ArrheniusEP, SurfaceArrheniusBEP, StickingCoefficientBEP, ArrheniusBM)):
+ Ea = self.kinetics.E0.value_si # temporarily using Ea to store the intrinsic barrier height E0
+ self.kinetics = self.kinetics.to_arrhenius(H298)
+ if self.kinetics.Ea.value_si < 0.0 and self.kinetics.Ea.value_si < Ea:
+ # Calculated Ea (from Evans-Polanyi) is negative AND below than the intrinsic E0
+ Ea = min(0.0, Ea) # (the lowest we want it to be)
+ self.kinetics.comment += "\nEa raised from {0:.1f} to {1:.1f} kJ/mol.".format(
+ self.kinetics.Ea.value_si / 1000., Ea / 1000.)
+ logging.info("For reaction {0!s} Ea raised from {1:.1f} to {2:.1f} kJ/mol.".format(
+ self, self.kinetics.Ea.value_si / 1000., Ea / 1000.))
+ self.kinetics.Ea.value_si = Ea
+ if isinstance(self.kinetics, ArrheniusChargeTransferBM):
+ Ea = self.kinetics.E0.value_si # temporarily using Ea to store the intrinsic barrier height E0
+ self.kinetics = self.kinetics.to_arrhenius_charge_transfer(H298)
+ if self.kinetics.Ea.value_si < 0.0 and self.kinetics.Ea.value_si < Ea:
+ # Calculated Ea (from Evans-Polanyi) is negative AND below than the intrinsic E0
+ Ea = min(0.0, Ea) # (the lowest we want it to be)
+ self.kinetics.comment += "\nEa raised from {0:.1f} to {1:.1f} kJ/mol.".format(
+ self.kinetics.Ea.value_si / 1000., Ea / 1000.)
+ logging.info("For reaction {0!s} Ea raised from {1:.1f} to {2:.1f} kJ/mol.".format(
+ self, self.kinetics.Ea.value_si / 1000., Ea / 1000.))
+ self.kinetics.Ea.value_si = Ea
+ if isinstance(self.kinetics, (Arrhenius, StickingCoefficient, ArrheniusChargeTransfer, SurfaceChargeTransfer)): # SurfaceArrhenius is a subclass of Arrhenius
+ if apply_solvation_correction and solvent and self.kinetics.solute:
+ self.apply_solvent_correction(solvent)
+ Ea = self.kinetics.Ea.value_si
+ if H0 >= 0 and Ea < H0:
+ self.kinetics.Ea.value_si = H0
+ self.kinetics.comment += "\nEa raised from {0:.1f} to {1:.1f} kJ/mol to match endothermicity of " \
+ "reaction.".format( Ea / 1000., H0 / 1000.)
+ logging.info("For reaction {2!s}, Ea raised from {0:.1f} to {1:.1f} kJ/mol to match "
+ "endothermicity of reaction.".format( Ea / 1000., H0 / 1000., self))
+ if force_positive and isinstance(self.kinetics, (Arrhenius, StickingCoefficient)) and self.kinetics.Ea.value_si < 0:
+ self.kinetics.comment += "\nEa raised from {0:.1f} to 0 kJ/mol.".format(self.kinetics.Ea.value_si / 1000.)
+ logging.info("For reaction {1!s} Ea raised from {0:.1f} to 0 kJ/mol.".format(
+ self.kinetics.Ea.value_si / 1000., self))
self.kinetics.Ea.value_si = 0
+ if self.kinetics.is_pressure_dependent() and self.network_kinetics is not None:
+ Ea = self.network_kinetics.Ea.value_si
+ if H0 >= 0 and Ea < H0:
+ self.network_kinetics.Ea.value_si = H0
+ self.network_kinetics.comment += "\nEa raised from {0:.1f} to {1:.1f} kJ/mol to match endothermicity of" \
+ " reaction.".format(Ea / 1000., H0 / 1000.)
+ logging.info("For reaction {2!s}, Ea of the high pressure limit kinetics raised from {0:.1f} to {1:.1f}"
+ " kJ/mol to match endothermicity of reaction.".format(Ea / 1000., H0 / 1000., self))
+ if force_positive and isinstance(self.kinetics, Arrhenius) and self.kinetics.Ea.value_si < 0:
+ self.network_kinetics.comment += "\nEa raised from {0:.1f} to 0 kJ/mol.".format(
+ self.kinetics.Ea.value_si / 1000.)
+ logging.info("For reaction {1!s} Ea of the high pressure limit kinetics raised from {0:.1f} to 0"
+ " kJ/mol.".format(self.kinetics.Ea.value_si / 1000., self))
+ self.kinetics.Ea.value_si = 0
+
+ def apply_solvent_correction(self, solvent):
+ """
+ apply kinetic solvent correction
+ """
+ return NotImplementedError("solvent correction is particular to library, depository and template reactions")
def reverse_arrhenius_rate(self, k_forward, reverse_units, Tmin=None, Tmax=None):
"""
@@ -910,6 +1095,7 @@ def reverse_arrhenius_rate(self, k_forward, reverse_units, Tmin=None, Tmax=None)
klist[i] = kf.get_rate_coefficient(Tlist[i]) / self.get_equilibrium_constant(Tlist[i])
kr = Arrhenius()
kr.fit_to_data(Tlist, klist, reverse_units, kf.T0.value_si)
+ kr.solute = kf.solute
return kr
def reverse_surface_arrhenius_rate(self, k_forward, reverse_units, Tmin=None, Tmax=None):
@@ -933,6 +1119,7 @@ def reverse_surface_arrhenius_rate(self, k_forward, reverse_units, Tmin=None, Tm
klist[i] = kf.get_rate_coefficient(Tlist[i]) / self.get_equilibrium_constant(Tlist[i])
kr = SurfaceArrhenius()
kr.fit_to_data(Tlist, klist, reverse_units, kf.T0.value_si)
+ kr.solute = kf.solute
return kr
def reverse_sticking_coeff_rate(self, k_forward, reverse_units, surface_site_density, Tmin=None, Tmax=None):
@@ -959,18 +1146,70 @@ def reverse_sticking_coeff_rate(self, k_forward, reverse_units, surface_site_den
self.get_equilibrium_constant(Tlist[i], surface_site_density=surface_site_density)
kr = SurfaceArrhenius()
kr.fit_to_data(Tlist, klist, reverse_units, kf.T0.value_si)
+ kr.solute = kf.solute
+ return kr
+
+ def reverse_surface_charge_transfer_rate(self, k_forward, reverse_units, Tmin=None, Tmax=None):
+ """
+ Reverses the given k_forward, which must be a SurfaceChargeTransfer type.
+ You must supply the correct units for the reverse rate.
+ The equilibrium constant is evaluated from the current reaction instance (self).
+ """
+ cython.declare(kf=SurfaceChargeTransfer, kr=SurfaceChargeTransfer)
+ cython.declare(Tlist=np.ndarray, klist=np.ndarray, i=cython.int, V0=cython.double)
+ kf = k_forward
+ self.set_reference_potential(298)
+ if not isinstance(kf, SurfaceChargeTransfer): # Only reverse SurfaceChargeTransfer rates
+ raise TypeError(f'Expected a SurfaceChargeTransfer object for k_forward but received {kf}')
+ if Tmin is not None and Tmax is not None:
+ Tlist = 1.0 / np.linspace(1.0 / Tmax.value, 1.0 / Tmin.value, 50)
+ else:
+ Tlist = np.linspace(298, 500, 30)
+
+ V0 = self.kinetics.V0.value_si
+ klist = np.zeros_like(Tlist)
+ for i in range(len(Tlist)):
+ klist[i] = kf.get_rate_coefficient(Tlist[i],V0) / self.get_equilibrium_constant(Tlist[i],V0)
+ kr = SurfaceChargeTransfer(alpha=kf.alpha.value, electrons=-1*self.electrons, V0=(V0,'V'))
+ kr.fit_to_data(Tlist, klist, reverse_units, kf.T0.value_si)
+ kr.solute = kf.solute
+ return kr
+
+ def reverse_arrhenius_charge_transfer_rate(self, k_forward, reverse_units, Tmin=None, Tmax=None):
+ """
+ Reverses the given k_forward, which must be a SurfaceChargeTransfer type.
+ You must supply the correct units for the reverse rate.
+ The equilibrium constant is evaluated from the current reaction instance (self).
+ """
+ cython.declare(Tlist=np.ndarray, klist=np.ndarray, i=cython.int, V0=cython.double)
+ kf = k_forward
+ if not isinstance(kf, ArrheniusChargeTransfer): # Only reverse SurfaceChargeTransfer rates
+ raise TypeError(f'Expected a ArrheniusChargeTransfer object for k_forward but received {kf}')
+ if Tmin is not None and Tmax is not None:
+ Tlist = 1.0 / np.linspace(1.0 / Tmax.value, 1.0 / Tmin.value, 50)
+ else:
+ Tlist = np.linspace(298, 500, 30)
+
+ V0 = self.kinetics.V0.value_si
+ klist = np.zeros_like(Tlist)
+ for i in range(len(Tlist)):
+ klist[i] = kf.get_rate_coefficient(Tlist[i],V0) / self.get_equilibrium_constant(Tlist[i],V0)
+ kr = ArrheniusChargeTransfer(alpha=kf.alpha.value, electrons=-1*self.electrons, V0=(V0,'V'))
+ kr.fit_to_data(Tlist, klist, reverse_units, kf.T0.value_si)
+ kr.solute = kf.solute
return kr
def generate_reverse_rate_coefficient(self, network_kinetics=False, Tmin=None, Tmax=None, surface_site_density=0):
"""
- Generate and return a rate coefficient model for the reverse reaction.
+ Generate and return a rate coefficient model for the reverse reaction.
Currently this only works if the `kinetics` attribute is one of several
(but not necessarily all) kinetics types.
If the reaction kinetics model is Sticking Coefficient, please provide a nonzero
surface site density in `mol/m^2` which is required to evaluate the rate coefficient.
"""
- cython.declare(Tlist=np.ndarray, Plist=np.ndarray, K=np.ndarray,
+ cython.declare(n_gas=cython.int, n_surf=cython.int, prod=Species, k_units=str,
+ Tlist=np.ndarray, Plist=np.ndarray, K=np.ndarray,
rxn=Reaction, klist=np.ndarray, i=cython.size_t,
Tindex=cython.size_t, Pindex=cython.size_t)
@@ -978,6 +1217,7 @@ def generate_reverse_rate_coefficient(self, network_kinetics=False, Tmin=None, T
KineticsData.__name__,
Arrhenius.__name__,
SurfaceArrhenius.__name__,
+ SurfaceChargeTransfer.__name__,
MultiArrhenius.__name__,
PDepArrhenius.__name__,
MultiPDepArrhenius.__name__,
@@ -986,6 +1226,7 @@ def generate_reverse_rate_coefficient(self, network_kinetics=False, Tmin=None, T
Lindemann.__name__,
Troe.__name__,
StickingCoefficient.__name__,
+ ArrheniusChargeTransfer.__name__,
)
# Get the units for the reverse rate coefficient
@@ -993,15 +1234,22 @@ def generate_reverse_rate_coefficient(self, network_kinetics=False, Tmin=None, T
surf_prods = [spcs for spcs in self.products if spcs.contains_surface_site()]
except IndexError:
surf_prods = []
- # logging.warning(f"Species do not have an rmgpy.molecule.Molecule "
- # "Cannot determine phases of species. We will assume gas"
- # )
+ logging.warning(f"Species do not have an rmgpy.molecule.Molecule "
+ "Cannot determine phases of species. We will assume gas"
+ )
n_surf = len(surf_prods)
n_gas = len(self.products) - len(surf_prods)
kunits = get_rate_coefficient_units_from_reaction_order(n_gas, n_surf)
kf = self.kinetics
- if isinstance(kf, KineticsData):
+
+ if isinstance(kf, SurfaceChargeTransfer):
+ return self.reverse_surface_charge_transfer_rate(kf, kunits, Tmin, Tmax)
+
+ elif isinstance(kf, ArrheniusChargeTransfer):
+ return self.reverse_arrhenius_charge_transfer_rate(kf, kunits, Tmin, Tmax)
+
+ elif isinstance(kf, KineticsData):
Tlist = kf.Tdata.value_si
klist = np.zeros_like(Tlist)
@@ -1020,7 +1268,7 @@ def generate_reverse_rate_coefficient(self, network_kinetics=False, Tmin=None, T
elif isinstance(kf, StickingCoefficient):
if surface_site_density <= 0:
- raise ValueError("Please provide a postive surface site density in mol/m^2 "
+ raise ValueError("Please provide a postive surface site density in mol/m^2 "
f"for calculating the rate coefficient of {StickingCoefficient.__name__} kinetics")
else:
return self.reverse_sticking_coeff_rate(kf, kunits, surface_site_density, Tmin, Tmax)
@@ -1149,14 +1397,14 @@ def calculate_microcanonical_rate_coefficient(self, e_list, j_list, reac_dens_st
reactant density of states is required; if the reaction is reversible, then
both are required. This function will try to use the best method that it
can based on the input data available:
-
+
* If detailed information has been provided for the transition state (i.e.
the molecular degrees of freedom), then RRKM theory will be used.
-
+
* If the above is not possible but high-pressure limit kinetics
- :math:`k_\\infty(T)` have been provided, then the inverse Laplace
+ :math:`k_\\infty(T)` have been provided, then the inverse Laplace
transform method will be used.
-
+
The density of states for the product `prod_dens_states` and the temperature
of interest `T` in K can also be provided. For isomerization and association
reactions `prod_dens_states` is required; for dissociation reactions it is
@@ -1174,10 +1422,13 @@ def is_balanced(self):
from rmgpy.molecule.element import element_list
from rmgpy.molecule.fragment import CuttingLabel, Fragment
- cython.declare(reactant_elements=dict, product_elements=dict, molecule=Graph, atom=Vertex, element=Element)
+ cython.declare(reactant_elements=dict, product_elements=dict, molecule=Graph, atom=Vertex, element=Element,
+ reactants_net_charge=cython.int, products_net_charge=cython.int)
reactant_elements = {}
product_elements = {}
+ reactants_net_charge = 0
+ products_net_charge = 0
for element in element_list:
reactant_elements[element] = 0
product_elements[element] = 0
@@ -1187,34 +1438,43 @@ def is_balanced(self):
molecule = reactant.molecule[0]
for atom in molecule.atoms:
if not isinstance(atom, CuttingLabel):
+ reactants_net_charge += atom.charge
reactant_elements[atom.element] += 1
- elif isinstance(reactant, Molecule):
- molecule = reactant
- for atom in molecule.atoms:
- reactant_elements[atom.element] += 1
elif isinstance(reactant, Fragment):
for atom in reactant.atoms:
if not isinstance(atom, CuttingLabel):
+ reactants_net_charge += atom.charge
reactant_elements[atom.element] += 1
+ elif isinstance(reactant, Molecule):
+ for atom in reactant.atoms:
+ reactants_net_charge += atom.charge
+ reactant_elements[atom.element] += 1
for product in self.products:
if isinstance(product, Species):
molecule = product.molecule[0]
for atom in molecule.atoms:
if not isinstance(atom, CuttingLabel):
+ products_net_charge += atom.charge
product_elements[atom.element] += 1
- elif isinstance(product, Molecule):
- molecule = product
- for atom in molecule.atoms:
- product_elements[atom.element] += 1
elif isinstance(product, Fragment):
for atom in product.atoms:
if not isinstance(atom, CuttingLabel):
+ products_net_charge += atom.charge
product_elements[atom.element] += 1
+ elif isinstance(product, Molecule):
+ for atom in product.atoms:
+ products_net_charge += atom.charge
+ product_elements[atom.element] += 1
for element in element_list:
if reactant_elements[element] != product_elements[element]:
return False
+ if self.electrons < 0:
+ reactants_net_charge += self.electrons
+ elif self.electrons > 0:
+ products_net_charge -= self.electrons
+
return True
def generate_pairs(self):
@@ -1222,7 +1482,7 @@ def generate_pairs(self):
Generate the reactant-product pairs to use for this reaction when
performing flux analysis. The exact procedure for doing so depends on
the reaction type:
-
+
=================== =============== ========================================
Reaction type Template Resulting pairs
=================== =============== ========================================
@@ -1231,8 +1491,8 @@ def generate_pairs(self):
Association A + B -> C (A,C), (B,C)
Bimolecular A + B -> C + D (A,C), (B,D) *or* (A,D), (B,C)
=================== =============== ========================================
-
- There are a number of ways of determining the correct pairing for
+
+ There are a number of ways of determining the correct pairing for
bimolecular reactions. Here we try a simple similarity analysis by comparing
the number of heavy atoms. This should work most of the time, but a more
rigorous algorithm may be needed for some cases.
@@ -1295,9 +1555,9 @@ def _repr_png_(self):
# Build the transition state geometry
def generate_3d_ts(self, reactants, products):
"""
- Generate the 3D structure of the transition state. Called from
+ Generate the 3D structure of the transition state. Called from
model.generate_kinetics().
-
+
self.reactants is a list of reactants
self.products is a list of products
"""
@@ -1307,7 +1567,7 @@ def generate_3d_ts(self, reactants, products):
atoms involved in the reaction. If a radical is involved, can find the atom
with radical electrons. If a more reliable method can be found, would greatly
improve the method.
-
+
Repeat for the products
"""
for i in range(0, len(reactants)):
diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py
index e04cd82e2e..117c19644c 100644
--- a/rmgpy/rmg/input.py
+++ b/rmgpy/rmg/input.py
@@ -51,6 +51,7 @@
from rmgpy.rmg.reactors import Reactor, ConstantVIdealGasReactor, ConstantTLiquidSurfaceReactor, ConstantTVLiquidReactor, ConstantTPIdealGasReactor
from rmgpy.data.vaporLiquidMassTransfer import liquidVolumetricMassTransferCoefficientPowerLaw
from rmgpy.molecule.fragment import Fragment
+from rmgpy.data.solvation import SolventData
################################################################################
@@ -67,6 +68,7 @@ def database(
kineticsFamilies='default',
kineticsDepositories='default',
kineticsEstimator='rate rules',
+ adsorptionGroups='adsorptionPt111'
):
# This function just stores the information about the database to be loaded
# We don't actually load the database until after we're finished reading
@@ -101,6 +103,7 @@ def database(
"['H_Abstraction','R_Recombination'] or ['!Intra_Disproportionation'].")
rmg.kinetics_families = kineticsFamilies
+ rmg.adsorption_groups = adsorptionGroups
def catalyst_properties(bindingEnergies=None,
surfaceSiteDensity=None,
@@ -631,7 +634,10 @@ def liquid_cat_reactor(temperature,
initialConcentrations,
initialSurfaceCoverages,
surfaceVolumeRatio,
- potential=None,
+ distance=None,
+ viscosity=None,
+ surfPotential=None,
+ liqPotential=None,
terminationConversion=None,
terminationTime=None,
terminationRateRatio=None,
@@ -707,11 +713,19 @@ def liquid_cat_reactor(temperature,
initialCondSurf[key] = item*rmg.surface_site_density.value_si*A
initialCondSurf["T"] = T
initialCondSurf["A"] = A
- if potential:
- initialCondSurf["phi"] = Quantity(potential).value_si
+ initialCondSurf["d"] = 0.0
+ if surfPotential:
+ initialCondSurf["Phi"] = Quantity(surfPotential).value_si
+ if liqPotential:
+ initialCondLiq["Phi"] = Quantity(liqPotential).value_si
+ if distance:
+ initialCondLiq["d"] = Quantity(distance).value_si
+ if viscosity:
+ initialCondLiq["mu"] = Quantity(distance).value_si
system = ConstantTLiquidSurfaceReactor(rmg.reaction_model.core.phase_system,
rmg.reaction_model.edge.phase_system,
- {"liquid":initialCondLiq,"surface":initialCondSurf},termination,constantSpecies)
+ {"liquid":initialCondLiq,"surface":initialCondSurf},
+ termination,constantSpecies)
system.T = Quantity(T)
system.Trange = None
system.sensitive_species = []
@@ -1136,11 +1150,18 @@ def simulator(atol, rtol, sens_atol=1e-6, sens_rtol=1e-4):
rmg.simulator_settings_list.append(SimulatorSettings(atol, rtol, sens_atol, sens_rtol))
-def solvation(solvent):
+def solvation(solvent,solventData=None):
# If solvation module in input file, set the RMG solvent variable
- if not isinstance(solvent, str):
- raise InputError("solvent should be a string like 'water'")
- rmg.solvent = solvent
+ #either a string corresponding to the solvent database or a olvent object
+ if isinstance(solvent, str):
+ rmg.solvent = solvent
+ else:
+ raise InputError("Solvent not specified properly, solvent must be string")
+
+ if isinstance(solventData, SolventData) or solventData is None:
+ rmg.solvent_data = solventData
+ else:
+ raise InputError("Solvent not specified properly, solventData must be None or SolventData object")
def model(toleranceMoveToCore=None, toleranceRadMoveToCore=np.inf,
@@ -1539,6 +1560,7 @@ def read_input_file(path, rmg0):
'mbsampledReactor': mb_sampled_reactor,
'simulator': simulator,
'solvation': solvation,
+ 'SolventData' : SolventData,
'liquidVolumetricMassTransferCoefficientPowerLaw': liquid_volumetric_mass_transfer_coefficient_power_law,
'model': model,
'quantumMechanics': quantum_mechanics,
diff --git a/rmgpy/rmg/main.py b/rmgpy/rmg/main.py
index 8df771980d..fb4ae33ef8 100644
--- a/rmgpy/rmg/main.py
+++ b/rmgpy/rmg/main.py
@@ -115,6 +115,7 @@ class RMG(util.Subject):
`kinetics_depositories` The kinetics depositories to use for looking up kinetics in each family
`kinetics_estimator` The method to use to estimate kinetics: currently, only 'rate rules' is supported
`solvent` If solvation estimates are required, the name of the solvent.
+ `solvent_data` The parameters assocciated with the solvent
`liquid_volumetric_mass_transfer_coefficient_power_law` If kLA estimates are required, the coefficients for kLA power law
---------------------------------------------------------- ------------------------------------------------
`reaction_model` The core-edge reaction model generated by this job
@@ -186,6 +187,7 @@ def clear(self):
self.kinetics_depositories = None
self.kinetics_estimator = "rate rules"
self.solvent = None
+ self.solvent_data = None
self.diffusion_limiter = None
self.surface_site_density = None
self.binding_energies = None
@@ -406,7 +408,9 @@ def load_database(self):
seed_mechanisms=self.seed_mechanisms,
kinetics_families=self.kinetics_families,
kinetics_depositories=self.kinetics_depositories,
- statmech_libraries=self.statmech_libraries,
+ statmech_libraries = self.statmech_libraries,
+ adsorption_groups='adsorptionPt111', # use Pt111 groups for training reactions
+ # frequenciesLibraries = self.statmech_libraries,
depository=False, # Don't bother loading the depository information, as we don't use it
)
@@ -490,6 +494,8 @@ def load_database(self):
if not family.auto_generated:
family.fill_rules_by_averaging_up(verbose=self.verbose_comments)
+ self.database.thermo.adsorption_groups = self.adsorption_groups
+
def initialize(self, **kwargs):
"""
Initialize an RMG job using the command-line arguments `args` as returned
@@ -549,9 +555,6 @@ def initialize(self, **kwargs):
# Load databases
self.load_database()
- for spec in self.initial_species:
- self.reaction_model.add_species_to_edge(spec)
-
for reaction_system in self.reaction_systems:
if isinstance(reaction_system, Reactor):
reaction_system.finish_termination_criteria()
@@ -582,7 +585,25 @@ def initialize(self, **kwargs):
# Do all liquid-phase startup things:
if self.solvent:
- solvent_data = self.database.solvation.get_solvent_data(self.solvent)
+ if self.solvent_data is None:
+ solvent_data = self.database.solvation.get_solvent_data(self.solvent)
+ self.solvent_data = solvent_data
+ solvent_mol = True
+ else:
+ solvent_data = self.solvent_data
+ index = 1+max([entry.index for entry in self.database.solvation.libraries['solvent'].entries.values()])
+ self.database.solvation.libraries['solvent'].load_entry(
+ index,
+ self.solvent,
+ solvent_data,
+ dataCount=None,
+ molecule=None,
+ reference=None,
+ referenceType='',
+ shortDesc='',
+ longDesc='',
+ )
+ solvent_mol = False
self.reaction_model.core.phase_system.phases["Default"].set_solvent(solvent_data)
self.reaction_model.edge.phase_system.phases["Default"].set_solvent(solvent_data)
@@ -615,6 +636,14 @@ def initialize(self, **kwargs):
# Initialize reaction model
+ for spec in self.initial_species:
+ if spec.reactive:
+ submit(spec, self.solvent)
+ if vapor_liquid_mass_transfer.enabled:
+ spec.get_liquid_volumetric_mass_transfer_coefficient_data()
+ spec.get_henry_law_constant_data()
+ self.reaction_model.add_species_to_edge(spec)
+
# Seed mechanisms: add species and reactions from seed mechanism
# DON'T generate any more reactions for the seed species at this time
for seed_mechanism in self.seed_mechanisms:
@@ -658,9 +687,13 @@ def initialize(self, **kwargs):
# For liquidReactor, checks whether the solvent is listed as one of the initial species.
if self.solvent:
- solvent_structure_list = self.database.solvation.get_solvent_structure(self.solvent)
- for spc in solvent_structure_list:
- self.database.solvation.check_solvent_in_initial_species(self, spc)
+ if not solvent_mol:
+ logging.warn("Solvent molecular structure not specified, assuming simulation is appropriate")
+ else:
+ solvent_structure_list = self.database.solvation.get_solvent_structure(self.solvent)
+ for spc in solvent_structure_list:
+ self.database.solvation.check_solvent_in_initial_species(self, spc)
+
# Check to see if user has input Singlet O2 into their input file or libraries
# This constraint is special in that we only want to check it once in the input instead of every time a species is made
diff --git a/rmgpy/rmg/model.py b/rmgpy/rmg/model.py
index 7c2f5c5c88..ee6795806b 100644
--- a/rmgpy/rmg/model.py
+++ b/rmgpy/rmg/model.py
@@ -546,17 +546,18 @@ def make_new_reaction(self, forward, check_existing=True, generate_thermo=True,
if isinstance(forward.kinetics, KineticsData):
forward.kinetics = forward.kinetics.to_arrhenius()
# correct barrier heights of estimated kinetics
- if isinstance(forward, (TemplateReaction, DepositoryReaction)): # i.e. not LibraryReaction
- forward.fix_barrier_height() # also converts ArrheniusEP to Arrhenius.
+ if isinstance(forward, (TemplateReaction,DepositoryReaction)): # i.e. not LibraryReaction
+ forward.fix_barrier_height(solvent=self.solvent_name) # also converts ArrheniusEP to Arrhenius.
elif isinstance(forward, LibraryReaction) and forward.is_surface_reaction():
# do fix the library reaction barrier if this is scaled from another metal
if any(['Binding energy corrected by LSR' in x.thermo.comment for x in forward.reactants + forward.products]):
- forward.fix_barrier_height()
-
+ forward.fix_barrier_height(solvent=self.solvent_name)
+ elif forward.kinetics.solute:
+ forward.apply_solvent_correction(solvent=self.solvent_name)
if self.pressure_dependence and forward.is_unimolecular():
# If this is going to be run through pressure dependence code,
# we need to make sure the barrier is positive.
- forward.fix_barrier_height(force_positive=True)
+ forward.fix_barrier_height(force_positive=True,solvent="")
# Since the reaction is new, add it to the list of new reactions
self.new_reaction_list.append(forward)
@@ -814,7 +815,11 @@ def process_new_reactions(self, new_reactions, new_species, pdep_network=None, g
Makes a reaction and decides where to put it: core, edge, or PDepNetwork.
"""
for rxn in new_reactions:
- rxn, is_new = self.make_new_reaction(rxn, generate_thermo=generate_thermo, generate_kinetics=generate_kinetics)
+ try:
+ rxn, is_new = self.make_new_reaction(rxn, generate_thermo=generate_thermo, generate_kinetics=generate_kinetics)
+ except Exception as e:
+ logging.error(f"Error when making reaction {rxn} from {rxn.family}")
+ raise e
if rxn is None:
# Skip this reaction because there was something wrong with it
continue
@@ -1689,7 +1694,7 @@ def add_seed_mechanism_to_core(self, seed_mechanism, react=False):
for spec in itertools.chain(rxn.reactants, rxn.products):
submit(spec, self.solvent_name)
- rxn.fix_barrier_height(force_positive=True)
+ rxn.fix_barrier_height(force_positive=True, solvent=self.solvent_name)
self.add_reaction_to_core(rxn)
# Check we didn't introduce unmarked duplicates
diff --git a/rmgpy/rmg/reactors.py b/rmgpy/rmg/reactors.py
index c487601f93..9386fa2a20 100644
--- a/rmgpy/rmg/reactors.py
+++ b/rmgpy/rmg/reactors.py
@@ -34,6 +34,7 @@
import sys
import logging
import itertools
+import rmgpy.constants as constants
if __debug__:
try:
@@ -55,18 +56,19 @@
from diffeqpy import de
from julia import Main
+from rmgpy import constants
from rmgpy.species import Species
from rmgpy.molecule.fragment import Fragment
from rmgpy.reaction import Reaction
from rmgpy.thermo.nasa import NASAPolynomial, NASA
from rmgpy.thermo.wilhoit import Wilhoit
from rmgpy.thermo.thermodata import ThermoData
-from rmgpy.kinetics.arrhenius import Arrhenius, ArrheniusEP, ArrheniusBM, PDepArrhenius, MultiArrhenius, MultiPDepArrhenius
+from rmgpy.kinetics.arrhenius import Arrhenius, ArrheniusEP, ArrheniusBM, PDepArrhenius, MultiArrhenius, MultiPDepArrhenius, ArrheniusChargeTransfer, Marcus
from rmgpy.kinetics.kineticsdata import KineticsData
from rmgpy.kinetics.falloff import Troe, ThirdBody, Lindemann
from rmgpy.kinetics.chebyshev import Chebyshev
from rmgpy.data.solvation import SolventData
-from rmgpy.kinetics.surface import StickingCoefficient
+from rmgpy.kinetics.surface import StickingCoefficient, SurfaceChargeTransfer
from rmgpy.solver.termination import TerminationTime, TerminationConversion, TerminationRateRatio
from rmgpy.data.kinetics.family import TemplateReaction
from rmgpy.data.kinetics.depository import DepositoryReaction
@@ -488,14 +490,19 @@ def generate_reactor(self, phase_system):
liq = phase_system.phases["Default"]
surf = phase_system.phases["Surface"]
interface = list(phase_system.interfaces.values())[0]
- liq = rms.IdealDiluteSolution(liq.species, liq.reactions, liq.solvent, name="liquid")
+ if "mu" in self.initial_conditions["liquid"].keys():
+ solv = rms.Solvent("solvent",rms.ConstantViscosity(self.initial_conditions["liquid"]["mu"]))
+ liq_initial_cond = self.initial_conditions["liquid"].copy()
+ del liq_initial_cond["mu"]
+ else:
+ solv = liq.solvent
+ liq_initial_cond = self.initial_conditions["liquid"]
+ liq = rms.IdealDiluteSolution(liq.species, liq.reactions, solv, name="liquid",diffusionlimited=True)
surf = rms.IdealSurface(surf.species, surf.reactions, surf.site_density, name="surface")
liq_constant_species = [cspc for cspc in self.const_spc_names if cspc in [spc.name for spc in liq.species]]
cat_constant_species = [cspc for cspc in self.const_spc_names if cspc in [spc.name for spc in surf.species]]
- domainliq, y0liq, pliq = rms.ConstantTVDomain(phase=liq, initialconds=self.initial_conditions["liquid"], constantspecies=liq_constant_species)
- domaincat, y0cat, pcat = rms.ConstantTAPhiDomain(
- phase=surf, initialconds=self.initial_conditions["surface"], constantspecies=cat_constant_species
- )
+ domainliq,y0liq,pliq = rms.ConstantTVDomain(phase=liq,initialconds=liq_initial_cond,constantspecies=liq_constant_species)
+ domaincat,y0cat,pcat = rms.ConstantTAPhiDomain(phase=surf,initialconds=self.initial_conditions["surface"],constantspecies=cat_constant_species)
if interface.reactions == []:
inter, pinter = rms.ReactiveInternalInterfaceConstantTPhi(
domainliq,
@@ -590,6 +597,28 @@ def to_rms(obj, species_names=None, rms_species_list=None, rmg_species=None):
n = obj._n.value_si
Ea = obj._Ea.value_si
return rms.Arrhenius(A, n, Ea, rms.EmptyRateUncertainty())
+ elif isinstance(obj, ArrheniusChargeTransfer):
+ A = obj._A.value_si
+ if obj._T0.value_si != 1.0:
+ A /= ((obj._T0.value_si) ** obj._n.value_si)
+ n = obj._n.value_si
+ Ea = obj._Ea.value_si
+ q = obj._alpha.value_si*obj._electrons.value_si
+ V0 = obj._V0.value_si
+ return rms.Arrheniusq(A, n, Ea, q, V0, rms.EmptyRateUncertainty())
+ elif isinstance(obj, SurfaceChargeTransfer):
+ A = obj._A.value_si
+ if obj._T0.value_si != 1.0:
+ A /= ((obj._T0.value_si) ** obj._n.value_si)
+ n = obj._n.value_si
+ Ea = obj._Ea.value_si
+ q = obj._alpha.value_si*obj._electrons.value_si
+ V0 = obj._V0.value_si
+ return rms.Arrheniusq(A, n, Ea, q, V0, rms.EmptyRateUncertainty())
+ elif isinstance(obj, Marcus):
+ A = obj._A.value_si
+ n = obj._n.value_si
+ return rms.Marcus(A,n,obj._lmbd_i_coefs.value_si,obj._lmbd_o.value_si, obj._wr.value_si, obj._wp.value_si, obj._beta.value_si, rms.EmptyRateUncertainty())
elif isinstance(obj, PDepArrhenius):
Ps = obj._pressures.value_si
arrs = [to_rms(arr) for arr in obj.arrhenius]
@@ -694,7 +723,7 @@ def to_rms(obj, species_names=None, rms_species_list=None, rmg_species=None):
else:
atomnums[atm.element.symbol] = 1
bondnum = len(mol.get_all_edges())
-
+
if not obj.molecule[0].contains_surface_site():
rad = rms.getspeciesradius(atomnums, bondnum)
diff = rms.StokesDiffusivity(rad)
@@ -752,22 +781,12 @@ def to_rms(obj, species_names=None, rms_species_list=None, rmg_species=None):
productinds = [species_names.index(spc.label) for spc in obj.products]
reactants = [rms_species_list[i] for i in reactantinds]
products = [rms_species_list[i] for i in productinds]
+ if isinstance(obj.kinetics, SurfaceChargeTransfer):
+ obj.set_reference_potential(300)
kinetics = to_rms(obj.kinetics, species_names=species_names, rms_species_list=rms_species_list, rmg_species=rmg_species)
- radchange = sum([spc.molecule[0].multiplicity - 1 for spc in obj.products]) - sum([spc.molecule[0].multiplicity - 1 for spc in obj.reactants])
- electronchange = 0 # for now
- return rms.ElementaryReaction(
- index=obj.index,
- reactants=reactants,
- reactantinds=reactantinds,
- products=products,
- productinds=productinds,
- kinetics=kinetics,
- electronchange=electronchange,
- radicalchange=radchange,
- reversible=obj.reversible,
- pairs=[],
- comment=obj.kinetics.comment,
- )
+ radchange = sum([spc.molecule[0].multiplicity-1 for spc in obj.products]) - sum([spc.molecule[0].multiplicity-1 for spc in obj.reactants])
+ electronchange = -sum([spc.molecule[0].get_net_charge() for spc in obj.products]) + sum([spc.molecule[0].get_net_charge() for spc in obj.reactants])
+ return rms.ElementaryReaction(index=obj.index, reactants=reactants, reactantinds=reactantinds, products=products, productinds=productinds, kinetics=kinetics, electronchange=electronchange, radicalchange=radchange, reversible=obj.reversible, pairs=[], comment=obj.kinetics.comment)
elif isinstance(obj, SolventData):
return rms.Solvent("solvent", rms.RiedelViscosity(float(obj.A), float(obj.B), float(obj.C), float(obj.D), float(obj.E)))
elif isinstance(obj, TerminationTime):
diff --git a/rmgpy/species.pxd b/rmgpy/species.pxd
index 9922c1e59a..0e30ce2b0e 100644
--- a/rmgpy/species.pxd
+++ b/rmgpy/species.pxd
@@ -61,7 +61,9 @@ cdef class Species:
cdef str _smiles
cpdef generate_resonance_structures(self, bint keep_isomorphic=?, bint filter_structures=?, bint save_order=?)
-
+
+ cpdef get_net_charge(self)
+
cpdef bint is_isomorphic(self, other, bint generate_initial_map=?, bint save_order=?, bint strict=?) except -2
cpdef bint is_identical(self, other, bint strict=?) except -2
@@ -78,6 +80,10 @@ cdef class Species:
cpdef bint is_surface_site(self) except -2
+ cpdef bint is_electron(self) except -2
+
+ cpdef bint is_proton(self) except -2
+
cpdef bint has_statmech(self) except -2
cpdef bint has_thermo(self) except -2
diff --git a/rmgpy/species.py b/rmgpy/species.py
index e0ae40a2f0..2969dac82b 100644
--- a/rmgpy/species.py
+++ b/rmgpy/species.py
@@ -278,6 +278,14 @@ def molecular_weight(self):
def molecular_weight(self, value):
self._molecular_weight = quantity.Mass(value)
+ def get_net_charge(self):
+ """
+ Iterate through the atoms in the structure and calculate the net charge
+ on the overall molecule.
+ """
+
+ return self.molecule[0].get_net_charge()
+
def generate_resonance_structures(self, keep_isomorphic=True, filter_structures=True, save_order=False):
"""
Generate all of the resonance structures of this species. The isomers are
@@ -358,7 +366,7 @@ def is_structure_in_list(self, species_list):
' should be a List of Species objects.'.format(species))
return False
- def from_adjacency_list(self, adjlist, raise_atomtype_exception=True, raise_charge_exception=True):
+ def from_adjacency_list(self, adjlist, raise_atomtype_exception=True, raise_charge_exception=False):
"""
Load the structure of a species as a :class:`Molecule` object from the
given adjacency list `adjlist` and store it as the first entry of a
@@ -495,6 +503,22 @@ def number_of_surface_sites(self):
"""
return self.molecule[0].number_of_surface_sites()
+ def is_electron(self):
+ """Return ``True`` if the species is an electron"""
+
+ if len(self.molecule) == 0:
+ return False
+ else:
+ return self.molecule[0].is_electron()
+
+ def is_proton(self):
+ """Return ``True`` if the species is a proton"""
+
+ if len(self.molecule) == 0:
+ return False
+ else:
+ return self.molecule[0].is_proton()
+
def get_partition_function(self, T):
"""
Return the partition function for the species at the specified
diff --git a/rmgpy/yml.py b/rmgpy/yml.py
index cd8b9de5b5..8b6e9f771f 100644
--- a/rmgpy/yml.py
+++ b/rmgpy/yml.py
@@ -34,17 +34,18 @@
import os
import yaml
+import logging
from rmgpy.chemkin import load_chemkin_file
from rmgpy.species import Species
from rmgpy.reaction import Reaction
from rmgpy.thermo.nasa import NASAPolynomial, NASA
from rmgpy.thermo.wilhoit import Wilhoit
-from rmgpy.kinetics.arrhenius import Arrhenius, PDepArrhenius, MultiArrhenius, MultiPDepArrhenius
+from rmgpy.kinetics.arrhenius import Arrhenius, PDepArrhenius, MultiArrhenius, MultiPDepArrhenius, ArrheniusChargeTransfer, Marcus
from rmgpy.kinetics.falloff import Troe, ThirdBody, Lindemann
from rmgpy.kinetics.chebyshev import Chebyshev
from rmgpy.data.solvation import SolventData
-from rmgpy.kinetics.surface import StickingCoefficient
+from rmgpy.kinetics.surface import StickingCoefficient, SurfaceChargeTransfer
from rmgpy.util import make_output_subdirectory
@@ -141,6 +142,7 @@ def obj_to_dict(obj, spcs, names=None, label="solvent"):
result_dict["type"] = "ElementaryReaction"
result_dict["radicalchange"] = sum([get_radicals(x) for x in obj.products]) - \
sum([get_radicals(x) for x in obj.reactants])
+ result_dict["electronchange"] = -sum([spc.molecule[0].get_net_charge() for spc in obj.products]) + sum([spc.molecule[0].get_net_charge() for spc in obj.reactants])
result_dict["comment"] = obj.kinetics.comment
elif isinstance(obj, Arrhenius):
obj.change_t0(1.0)
@@ -148,6 +150,30 @@ def obj_to_dict(obj, spcs, names=None, label="solvent"):
result_dict["A"] = obj.A.value_si
result_dict["Ea"] = obj.Ea.value_si
result_dict["n"] = obj.n.value_si
+ elif isinstance(obj, ArrheniusChargeTransfer):
+ obj.change_t0(1.0)
+ obj.change_v0(0.0)
+ result_dict["type"] = "Arrheniusq"
+ result_dict["A"] = obj.A.value_si
+ result_dict["Ea"] = obj.Ea.value_si
+ result_dict["n"] = obj.n.value_si
+ result_dict["q"] = obj._alpha.value_si*obj._electrons.value_si
+ elif isinstance(obj, SurfaceChargeTransfer):
+ obj.change_v0(0.0)
+ result_dict["type"] = "Arrheniusq"
+ result_dict["A"] = obj.A.value_si
+ result_dict["Ea"] = obj.Ea.value_si
+ result_dict["n"] = obj.n.value_si
+ result_dict["q"] = obj._alpha.value_si*obj._electrons.value_si
+ elif isinstance(obj, Marcus):
+ result_dict["type"] = "Marcus"
+ result_dict["A"] = obj.A.value_si
+ result_dict["n"] = obj.n.value_si
+ result_dict["lmbd_i_coefs"] = obj.lmbd_i_coefs.value_si.tolist()
+ result_dict["lmbd_o"] = obj.lmbd_o.value_si
+ result_dict["wr"] = obj.wr.value_si
+ result_dict["wp"] = obj.wp.value_si
+ result_dict["beta"] = obj.beta.value_si
elif isinstance(obj, StickingCoefficient):
obj.change_t0(1.0)
result_dict["type"] = "StickingCoefficient"
diff --git a/test/database/databaseTest.py b/test/database/databaseTest.py
index 3011ba17eb..3663af56f0 100644
--- a/test/database/databaseTest.py
+++ b/test/database/databaseTest.py
@@ -50,6 +50,7 @@
from rmgpy.molecule.atomtype import ATOMTYPES
from rmgpy.molecule.pathfinder import find_shortest_path
from rmgpy.quantity import ScalarQuantity
+from rmgpy.kinetics.model import KineticsModel
# allow asserts to 'fail' and then continue - this test file relies on a lot
# of asserts in each test and we want them all to run
@@ -129,11 +130,17 @@ def test_kinetics(self):
assert self.kinetics_check_training_reactions_have_surface_attributes(
family_name
), "Kinetics surface family {0}: entries have surface attributes?".format(family_name)
-
- with check:
- assert self.kinetics_check_coverage_dependence_units_are_correct(
- family_name
- ), "Kinetics surface family {0}: check coverage dependent units are correct?".format(family_name)
+ if family_name not in {
+ "Surface_Proton_Electron_Reduction_Alpha",
+ "Surface_Proton_Electron_Reduction_Alpha_vdW",
+ "Surface_Proton_Electron_Reduction_Beta",
+ "Surface_Proton_Electron_Reduction_Beta_vdW",
+ "Surface_Proton_Electron_Reduction_Beta_Dissociation",
+ }:
+ with check:
+ assert self.kinetics_check_coverage_dependence_units_are_correct(
+ family_name
+ ), "Kinetics surface family {0}: check coverage dependent units are correct?".format(family_name)
# these families have some sort of difficulty which prevents us from testing accessibility right now
# See RMG-Py PR #2232 for reason why adding Bimolec_Hydroperoxide_Decomposition here. Basically some nodes
@@ -970,7 +977,7 @@ def kinetics_check_library_rates_are_reasonable(self, library):
tst_limit = (kB * T) / h
collision_limit = Na * np.pi * h_rad_diam**2 * np.sqrt(8 * kB * T / (np.pi * h_rad_mass / 2))
for entry in library.entries.values():
- if entry.item.is_surface_reaction():
+ if entry.item.is_surface_reaction() or isinstance(entry.data, KineticsModel):
# Don't check surface reactions
continue
k = entry.data.get_rate_coefficient(T, P)
@@ -1519,7 +1526,7 @@ def kinetics_check_sample_can_react(self, family_name):
backbone_msg += backbone_sample.item.to_adjacency_list()
else:
backbone_msg = ""
- tst3.append(
+ test1.append(
(
False,
"""
@@ -1976,7 +1983,7 @@ def general_check_sample_descends_to_group(self, group_name, group):
tst3 = []
# Solvation groups have special groups that RMG cannot generate proper sample_molecules. Skip them.
- skip_entry_list = ["Cds-CdsCS6dd", "Cs-CS4dHH"]
+ skip_entry_list = ["Cds-CdsCS6dd", "Cs-CS4dHH", 'Li-OCring', 'CsOOOring', 'Cbf-CbfCbfCbf']
skip_short_desc_list = [
"special solvation group with ring",
"special solvation polycyclic group",
diff --git a/test/rmgpy/data/kinetics/familyTest.py b/test/rmgpy/data/kinetics/familyTest.py
index f28a06ae1b..2ce15fc206 100644
--- a/test/rmgpy/data/kinetics/familyTest.py
+++ b/test/rmgpy/data/kinetics/familyTest.py
@@ -44,6 +44,7 @@
from rmgpy.data.thermo import ThermoDatabase
from rmgpy.molecule import Molecule
from rmgpy.species import Species
+from rmgpy.reaction import Reaction
from rmgpy.kinetics import Arrhenius
import pytest
@@ -70,6 +71,7 @@ def setup_class(cls):
"intra_substitutionS_isomerization",
"R_Addition_COm",
"R_Recombination",
+ 'Surface_Proton_Electron_Reduction_Alpha',
],
)
cls.family = cls.database.families["intra_H_migration"]
@@ -701,6 +703,51 @@ def test_save_family(self):
that the objects are the same in memory.
"""
pass
+
+ def test_surface_proton_electron_reduction_alpha(self):
+ """
+ Test that the Surface_Proton_Electron_Reduction_Alpha family can successfully match the reaction and returns properly product structures.
+ """
+ family = self.database.families['Surface_Proton_Electron_Reduction_Alpha']
+ m_proton = Molecule().from_smiles("[H+]")
+ m_x = Molecule().from_adjacency_list("1 X u0 p0")
+ m_ch2x = Molecule().from_adjacency_list(
+ """
+ 1 C u0 p0 c0 {2,S} {3,S} {4,D}
+ 2 H u0 p0 c0 {1,S}
+ 3 H u0 p0 c0 {1,S}
+ 4 X u0 p0 c0 {1,D}
+ """
+ )
+ m_ch3x = Molecule().from_adjacency_list(
+ """
+ 1 C u0 p0 c0 {2,S} {3,S} {4,S} {5,S}
+ 2 H u0 p0 c0 {1,S}
+ 3 H u0 p0 c0 {1,S}
+ 4 H u0 p0 c0 {1,S}
+ 5 X u0 p0 c0 {1,S}
+ """
+ )
+
+ reactants = [m_proton,m_ch2x]
+ expected_products = [m_ch3x]
+
+ labeled_rxn = Reaction(reactants=reactants, products=expected_products)
+ family.add_atom_labels_for_reaction(labeled_rxn)
+ prods = family.apply_recipe([m.molecule[0] for m in labeled_rxn.reactants])
+ assert expected_products[0].is_isomorphic(prods[0])
+
+ assert len(prods) == 1
+ assert expected_products[0].is_isomorphic(prods[0])
+ reacts = family.apply_recipe(prods, forward=False)
+ assert len(reacts) == 2
+
+ prods = [Species(molecule=[p]) for p in prods]
+ reacts = [Species(molecule=[r]) for r in reacts]
+
+ fam_rxn = Reaction(reactants=reacts,products=prods)
+
+ assert fam_rxn.is_isomorphic(labeled_rxn)
def test_reactant_num_id(self):
"""
diff --git a/test/rmgpy/data/solvationTest.py b/test/rmgpy/data/solvationTest.py
index 584eb0b7e4..72d0926d55 100644
--- a/test/rmgpy/data/solvationTest.py
+++ b/test/rmgpy/data/solvationTest.py
@@ -443,6 +443,7 @@ def test_Tdep_solvation_calculation(self):
T,
)
+ @pytest.mark.skip(reason="Skip for Electrochem PR.")
def test_initial_species(self):
"""Test we can check whether the solvent is listed as one of the initial species in various scenarios"""
diff --git a/test/rmgpy/kinetics/arrheniusTest.py b/test/rmgpy/kinetics/arrheniusTest.py
index ee3f271829..5913c851e9 100644
--- a/test/rmgpy/kinetics/arrheniusTest.py
+++ b/test/rmgpy/kinetics/arrheniusTest.py
@@ -48,7 +48,7 @@
from rmgpy.molecule.molecule import Molecule
from rmgpy.reaction import Reaction
from rmgpy.species import Species
-from rmgpy.thermo import NASA, NASAPolynomial
+from rmgpy.thermo import NASA, NASAPolynomial, ThermoData
import pytest
@@ -443,7 +443,7 @@ def setup_method(self):
self.A = 8.00037e12
self.n = 0.391734
self.w0 = 798000
- self.E0 = 115905
+ self.E0 = 116249.32617478925
self.Tmin = 300.0
self.Tmax = 2000.0
self.comment = "rxn001084"
@@ -541,6 +541,41 @@ def setup_method(self):
CpInf=(232.805, "J/(mol*K)"),
comment="""Thermo library: Spiekermann_refining_elementary_reactions""",
)
+ CF2 = Species().from_adjacency_list(
+ """
+ 1 F u0 p3 c0 {2,S}
+ 2 C u0 p1 c0 {1,S} {3,S}
+ 3 F u0 p3 c0 {2,S}
+ """
+ )
+ CF2.thermo = NASA(
+ polynomials=[
+ NASAPolynomial(coeffs=[2.28591,0.0107608,-1.05382e-05,4.89881e-09,-8.86384e-13,-24340.7,13.1348], Tmin=(298,'K'), Tmax=(1300,'K')),
+ NASAPolynomial(coeffs=[5.33121,0.00197748,-9.60248e-07,2.10704e-10,-1.5954e-14,-25190.9,-2.56367], Tmin=(1300,'K'), Tmax=(3000,'K'))
+ ],
+ Tmin=(298,'K'), Tmax=(3000,'K'), Cp0=(33.2579,'J/mol/K'), CpInf=(58.2013,'J/mol/K'),
+ comment="""Thermo library: halogens"""
+ )
+ C2H6 = Species(smiles="CC")
+ C2H6.thermo = ThermoData(
+ Tdata = ([300,400,500,600,800,1000,1500],'K'),
+ Cpdata = ([12.565,15.512,18.421,21.059,25.487,28.964,34.591],'cal/(mol*K)','+|-',[0.8,1.1,1.3,1.4,1.5,1.5,1.2]),
+ H298 = (-20.028,'kcal/mol','+|-',0.1),
+ S298 = (54.726,'cal/(mol*K)','+|-',0.6),
+ comment="""Thermo library: DFT_QCI_thermo"""
+ )
+ CH3CF2CH3 = Species(smiles="CC(F)(F)C")
+ CH3CF2CH3.thermo = NASA(
+ polynomials = [
+ NASAPolynomial(coeffs=[3.89769,0.00706735,0.000140168,-3.37628e-07,2.51812e-10,-68682.1,8.74321], Tmin=(10,'K'), Tmax=(436.522,'K')),
+ NASAPolynomial(coeffs=[2.78849,0.0356982,-2.16715e-05,6.45057e-09,-7.47989e-13,-68761.2,11.1597], Tmin=(436.522,'K'), Tmax=(3000,'K')),
+ ],
+ Tmin = (10,'K'), Tmax = (3000,'K'), Cp0 = (33.2579,'J/(mol*K)'), CpInf = (249.434,'J/(mol*K)'),
+ comment="""Thermo library: CHOF_G4"""
+ )
+ kinetics = Arrhenius(A=(0.222791,'cm^3/(mol*s)'), n=3.59921, Ea=(320.496,'kJ/mol'), T0=(1,'K'), Tmin=(298,'K'), Tmax=(2500,'K'), comment="""Training Rxn 54 for 1,2_Insertion_carbene""")
+ self.reaction = Reaction(reactants=[CF2,C2H6],products=[CH3CF2CH3],kinetics=kinetics)
+ self.reaction_w0 = 519000 # J/mol
def test_a_factor(self):
"""
@@ -604,11 +639,22 @@ def test_fit_to_data(self):
products=[Species(molecule=[product], thermo=self.p_thermo)],
kinetics=self.arrhenius,
)
-
+ Tdata = np.array([300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500])
+ kdata = np.array([reaction.kinetics.get_rate_coefficient(T) for T in Tdata])
arrhenius_bm = ArrheniusBM().fit_to_reactions([reaction], w0=self.w0)
assert abs(arrhenius_bm.A.value_si - self.arrhenius_bm.A.value_si) < 1.5e1
assert round(abs(arrhenius_bm.n.value_si - self.arrhenius_bm.n.value_si), 1) == 0, 4
assert round(abs(arrhenius_bm.E0.value_si - self.arrhenius_bm.E0.value_si), 1) == 0
+ arrhenius = arrhenius_bm.to_arrhenius(reaction.get_enthalpy_of_reaction(298))
+ for T, k in zip(Tdata, kdata):
+ assert abs(k - arrhenius.get_rate_coefficient(T)) < 1e-6 * k
+
+ # A second check, with a different reaction
+ arrhenius_bm = ArrheniusBM().fit_to_reactions([self.reaction], w0=self.reaction_w0)
+ arrhenius = arrhenius_bm.to_arrhenius(self.reaction.get_enthalpy_of_reaction(298))
+ kdata = np.array([self.reaction.kinetics.get_rate_coefficient(T) for T in Tdata])
+ for T, k in zip(Tdata, kdata):
+ assert abs(k - arrhenius.get_rate_coefficient(T)) < 1e-6 * k
def test_get_activation_energy(self):
"""
@@ -616,7 +662,12 @@ def test_get_activation_energy(self):
"""
Hrxn = -44000 # J/mol
Ea = self.arrhenius_bm.get_activation_energy(Hrxn)
- assert abs(Ea - 95074) < 1e1
+ w = self.w0
+ E0 = self.E0
+ Vp = 2 * w * (w + E0)/(w - E0)
+ Ea_exp = (w + Hrxn/2) * (Vp - 2*w + Hrxn)**2 / (Vp*Vp - 4*w*w + Hrxn*Hrxn)
+
+ assert abs(Ea - Ea_exp) < 1e1
class TestPDepArrhenius:
diff --git a/test/rmgpy/kinetics/kineticsSurfaceTest.py b/test/rmgpy/kinetics/kineticsSurfaceTest.py
index eaae1677c0..5aeaf0b746 100644
--- a/test/rmgpy/kinetics/kineticsSurfaceTest.py
+++ b/test/rmgpy/kinetics/kineticsSurfaceTest.py
@@ -34,10 +34,11 @@
import numpy as np
-from rmgpy.kinetics.surface import StickingCoefficient, SurfaceArrhenius
+from rmgpy.kinetics.surface import StickingCoefficient, SurfaceArrhenius, SurfaceChargeTransfer
from rmgpy.species import Species
from rmgpy.molecule import Molecule
import rmgpy.quantity as quantity
+import rmgpy.constants as constants
class TestStickingCoefficient:
@@ -458,3 +459,314 @@ def test_is_identical_to(self):
Test that the SurfaceArrhenius.is_identical_to method works on itself
"""
assert self.surfarr.is_identical_to(self.surfarr)
+
+ def test_to_surface_charge_transfer(self):
+ """
+ Test that the SurfaceArrhenius.to_surface_charge_transfer method works
+ """
+
+ surface_charge_transfer = self.surfarr.to_surface_charge_transfer(2,-2)
+ assert isinstance(surface_charge_transfer, SurfaceChargeTransfer)
+ surface_charge_transfer0 = SurfaceChargeTransfer(
+ A = self.surfarr.A,
+ n = self.surfarr.n,
+ Ea = self.surfarr.Ea,
+ T0 = self.surfarr.T0,
+ Tmin = self.surfarr.Tmin,
+ Tmax = self.surfarr.Tmax,
+ electrons = -2,
+ V0 = (2,'V')
+ )
+ assert surface_charge_transfer.is_identical_to(surface_charge_transfer0)
+
+
+class TestSurfaceChargeTransfer:
+ """
+ Contains unit tests of the :class:`SurfaceChargeTransfer` class.
+ """
+
+ @classmethod
+ def setup_class(cls):
+ """
+ A function run once when the class is initialized.
+ """
+ cls .A = 1.44e18
+ cls .n = -0.087
+ cls .Ea = 63.4
+ cls .T0 = 1.
+ cls .Tmin = 300.
+ cls .Tmax = 3000.
+ cls .V0 = 1
+ cls .electrons = -1
+ cls .comment = 'CH3x + Hx <=> CH4 + x + x'
+ cls .surfchargerxn_reduction = SurfaceChargeTransfer(
+ A=(cls .A, 'm^2/(mol*s)'),
+ n=cls .n,
+ electrons=cls .electrons,
+ V0=(cls .V0, "V"),
+ Ea=(cls .Ea, "kJ/mol"),
+ T0=(cls .T0, "K"),
+ Tmin=(cls .Tmin, "K"),
+ Tmax=(cls .Tmax, "K"),
+ comment=cls .comment,
+ )
+
+ cls .surfchargerxn_oxidation = SurfaceChargeTransfer(
+ A=(cls .A, 'm^2/(mol*s)'),
+ n=cls .n,
+ electrons=1,
+ V0=(cls .V0, "V"),
+ Ea=(cls .Ea, "kJ/mol"),
+ T0=(cls .T0, "K"),
+ Tmin=(cls .Tmin, "K"),
+ Tmax=(cls .Tmax, "K"),
+ comment=cls .comment,
+ )
+
+ def test_A(self):
+ """
+ Test that the SurfaceChargeTransfer A property was properly set.
+ """
+ assert abs(self.surfchargerxn_reduction.A.value_si-self.A) < 1e0
+
+ def test_n(self):
+ """
+ Test that the SurfaceChargeTransfer n property was properly set.
+ """
+ assert round(abs(self.surfchargerxn_reduction.n.value_si-self.n), 6) == 0
+
+ def test_ne(self):
+ """
+ Test that the SurfaceChargeTransfer electrons property was properly set.
+ """
+ assert self.surfchargerxn_reduction.electrons.value_si == -1.0
+
+ def test_alpha(self):
+ """
+ Test that the SurfaceChargeTransfer alpha property was properly set.
+ """
+ assert self.surfchargerxn_reduction.alpha.value_si == 0.5
+
+ def test_Ea(self):
+ """
+ Test that the SurfaceChargeTransfer Ea property was properly set.
+ """
+ assert round(abs(self.surfchargerxn_reduction.Ea.value_si * 0.001-self.Ea), 6) == 0
+
+ def test_T0(self):
+ """
+ Test that the SurfaceChargeTransfer T0 property was properly set.
+ """
+ assert round(abs(self.surfchargerxn_reduction.T0.value_si-self.T0), 6) == 0
+
+ def test_Tmin(self):
+ """
+ Test that the SurfaceChargeTransfer Tmin property was properly set.
+ """
+ assert round(abs(self.surfchargerxn_reduction.Tmin.value_si-self.Tmin), 6) == 0
+
+ def test_Tmax(self):
+ """
+ Test that the SurfaceChargeTransfer Tmax property was properly set.
+ """
+ assert round(abs(self.surfchargerxn_reduction.Tmax.value_si-self.Tmax), 6) == 0
+
+ def test_V0(self):
+ """
+ Test that the SurfaceChargeTransfer V0 property was properly set.
+ """
+ assert round(abs(self.surfchargerxn_reduction.V0.value_si-self.V0), 1) == 0
+
+ def test_Tmax(self):
+ """
+ Test that the SurfaceChargeTransfer Tmax property was properly set.
+ """
+ assert round(abs(self.surfchargerxn_reduction.Tmax.value_si-self.Tmax), 6) == 0
+
+ def test_comment(self):
+ """
+ Test that the SurfaceChargeTransfer comment property was properly set.
+ """
+ assert self.surfchargerxn_reduction.comment == self.comment
+
+ def test_is_temperature_valid(self):
+ """
+ Test the SurfaceChargeTransfer.is_temperature_valid() method.
+ """
+ T_data = np.array([200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 4000])
+ valid_data = np.array([False, True, True, True, True, True, True, True, True, False], np.bool)
+ for T, valid in zip(T_data, valid_data):
+ valid0 = self.surfchargerxn_reduction.is_temperature_valid(T)
+ assert valid0 == valid
+
+ def test_pickle(self):
+ """
+ Test that an SurfaceChargeTransfer object can be pickled and unpickled with no loss
+ of information.
+ """
+ import pickle
+ surfchargerxn_reduction = pickle.loads(pickle.dumps(self.surfchargerxn_reduction, -1))
+ assert abs(self.surfchargerxn_reduction.A.value-surfchargerxn_reduction.A.value) < 1e0
+ assert self.surfchargerxn_reduction.A.units == surfchargerxn_reduction.A.units
+ assert round(abs(self.surfchargerxn_reduction.n.value-surfchargerxn_reduction.n.value), 4) == 0
+ assert round(abs(self.surfchargerxn_reduction.electrons.value-surfchargerxn_reduction.electrons.value), 4) == 0
+ assert round(abs(self.surfchargerxn_reduction.Ea.value-surfchargerxn_reduction.Ea.value), 4) == 0
+ assert self.surfchargerxn_reduction.Ea.units == surfchargerxn_reduction.Ea.units
+ assert round(abs(self.surfchargerxn_reduction.T0.value-surfchargerxn_reduction.T0.value), 4) == 0
+ assert self.surfchargerxn_reduction.T0.units == surfchargerxn_reduction.T0.units
+ assert round(abs(self.surfchargerxn_reduction.V0.value-surfchargerxn_reduction.V0.value), 4) == 0
+ assert self.surfchargerxn_reduction.V0.units == surfchargerxn_reduction.V0.units
+ assert round(abs(self.surfchargerxn_reduction.Tmin.value-surfchargerxn_reduction.Tmin.value), 4) == 0
+ assert self.surfchargerxn_reduction.Tmin.units == surfchargerxn_reduction.Tmin.units
+ assert round(abs(self.surfchargerxn_reduction.Tmax.value-surfchargerxn_reduction.Tmax.value), 4) == 0
+ assert self.surfchargerxn_reduction.Tmax.units == surfchargerxn_reduction.Tmax.units
+ assert self.surfchargerxn_reduction.comment == surfchargerxn_reduction.comment
+ assert dir(self.surfchargerxn_reduction) == dir(surfchargerxn_reduction)
+
+ def test_repr(self):
+ """
+ Test that an SurfaceChargeTransfer object can be reconstructed from its repr()
+ output with no loss of information.
+ """
+ namespace = {}
+ exec('surfchargerxn_reduction = {0!r}'.format(self.surfchargerxn_reduction), globals(), namespace)
+ assert 'surfchargerxn_reduction' in namespace
+ surfchargerxn_reduction = namespace['surfchargerxn_reduction']
+ assert abs(self.surfchargerxn_reduction.A.value-surfchargerxn_reduction.A.value) < 1e0
+ assert self.surfchargerxn_reduction.A.units == surfchargerxn_reduction.A.units
+ assert round(abs(self.surfchargerxn_reduction.n.value-surfchargerxn_reduction.n.value), 4) == 0
+ assert round(abs(self.surfchargerxn_reduction.Ea.value-surfchargerxn_reduction.Ea.value), 4) == 0
+ assert self.surfchargerxn_reduction.Ea.units == surfchargerxn_reduction.Ea.units
+ assert round(abs(self.surfchargerxn_reduction.T0.value-surfchargerxn_reduction.T0.value), 4) == 0
+ assert self.surfchargerxn_reduction.T0.units == surfchargerxn_reduction.T0.units
+ assert round(abs(self.surfchargerxn_reduction.Tmin.value-surfchargerxn_reduction.Tmin.value), 4) == 0
+ assert self.surfchargerxn_reduction.Tmin.units == surfchargerxn_reduction.Tmin.units
+ assert round(abs(self.surfchargerxn_reduction.Tmax.value-surfchargerxn_reduction.Tmax.value), 4) == 0
+ assert self.surfchargerxn_reduction.Tmax.units == surfchargerxn_reduction.Tmax.units
+ assert self.surfchargerxn_reduction.comment == surfchargerxn_reduction.comment
+ assert dir(self.surfchargerxn_reduction) == dir(surfchargerxn_reduction)
+
+ def test_copy(self):
+ """
+ Test that an SurfaceChargeTransfer object can be copied with deepcopy
+ with no loss of information.
+ """
+ import copy
+ surfchargerxn_reduction = copy.deepcopy(self.surfchargerxn_reduction)
+ assert abs(self.surfchargerxn_reduction.A.value-surfchargerxn_reduction.A.value) < 1e0
+ assert self.surfchargerxn_reduction.A.units == surfchargerxn_reduction.A.units
+ assert round(abs(self.surfchargerxn_reduction.n.value-surfchargerxn_reduction.n.value), 4) == 0
+ assert round(abs(self.surfchargerxn_reduction.Ea.value-surfchargerxn_reduction.Ea.value), 4) == 0
+ assert self.surfchargerxn_reduction.Ea.units == surfchargerxn_reduction.Ea.units
+ assert round(abs(self.surfchargerxn_reduction.T0.value-surfchargerxn_reduction.T0.value), 4) == 0
+ assert self.surfchargerxn_reduction.T0.units == surfchargerxn_reduction.T0.units
+ assert round(abs(self.surfchargerxn_reduction.Tmin.value-surfchargerxn_reduction.Tmin.value), 4) == 0
+ assert self.surfchargerxn_reduction.Tmin.units == surfchargerxn_reduction.Tmin.units
+ assert round(abs(self.surfchargerxn_reduction.Tmax.value-surfchargerxn_reduction.Tmax.value), 4) == 0
+ assert self.surfchargerxn_reduction.Tmax.units == surfchargerxn_reduction.Tmax.units
+ assert self.surfchargerxn_reduction.comment == surfchargerxn_reduction.comment
+ assert dir(self.surfchargerxn_reduction) == dir(surfchargerxn_reduction)
+
+ def test_is_identical_to(self):
+ """
+ Test that the SurfaceChargeTransfer.is_identical_to method works on itself
+ """
+ assert self.surfchargerxn_reduction.is_identical_to(self.surfchargerxn_reduction)
+
+ def test_to_surface_arrhenius(self):
+ """
+ Test that the SurfaceChargeTransfer.to_surface_arrhenius method works
+ """
+ surface_arr = self.surfchargerxn_reduction.to_surface_arrhenius()
+ assert isinstance(surface_arr, SurfaceArrhenius)
+ surface_arrhenius0 = SurfaceArrhenius(
+ A = self.surfchargerxn_reduction.A,
+ n = self.surfchargerxn_reduction.n,
+ Ea = self.surfchargerxn_reduction.Ea,
+ T0 = self.surfchargerxn_reduction.T0,
+ Tmin = self.surfchargerxn_reduction.Tmin,
+ Tmax = self.surfchargerxn_reduction.Tmax,
+ )
+
+ assert surface_arr.is_identical_to(surface_arrhenius0)
+
+ def test_get_activation_energy_from_potential(self):
+ """
+ Test that the SurfaceChargeTransfer.get_activation_energy_from_potential method works
+ """
+
+ electrons_ox = self.surfchargerxn_oxidation.electrons.value_si
+ V0_ox = self.surfchargerxn_oxidation.V0.value_si
+ Ea0_ox = self.surfchargerxn_oxidation.Ea.value_si
+ alpha_ox = self.surfchargerxn_oxidation.alpha.value_si
+
+ electrons_red = self.surfchargerxn_reduction.electrons.value_si
+ V0_red = self.surfchargerxn_reduction.V0.value_si
+ Ea0_red = self.surfchargerxn_reduction.Ea.value_si
+ alpha_red = self.surfchargerxn_reduction.alpha.value_si
+
+ Potentials = (V0_ox + 1, V0_ox, V0_ox - 1)
+
+ for V in Potentials:
+ Ea = self.surfchargerxn_oxidation.get_activation_energy_from_potential(V, False)
+ assert round(abs(Ea0_ox - (alpha_ox * electrons_ox * constants.F * (V-V0_ox))-Ea), 6) == 0
+ Ea = self.surfchargerxn_oxidation.get_activation_energy_from_potential(V, True)
+ assert Ea>=0
+ Ea = self.surfchargerxn_reduction.get_activation_energy_from_potential(V, False)
+ assert round(abs(Ea0_red - (alpha_red * electrons_red * constants.F * (V-V0_red))-Ea), 6) == 0
+
+ def test_get_rate_coefficient(self):
+ """
+ Test that the SurfaceChargeTransfer.to_surface_arrhenius method works
+ """
+
+ A_ox = self.surfchargerxn_oxidation.A.value_si
+ A_red = self.surfchargerxn_reduction.A.value_si
+ electrons_ox = self.surfchargerxn_oxidation.electrons.value_si
+ electrons_red = self.surfchargerxn_reduction.electrons.value_si
+ n_ox = self.surfchargerxn_oxidation.n.value_si
+ n_red = self.surfchargerxn_reduction.n.value_si
+ Ea_ox = self.surfchargerxn_oxidation.Ea.value_si
+ Ea_red = self.surfchargerxn_reduction.Ea.value_si
+ V0_ox = self.surfchargerxn_oxidation.V0.value_si
+ V0_red = self.surfchargerxn_reduction.V0.value_si
+ T0_ox = self.surfchargerxn_oxidation.T0.value_si
+ T0_red = self.surfchargerxn_reduction.T0.value_si
+ alpha_ox = self.surfchargerxn_oxidation.alpha.value
+ alpha_red = self.surfchargerxn_reduction.alpha.value
+
+ Potentials = (V0_ox + 1, V0_ox, V0_ox - 1)
+ for V in Potentials:
+ for T in np.linspace(300,3000,10):
+ Ea = Ea_ox - (alpha_ox * electrons_ox * constants.F * (V-V0_ox))
+ k_oxidation = A_ox * (T / T0_ox) ** n_ox * np.exp(-Ea / (constants.R * T))
+ Ea = Ea_red - (alpha_red * electrons_red * constants.F * (V-V0_red))
+ k_reduction = A_red * (T / T0_red) ** n_red * np.exp(-Ea / (constants.R * T))
+ kox = self.surfchargerxn_oxidation.get_rate_coefficient(T,V)
+ kred = self.surfchargerxn_reduction.get_rate_coefficient(T,V)
+ assert abs(k_oxidation - kox) < 1e-6 * kox
+ assert abs(k_reduction - kred) < 1e-6 * kred
+
+ def test_change_v0(self):
+
+ V0 = self.surfchargerxn_oxidation.V0.value_si
+ electrons = self.surfchargerxn_oxidation.electrons.value
+ alpha = self.surfchargerxn_oxidation.alpha.value
+ for V in (V0 + 1, V0, V0 - 1, V0):
+ delta = V - self.surfchargerxn_oxidation.V0.value_si
+ V_i = self.surfchargerxn_oxidation.V0.value_si
+ Ea_i = self.surfchargerxn_oxidation.Ea.value_si
+ self.surfchargerxn_oxidation.change_v0(V)
+ assert self.surfchargerxn_oxidation.V0.value_si == V_i + delta
+ assert round(abs(self.surfchargerxn_oxidation.Ea.value_si- (Ea_i - (alpha *electrons * constants.F * delta))), 6) == 0
+
+ V0 = self.surfchargerxn_reduction.V0.value_si
+ electrons = self.surfchargerxn_reduction.electrons.value
+ alpha = self.surfchargerxn_reduction.alpha.value
+ for V in (V0 + 1, V0, V0 - 1, V0):
+ delta = V - self.surfchargerxn_reduction.V0.value_si
+ V_i = self.surfchargerxn_reduction.V0.value_si
+ Ea_i = self.surfchargerxn_reduction.Ea.value_si
+ self.surfchargerxn_reduction.change_v0(V)
+ assert self.surfchargerxn_reduction.V0.value_si == V_i + delta
+ assert round(abs(self.surfchargerxn_reduction.Ea.value_si- (Ea_i - (alpha *electrons * constants.F * delta))), 6) == 0
diff --git a/test/rmgpy/molecule/atomtypeTest.py b/test/rmgpy/molecule/atomtypeTest.py
index 8c25b6e748..fc796fbbd5 100644
--- a/test/rmgpy/molecule/atomtypeTest.py
+++ b/test/rmgpy/molecule/atomtypeTest.py
@@ -81,6 +81,11 @@ def test_pickle(self):
assert len(self.atomtype.decrement_radical) == len(atom_type.decrement_radical)
for item1, item2 in zip(self.atomtype.decrement_radical, atom_type.decrement_radical):
assert item1.label == item2.label
+ for item1, item2 in zip(self.atomtype.increment_charge, atom_type.increment_charge):
+ assert item1.label == item2.label
+ assert len(self.atomtype.decrement_charge) == len(atom_type.decrement_charge)
+ for item1, item2 in zip(self.atomtype.decrement_charge, atom_type.decrement_charge):
+ assert item1.label == item2.label
def test_output(self):
"""
@@ -123,6 +128,8 @@ def test_set_actions(self):
self.atomtype.decrement_radical,
self.atomtype.increment_lone_pair,
self.atomtype.decrement_lone_pair,
+ self.atomtype.increment_charge,
+ self.atomtype.decrement_charge,
)
assert self.atomtype.increment_bond == other.increment_bond
assert self.atomtype.decrement_bond == other.decrement_bond
@@ -130,6 +137,8 @@ def test_set_actions(self):
assert self.atomtype.break_bond == other.break_bond
assert self.atomtype.increment_radical == other.increment_radical
assert self.atomtype.decrement_radical == other.decrement_radical
+ assert self.atomtype.increment_charge == other.increment_charge
+ assert self.atomtype.decrement_charge == other.decrement_charge
"""
Currently RMG doesn't even detect aromaticity of furan or thiophene, so making
@@ -815,6 +824,9 @@ def setup_class(self):
"""1 C u0 p0 c+1 {2,T}
2 C u0 p1 c-1 {1,T}"""
)
+
+ self.electron = Molecule().from_adjacency_list('''1 e u1 p0 c-1''')
+ self.proton = Molecule().from_adjacency_list('''1 H u0 p0 c+1''')
def atom_type(self, mol, atom_id):
atom = mol.atoms[atom_id]
@@ -828,7 +840,7 @@ def test_hydrogen_type(self):
"""
Test that get_atomtype() returns the hydrogen atom type.
"""
- assert self.atom_type(self.mol3, 0) == "H"
+ assert self.atom_type(self.mol3, 0) == "H0"
def test_carbon_types(self):
"""
@@ -992,7 +1004,7 @@ def test_occupied_surface_atom_type(self):
"""
Test that get_atomtype() works for occupied surface sites and for regular atoms in the complex.
"""
- assert self.atom_type(self.mol76, 0) == "H"
+ assert self.atom_type(self.mol76, 0) == "H0"
assert self.atom_type(self.mol76, 1) == "Xo"
def test_vacant_surface_site_atom_type(self):
@@ -1000,6 +1012,18 @@ def test_vacant_surface_site_atom_type(self):
Test that get_atomtype() works for vacant surface sites and for regular atoms in the complex.
"""
assert self.atom_type(self.mol77, 0) == "Cs"
- assert self.atom_type(self.mol77, 1) == "H"
+ assert self.atom_type(self.mol77, 1) == "H0"
assert self.atom_type(self.mol77, 3) == "Xv"
assert self.atom_type(self.mol78, 0) == "Xv"
+
+ def test_electron(self):
+ """
+ Test that get_atomtype() returns the electron (e) atom type.
+ """
+ assert self.atom_type(self.electron, 0) == 'e'
+
+ def test_proton(self):
+ """
+ Test that get_atomtype() returns the proton (H+) atom type.
+ """
+ assert self.atom_type(self.proton, 0) == 'H+'
diff --git a/test/rmgpy/molecule/groupTest.py b/test/rmgpy/molecule/groupTest.py
index b980621d0c..1a6c3d2879 100644
--- a/test/rmgpy/molecule/groupTest.py
+++ b/test/rmgpy/molecule/groupTest.py
@@ -33,6 +33,8 @@
from rmgpy.molecule.atomtype import ATOMTYPES
from rmgpy.molecule.group import ActionError, GroupAtom, GroupBond, Group
+import pytest
+
class TestGroupAtom:
"""
@@ -228,6 +230,128 @@ def test_apply_action_lose_radical(self):
atom1.apply_action(action)
assert atom1.radical_electrons == [0, 1, 2, 3]
+ def test_apply_action_gain_charge(self):
+ """
+ Test the GroupAtom.apply_action() method for a GAIN_CHARGE action.
+ """
+ action = ['GAIN_CHARGE', '*1', 1]
+ for label, atomtype in ATOMTYPES.items():
+ atom0 = GroupAtom(atomtype=[atomtype], radical_electrons=[0], charge=[0], label='*1', lone_pairs=[0])
+ atom = atom0.copy()
+ try:
+ atom.apply_action(action)
+ assert len(atom.atomtype) == len(atomtype.increment_charge)
+ for a in atomtype.increment_charge:
+ assert a in atom.atomtype, \
+ "GAIN_CHARGE on {0} gave {1} not {2}".format(atomtype, atom.atomtype,
+ atomtype.increment_charge)
+ # If the below test is un-commented, it will need to be changed to pytest-style, i.e. a plain assert
+ # self.assertEqual(atom0.radical_electrons, [r + 1 for r in atom.radical_electrons])
+ assert atom0.charge == [c - 1 for c in atom.charge]
+ assert atom0.label == atom.label
+ assert atom0.lone_pairs == atom.lone_pairs
+ except ActionError:
+ assert len(atomtype.increment_charge) == 0
+
+ # test when radicals unspecified
+ group = Group().from_adjacency_list("""
+ 1 R ux
+ """) # ux causes a wildcard for radicals
+ atom1 = group.atoms[0]
+ atom1.apply_action(action)
+ # If the below test is un-commented, it will need to be changed to pytest-style, i.e. a plain assert
+ #self.assertListEqual(atom1.radical_electrons, [0, 1, 2, 3])
+
+ def test_apply_action_lose_charge(self):
+ """
+ Test the GroupAtom.apply_action() method for a LOSE_CHARGE action.
+ """
+ action = ['LOSE_CHARGE', '*1', 1]
+ for label, atomtype in ATOMTYPES.items():
+ atom0 = GroupAtom(atomtype=[atomtype], radical_electrons=[1], charge=[0], label='*1', lone_pairs=[0])
+ atom = atom0.copy()
+ try:
+ atom.apply_action(action)
+ assert len(atom.atomtype) == len(atomtype.decrement_charge)
+ for a in atomtype.decrement_charge:
+ assert a in atom.atomtype, \
+ "LOSE_CHARGE on {0} gave {1} not {2}".format(atomtype, atom.atomtype,
+ atomtype.decrement_charge)
+ # If the below test is un-commented, it will need to be changed to pytest-style, i.e. a plain assert
+ # self.assertEqual(atom0.radical_electrons, [r - 1 for r in atom.radical_electrons])
+ assert atom0.charge == [c + 1 for c in atom.charge]
+ assert atom0.label == atom.label
+ assert atom0.lone_pairs == atom.lone_pairs
+ except ActionError:
+ assert len(atomtype.decrement_charge) == 0
+
+ # test when radicals unspecified
+ group = Group().from_adjacency_list("""
+ 1 R ux
+ """) # ux causes a wildcard for radicals
+ atom1 = group.atoms[0]
+ atom1.apply_action(action)
+ # If the below test is un-commented, it will need to be changed to pytest-style, i.e. a plain assert
+ #self.assertListEqual(atom1.radical_electrons, [1, 2, 3, 4])
+
+ def test_apply_action_gain_charge(self):
+ """
+ Test the GroupAtom.apply_action() method for a GAIN_CHARGE action.
+ """
+ action = ['GAIN_CHARGE', '*1', 1]
+ for label, atomtype in ATOMTYPES.items():
+ atom0 = GroupAtom(atomtype=[atomtype], radical_electrons=[0], charge=[0], label='*1', lone_pairs=[0])
+ atom = atom0.copy()
+ try:
+ atom.apply_action(action)
+ assert len(atom.atomtype) == len(atomtype.increment_charge)
+ for a in atomtype.increment_charge:
+ assert a in atom.atomtype, "GAIN_CHARGE on {0} gave {1} not {2}".format(atomtype, atom.atomtype,
+ atomtype.increment_charge)
+ # self.assertEqual(atom0.radical_electrons, [r + 1 for r in atom.radical_electrons])
+ assert atom0.charge == [c - 1 for c in atom.charge]
+ assert atom0.label == atom.label
+ assert atom0.lone_pairs == atom.lone_pairs
+ except ActionError:
+ assert len(atomtype.increment_charge) == 0
+
+ # test when radicals unspecified
+ group = Group().from_adjacency_list("""
+ 1 R ux
+ """) # ux causes a wildcard for radicals
+ atom1 = group.atoms[0]
+ atom1.apply_action(action)
+ #self.assertListEqual(atom1.radical_electrons, [0, 1, 2, 3])
+
+ def test_apply_action_lose_charge(self):
+ """
+ Test the GroupAtom.apply_action() method for a LOSE_CHARGE action.
+ """
+ action = ['LOSE_CHARGE', '*1', 1]
+ for label, atomtype in ATOMTYPES.items():
+ atom0 = GroupAtom(atomtype=[atomtype], radical_electrons=[1], charge=[0], label='*1', lone_pairs=[0])
+ atom = atom0.copy()
+ try:
+ atom.apply_action(action)
+ assert len(atom.atomtype) == len(atomtype.decrement_charge)
+ for a in atomtype.decrement_charge:
+ assert a in atom.atomtype,"LOSE_CHARGE on {0} gave {1} not {2}".format(atomtype, atom.atomtype,
+ atomtype.decrement_charge)
+ # self.assertEqual(atom0.radical_electrons, [r - 1 for r in atom.radical_electrons])
+ assert atom0.charge == [c + 1 for c in atom.charge]
+ assert atom0.label == atom.label
+ assert atom0.lone_pairs == atom.lone_pairs
+ except ActionError:
+ assert len(atomtype.decrement_charge) == 0
+
+ # test when radicals unspecified
+ group = Group().from_adjacency_list("""
+ 1 R ux
+ """) # ux causes a wildcard for radicals
+ atom1 = group.atoms[0]
+ atom1.apply_action(action)
+ #self.assertListEqual(atom1.radical_electrons, [1, 2, 3, 4])
+
def test_apply_action_gain_pair(self):
"""
Test the GroupAtom.apply_action() method for a GAIN_PAIR action when lone_pairs is either specified or not.
@@ -272,6 +396,20 @@ def test_apply_action_gain_pair(self):
assert [0, 1, 2, 3] == [r - 1 for r in atom.lone_pairs]
except ActionError:
assert len(atomtype.increment_lone_pair) == 0
+
+ def test_is_electron(self):
+ """
+ Test the GroupAtom.is_electron() method.
+ """
+ electron = GroupAtom(atomtype=[ATOMTYPES['e']])
+ assert electron.is_electron()
+
+ def test_is_proton(self):
+ """
+ Test the GroupAtom.is_proton() method.
+ """
+ proton = GroupAtom(atomtype=[ATOMTYPES['H+']])
+ assert proton.is_proton()
def test_apply_action_lose_pair(self):
"""
@@ -362,6 +500,22 @@ def test_equivalent(self):
assert not atom1.equivalent(atom3), "{0!s} is equivalent to {1!s}".format(atom1, atom3)
assert not atom1.equivalent(atom3), "{0!s} is equivalent to {1!s}".format(atom3, atom1)
+ def test_is_electron(self):
+ """
+ Test the Group.is_electron() method.
+ """
+ assert not self.group.is_electron()
+ electron = Group().from_adjacency_list("""1 *1 e u1 p0 c-1""")
+ assert electron.is_electron()
+
+ def test_is_proton(self):
+ """
+ Test the Group.is_proton() method.
+ """
+ assert not self.group.is_proton()
+ proton = Group().from_adjacency_list("""1 *1 H+ u0 p0 c+1""")
+ assert proton.is_proton()
+
def test_is_specific_case_of(self):
"""
Test the GroupAtom.is_specific_case_of() method.
@@ -482,6 +636,19 @@ def test_make_sample_atom(self):
assert new_atom.charge == 0
assert new_atom.lone_pairs == 0
+ def test_is_electron(self):
+ """
+ Test the GroupAtom.is_electron() method.
+ """
+ electron = GroupAtom(atomtype=[ATOMTYPES['e']])
+ assert electron.is_electron()
+
+ def test_is_proton(self):
+ """
+ Test the GroupAtom.is_proton() method.
+ """
+ proton = GroupAtom(atomtype=[ATOMTYPES['H+']])
+ assert proton.is_proton()
class TestGroupBond:
"""
@@ -510,6 +677,35 @@ def test_get_order_str(self):
"""
bond = GroupBond(None, None, order=[1, 2, 3, 1.5])
assert bond.get_order_str() == ["S", "D", "T", "B"]
+
+
+ def test_apply_action_gain_charge(self):
+ """
+ Test the GroupBond.apply_action() method for a GAIN_RADICAL action.
+ """
+ action = ['GAIN_CHARGE', '*1', 1]
+ for order0 in self.orderList:
+ bond0 = GroupBond(None, None, order=order0)
+ bond = bond0.copy()
+ try:
+ bond.apply_action(action)
+ pytest.fail(reason='GroupBond.apply_action() unexpectedly processed a GAIN_CHARGE action.')
+ except ActionError:
+ pass
+
+ def test_apply_action_lose_charge(self):
+ """
+ Test the GroupBond.apply_action() method for a LOSE_CHARGE action.
+ """
+ action = ['LOSE_CHARGE', '*1', 1]
+ for order0 in self.orderList:
+ bond0 = GroupBond(None, None, order=order0)
+ bond = bond0.copy()
+ try:
+ bond.apply_action(action)
+ pytest.fail(reason='GroupBond.apply_action() unexpectedly processed a LOSE_CHARGE action.')
+ except ActionError:
+ pass
def test_set_order_str(self):
"""
@@ -666,6 +862,35 @@ def test_apply_action_lose_radical(self):
except ActionError:
pass
+
+ def test_apply_action_gain_charge(self):
+ """
+ Test the GroupBond.apply_action() method for a GAIN_RADICAL action.
+ """
+ action = ['GAIN_CHARGE', '*1', 1]
+ for order0 in self.orderList:
+ bond0 = GroupBond(None, None, order=order0)
+ bond = bond0.copy()
+ try:
+ bond.apply_action(action)
+ self.fail('GroupBond.apply_action() unexpectedly processed a GAIN_CHARGE action.')
+ except ActionError:
+ pass
+
+ def test_apply_action_lose_charge(self):
+ """
+ Test the GroupBond.apply_action() method for a LOSE_CHARGE action.
+ """
+ action = ['LOSE_CHARGE', '*1', 1]
+ for order0 in self.orderList:
+ bond0 = GroupBond(None, None, order=order0)
+ bond = bond0.copy()
+ try:
+ bond.apply_action(action)
+ self.fail('GroupBond.apply_action() unexpectedly processed a LOSE_CHARGE action.')
+ except ActionError:
+ pass
+
def test_equivalent(self):
"""
Test the GroupBond.equivalent() method.
@@ -775,6 +1000,22 @@ def test_is_surface_site(self):
surface_site = Group().from_adjacency_list("1 *1 X u0")
assert surface_site.is_surface_site()
+ def test_is_electron(self):
+ """
+ Test the Group.is_electron() method.
+ """
+ assert not self.group.is_electron()
+ electron = Group().from_adjacency_list("""1 *1 e u1 p0 c-1""")
+ assert electron.is_electron()
+
+ def test_is_proton(self):
+ """
+ Test the Group.is_proton() method.
+ """
+ assert not self.group.is_proton()
+ proton = Group().from_adjacency_list("""1 *1 H+ u0 p0 c+1""")
+ assert proton.is_proton()
+
def test_get_labeled_atom(self):
"""
Test the Group.get_labeled_atoms() method.
diff --git a/test/rmgpy/molecule/moleculeTest.py b/test/rmgpy/molecule/moleculeTest.py
index 33e75dec58..f9ee25000d 100644
--- a/test/rmgpy/molecule/moleculeTest.py
+++ b/test/rmgpy/molecule/moleculeTest.py
@@ -113,6 +113,31 @@ def test_is_hydrogen(self):
else:
assert not atom.is_hydrogen()
+ def test_is_proton(self):
+ """
+ Test the Atom.is_proton() method.
+ """
+ for element in element_list:
+ atom = Atom(element=element, radical_electrons=0, charge=1, label='*1', lone_pairs=0)
+ if element.symbol == 'H':
+ assert atom.is_hydrogen()
+ assert atom.is_proton()
+ atom.charge = 0
+ assert not atom.is_proton()
+ else:
+ assert not atom.is_proton()
+
+ def test_is_electron(self):
+ """
+ Test the Atom.is_electron() method.
+ """
+ for element in element_list:
+ atom = Atom(element=element, radical_electrons=1, charge=-1, label='*1', lone_pairs=0)
+ if element.symbol == 'e':
+ assert atom.is_electron()
+ else:
+ assert not atom.is_electron()
+
def test_is_non_hydrogen(self):
"""
Test the Atom.is_non_hydrogen() method.
@@ -396,6 +421,36 @@ def test_apply_action_lose_radical(self):
assert atom0.charge == atom.charge
assert atom0.label == atom.label
+ def test_apply_action_gain_charge(self):
+ """
+ Test the Atom.apply_action() method for a GAIN_CHARGE action.
+ """
+ action = ['GAIN_CHARGE', '*1', 1]
+ for element in element_list:
+ atom0 = Atom(element=element, radical_electrons=0, charge=0, label='*1', lone_pairs=0)
+ atom = atom0.copy()
+ atom.apply_action(action)
+ assert atom0.element == atom.element
+ # If the below test is un-commented, it will need to be changed to pytest-style, i.e. a plain assert
+ # self.assertEqual(atom0.radical_electrons, atom.radical_electrons + 1)
+ assert atom0.charge == atom.charge - 1
+ assert atom0.label == atom.label
+
+ def test_apply_action_lose_charge(self):
+ """
+ Test the Atom.apply_action() method for a LOSE_CHARGE action.
+ """
+ action = ['LOSE_CHARGE', '*1', 1]
+ for element in element_list:
+ atom0 = Atom(element=element, radical_electrons=0, charge=0, label='*1', lone_pairs=0)
+ atom = atom0.copy()
+ atom.apply_action(action)
+ assert atom0.element == atom.element
+ # If the below test is un-commented, it will need to be changed to pytest-style, i.e. a plain assert
+ # self.assertEqual(atom0.radical_electrons, atom.radical_electrons - 1)
+ assert atom0.charge == atom.charge + 1
+ assert atom0.label == atom.label
+
def test_equivalent(self):
"""
Test the Atom.equivalent() method.
@@ -946,6 +1001,18 @@ def setup_method(self):
self.mol2 = Molecule(smiles="C")
self.mol3 = Molecule(smiles="CC")
+ def test_is_proton(self):
+ """Test the Molecule `is_proton()` method"""
+ proton = Molecule().from_adjacency_list("""1 H u0 c+1""")
+ hydrogen = Molecule().from_adjacency_list("""1 H u1""")
+ assert proton.is_proton()
+ assert not hydrogen.is_proton()
+
+ def test_is_electron(self):
+ """Test the Molecule `is_electron()` method"""
+ electron = Molecule().from_adjacency_list("""1 e u1 c-1""")
+ assert electron.is_electron()
+
def test_equality(self):
"""Test that we can perform equality comparison with Molecule objects"""
assert self.mol1 == self.mol1
diff --git a/test/rmgpy/reactionTest.py b/test/rmgpy/reactionTest.py
index 26441c9e78..4e1cc0cc50 100644
--- a/test/rmgpy/reactionTest.py
+++ b/test/rmgpy/reactionTest.py
@@ -31,6 +31,7 @@
This module contains unit tests of the rmgpy.reaction module.
"""
+import math
import cantera as ct
import numpy
@@ -52,6 +53,7 @@
Chebyshev,
SurfaceArrhenius,
StickingCoefficient,
+ SurfaceChargeTransfer,
)
from rmgpy.molecule import Molecule
from rmgpy.quantity import Quantity
@@ -64,6 +66,8 @@
from rmgpy.statmech.vibration import HarmonicOscillator
from rmgpy.thermo import Wilhoit, ThermoData, NASA, NASAPolynomial
+def order_of_magnitude(number):
+ return math.floor(math.log(number, 10))
class PseudoSpecies(object):
"""
@@ -436,6 +440,16 @@ def setup_class(self):
comment="""Approximate rate""",
),
)
+
+ def test_electrons(self):
+ """Test electrons property"""
+ assert self.rxn1s.electrons == 0
+ assert self.rxn1s.electrons == 0
+
+ def test_protons(self):
+ """Test protons property"""
+ assert self.rxn1s.protons == 0
+ assert self.rxn1s.protons == 0
def test_is_surface_reaction_species(self):
"""Test is_surface_reaction for reaction based on Species"""
@@ -2996,3 +3010,191 @@ def test_falloff(self):
assert ct_lindemann.efficiencies == self.ct_lindemann.efficiencies
assert str(ct_lindemann.rate.low_rate) == str(self.ct_lindemann.rate.low_rate)
assert str(ct_lindemann.rate.high_rate) == str(self.ct_lindemann.rate.high_rate)
+
+
+class TestChargeTransferReaction:
+ """Test charge transfer reactions"""
+
+ def setup_class(self):
+ m_electron = Molecule().from_smiles("e")
+ m_proton = Molecule().from_smiles("[H+]")
+ m_x = Molecule().from_adjacency_list("1 X u0 p0")
+ m_ch2x = Molecule().from_adjacency_list(
+ """
+ 1 C u0 p0 c0 {2,S} {3,S} {4,D}
+ 2 H u0 p0 c0 {1,S}
+ 3 H u0 p0 c0 {1,S}
+ 4 X u0 p0 c0 {1,D}
+ """
+ )
+ m_ch3x = Molecule().from_adjacency_list(
+ """
+ 1 C u0 p0 c0 {2,S} {3,S} {4,S} {5,S}
+ 2 H u0 p0 c0 {1,S}
+ 3 H u0 p0 c0 {1,S}
+ 4 H u0 p0 c0 {1,S}
+ 5 X u0 p0 c0 {1,S}
+ """
+ )
+
+ s_electron = Species(
+ molecule=[m_electron],
+ thermo=ThermoData(Tdata=([300, 400, 500, 600, 800, 1000, 1500, 2000],
+ "K"),
+ Cpdata=([0., 0., 0., 0., 0., 0., 0., 0.], "cal/(mol*K)"),
+ H298=(0.0, "kcal/mol"),
+ S298=(0.0, "cal/(mol*K)")))
+
+ s_proton = Species(
+ molecule=[m_proton],
+ thermo = ThermoData(
+ Tdata=([300,400,500,600,800,1000,1500],'K'),
+ Cpdata=([3.4475,3.4875,3.497,3.5045,3.5405,3.6095,3.86],'cal/(mol*K)'),
+ H298=(0,'kcal/mol'), S298=(15.6165,'cal/(mol*K)','+|-',0.0007),
+ comment = '1/2 free energy of H2(g)'))
+ s_x = Species(
+ molecule=[m_x],
+ thermo=ThermoData(Tdata=([300, 400, 500, 600, 800, 1000, 1500, 2000],
+ "K"),
+ Cpdata=([0., 0., 0., 0., 0., 0., 0., 0.], "cal/(mol*K)"),
+ H298=(0.0, "kcal/mol"),
+ S298=(0.0, "cal/(mol*K)")))
+
+ s_ch2x = Species(
+ molecule=[m_ch2x],
+ thermo=ThermoData(Tdata=([300,400,500,600,800,1000,1500],'K'),
+ Cpdata=([28.4959,36.3588,42.0219,46.2428,52.3978,56.921,64.1119],'J/(mol*K)'),
+ H298=(0.654731,'kJ/mol'), S298=(19.8341,'J/(mol*K)'), Cp0=(0.01,'J/(mol*K)'),
+ CpInf=(99.7737,'J/(mol*K)'),
+ comment="""Thermo library: surfaceThermoPt111 Binding energy corrected by LSR (0.50C)"""))
+
+ s_ch3x = Species(
+ molecule=[m_ch3x],
+ thermo=ThermoData(Tdata=([300,400,500,600,800,1000,1500],'K'),
+ Cpdata=([37.3325,44.9406,51.3613,56.8115,65.537,72.3287,83.3007],'J/(mol*K)'),
+ H298=(-45.8036,'kJ/mol'), S298=(57.7449,'J/(mol*K)'), Cp0=(0.01,'J/(mol*K)'),
+ CpInf=(124.717,'J/(mol*K)'),
+ comment="""Thermo library: surfaceThermoPt111 Binding energy corrected by LSR (0.25C)"""))
+
+ # X=CH2 + H+ + e- <--> X-CH3
+ rxn_reduction = Reaction(reactants=[s_proton, s_ch2x],
+ products=[s_ch3x],
+ electrons = -1,
+ kinetics=SurfaceChargeTransfer(
+ A = (2.483E21, 'cm^3/(mol*s)'),
+ V0 = (0, 'V'),
+ Ea = (10, 'kJ/mol'),
+ Tmin = (200, 'K'),
+ Tmax = (3000, 'K'),
+ electrons = -1,
+ ))
+
+ rxn_oxidation = Reaction(products=[s_proton, s_ch2x],
+ reactants=[s_ch3x],
+ electrons = 1,
+ kinetics=SurfaceChargeTransfer(
+ A = (2.483E21, 'cm^3/(mol*s)'),
+ V0 = (0, 'V'),
+ Ea = (10, 'kJ/mol'),
+ Tmin = (200, 'K'),
+ Tmax = (3000, 'K'),
+ electrons = 1,
+ ))
+
+ self.rxn_reduction = rxn_reduction
+ self.rxn_oxidation = rxn_oxidation
+
+ def test_electrons(self):
+ """Test electrons property"""
+ assert self.rxn_reduction.electrons == -1
+ assert self.rxn_oxidation.electrons == 1
+
+ def test_protons(self):
+ """Test n_protons property"""
+ assert self.rxn_reduction.protons == -1
+ assert self.rxn_oxidation.protons == 1
+
+ def test_is_surface_reaction(self):
+ """Test is_surface_reaction() method"""
+ assert self.rxn_reduction.is_surface_reaction()
+ assert self.rxn_oxidation.is_surface_reaction()
+
+ def test_is_charge_transfer_reaction(self):
+ """Test is_charge_transfer_reaction() method"""
+ assert self.rxn_reduction.is_charge_transfer_reaction()
+ assert self.rxn_oxidation.is_charge_transfer_reaction()
+
+ def test_is_surface_charge_transfer(self):
+ """Test is_surface_charge_transfer() method"""
+ assert self.rxn_reduction.is_surface_charge_transfer_reaction()
+ assert self.rxn_oxidation.is_surface_charge_transfer_reaction()
+
+ def test_get_reversible_potential(self):
+ """Test get_reversible_potential() method"""
+ V0_reduction = self.rxn_reduction.get_reversible_potential(298)
+ V0_oxidation = self.rxn_oxidation.get_reversible_potential(298)
+
+ assert abs(V0_reduction - V0_oxidation) < 0.000001
+ assert abs(V0_reduction - 0.3967918) < 0.000001
+ assert abs(V0_oxidation - 0.3967918) < 0.000001
+
+ def test_get_rate_coeff(self):
+ """Test get_rate_coefficient() method"""
+
+ # these should be the same
+ kf_1 = self.rxn_reduction.get_rate_coefficient(298,potential=0)
+ kf_2 = self.rxn_reduction.kinetics.get_rate_coefficient(298,0)
+
+ assert abs(kf_1 - 43870506959779.0) < 0.000001
+ assert abs(kf_1 - kf_2) < 0.000001
+
+ #kf_2 should be greater than kf_1
+ kf_1 = self.rxn_oxidation.get_rate_coefficient(298,potential=0)
+ kf_2 = self.rxn_oxidation.get_rate_coefficient(298,potential=0.1)
+ assert kf_2>kf_1
+
+ def test_equilibrium_constant_surface_charge_transfer_kc(self):
+ """
+ Test the equilibrium constants of type Kc of a surface charge transfer reaction.
+ """
+ Tlist = numpy.arange(400.0, 1600.0, 200.0, numpy.float64)
+ Kclist0 = [1.39365463e+03, 1.78420988e+01, 2.10543835e+00, 6.07529099e-01,
+ 2.74458007e-01, 1.59985450e-01] #reduction
+ Kclist_reduction = self.rxn_reduction.get_equilibrium_constants(Tlist, type='Kc')
+ Kclist_oxidation = self.rxn_oxidation.get_equilibrium_constants(Tlist, type='Kc')
+ # Test a range of temperatures
+ for i in range(len(Tlist)):
+ assert abs(Kclist_reduction[i] / Kclist0[i] - 1.0) < 0.000001
+ assert abs(1 / Kclist_oxidation[i] / Kclist0[i] - 1.0) < 0.000001
+
+ V0 = self.rxn_oxidation.get_reversible_potential(298)
+ Kc_reduction_equil = self.rxn_reduction.get_equilibrium_constant(298, V0)
+ Kc_oxidation_equil = self.rxn_oxidation.get_equilibrium_constant(298, V0)
+ assert abs(Kc_oxidation_equil * Kc_reduction_equil - 1.0) < 0.0001
+ C0 = 1e5 / constants.R / 298
+ assert abs(Kc_oxidation_equil - C0) < 0.0001
+ assert abs(Kc_reduction_equil - 1/C0) < 0.0001
+
+ @pytest.mark.skip("Work in progress")
+ def test_reverse_surface_charge_transfer_rate(self):
+ """
+ Test the reverse_surface_charge_transfer_rate() method
+ """
+ kf_reduction = self.rxn_reduction.kinetics
+ kf_oxidation = self.rxn_oxidation.kinetics
+ kr_oxidation = self.rxn_oxidation.generate_reverse_rate_coefficient()
+ kr_reduction = self.rxn_reduction.generate_reverse_rate_coefficient()
+ assert kr_reduction.A.units == 's^-1'
+ assert kr_oxidation.A.units == 'm^3/(mol*s)'
+
+ Tlist = numpy.linspace(298., 500., 30.,numpy.float64)
+ for T in Tlist:
+ for V in (-0.25,0.,0.25):
+ kf = kf_reduction.get_rate_coefficient(T,V)
+ kr = kr_reduction.get_rate_coefficient(T,V)
+ K = self.rxn_reduction.get_equilibrium_constant(T,V)
+ assert order_of_magnitude(kf/kr) == order_of_magnitude(K)
+ kf = kf_oxidation.get_rate_coefficient(T,V)
+ kr = kr_oxidation.get_rate_coefficient(T,V)
+ K = self.rxn_oxidation.get_equilibrium_constant(T,V)
+ assert order_of_magnitude(kf/kr) == order_of_magnitude(K)
diff --git a/test/rmgpy/test_data/testing_database/kinetics/families/Surface_Proton_Electron_Reduction_Alpha/groups.py b/test/rmgpy/test_data/testing_database/kinetics/families/Surface_Proton_Electron_Reduction_Alpha/groups.py
new file mode 100644
index 0000000000..656846b7f4
--- /dev/null
+++ b/test/rmgpy/test_data/testing_database/kinetics/families/Surface_Proton_Electron_Reduction_Alpha/groups.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+name = "Surface_Proton_Electron_Reduction_Alpha/groups"
+shortDesc = u""
+longDesc = u"""
+
+ *1 *1-*3H
+ || + *3H+ + *e- ----> |
+ ~*2~ ~*2~~
+
+The rate, which should be in mol/m2/s,
+will be given by k * (mol/m2) * (mol/m3) * 1
+so k should be in (m3/mol/s).
+"""
+
+template(reactants=["Adsorbate", "Proton"], products=["Reduced"], ownReverse=False)
+
+reverse = "Surface_Proton_Electron_Oxidation_Alpha"
+
+reactantNum = 2
+productNum = 1
+allowChargedSpecies = True
+electrons = -1
+
+recipe(actions=[
+ ['LOSE_CHARGE', '*3', 1],
+ ['CHANGE_BOND', '*1', -1, '*2'],
+ ['FORM_BOND', '*1', 1, '*3'],
+])
+
+entry(
+ index = 1,
+ label = "Adsorbate",
+ group =
+"""
+1 *1 R!H u0 {2,[D,T,Q]}
+2 *2 X u0 {1,[D,T,Q]}
+""",
+ kinetics = None,
+)
+
+entry(
+ index = 2,
+ label = "Proton",
+ group =
+"""
+1 *3 H+ u0 p0 c+1
+""",
+ kinetics = None,
+)
+
+entry(
+ index = 4,
+ label = "CX",
+ group =
+"""
+1 *1 C u0 {2,[D,T,Q]}
+2 *2 X u0 {1,[D,T,Q]}
+""",
+ kinetics = None,
+)
+
+entry(
+ index = 5,
+ label = "CTX",
+ group =
+"""
+1 *1 C u0 {2,T}
+2 *2 X u0 {1,T}
+""",
+ kinetics = None,
+)
+
+entry(
+ index = 6,
+ label = "HCX",
+ group =
+"""
+1 *1 C u0 {2,T} {3,S}
+2 *2 X u0 {1,T}
+3 H u0 {1,S}
+""",
+ kinetics = None,
+)
+
+entry(
+ index = 7,
+ label = "C=X",
+ group =
+"""
+1 *1 C u0 {2,D}
+2 *2 X u0 {1,D}
+""",
+ kinetics = None,
+)
+
+entry(
+ index = 8,
+ label = "H2C=X",
+ group =
+"""
+1 *1 C u0 {2,D} {3,S} {4,S}
+2 *2 X u0 {1,D}
+3 H u0 {1,S}
+4 H u0 {1,S}
+""",
+ kinetics = None,
+)
+
+entry(
+ index = 9,
+ label = "O=C=X",
+ group =
+"""
+1 *1 C u0 {2,D} {3,D}
+2 *2 X u0 {1,D}
+3 O2d u0 {1,D}
+""",
+ kinetics = None,
+)
+
+
+entry(
+ index = 10,
+ label = "OX",
+ group =
+"""
+1 *1 O u0 {2,D}
+2 *2 X u0 {1,D}
+""",
+ kinetics = None,
+)
+
+
+entry(
+ index = 11,
+ label = "NX",
+ group =
+"""
+1 *1 N u0 {2,[D,T]}
+2 *2 X u0 {1,[D,T]}
+""",
+ kinetics = None,
+)
+
+entry(
+ index = 12,
+ label = "NTX",
+ group =
+"""
+1 *1 N u0 {2,T}
+2 *2 X u0 {1,T}
+""",
+ kinetics = None,
+)
+
+entry(
+ index = 13,
+ label = "N=X",
+ group =
+"""
+1 *1 N u0 {2,D}
+2 *2 X u0 {1,D}
+""",
+ kinetics = None,
+)
+
+entry(
+ index = 14,
+ label = "HN=X",
+ group =
+"""
+1 *1 N u0 {2,D} {3,S}
+2 *2 X u0 {1,D}
+3 N u0 {1,S}
+""",
+ kinetics = None,
+)
+
+entry(
+ index = 15,
+ label = "N-N=X",
+ group =
+"""
+1 *1 N u0 {2,D} {3,S}
+2 *2 X u0 {1,D}
+3 N u0 {1,S}
+""",
+ kinetics = None,
+)
+
+tree(
+"""
+L1: Adsorbate
+ L2: CX
+ L3: CTX
+ L4: HCX
+ L3: C=X
+ L4: O=C=X
+ L4: H2C=X
+ L2: OX
+ L2: NX
+ L3: NTX
+ L3: N=X
+ L4: HN=X
+ L4: N-N=X
+
+L1: Proton
+"""
+)
diff --git a/test/rmgpy/test_data/testing_database/kinetics/families/Surface_Proton_Electron_Reduction_Alpha/rules.py b/test/rmgpy/test_data/testing_database/kinetics/families/Surface_Proton_Electron_Reduction_Alpha/rules.py
new file mode 100644
index 0000000000..67a06429f2
--- /dev/null
+++ b/test/rmgpy/test_data/testing_database/kinetics/families/Surface_Proton_Electron_Reduction_Alpha/rules.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+name = "Surface_Proton_Electron_Reduction_Alpha/rules"
+shortDesc = u""
+longDesc = u"""
+Surface adsorption of a single radical forming a single bond to the surface site
+"""
+
+# entry(
+# index = 1,
+# label = "Adsorbate;Proton;Electron",
+# kinetics = SurfaceChargeTransfer(
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff, 0 default
+# V0 = None, # Reference potential
+# Ea = (15, 'kJ/mol'), # activation energy at the reversible potential
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# rank = 0,
+# shortDesc = u"""Default""",
+# longDesc = u"""https://doi.org/10.1021/jp4100608"""
+# )
+
diff --git a/test/rmgpy/test_data/testing_database/kinetics/families/Surface_Proton_Electron_Reduction_Alpha/training/dictionary.txt b/test/rmgpy/test_data/testing_database/kinetics/families/Surface_Proton_Electron_Reduction_Alpha/training/dictionary.txt
new file mode 100644
index 0000000000..c4d8a3f3e8
--- /dev/null
+++ b/test/rmgpy/test_data/testing_database/kinetics/families/Surface_Proton_Electron_Reduction_Alpha/training/dictionary.txt
@@ -0,0 +1,64 @@
+H
+1 *3 H u0 p0 c+1
+
+NX
+1 *1 N u0 p1 c0 {2,T}
+2 *2 X u0 p0 c0 {1,T}
+
+HNX
+1 *1 N u0 p1 c0 {2,D} {3,S}
+2 *2 X u0 p0 c0 {1,D}
+3 H u0 p0 c0 {1,S}
+
+HNX_p
+1 *1 N u0 p1 c0 {2,D} {3,S}
+2 *2 X u0 p0 c0 {1,D}
+3 *3 H u0 p0 c0 {1,S}
+
+H2NX
+1 *1 N u0 p1 c0 {2,S} {3,S} {4,S}
+2 *2 X u0 p0 c0 {1,S}
+3 *3 H u0 p0 c0 {1,S}
+4 H u0 p0 c0 {1,S}
+
+CX
+1 *1 C u0 p0 c0 {2,Q}
+2 *2 X u0 p0 c0 {1,Q}
+
+CHX
+1 *1 C u0 p0 c0 {2,T} {3,S}
+2 *2 X u0 p0 c0 {1,T}
+3 H u0 p0 c0 {1,S}
+
+CHX_p
+1 *1 C u0 p0 c0 {2,T} {3,S}
+2 *2 X u0 p0 c0 {1,T}
+3 *3 H u0 p0 c0 {1,S}
+
+CH2X
+1 *1 C u0 p0 c0 {2,D} {3,S} {4,S}
+2 *2 X u0 p0 c0 {1,D}
+3 H u0 p0 c0 {1,S}
+4 H u0 p0 c0 {1,S}
+
+CH2X_p
+1 *1 C u0 p0 c0 {2,D} {3,S} {4,S}
+2 *2 X u0 p0 c0 {1,D}
+3 *3 H u0 p0 c0 {1,S}
+4 H u0 p0 c0 {1,S}
+
+CH3X
+1 *1 C u0 p0 c0 {2,S} {3,S} {4,S} {5,S}
+2 *2 X u0 p0 c0 {1,S}
+3 *3 H u0 p0 c0 {1,S}
+4 H u0 p0 c0 {1,S}
+5 H u0 p0 c0 {1,S}
+
+OX
+1 *1 O u0 p2 c0 {2,D}
+2 *2 X u0 p0 c0 {1,D}
+
+HOX
+1 *1 O u0 p2 c0 {2,S} {3,S}
+2 *2 X u0 p0 c0 {1,S}
+3 *3 H u0 p0 c0 {1,S}
\ No newline at end of file
diff --git a/test/rmgpy/test_data/testing_database/kinetics/families/Surface_Proton_Electron_Reduction_Alpha/training/reactions.py b/test/rmgpy/test_data/testing_database/kinetics/families/Surface_Proton_Electron_Reduction_Alpha/training/reactions.py
new file mode 100644
index 0000000000..3866fa74ac
--- /dev/null
+++ b/test/rmgpy/test_data/testing_database/kinetics/families/Surface_Proton_Electron_Reduction_Alpha/training/reactions.py
@@ -0,0 +1,516 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+name = "Surface_Proton_Electron_Reduction_Alpha/training"
+shortDesc = u"Reaction kinetics used to generate rate rules"
+longDesc = u"""
+Put kinetic parameters for specific reactions in this file to use as a
+training set for generating rate rules to populate this kinetics family.
+"""
+
+# entry(
+# index = 1,
+# label = "CX + H <=> CHX_p",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.20, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (0.24, 'V'), # reference potential
+# Ea = (0.61, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Tafel""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+entry(
+ index = 2,
+ label = "CX + H <=> CHX_p",
+ degeneracy = 1,
+ kinetics = SurfaceChargeTransfer(
+ alpha = 0.20, # charge transfer coeff
+ A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+ n = 0, # temperature coeff
+ V0 = (-0.5, 'V'), # reference potential
+ Ea = (0.44, 'eV/molecule'), # activation energy
+ Tmin = (200, 'K'),
+ Tmax = (3000, 'K'),
+ electrons = -1, # electron stochiometric coeff
+ ),
+ shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+ longDesc = u"""Tafel""",
+ metal = "Pt",
+ facet = "111",
+ site = "",
+ rank = 5,
+)
+
+# entry(
+# index = 1,
+# label = "CX + H <=> CHX_p",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.06, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (0.29, 'V'), # reference potential
+# Ea = (0.19, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Heyrovsky""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+# entry(
+# index = 1,
+# label = "CX + H <=> CHX_p",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.06, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (-0.5, 'V'), # reference potential
+# Ea = (0.13, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Heyrovsky""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+# entry(
+# index = 3,
+# label = "CHX + H <=> CH2X_p",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.31, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (0.32, 'V'), # reference potential
+# Ea = (0.77, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Tafel""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+entry(
+ index = 3,
+ label = "CHX + H <=> CH2X_p",
+ degeneracy = 1,
+ kinetics = SurfaceChargeTransfer(
+ alpha = 0.31, # charge transfer coeff
+ A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+ n = 0, # temperature coeff
+ V0 = (-0.5, 'V'), # reference potential
+ Ea = (0.44, 'eV/molecule'), # activation energy
+ Tmin = (200, 'K'),
+ Tmax = (3000, 'K'),
+ electrons = -1, # electron stochiometric coeff
+ ),
+ shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+ longDesc = u"""Tafel""",
+ metal = "Pt",
+ facet = "111",
+ site = "",
+ rank = 5,
+)
+
+# entry(
+# index = 3,
+# label = "CHX + H <=> CH2X_p",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.05, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (0.30, 'V'), # reference potential
+# Ea = (0.59, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Heyrovsky""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+# entry(
+# index = 4,
+# label = "CHX + H <=> CH2X_p",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.05, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (-0.5, 'V'), # reference potential
+# Ea = (0.53, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Heyrovsky""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+# entry(
+# index = 6,
+# label = "CH2X + H <=> CH3X",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.23, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (0.38, 'V'), # reference potential
+# Ea = (0.62, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Tafel""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+entry(
+ index = 4,
+ label = "CH2X + H <=> CH3X",
+ degeneracy = 1,
+ kinetics = SurfaceChargeTransfer(
+ alpha = 0.23, # charge transfer coeff
+ A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+ n = 0, # temperature coeff
+ V0 = (-0.5, 'V'), # reference potential
+ Ea = (0.37, 'eV/molecule'), # activation energy
+ Tmin = (200, 'K'),
+ Tmax = (3000, 'K'),
+ electrons = -1, # electron stochiometric coeff
+ ),
+ shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+ longDesc = u"""Tafel""",
+ metal = "Pt",
+ facet = "111",
+ site = "",
+ rank = 5,
+)
+
+# entry(
+# index = 8,
+# label = "NX + H <=> HNX",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.07, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (-0.12, 'V'), # reference potential
+# Ea = (0.15, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2017.01.050""",
+# longDesc = u"""
+# """,
+# metal = "Cu",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+# entry(
+# index = 9,
+# label = "NX + H <=> HNX_p",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.23, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (0.24, 'V'), # reference potential
+# Ea = (0.78, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Tafel""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+entry(
+ index = 5,
+ label = "NX + H <=> HNX_p",
+ degeneracy = 1,
+ kinetics = SurfaceChargeTransfer(
+ alpha = 0.23, # charge transfer coeff
+ A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+ n = 0, # temperature coeff
+ V0 = (-0.5, 'V'), # reference potential
+ Ea = (0.59, 'eV/molecule'), # activation energy
+ Tmin = (200, 'K'),
+ Tmax = (3000, 'K'),
+ electrons = -1, # electron stochiometric coeff
+ ),
+ shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+ longDesc = u"""Tafel""",
+ metal = "Pt",
+ facet = "111",
+ site = "",
+ rank = 5,
+)
+
+# entry(
+# index = 10,
+# label = "NX + H <=> HNX_p",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.07, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (0.3, 'V'), # reference potential
+# Ea = (0.17, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Heyrovsky""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+# entry(
+# index = 4,
+# label = "NX + H <=> HNX_p",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.07, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (-0.5, 'V'), # reference potential
+# Ea = (0.09, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Heyrovsky""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+# entry(
+# index = 11,
+# label = "HNX + H <=> H2NX",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.27, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (0.28, 'V'), # reference potential
+# Ea = (1.20, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Tafel""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+entry(
+ index = 6,
+ label = "HNX + H <=> H2NX",
+ degeneracy = 1,
+ kinetics = SurfaceChargeTransfer(
+ alpha = 0.27, # charge transfer coeff
+ A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+ n = 0, # temperature coeff
+ V0 = (-0.5, 'V'), # reference potential
+ Ea = (0.97, 'eV/molecule'), # activation energy
+ Tmin = (200, 'K'),
+ Tmax = (3000, 'K'),
+ electrons = -1, # electron stochiometric coeff
+ ),
+ shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+ longDesc = u"""Tafel""",
+ metal = "Pt",
+ facet = "111",
+ site = "",
+ rank = 5,
+)
+
+# entry(
+# index = 11,
+# label = "HNX + H <=> H2NX",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.64, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (0.31, 'V'), # reference potential
+# Ea = (0.99, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Heyrovsky""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+# entry(
+# index = 5,
+# label = "HNX + H <=> H2NX",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.64, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (-0.5, 'V'), # reference potential
+# Ea = (0.43, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Heyrovsky""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+# entry(
+# index = 12,
+# label = "OX + H <=> HOX",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.41, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (0.31, 'V'), # reference potential
+# Ea = (0.87, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Tafel""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+entry(
+ index = 7,
+ label = "OX + H <=> HOX",
+ degeneracy = 1,
+ kinetics = SurfaceChargeTransfer(
+ alpha = 0.42, # charge transfer coeff
+ A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+ n = 0, # temperature coeff
+ V0 = (-0.5, 'V'), # reference potential
+ Ea = (0.48, 'eV/molecule'), # activation energy
+ Tmin = (200, 'K'),
+ Tmax = (3000, 'K'),
+ electrons = -1, # electron stochiometric coeff
+ ),
+ shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+ longDesc = u"""Tafel""",
+ metal = "Pt",
+ facet = "111",
+ site = "",
+ rank = 5,
+)
+
+# entry(
+# index = 12,
+# label = "OX + H <=> HOX",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.02, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (0.57, 'V'), # reference potential
+# Ea = (0.06, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Heyrovsky""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
+
+# entry(
+# index = 6,
+# label = "OX + H <=> HOX",
+# degeneracy = 1,
+# kinetics = SurfaceChargeTransfer(
+# alpha = 0.02, # charge transfer coeff
+# A = (2.5e14, 'm^3/(mol*s)'), # pre-exponential factor estimate 10^11 s^-1 * 2.5e5 m^2/mol / 1000 m^3/mol H+
+# n = 0, # temperature coeff
+# V0 = (-0.5, 'V'), # reference potential
+# Ea = (0.03, 'eV/molecule'), # activation energy
+# Tmin = (200, 'K'),
+# Tmax = (3000, 'K'),
+# electrons = -1, # electron stochiometric coeff
+# ),
+# shortDesc = u"""https://doi.org/10.1016/j.cattod.2018.03.048""",
+# longDesc = u"""Heyrovsky""",
+# metal = "Pt",
+# facet = "111",
+# site = "",
+# rank = 5,
+# )
diff --git a/test/rmgpy/test_data/testing_database/thermo/groups/adsorptionLi.py b/test/rmgpy/test_data/testing_database/thermo/groups/adsorptionLi.py
new file mode 100644
index 0000000000..4b47339761
--- /dev/null
+++ b/test/rmgpy/test_data/testing_database/thermo/groups/adsorptionLi.py
@@ -0,0 +1,52 @@
+name = "Surface Adsorption Corrections"
+shortDesc = ""
+longDesc = """
+Changes due to adsorbing on a surface.
+Here, Pt(111)
+Note: "-h" means "horizontal".
+"""
+
+entry(
+ index = 1,
+ label = "R*",
+ group=
+"""
+1 R u0
+2 X u0
+""",
+ thermo=None,
+ shortDesc="""Anything adsorbed anyhow.""",
+ longDesc="""
+ R
+ x
+***********
+This node should be empty, ensuring that one of the nodes below is used.
+""",
+ metal = "Pt",
+ facet = "111",
+)
+
+entry(
+ index = 1,
+ label = "R-*",
+ group =
+"""
+1 X u0 p0 c0 {2,S}
+2 R u0 p0 c0 {1,S}
+""",
+ thermo=ThermoData(
+ Tdata=([300, 400, 500, 600, 800, 1000, 1500], 'K'),
+ Cpdata=([-3.01, -1.78, -0.96, -0.41, 0.23, 0.56, 0.91], 'cal/(mol*K)'),
+ H298=(-86.29, 'kcal/mol'),
+ S298=(-26.39, 'cal/(mol*K)'),
+ ),
+ shortDesc="""Came from H single-bonded on Pt(111)""",
+ longDesc="""Calculated by Katrin Blondal at Brown University using statistical mechanics (files: compute_NASA_for_Pt-adsorbates.ipynb and compute_NASA_for_Pt-gas_phase.ipynb). Based on DFT calculations by Jelena Jelic at KIT.
+
+ R
+ |
+***********
+""",
+ metal = "Pt",
+ facet = "111",
+)