|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +############################################################################### |
| 4 | +# # |
| 5 | +# RMG - Reaction Mechanism Generator # |
| 6 | +# # |
| 7 | +# Copyright (c) 2002-2023 Prof. William H. Green (whgreen@mit.edu), # |
| 8 | +# Prof. Richard H. West (r.west@neu.edu) and the RMG Team (rmg_dev@mit.edu) # |
| 9 | +# # |
| 10 | +# Permission is hereby granted, free of charge, to any person obtaining a # |
| 11 | +# copy of this software and associated documentation files (the 'Software'), # |
| 12 | +# to deal in the Software without restriction, including without limitation # |
| 13 | +# the rights to use, copy, modify, merge, publish, distribute, sublicense, # |
| 14 | +# and/or sell copies of the Software, and to permit persons to whom the # |
| 15 | +# Software is furnished to do so, subject to the following conditions: # |
| 16 | +# # |
| 17 | +# The above copyright notice and this permission notice shall be included in # |
| 18 | +# all copies or substantial portions of the Software. # |
| 19 | +# # |
| 20 | +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # |
| 21 | +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # |
| 22 | +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # |
| 23 | +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # |
| 24 | +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # |
| 25 | +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # |
| 26 | +# DEALINGS IN THE SOFTWARE. # |
| 27 | +# # |
| 28 | +############################################################################### |
| 29 | + |
| 30 | +""" |
| 31 | +This module contains unit tests of the :mod:`arkane.input` module. |
| 32 | +""" |
| 33 | + |
| 34 | +import os |
| 35 | + |
| 36 | +from rmgpy.pdep.collision import SingleExponentialDown |
| 37 | +import pytest |
| 38 | + |
| 39 | +import rmgpy |
| 40 | +from rmgpy.exceptions import InputError |
| 41 | +from rmgpy.kinetics.tunneling import Eckart |
| 42 | +from rmgpy.statmech.rotation import NonlinearRotor |
| 43 | +from rmgpy.statmech.translation import IdealGasTranslation |
| 44 | +from rmgpy.statmech.vibration import HarmonicOscillator |
| 45 | +from rmgpy.thermo.nasa import NASAPolynomial, NASA |
| 46 | +from rmgpy.transport import TransportData |
| 47 | + |
| 48 | +from arkane.input import ( |
| 49 | + species, |
| 50 | + transitionState, |
| 51 | + reaction, |
| 52 | + SMILES, |
| 53 | + load_input_file, |
| 54 | + process_model_chemistry, |
| 55 | +) |
| 56 | +from arkane.modelchem import LevelOfTheory, CompositeLevelOfTheory |
| 57 | + |
| 58 | + |
| 59 | +ADMONITION = ( |
| 60 | + "This unit test fails in the new pytest framework despite other similar tests passing. " |
| 61 | + "This is likely due to a global state issue that was implicitly handled differently by the old nose testing " |
| 62 | + "framework. \nThe best solution for this problem is to remove the abuse of the global " |
| 63 | + "state in Arkane and RMG-Py rather than try and fix this single test." |
| 64 | +) |
| 65 | + |
| 66 | + |
| 67 | +class FunctionalInputTest: |
| 68 | + """ |
| 69 | + Contains unit tests for the Arkane input module |
| 70 | + """ |
| 71 | + |
| 72 | + def test_species(self): |
| 73 | + """ |
| 74 | + Test loading a species from input file-like kew word arguments |
| 75 | + """ |
| 76 | + label0 = "CH2O" |
| 77 | + kwargs = { |
| 78 | + "E0": (28.69, "kcal/mol"), |
| 79 | + "structure": SMILES("C=O"), |
| 80 | + "collisionModel": TransportData(sigma=(3.69e-10, "m"), epsilon=(4.0, "kJ/mol")), |
| 81 | + "energyTransferModel": SingleExponentialDown(alpha0=(0.956, "kJ/mol"), T0=(300, "K"), n=0.95), |
| 82 | + "spinMultiplicity": 1, |
| 83 | + "opticalIsomers": 1, |
| 84 | + "modes": [ |
| 85 | + HarmonicOscillator(frequencies=([1180, 1261, 1529, 1764, 2931, 2999], "cm^-1")), |
| 86 | + NonlinearRotor( |
| 87 | + rotationalConstant=( |
| 88 | + [1.15498821005263, 1.3156969584727, 9.45570474524524], |
| 89 | + "cm^-1", |
| 90 | + ), |
| 91 | + symmetry=2, |
| 92 | + quantum=False, |
| 93 | + ), |
| 94 | + IdealGasTranslation(mass=(30.0106, "g/mol")), |
| 95 | + ], |
| 96 | + } |
| 97 | + |
| 98 | + spc0 = species(label0, **kwargs) |
| 99 | + assert spc0.label == "CH2O" |
| 100 | + assert spc0.smiles == "C=O" |
| 101 | + assert round(abs(spc0.conformer.E0.value_si - 120038.96), 7) == 0 |
| 102 | + assert spc0.conformer.spin_multiplicity == 1 |
| 103 | + assert spc0.conformer.optical_isomers == 1 |
| 104 | + assert len(spc0.conformer.modes) == 3 |
| 105 | + assert isinstance(spc0.transport_data, TransportData) |
| 106 | + assert isinstance(spc0.energy_transfer_model, SingleExponentialDown) |
| 107 | + |
| 108 | + @pytest.mark.skip(reason=ADMONITION) |
| 109 | + def test_species_atomic_nasa_polynomial(self): |
| 110 | + """ |
| 111 | + Test loading a atom with NASA polynomials |
| 112 | + """ |
| 113 | + label0 = "H(1)" |
| 114 | + kwargs = { |
| 115 | + "structure": SMILES("[H]"), |
| 116 | + "thermo": NASA( |
| 117 | + polynomials=[ |
| 118 | + NASAPolynomial( |
| 119 | + coeffs=[2.5, 0, 0, 0, 0, 25473.7, -0.446683], |
| 120 | + Tmin=(200, "K"), |
| 121 | + Tmax=(1000, "K"), |
| 122 | + ), |
| 123 | + NASAPolynomial( |
| 124 | + coeffs=[2.5, 0, 0, 0, 0, 25473.7, -0.446683], |
| 125 | + Tmin=(1000, "K"), |
| 126 | + Tmax=(6000, "K"), |
| 127 | + ), |
| 128 | + ], |
| 129 | + Tmin=(200, "K"), |
| 130 | + Tmax=(6000, "K"), |
| 131 | + comment="""Thermo library: FFCM1(-)""", |
| 132 | + ), |
| 133 | + "energyTransferModel": SingleExponentialDown(alpha0=(3.5886, "kJ/mol"), T0=(300, "K"), n=0.85), |
| 134 | + } |
| 135 | + spc0 = species(label0, **kwargs) |
| 136 | + assert spc0.label == label0 |
| 137 | + assert spc0.smiles == "[H]" |
| 138 | + assert spc0.has_statmech() |
| 139 | + assert spc0.thermo == kwargs["thermo"] |
| 140 | + |
| 141 | + @pytest.mark.skip(reason=ADMONITION) |
| 142 | + def test_species_polyatomic_nasa_polynomial(self): |
| 143 | + """ |
| 144 | + Test loading a species with NASA polynomials |
| 145 | + """ |
| 146 | + label0 = "benzyl" |
| 147 | + kwargs = { |
| 148 | + "structure": SMILES("[c]1ccccc1"), |
| 149 | + "thermo": NASA( |
| 150 | + polynomials=[ |
| 151 | + NASAPolynomial( |
| 152 | + coeffs=[ |
| 153 | + 2.78632, |
| 154 | + 0.00784632, |
| 155 | + 7.97887e-05, |
| 156 | + -1.11617e-07, |
| 157 | + 4.39429e-11, |
| 158 | + 39695, |
| 159 | + 11.5114, |
| 160 | + ], |
| 161 | + Tmin=(100, "K"), |
| 162 | + Tmax=(943.73, "K"), |
| 163 | + ), |
| 164 | + NASAPolynomial( |
| 165 | + coeffs=[ |
| 166 | + 13.2455, |
| 167 | + 0.0115667, |
| 168 | + -2.49996e-06, |
| 169 | + 4.66496e-10, |
| 170 | + -4.12376e-14, |
| 171 | + 35581.1, |
| 172 | + -49.6793, |
| 173 | + ], |
| 174 | + Tmin=(943.73, "K"), |
| 175 | + Tmax=(5000, "K"), |
| 176 | + ), |
| 177 | + ], |
| 178 | + Tmin=(100, "K"), |
| 179 | + Tmax=(5000, "K"), |
| 180 | + comment="""Thermo library: Fulvene_H + radical(CbJ)""", |
| 181 | + ), |
| 182 | + "energyTransferModel": SingleExponentialDown(alpha0=(3.5886, "kJ/mol"), T0=(300, "K"), n=0.85), |
| 183 | + } |
| 184 | + spc0 = species(label0, **kwargs) |
| 185 | + assert spc0.label == label0 |
| 186 | + assert spc0.has_statmech() |
| 187 | + assert spc0.thermo == kwargs["thermo"] |
| 188 | + |
| 189 | + def test_transition_state(self): |
| 190 | + """ |
| 191 | + Test loading a transition state from input file-like kew word arguments |
| 192 | + """ |
| 193 | + label0 = "TS1" |
| 194 | + kwargs = { |
| 195 | + "E0": (39.95, "kcal/mol"), |
| 196 | + "spinMultiplicity": 2, |
| 197 | + "opticalIsomers": 1, |
| 198 | + "frequency": (-1934, "cm^-1"), |
| 199 | + "modes": [ |
| 200 | + HarmonicOscillator( |
| 201 | + frequencies=( |
| 202 | + [792, 987, 1136, 1142, 1482, 2441, 3096, 3183], |
| 203 | + "cm^-1", |
| 204 | + ) |
| 205 | + ), |
| 206 | + NonlinearRotor( |
| 207 | + rotationalConstant=([0.928, 0.962, 5.807], "cm^-1"), |
| 208 | + symmetry=1, |
| 209 | + quantum=False, |
| 210 | + ), |
| 211 | + IdealGasTranslation(mass=(31.01843, "g/mol")), |
| 212 | + ], |
| 213 | + } |
| 214 | + |
| 215 | + ts0 = transitionState(label0, **kwargs) |
| 216 | + assert ts0.label == "TS1" |
| 217 | + assert round(abs(ts0.conformer.E0.value_si - 167150.8), 7) == 0 |
| 218 | + assert ts0.conformer.spin_multiplicity == 2 |
| 219 | + assert ts0.conformer.optical_isomers == 1 |
| 220 | + assert ts0.frequency.value_si == -1934.0 |
| 221 | + assert len(ts0.conformer.modes) == 3 |
| 222 | + |
| 223 | + def test_reaction(self): |
| 224 | + """ |
| 225 | + Test loading a reaction from input file-like kew word arguments |
| 226 | + """ |
| 227 | + |
| 228 | + species( |
| 229 | + label="methoxy", |
| 230 | + structure=SMILES("C[O]"), |
| 231 | + E0=(9.44, "kcal/mol"), |
| 232 | + modes=[ |
| 233 | + HarmonicOscillator( |
| 234 | + frequencies=( |
| 235 | + [758, 960, 1106, 1393, 1403, 1518, 2940, 3019, 3065], |
| 236 | + "cm^-1", |
| 237 | + ) |
| 238 | + ), |
| 239 | + NonlinearRotor( |
| 240 | + rotationalConstant=([0.916, 0.921, 5.251], "cm^-1"), |
| 241 | + symmetry=3, |
| 242 | + quantum=False, |
| 243 | + ), |
| 244 | + IdealGasTranslation(mass=(31.01843, "g/mol")), |
| 245 | + ], |
| 246 | + spinMultiplicity=2, |
| 247 | + opticalIsomers=1, |
| 248 | + molecularWeight=(31.01843, "amu"), |
| 249 | + collisionModel=TransportData(sigma=(3.69e-10, "m"), epsilon=(4.0, "kJ/mol")), |
| 250 | + energyTransferModel=SingleExponentialDown(alpha0=(0.956, "kJ/mol"), T0=(300, "K"), n=0.95), |
| 251 | + ) |
| 252 | + |
| 253 | + species( |
| 254 | + label="formaldehyde", |
| 255 | + E0=(28.69, "kcal/mol"), |
| 256 | + molecularWeight=(30.0106, "g/mol"), |
| 257 | + collisionModel=TransportData(sigma=(3.69e-10, "m"), epsilon=(4.0, "kJ/mol")), |
| 258 | + energyTransferModel=SingleExponentialDown(alpha0=(0.956, "kJ/mol"), T0=(300, "K"), n=0.95), |
| 259 | + spinMultiplicity=1, |
| 260 | + opticalIsomers=1, |
| 261 | + modes=[ |
| 262 | + HarmonicOscillator(frequencies=([1180, 1261, 1529, 1764, 2931, 2999], "cm^-1")), |
| 263 | + NonlinearRotor( |
| 264 | + rotationalConstant=( |
| 265 | + [1.15498821005263, 1.3156969584727, 9.45570474524524], |
| 266 | + "cm^-1", |
| 267 | + ), |
| 268 | + symmetry=2, |
| 269 | + quantum=False, |
| 270 | + ), |
| 271 | + IdealGasTranslation(mass=(30.0106, "g/mol")), |
| 272 | + ], |
| 273 | + ) |
| 274 | + |
| 275 | + species( |
| 276 | + label="H", |
| 277 | + E0=(0.000, "kcal/mol"), |
| 278 | + molecularWeight=(1.00783, "g/mol"), |
| 279 | + collisionModel=TransportData(sigma=(3.69e-10, "m"), epsilon=(4.0, "kJ/mol")), |
| 280 | + energyTransferModel=SingleExponentialDown(alpha0=(0.956, "kJ/mol"), T0=(300, "K"), n=0.95), |
| 281 | + modes=[IdealGasTranslation(mass=(1.00783, "g/mol"))], |
| 282 | + spinMultiplicity=2, |
| 283 | + opticalIsomers=1, |
| 284 | + ) |
| 285 | + |
| 286 | + transitionState( |
| 287 | + label="TS3", |
| 288 | + E0=(34.1, "kcal/mol"), |
| 289 | + spinMultiplicity=2, |
| 290 | + opticalIsomers=1, |
| 291 | + frequency=(-967, "cm^-1"), |
| 292 | + modes=[ |
| 293 | + HarmonicOscillator( |
| 294 | + frequencies=( |
| 295 | + [466, 581, 1169, 1242, 1499, 1659, 2933, 3000], |
| 296 | + "cm^-1", |
| 297 | + ) |
| 298 | + ), |
| 299 | + NonlinearRotor( |
| 300 | + rotationalConstant=([0.970, 1.029, 3.717], "cm^-1"), |
| 301 | + symmetry=1, |
| 302 | + quantum=False, |
| 303 | + ), |
| 304 | + IdealGasTranslation(mass=(31.01843, "g/mol")), |
| 305 | + ], |
| 306 | + ) |
| 307 | + |
| 308 | + reactants = ["formaldehyde", "H"] |
| 309 | + products = ["methoxy"] |
| 310 | + tunneling = "Eckart" |
| 311 | + |
| 312 | + rxn = reaction("CH2O+H=Methoxy", reactants, products, "TS3", tunneling=tunneling) |
| 313 | + assert rxn.label == "CH2O+H=Methoxy" |
| 314 | + assert len(rxn.reactants) == 2 |
| 315 | + assert len(rxn.products) == 1 |
| 316 | + assert round(abs(rxn.reactants[0].conformer.E0.value_si - 0), 7) == 0 |
| 317 | + assert round(abs(rxn.reactants[1].conformer.E0.value_si - 120038.96), 7) == 0 |
| 318 | + assert round(abs(rxn.products[0].conformer.E0.value_si - 39496.96), 7) == 0 |
| 319 | + assert round(abs(rxn.transition_state.conformer.E0.value_si - 142674.4), 7) == 0 |
| 320 | + assert round(abs(rxn.transition_state.frequency.value_si - -967.0), 7) == 0 |
| 321 | + assert isinstance(rxn.transition_state.tunneling, Eckart) |
| 322 | + |
| 323 | + def test_load_input_file(self): |
| 324 | + """Test loading an Arkane input file""" |
| 325 | + path = os.path.join( |
| 326 | + os.path.dirname(os.path.dirname(rmgpy.__file__)), |
| 327 | + "examples", |
| 328 | + "arkane", |
| 329 | + "networks", |
| 330 | + "acetyl+O2", |
| 331 | + "input.py", |
| 332 | + ) |
| 333 | + ( |
| 334 | + job_list, |
| 335 | + reaction_dict, |
| 336 | + species_dict, |
| 337 | + transition_state_dict, |
| 338 | + network_dict, |
| 339 | + model_chemistry, |
| 340 | + ) = load_input_file(path) |
| 341 | + |
| 342 | + assert len(job_list) == 1 |
| 343 | + |
| 344 | + assert len(reaction_dict) == 5 |
| 345 | + assert "entrance1" in reaction_dict |
| 346 | + assert "exit2" in reaction_dict |
| 347 | + |
| 348 | + assert len(species_dict) == 9 |
| 349 | + assert "acetyl" in species_dict |
| 350 | + assert "hydroperoxyl" in species_dict |
| 351 | + |
| 352 | + assert len(transition_state_dict) == 5 |
| 353 | + assert "entrance1" in transition_state_dict |
| 354 | + assert "isom1" in transition_state_dict |
| 355 | + |
| 356 | + assert len(network_dict) == 1 |
| 357 | + assert "acetyl + O2" in network_dict |
| 358 | + |
| 359 | + assert model_chemistry is None |
| 360 | + |
| 361 | + def test_process_model_chemistry(self): |
| 362 | + """ |
| 363 | + Test processing the model chemistry to derive the sp and freq levels |
| 364 | + """ |
| 365 | + mc = "ccsd(t)-f12a/aug-cc-pvtz//b3lyp/6-311++g(3df,3pd)" |
| 366 | + lot = process_model_chemistry(mc) |
| 367 | + assert isinstance(lot, CompositeLevelOfTheory) |
| 368 | + assert lot.energy == LevelOfTheory("ccsd(t)-f12a", "aug-cc-pvtz") |
| 369 | + assert lot.freq == LevelOfTheory("b3lyp", "6-311++g(3df,3pd)") |
| 370 | + |
| 371 | + mc = "b3lyp-d3/def2-tzvp" |
| 372 | + lot = process_model_chemistry(mc) |
| 373 | + assert isinstance(lot, LevelOfTheory) |
| 374 | + assert lot == LevelOfTheory("b3lyp-d3", "def2-tzvp") |
| 375 | + |
| 376 | + mc = "cbs-qb3" |
| 377 | + lot = process_model_chemistry(mc) |
| 378 | + assert isinstance(lot, LevelOfTheory) |
| 379 | + assert lot == LevelOfTheory("cbs-qb3") |
| 380 | + |
| 381 | + mc = LevelOfTheory("test") |
| 382 | + lot = process_model_chemistry(mc) |
| 383 | + assert mc is lot |
| 384 | + |
| 385 | + with pytest.raises(InputError): |
| 386 | + process_model_chemistry("CCSD(T)-F12a/aug-cc-pVTZ//CCSD(T)-F12a/aug-cc-pVTZ//B3LYP/6-311++G(3df,3pd)") |
0 commit comments