diff --git a/.conda/bld.bat b/.conda/bld.bat deleted file mode 100644 index 6de1123c89..0000000000 --- a/.conda/bld.bat +++ /dev/null @@ -1,7 +0,0 @@ -:: Install RMG -mingw32-make install - -:: lazy "install" of everything in our 'external' folder. -:: most of which should probably be elsewhere -mkdir %SP_DIR%\external -xcopy %SRC_DIR%\external %SP_DIR%\external /E /Y diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index b5e8023971..0000000000 --- a/.coveragerc +++ /dev/null @@ -1,35 +0,0 @@ -# .coveragerc to control coverage.py -[run] -plugins = Cython.Coverage -branch = True -source = - arkane - Arkane.py - rmgpy - rmg.py -omit = - *Test.py - */test_data/* - arkane/data/* - -[report] -show_missing = False -exclude_lines = - pragma: no cover - def __repr__ - if self.debug: - if settings.DEBUG - raise AssertionError - raise NotImplementedError - if 0: - if __name__ == .__main__.: -include = - arkane/* - rmgpy/* -omit = - *Test.py - */test_data/* - arkane/data/* - -[html] -directory = testing/coverage diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a13e807029..971ee7f7fb 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -65,11 +65,16 @@ jobs: run: shell: bash -l {0} steps: - - name: Checkout RMG-Py + - name: Checkout RMG-Py - Resuable Workflow + if: github.repository == 'ReactionMechanismGenerator/RMG-database' uses: actions/checkout@v3 with: repository: ReactionMechanismGenerator/RMG-Py + - name: Clone RMG-Py - RMG-Py + if: github.repository != 'ReactionMechanismGenerator/RMG-database' + uses: actions/checkout@v3 + # configures the mamba environment manager and builds the environment - name: Setup Mambaforge Python 3.7 uses: conda-incubator/setup-miniconda@v2 @@ -185,12 +190,9 @@ jobs: julia -e 'using Pkg; Pkg.add(PackageSpec(name="ReactionMechanismSimulator",rev="main")); using ReactionMechanismSimulator' || true # non-regression testing - - name: Unit tests - run: make test-unittests - - name: Functional tests - run: make test-functional - - name: Database tests - run: make test-database + - name: Run Unit, Functional, and Database Tests + # aggregate into one command so we only have to eat the collection time once + run: make test-all # Regression Testing - Test Execution - name: Regression Tests - Execution diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3a1da9440e..82c694d42b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,10 +16,6 @@ jobs: shell: bash -l {0} steps: - uses: actions/checkout@v2 - - name: Patch the environment file - run: | - echo -e "\n - libstdcxx-ng < 13\n" >> environment.yml - cat environment.yml - name: Setup Mambaforge Python 3.7 uses: conda-incubator/setup-miniconda@v2 with: diff --git a/.gitignore b/.gitignore index 97e993156b..83ed904548 100644 --- a/.gitignore +++ b/.gitignore @@ -27,10 +27,8 @@ documentation/build/* # (These will be unique to each developer's setup) make.inc -# Example output files -examples/* -!examples/*/input.py -!examples/**/*.ipynb +# output files +rmgpy/output/* # NetBeans project files nbproject/* @@ -76,9 +74,6 @@ bin/symmetry ipython/* !ipython/*.ipynb -# Q2DTor -external/Q2DTor - # egg files (pip install -e) RMG_Py.egg-info/* @@ -96,3 +91,24 @@ scripts/treegen_backup.log test/regression/* !test/regression/*/input.py !test/regression/*/regression_input.py + +# testing files +test/arkane/data/two_parameter_arrhenius_fit/arkane.log +test/rmgpy/test_data/temp_dir_for_testing/cantera/chem001.yaml +rmgpy/test_data/copied_kinetic_lib/ +testing/qm/* +test_log.txt + +# example directory - save the inputs but not the outputs +# cantera input files +examples/**/chem.inp +# log files from the runs +examples/**/arkane.log +# results from the runs +examples/**/species_dictionary.txt +examples/**/output*py +examples/**/network*py +examples/**/dictionary.txt +examples/**/reactions.py +examples/**/RMG_libraries +examples/**/species diff --git a/Makefile b/Makefile index 7ec4bae352..466b923e6b 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # ################################################################################ -.PHONY : all minimal main solver check pycheck arkane clean install decython documentation mopac_travis test +.PHONY : all minimal main solver check pycheck arkane clean install decython documentation test all: pycheck main solver check @@ -40,17 +40,6 @@ clean-solver: install: @ python utilities.py check-pydas python setup.py install - -q2dtor: - @ echo -e "\nInstalling Q2DTor...\n" - @ echo -e "Q2DTor is a software for calculating the partition functions and themodynamic properties\ - of molecular systems with two or more torsional modes developed by David Ferro Costas (david.ferro@usc.es)\ - and Antonio Fernandez Ramos (qf.ramos@usc.es) at the Universidade de Santiago de Compostela. Arkane can\ - integrate Q2DTor to compute the quantum mechanical partition function of 2D rotors. \n\nFor use of Q2DTor\ - and HinderedRotor2D within Arkane please cite: \n\nD. Ferro-Costas, M. N. D. S.Cordeiro, D. G. Truhlar, A.\ - Fernández-Ramos, Comput. Phys. Commun. 232, 190-205, 2018.\n" - @ read -p "Press ENTER to continue" dummy - @ git clone https://github.com/mjohnson541/Q2DTor.git external/Q2DTor --branch arkanepy3 decython: # de-cythonize all but the 'minimal'. Helpful for debugging in "pure python" mode. @@ -58,16 +47,16 @@ decython: find . -name *.pyc -exec rm -f '{}' \; test-all: - nosetests --nocapture --nologcapture --all-modules --verbose --with-coverage --cover-inclusive --cover-erase --cover-html --cover-html-dir=testing/coverage --exe rmgpy arkane + python-jl -m pytest test test-unittests: - nosetests --nocapture --nologcapture --all-modules -A 'not functional' --verbose --with-coverage --cover-inclusive --cover-erase --cover-html --cover-html-dir=testing/coverage --exe rmgpy arkane + python-jl -m pytest -m "not functional and not database" test-functional: - nosetests --nologcapture --all-modules -A 'functional' --verbose --exe rmgpy arkane + python-jl -m pytest -m "functional" test-database: - nosetests --nocapture --nologcapture --verbose --detailed-errors testing/databaseTest.py + python-jl -m pytest -m "database" eg0: all mkdir -p testing/eg0 @@ -85,25 +74,22 @@ eg1: all coverage run rmg.py -p testing/eg1/input.py coverage report coverage html + eg2: all mkdir -p testing/eg2 rm -rf testing/eg2/* cp examples/rmg/1,3-hexadiene/input.py testing/eg2/input.py coverage erase - @ echo "Running eg2: 1,3-hexadiene example with coverage tracking AND profiling" - coverage run rmg.py -p testing/eg2/input.py - coverage report - coverage html + @ echo "Running eg2: 1,3-hexadiene example with profiling" + python rmg.py -p testing/eg2/input.py eg3: all mkdir -p testing/eg3 rm -rf testing/eg3/* cp examples/rmg/liquid_phase/input.py testing/eg3/input.py coverage erase - @ echo "Running eg3: liquid_phase example with coverage tracking AND profiling" - coverage run rmg.py -p testing/eg3/input.py - coverage report - coverage html + @ echo "Running eg3: liquid_phase example with profiling" + python rmg.py -p testing/eg3/input.py eg5: all mkdir -p testing/eg5 @@ -140,3 +126,15 @@ eg4: all cp examples/thermoEstimator/input.py testing/eg4/input.py @ echo "Running thermo data estimator example. This tests QM." python scripts/thermoEstimator.py testing/eg4/input.py + +q2dtor: + @ echo -e "\nInstalling Q2DTor...\n" + @ echo -e "Q2DTor is a software for calculating the partition functions and themodynamic properties\ + of molecular systems with two or more torsional modes developed by David Ferro Costas (david.ferro@usc.es)\ + and Antonio Fernandez Ramos (qf.ramos@usc.es) at the Universidade de Santiago de Compostela. Arkane can\ + integrate Q2DTor to compute the quantum mechanical partition function of 2D rotors. \n\nFor use of Q2DTor\ + and HinderedRotor2D within Arkane please cite: \n\nD. Ferro-Costas, M. N. D. S.Cordeiro, D. G. Truhlar, A.\ + Fernández-Ramos, Comput. Phys. Commun. 232, 190-205, 2018.\n" + @ read -p "Press ENTER to continue" dummy + @ mkdir -p external + @ git clone https://github.com/cathedralpkg/Q2DTor external/Q2DTor \ No newline at end of file diff --git a/arkane/common.py b/arkane/common.py index 4f67ff1977..102cf6649e 100644 --- a/arkane/common.py +++ b/arkane/common.py @@ -64,27 +64,28 @@ ################################################################################ # Class dictionary for recreating objects from YAML files. This is needed elsewhere, so store as a module level variable -ARKANE_CLASS_DICT = {'ScalarQuantity': ScalarQuantity, - 'ArrayQuantity': ArrayQuantity, - 'Conformer': Conformer, - 'LinearRotor': LinearRotor, - 'NonlinearRotor': NonlinearRotor, - 'KRotor': KRotor, - 'SphericalTopRotor': SphericalTopRotor, - 'HinderedRotor': HinderedRotor, - 'FreeRotor': FreeRotor, - 'IdealGasTranslation': IdealGasTranslation, - 'HarmonicOscillator': HarmonicOscillator, - 'TransportData': TransportData, - 'SingleExponentialDown': SingleExponentialDown, - 'Wilhoit': Wilhoit, - 'NASA': NASA, - 'NASAPolynomial': NASAPolynomial, - 'ThermoData': ThermoData, - 'np_array': np.array, - 'LevelOfTheory': LevelOfTheory, - 'CompositeLevelOfTheory': CompositeLevelOfTheory - } +ARKANE_CLASS_DICT = { + "ScalarQuantity": ScalarQuantity, + "ArrayQuantity": ArrayQuantity, + "Conformer": Conformer, + "LinearRotor": LinearRotor, + "NonlinearRotor": NonlinearRotor, + "KRotor": KRotor, + "SphericalTopRotor": SphericalTopRotor, + "HinderedRotor": HinderedRotor, + "FreeRotor": FreeRotor, + "IdealGasTranslation": IdealGasTranslation, + "HarmonicOscillator": HarmonicOscillator, + "TransportData": TransportData, + "SingleExponentialDown": SingleExponentialDown, + "Wilhoit": Wilhoit, + "NASA": NASA, + "NASAPolynomial": NASAPolynomial, + "ThermoData": ThermoData, + "np_array": np.array, + "LevelOfTheory": LevelOfTheory, + "CompositeLevelOfTheory": CompositeLevelOfTheory, +} # Add a custom string representer to use block literals for multiline strings @@ -93,8 +94,8 @@ def str_repr(dumper, data): Repair YAML string representation """ if len(data.splitlines()) > 1: - return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|') - return dumper.represent_scalar('tag:yaml.org,2002:str', data) + return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|") + return dumper.represent_scalar("tag:yaml.org,2002:str", data) yaml.add_representer(str, str_repr) @@ -105,19 +106,47 @@ class ArkaneSpecies(RMGObject): A class for archiving an Arkane species including its statmech data into .yml files """ - def __init__(self, species=None, conformer=None, author='', level_of_theory='', model_chemistry='', - frequency_scale_factor=None, use_hindered_rotors=None, use_bond_corrections=None, atom_energies='', - chemkin_thermo_string='', smiles=None, adjacency_list=None, inchi=None, inchi_key=None, xyz=None, - molecular_weight=None, symmetry_number=None, transport_data=None, energy_transfer_model=None, - thermo=None, thermo_data=None, label=None, datetime=None, RMG_version=None, reactants=None, - products=None, reaction_label=None, is_ts=None, charge=None, formula=None, multiplicity=None): + def __init__( + self, + species=None, + conformer=None, + author="", + level_of_theory="", + model_chemistry="", + frequency_scale_factor=None, + use_hindered_rotors=None, + use_bond_corrections=None, + atom_energies="", + chemkin_thermo_string="", + smiles=None, + adjacency_list=None, + inchi=None, + inchi_key=None, + xyz=None, + molecular_weight=None, + symmetry_number=None, + transport_data=None, + energy_transfer_model=None, + thermo=None, + thermo_data=None, + label=None, + datetime=None, + RMG_version=None, + reactants=None, + products=None, + reaction_label=None, + is_ts=None, + charge=None, + formula=None, + multiplicity=None, + ): # reactants/products/reaction_label need to be in the init() to avoid error when loading a TS YAML file, # but we don't use them super(ArkaneSpecies, self).__init__() if species is None and conformer is None: # Expecting to get a species or a TS when generating the object within Arkane, # or a conformer when parsing from YAML. - raise ValueError('No species (or TS) or conformer was passed to the ArkaneSpecies object') + raise ValueError("No species (or TS) or conformer was passed to the ArkaneSpecies object") if conformer is not None: self.conformer = conformer if label is None and species is not None: @@ -151,7 +180,7 @@ def __init__(self, species=None, conformer=None, author='', level_of_theory='', else: # initialize TS-related attributes self.imaginary_frequency = None - self.reaction_label = '' + self.reaction_label = "" self.reactants = list() self.products = list() if species is not None: @@ -163,12 +192,12 @@ def __repr__(self): """ Return a string representation that can be used to reconstruct the object """ - result = '{0!r}'.format(self.__class__.__name__) - result += '{' + result = "{0!r}".format(self.__class__.__name__) + result += "{" for key, value in self.as_dict().items(): - if key != 'class': - result += '{0!r}: {1!r}'.format(str(key), str(value)) - result += '}' + if key != "class": + result += "{0!r}: {1!r}".format(str(key), str(value)) + result += "}" return result def update_species_attributes(self, species=None): @@ -176,7 +205,7 @@ def update_species_attributes(self, species=None): Update the object with a new species/TS (while keeping non-species-dependent attributes unchanged) """ if species is None: - raise ValueError('No species was passed to ArkaneSpecies') + raise ValueError("No species was passed to ArkaneSpecies") # Don't overwrite the label if it already exists self.label = self.label or species.label if isinstance(species, TransitionState): @@ -191,13 +220,13 @@ def update_species_attributes(self, species=None): self.multiplicity = species.molecule[0].multiplicity self.formula = species.molecule[0].get_formula() try: - inchi = to_inchi(species.molecule[0], backend='try-all', aug_level=0) + inchi = to_inchi(species.molecule[0], backend="try-all", aug_level=0) except ValueError: - inchi = '' + inchi = "" try: - inchi_key = to_inchi_key(species.molecule[0], backend='try-all', aug_level=0) + inchi_key = to_inchi_key(species.molecule[0], backend="try-all", aug_level=0) except ValueError: - inchi_key = '' + inchi_key = "" self.inchi = inchi self.inchi_key = inchi_key if species.conformer is not None: @@ -213,18 +242,19 @@ def update_species_attributes(self, species=None): if species.thermo is not None: self.thermo = species.thermo.as_dict() data = species.get_thermo_data() - h298 = data.get_enthalpy(298) / 4184. + h298 = data.get_enthalpy(298) / 4184.0 s298 = data.get_entropy(298) / 4.184 temperatures = np.array([300, 400, 500, 600, 800, 1000, 1500, 2000, 2400]) cp = [] for t in temperatures: cp.append(data.get_heat_capacity(t) / 4.184) - self.thermo_data = ThermoData(H298=(h298, 'kcal/mol'), - S298=(s298, 'cal/(mol*K)'), - Tdata=(temperatures, 'K'), - Cpdata=(cp, 'cal/(mol*K)'), - ) + self.thermo_data = ThermoData( + H298=(h298, "kcal/mol"), + S298=(s298, "cal/(mol*K)"), + Tdata=(temperatures, "K"), + Cpdata=(cp, "cal/(mol*K)"), + ) def update_xyz_string(self): """ @@ -240,24 +270,23 @@ def update_xyz_string(self): xyz_list.append(self.label) for number, coordinate in zip(self.conformer.number.value_si, self.conformer.coordinates.value_si): element_symbol = get_element(int(number)).symbol - row = '{0:4}'.format(element_symbol) - row += '{0:14.8f}{1:14.8f}{2:14.8f}'.format(*(coordinate * 1e10).tolist()) # convert m to Angstrom + row = "{0:4}".format(element_symbol) + row += "{0:14.8f}{1:14.8f}{2:14.8f}".format(*(coordinate * 1e10).tolist()) # convert m to Angstrom xyz_list.append(row) - return '\n'.join(xyz_list) + return "\n".join(xyz_list) def save_yaml(self, path): """ Save the species with all statMech data to a .yml file """ - if not os.path.exists(os.path.join(os.path.abspath(path), 'species', '')): - os.mkdir(os.path.join(os.path.abspath(path), 'species', '')) + if not os.path.exists(os.path.join(os.path.abspath(path), "species", "")): + os.mkdir(os.path.join(os.path.abspath(path), "species", "")) valid_chars = "-_.()<=>+ %s%s" % (string.ascii_letters, string.digits) - filename = os.path.join('species', - ''.join(c for c in self.label if c in valid_chars) + '.yml') + filename = os.path.join("species", "".join(c for c in self.label if c in valid_chars) + ".yml") full_path = os.path.join(path, filename) - with open(full_path, 'w') as f: + with open(full_path, "w") as f: yaml.dump(data=self.as_dict(), stream=f) - logging.debug('Dumping species {0} data as {1}'.format(self.label, filename)) + logging.debug("Dumping species {0} data as {1}".format(self.label, filename)) def load_yaml(self, path, label=None, pdep=False): """ @@ -266,59 +295,63 @@ def load_yaml(self, path, label=None, pdep=False): """ yml_file = os.path.basename(path) if label: - logging.info('Loading statistical mechanics parameters for {0} from {1} file...'.format(label, yml_file)) + logging.info("Loading statistical mechanics parameters for {0} from {1} file...".format(label, yml_file)) else: - logging.info('Loading statistical mechanics parameters from {0} file...'.format(yml_file)) - with open(path, 'r') as f: + logging.info("Loading statistical mechanics parameters from {0} file...".format(yml_file)) + with open(path, "r") as f: content = f.read() content = replace_yaml_syntax(content, label) data = yaml.safe_load(stream=content) if label: # First, warn the user if the label doesn't match try: - if label != data['label']: - logging.debug('Found different labels for species: {0} in input file, and {1} in the .yml file. ' - 'Using the label "{0}" for this species.'.format(label, data['label'])) + if label != data["label"]: + logging.debug( + "Found different labels for species: {0} in input file, and {1} in the .yml file. " + 'Using the label "{0}" for this species.'.format(label, data["label"]) + ) except KeyError: # Lacking label in the YAML file is strange, but accepted - logging.debug('Did not find label for species {0} in .yml file.'.format(label)) + logging.debug("Did not find label for species {0} in .yml file.".format(label)) # Then, set the ArkaneSpecies label to the user supplied label - data['label'] = label + data["label"] = label try: - class_name = data['class'] + class_name = data["class"] except KeyError: raise KeyError("Can only make objects if the `class` attribute in the dictionary is known") - if class_name != 'ArkaneSpecies': + if class_name != "ArkaneSpecies": raise KeyError("Expected a ArkaneSpecies object, but got {0}".format(class_name)) - del data['class'] + del data["class"] freq_data = None - if 'imaginary_frequency' in data: - freq_data = data['imaginary_frequency'] - del data['imaginary_frequency'] - if not data['is_ts']: - if 'smiles' in data: - data['species'] = Species(smiles=data['smiles']) - elif 'adjacency_list' in data: - data['species'] = Species().from_adjacency_list(data['adjacency_list']) - elif 'inchi' in data: - data['species'] = Species(inchi=data['inchi']) + if "imaginary_frequency" in data: + freq_data = data["imaginary_frequency"] + del data["imaginary_frequency"] + if not data["is_ts"]: + if "smiles" in data: + data["species"] = Species(smiles=data["smiles"]) + elif "adjacency_list" in data: + data["species"] = Species().from_adjacency_list(data["adjacency_list"]) + elif "inchi" in data: + data["species"] = Species(inchi=data["inchi"]) else: - raise ValueError('Cannot load ArkaneSpecies from YAML file {0}. Either `smiles`, `adjacency_list`, or ' - 'InChI must be specified'.format(path)) + raise ValueError( + "Cannot load ArkaneSpecies from YAML file {0}. Either `smiles`, `adjacency_list`, or " "InChI must be specified".format(path) + ) # Finally, set the species label so that the special attributes are updated properly - data['species'].label = data['label'] + data["species"].label = data["label"] self.make_object(data=data, class_dict=ARKANE_CLASS_DICT) if freq_data is not None: self.imaginary_frequency = ScalarQuantity() self.imaginary_frequency.make_object(data=freq_data, class_dict=ARKANE_CLASS_DICT) - if pdep and not self.is_ts and self.smiles is None and self.adjacency_list is None \ - and self.inchi is None and self.molecular_weight is None: - raise ValueError('The molecular weight was not specified, and a structure was not given so it could ' - 'not be calculated. Specify either the molecular weight or structure if ' - 'pressure-dependent calculations are requested. Check file {0}'.format(path)) + if pdep and not self.is_ts and self.smiles is None and self.adjacency_list is None and self.inchi is None and self.molecular_weight is None: + raise ValueError( + "The molecular weight was not specified, and a structure was not given so it could " + "not be calculated. Specify either the molecular weight or structure if " + "pressure-dependent calculations are requested. Check file {0}".format(path) + ) logging.debug("Parsed all YAML objects") @@ -333,23 +366,25 @@ def replace_yaml_syntax(content, label=None): Returns: str: The modified content to be processed via yaml.safe_load(). """ - syntax_correction_dict = {'spinMultiplicity': 'spin_multiplicity', - 'opticalIsomers': 'optical_isomers', - } + syntax_correction_dict = { + "spinMultiplicity": "spin_multiplicity", + "opticalIsomers": "optical_isomers", + } replaced_keys = list() for key, value in syntax_correction_dict.items(): if key in content: content = content.replace(key, value) replaced_keys.append(key) - label = ' for species {0}'.format(label) if label is not None else '' + label = " for species {0}".format(label) if label is not None else "" if replaced_keys: - logging.info('\nThe loaded YAML file{0} seems to be from an older version of RMG/Arkane.\n' - 'Some keywords will be automatically replaced before loading objects from this file.'.format(label)) + logging.info( + "\nThe loaded YAML file{0} seems to be from an older version of RMG/Arkane.\n" + "Some keywords will be automatically replaced before loading objects from this file.".format(label) + ) for key in replaced_keys: - logging.info('Replacing keyword "{key}" with "{value}" in the Arkane YAML file.'.format( - key=key, value=syntax_correction_dict[key])) + logging.info('Replacing keyword "{key}" with "{value}" in the Arkane YAML file.'.format(key=key, value=syntax_correction_dict[key])) if replaced_keys: - logging.info('\n') + logging.info("\n") return content @@ -364,15 +399,17 @@ def is_pdep(job_list): def check_conformer_energy(energies, path): """ Check to see that the starting energy of the species in the potential energy scan calculation - is not 0.5 kcal/mol (or more) higher than any other energies in the scan. If so, print and - log a warning message. + is not 0.5 kcal/mol (or more) higher than any other energies in the scan. If so, print and + log a warning message. """ - energies = np.array(energies, np.float64) + energies = np.array(energies, float) e_diff = (energies[0] - np.min(energies)) * constants.E_h * constants.Na / 1000 if e_diff >= 2: # we choose 2 kJ/mol to be the critical energy - logging.warning(f'The species corresponding to {os.path.basename(path)} is different in energy from the ' - f'lowest energy conformer by {e_diff:.2f} kJ/mol. This can cause significant errors in ' - f'your computed thermodynamic properties and rate coefficients.') + logging.warning( + f"The species corresponding to {os.path.basename(path)} is different in energy from the " + f"lowest energy conformer by {e_diff:.2f} kJ/mol. This can cause significant errors in " + f"your computed thermodynamic properties and rate coefficients." + ) def get_element_mass(input_element, isotope=None): @@ -397,7 +434,7 @@ def get_element_mass(input_element, isotope=None): number = number_by_symbol[symbol] if symbol is None or number is None: - raise ValueError('Could not identify element {0}'.format(input_element)) + raise ValueError("Could not identify element {0}".format(input_element)) mass_list = mass_by_symbol[symbol] @@ -414,7 +451,7 @@ def get_element_mass(input_element, isotope=None): if len(mass_list[0]) == 2: # isotope weight is unavailable, use the first entry mass = mass_list[0][1] - logging.warning('Assuming isotope {0} is representative of element {1}'.format(mass_list[0][0], symbol)) + logging.warning("Assuming isotope {0} is representative of element {1}".format(mass_list[0][0], symbol)) else: # use the most common isotope max_weight = mass_list[0][2] @@ -426,189 +463,430 @@ def get_element_mass(input_element, isotope=None): return mass, number -symbol_by_number = {1: 'H', 2: 'He', 3: 'Li', 4: 'Be', 5: 'B', 6: 'C', 7: 'N', 8: 'O', 9: 'F', 10: 'Ne', 11: 'Na', - 12: 'Mg', 13: 'Al', 14: 'Si', 15: 'P', 16: 'S', 17: 'Cl', 18: 'Ar', 19: 'K', 20: 'Ca', 21: 'Sc', - 22: 'Ti', 23: 'V', 24: 'Cr', 25: 'Mn', 26: 'Fe', 27: 'Co', 28: 'Ni', 29: 'Cu', 30: 'Zn', 31: 'Ga', - 32: 'Ge', 33: 'As', 34: 'Se', 35: 'Br', 36: 'Kr', 37: 'Rb', 38: 'Sr', 39: 'Y', 40: 'Zr', 41: 'Nb', - 42: 'Mo', 43: 'Tc', 44: 'Ru', 45: 'Rh', 46: 'Pd', 47: 'Ag', 48: 'Cd', 49: 'In', 50: 'Sn', 51: 'Sb', - 52: 'Te', 53: 'I', 54: 'Xe', 55: 'Cs', 56: 'Ba', 57: 'La', 58: 'Ce', 59: 'Pr', 60: 'Nd', 61: 'Pm', - 62: 'Sm', 63: 'Eu', 64: 'Gd', 65: 'Tb', 66: 'Dy', 67: 'Ho', 68: 'Er', 69: 'Tm', 70: 'Yb', 71: 'Lu', - 72: 'Hf', 73: 'Ta', 74: 'W', 75: 'Re', 76: 'Os', 77: 'Ir', 78: 'Pt', 79: 'Au', 80: 'Hg', 81: 'Tl', - 82: 'Pb', 83: 'Bi', 84: 'Po', 85: 'At', 86: 'Rn', 87: 'Fr', 88: 'Ra', 89: 'Ac', 90: 'Th', 91: 'Pa', - 92: 'U', 93: 'Np', 94: 'Pu', 95: 'Am', 96: 'Cm', 97: 'Bk', 98: 'Cf', 99: 'Es', 100: 'Fm', 101: 'Md', - 102: 'No', 103: 'Lr', 104: 'Rf', 105: 'Db', 106: 'Sg', 107: 'Bh', 108: 'Hs', 109: 'Mt', 110: 'Ds', - 111: 'Rg', 112: 'Cn', 113: 'Nh', 114: 'Fl', 115: 'Mc', 116: 'Lv', 117: 'Ts', 118: 'Og'} +symbol_by_number = { + 1: "H", + 2: "He", + 3: "Li", + 4: "Be", + 5: "B", + 6: "C", + 7: "N", + 8: "O", + 9: "F", + 10: "Ne", + 11: "Na", + 12: "Mg", + 13: "Al", + 14: "Si", + 15: "P", + 16: "S", + 17: "Cl", + 18: "Ar", + 19: "K", + 20: "Ca", + 21: "Sc", + 22: "Ti", + 23: "V", + 24: "Cr", + 25: "Mn", + 26: "Fe", + 27: "Co", + 28: "Ni", + 29: "Cu", + 30: "Zn", + 31: "Ga", + 32: "Ge", + 33: "As", + 34: "Se", + 35: "Br", + 36: "Kr", + 37: "Rb", + 38: "Sr", + 39: "Y", + 40: "Zr", + 41: "Nb", + 42: "Mo", + 43: "Tc", + 44: "Ru", + 45: "Rh", + 46: "Pd", + 47: "Ag", + 48: "Cd", + 49: "In", + 50: "Sn", + 51: "Sb", + 52: "Te", + 53: "I", + 54: "Xe", + 55: "Cs", + 56: "Ba", + 57: "La", + 58: "Ce", + 59: "Pr", + 60: "Nd", + 61: "Pm", + 62: "Sm", + 63: "Eu", + 64: "Gd", + 65: "Tb", + 66: "Dy", + 67: "Ho", + 68: "Er", + 69: "Tm", + 70: "Yb", + 71: "Lu", + 72: "Hf", + 73: "Ta", + 74: "W", + 75: "Re", + 76: "Os", + 77: "Ir", + 78: "Pt", + 79: "Au", + 80: "Hg", + 81: "Tl", + 82: "Pb", + 83: "Bi", + 84: "Po", + 85: "At", + 86: "Rn", + 87: "Fr", + 88: "Ra", + 89: "Ac", + 90: "Th", + 91: "Pa", + 92: "U", + 93: "Np", + 94: "Pu", + 95: "Am", + 96: "Cm", + 97: "Bk", + 98: "Cf", + 99: "Es", + 100: "Fm", + 101: "Md", + 102: "No", + 103: "Lr", + 104: "Rf", + 105: "Db", + 106: "Sg", + 107: "Bh", + 108: "Hs", + 109: "Mt", + 110: "Ds", + 111: "Rg", + 112: "Cn", + 113: "Nh", + 114: "Fl", + 115: "Mc", + 116: "Lv", + 117: "Ts", + 118: "Og", +} number_by_symbol = {value: key for key, value in symbol_by_number.items()} # Structure of mass_by_symbol items: list(list(isotope1, mass1, weight1), list(isotope2, mass2, weight2), ...) mass_by_symbol = { - 'H': [[1, 1.00782503224, 0.999885], [2, 2.01410177812, 0.000115], [3, 3.0160492779, 0]], - 'He': [[3, 3.0160293201, 0.00000134], [4, 4.00260325414, 0.99999866]], - 'Li': [[6, 6.0151228874, 0.0759], [7, 7.0160034366, 0.9241]], - 'Be': [[9, 9.012183066, 1]], - 'B': [[10, 10.01293695, 0.199], [11, 11.00930536, 0.801]], - 'C': [[12, 12.0000000, 0.9893], [13, 13.00335483507, 0.0107], [14, 14.0032419884, 0]], - 'N': [[14, 14.00307400443, 0.99636], [15, 15.00010889889, 0.00364]], - 'O': [[16, 15.99491461957, 0.99757], [17, 16.99913175651, 0.00038], [18, 17.99915961287, 0.00205]], - 'F': [[19, 18.99840316274, 1]], - 'Ne': [[20, 19.9924401762, 0.9048], [21, 20.993846685, 0.0027], [22, 21.991385114, 0.0925]], - 'Na': [[23, 22.9897692820, 1]], - 'Mg': [[24, 23.985041697, 0.7899], [25, 24.985836976, 0.1000], [26, 25.982592968, 0.1101]], - 'Al': [[27, 26.98153853, 1]], - 'Si': [[28, 27.97692653465, 0.92223], [29, 28.97649466491, 0.04685], [30, 29.973770136, 0.03092]], - 'P': [[31, 30.97376199843, 1]], - 'S': [[32, 31.9720711744, 0.9499], [33, 32.9714589098, 0.0075], [34, 33.967867004, 0.0425], - [36, 35.96708071, 0.0001]], - 'Cl': [[35, 34.968852682, 0.7576], [37, 36.965902603, 0.2424]], - 'Ar': [[36, 35.967545105, 0.003336], [38, 37.96273211, 0.000629], [40, 39.9623831237, 0.996035]], - 'K': [[39, 38.9637064864, 0.932581], [40, 39.963998167, 0.000117], [41, 40.9618252579, 0.067302]], - 'Ca': [[40, 39.962590863, 0.96941], [42, 41.95861783, 0.00647], [43, 42.95876644, 0.00135], - [44, 43.95548156, 0.02086], [46, 45.9536890, 0.00004], [48, 47.95252276, 0.00187]], - 'Sc': [[45, 44.95590829, 1]], - 'Ti': [[46, 45.95262772, 0.0825], [47, 46.95175879, 0.0744], [48, 47.94794198, 0.7372], [49, 48.94786568, 0.0541], - [50, 49.94478689, 0.0518]], - 'V': [[50, 49.94715602, 0.00250], [51, 50.94395705, 0.99750]], - 'Cr': [[50, 49.94604184, 0.04345], [52, 51.94050624, 0.83789], [53, 52.94064816, 0.09501], - [54, 53.93887917, 0.02365]], - 'Mn': [[55, 54.93804391, 1]], - 'Fe': [[54, 53.93960900, 0.05845], [56, 55.93493633, 0.91754], [57, 56.93539284, 0.02119], - [58, 57.93327444, 0.00282]], - 'Co': [[59, 58.93319430, 1]], - 'Ni': [[58, 57.93534242, 0.68077], [60, 59.93078589, 0.26223], [61, 60.93105558, 0.011399], - [62, 61.92834538, 0.036346], [64, 63.92796682, 0.009255]], - 'Cu': [[63, 62.92959773, 0.6915], [65, 64.92778971, 0.3085]], - 'Zn': [[64, 63.92914202, 0.4917], [66, 65.92603382, 0.2773], [67, 66.92712776, 0.0404], [68, 67.92484456, 0.1845], - [70, 69.9253192, 0.0061]], - 'Ga': [[69, 68.9255735, 0.60108], [71, 70.92470259, 0.39892]], - 'Ge': [[70, 69.92424876, 0.2057], [72, 71.922075827, 0.2745], [73, 72.923458957, 0.0775], - [74, 73.921177761, 0.3650], [76, 75.921402726, 0.0773]], - 'As': [[75, 74.92159458, 1]], - 'Se': [[74, 73.922475934, 0.0089], [76, 75.919213704, 0.0937], [77, 76.919914155, 0.0763], - [78, 77.91730928, 0.2377], [80, 79.9165218, 0.4961], [82, 81.9166995, 0.0873]], - 'Br': [[79, 78.9183376, 0.5069], [81, 80.9162897, 0.4931]], - 'Kr': [[78, 77.92036495, 0.00355], [80, 79.91637809, 0.02286], [82, 81.91348274, 0.11593], - [83, 82.91412716, 0.11500], [84, 83.9114977282, 0.56987], [86, 85.9106106269, 0.17279]], - 'Rb': [[85, 84.9117897380, 0.7217], [87, 86.9091805311, 0.2783]], - 'Sr': [[84, 83.9134191, 0.0056], [86, 85.9092606, 0.0986], [87, 86.9088775, 0.0700], - [88, 87.9056125, 0.8258]], - 'Y': [[89, 88.9058403, 1]], - 'Zr': [[90, 89.9046977, 0.5145], [91, 90.9056396, 0.1122], [92, 91.9050347, 0.1715], - [94, 93.9063108, 0.1738], [96, 95.9082714, 0.0280]], - 'Nb': [[93, 92.9063730, 1]], - 'Mo': [[92, 91.90680797, 0.1453], [94, 93.90508490, 0.0915], [95, 94.90583877, 0.1584], [96, 95.90467612, 0.1667], - [97, 96.90601812, 0.0960], [98, 97.90540482, 0.2439], [100, 99.9074718, 0.0982]], - 'Tc': [[97, 96.9063667, ], [98, 97.9072124], [99, 98.9062508]], - 'Ru': [[96, 95.90759025, 0.0554], [98, 97.9052869, 0.0187], [99, 98.9059341, 0.1276], [100, 99.9042143, 0.1260], - [101, 100.9055769, 0.1706], [102, 101.9043441, 0.3155], [104, 103.9054275, 0.1862]], - 'Rh': [[103, 102.905498, 1]], - 'Pd': [[102, 101.9056022, 0.0102], [104, 103.9040305, 0.1114], [105, 104.9050796, 0.2233], - [106, 105.9034804, 0.2733], [108, 107.9038916, 0.2646], [110, 109.90517221, 0.1172]], - 'Ag': [[107, 106.9050916, 0.51839], [109, 108.9047553, 0.48161]], - 'Cd': [[106, 105.9064599, 0.0125], [108, 107.9041834, 0.0089], [110, 109.90300662, 0.1249], - [111, 110.90418288, 0.1280], [112, 111.9027629, 0.2413], [113, 112.9044081, 0.1222], - [114, 113.9033651, 0.2873], [116, 115.9047632, 0.0749]], - 'In': [[113, 112.9040618, 0.0429], [115, 114.9038788, 0.9571]], - 'Sn': [[112, 111.9048239, 0.0097], [114, 113.9027827, 0.0066], [115, 114.9033447, 0.0034], - [116, 115.9017428, 0.1454], [117, 116.902954, 0.0768], [118, 117.9016066, 0.2422], - [119, 118.9033112, 0.0859], [120, 119.9022016, 0.3258], [122, 121.9034438, 0.0463], - [124, 123.9052766, 0.0579]], - 'Sb': [[121, 120.903812, 0.5721], [123, 122.9042132, 0.4279]], - 'Te': [[120, 119.9040593, 0.0009], [122, 121.9030435, 0.0255], [123, 122.9042698, 0.0089], - [124, 123.9028171, 0.0474], [125, 124.9044299, 0.0707], [126, 125.9033109, 0.1884], - [128, 127.9044613, 0.3174], [130, 129.9062227, 0.3408]], - 'I': [[127, 126.9044719, 1]], - 'Xe': [[124, 123.905892, 0.000952], [126, 125.9042983, 0.000890], [128, 127.903531, 0.019102], - [129, 128.9047809, 0.264006], [130, 129.9035093, 0.040710], [131, 130.9050841, 0.212324], - [132, 131.9041551, 0.269086], [134, 133.9053947, 0.104357], [136, 135.9072145, 0.088573]], - 'Cs': [[133, 132.905452, 1]], - 'Ba': [[130, 129.9063207, 0.00106], [132, 131.9050611, 0.00101], [134, 133.9045082, 0.02417], - [135, 134.9056884, 0.06592], [136, 135.9045757, 0.07854], [137, 136.9058271, 0.11232], - [138, 137.905247, 0.71698]], - 'La': [[138, 137.9071149, 0.0008881], [139, 138.9063563, 0.9991119]], - 'Ce': [[136, 135.9071292, 0.00185], [138, 137.905991, 0.00251], [140, 139.9054431, 0.88450], - [142, 141.9092504, 0.11114]], - 'Pr': [[141, 140.9076576, 1]], - 'Nd': [[142, 141.907729, 0.27152], [143, 142.90982, 0.12174], [144, 143.910093, 0.23798], - [145, 144.9125793, 0.08293], [146, 145.9131226, 0.17189], [148, 147.9168993, 0.05756], - [150, 149.9209022, 0.05638]], - 'Pm': [[145, 144.9127559], [147, 146.915145]], - 'Sm': [[144, 143.9120065, 0.0307], [147, 146.9149044, 0.1499], [148, 147.9148292, 0.1124], - [149, 148.9171921, 0.1382], [150, 149.9172829, 0.0738], [152, 151.9197397, 0.2675], - [154, 153.9222169, 0.2275]], - 'Eu': [[151, 150.9198578, 0.4781], [153, 152.921238, 0.5219]], - 'Gd': [[152, 151.9197995, 0.0020], [154, 153.9208741, 0.0218], [155, 154.9226305, 0.1480], - [156, 155.9221312, 0.2047], [157, 156.9239686, 0.1565], [158, 157.9241123, 0.2484], - [160, 159.9270624, 0.2186]], - 'Tb': [[159, 158.9253547, 1]], - 'Dy': [[156, 155.9242847, 0.00056], [158, 157.9244159, 0.00095], [160, 159.9252046, 0.02329], - [161, 160.9269405, 0.18889], [162, 161.9268056, 0.25475], [163, 162.9287383, 0.24896], - [164, 163.9291819, 0.28260]], - 'Ho': [[165, 164.9303288, 1]], - 'Er': [[162, 161.9287884, 0.00139], [164, 163.9292088, 0.01601], [166, 165.9302995, 0.33503], - [167, 166.9320546, 0.22869], [168, 167.9323767, 0.26978], [170, 169.9354702, 0.14910]], - 'Tm': [[169, 168.9342179, 1]], - 'Yb': [[168, 167.9338896, 0.00123], [170, 169.9347664, 0.02982], [171, 170.9363302, 0.1409], - [172, 171.9363859, 0.2168], [173, 172.9382151, 0.16103], [174, 173.9388664, 0.32026], - [176, 175.9425764, 0.12996]], - 'Lu': [[175, 174.9407752, 0.97401], [176, 175.9426897, 0.02599]], - 'Hf': [[174, 173.9400461, 0.0016], [176, 175.9414076, 0.0526], [177, 176.9432277, 0.1860], - [178, 177.9437058, 0.2728], [179, 178.9458232, 0.1362], [180, 179.946557, 0.3508]], - 'Ta': [[180, 179.9474648, 0.0001201], [181, 180.9479958, 0.9998799]], - 'W': [[180, 179.9467108, 0.0012], [182, 181.9482039, 0.2650], [183, 182.9502228, 0.1431], - [184, 183.9509309, 0.3064], [186, 185.9543628, 0.2843]], - 'Re': [[185, 184.9529545, 0.3740], [187, 186.9557501, 0.6260]], - 'Os': [[184, 183.9524885, 0.0002], [186, 185.953835, 0.0159], [187, 186.9557474, 0.0196], - [188, 187.9558352, 0.1324], [189, 188.9581442, 0.1615], [190, 189.9584437, 0.2626], - [192, 191.961477, 0.4078]], - 'Ir': [[191, 190.9605893, 0.373], [193, 192.9629216, 0.627]], - 'Pt': [[190, 189.9599297, 0.00012], [192, 191.9610387, 0.00782], [194, 193.9626809, 0.3286], - [195, 194.9647917, 0.3378], [196, 195.9649521, 0.2521], [198, 197.9678949, 0.07356]], - 'Au': [[197, 196.9665688, 1]], - 'Hg': [[196, 195.9658326, 0.0015], [198, 197.9667686, 0.0997], [199, 198.9682806, 0.1687], - [200, 199.9683266, 0.2310], [201, 200.9703028, 0.1318], [202, 201.9706434, 0.2986], - [204, 203.973494, 0.0687]], - 'Tl': [[203, 202.9723446, 0.2952], [205, 204.9744278, 0.7048]], - 'Pb': [[204, 203.973044, 0.014], [206, 205.9744657, 0.241], [207, 206.9758973, 0.221], - [208, 207.9766525, 0.524]], - 'Bi': [[209, 208.9803991, 1]], - 'Po': [[209, 208.9824308], [210, 209.9828741]], - 'At': [[210, 209.9871479], [211, 210.9874966]], - 'Rn': [[211, 210.9906011], [220, 220.0113941], [222, 222.0175782]], - 'Fr': [[223, 223.019736]], - 'Ra': [[223, 223.0185023], [224, 224.020212], [226, 226.0254103], [228, 228.0310707]], - 'Ac': [[227, 227.0277523]], - 'Th': [[230, 230.0331341, 0], [232, 232.0380558, 1]], - 'Pa': [[231, 231.0358842, 1]], - 'U': [[233, 233.0396355, 0], [234, 234.0409523, 0.000054], [235, 235.0439301, 0.007204], - [236, 236.0455682, 0], [238, 238.0507884, 0.992742]], - 'Np': [[236, 236.04657], [237, 237.0481736]], - 'Pu': [[238, 238.0495601], [239, 239.0521636], [240, 240.0538138], [241, 241.0568517], - [242, 242.0587428], [244, 244.0642053]], - 'Am': [[241, 241.0568293], [243, 243.0613813]], - 'Cm': [[243, 243.0613893], [244, 244.0627528], [245, 245.0654915], [246, 246.0672238], - [247, 247.0703541], [248, 248.0723499]], - 'Bk': [[247, 247.0703073], [249, 249.0749877]], - 'Cf': [[249, 249.0748539], [250, 250.0764062], [251, 251.0795886], [252, 252.0816272]], - 'Es': [[252, 252.08298]], - 'Fm': [[257, 257.0951061]], - 'Md': [[258, 258.0984315], [260, 260.10365]], - 'No': [[259, 259.10103]], - 'Lr': [[262, 262.10961]], - 'Rf': [[267, 267.12179]], - 'Db': [[268, 268.12567]], - 'Sg': [[271, 271.13393]], - 'Bh': [[272, 272.13826]], - 'Hs': [[270, 270.13429]], - 'Mt': [[276, 276.15159]], - 'Ds': [[281, 281.16451]], - 'Rg': [[280, 280.16514]], - 'Cn': [[285, 285.17712]], - 'Nh': [[284, 284.17873]], - 'Fl': [[289, 289.19042]], - 'Mc': [[288, 288.19274]], - 'Lv': [[293, 293.20449]], - 'Ts': [[292, 292.20746]], - 'Og': [[294, 294.21392]]} + "H": [[1, 1.00782503224, 0.999885], [2, 2.01410177812, 0.000115], [3, 3.0160492779, 0]], + "He": [[3, 3.0160293201, 0.00000134], [4, 4.00260325414, 0.99999866]], + "Li": [[6, 6.0151228874, 0.0759], [7, 7.0160034366, 0.9241]], + "Be": [[9, 9.012183066, 1]], + "B": [[10, 10.01293695, 0.199], [11, 11.00930536, 0.801]], + "C": [[12, 12.0000000, 0.9893], [13, 13.00335483507, 0.0107], [14, 14.0032419884, 0]], + "N": [[14, 14.00307400443, 0.99636], [15, 15.00010889889, 0.00364]], + "O": [[16, 15.99491461957, 0.99757], [17, 16.99913175651, 0.00038], [18, 17.99915961287, 0.00205]], + "F": [[19, 18.99840316274, 1]], + "Ne": [[20, 19.9924401762, 0.9048], [21, 20.993846685, 0.0027], [22, 21.991385114, 0.0925]], + "Na": [[23, 22.9897692820, 1]], + "Mg": [[24, 23.985041697, 0.7899], [25, 24.985836976, 0.1000], [26, 25.982592968, 0.1101]], + "Al": [[27, 26.98153853, 1]], + "Si": [[28, 27.97692653465, 0.92223], [29, 28.97649466491, 0.04685], [30, 29.973770136, 0.03092]], + "P": [[31, 30.97376199843, 1]], + "S": [[32, 31.9720711744, 0.9499], [33, 32.9714589098, 0.0075], [34, 33.967867004, 0.0425], [36, 35.96708071, 0.0001]], + "Cl": [[35, 34.968852682, 0.7576], [37, 36.965902603, 0.2424]], + "Ar": [[36, 35.967545105, 0.003336], [38, 37.96273211, 0.000629], [40, 39.9623831237, 0.996035]], + "K": [[39, 38.9637064864, 0.932581], [40, 39.963998167, 0.000117], [41, 40.9618252579, 0.067302]], + "Ca": [ + [40, 39.962590863, 0.96941], + [42, 41.95861783, 0.00647], + [43, 42.95876644, 0.00135], + [44, 43.95548156, 0.02086], + [46, 45.9536890, 0.00004], + [48, 47.95252276, 0.00187], + ], + "Sc": [[45, 44.95590829, 1]], + "Ti": [[46, 45.95262772, 0.0825], [47, 46.95175879, 0.0744], [48, 47.94794198, 0.7372], [49, 48.94786568, 0.0541], [50, 49.94478689, 0.0518]], + "V": [[50, 49.94715602, 0.00250], [51, 50.94395705, 0.99750]], + "Cr": [[50, 49.94604184, 0.04345], [52, 51.94050624, 0.83789], [53, 52.94064816, 0.09501], [54, 53.93887917, 0.02365]], + "Mn": [[55, 54.93804391, 1]], + "Fe": [[54, 53.93960900, 0.05845], [56, 55.93493633, 0.91754], [57, 56.93539284, 0.02119], [58, 57.93327444, 0.00282]], + "Co": [[59, 58.93319430, 1]], + "Ni": [ + [58, 57.93534242, 0.68077], + [60, 59.93078589, 0.26223], + [61, 60.93105558, 0.011399], + [62, 61.92834538, 0.036346], + [64, 63.92796682, 0.009255], + ], + "Cu": [[63, 62.92959773, 0.6915], [65, 64.92778971, 0.3085]], + "Zn": [[64, 63.92914202, 0.4917], [66, 65.92603382, 0.2773], [67, 66.92712776, 0.0404], [68, 67.92484456, 0.1845], [70, 69.9253192, 0.0061]], + "Ga": [[69, 68.9255735, 0.60108], [71, 70.92470259, 0.39892]], + "Ge": [[70, 69.92424876, 0.2057], [72, 71.922075827, 0.2745], [73, 72.923458957, 0.0775], [74, 73.921177761, 0.3650], [76, 75.921402726, 0.0773]], + "As": [[75, 74.92159458, 1]], + "Se": [ + [74, 73.922475934, 0.0089], + [76, 75.919213704, 0.0937], + [77, 76.919914155, 0.0763], + [78, 77.91730928, 0.2377], + [80, 79.9165218, 0.4961], + [82, 81.9166995, 0.0873], + ], + "Br": [[79, 78.9183376, 0.5069], [81, 80.9162897, 0.4931]], + "Kr": [ + [78, 77.92036495, 0.00355], + [80, 79.91637809, 0.02286], + [82, 81.91348274, 0.11593], + [83, 82.91412716, 0.11500], + [84, 83.9114977282, 0.56987], + [86, 85.9106106269, 0.17279], + ], + "Rb": [[85, 84.9117897380, 0.7217], [87, 86.9091805311, 0.2783]], + "Sr": [[84, 83.9134191, 0.0056], [86, 85.9092606, 0.0986], [87, 86.9088775, 0.0700], [88, 87.9056125, 0.8258]], + "Y": [[89, 88.9058403, 1]], + "Zr": [[90, 89.9046977, 0.5145], [91, 90.9056396, 0.1122], [92, 91.9050347, 0.1715], [94, 93.9063108, 0.1738], [96, 95.9082714, 0.0280]], + "Nb": [[93, 92.9063730, 1]], + "Mo": [ + [92, 91.90680797, 0.1453], + [94, 93.90508490, 0.0915], + [95, 94.90583877, 0.1584], + [96, 95.90467612, 0.1667], + [97, 96.90601812, 0.0960], + [98, 97.90540482, 0.2439], + [100, 99.9074718, 0.0982], + ], + "Tc": [ + [ + 97, + 96.9063667, + ], + [98, 97.9072124], + [99, 98.9062508], + ], + "Ru": [ + [96, 95.90759025, 0.0554], + [98, 97.9052869, 0.0187], + [99, 98.9059341, 0.1276], + [100, 99.9042143, 0.1260], + [101, 100.9055769, 0.1706], + [102, 101.9043441, 0.3155], + [104, 103.9054275, 0.1862], + ], + "Rh": [[103, 102.905498, 1]], + "Pd": [ + [102, 101.9056022, 0.0102], + [104, 103.9040305, 0.1114], + [105, 104.9050796, 0.2233], + [106, 105.9034804, 0.2733], + [108, 107.9038916, 0.2646], + [110, 109.90517221, 0.1172], + ], + "Ag": [[107, 106.9050916, 0.51839], [109, 108.9047553, 0.48161]], + "Cd": [ + [106, 105.9064599, 0.0125], + [108, 107.9041834, 0.0089], + [110, 109.90300662, 0.1249], + [111, 110.90418288, 0.1280], + [112, 111.9027629, 0.2413], + [113, 112.9044081, 0.1222], + [114, 113.9033651, 0.2873], + [116, 115.9047632, 0.0749], + ], + "In": [[113, 112.9040618, 0.0429], [115, 114.9038788, 0.9571]], + "Sn": [ + [112, 111.9048239, 0.0097], + [114, 113.9027827, 0.0066], + [115, 114.9033447, 0.0034], + [116, 115.9017428, 0.1454], + [117, 116.902954, 0.0768], + [118, 117.9016066, 0.2422], + [119, 118.9033112, 0.0859], + [120, 119.9022016, 0.3258], + [122, 121.9034438, 0.0463], + [124, 123.9052766, 0.0579], + ], + "Sb": [[121, 120.903812, 0.5721], [123, 122.9042132, 0.4279]], + "Te": [ + [120, 119.9040593, 0.0009], + [122, 121.9030435, 0.0255], + [123, 122.9042698, 0.0089], + [124, 123.9028171, 0.0474], + [125, 124.9044299, 0.0707], + [126, 125.9033109, 0.1884], + [128, 127.9044613, 0.3174], + [130, 129.9062227, 0.3408], + ], + "I": [[127, 126.9044719, 1]], + "Xe": [ + [124, 123.905892, 0.000952], + [126, 125.9042983, 0.000890], + [128, 127.903531, 0.019102], + [129, 128.9047809, 0.264006], + [130, 129.9035093, 0.040710], + [131, 130.9050841, 0.212324], + [132, 131.9041551, 0.269086], + [134, 133.9053947, 0.104357], + [136, 135.9072145, 0.088573], + ], + "Cs": [[133, 132.905452, 1]], + "Ba": [ + [130, 129.9063207, 0.00106], + [132, 131.9050611, 0.00101], + [134, 133.9045082, 0.02417], + [135, 134.9056884, 0.06592], + [136, 135.9045757, 0.07854], + [137, 136.9058271, 0.11232], + [138, 137.905247, 0.71698], + ], + "La": [[138, 137.9071149, 0.0008881], [139, 138.9063563, 0.9991119]], + "Ce": [[136, 135.9071292, 0.00185], [138, 137.905991, 0.00251], [140, 139.9054431, 0.88450], [142, 141.9092504, 0.11114]], + "Pr": [[141, 140.9076576, 1]], + "Nd": [ + [142, 141.907729, 0.27152], + [143, 142.90982, 0.12174], + [144, 143.910093, 0.23798], + [145, 144.9125793, 0.08293], + [146, 145.9131226, 0.17189], + [148, 147.9168993, 0.05756], + [150, 149.9209022, 0.05638], + ], + "Pm": [[145, 144.9127559], [147, 146.915145]], + "Sm": [ + [144, 143.9120065, 0.0307], + [147, 146.9149044, 0.1499], + [148, 147.9148292, 0.1124], + [149, 148.9171921, 0.1382], + [150, 149.9172829, 0.0738], + [152, 151.9197397, 0.2675], + [154, 153.9222169, 0.2275], + ], + "Eu": [[151, 150.9198578, 0.4781], [153, 152.921238, 0.5219]], + "Gd": [ + [152, 151.9197995, 0.0020], + [154, 153.9208741, 0.0218], + [155, 154.9226305, 0.1480], + [156, 155.9221312, 0.2047], + [157, 156.9239686, 0.1565], + [158, 157.9241123, 0.2484], + [160, 159.9270624, 0.2186], + ], + "Tb": [[159, 158.9253547, 1]], + "Dy": [ + [156, 155.9242847, 0.00056], + [158, 157.9244159, 0.00095], + [160, 159.9252046, 0.02329], + [161, 160.9269405, 0.18889], + [162, 161.9268056, 0.25475], + [163, 162.9287383, 0.24896], + [164, 163.9291819, 0.28260], + ], + "Ho": [[165, 164.9303288, 1]], + "Er": [ + [162, 161.9287884, 0.00139], + [164, 163.9292088, 0.01601], + [166, 165.9302995, 0.33503], + [167, 166.9320546, 0.22869], + [168, 167.9323767, 0.26978], + [170, 169.9354702, 0.14910], + ], + "Tm": [[169, 168.9342179, 1]], + "Yb": [ + [168, 167.9338896, 0.00123], + [170, 169.9347664, 0.02982], + [171, 170.9363302, 0.1409], + [172, 171.9363859, 0.2168], + [173, 172.9382151, 0.16103], + [174, 173.9388664, 0.32026], + [176, 175.9425764, 0.12996], + ], + "Lu": [[175, 174.9407752, 0.97401], [176, 175.9426897, 0.02599]], + "Hf": [ + [174, 173.9400461, 0.0016], + [176, 175.9414076, 0.0526], + [177, 176.9432277, 0.1860], + [178, 177.9437058, 0.2728], + [179, 178.9458232, 0.1362], + [180, 179.946557, 0.3508], + ], + "Ta": [[180, 179.9474648, 0.0001201], [181, 180.9479958, 0.9998799]], + "W": [[180, 179.9467108, 0.0012], [182, 181.9482039, 0.2650], [183, 182.9502228, 0.1431], [184, 183.9509309, 0.3064], [186, 185.9543628, 0.2843]], + "Re": [[185, 184.9529545, 0.3740], [187, 186.9557501, 0.6260]], + "Os": [ + [184, 183.9524885, 0.0002], + [186, 185.953835, 0.0159], + [187, 186.9557474, 0.0196], + [188, 187.9558352, 0.1324], + [189, 188.9581442, 0.1615], + [190, 189.9584437, 0.2626], + [192, 191.961477, 0.4078], + ], + "Ir": [[191, 190.9605893, 0.373], [193, 192.9629216, 0.627]], + "Pt": [ + [190, 189.9599297, 0.00012], + [192, 191.9610387, 0.00782], + [194, 193.9626809, 0.3286], + [195, 194.9647917, 0.3378], + [196, 195.9649521, 0.2521], + [198, 197.9678949, 0.07356], + ], + "Au": [[197, 196.9665688, 1]], + "Hg": [ + [196, 195.9658326, 0.0015], + [198, 197.9667686, 0.0997], + [199, 198.9682806, 0.1687], + [200, 199.9683266, 0.2310], + [201, 200.9703028, 0.1318], + [202, 201.9706434, 0.2986], + [204, 203.973494, 0.0687], + ], + "Tl": [[203, 202.9723446, 0.2952], [205, 204.9744278, 0.7048]], + "Pb": [[204, 203.973044, 0.014], [206, 205.9744657, 0.241], [207, 206.9758973, 0.221], [208, 207.9766525, 0.524]], + "Bi": [[209, 208.9803991, 1]], + "Po": [[209, 208.9824308], [210, 209.9828741]], + "At": [[210, 209.9871479], [211, 210.9874966]], + "Rn": [[211, 210.9906011], [220, 220.0113941], [222, 222.0175782]], + "Fr": [[223, 223.019736]], + "Ra": [[223, 223.0185023], [224, 224.020212], [226, 226.0254103], [228, 228.0310707]], + "Ac": [[227, 227.0277523]], + "Th": [[230, 230.0331341, 0], [232, 232.0380558, 1]], + "Pa": [[231, 231.0358842, 1]], + "U": [[233, 233.0396355, 0], [234, 234.0409523, 0.000054], [235, 235.0439301, 0.007204], [236, 236.0455682, 0], [238, 238.0507884, 0.992742]], + "Np": [[236, 236.04657], [237, 237.0481736]], + "Pu": [[238, 238.0495601], [239, 239.0521636], [240, 240.0538138], [241, 241.0568517], [242, 242.0587428], [244, 244.0642053]], + "Am": [[241, 241.0568293], [243, 243.0613813]], + "Cm": [[243, 243.0613893], [244, 244.0627528], [245, 245.0654915], [246, 246.0672238], [247, 247.0703541], [248, 248.0723499]], + "Bk": [[247, 247.0703073], [249, 249.0749877]], + "Cf": [[249, 249.0748539], [250, 250.0764062], [251, 251.0795886], [252, 252.0816272]], + "Es": [[252, 252.08298]], + "Fm": [[257, 257.0951061]], + "Md": [[258, 258.0984315], [260, 260.10365]], + "No": [[259, 259.10103]], + "Lr": [[262, 262.10961]], + "Rf": [[267, 267.12179]], + "Db": [[268, 268.12567]], + "Sg": [[271, 271.13393]], + "Bh": [[272, 272.13826]], + "Hs": [[270, 270.13429]], + "Mt": [[276, 276.15159]], + "Ds": [[281, 281.16451]], + "Rg": [[280, 280.16514]], + "Cn": [[285, 285.17712]], + "Nh": [[284, 284.17873]], + "Fl": [[289, 289.19042]], + "Mc": [[288, 288.19274]], + "Lv": [[293, 293.20449]], + "Ts": [[292, 292.20746]], + "Og": [[294, 294.21392]], +} def get_center_of_mass(coords, numbers=None, symbols=None): @@ -625,10 +903,10 @@ def get_center_of_mass(coords, numbers=None, symbols=None): np.array: The center of mass coordinates. """ if symbols is None and numbers is None: - raise IndexError('Either symbols or numbers must be given.') + raise IndexError("Either symbols or numbers must be given.") if numbers is not None: symbols = [symbol_by_number[number] for number in numbers] - center, total_mass = np.zeros(3, np.float64), 0 + center, total_mass = np.zeros(3, float), 0 for coord, symbol in zip(coords, symbols): mass = get_element_mass(symbol)[0] center += mass * coord @@ -655,13 +933,12 @@ def get_moment_of_inertia_tensor(coords, numbers=None, symbols=None): InputError: If neither ``symbols`` nor ``numbers`` are given, or if they have a different length than ``coords`` """ if symbols is None and numbers is None: - raise InputError('Either symbols or numbers must be given.') + raise InputError("Either symbols or numbers must be given.") if numbers is not None: symbols = [symbol_by_number[number] for number in numbers] if len(coords) != len(symbols): - raise InputError(f'The number of atoms ({len(symbols)}) is not equal to the number of ' - f'atomic coordinates ({len(list(coords))})') - tensor = np.zeros((3, 3), np.float64) + raise InputError(f"The number of atoms ({len(symbols)}) is not equal to the number of atomic coordinates ({len(list(coords))})") + tensor = np.zeros((3, 3), float) center_of_mass = get_center_of_mass(coords=coords, numbers=numbers, symbols=symbols) for symbol, coord in zip(symbols, coords): mass = get_element_mass(symbol)[0] @@ -697,17 +974,17 @@ def get_principal_moments_of_inertia(coords, numbers=None, symbols=None): tensor0 = get_moment_of_inertia_tensor(coords=coords, numbers=numbers, symbols=symbols) # Since tensor0 is real and symmetric, diagonalization is always possible principal_moments_of_inertia, axes = np.linalg.eig(tensor0) - principal_moments_of_inertia, axes = zip(*sorted(zip(np.ndarray.tolist(principal_moments_of_inertia), - np.ndarray.tolist(axes)), reverse=True)) + principal_moments_of_inertia, axes = zip(*sorted(zip(np.ndarray.tolist(principal_moments_of_inertia), np.ndarray.tolist(axes)), reverse=True)) return principal_moments_of_inertia, axes -def clean_dir(base_dir_path: str = '', - files_to_delete: List[str] = None, - file_extensions_to_delete: List[str] = None, - files_to_keep: List[str] = None, - sub_dir_to_keep: List[str] = None, - ) -> None: +def clean_dir( + base_dir_path: str = "", + files_to_delete: List[str] = None, + file_extensions_to_delete: List[str] = None, + files_to_keep: List[str] = None, + sub_dir_to_keep: List[str] = None, +) -> None: """ Clean up a directory. Commonly used for removing unwanted files after unit tests. @@ -744,6 +1021,6 @@ def convert_imaginary_freq_to_negative_float(freq: Union[str, float, int]): Returns: float: A float representation of the frequency value. """ - if isinstance(freq, str) and freq.endswith('i'): + if isinstance(freq, str) and freq.endswith("i"): freq = float(freq[:-1]) * -1 return float(freq) diff --git a/arkane/commonTest.py b/arkane/commonTest.py deleted file mode 100644 index 50bff7fe77..0000000000 --- a/arkane/commonTest.py +++ /dev/null @@ -1,572 +0,0 @@ -#!/usr/bin/env python3 - -############################################################################### -# # -# RMG - Reaction Mechanism Generator # -# # -# Copyright (c) 2002-2021 Prof. William H. Green (whgreen@mit.edu), # -# Prof. Richard H. West (r.west@neu.edu) and the RMG Team (rmg_dev@mit.edu) # -# # -# Permission is hereby granted, free of charge, to any person obtaining a # -# copy of this software and associated documentation files (the 'Software'), # -# to deal in the Software without restriction, including without limitation # -# the rights to use, copy, modify, merge, publish, distribute, sublicense, # -# and/or sell copies of the Software, and to permit persons to whom the # -# Software is furnished to do so, subject to the following conditions: # -# # -# The above copyright notice and this permission notice shall be included in # -# all copies or substantial portions of the Software. # -# # -# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # -# DEALINGS IN THE SOFTWARE. # -# # -############################################################################### - -""" -This module contains unit tests of the :mod:`arkane.common` module. -""" - -import logging -import os -import shutil -import unittest - -import numpy as np - -import rmgpy -import rmgpy.constants as constants -from rmgpy.pdep.collision import SingleExponentialDown -from rmgpy.quantity import ScalarQuantity -from rmgpy.species import Species, TransitionState -from rmgpy.thermo import NASA, ThermoData - -from arkane import Arkane, input -from arkane.common import (ArkaneSpecies, - convert_imaginary_freq_to_negative_float, - get_element_mass, - get_center_of_mass, - get_moment_of_inertia_tensor, - get_principal_moments_of_inertia, - ) -from arkane.input import job_list -from arkane.modelchem import LevelOfTheory -from arkane.statmech import InputError, StatMechJob - -################################################################################ - - -class CommonTest(unittest.TestCase): - """ - Contains unit tests of Arkane's common functions. - """ - - def test_check_conformer_energy(self): - """ - test the check_conformer_energy function with an list of energies. - """ - v_list = [-272.2779012225, -272.2774933703, -272.2768397635, -272.2778432059, -272.278645477, -272.2789602654, - -272.2788749196, -272.278496709, -272.2779350675, -272.2777008843, -272.2777167286, -272.2780937643, - -272.2784838846, -272.2788050464, -272.2787865352, -272.2785091607, -272.2779977452, -272.2777957743, - -272.2779134906, -272.2781827547, -272.278443339, -272.2788244214, -272.2787748749] - v_list = np.array(v_list, np.float64) - v_diff = (v_list[0] - np.min(v_list)) * constants.E_h * constants.Na / 1000 - self.assertAlmostEqual(v_diff / 2.7805169838282797, 1, 5) - - -class TestArkaneJob(unittest.TestCase): - """ - Contains unit tests of the Arkane module and its interactions with other RMG modules. - """ - - @classmethod - def setUp(cls): - """A method that is run before each unit test in this class""" - arkane = Arkane() - job_list = arkane.load_input_file(os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'data', 'methoxy.py')) - pdepjob = job_list[-1] - cls.kineticsjob = job_list[0] - pdepjob.active_j_rotor = True - network = pdepjob.network - cls.Nisom = len(network.isomers) - cls.Nreac = len(network.reactants) - cls.Nprod = len(network.products) - cls.Npath = len(network.path_reactions) - cls.PathReaction2 = network.path_reactions[2] - cls.TminValue = pdepjob.Tmin.value - cls.Tmaxvalue = pdepjob.Tmax.value - cls.TmaxUnits = pdepjob.Tmax.units - cls.TlistValue = pdepjob.Tlist.value - cls.PminValue = pdepjob.Pmin.value - cls.Pcount = pdepjob.Pcount - cls.Tcount = pdepjob.Tcount - cls.GenTlist = pdepjob.generate_T_list() - cls.PlistValue = pdepjob.Plist.value - cls.maximum_grain_size_value = pdepjob.maximum_grain_size.value - cls.method = pdepjob.method - cls.rmgmode = pdepjob.rmgmode - - # test Arkane's interactions with the network module - def test_num_isom(self): - """ - Test the number of isomers identified. - """ - self.assertEqual(self.Nisom, 2) - - def test_num_reac(self): - """ - Test the number of reactants identified. - """ - self.assertEqual(self.Nreac, 1) - - def test_num_prod(self): - """ - Test the number of products identified. - """ - self.assertEqual(self.Nprod, 1) - - def test_n_path_reactions(self): - """ - Test the whether or not RMG mode is turned on. - """ - self.assertEqual(self.Npath, 3) - - def test_path_reactions(self): - """ - Test a path reaction label - """ - self.assertEqual(str(self.PathReaction2), 'CH2OH <=> methoxy') - - # test Arkane's interactions with the pdep module - def test_temperatures_units(self): - """ - Test the Temperature Units. - """ - self.assertEqual(str(self.TmaxUnits), 'K') - - def test_temperatures_value(self): - """ - Test the temperature value. - """ - self.assertEqual(self.TminValue, 450.0) - - def test_temperatures_list(self): - """ - Test the temperature list. - """ - self.assertTrue(np.array_equal(self.TlistValue, np.array([450, 500, 678, 700]))) - - def test_min_pressure_value(self): - """ - Test the minimum pressure value. - """ - self.assertEqual("%0.7f" % self.PminValue, str(0.0101325)) - - def test_pressure_count(self): - """ - Test the number pressures specified. - """ - self.assertEqual(self.Pcount, 7) - - def test_temperature_count(self): - """ - Test the number temperatures specified. - """ - self.assertEqual(self.Tcount, 4) - - def test_pressure_list(self): - """ - Test the pressure list. - """ - self.assertTrue(np.array_equal(self.PlistValue, np.array([0.01, 0.1, 1, 3, 10, 100, 1000]))) - - def test_generate_temperature_list(self): - """ - Test the generated temperature list. - """ - self.assertEqual(list(self.GenTlist), [450.0, 500.0, 678.0, 700.0]) - - def test_maximum_grain_size_value(self): - """ - Test the max grain size value. - """ - self.assertEqual(self.maximum_grain_size_value, 0.5) - - def test_method(self): - """ - Test the master equation solution method chosen. - """ - self.assertEqual(self.method, 'modified strong collision') - - def test_rmg_mode(self): - """ - Test the whether or not RMG mode is turned on. - """ - self.assertEqual(self.rmgmode, False) - - # Test Arkane's interactions with the kinetics module - def test_calculate_tst_rate_coefficient(self): - """ - Test the calculation of the high-pressure limit rate coef for one of the kinetics jobs at Tmin and Tmax. - """ - self.assertEqual("%0.7f" % self.kineticsjob.reaction.calculate_tst_rate_coefficient(self.TminValue), - str(46608.5904933)) - self.assertEqual("%0.5f" % self.kineticsjob.reaction.calculate_tst_rate_coefficient(self.Tmaxvalue), - str(498796.64535)) - - def test_tunneling(self): - """ - Test the whether or not tunneling has been included in a specific kinetics job. - """ - self.assertEqual(self.kineticsjob.reaction.transition_state.tunneling, None) - - -class TestArkaneInput(unittest.TestCase): - """ - Contains unit tests for loading and processing Arkane input files. - """ - - @classmethod - def setUp(cls): - """Preparation for all unit tests in this class.""" - cls.directory = os.path.join(os.path.dirname(os.path.dirname(rmgpy.__file__)), 'examples', 'arkane') - cls.level_of_theory = LevelOfTheory("cbs-qb3") - cls.frequencyScaleFactor = 0.99 - cls.useHinderedRotors = False - cls.useBondCorrections = True - - def test_species(self): - """Test loading of species input file.""" - spec = input.species('C2H4', os.path.join(self.directory, 'species', 'C2H4', 'ethene.py')) - self.assertTrue(isinstance(spec, Species)) - self.assertEqual(len(spec.molecule), 0) - - def test_species_statmech(self): - """Test loading of statmech job from species input file.""" - job = job_list[-1] - self.assertTrue(isinstance(job, StatMechJob)) - job.level_of_theory = self.level_of_theory - job.frequencyScaleFactor = self.frequencyScaleFactor - job.includeHinderedRotors = self.useHinderedRotors - job.applyBondEnergyCorrections = self.useBondCorrections - job.load() - self.assertTrue(isinstance(job.species.props['element_counts'], dict)) - self.assertEqual(job.species.props['element_counts']['C'], 2) - self.assertEqual(job.species.props['element_counts']['H'], 4) - - def test_species_thermo(self): - """Test thermo job execution for species from separate input file.""" - input.thermo('C2H4', 'NASA') - job = job_list[-1] - filepath = os.path.join(self.directory, 'reactions', 'H+C2H4=C2H5') - job.execute(output_directory=filepath) - self.assertTrue(os.path.isfile(os.path.join(filepath, 'output.py'))) - self.assertTrue(os.path.isfile(os.path.join(filepath, 'chem.inp'))) - os.remove(os.path.join(filepath, 'output.py')) - os.remove(os.path.join(filepath, 'chem.inp')) - - def test_transition_state(self): - """Test loading of transition state input file.""" - ts = input.transitionState('TS', os.path.join(self.directory, 'reactions', 'H+C2H4=C2H5', 'TS.py')) - self.assertTrue(isinstance(ts, TransitionState)) - - def test_transition_state_statmech(self): - """Test loading of statmech job from transition state input file.""" - job = job_list[-1] - self.assertTrue(isinstance(job, StatMechJob)) - job.level_of_theory = self.level_of_theory - job.frequencyScaleFactor = self.frequencyScaleFactor - job.includeHinderedRotors = self.useHinderedRotors - job.applyBondEnergyCorrections = self.useBondCorrections - job.load() - - -class TestStatmech(unittest.TestCase): - """ - Contains unit tests of statmech.py - """ - - @classmethod - def setUp(cls): - """A method that is run before each unit test in this class""" - arkane = Arkane() - cls.job_list = arkane.load_input_file(os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'data', 'Benzyl', 'input.py')) - - def test_gaussian_log_file_error(self): - """Test that the proper error is raised if gaussian geometry and frequency file paths are the same""" - job = self.job_list[-2] - self.assertTrue(isinstance(job, StatMechJob)) - with self.assertRaises(InputError): - job.load() - - -class TestArkaneSpecies(unittest.TestCase): - """ - Contains YAML dump and load unit tests for :class:ArkaneSpecies - """ - - @classmethod - def setUpClass(cls): - """ - A method that is run ONCE before all unit tests in this class. - """ - cls.arkane = Arkane() - path = os.path.join(os.path.dirname(os.path.dirname(rmgpy.__file__)), - 'examples', 'arkane', 'species') - cls.dump_path = os.path.join(path, 'C2H6') - cls.dump_input_path = os.path.join(cls.dump_path, 'input.py') - cls.dump_output_file = os.path.join(cls.dump_path, 'output.py') - cls.dump_yaml_file = os.path.join(cls.dump_path, 'species', 'C2H6.yml') - - cls.load_path = os.path.join(path, 'C2H6_from_yaml') - cls.load_input_path = os.path.join(cls.load_path, 'input.py') - cls.load_output_file = os.path.join(cls.load_path, 'output.py') - - cls.data_path = os.path.join(os.path.dirname(os.path.dirname(rmgpy.__file__)), 'arkane', 'data') - - if os.path.exists(cls.dump_yaml_file): - logging.debug('removing existing yaml file {0} before running tests'.format(cls.dump_yaml_file)) - os.remove(cls.dump_yaml_file) - - def test_dump_yaml(self): - """ - Test properly dumping the ArkaneSpecies object and respective sub-objects - """ - job_list = self.arkane.load_input_file(self.dump_input_path) - for job in job_list: - job.execute(output_directory=self.dump_path) - self.assertTrue(os.path.isfile(self.dump_output_file)) - - def test_create_and_load_yaml(self): - """ - Test properly loading the ArkaneSpecies object and respective sub-objects - """ - # Create YAML file by running Arkane - job_list = self.arkane.load_input_file(self.dump_input_path) - for job in job_list: - job.execute(output_directory=self.dump_path) - - # Load in newly created YAML file - arkane_spc_old = job_list[0].arkane_species - arkane_spc = ArkaneSpecies.__new__(ArkaneSpecies) - arkane_spc.load_yaml(path=os.path.join(self.dump_path, 'species', arkane_spc_old.label + '.yml')) - - self.assertIsInstance(arkane_spc, ArkaneSpecies) # checks make_object - self.assertIsInstance(arkane_spc.molecular_weight, ScalarQuantity) - self.assertIsInstance(arkane_spc.thermo, NASA) - self.assertNotEqual(arkane_spc.author, '') - self.assertEqual(arkane_spc.inchi, 'InChI=1S/C2H6/c1-2/h1-2H3') - self.assertEqual(arkane_spc.inchi_key, 'OTMSDBZUPAUEDD-UHFFFAOYSA-N') - self.assertEqual(arkane_spc.smiles, 'CC') - self.assertTrue('8 H u0 p0 c0 {2,S}' in arkane_spc.adjacency_list) - self.assertEqual(arkane_spc.label, 'C2H6') - self.assertEqual(arkane_spc.frequency_scale_factor, 0.99 * 1.014) # checks float conversion - self.assertFalse(arkane_spc.use_bond_corrections) - self.assertAlmostEqual(arkane_spc.conformer.modes[2].frequencies.value_si[0], 830.38202, 4) # HarmonicOsc. - self.assertIsInstance(arkane_spc.energy_transfer_model, SingleExponentialDown) - self.assertFalse(arkane_spc.is_ts) - self.assertEqual(arkane_spc.level_of_theory, LevelOfTheory('cbs-qb3')) - self.assertIsInstance(arkane_spc.thermo_data, ThermoData) - self.assertTrue(arkane_spc.use_hindered_rotors) - self.assertIsInstance(arkane_spc.chemkin_thermo_string, str) - expected_xyz = """8 -C2H6 -C 0.00075400 0.00119300 0.00055200 -H 0.00074000 0.00117100 1.09413800 -H 1.04376600 0.00117100 -0.32820200 -H -0.44760300 0.94289500 -0.32825300 -C -0.76014200 -1.20389600 -0.55748300 -H -0.76012800 -1.20387400 -1.65106900 -H -0.31178500 -2.14559800 -0.22867800 -H -1.80315400 -1.20387400 -0.22872900""" - self.assertEqual(arkane_spc.xyz, expected_xyz) - - def test_load_existing_yaml(self): - """ - Test that existing Arkane YAML files can still be loaded - """ - # Load in YAML file - arkane_spc = ArkaneSpecies.__new__(ArkaneSpecies) - arkane_spc.load_yaml(path=os.path.join(self.load_path, 'C2H6.yml')) - - self.assertIsInstance(arkane_spc, ArkaneSpecies) # checks make_object - self.assertIsInstance(arkane_spc.molecular_weight, ScalarQuantity) - self.assertIsInstance(arkane_spc.thermo, NASA) - self.assertNotEqual(arkane_spc.author, '') - self.assertEqual(arkane_spc.inchi, 'InChI=1S/C2H6/c1-2/h1-2H3') - self.assertEqual(arkane_spc.inchi_key, 'OTMSDBZUPAUEDD-UHFFFAOYSA-N') - self.assertEqual(arkane_spc.smiles, 'CC') - self.assertTrue('8 H u0 p0 c0 {2,S}' in arkane_spc.adjacency_list) - self.assertEqual(arkane_spc.label, 'C2H6') - self.assertEqual(arkane_spc.frequency_scale_factor, 0.99) # checks float conversion - self.assertFalse(arkane_spc.use_bond_corrections) - self.assertAlmostEqual(arkane_spc.conformer.modes[2].frequencies.value_si[0], 818.91718, 4) # HarmonicOsc. - self.assertIsInstance(arkane_spc.energy_transfer_model, SingleExponentialDown) - self.assertFalse(arkane_spc.is_ts) - self.assertTrue(arkane_spc.use_hindered_rotors) - self.assertTrue('C 7.54e-14 1.193e-13 5.52e-14' in arkane_spc.xyz) - self.assertIsInstance(arkane_spc.chemkin_thermo_string, str) - - def test_loading_different_versions_of_yaml(self): - """Test loading a YAML file generated by RMG v 2.4.1 and by a more recent version""" - arkane_spc_v_241 = ArkaneSpecies.__new__(ArkaneSpecies) - arkane_spc_v_241.load_yaml(path=os.path.join(self.data_path, 'vinoxy_v_2.4.1.yml')) - self.assertIsInstance(arkane_spc_v_241, ArkaneSpecies) # checks make_object - self.assertEqual(arkane_spc_v_241.conformer.spin_multiplicity, 2) - - arkane_current = ArkaneSpecies.__new__(ArkaneSpecies) - arkane_current.load_yaml(path=os.path.join(self.data_path, 'vinoxy_current.yml')) - self.assertIsInstance(arkane_current, ArkaneSpecies) # checks make_object - self.assertEqual(arkane_current.conformer.spin_multiplicity, 2) - - @classmethod - def tearDownClass(cls): - """ - A method that is run ONCE after all unit tests in this class. - """ - path = os.path.join(os.path.dirname(os.path.dirname(rmgpy.__file__)), - 'examples', 'arkane', 'species') - cls.dump_path = os.path.join(path, 'C2H6') - cls.load_path = os.path.join(path, 'C2H6_from_yaml') - cls.extensions_to_delete = ['pdf', 'txt', 'inp', 'csv'] - cls.files_to_delete = ['arkane.log', 'output.py'] - cls.files_to_keep = ['C2H6.yml'] - for path in [cls.dump_path, cls.load_path]: - for name in os.listdir(path): - item_path = os.path.join(path, name) - if os.path.isfile(item_path): - extension = name.split('.')[-1] - if name in cls.files_to_delete or \ - (extension in cls.extensions_to_delete and name not in cls.files_to_keep): - os.remove(item_path) - else: - # This is a sub-directory. remove. - shutil.rmtree(item_path) - - -class TestMomentOfInertia(unittest.TestCase): - """ - Contains unit tests for attaining moments of inertia from the 3D coordinates. - """ - - def test_get_mass(self): - """Test that the correct mass/number/isotope is returned from get_element_mass""" - self.assertEqual(get_element_mass(1), (1.00782503224, 1)) # test input by integer - self.assertEqual(get_element_mass('Si'), (27.97692653465, 14)) # test string input and most common isotope - self.assertEqual(get_element_mass('SI'), (27.97692653465, 14)) # test string in all caps - self.assertEqual(get_element_mass('C', 13), (13.00335483507, 6)) # test specific isotope - self.assertEqual(get_element_mass('Bk'), (247.0703073, 97)) # test a two-element array (no isotope data) - - def test_get_center_of_mass(self): - """Test attaining the center of mass""" - symbols = ['C', 'H', 'H', 'H', 'H'] - coords = np.array([[0.0000000, 0.0000000, 0.0000000], - [0.6269510, 0.6269510, 0.6269510], - [-0.6269510, -0.6269510, 0.6269510], - [-0.6269510, 0.6269510, -0.6269510], - [0.6269510, -0.6269510, -0.6269510]], np.float64) - center_of_mass = get_center_of_mass(coords=coords, symbols=symbols) - for cm_coord in center_of_mass: - self.assertEqual(cm_coord, 0.0) - - symbols = ['O', 'C', 'C', 'H', 'H', 'H', 'H', 'H', 'H'] - coords = np.array([[1.28706525, 0.52121353, 0.04219198], - [0.39745682, -0.35265044, -0.63649234], - [0.36441173, -1.68197093, 0.08682400], - [-0.59818222, 0.10068325, -0.65235399], - [0.74799641, -0.48357798, -1.66461710], - [0.03647269, -1.54932006, 1.12314420], - [-0.31340646, -2.38081353, -0.41122551], - [1.36475837, -2.12581592, 0.12433596], - [2.16336803, 0.09985803, 0.03295192]], np.float64) - center_of_mass = get_center_of_mass(coords=coords, symbols=symbols) - self.assertAlmostEqual(center_of_mass[0], 0.7201, 3) - self.assertAlmostEqual(center_of_mass[1], -0.4880, 3) - self.assertAlmostEqual(center_of_mass[2], -0.1603, 3) - - numbers = [6, 6, 8, 1, 1, 1, 1, 1, 1] - coords = np.array([[1.1714680, -0.4048940, 0.0000000], - [0.0000000, 0.5602500, 0.0000000], - [-1.1945070, -0.2236470, 0.0000000], - [-1.9428910, 0.3834580, 0.0000000], - [2.1179810, 0.1394450, 0.0000000], - [1.1311780, -1.0413680, 0.8846660], - [1.1311780, -1.0413680, -0.8846660], - [0.0448990, 1.2084390, 0.8852880], - [0.0448990, 1.2084390, -0.8852880]], np.float64) - center_of_mass = get_center_of_mass(coords=coords, numbers=numbers) - self.assertAlmostEqual(center_of_mass[0], -0.0540, 3) - self.assertAlmostEqual(center_of_mass[1], -0.0184, 3) - self.assertAlmostEqual(center_of_mass[2], -0.0000, 3) - - def test_get_moment_of_inertia_tensor(self): - """Test calculating the moment of inertia tensor""" - symbols = ['O', 'C', 'C', 'H', 'H', 'H', 'H', 'H', 'H'] - coords = np.array([[1.28706525, 0.52121353, 0.04219198], - [0.39745682, -0.35265044, -0.63649234], - [0.36441173, -1.68197093, 0.08682400], - [-0.59818222, 0.10068325, -0.65235399], - [0.74799641, -0.48357798, -1.66461710], - [0.03647269, -1.54932006, 1.12314420], - [-0.31340646, -2.38081353, -0.41122551], - [1.36475837, -2.12581592, 0.12433596], - [2.16336803, 0.09985803, 0.03295192]], np.float64) - tensor = get_moment_of_inertia_tensor(coords=coords, symbols=symbols) - expected_tensor = [[50.24197604, -15.43600683, -3.07977736], - [-15.43600683, 22.20416597, 2.5935549], - [-3.07977736, 2.5935549, 55.49144794]] - np.testing.assert_almost_equal(tensor, expected_tensor) - - def test_get_principal_moments_of_inertia(self): - """Test calculating the principal moments of inertia""" - numbers = [6, 6, 8, 1, 1, 1, 1, 1, 1] - coords = np.array([[1.235366, -0.257231, -0.106315], - [0.083698, 0.554942, 0.046628], - [-1.210594, -0.239505, -0.021674], - [0.132571, 1.119728, 0.987719], - [0.127795, 1.278999, -0.769346], - [-1.272620, -0.962700, 0.798216], - [-2.074974, 0.426198, 0.055846], - [-1.275744, -0.785745, -0.965493], - [1.241416, -0.911257, 0.593856]], np.float64) - principal_moments_of_inertia = get_principal_moments_of_inertia(coords=coords, numbers=numbers)[0] - expected_principal_moments_of_inertia = [60.98026894, 53.83156297, 14.48858465] - for moment, expected_moment in zip(principal_moments_of_inertia, expected_principal_moments_of_inertia): - self.assertAlmostEqual(moment, expected_moment) - - symbols = ['N', 'O', 'O'] # test a linear molecule - coords = np.array([[0.000000, 0.000000, 1.106190], - [0.000000, 0.000000, -0.072434], - [0.000000, 0.000000, -1.191782]], np.float64) - with self.assertRaises(InputError): - get_principal_moments_of_inertia(coords=coords, numbers=numbers) - principal_moments_of_inertia, axes = get_principal_moments_of_inertia(coords=coords, symbols=symbols) - expected_principal_moments_of_inertia = [39.4505153, 39.4505153, 0.0] - expected_axes = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] - for moment, expected_moment in zip(principal_moments_of_inertia, expected_principal_moments_of_inertia): - self.assertAlmostEqual(moment, expected_moment) - for axis, expected_axis in zip(axes, expected_axes): - for entry, expected_entry in zip(axis, expected_axis): - self.assertAlmostEqual(entry, expected_entry) - self.assertIsInstance(principal_moments_of_inertia, tuple) - self.assertIsInstance(axes, tuple) - - def test_convert_imaginary_freq_to_negative_float(self): - self.assertEqual(convert_imaginary_freq_to_negative_float(1), 1) - self.assertEqual(convert_imaginary_freq_to_negative_float(-5.2), -5.2) - self.assertEqual(convert_imaginary_freq_to_negative_float('-5.2'), -5.2) - self.assertEqual(convert_imaginary_freq_to_negative_float('5.2'), 5.2) - self.assertEqual(convert_imaginary_freq_to_negative_float('5.2i'), -5.2) - self.assertEqual(convert_imaginary_freq_to_negative_float('635.8i'), -635.8) - -################################################################################ - - -if __name__ == '__main__': - unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arkane/encorr/isodesmic.py b/arkane/encorr/isodesmic.py index 407fcfc531..e3ecb13082 100644 --- a/arkane/encorr/isodesmic.py +++ b/arkane/encorr/isodesmic.py @@ -76,21 +76,25 @@ def __init__(self, molecule, low_level_hf298, level_of_theory, high_level_hf298= if isinstance(molecule, Molecule): self.molecule = molecule else: - raise ValueError(f'ErrorCancelingSpecies molecule attribute must be an rmgpy Molecule object. Instead a ' - f'{type(molecule)} object was given') + raise ValueError( + f"ErrorCancelingSpecies molecule attribute must be an rmgpy Molecule object. Instead a " f"{type(molecule)} object was given" + ) if isinstance(level_of_theory, LOT): self.level_of_theory = level_of_theory else: - raise ValueError(f'The level of theory used to calculate the low level Hf298 must be provided ' - f'for consistency checks. Instead, a {type(level_of_theory)} object was given') + raise ValueError( + f"The level of theory used to calculate the low level Hf298 must be provided " + f"for consistency checks. Instead, a {type(level_of_theory)} object was given" + ) if not isinstance(low_level_hf298, ScalarQuantity): if isinstance(low_level_hf298, tuple): low_level_hf298 = ScalarQuantity(*low_level_hf298) else: - raise TypeError(f'Low level Hf298 should be a ScalarQuantity object or its tuple representation, but ' - f'received {low_level_hf298} instead.') + raise TypeError( + f"Low level Hf298 should be a ScalarQuantity object or its tuple representation, but " f"received {low_level_hf298} instead." + ) self.low_level_hf298 = low_level_hf298 # If the species is a reference species, then the high level data is already known @@ -98,13 +102,14 @@ def __init__(self, molecule, low_level_hf298, level_of_theory, high_level_hf298= if isinstance(high_level_hf298, tuple): high_level_hf298 = ScalarQuantity(*high_level_hf298) else: - raise TypeError(f'High level Hf298 should be a ScalarQuantity object or its tuple representation, but ' - f'received {high_level_hf298} instead.') + raise TypeError( + f"High level Hf298 should be a ScalarQuantity object or its tuple representation, but " f"received {high_level_hf298} instead." + ) self.high_level_hf298 = high_level_hf298 self.source = source def __repr__(self): - return f'' + return f"" class ErrorCancelingReaction: @@ -131,22 +136,24 @@ def __init__(self, target, species): # Perform a consistency check that all species are using the same level of theory for spcs in species.keys(): if spcs.level_of_theory != self.level_of_theory: - raise ValueError(f'Species {spcs} has level of theory {spcs.level_of_theory}, which does not match the ' - f'level of theory of the reaction of {self.level_of_theory}') + raise ValueError( + f"Species {spcs} has level of theory {spcs.level_of_theory}, which does not match the " + f"level of theory of the reaction of {self.level_of_theory}" + ) # Does not include the target, which is handled separately. self.species = species def __repr__(self): - reactant_string = f'1*{self.target.molecule.to_smiles()}' - product_string = '' + reactant_string = f"1*{self.target.molecule.to_smiles()}" + product_string = "" for spcs, coeff in self.species.items(): if coeff > 0: - product_string += f' + {int(coeff)}*{spcs.molecule.to_smiles()}' + product_string += f" + {int(coeff)}*{spcs.molecule.to_smiles()}" else: - reactant_string += f' + {-1*int(coeff)}*{spcs.molecule.to_smiles()}' + reactant_string += f" + {-1*int(coeff)}*{spcs.molecule.to_smiles()}" - return f' {product_string[3:]} >' + return f" {product_string[3:]} >" def calculate_target_thermo(self): """ @@ -155,12 +162,10 @@ def calculate_target_thermo(self): Returns: rmgpy.quantity.ScalarQuantity: Hf298 in 'J/mol' estimated for the target species """ - low_level_h_rxn = sum(spec[0].low_level_hf298.value_si*spec[1] for spec in self.species.items()) - \ - self.target.low_level_hf298.value_si + low_level_h_rxn = sum(spec[0].low_level_hf298.value_si * spec[1] for spec in self.species.items()) - self.target.low_level_hf298.value_si - target_thermo = sum(spec[0].high_level_hf298.value_si*spec[1] for spec in self.species.items()) - \ - low_level_h_rxn - return ScalarQuantity(target_thermo, 'J/mol') + target_thermo = sum(spec[0].high_level_hf298.value_si * spec[1] for spec in self.species.items()) - low_level_h_rxn + return ScalarQuantity(target_thermo, "J/mol") class SpeciesConstraints: @@ -197,12 +202,10 @@ def _get_constraint_map(self): constraint_map = {label: i for i, label in enumerate(self.target.molecule.get_element_count().keys())} if self.conserve_bonds: j = len(constraint_map) - constraint_map.update( - {label: j + i for i, label in enumerate(self.target.molecule.enumerate_bonds().keys())}) + constraint_map.update({label: j + i for i, label in enumerate(self.target.molecule.enumerate_bonds().keys())}) if self.conserve_ring_size: j = len(constraint_map) - possible_rings_sizes = set(f'{len(sssr)}_ring' for sssr in - self.target.molecule.get_smallest_set_of_smallest_rings()) + possible_rings_sizes = set(f"{len(sssr)}_ring" for sssr in self.target.molecule.get_smallest_set_of_smallest_rings()) constraint_map.update({label: j + i for i, label in enumerate(possible_rings_sizes)}) return constraint_map @@ -233,7 +236,7 @@ def _enumerate_constraints(self, species): if self.conserve_ring_size: rings = molecule.get_smallest_set_of_smallest_rings() for ring in rings: - constraint_vector[self.constraint_map[f'{len(ring)}_ring']] += 1 + constraint_vector[self.constraint_map[f"{len(ring)}_ring"]] += 1 except KeyError: # This molecule has a feature not found in the target molecule. Return None to exclude this return None @@ -279,8 +282,7 @@ def __init__(self, target, reference_set, conserve_bonds, conserve_ring_size): """ self.target = target - self.constraints = SpeciesConstraints(target, reference_set, conserve_bonds=conserve_bonds, - conserve_ring_size=conserve_ring_size) + self.constraints = SpeciesConstraints(target, reference_set, conserve_bonds=conserve_bonds, conserve_ring_size=conserve_ring_size) self.target_constraint, self.constraint_matrix = self.constraints.calculate_constraints() self.reference_species = self.constraints.reference_species @@ -302,35 +304,36 @@ def _find_error_canceling_reaction(self, reference_subset, milp_software=None): - Indices (of the subset) for the species that participated in the return reaction """ if milp_software is None: - milp_software = ['lpsolve'] + milp_software = ["lpsolve"] if pyo is not None: - milp_software.append('pyomo') + milp_software.append("pyomo") # Define the constraints based on the provided subset c_matrix = np.take(self.constraint_matrix, reference_subset, axis=0) c_matrix = np.tile(c_matrix, (2, 1)) sum_constraints = np.sum(c_matrix, 1, dtype=int) - targets = -1*self.target_constraint + targets = -1 * self.target_constraint m = c_matrix.shape[0] n = c_matrix.shape[1] - split = int(m/2) + split = int(m / 2) for solver in milp_software: - if solver == 'pyomo': + if solver == "pyomo": # Check that pyomo is available if pyo is None: - raise ImportError('Cannot import optional package pyomo. Either install this dependency with ' - '`conda install -c conda-forge pyomo glpk` or set milp_software to `lpsolve`') + raise ImportError( + "Cannot import optional package pyomo. Either install this dependency with " + "`conda install -c conda-forge pyomo glpk` or set milp_software to `lpsolve`" + ) # Setup the MILP problem using pyomo lp_model = pyo.ConcreteModel() lp_model.i = pyo.RangeSet(0, m - 1) lp_model.j = pyo.RangeSet(0, n - 1) - lp_model.r = pyo.RangeSet(0, split-1) # indices before the split correspond to reactants + lp_model.r = pyo.RangeSet(0, split - 1) # indices before the split correspond to reactants lp_model.p = pyo.RangeSet(split, m - 1) # indices after the split correspond to products lp_model.v = pyo.Var(lp_model.i, domain=pyo.NonNegativeIntegers) # The stoich. coef. we are solving for - lp_model.c = pyo.Param(lp_model.i, lp_model.j, initialize=lambda _, i_ind, j_ind: c_matrix[i_ind, - j_ind]) + lp_model.c = pyo.Param(lp_model.i, lp_model.j, initialize=lambda _, i_ind, j_ind: c_matrix[i_ind, j_ind]) lp_model.s = pyo.Param(lp_model.i, initialize=lambda _, i_ind: sum_constraints[i_ind]) lp_model.t = pyo.Param(lp_model.j, initialize=lambda _, j_ind: targets[j_ind]) @@ -338,7 +341,7 @@ def _find_error_canceling_reaction(self, reference_subset, milp_software=None): lp_model.constraints = pyo.Constraint(lp_model.j, rule=_pyo_constraint_rule) # Solve the MILP problem using the GLPK MILP solver (https://www.gnu.org/software/glpk/) - opt = pyo.SolverFactory('glpk') + opt = pyo.SolverFactory("glpk") results = opt.solve(lp_model, timelimit=1) # Return the solution if a valid reaction is found. Otherwise continue to next solver @@ -347,31 +350,30 @@ def _find_error_canceling_reaction(self, reference_subset, milp_software=None): solution = lp_model.v.extract_values().values() break - elif solver == 'lpsolve': + elif solver == "lpsolve": # Save the current signal handler sig = signal.getsignal(signal.SIGINT) # Setup the MILP problem using lpsolve - lp = lpsolve('make_lp', 0, m) - lpsolve('set_verbose', lp, 2) # Reduce the logging from lpsolve - lpsolve('set_obj_fn', lp, sum_constraints) - lpsolve('set_minim', lp) + lp = lpsolve("make_lp", 0, m) + lpsolve("set_verbose", lp, 2) # Reduce the logging from lpsolve + lpsolve("set_obj_fn", lp, sum_constraints) + lpsolve("set_minim", lp) for j in range(n): - lpsolve('add_constraint', lp, np.concatenate((c_matrix[:split, j], -1*c_matrix[split:, j])), EQ, - targets[j]) + lpsolve("add_constraint", lp, np.concatenate((c_matrix[:split, j], -1 * c_matrix[split:, j])), EQ, targets[j]) - lpsolve('add_constraint', lp, np.ones(m), LE, 20) # Use at most 20 species (including replicates) - lpsolve('set_timeout', lp, 1) # Move on if lpsolve can't find a solution quickly + lpsolve("add_constraint", lp, np.ones(m), LE, 20) # Use at most 20 species (including replicates) + lpsolve("set_timeout", lp, 1) # Move on if lpsolve can't find a solution quickly # Constrain v_i to be 4 or less for i in range(m): - lpsolve('set_upbo', lp, i, 4) + lpsolve("set_upbo", lp, i, 4) # All v_i must be integers - lpsolve('set_int', lp, [True]*m) + lpsolve("set_int", lp, [True] * m) - status = lpsolve('solve', lp) + status = lpsolve("solve", lp) # Reset signal handling since lpsolve changed it try: @@ -379,14 +381,16 @@ def _find_error_canceling_reaction(self, reference_subset, milp_software=None): except ValueError: # This is not being run in the main thread, so we cannot reset signal pass + except TypeError: + print("Failed to reset signal handling in LPSolve - are you running pytest?") # Return the solution if a valid reaction is found. Otherwise continue to next solver if status == 0: - _, solution = lpsolve('get_solution', lp)[:2] + _, solution = lpsolve("get_solution", lp)[:2] break else: - raise ValueError(f'Unrecognized MILP solver {solver} for isodesmic reaction generation') + raise ValueError(f"Unrecognized MILP solver {solver} for isodesmic reaction generation") else: return None, None @@ -461,7 +465,7 @@ def calculate_target_enthalpy(self, n_reactions_max=20, milp_software=None): for i, rxn in enumerate(reaction_list): h298_list[i] = rxn.calculate_target_thermo().value_si - return ScalarQuantity(np.median(h298_list), 'J/mol'), reaction_list + return ScalarQuantity(np.median(h298_list), "J/mol"), reaction_list def _pyo_obj_expression(model): @@ -469,14 +473,14 @@ def _pyo_obj_expression(model): def _pyo_constraint_rule(model, col): - return sum(model.v[row] * model.c[row, col] for row in model.r) - \ - sum(model.v[row] * model.c[row, col] for row in model.p) == model.t[col] + return sum(model.v[row] * model.c[row, col] for row in model.r) - sum(model.v[row] * model.c[row, col] for row in model.p) == model.t[col] class IsodesmicScheme(ErrorCancelingScheme): """ An error canceling reaction where the number and type of both atoms and bonds are conserved """ + def __init__(self, target, reference_set): super().__init__(target, reference_set, conserve_bonds=True, conserve_ring_size=False) @@ -485,9 +489,10 @@ class IsodesmicRingScheme(ErrorCancelingScheme): """ A stricter form of the traditional isodesmic reaction scheme where the number of each ring size is also conserved """ + def __init__(self, target, reference_set): super().__init__(target, reference_set, conserve_bonds=True, conserve_ring_size=True) -if __name__ == '__main__': +if __name__ == "__main__": pass diff --git a/arkane/encorr/isodesmicTest.py b/arkane/encorr/isodesmicTest.py deleted file mode 100644 index 7a08450d67..0000000000 --- a/arkane/encorr/isodesmicTest.py +++ /dev/null @@ -1,280 +0,0 @@ -#!/usr/bin/env python3 - -############################################################################### -# # -# RMG - Reaction Mechanism Generator # -# # -# Copyright (c) 2002-2021 Prof. William H. Green (whgreen@mit.edu), # -# Prof. Richard H. West (r.west@neu.edu) and the RMG Team (rmg_dev@mit.edu) # -# # -# Permission is hereby granted, free of charge, to any person obtaining a # -# copy of this software and associated documentation files (the 'Software'), # -# to deal in the Software without restriction, including without limitation # -# the rights to use, copy, modify, merge, publish, distribute, sublicense, # -# and/or sell copies of the Software, and to permit persons to whom the # -# Software is furnished to do so, subject to the following conditions: # -# # -# The above copyright notice and this permission notice shall be included in # -# all copies or substantial portions of the Software. # -# # -# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # -# DEALINGS IN THE SOFTWARE. # -# # -############################################################################### - -""" -This script contains unit tests of the :mod:`arkane.isodesmic` module. -""" - -import unittest - -import numpy as np - -from rmgpy.molecule import Molecule -from rmgpy.species import Species - -from arkane.encorr.isodesmic import ErrorCancelingScheme, ErrorCancelingSpecies, ErrorCancelingReaction, \ - IsodesmicScheme, SpeciesConstraints -from arkane.modelchem import LevelOfTheory - -################################################################################ - - -class TestErrorCancelingReactionAndSpecies(unittest.TestCase): - """ - Tests that ErrorCancelingReaction objects and ErrorCancelingSpecies object are properly implemented - """ - - @classmethod - def setUpClass(cls): - """ - A method called before each unit test in this class. - """ - cls.molecule1 = Molecule(smiles='CC') - cls.molecule2 = Molecule(smiles='[CH3]') - cls.species = Species(smiles='CC') - - def test_error_canceling_species(self): - """ - Test that ErrorCancelingSpecies can be created properly - """ - lot = LevelOfTheory('test') - error_canceling_species = ErrorCancelingSpecies(self.molecule1, (123.4, 'kcal/mol'), lot, (100.0, 'kJ/mol')) - self.assertIsInstance(error_canceling_species, ErrorCancelingSpecies) - self.assertAlmostEqual(error_canceling_species.low_level_hf298.value_si, 123.4*4184) - - # For target species the high level data is not given - target_species = ErrorCancelingSpecies(self.molecule2, (10.1, 'J/mol'), lot) - self.assertIs(target_species.high_level_hf298, None) - - def test_molecule_input_in_error_canceling_species(self): - """ - Test that an exception is raised if an rmgpy Molecule object is not passed to an ErrorCancelingSpecies - """ - with self.assertRaises(ValueError): - ErrorCancelingSpecies(self.species, (100.0, 'J/mol'), LevelOfTheory('test')) - - def test_error_canceling_reactions(self): - """ - Test that ErrorCancelingReaction object can be created and that hf298 can be calculated for the target - """ - # Take ethane as the target - lot = LevelOfTheory('test') - ethane = ErrorCancelingSpecies(self.molecule1, (100.0, 'kJ/mol'), lot) - methyl = ErrorCancelingSpecies(self.molecule2, (20.0, 'kcal/mol'), lot, (21000.0, 'cal/mol')) - - # This reaction is not an isodesmic reaction, but that does not matter for the unit test - rxn = ErrorCancelingReaction(ethane, {methyl: 2}) - self.assertAlmostEqual(rxn.calculate_target_thermo().value_si, 2*21000.0*4.184-(2*20.0*4184-100.0*1000)) - - def test_level_of_theory_consistency(self): - """ - Test that ErrorCancelingReaction objects properly check that all species use the same level of theory - """ - # Take ethane as the target - ethane = ErrorCancelingSpecies(self.molecule1, (100.0, 'kJ/mol'), LevelOfTheory('test_A')) - methyl = ErrorCancelingSpecies(self.molecule2, (20.0, 'kcal/mol'), LevelOfTheory('test_B'), - (21000.0, 'cal/mol')) - - # This should throw an exception because the model chemistry is different - with self.assertRaises(ValueError): - ErrorCancelingReaction(ethane, {methyl: 2}) - - -class TestSpeciesConstraints(unittest.TestCase): - """ - A class for testing that the SpeciesConstraint class functions properly - """ - - @classmethod - def setUpClass(cls): - """ - A method called before each unit test in this class. - """ - # Give all species a low level Hf298 of 100 J/mol--this is not important for this test - hf = (100.0, 'J/mol') - - lot = LevelOfTheory('test') - cls.propene = ErrorCancelingSpecies(Molecule(smiles='CC=C'), hf, lot) - cls.butane = ErrorCancelingSpecies(Molecule(smiles='CCCC'), hf, lot) - cls.benzene = ErrorCancelingSpecies(Molecule(smiles='c1ccccc1'), hf, lot) - cls.caffeine = ErrorCancelingSpecies(Molecule(smiles='CN1C=NC2=C1C(=O)N(C(=O)N2C)C'), hf, lot) - cls.ethyne = ErrorCancelingSpecies(Molecule(smiles='C#C'), hf, lot) - - def test_initializing_constraint_map(self): - """ - Test that the constraint map is properly initialized when a SpeciesConstraints object is initialized - """ - caffeine_consts = SpeciesConstraints(self.caffeine, [self.butane, self.benzene]) - self.assertEqual(set(caffeine_consts.constraint_map.keys()), {'H', 'C', 'O', 'N', - 'C=O', 'C-N', 'C-H', 'C=C', 'C=N', 'C-C', - '5_ring', '6_ring'}) - - no_rings = SpeciesConstraints(self.caffeine, [self.butane, self.benzene], conserve_ring_size=False) - self.assertEqual(set(no_rings.constraint_map.keys()), {'H', 'C', 'O', 'N', - 'C=O', 'C-N', 'C-H', 'C=C', 'C=N', 'C-C'}) - - atoms_only = SpeciesConstraints(self.caffeine, [self.butane], conserve_ring_size=False, conserve_bonds=False) - self.assertEqual(set(atoms_only.constraint_map.keys()), {'H', 'C', 'O', 'N'}) - - def test_enumerating_constraints(self): - """ - Test that a SpeciesConstraints object can properly enumerate the constraints of a given ErrorCancelingSpecies - """ - spcs_consts = SpeciesConstraints(self.benzene, []) - self.assertEqual(set(spcs_consts.constraint_map.keys()), {'C', 'H', 'C=C', 'C-C', 'C-H', '6_ring'}) - - # Now that we have confirmed that the correct keys are present, overwrite the constraint map to set the order - spcs_consts.constraint_map = {'H': 0, 'C': 1, 'C=C': 2, 'C-C': 3, 'C-H': 4, '6_ring': 5} - - self.assertTrue(np.array_equal(spcs_consts._enumerate_constraints(self.propene), np.array([6, 3, 1, 1, 6, 0]))) - self.assertTrue(np.array_equal(spcs_consts._enumerate_constraints(self.butane), np.array([10, 4, 0, 3, 10, 0]))) - self.assertTrue(np.array_equal(spcs_consts._enumerate_constraints(self.benzene), np.array([6, 6, 3, 3, 6, 1]))) - - # Caffeine and ethyne should return None since they have features not found in benzene - self.assertIs(spcs_consts._enumerate_constraints(self.caffeine), None) - self.assertIs(spcs_consts._enumerate_constraints(self.ethyne), None) - - def test_calculating_constraints(self): - """ - Test that a SpeciesConstraints object can properly return the target constraint vector and the constraint matrix - """ - spcs_consts = SpeciesConstraints(self.caffeine, [self.propene, self.butane, self.benzene, self.ethyne]) - self.assertEqual(set(spcs_consts.constraint_map.keys()), {'H', 'C', 'O', 'N', 'C=O', 'C-N', 'C-H', 'C=C', 'C=N', - 'C-C', '5_ring', '6_ring'}) - - # Now that we have confirmed that the correct keys are present, overwrite the constraint map to set the order - spcs_consts.constraint_map = ({'H': 0, 'C': 1, 'O': 2, 'N': 3, - 'C=O': 4, 'C-N': 5, 'C-H': 6, 'C=C': 7, 'C=N': 8, 'C-C': 9, - '5_ring': 10, '6_ring': 11}) - - target_consts, consts_matrix = spcs_consts.calculate_constraints() - - # First, test that ethyne is not included in the reference set - self.assertEqual(spcs_consts.reference_species, [self.propene, self.butane, self.benzene]) - - # Then, test the output of the calculation - self.assertTrue(np.array_equal(target_consts, np.array([10, 8, 2, 4, 2, 10, 10, 1, 1, 1, 1, 1]))) - self.assertTrue(np.array_equal(consts_matrix, np.array([[6, 3, 0, 0, 0, 0, 6, 1, 0, 1, 0, 0], - [10, 4, 0, 0, 0, 0, 10, 0, 0, 3, 0, 0], - [6, 6, 0, 0, 0, 0, 6, 3, 0, 3, 0, 1]]))) - - -class TestErrorCancelingScheme(unittest.TestCase): - """ - A class for testing that the ErrorCancelingScheme class functions properly - """ - - @classmethod - def setUpClass(cls): - try: - import pyomo as pyo - except ImportError: - pyo = None - cls.pyo = pyo - - lot = LevelOfTheory('test') - cls.propene = ErrorCancelingSpecies(Molecule(smiles='CC=C'), (100, 'kJ/mol'), lot, (105, 'kJ/mol')) - cls.propane = ErrorCancelingSpecies(Molecule(smiles='CCC'), (75, 'kJ/mol'), lot, (80, 'kJ/mol')) - cls.butane = ErrorCancelingSpecies(Molecule(smiles='CCCC'), (150, 'kJ/mol'), lot, (145, 'kJ/mol')) - cls.butene = ErrorCancelingSpecies(Molecule(smiles='C=CCC'), (175, 'kJ/mol'), lot, (180, 'kJ/mol')) - cls.pentane = ErrorCancelingSpecies(Molecule(smiles='CCCCC'), (200, 'kJ/mol'), lot, (190, 'kJ/mol')) - cls.pentene = ErrorCancelingSpecies(Molecule(smiles='C=CCCC'), (225, 'kJ/mol'), lot, (220, 'kJ/mol')) - cls.hexane = ErrorCancelingSpecies(Molecule(smiles='CCCCCC'), (250, 'kJ/mol'), lot, (260, 'kJ/mol')) - cls.hexene = ErrorCancelingSpecies(Molecule(smiles='C=CCCCC'), (275, 'kJ/mol'), lot, (275, 'kJ/mol')) - cls.benzene = ErrorCancelingSpecies(Molecule(smiles='c1ccccc1'), (-50, 'kJ/mol'), lot, (-80, 'kJ/mol')) - cls.caffeine = ErrorCancelingSpecies(Molecule(smiles='CN1C=NC2=C1C(=O)N(C(=O)N2C)C'), (300, 'kJ/mol'), lot) - cls.ethyne = ErrorCancelingSpecies(Molecule(smiles='C#C'), (200, 'kJ/mol'), lot) - - def test_creating_error_canceling_schemes(self): - scheme = ErrorCancelingScheme(self.propene, [self.butane, self.benzene, self.caffeine, self.ethyne], True, True) - - self.assertEqual(scheme.reference_species, [self.butane]) - - isodesmic_scheme = IsodesmicScheme(self.propene, [self.butane, self.benzene, self.caffeine, self.ethyne]) - - self.assertEqual(isodesmic_scheme.reference_species, [self.butane, self.benzene]) - - def test_find_error_canceling_reaction(self): - """ - Test that the MILP problem can be solved to find a single isodesmic reaction - """ - scheme = IsodesmicScheme(self.propene, [self.propane, self.butane, self.butene, self.caffeine, self.ethyne]) - - # Note that caffeine and ethyne will not be allowed, so for the full set the indices are [0, 1, 2] - rxn, _ = scheme._find_error_canceling_reaction([0, 1, 2], milp_software=['lpsolve']) - self.assertEqual(rxn.species[self.butane], -1) - self.assertEqual(rxn.species[self.propane], 1) - self.assertEqual(rxn.species[self.butene], 1) - - if self.pyo is not None: - rxn, _ = scheme._find_error_canceling_reaction([0, 1, 2], milp_software=['pyomo']) - self.assertEqual(rxn.species[self.butane], -1) - self.assertEqual(rxn.species[self.propane], 1) - self.assertEqual(rxn.species[self.butene], 1) - - def test_multiple_error_canceling_reactions(self): - """ - Test that multiple error canceling reactions can be found - """ - scheme = IsodesmicScheme(self.propene, [self.propane, self.butane, self.butene, self.pentane, self.pentene, - self.hexane, self.hexene, self.benzene]) - - reaction_list = scheme.multiple_error_canceling_reaction_search(n_reactions_max=20) - self.assertEqual(len(reaction_list), 20) - reaction_string = reaction_list.__repr__() - # Consider both permutations of the products in the reaction string - rxn_str1 = ' 1*CCC + 1*C=CCC >' - rxn_str2 = ' 1*C=CCC + 1*CCC >' - self.assertTrue(any(rxn_string in reaction_string for rxn_string in [rxn_str1, rxn_str2])) - - if self.pyo is not None: - # pyomo is slower, so don't test as many - reaction_list = scheme.multiple_error_canceling_reaction_search(n_reactions_max=5, milp_software=['pyomo']) - self.assertEqual(len(reaction_list), 5) - reaction_string = reaction_list.__repr__() - self.assertTrue(any(rxn_string in reaction_string for rxn_string in [rxn_str1, rxn_str2])) - - def test_calculate_target_enthalpy(self): - """ - Test that ErrorCancelingScheme is able to calculate thermochemistry for the target species - """ - scheme = IsodesmicScheme(self.propene, [self.propane, self.butane, self.butene, self.pentane, self.pentene, - self.hexane, self.hexene, self.benzene]) - - target_thermo, rxn_list = scheme.calculate_target_enthalpy(n_reactions_max=3, milp_software=['lpsolve']) - self.assertEqual(target_thermo.value_si, 115000.0) - self.assertIsInstance(rxn_list[0], ErrorCancelingReaction) - - if self.pyo is not None: - target_thermo, _ = scheme.calculate_target_enthalpy(n_reactions_max=3, milp_software=['pyomo']) - self.assertEqual(target_thermo.value_si, 115000.0) - - -if __name__ == '__main__': - unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arkane/ess/gaussian.py b/arkane/ess/gaussian.py index 0badb0e12d..1d35962098 100644 --- a/arkane/ess/gaussian.py +++ b/arkane/ess/gaussian.py @@ -62,45 +62,43 @@ def check_for_errors(self): Checks for common errors in a Gaussian log file. If any are found, this method will raise an error and crash. """ - with open(self.path, 'r') as f: + with open(self.path, "r") as f: lines = f.readlines()[-100:] error = None terminated = False for line in reversed(lines): # check for common error messages - if 'termination' in line: + if "termination" in line: terminated = True - if 'l9999.exe' in line or 'link 9999' in line: - error = 'Unconverged' - elif 'l101.exe' in line: - error = 'The blank line after the coordinate section is missing, ' \ - 'or charge/multiplicity was not specified correctly.' - elif 'l103.exe' in line: - error = 'Internal coordinate error' - elif 'l108.exe' in line: - error = 'There are two blank lines between z-matrix and ' \ - 'the variables, expected only one.' - elif 'l202.exe' in line: - error = 'During the optimization process, either the standard ' \ - 'orientation or the point group of the molecule has changed.' - elif 'l502.exe' in line: - error = 'Unconverged SCF.' - elif 'l716.exe' in line: - error = 'Angle in z-matrix outside the allowed range 0 < x < 180.' - elif 'l906.exe' in line: - error = 'The MP2 calculation has failed. It may be related to pseudopotential. ' \ - 'Basis sets (CEP-121G*) that are used with polarization functions, ' \ - 'where no polarization functions actually exist.' - elif 'l913.exe' in line: - error = 'Maximum optimization cycles reached.' + if "l9999.exe" in line or "link 9999" in line: + error = "Unconverged" + elif "l101.exe" in line: + error = "The blank line after the coordinate section is missing, " "or charge/multiplicity was not specified correctly." + elif "l103.exe" in line: + error = "Internal coordinate error" + elif "l108.exe" in line: + error = "There are two blank lines between z-matrix and " "the variables, expected only one." + elif "l202.exe" in line: + error = "During the optimization process, either the standard " "orientation or the point group of the molecule has changed." + elif "l502.exe" in line: + error = "Unconverged SCF." + elif "l716.exe" in line: + error = "Angle in z-matrix outside the allowed range 0 < x < 180." + elif "l906.exe" in line: + error = ( + "The MP2 calculation has failed. It may be related to pseudopotential. " + "Basis sets (CEP-121G*) that are used with polarization functions, " + "where no polarization functions actually exist." + ) + elif "l913.exe" in line: + error = "Maximum optimization cycles reached." if error: - raise LogError(f'There was an error ({error}) with Gaussian output file {self.path} ' - f'due to line:\n{line}') + raise LogError(f"There was an error ({error}) with Gaussian output file {self.path} " f"due to line:\n{line}") else: # no need to continue parsing if terminated without errors break if not terminated: - raise LogError(f'Gaussian output file {self.path} did not terminate') + raise LogError(f"Gaussian output file {self.path} did not terminate") def get_number_of_atoms(self): """ @@ -109,14 +107,14 @@ def get_number_of_atoms(self): """ n_atoms = 0 - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '' and n_atoms == 0: + while line != "" and n_atoms == 0: # Automatically determine the number of atoms - if 'Input orientation:' in line and n_atoms == 0: + if "Input orientation:" in line and n_atoms == 0: for i in range(5): line = f.readline() - while '---------------------------------------------------------------------' not in line: + while "---------------------------------------------------------------------" not in line: n_atoms += 1 line = f.readline() line = f.readline() @@ -138,12 +136,12 @@ def load_force_constant_matrix(self): n_atoms = self.get_number_of_atoms() n_rows = n_atoms * 3 - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": # Read force constant matrix - if 'Force constants in Cartesian coordinates:' in line: - force = np.zeros((n_rows, n_rows), np.float64) + if "Force constants in Cartesian coordinates:" in line: + force = np.zeros((n_rows, n_rows), float) for i in range(int(math.ceil(n_rows / 5.0))): # Header row line = f.readline() @@ -151,10 +149,10 @@ def load_force_constant_matrix(self): for j in range(i * 5, n_rows): data = f.readline().split() for k in range(len(data) - 1): - force[j, i * 5 + k] = float(data[k + 1].replace('D', 'E')) + force[j, i * 5 + k] = float(data[k + 1].replace("D", "E")) force[i * 5 + k, j] = force[j, i * 5 + k] # Convert from atomic units (Hartree/Bohr_radius^2) to J/m^2 - force *= 4.35974417e-18 / 5.291772108e-11 ** 2 + force *= 4.35974417e-18 / 5.291772108e-11**2 line = f.readline() return force @@ -167,15 +165,15 @@ def load_geometry(self): """ number, coord, mass = [], [], [] - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": # Automatically determine the number of atoms - if 'Input orientation:' in line: + if "Input orientation:" in line: number, coord = [], [] for i in range(5): line = f.readline() - while '---------------------------------------------------------------------' not in line: + while "---------------------------------------------------------------------" not in line: data = line.split() number.append(int(data[1])) coord.append([float(data[3]), float(data[4]), float(data[5])]) @@ -187,18 +185,20 @@ def load_geometry(self): for num in number: mass1, _ = get_element_mass(num) mass.append(mass1) - coord = np.array(coord, np.float64) - number = np.array(number, np.int) - mass = np.array(mass, np.float64) + coord = np.array(coord, float) + number = np.array(number, int) + mass = np.array(mass, float) if len(number) == 0 or len(coord) == 0 or len(mass) == 0: - raise LogError('Unable to read atoms from Gaussian geometry output file {0}. ' - 'Make sure the output file is not corrupt.\nNote: if your species has ' - '50 or more atoms, you will need to add the `iop(2/9=2000)` keyword to your ' - 'input file so Gaussian will print the input orientation geometry.'.format(self.path)) + raise LogError( + "Unable to read atoms from Gaussian geometry output file {0}. " + "Make sure the output file is not corrupt.\nNote: if your species has " + "50 or more atoms, you will need to add the `iop(2/9=2000)` keyword to your " + "input file so Gaussian will print the input orientation geometry.".format(self.path) + ) return coord, number, mass - def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=''): + def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=""): """ Load the molecular degree of freedom data from a log file created as the result of a Gaussian "Freq" quantum chemistry calculation. As @@ -217,56 +217,51 @@ def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=Non optical_isomers = _optical_isomers if symmetry is None: symmetry = _symmetry - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': - + while line != "": # Read the spin multiplicity if not explicitly given - if spin_multiplicity == 0 and 'Multiplicity =' in line: + if spin_multiplicity == 0 and "Multiplicity =" in line: spin_multiplicity = int(line.split()[-1]) - logging.debug('Conformer {0} is assigned a spin multiplicity of {1}' - .format(label, spin_multiplicity)) + logging.debug("Conformer {0} is assigned a spin multiplicity of {1}".format(label, spin_multiplicity)) # The data we want is in the Thermochemistry section of the output - if '- Thermochemistry -' in line: + if "- Thermochemistry -" in line: modes = [] in_partition_functions = False line = f.readline() - while line != '': - + while line != "": # This marks the end of the thermochemistry section - if '-------------------------------------------------------------------' in line: + if "-------------------------------------------------------------------" in line: break # Read molecular mass for external translational modes - elif 'Molecular mass:' in line: + elif "Molecular mass:" in line: mass = float(line.split()[2]) translation = IdealGasTranslation(mass=(mass, "amu")) modes.append(translation) # Read moments of inertia for external rotational modes - elif 'Rotational constants (GHZ):' in line: + elif "Rotational constants (GHZ):" in line: inertia = [float(d) for d in line.split()[-3:]] for i in range(3): - inertia[i] = constants.h / (8 * constants.pi * constants.pi * inertia[i] * 1e9) \ - * constants.Na * 1e23 + inertia[i] = constants.h / (8 * constants.pi * constants.pi * inertia[i] * 1e9) * constants.Na * 1e23 rotation = NonlinearRotor(inertia=(inertia, "amu*angstrom^2"), symmetry=symmetry) modes.append(rotation) - elif 'Rotational constant (GHZ):' in line: + elif "Rotational constant (GHZ):" in line: inertia = [float(line.split()[3])] - inertia[0] = constants.h / (8 * constants.pi * constants.pi * inertia[0] * 1e9) \ - * constants.Na * 1e23 + inertia[0] = constants.h / (8 * constants.pi * constants.pi * inertia[0] * 1e9) * constants.Na * 1e23 rotation = LinearRotor(inertia=(inertia[0], "amu*angstrom^2"), symmetry=symmetry) modes.append(rotation) # Read vibrational modes - elif 'Vibrational temperatures:' in line: + elif "Vibrational temperatures:" in line: frequencies = [] frequencies.extend([float(d) for d in line.split()[2:]]) line = f.readline() frequencies.extend([float(d) for d in line.split()[1:]]) line = f.readline() - while line.strip() != '': + while line.strip() != "": frequencies.extend([float(d) for d in line.split()]) line = f.readline() # Convert from K to cm^-1 @@ -277,14 +272,14 @@ def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=Non modes.append(vibration) # Read ground-state energy - elif 'Sum of electronic and zero-point Energies=' in line: + elif "Sum of electronic and zero-point Energies=" in line: e0 = float(line.split()[6]) * 4.35974394e-18 * constants.Na # Read spin multiplicity if above method was unsuccessful - elif 'Electronic' in line and in_partition_functions and spin_multiplicity == 0: - spin_multiplicity = int(float(line.split()[1].replace('D', 'E'))) + elif "Electronic" in line and in_partition_functions and spin_multiplicity == 0: + spin_multiplicity = int(float(line.split()[1].replace("D", "E"))) - elif 'Log10(Q)' in line: + elif "Log10(Q)" in line: in_partition_functions = True # Read the next line in the file @@ -293,61 +288,62 @@ def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=Non # Read the next line in the file line = f.readline() - return Conformer(E0=(e0 * 0.001, "kJ/mol"), modes=modes, spin_multiplicity=spin_multiplicity, - optical_isomers=optical_isomers), unscaled_frequencies + return ( + Conformer(E0=(e0 * 0.001, "kJ/mol"), modes=modes, spin_multiplicity=spin_multiplicity, optical_isomers=optical_isomers), + unscaled_frequencies, + ) - def load_energy(self, zpe_scale_factor=1.): + def load_energy(self, zpe_scale_factor=1.0): """ - Load the energy in J/mol from a Gaussian log file. The file is checked - for a complete basis set extrapolation; if found, that value is + Load the energy in J/mol from a Gaussian log file. The file is checked + for a complete basis set extrapolation; if found, that value is returned. Only the last energy in the file is returned. The zero-point energy is *not* included in the returned value; it is removed from the CBS-QB3 value. """ e_elect, e0_composite, scaled_zpe = None, None, None - elect_energy_source = '' - with open(self.path, 'r') as f: + elect_energy_source = "" + with open(self.path, "r") as f: line = f.readline() - while line != '': - - if 'SCF Done:' in line: + while line != "": + if "SCF Done:" in line: e_elect = float(line.split()[4]) * constants.E_h * constants.Na - elect_energy_source = 'SCF' - elif ' E2(' in line and ' E(' in line: - e_elect = float(line.split()[-1].replace('D', 'E')) * constants.E_h * constants.Na - elect_energy_source = 'doublehybrd or MP2' - elif 'MP2 =' in line: - e_elect = float(line.split()[-1].replace('D', 'E')) * constants.E_h * constants.Na - elect_energy_source = 'MP2' - elif 'E(CORR)=' in line: + elect_energy_source = "SCF" + elif " E2(" in line and " E(" in line: + e_elect = float(line.split()[-1].replace("D", "E")) * constants.E_h * constants.Na + elect_energy_source = "doublehybrd or MP2" + elif "MP2 =" in line: + e_elect = float(line.split()[-1].replace("D", "E")) * constants.E_h * constants.Na + elect_energy_source = "MP2" + elif "E(CORR)=" in line: e_elect = float(line.split()[3]) * constants.E_h * constants.Na - elect_energy_source = 'CCSD' - elif 'CCSD(T)= ' in line: - e_elect = float(line.split()[1].replace('D', 'E')) * constants.E_h * constants.Na - elect_energy_source = 'CCSD(T)' - elif 'CBS-QB3 (0 K)' in line: + elect_energy_source = "CCSD" + elif "CCSD(T)= " in line: + e_elect = float(line.split()[1].replace("D", "E")) * constants.E_h * constants.Na + elect_energy_source = "CCSD(T)" + elif "CBS-QB3 (0 K)" in line: e0_composite = float(line.split()[3]) * constants.E_h * constants.Na - elif 'E(CBS-QB3)=' in line: + elif "E(CBS-QB3)=" in line: # CBS-QB3 calculation without opt and freq calculation # Keyword in Gaussian CBS-QB3(SP), No zero-point or thermal energies are included. e_elect = float(line.split()[1]) * constants.E_h * constants.Na - elif 'CBS-4 (0 K)=' in line: + elif "CBS-4 (0 K)=" in line: e0_composite = float(line.split()[3]) * constants.E_h * constants.Na - elif 'G3(0 K)' in line: + elif "G3(0 K)" in line: e0_composite = float(line.split()[2]) * constants.E_h * constants.Na - elif 'G3 Energy=' in line: + elif "G3 Energy=" in line: # G3 calculation without opt and freq calculation # Keyword in Gaussian G3(SP), No zero-point or thermal energies are included. e_elect = float(line.split()[2]) * constants.E_h * constants.Na - elif 'G4(0 K)' in line: + elif "G4(0 K)" in line: e0_composite = float(line.split()[2]) * constants.E_h * constants.Na - elif 'G4 Energy=' in line: + elif "G4 Energy=" in line: # G4 calculation without opt and freq calculation # Keyword in Gaussian G4(SP), No zero-point or thermal energies are included. e_elect = float(line.split()[2]) * constants.E_h * constants.Na - elif 'G4MP2(0 K)' in line: + elif "G4MP2(0 K)" in line: e0_composite = float(line.split()[2]) * constants.E_h * constants.Na - elif 'G4MP2 Energy=' in line: + elif "G4MP2 Energy=" in line: # G4MP2 calculation without opt and freq calculation # Keyword in Gaussian G4MP2(SP), No zero-point or thermal energies are included. e_elect = float(line.split()[2]) * constants.E_h * constants.Na @@ -358,12 +354,12 @@ def load_energy(self, zpe_scale_factor=1.): # The ZPE is the scaled ZPE given by E(ZPE) in the log file, # hence to get the correct Elec from E (0 K) we need to subtract the scaled ZPE - elif 'E(ZPE)' in line: + elif "E(ZPE)" in line: scaled_zpe = float(line.split()[1]) * constants.E_h * constants.Na - elif '\\ZeroPoint=' in line: + elif "\\ZeroPoint=" in line: line = line.strip() + f.readline().strip() - start = line.find('\\ZeroPoint=') + 11 - end = line.find('\\', start) + start = line.find("\\ZeroPoint=") + 11 + end = line.find("\\", start) scaled_zpe = float(line[start:end]) * constants.E_h * constants.Na * zpe_scale_factor # Read the next line in the file line = f.readline() @@ -371,13 +367,13 @@ def load_energy(self, zpe_scale_factor=1.): if e0_composite is not None: logging.debug("Using the composite energy from the gaussian output file") if scaled_zpe is None: - raise LogError('Unable to find zero-point energy in Gaussian log file.') + raise LogError("Unable to find zero-point energy in Gaussian log file.") return e0_composite - scaled_zpe elif e_elect is not None: logging.debug("Using the {0} energy from the gaussian output file".format(elect_energy_source)) return e_elect else: - raise LogError('Unable to find energy in Gaussian log file.') + raise LogError("Unable to find energy in Gaussian log file.") def load_zero_point_energy(self): """ @@ -385,29 +381,29 @@ def load_zero_point_energy(self): """ zpe = None - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": # Do NOT read the ZPE from the "E(ZPE)=" line, as this is the scaled version! # We will read in the unscaled ZPE and later multiply the scaling factor # from the input file - if 'Zero-point correction=' in line: + if "Zero-point correction=" in line: zpe = float(line.split()[2]) * constants.E_h * constants.Na - elif '\\ZeroPoint=' in line: + elif "\\ZeroPoint=" in line: line = line.strip() + f.readline().strip() - start = line.find('\\ZeroPoint=') + 11 - end = line.find('\\', start) + start = line.find("\\ZeroPoint=") + 11 + end = line.find("\\", start) zpe = float(line[start:end]) * constants.E_h * constants.Na line = f.readline() if zpe is not None: return zpe else: - raise LogError('Unable to find zero-point energy in Gaussian log file.') + raise LogError("Unable to find zero-point energy in Gaussian log file.") def load_scan_energies(self): """ - Extract the optimized energies in J/mol from a log file, e.g. the + Extract the optimized energies in J/mol from a log file, e.g. the result of a Gaussian "Scan" quantum chemistry calculation. """ opt_freq = False @@ -416,23 +412,23 @@ def load_scan_energies(self): vlist = [] # The array of potentials at each scan angle non_optimized = [] # The array of indexes of non-optimized point - internal_coord = 'D(' + ','.join(self.load_scan_pivot_atoms()) + ')' + internal_coord = "D(" + ",".join(self.load_scan_pivot_atoms()) + ")" angle = [] # Parse the Gaussian log file, extracting the energies of each # optimized conformer in the scan - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": # If the job contains a "freq" then we want to ignore the last energy - if ' Freq' in line and ' Geom=' in line: + if " Freq" in line and " Geom=" in line: opt_freq = True # if # scan is keyword instead of # opt, then this is a rigid scan job # and parsing the energies is done a little differently - if '# scan' in line: + if "# scan" in line: rigid_scan = True # The lines containing "SCF Done" give the energy at each # iteration (even the intermediate ones) - if 'SCF Done:' in line: + if "SCF Done:" in line: energy = float(line.split()[4]) # rigid scans will only not optimize, so just append every time it finds an energy. if rigid_scan: @@ -440,15 +436,15 @@ def load_scan_energies(self): # We want to keep the values of energy that come most recently before # the line containing "Optimization completed", since it refers # to the optimized geometry - if 'Optimization completed' in line: + if "Optimization completed" in line: vlist.append(energy) # In some cases, the optimization cannot converge within the given steps. # Then, the geometry is not optimized. we need to exclude these values. - if 'Optimization stopped' in line: + if "Optimization stopped" in line: non_optimized.append(len(vlist)) vlist.append(energy) # Read the optimized angle from optimized parameters - if internal_coord in line and 'Scan' not in line: + if internal_coord in line and "Scan" not in line: # EXAMPLE: # ! D9 D(1,2,3,15) 42.4441 -DE/DX = 0.0 ! angle.append(float(line.strip().split()[3])) @@ -457,20 +453,20 @@ def load_scan_energies(self): # give warning in case this assumption is not true if rigid_scan: - print(f' Assuming {os.path.basename(self.path)} is the output from a rigid scan...') + print(f" Assuming {os.path.basename(self.path)} is the output from a rigid scan...") # For rigid scans, all of the angles are evenly spaced with a constant step size scan_res = math.pi / 180 * self._load_scan_angle() - angle = np.arange(0.0, scan_res * (len(vlist) - 1) + 0.00001, scan_res, np.float64) + angle = np.arange(0.0, scan_res * (len(vlist) - 1) + 0.00001, scan_res, float) else: - angle = np.array(angle, np.float64) + angle = np.array(angle, float) # Convert -180 ~ 180 degrees to 0 ~ 2pi rads - angle = (angle - angle[0]) + angle = angle - angle[0] angle[angle < 0] += 360.0 # Adjust angle[-1] to make it close to 360 degrees angle[-1] = angle[-1] if angle[-1] > 2 * self._load_scan_angle() else angle[-1] + 360.0 angle = angle * math.pi / 180 - vlist = np.array(vlist, np.float64) + vlist = np.array(vlist, float) # check to see if the scanlog indicates that a one of your reacting species may not be # the lowest energy conformer check_conformer_energy(vlist, self.path) @@ -485,8 +481,7 @@ def load_scan_energies(self): angle = angle[:-1] if non_optimized: - logging.warning(f'Scan results for angles at {angle[non_optimized]} are discarded ' - f'due to non-converged optimization.') + logging.warning(f"Scan results for angles at {angle[non_optimized]} are discarded " f"due to non-converged optimization.") vlist = np.delete(vlist, non_optimized) angle = np.delete(angle, non_optimized) @@ -509,21 +504,21 @@ def _load_scan_specs(self, letter_spec, get_after_letter_spec=False): """ output = [] reached_input_spec_section = False - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": if reached_input_spec_section: terms = line.split() if len(terms) == 0: # finished reading specs break - if terms[0] == 'D': + if terms[0] == "D": action_index = 5 # dihedral angle with four terms - elif terms[0] == 'A': + elif terms[0] == "A": action_index = 4 # valance angle with three terms - elif terms[0] == 'B': + elif terms[0] == "B": action_index = 3 # bond length with 2 terms - elif terms[0] == 'L': + elif terms[0] == "L": # Can be either L 1 2 3 B or L 1 2 3 -1 B # It defines a linear bend which is helpful in calculating # molecules with ~180 degree bond angles. As no other module @@ -531,18 +526,17 @@ def _load_scan_specs(self, letter_spec, get_after_letter_spec=False): line = f.readline() continue else: - raise LogError('This file has an option not supported by Arkane. ' - 'Unable to read scan specs for line: {0}'.format(line)) + raise LogError("This file has an option not supported by Arkane. " "Unable to read scan specs for line: {0}".format(line)) if len(terms) > action_index: # specified type explicitly if terms[action_index] == letter_spec: if get_after_letter_spec: - output.append(terms[action_index+1:]) + output.append(terms[action_index + 1 :]) else: output.append(terms[1:action_index]) else: # no specific specification, assume freezing - if letter_spec == 'F': + if letter_spec == "F": output.append(terms[1:action_index]) if " The following ModRedundant input section has been read:" in line: reached_input_spec_section = True @@ -554,7 +548,7 @@ def load_scan_pivot_atoms(self): Extract the atom numbers which the rotor scan pivots around Return a list of atom numbers starting with the first atom as 1 """ - output = self._load_scan_specs('S') + output = self._load_scan_specs("S") return output[0] if len(output) > 0 else [] def load_scan_frozen_atoms(self): @@ -566,20 +560,20 @@ def load_scan_frozen_atoms(self): Inner lists with length 3 represent frozen bond angles Inner lists with length 4 represent frozen dihedral angles """ - return self._load_scan_specs('F') + return self._load_scan_specs("F") def _load_scan_angle(self): """ Return the angle difference (degrees) for a gaussian scan. """ - output = self._load_scan_specs('S', get_after_letter_spec=True) + output = self._load_scan_specs("S", get_after_letter_spec=True) return float(output[0][1]) def _load_number_scans(self): """ Return the number of scans for a gaussian scan specified in the input file """ - output = self._load_scan_specs('S', get_after_letter_spec=True) + output = self._load_scan_specs("S", get_after_letter_spec=True) return int(output[0][0]) def load_negative_frequency(self): @@ -588,11 +582,11 @@ def load_negative_frequency(self): calculation in cm^-1. """ frequencies = [] - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": # Read vibrational frequencies - if 'Frequencies --' in line: + if "Frequencies --" in line: frequencies.extend(line.split()[2:]) line = f.readline() @@ -602,10 +596,10 @@ def load_negative_frequency(self): if len(neg_idx) == 1: return frequencies[neg_idx[0]] elif len(neg_idx) > 1: - logging.info('More than one imaginary frequency in Gaussian output file {0}.'.format(self.path)) + logging.info("More than one imaginary frequency in Gaussian output file {0}.".format(self.path)) return frequencies[neg_idx[0]] else: - raise LogError(f'Unable to find imaginary frequency in Gaussian output file {self.path}') + raise LogError(f"Unable to find imaginary frequency in Gaussian output file {self.path}") register_ess_adapter("GaussianLog", GaussianLog) diff --git a/arkane/ess/gaussianTest.py b/arkane/ess/gaussianTest.py deleted file mode 100644 index cc2bac6243..0000000000 --- a/arkane/ess/gaussianTest.py +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/env python3 - -############################################################################### -# # -# RMG - Reaction Mechanism Generator # -# # -# Copyright (c) 2002-2021 Prof. William H. Green (whgreen@mit.edu), # -# Prof. Richard H. West (r.west@neu.edu) and the RMG Team (rmg_dev@mit.edu) # -# # -# Permission is hereby granted, free of charge, to any person obtaining a # -# copy of this software and associated documentation files (the 'Software'), # -# to deal in the Software without restriction, including without limitation # -# the rights to use, copy, modify, merge, publish, distribute, sublicense, # -# and/or sell copies of the Software, and to permit persons to whom the # -# Software is furnished to do so, subject to the following conditions: # -# # -# The above copyright notice and this permission notice shall be included in # -# all copies or substantial portions of the Software. # -# # -# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # -# DEALINGS IN THE SOFTWARE. # -# # -############################################################################### - -""" -This module contains unit tests of the :mod:`arkane.ess.gaussian` module. -""" - -import os -import unittest - -import numpy as np - -import rmgpy.constants as constants -from external.wip import work_in_progress -from rmgpy.statmech import IdealGasTranslation, LinearRotor, NonlinearRotor, HarmonicOscillator, HinderedRotor - -from arkane.ess.gaussian import GaussianLog -from arkane.exceptions import LogError - -################################################################################ - - -class GaussianLogTest(unittest.TestCase): - """ - Contains unit tests for the gaussian module, used for parsing Gaussian log files. - """ - @classmethod - def setUpClass(cls): - """ - A method that is run before all unit tests in this class. - """ - cls.data_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'gaussian') - - def test_check_for_errors(self): - """ - Uses Gaussian log files that had various errors - to test if errors are properly parsed. - """ - with self.assertRaises(LogError): - GaussianLog(os.path.join(self.data_path, 'l913.out')) - with self.assertRaises(LogError): - GaussianLog(os.path.join(self.data_path, 'l9999.out')) - with self.assertRaises(LogError): - GaussianLog(os.path.join(self.data_path, 'error_termination.out')) - - @work_in_progress - def test_load_ethylene_from_gaussian_log_cbsqb3(self): - """ - Uses a Gaussian03 log file for ethylene (C2H4) to test that its - molecular degrees of freedom can be properly read. - """ - - log = GaussianLog(os.path.join(self.data_path, 'ethylene.log')) - conformer, unscaled_frequencies = log.load_conformer() - e0 = log.load_energy() - - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, IdealGasTranslation)]) == 1) - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, NonlinearRotor)]) == 1) - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, HarmonicOscillator)]) == 1) - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, HinderedRotor)]) == 0) - - trans = [mode for mode in conformer.modes if isinstance(mode, IdealGasTranslation)][0] - rot = [mode for mode in conformer.modes if isinstance(mode, NonlinearRotor)][0] - vib = [mode for mode in conformer.modes if isinstance(mode, HarmonicOscillator)][0] - t_list = np.array([298.15], np.float64) - self.assertAlmostEqual(trans.get_partition_function(t_list), 5.83338e6, delta=1e1) - self.assertAlmostEqual(rot.get_partition_function(t_list), 2.59622e3, delta=1e-2) - self.assertAlmostEqual(vib.get_partition_function(t_list), 1.0481e0, delta=1e-4) - - self.assertAlmostEqual(e0 / constants.Na / constants.E_h, -78.467452, 4) - self.assertEqual(conformer.spin_multiplicity, 1) - self.assertEqual(conformer.optical_isomers, 1) - - def test_gaussian_energies(self): - """ - test parsing double hydride, MP2, CCSD, CCSD(T), cbs-qb3, cbs-4m, g4, g4mp2 form Gaussian log - """ - log_doublehybrid = GaussianLog(os.path.join(self.data_path, 'B2PLYP.LOG')) - log_mp2 = GaussianLog(os.path.join(self.data_path, 'UMP2_C_ATOM.LOG')) - log_ccsd = GaussianLog(os.path.join(self.data_path, 'UCCSD_C_ATOM.LOG')) - log_ccsdt = GaussianLog(os.path.join(self.data_path, 'UCCSDT_C_ATOM.LOG')) - log_qb3 = GaussianLog(os.path.join(os.path.dirname(os.path.dirname(__file__)), - '../examples/arkane/species/C2H5/', 'ethyl_cbsqb3.log')) - log_cbs4m = GaussianLog(os.path.join(self.data_path, 'cbs-4m_85_methanol.out')) - log_g4 = GaussianLog(os.path.join(self.data_path, 'g4_85_methanol.out')) - log_g4mp2 = GaussianLog(os.path.join(self.data_path, 'g4mp2_85_methanol.out')) - log_rocbsqb3 = GaussianLog(os.path.join(self.data_path, 'rocbs-qb3_85_methanol.out')) - - - self.assertAlmostEqual(log_doublehybrid.load_energy() / constants.Na / constants.E_h, -0.40217794572194e+02, - delta=1e-6) - self.assertAlmostEqual(log_mp2.load_energy() / constants.Na / constants.E_h, -0.37504683723025e+02, - delta=1e-6) - self.assertAlmostEqual(log_ccsd.load_energy() / constants.Na / constants.E_h, -37.517151426, - delta=1e-6) - self.assertAlmostEqual(log_ccsdt.load_energy() / constants.Na / constants.E_h, -0.37517454469e+02, - delta=1e-6) - self.assertAlmostEqual(log_qb3.load_energy() / constants.Na / constants.E_h, -79.029798, - delta=1e-6) - self.assertAlmostEqual(log_cbs4m.load_energy() / constants.Na / constants.E_h, -115.613180, - delta=1e-6) - self.assertAlmostEqual(log_g4.load_energy() / constants.Na / constants.E_h, -115.698896, - delta=1e-6) - self.assertAlmostEqual(log_g4mp2.load_energy() / constants.Na / constants.E_h, -115.617241, - delta=1e-6) - self.assertAlmostEqual(log_rocbsqb3.load_energy() / constants.Na / constants.E_h, -115.590540, - delta=1e-6) - - def test_load_oxygen_from_gaussian_log(self): - """ - Uses a Gaussian03 log file for oxygen (O2) to test that its - molecular degrees of freedom can be properly read. - """ - - log = GaussianLog(os.path.join(self.data_path, 'oxygen.log')) - conformer, unscaled_frequencies = log.load_conformer() - e0 = log.load_energy() - - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, IdealGasTranslation)]) == 1) - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, LinearRotor)]) == 1) - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, HarmonicOscillator)]) == 1) - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, HinderedRotor)]) == 0) - - trans = [mode for mode in conformer.modes if isinstance(mode, IdealGasTranslation)][0] - rot = [mode for mode in conformer.modes if isinstance(mode, LinearRotor)][0] - vib = [mode for mode in conformer.modes if isinstance(mode, HarmonicOscillator)][0] - t_list = np.array([298.15], np.float64) - self.assertAlmostEqual(trans.get_partition_function(t_list), 7.11169e6, delta=1e1) - self.assertAlmostEqual(rot.get_partition_function(t_list), 7.13316e1, delta=1e-4) - self.assertAlmostEqual(vib.get_partition_function(t_list), 1.00037e0, delta=1e-4) - - self.assertAlmostEqual(e0 / constants.Na / constants.E_h, -150.3784877, 4) - self.assertEqual(conformer.spin_multiplicity, 3) - self.assertEqual(conformer.optical_isomers, 1) - - @work_in_progress - def test_load_ethylene_from_gaussian_log_g3(self): - """ - Uses a Gaussian03 log file for ethylene (C2H4) to test that its - molecular degrees of freedom can be properly read. - """ - - log = GaussianLog(os.path.join(self.data_path, 'ethylene_G3.log')) - conformer, unscaled_frequencies = log.load_conformer() - e0 = log.load_energy() - - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, IdealGasTranslation)]) == 1) - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, NonlinearRotor)]) == 1) - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, HarmonicOscillator)]) == 1) - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, HinderedRotor)]) == 0) - - trans = [mode for mode in conformer.modes if isinstance(mode, IdealGasTranslation)][0] - rot = [mode for mode in conformer.modes if isinstance(mode, NonlinearRotor)][0] - vib = [mode for mode in conformer.modes if isinstance(mode, HarmonicOscillator)][0] - t_list = np.array([298.15], np.float64) - - self.assertAlmostEqual(trans.get_partition_function(t_list), 5.83338e6, delta=1e1) - self.assertAlmostEqual(rot.get_partition_function(t_list), 2.53410e3, delta=1e-2) - self.assertAlmostEqual(vib.get_partition_function(t_list), 1.0304e0, delta=1e-4) - - self.assertAlmostEqual(e0 / constants.Na / constants.E_h, -78.562189, 4) - self.assertEqual(conformer.spin_multiplicity, 1) - self.assertEqual(conformer.optical_isomers, 1) - - def test_load_symmetry_and_optics(self): - """ - Uses a Gaussian03 log file for oxygen (O2) to test that its - molecular degrees of freedom can be properly read. - """ - - log = GaussianLog(os.path.join(self.data_path, 'oxygen.log')) - optical, symmetry, _ = log.get_symmetry_properties() - self.assertEqual(optical, 1) - self.assertEqual(symmetry, 2) - - conf = log.load_conformer()[0] - self.assertEqual(conf.optical_isomers, 1) - found_rotor = False - for mode in conf.modes: - if isinstance(mode, LinearRotor): - self.assertEqual(mode.symmetry, 2) - found_rotor = True - self.assertTrue(found_rotor) - - def test_load_scan_angle(self): - """ - Ensures proper scan angle found in Gaussian scan job - """ - log = GaussianLog(os.path.join(self.data_path, 'isobutanolQOOH_scan.log')) - self.assertAlmostEqual(log._load_scan_angle(), 10.0) - - def test_load_number_scans(self): - """ - Ensures proper scan angle found in Gaussian scan job - """ - log = GaussianLog(os.path.join(self.data_path, 'isobutanolQOOH_scan.log')) - self.assertAlmostEqual(log._load_number_scans(), 36) - - def test_load_scan_with_freq(self): - """ - Ensures that the length of enegies with hr scans and freq calc is correct - """ - log = GaussianLog(os.path.join(self.data_path, 'hr_scan_with_freq.log')) - self.assertAlmostEqual(log._load_number_scans(), 36) - self.assertAlmostEqual(log._load_scan_angle(), 10.0) - vlist, _ = log.load_scan_energies() - self.assertEqual(len(vlist), 37) - - def test_load_negative_frequency(self): - """ - Load an imaginary frequency from a Gaussian output file. - """ - log = GaussianLog(os.path.join(self.data_path, 'hr_scan_with_freq.log')) - imaginary_freq = log.load_negative_frequency() - self.assertEqual(imaginary_freq, -556.0124) - - # verify that an error is raised if there are no negative frequencies - with self.assertRaises(LogError): - log = GaussianLog(os.path.join(self.data_path, 'rocbs-qb3_85_methanol.out')) - imaginary_freq = log.load_negative_frequency() - - -################################################################################ - - -if __name__ == '__main__': - unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arkane/ess/molpro.py b/arkane/ess/molpro.py index 1d5b99006e..90145a6e63 100644 --- a/arkane/ess/molpro.py +++ b/arkane/ess/molpro.py @@ -62,26 +62,26 @@ def check_for_errors(self): Checks for common errors in a Molpro log file. If any are found, this method will raise an error and crash. """ - with open(os.path.join(self.path), 'r') as f: + with open(os.path.join(self.path), "r") as f: lines = f.readlines() error = None for line in reversed(lines): - if 'molpro calculation terminated' in line.lower() or 'variable memory released' in line.lower(): + if "molpro calculation terminated" in line.lower() or "variable memory released" in line.lower(): break # check for common error messages - elif 'No convergence' in line: - error = 'Unconverged' - elif 'A further' in line and 'Mwords of memory are needed' in line and 'Increase memory to' in line: + elif "No convergence" in line: + error = "Unconverged" + elif "A further" in line and "Mwords of memory are needed" in line and "Increase memory to" in line: # e.g.: `A further 246.03 Mwords of memory are needed for the triples to run. # Increase memory to 996.31 Mwords.` (w/o the line break) - error = 'Memory' - elif 'insufficient memory available - require' in line: + error = "Memory" + elif "insufficient memory available - require" in line: # e.g.: `insufficient memory available - require 228765625 have # 62928590 # the request was for real words` # add_mem = (float(line.split()[-2]) - float(prev_line.split()[0])) / 1e6 - error = 'Memory' - elif 'Basis library exhausted' in line: + error = "Memory" + elif "Basis library exhausted" in line: # e.g.: # ` SETTING BASIS = 6-311G** # @@ -96,12 +96,11 @@ def check_for_errors(self): # ? The problem occurs in Binput` basis_set = None for line0 in reversed(line): - if 'SETTING BASIS' in line0: + if "SETTING BASIS" in line0: basis_set = line0.split()[-1] - error = f'Unrecognized basis set {basis_set}' + error = f"Unrecognized basis set {basis_set}" if error: - raise LogError(f'There was an error ({error}) with Molpro output file {self.path} ' - f'due to line:\n{line}') + raise LogError(f"There was an error ({error}) with Molpro output file {self.path} " f"due to line:\n{line}") def get_number_of_atoms(self): """ @@ -110,14 +109,14 @@ def get_number_of_atoms(self): """ n_atoms = 0 # Open Molpro log file for parsing - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '' and n_atoms == 0: + while line != "" and n_atoms == 0: # Automatically determine the number of atoms - if 'ATOMIC COORDINATES' in line and n_atoms == 0: + if "ATOMIC COORDINATES" in line and n_atoms == 0: for i in range(4): line = f.readline() - while 'Bond lengths' not in line and 'nuclear charge' not in line.lower(): + while "Bond lengths" not in line and "nuclear charge" not in line.lower(): n_atoms += 1 line = f.readline() line = f.readline() @@ -133,12 +132,12 @@ def load_force_constant_matrix(self): n_atoms = self.get_number_of_atoms() n_rows = n_atoms * 3 - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": # Read force constant matrix - if 'Force Constants (Second Derivatives of the Energy) in [a.u.]' in line: - fc = np.zeros((n_rows, n_rows), np.float64) + if "Force Constants (Second Derivatives of the Energy) in [a.u.]" in line: + fc = np.zeros((n_rows, n_rows), float) for i in range(int(math.ceil(n_rows / 5.0))): # Header row line = f.readline() @@ -146,10 +145,10 @@ def load_force_constant_matrix(self): for j in range(i * 5, n_rows): data = f.readline().split() for k in range(len(data) - 1): - fc[j, i * 5 + k] = float(data[k + 1].replace('D', 'E')) + fc[j, i * 5 + k] = float(data[k + 1].replace("D", "E")) fc[i * 5 + k, j] = fc[j, i * 5 + k] # Convert from atomic units (Hartree/Bohr_radius^2) to J/m^2 - fc *= 4.35974417e-18 / 5.291772108e-11 ** 2 + fc *= 4.35974417e-18 / 5.291772108e-11**2 line = f.readline() return fc @@ -163,16 +162,16 @@ def load_geometry(self): symbol, coord, mass, number = [], [], [], [] - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": # Automatically determine the number of atoms - if 'Current geometry' in line: + if "Current geometry" in line: symbol, coord = [], [] - while 'ENERGY' not in line: + while "ENERGY" not in line: line = f.readline() line = f.readline() - while line != '\n': + while line != "\n": data = line.split() symbol.append(str(data[0])) coord.append([float(data[1]), float(data[2]), float(data[3])]) @@ -183,14 +182,14 @@ def load_geometry(self): # If no optimized coordinates were found, uses the input geometry # (for example if reading the geometry from a frequency file) if not coord: - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': - if 'atomic coordinates' in line.lower(): + while line != "": + if "atomic coordinates" in line.lower(): symbol, coord = [], [] for i in range(4): line = f.readline() - while line != '\n': + while line != "\n": data = line.split() symbol.append(str(data[1])) coord.append([float(data[3]), float(data[4]), float(data[5])]) @@ -202,15 +201,15 @@ def load_geometry(self): mass1, num1 = get_element_mass(atom1) mass.append(mass1) number.append(num1) - number = np.array(number, np.int) - mass = np.array(mass, np.float64) - coord = np.array(coord, np.float64) + number = np.array(number, int) + mass = np.array(mass, float) + coord = np.array(coord, float) if len(number) == 0 or len(coord) == 0 or len(mass) == 0: - raise LogError('Unable to read atoms from Molpro geometry output file {0}'.format(self.path)) + raise LogError("Unable to read atoms from Molpro geometry output file {0}".format(self.path)) return coord, number, mass - def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=''): + def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=""): """ Load the molecular degree of freedom data from a log file created as the result of a MolPro "Freq" quantum chemistry calculation with the thermo printed. @@ -225,76 +224,70 @@ def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=Non optical_isomers = _optical_isomers if symmetry is None: symmetry = _symmetry - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': - + while line != "": # Read the spin multiplicity if not explicitly given - if spin_multiplicity == 0 and 'spin' in line: - splits = line.replace('=', ' ').replace(',', ' ').split(' ') + if spin_multiplicity == 0 and "spin" in line: + splits = line.replace("=", " ").replace(",", " ").split(" ") for i, s in enumerate(splits): - if 'spin' in s: + if "spin" in s: spin_multiplicity = int(splits[i + 1]) + 1 - logging.debug( - 'Conformer {0} is assigned a spin multiplicity of {1}'.format(label, spin_multiplicity)) + logging.debug("Conformer {0} is assigned a spin multiplicity of {1}".format(label, spin_multiplicity)) break - if spin_multiplicity == 0 and 'SPIN SYMMETRY' in line: + if spin_multiplicity == 0 and "SPIN SYMMETRY" in line: spin_symmetry = line.split()[-1] - if spin_symmetry == 'Singlet': + if spin_symmetry == "Singlet": spin_multiplicity = 1 - elif spin_symmetry == 'Doublet': + elif spin_symmetry == "Doublet": spin_multiplicity = 2 - elif spin_symmetry == 'Triplet': + elif spin_symmetry == "Triplet": spin_multiplicity = 3 - elif spin_symmetry == 'Quartet': + elif spin_symmetry == "Quartet": spin_multiplicity = 4 - elif spin_symmetry == 'Quintet': + elif spin_symmetry == "Quintet": spin_multiplicity = 5 - elif spin_symmetry == 'Sextet': + elif spin_symmetry == "Sextet": spin_multiplicity = 6 if spin_multiplicity: - logging.debug( - 'Conformer {0} is assigned a spin multiplicity of {1}'.format(label, spin_multiplicity)) + logging.debug("Conformer {0} is assigned a spin multiplicity of {1}".format(label, spin_multiplicity)) break # The data we want is in the Thermochemistry section of the output - if 'THERMODYNAMICAL' in line: + if "THERMODYNAMICAL" in line: modes = [] line = f.readline() - while line != '': - + while line != "": # This marks the end of the thermochemistry section - if '*************************************************' in line: + if "*************************************************" in line: break # Read molecular mass for external translational modes - elif 'Molecular Mass:' in line: + elif "Molecular Mass:" in line: mass = float(line.split()[2]) translation = IdealGasTranslation(mass=(mass, "amu")) modes.append(translation) # Read moments of inertia for external rotational modes - elif 'Rotational Constants' in line and line.split()[-1] == '[GHz]': + elif "Rotational Constants" in line and line.split()[-1] == "[GHz]": inertia = [float(d) for d in line.split()[-4:-1]] for i in range(3): - inertia[i] = constants.h / (8 * constants.pi * constants.pi * inertia[i] * 1e9) \ - * constants.Na * 1e23 + inertia[i] = constants.h / (8 * constants.pi * constants.pi * inertia[i] * 1e9) * constants.Na * 1e23 rotation = NonlinearRotor(inertia=(inertia, "amu*angstrom^2"), symmetry=symmetry) modes.append(rotation) - elif 'Rotational Constant' in line and line.split()[3] == '[GHz]': + elif "Rotational Constant" in line and line.split()[3] == "[GHz]": inertia = float(line.split()[2]) - inertia = constants.h / (8 * constants.pi * constants.pi * inertia * 1e9) \ - * constants.Na * 1e23 + inertia = constants.h / (8 * constants.pi * constants.pi * inertia * 1e9) * constants.Na * 1e23 rotation = LinearRotor(inertia=(inertia, "amu*angstrom^2"), symmetry=symmetry) modes.append(rotation) # Read vibrational modes - elif 'Vibrational Temperatures' in line: + elif "Vibrational Temperatures" in line: frequencies = [] frequencies.extend([float(d) for d in line.split()[3:]]) line = f.readline() - while line.strip() != '': + while line.strip() != "": frequencies.extend([float(d) for d in line.split()]) line = f.readline() # Convert from K to cm^-1 @@ -310,10 +303,12 @@ def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=Non # Read the next line in the file line = f.readline() - return Conformer(E0=(e0 * 0.001, "kJ/mol"), modes=modes, spin_multiplicity=spin_multiplicity, - optical_isomers=optical_isomers), unscaled_frequencies + return ( + Conformer(E0=(e0 * 0.001, "kJ/mol"), modes=modes, spin_multiplicity=spin_multiplicity, optical_isomers=optical_isomers), + unscaled_frequencies, + ) - def load_energy(self, zpe_scale_factor=1.): + def load_energy(self, zpe_scale_factor=1.0): """ Return either the f12 or MRCI energy in J/mol from a Molpro Logfile. If the MRCI job outputted the MRCI+Davidson energy, the latter is returned. @@ -322,53 +317,51 @@ def load_energy(self, zpe_scale_factor=1.): a better approximation, but for higher basis sets f12b is a better approximation. """ e_elect = None - with open(self.path, 'r') as f: + with open(self.path, "r") as f: lines = f.readlines() # Determine whether the sp method is f12, # if so whether we should parse f12a or f12b according to the basis set. # Otherwise, check whether the sp method is MRCI. f12, f12a, f12b, mrci = False, False, False, False for line in lines: - if 'basis' in line.lower(): - if 'vtz' in line.lower() or 'vdz' in line.lower(): + if "basis" in line.lower(): + if "vtz" in line.lower() or "vdz" in line.lower(): f12a = True # MRCI could also have a vdz/vtz basis, so don't break yet - elif any(high_basis in line.lower() for high_basis in ['vqz', 'v5z', 'v6z', 'v7z', 'v8z']): + elif any(high_basis in line.lower() for high_basis in ["vqz", "v5z", "v6z", "v7z", "v8z"]): f12b = True # MRCI could also have a v(4+)z basis, so don't break yet - elif 'ccsd' in line.lower() and 'f12' in line.lower(): + elif "ccsd" in line.lower() and "f12" in line.lower(): f12 = True - elif 'mrci' in line.lower(): + elif "mrci" in line.lower(): mrci = True f12a, f12b = False, False break - elif 'point group' in line.lower(): + elif "point group" in line.lower(): # We should know the method by this point, so break if possible, but don't throw an error yet if any([mrci, f12a, f12b]): break else: - raise LogError('Could not determine type of calculation. Currently, CCSD(T)-F12a, CCSD(T)-F12b, ' - 'MRCI, MRCI+Davidson are supported') + raise LogError("Could not determine type of calculation. Currently, CCSD(T)-F12a, CCSD(T)-F12b, " "MRCI, MRCI+Davidson are supported") # Search for e_elect for line in lines: if f12 and f12a: - if 'CCSD(T)-F12a' in line and 'energy' in line: + if "CCSD(T)-F12a" in line and "energy" in line: e_elect = float(line.split()[-1]) break elif f12 and f12b: - if 'CCSD(T)-F12b' in line and 'energy' in line: + if "CCSD(T)-F12b" in line and "energy" in line: e_elect = float(line.split()[-1]) break elif mrci: # First search for MRCI+Davidson energy - if '(Davidson, relaxed reference)' in line: + if "(Davidson, relaxed reference)" in line: e_elect = float(line.split()[3]) - logging.debug('Found MRCI+Davidson energy in molpro log file {0}, using this value'.format( - self.path)) + logging.debug("Found MRCI+Davidson energy in molpro log file {0}, using this value".format(self.path)) break elif not f12: - if 'Electronic Energy at 0' in line: + if "Electronic Energy at 0" in line: e_elect = float(line.split()[-2]) break - if 'CCSD' in line and 'energy=' in line: + if "CCSD" in line and "energy=" in line: e_elect = float(line.split()[-1]) break if e_elect is None and mrci: @@ -377,19 +370,18 @@ def load_energy(self, zpe_scale_factor=1.): for line in lines: if read_e_elect: e_elect = float(line.split()[0]) - logging.debug('Found MRCI energy in molpro log file {0}, using this value' - ' (did NOT find MRCI+Davidson)'.format(self.path)) + logging.debug("Found MRCI energy in molpro log file {0}, using this value" " (did NOT find MRCI+Davidson)".format(self.path)) break - if all(w in line for w in ('MRCI', 'MULTI', 'HF-SCF')): + if all(w in line for w in ("MRCI", "MULTI", "HF-SCF")): read_e_elect = True - logging.debug('Molpro energy found is {0} Hartree'.format(e_elect)) + logging.debug("Molpro energy found is {0} Hartree".format(e_elect)) # multiply e_elect by correct constants if e_elect is not None: e_elect *= constants.E_h * constants.Na - logging.debug('Molpro energy found is {0} J/mol'.format(e_elect)) + logging.debug("Molpro energy found is {0} J/mol".format(e_elect)) return e_elect else: - raise LogError('Unable to find energy in Molpro log file {0}.'.format(self.path)) + raise LogError("Unable to find energy in Molpro log file {0}.".format(self.path)) def load_zero_point_energy(self): """ @@ -398,13 +390,13 @@ def load_zero_point_energy(self): zpe = None - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": # Do NOT read the ZPE from the "E(ZPE)=" line, as this is the scaled version! # We will read in the unscaled ZPE and later multiply the scaling factor # from the input file - if 'Electronic Energy at 0 [K]:' in line: + if "Electronic Energy at 0 [K]:" in line: electronic_energy = float(line.split()[5]) line = f.readline() ee_plus_zpe = float(line.split()[5]) @@ -414,19 +406,21 @@ def load_zero_point_energy(self): if zpe is not None: return zpe else: - raise LogError('Unable to find zero-point energy in Molpro log file. Make sure that the ' - 'keyword {frequencies, thermo, print,thermo} is included in the input file.') + raise LogError( + "Unable to find zero-point energy in Molpro log file. Make sure that the " + "keyword {frequencies, thermo, print,thermo} is included in the input file." + ) def load_negative_frequency(self): """ Return the negative frequency from a transition state frequency calculation in cm^-1. """ freqs = [] - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": # Read vibrational frequencies - if 'Normal Modes of imaginary frequencies' in line: + if "Normal Modes of imaginary frequencies" in line: for i in range(3): line = f.readline() freqs.append(line.split()[2]) @@ -435,16 +429,16 @@ def load_negative_frequency(self): if len(freqs) == 1: return -float(freqs[0]) elif len(freqs) > 1: - logging.info('More than one imaginary frequency in Molpro output file {0}.'.format(self.path)) + logging.info("More than one imaginary frequency in Molpro output file {0}.".format(self.path)) return -float(freqs[0]) else: - raise LogError('Unable to find imaginary frequency in Molpro output file {0}'.format(self.path)) + raise LogError("Unable to find imaginary frequency in Molpro output file {0}".format(self.path)) def load_scan_energies(self): """ Rotor scans are not implemented in Molpro """ - raise NotImplementedError('Rotor scans not implemented in Molpro') + raise NotImplementedError("Rotor scans not implemented in Molpro") def get_T1_diagnostic(self): """ @@ -455,10 +449,10 @@ def get_T1_diagnostic(self): log = f.readlines() for line in reversed(log): - if 'T1 diagnostic: ' in line: + if "T1 diagnostic: " in line: items = line.split() return float(items[-1]) - raise LogError('Unable to find T1 diagnostic in energy file: {0}'.format(self.path)) + raise LogError("Unable to find T1 diagnostic in energy file: {0}".format(self.path)) def get_D1_diagnostic(self): """ @@ -469,18 +463,18 @@ def get_D1_diagnostic(self): log = f.readlines() for line in reversed(log): - if 'D1 diagnostic: ' in line: + if "D1 diagnostic: " in line: items = line.split() return float(items[-1]) - raise LogError('Unable to find D1 diagnostic in energy file: {0}'.format(self.path)) + raise LogError("Unable to find D1 diagnostic in energy file: {0}".format(self.path)) def load_scan_pivot_atoms(self): """Not implemented for Molpro""" - raise NotImplementedError('The load_scan_pivot_atoms method is not implemented for Molpro Logs') + raise NotImplementedError("The load_scan_pivot_atoms method is not implemented for Molpro Logs") def load_scan_frozen_atoms(self): """Not implemented for Molpro""" - raise NotImplementedError('The load_scan_frozen_atoms method is not implemented for Molpro Logs') + raise NotImplementedError("The load_scan_frozen_atoms method is not implemented for Molpro Logs") register_ess_adapter("MolproLog", MolproLog) diff --git a/arkane/ess/orca.py b/arkane/ess/orca.py index 7b9774208a..9c618c8bc2 100644 --- a/arkane/ess/orca.py +++ b/arkane/ess/orca.py @@ -58,25 +58,24 @@ def check_for_errors(self): Checks for common errors in an Orca log file. If any are found, this method will raise an error and crash. """ - with open(os.path.join(self.path), 'r') as f: + with open(os.path.join(self.path), "r") as f: lines = f.readlines() error = None for line in reversed(lines): # check for common error messages - if 'ORCA finished by error termination in SCF' in line: - error = 'SCF' + if "ORCA finished by error termination in SCF" in line: + error = "SCF" break - elif 'ORCA finished by error termination in MDCI' in line: - error = 'MDCI' + elif "ORCA finished by error termination in MDCI" in line: + error = "MDCI" break - elif 'Error : multiplicity' in line: - error = f'The multiplicity and charge combination for species {species_label} are wrong.' + elif "Error : multiplicity" in line: + error = f"The multiplicity and charge combination for species {species_label} are wrong." break - elif 'ORCA TERMINATED NORMALLY' in line: + elif "ORCA TERMINATED NORMALLY" in line: break if error: - raise LogError(f'There was an error ({error}) with Orca output file {self.path} ' - f'due to line:\n{line}') + raise LogError(f"There was an error ({error}) with Orca output file {self.path} " f"due to line:\n{line}") def get_number_of_atoms(self): """ @@ -85,15 +84,15 @@ def get_number_of_atoms(self): """ natoms = 0 - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '' and natoms == 0: + while line != "" and natoms == 0: # Automatically determine the number of atoms - if 'CARTESIAN COORDINATES (ANGSTROEM)' in line and natoms == 0: + if "CARTESIAN COORDINATES (ANGSTROEM)" in line and natoms == 0: for i in range(2): line = f.readline() - while '---------------------------------' not in line: + while "---------------------------------" not in line: natoms += 1 line = f.readline() if not line.strip(): @@ -110,9 +109,9 @@ def load_force_constant_matrix(self): If no force constant matrix can be found, ``None`` is returned. """ hess_files = list() - for (_, _, files) in os.walk(os.path.dirname(self.path)): + for _, _, files in os.walk(os.path.dirname(self.path)): for file_ in files: - if file_.endswith('.hess'): + if file_.endswith(".hess"): hess_files.append(file_) break if len(hess_files) == 1: @@ -123,18 +122,20 @@ def load_force_constant_matrix(self): if hess_file == expected_hess_name: break else: - raise LogError(f'Could not identify the intended .hess file in {os.path.dirname(self.path)}.\n' - f'Expected to find {expected_hess_name} in that folder.') + raise LogError( + f"Could not identify the intended .hess file in {os.path.dirname(self.path)}.\n" + f"Expected to find {expected_hess_name} in that folder." + ) force = None n_atoms = self.get_number_of_atoms() n_rows = n_atoms * 3 - with open(hess_file, 'r') as f: + with open(hess_file, "r") as f: line = f.readline() - while line != '': - if '$hessian' in line: + while line != "": + if "$hessian" in line: line = f.readline() - force = np.zeros((n_rows, n_rows), np.float64) + force = np.zeros((n_rows, n_rows), float) for i in range(int(math.ceil(n_rows / 5.0))): line = f.readline() for j in range(n_rows): @@ -142,7 +143,7 @@ def load_force_constant_matrix(self): for k in range(len(data)): force[j, i * 5 + k] = float(data[k]) # Convert from atomic units (Hartree/Bohr_radius^2) to J/m^2 - force *= 4.35974417e-18 / 5.291772108e-11 ** 2 + force *= 4.35974417e-18 / 5.291772108e-11**2 line = f.readline() return force @@ -162,11 +163,11 @@ def load_geometry(self): geometry_flag = False for i in reversed(range(len(log))): line = log[i] - if 'CARTESIAN COORDINATES (ANGSTROEM)' in line: - for line in log[(i + 2):]: + if "CARTESIAN COORDINATES (ANGSTROEM)" in line: + for line in log[(i + 2) :]: if not line.strip(): break - if '---------------------------------' not in line: + if "---------------------------------" not in line: data = line.split() atoms.append(data[0]) coords.append([float(c) for c in data[1:]]) @@ -180,15 +181,15 @@ def load_geometry(self): mass1, num1 = get_element_mass(atom1) mass.append(mass1) numbers.append(num1) - coord = np.array(coords, np.float64) - number = np.array(numbers, np.int) - mass = np.array(mass, np.float64) + coord = np.array(coords, float) + number = np.array(numbers, int) + mass = np.array(mass, float) if len(number) == 0 or len(coord) == 0 or len(mass) == 0: - raise LogError(f'Unable to read atoms from orca geometry output file {self.path}.') + raise LogError(f"Unable to read atoms from orca geometry output file {self.path}.") return coord, number, mass - def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=''): + def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=""): """ Load the molecular degree of freedom data from a log file created as the result of an Orca "Freq" quantum chemistry calculation. As @@ -209,17 +210,17 @@ def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=Non with open(self.path) as f: log = f.readlines() for i, line in enumerate(log): - if spin_multiplicity == 0 and ' Multiplicity Mult' in line: + if spin_multiplicity == 0 and " Multiplicity Mult" in line: spin_multiplicity = int(float(line.split()[3])) - if ' Mode freq' in line: + if " Mode freq" in line: frequencies = list() - for line_ in log[(i + 2):]: + for line_ in log[(i + 2) :]: if not line_.strip(): break frequencies.extend([float(line_.split()[1])]) else: - raise Exception(f'Frequencies not found in {self.path}') + raise Exception(f"Frequencies not found in {self.path}") frequencies = [f for f in frequencies if f > 0.0] unscaled_frequencies = frequencies vibration = HarmonicOscillator(frequencies=(frequencies, "cm^-1")) @@ -243,25 +244,24 @@ def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=Non # Take only the last modes found (in the event of multiple jobs). modes = [mmass[-1], rot[-1], freq[-1]] - return Conformer(E0=(e0 * 0.001, "kJ/mol"), - modes=modes, - spin_multiplicity=spin_multiplicity, - optical_isomers=optical_isomers),\ - unscaled_frequencies + return ( + Conformer(E0=(e0 * 0.001, "kJ/mol"), modes=modes, spin_multiplicity=spin_multiplicity, optical_isomers=optical_isomers), + unscaled_frequencies, + ) - def load_energy(self, zpe_scale_factor=1.): + def load_energy(self, zpe_scale_factor=1.0): """ Load the energy in J/ml from an Orca log file. Only the last energy in the file is returned. The zero-point energy is *not* included in the returned value. """ e_elect = None - with open(self.path, 'r') as f: + with open(self.path, "r") as f: for line in f: - if 'FINAL SINGLE POINT ENERGY' in line: # for all methods in Orca + if "FINAL SINGLE POINT ENERGY" in line: # for all methods in Orca e_elect = float(line.split()[-1]) if e_elect is None: - raise LogError('Unable to find energy in Orca output file.') + raise LogError("Unable to find energy in Orca output file.") return e_elect * constants.E_h * constants.Na def load_zero_point_energy(self): @@ -269,12 +269,12 @@ def load_zero_point_energy(self): Load the unscaled zero-point energy in J/mol from a Orca output file. """ zpe = None - with open(self.path, 'r') as f: + with open(self.path, "r") as f: for line in f: - if 'Zero point energy' in line: + if "Zero point energy" in line: zpe = float(line.split()[-4]) * constants.E_h * constants.Na if zpe is None: - raise LogError('Unable to find zero-point energy in Orca output file.') + raise LogError("Unable to find zero-point energy in Orca output file.") return zpe def load_negative_frequency(self): @@ -286,16 +286,16 @@ def load_negative_frequency(self): with open(self.path) as f: log = f.readlines() for line in log: - if '***imaginary mode***' in line: + if "***imaginary mode***" in line: frequencies.append(float(line.split()[1])) if len(frequencies) == 1: return frequencies[0] elif len(frequencies) > 1: - logging.info('More than one imaginary frequency in Orca output file {0}.'.format(self.path)) + logging.info("More than one imaginary frequency in Orca output file {0}.".format(self.path)) return frequencies[0] else: - raise LogError(f'Unable to find imaginary frequency in Orca output file {self.path}') + raise LogError(f"Unable to find imaginary frequency in Orca output file {self.path}") def get_T1_diagnostic(self): """ @@ -306,21 +306,21 @@ def get_T1_diagnostic(self): with open(self.path) as f: log = f.readlines() for line in reversed(log): - if 'T1 diagnostic ' in line: + if "T1 diagnostic " in line: items = line.split() return float(items[-1]) def load_scan_energies(self): """not implemented in Orca""" - raise NotImplementedError('The load_scan_energies method is not implemented for Orca.') + raise NotImplementedError("The load_scan_energies method is not implemented for Orca.") def load_scan_pivot_atoms(self): """Not implemented for Orca""" - raise NotImplementedError('The load_scan_pivot_atoms method is not implemented for Orca.') + raise NotImplementedError("The load_scan_pivot_atoms method is not implemented for Orca.") def load_scan_frozen_atoms(self): """Not implemented for Orca""" - raise NotImplementedError('The load_scan_frozen_atoms method is not implemented for Orca.') + raise NotImplementedError("The load_scan_frozen_atoms method is not implemented for Orca.") register_ess_adapter("OrcaLog", OrcaLog) diff --git a/arkane/ess/psi4.py b/arkane/ess/psi4.py index fe61e56c01..f8aa517617 100644 --- a/arkane/ess/psi4.py +++ b/arkane/ess/psi4.py @@ -39,7 +39,7 @@ import rmgpy.constants as constants from rmgpy.statmech import IdealGasTranslation, NonlinearRotor, LinearRotor, HarmonicOscillator, Conformer -from arkane.common import get_element_mass, get_principal_moments_of_inertia, convert_imaginary_freq_to_negative_float +from arkane.common import get_element_mass, get_principal_moments_of_inertia, convert_imaginary_freq_to_negative_float from arkane.exceptions import LogError from arkane.ess.adapter import ESSAdapter from arkane.ess.factory import register_ess_adapter @@ -65,32 +65,31 @@ def check_for_errors(self): error = None terminated = False for line in reversed(log): - if 'Psi4 exiting successfully' in line: + if "Psi4 exiting successfully" in line: terminated = True - elif 'PSIO Error' in line: - error = 'I/O error' - elif 'Fatal Error' in line: - error = 'Fatal Error' - elif 'RuntimeError' in line: - error = 'runtime' + elif "PSIO Error" in line: + error = "I/O error" + elif "Fatal Error" in line: + error = "Fatal Error" + elif "RuntimeError" in line: + error = "runtime" if error is not None: - raise LogError(f'There was an error ({error}) with the Psi4 output file {self.path} ' - f'due to line:\n{line}') + raise LogError(f"There was an error ({error}) with the Psi4 output file {self.path} " f"due to line:\n{line}") if not terminated: - raise LogError(f'Psi4 run in output file {self.path} did not successfully converged.') + raise LogError(f"Psi4 run in output file {self.path} did not successfully converged.") def get_number_of_atoms(self): """ Return the number of atoms in the molecular configuration used in the Psi4 output file. """ n_atoms = 0 - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '' and n_atoms == 0: - if 'Center X Y Z Mass ' in line: + while line != "" and n_atoms == 0: + if "Center X Y Z Mass " in line: _ = f.readline() line = f.readline() - while line != '\n': + while line != "\n": n_atoms += 1 line = f.readline() break @@ -109,23 +108,24 @@ def load_force_constant_matrix(self): force = None n_atoms = self.get_number_of_atoms() n_rows = n_atoms * 3 - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': - if 'Force constants in mass-weighted Cartesian coordinates' in line: + while line != "": + if "Force constants in mass-weighted Cartesian coordinates" in line: f.readline() f_array = list() - while line != '\n': + while line != "\n": line = f.readline() # Convert from atomic units (Hartree/Bohr_radius^2) to J/m^2 - f_array.extend([float(f) * 4.35974417e-18 / 5.291772108e-11 ** 2 for f in - line.replace('[', '').replace(']', '').split()]) + f_array.extend([float(f) * 4.35974417e-18 / 5.291772108e-11**2 for f in line.replace("[", "").replace("]", "").split()]) force = np.array(f_array).reshape(n_rows, n_rows) line = f.readline() if force is None: - logging.warning(f'Could not find a force constant matrix in the Psi4 log file {self.path}\n' - f'To make sure Psi4 prints out the force constant matrix,' - f'make sure to set the verbose print level in Psi4 ("set_print") to at least 3.') + logging.warning( + f"Could not find a force constant matrix in the Psi4 log file {self.path}\n" + f"To make sure Psi4 prints out the force constant matrix," + f'make sure to set the verbose print level in Psi4 ("set_print") to at least 3.' + ) return force def load_geometry(self): @@ -143,9 +143,9 @@ def load_geometry(self): geometry_flag = False for i in reversed(range(len(log))): line = log[i] - if 'Center X Y Z Mass' in line: - for line in log[(i + 2):]: - if line != '\n': + if "Center X Y Z Mass" in line: + for line in log[(i + 2) :]: + if line != "\n": data = line.split() atoms.append(data[0]) coord.append([float(c) for c in data[1:-1]]) @@ -159,15 +159,15 @@ def load_geometry(self): mass_, num_ = get_element_mass(atom) mass.append(mass_) number.append(num_) - coord = np.array(coord, np.float64) - number = np.array(number, np.int) - mass = np.array(mass, np.float64) + coord = np.array(coord, float) + number = np.array(number, int) + mass = np.array(mass, float) if any(len(param) == 0 for param in [number, coord, mass]): - raise LogError(f'Unable to read atoms from Psi4 geometry output file {self.path}.') + raise LogError(f"Unable to read atoms from Psi4 geometry output file {self.path}.") return coord, number, mass - def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=''): + def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=""): """ Load the molecular degree of freedom data from a log file created as the result of a Psi4 "Freq" quantum chemistry calculation. As @@ -185,26 +185,23 @@ def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=Non if symmetry is None: symmetry = _symmetry - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': - if spin_multiplicity == 0 and 'Multiplicity =' in line: + while line != "": + if spin_multiplicity == 0 and "Multiplicity =" in line: spin_multiplicity = int(float(line.split()[2])) - logging.debug(f'Conformer {label} is assigned a spin multiplicity of {spin_multiplicity}') + logging.debug(f"Conformer {label} is assigned a spin multiplicity of {spin_multiplicity}") - if 'Harmonic Vibrational Analysis' in line: + if "Harmonic Vibrational Analysis" in line: frequencies = [] - while 'Thermochemistry Components' not in line: - if 'Freq [cm^-1]' in line: + while "Thermochemistry Components" not in line: + if "Freq [cm^-1]" in line: if len(line.split()) == 5: - frequencies.extend([float(convert_imaginary_freq_to_negative_float(d)) - for d in line.split()[-3:]]) + frequencies.extend([float(convert_imaginary_freq_to_negative_float(d)) for d in line.split()[-3:]]) elif len(line.split()) == 4: - frequencies.extend([float(convert_imaginary_freq_to_negative_float(d)) - for d in line.split()[-2:]]) + frequencies.extend([float(convert_imaginary_freq_to_negative_float(d)) for d in line.split()[-2:]]) elif len(line.split()) == 3: - frequencies.extend([float(convert_imaginary_freq_to_negative_float(d)) - for d in line.split()[-1:]]) + frequencies.extend([float(convert_imaginary_freq_to_negative_float(d)) for d in line.split()[-1:]]) line = f.readline() frequencies = [f for f in frequencies if f > 0.0] @@ -230,13 +227,12 @@ def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=Non # Take only the last modes found (in the event of multiple jobs). modes = [mmass[-1], rot[-1], freq[-1]] - return Conformer(E0=(e0 * 0.001, "kJ/mol"), - modes=modes, - spin_multiplicity=spin_multiplicity, - optical_isomers=optical_isomers),\ - unscaled_frequencies + return ( + Conformer(E0=(e0 * 0.001, "kJ/mol"), modes=modes, spin_multiplicity=spin_multiplicity, optical_isomers=optical_isomers), + unscaled_frequencies, + ) - def load_energy(self, zpe_scale_factor=1.): + def load_energy(self, zpe_scale_factor=1.0): """ Load the energy in J/mol from a Psi4 log file. Only the smallest energy in the file is returned. The zero-point energy is *not* included in @@ -246,12 +242,12 @@ def load_energy(self, zpe_scale_factor=1.): the smallest values is the correct value. """ a = list() - with open(self.path, 'r') as f: + with open(self.path, "r") as f: for line in f: - if 'Total Energy =' in line: + if "Total Energy =" in line: a.append(float(line.split()[3]) * constants.E_h * constants.Na) if not len(a): - raise LogError(f'Unable to find energy in Psi4 output file {self.path}.') + raise LogError(f"Unable to find energy in Psi4 output file {self.path}.") e_elect = min(a) return e_elect @@ -260,13 +256,13 @@ def load_zero_point_energy(self): Load the unscaled zero-point energy in J/mol from a Psi4 output file. """ zpe = [] - with open(self.path, 'r') as f: + with open(self.path, "r") as f: for line in f.readlines(): - if 'Correction ZPE' in line: + if "Correction ZPE" in line: zpe.append(float(line.split()[2]) * 4184) # Convert kcal/mol to J/mol. - logging.debug(f'ZPE is {zpe}') + logging.debug(f"ZPE is {zpe}") if not len(zpe): - raise LogError(f'Unable to find zero-point energy in Psi4 output file {self.path}.') + raise LogError(f"Unable to find zero-point energy in Psi4 output file {self.path}.") return zpe[-1] def load_negative_frequency(self): @@ -275,49 +271,46 @@ def load_negative_frequency(self): Since there can be many imaginary frequencies, only the first one is returned. """ negative_frequencies, frequency = None, None - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': - if 'Harmonic Vibrational Analysis' in line: + while line != "": + if "Harmonic Vibrational Analysis" in line: frequencies = [] - while 'Thermochemistry Components' not in line: - if 'Freq [cm^-1]' in line: + while "Thermochemistry Components" not in line: + if "Freq [cm^-1]" in line: if len(line.split()) == 5: - frequencies.extend([float(convert_imaginary_freq_to_negative_float(d)) - for d in line.split()[-3:]]) + frequencies.extend([float(convert_imaginary_freq_to_negative_float(d)) for d in line.split()[-3:]]) elif len(line.split()) == 4: - frequencies.extend([float(convert_imaginary_freq_to_negative_float(d)) - for d in line.split()[-2:]]) + frequencies.extend([float(convert_imaginary_freq_to_negative_float(d)) for d in line.split()[-2:]]) elif len(line.split()) == 3: - frequencies.extend([float(convert_imaginary_freq_to_negative_float(d)) - for d in line.split()[-1:]]) + frequencies.extend([float(convert_imaginary_freq_to_negative_float(d)) for d in line.split()[-1:]]) line = f.readline() negative_frequencies = [f for f in frequencies if f < 0.0] line = f.readline() if negative_frequencies is None: - raise LogError('Unable to find imaginary frequency in Psi4 output file {0}.'.format(self.path)) + raise LogError("Unable to find imaginary frequency in Psi4 output file {0}.".format(self.path)) elif len(negative_frequencies) == 1: return negative_frequencies[0] else: - logging.info('More than one imaginary frequency in Psi4 output file {0}.'.format(self.path)) + logging.info("More than one imaginary frequency in Psi4 output file {0}.".format(self.path)) return negative_frequencies[0] def load_scan_energies(self): """Not implemented for Psi4""" - raise NotImplementedError('The load_scan_energies method is not implemented for Psi4.') + raise NotImplementedError("The load_scan_energies method is not implemented for Psi4.") def load_scan_pivot_atoms(self): """Not implemented for Psi4""" - raise NotImplementedError('The load_scan_pivot_atoms method is not implemented for Psi4.') + raise NotImplementedError("The load_scan_pivot_atoms method is not implemented for Psi4.") def load_scan_frozen_atoms(self): """Not implemented for Psi4""" - raise NotImplementedError('The load_scan_frozen_atoms method is not implemented for Psi4.') + raise NotImplementedError("The load_scan_frozen_atoms method is not implemented for Psi4.") def get_T1_diagnostic(self): """Not implemented for Psi4""" - raise NotImplementedError('The get_T1_diagnostic method is not implemented for Psi4.') + raise NotImplementedError("The get_T1_diagnostic method is not implemented for Psi4.") register_ess_adapter("Psi4Log", Psi4Log) diff --git a/arkane/ess/psi4Test.py b/arkane/ess/psi4Test.py deleted file mode 100644 index 5280b8c421..0000000000 --- a/arkane/ess/psi4Test.py +++ /dev/null @@ -1,222 +0,0 @@ -#!/usr/bin/env python3 - -############################################################################### -# # -# RMG - Reaction Mechanism Generator # -# # -# Copyright (c) 2002-2021 Prof. William H. Green (whgreen@mit.edu), # -# Prof. Richard H. West (r.west@neu.edu) and the RMG Team (rmg_dev@mit.edu) # -# # -# Permission is hereby granted, free of charge, to any person obtaining a # -# copy of this software and associated documentation files (the 'Software'), # -# to deal in the Software without restriction, including without limitation # -# the rights to use, copy, modify, merge, publish, distribute, sublicense, # -# and/or sell copies of the Software, and to permit persons to whom the # -# Software is furnished to do so, subject to the following conditions: # -# # -# The above copyright notice and this permission notice shall be included in # -# all copies or substantial portions of the Software. # -# # -# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # -# DEALINGS IN THE SOFTWARE. # -# # -############################################################################### - -""" -This module contains unit tests of the :mod:`arkane.ess.psi4` module. -""" - -import os -import unittest - -import numpy as np - -from rmgpy.statmech import IdealGasTranslation, NonlinearRotor, HarmonicOscillator, HinderedRotor - -from arkane.exceptions import LogError -from arkane.ess.psi4 import Psi4Log - -################################################################################ - - -class Psi4LogTest(unittest.TestCase): - """ - Contains unit tests for the Psi4Log module, used for parsing Psi4 log files. - """ - @classmethod - def setUpClass(cls): - """ - A method that is run before all unit tests in this class. - """ - cls.data_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'psi4') - - def test_check_for_errors(self): - """ - Uses Psi4 log files that had various errors to test if errors are properly parsed. - """ - with self.assertRaises(LogError): - Psi4Log(os.path.join(self.data_path, 'IO_error.out')) - - def test_number_of_atoms_from_psi4_log(self): - """ - Uses a Psi4 log files to test that - number of atoms can be properly read. - """ - log = Psi4Log(os.path.join(self.data_path, 'opt_freq.out')) - self.assertEqual(log.get_number_of_atoms(), 3) - log = Psi4Log(os.path.join(self.data_path, 'opt_freq_ts.out')) - self.assertEqual(log.get_number_of_atoms(), 4) - log = Psi4Log(os.path.join(self.data_path, 'opt_freq_dft.out')) - self.assertEqual(log.get_number_of_atoms(), 3) - log = Psi4Log(os.path.join(self.data_path, 'opt_freq_dft_ts.out')) - self.assertEqual(log.get_number_of_atoms(), 4) - - def test_energy_from_psi4_log(self): - """ - Uses a Psi4 log files to test that - molecular energies can be properly read. - """ - log = Psi4Log(os.path.join(self.data_path, 'opt_freq.out')) - self.assertAlmostEqual(log.load_energy(), -199599899.9822719, delta=1e-2) - log = Psi4Log(os.path.join(self.data_path, 'opt_freq_ts.out')) - self.assertAlmostEqual(log.load_energy(), -395828407.5987777, delta=1e-2) - log = Psi4Log(os.path.join(self.data_path, 'opt_freq_dft.out')) - self.assertAlmostEqual(log.load_energy(), -200640009.37231186, delta=1e-2) - log = Psi4Log(os.path.join(self.data_path, 'opt_freq_dft_ts.out')) - self.assertAlmostEqual(log.load_energy(), -397841662.56434655, delta=1e-2) - - def test_zero_point_energy_from_psi4_log(self): - """ - Uses Psi4 log files to test that zero-point energies can be properly read. - """ - log = Psi4Log(os.path.join(self.data_path, 'opt_freq.out')) - self.assertAlmostEqual(log.load_zero_point_energy(), 60868.832, delta=1e-3) - log = Psi4Log(os.path.join(self.data_path, 'opt_freq_dft.out')) - self.assertAlmostEqual(log.load_zero_point_energy(), 56107.44, delta=1e-3) - log = Psi4Log(os.path.join(self.data_path, 'opt_freq_dft_ts.out')) - self.assertAlmostEqual(log.load_zero_point_energy(), 67328.928, delta=1e-3) - log = Psi4Log(os.path.join(self.data_path, 'opt_freq_ts.out')) - self.assertAlmostEqual(log.load_zero_point_energy(), 75136.272, delta=1e-3) - - def test_load_force_constant_matrix_from_psi4_log(self): - """ - Uses Psi4 log files to test that force constant matrices can be properly read. - """ - log = Psi4Log(os.path.join(self.data_path, 'opt_freq.out')) - expected_mat_1 = np.array([[79.60709821, 0., 0., -158.56969492, 0., -119.50250089, -158.56969492, 0., 119.50250089], - [0., 0., 0., 0., 0., 0., 0., 0., 0.], - [0., 0., 51.88196366, -92.52464457, 0., -103.3438893, 92.52464457, 0., -103.3438893], - [-158.56969492, 0., -92.52464457, 682.40616438, 0., 422.33771249, -50.69495535, 0., -53.73729893], - [0., 0., 0., 0., 0., 0., 0., 0., 0.], - [-119.50250089, 0., -103.3438893, 422.33771249, 0., 385.24274073, 53.73729893, 0., 26.45946547], - [-158.56969492, 0., 92.52464457, -50.69495535, 0., 53.73729893, 682.40616438, 0., -422.33771249], - [0., 0., 0., 0., 0., 0., 0., 0., 0.], - [119.50250089, 0., -103.3438893, -53.73729893, 0., 26.45946547, -422.33771249, 0., 385.24274073]], - np.float64) - self.assertTrue(np.allclose(log.load_force_constant_matrix(), expected_mat_1)) - - log = Psi4Log(os.path.join(self.data_path, 'opt_freq_dft.out')) - expected_mat_2 = np.array([[65.29227021, 0., 0., -130.05593215, 0., -102.09767406, -130.05593215, 0., 102.09767406], - [0., 0., 0., 0., 0., 0., 0., 0., 0.], - [0., 0., 44.51008758, -77.12673693, 0., -88.65982001, 77.12673693, 0., -88.65982001], - [-130.05593215, 0., -77.12673693, 567.48290169, 0., 356.99781537, -49.36504715, 0., -49.73970876], - [0., 0., 0., 0., 0., 0., 0., 0., 0.], - [-102.09767406, 0., -88.65982001, 356.99781537, 0., 336.25221646, 49.73970876, 0., 16.95147799], - [-130.05593215, 0., 77.12673693, -49.36504715, 0., 49.73970876, 567.48290169, 0., -356.99781537], - [0., 0., 0., 0., 0., 0., 0., 0., 0.], - [102.09767406, 0., -88.65982001, -49.73970876, 0., 16.95147799, -356.99781537, 0., 336.25221646]], - np.float64) - self.assertTrue(np.allclose(log.load_force_constant_matrix(), expected_mat_2)) - - log = Psi4Log(os.path.join(self.data_path, 'opt_freq_dft_ts.out')) - expected_mat_3 = np.array([[-1.13580195, 0., 0., 3.38451439, 0., 0., 1.13580195, 0., 0., -3.38451439, 0., 0.], - [0., 32.96704817, -7.81315371, 0., -24.52602914, 47.62525747, 0., -23.84113467, -5.93732553, 0., -11.82985738, 7.15401071], - [0., -7.81315371, 54.99575056, 0., 23.33047286, -191.28989554, 0., 5.93732553, -7.5316094, 0., -15.85753341, 2.20187269], - [3.38451439, 0., 0., -10.08532992, 0., 0., -3.38451439, 0., 0., 10.08532992, 0., 0.], - [0., -24.52602914, 23.33047286, 0., 143.78387645, -158.67358218, 0., -11.82985738, 15.85753341, 0., 1.05099332, 2.55609183], - [0., 47.62525747, -191.28989554, 0., -158.67358218, 742.27868659, 0., -7.15401071, 2.20187269, 0., -2.55609183, 11.01167933], - [1.13580195, 0., 0., -3.38451439, 0., 0., -1.13580195, 0., 0., 3.38451439, 0., 0.], - [0., -23.84113467, 5.93732553, 0., -11.82985738, -7.15401071, 0., 32.96704817, 7.81315371, 0., -24.52602914, -47.62525747], - [0., -5.93732553, -7.5316094, 0., 15.85753341, 2.20187269, 0., 7.81315371, 54.99575056, 0., -23.33047286, -191.28989554], - [-3.38451439, 0., 0., 10.08532992, 0., 0., 3.38451439, 0., 0., -10.08532992, 0., 0.], - [0., -11.82985738, -15.85753341, 0., 1.05099332, -2.55609183, 0., -24.52602914, -23.33047286, 0., 143.78387645, 158.67358218], - [0., 7.15401071, 2.20187269, 0., 2.55609183, 11.01167933, 0., -47.62525747, -191.28989554, 0., 158.67358218, 742.27868659]], - np.float64) - self.assertTrue(np.allclose(log.load_force_constant_matrix(), expected_mat_3)) - - log = Psi4Log(os.path.join(self.data_path, 'opt_freq_ts.out')) - expected_mat_4 = np.array([[-1.36856086, 0., 0., 3.91653225, 0., 0., 1.36856086, 0., 0., -3.91653225, 0., 0.], - [0., 47.82294224, -11.20296807, 0., -35.81980174, 65.25989773, 0., -35.42652343, -7.19264812, 0., -13.56514966, 8.02470416], - [0., -11.20296807, 66.4797624, 0., 35.78782098, -229.4811002, 0., 7.19264812, -9.74548387, 0., -19.81147687, 3.46263087], - [3.91653225, 0., 0., -11.2082886, 0., 0., -3.91653225, 0., 0., 11.2082886, 0., 0.], - [0., -35.81980174, 35.78782098, 0., 194.95334567, -224.75547126, 0., -13.56514966, 19.81147687, 0., 1.78681563, 3.25854712], - [0., 65.25989773, -229.4811002, 0., -224.75547126, 889.16020169, 0., -8.02470416, 3.46263087, 0., -3.25854712, 11.25397079], - [1.36856086, 0., 0., -3.91653225, 0., 0., -1.36856086, 0., 0., 3.91653225, 0., 0.], - [0., -35.42652343, 7.19264812, 0., -13.56514966, -8.02470416, 0., 47.82294224, 11.20296807, 0., -35.81980174, -65.25989773], - [0., -7.19264812, -9.74548387, 0., 19.81147687, 3.46263087, 0., 11.20296807, 66.4797624, 0., -35.78782098, -229.4811002], - [-3.91653225, 0., 0., 11.2082886, 0., 0., 3.91653225, 0., 0., -11.2082886, 0., 0.], - [0., -13.56514966, -19.81147687, 0., 1.78681563, -3.25854712, 0., -35.81980174, -35.78782098, 0., 194.95334567, 224.75547126], - [0., 8.02470416, 3.46263087, 0., 3.25854712, 11.25397079, 0., -65.25989773, -229.4811002, 0., 224.75547126, 889.16020169]], - np.float64) - self.assertTrue(np.allclose(log.load_force_constant_matrix(), expected_mat_4)) - - def test_load_vibrations_from_psi4_log(self): - """ - Uses a Psi4 log files to test that - molecular energies can be properly read. - """ - log = Psi4Log(os.path.join(self.data_path, 'opt_freq.out')) - conformer, unscaled_frequencies = log.load_conformer() - self.assertEqual(len(conformer.modes[2]._frequencies.value), 3) - self.assertEqual(conformer.modes[2]._frequencies.value[2], 4261.7445) - log = Psi4Log(os.path.join(self.data_path, 'opt_freq_dft_ts.out')) - conformer, unscaled_frequencies = log.load_conformer() - self.assertEqual(len(conformer.modes[2]._frequencies.value), 5) - self.assertEqual(conformer.modes[2]._frequencies.value[2], 1456.2449) - - def test_load_modes_from_psi4_log(self): - """ - Uses a Psi4 log file for opt_freq.out to test that its - molecular modes can be properly read. - """ - log = Psi4Log(os.path.join(self.data_path, 'opt_freq.out')) - conformer, unscaled_frequencies = log.load_conformer() - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, IdealGasTranslation)]) == 1) - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, NonlinearRotor)]) == 1) - self.assertTrue(len([mode for mode in conformer.modes if isinstance(mode, HarmonicOscillator)]) == 1) - self.assertEqual(len(unscaled_frequencies), 3) - - def test_load_negative_frequency(self): - """ - Test properly loading negative frequencies. - """ - log_1 = Psi4Log(os.path.join(self.data_path, 'opt_freq_dft_ts.out')) - neg_freq_1 = log_1.load_negative_frequency() - self.assertEqual(neg_freq_1, -617.1749) - log_2 = Psi4Log(os.path.join(self.data_path, 'opt_freq_ts.out')) - neg_freq_2 = log_2.load_negative_frequency() - self.assertEqual(neg_freq_2, -653.3950) - - def test_spin_multiplicity_from_psi4_log(self): - """ - Uses a Psi4 log file for opt_freq_dft_ts.out to test that its - molecular degrees of freedom can be properly read. - """ - log = Psi4Log(os.path.join(self.data_path, 'opt_freq.out')) - conformer, unscaled_frequencies = log.load_conformer() - self.assertEqual(conformer.spin_multiplicity, 1) - log = Psi4Log(os.path.join(self.data_path, 'opt_freq_dft_ts.out')) - conformer, unscaled_frequencies = log.load_conformer() - self.assertEqual(conformer.spin_multiplicity, 1) - - -################################################################################ - - -if __name__ == '__main__': - unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arkane/ess/qchem.py b/arkane/ess/qchem.py index 7d54eccfe6..8e3f9c11f6 100644 --- a/arkane/ess/qchem.py +++ b/arkane/ess/qchem.py @@ -62,16 +62,17 @@ def check_for_errors(self): Checks for common errors in a QChem log file. If any are found, this method will raise an error and crash. """ - with open(os.path.join(self.path), 'r') as f: + with open(os.path.join(self.path), "r") as f: lines = f.readlines() error, warning_line, warning, warn_message = None, None, None, None for line in reversed(lines): # check for common error messages - if 'SCF failed' in line: - error = 'SCF failed' + if "SCF failed" in line: + error = "SCF failed" break - elif 'error' in line and 'DIIS' not in line and 'gprntSymmMtrx' not in line \ - and 'Relative error' not in line and 'zonesort' not in line: + elif ( + "error" in line and "DIIS" not in line and "gprntSymmMtrx" not in line and "Relative error" not in line and "zonesort" not in line + ): # these are **normal** lines that we should not capture: # "SCF converges when DIIS error is below 1.0E-08", or # "Cycle Energy DIIS Error" or @@ -85,15 +86,15 @@ def check_for_errors(self): # The end result is that your code may have run less optimally. # Other than that, the message is usually harmless. # https://docs.nersc.gov/jobs/errors/ - error = 'SCF failed' + error = "SCF failed" break - elif 'Invalid charge/multiplicity combination' in line: - error = 'Invalid charge/multiplicity combination' + elif "Invalid charge/multiplicity combination" in line: + error = "Invalid charge/multiplicity combination" break - elif 'MAXIMUM OPTIMIZATION CYCLES REACHED' in line: - error = 'Maximum optimization cycles reached.' + elif "MAXIMUM OPTIMIZATION CYCLES REACHED" in line: + error = "Maximum optimization cycles reached." break - elif 'Relative error' in line: + elif "Relative error" in line: warn_message = """ Per the QChem version 5 documentation: https://manual.q-chem.com/pdf/qchem_manual_5.0.pdf A warning message is printed whenever the relative error in the numerical electron count @@ -101,15 +102,12 @@ def check_for_errors(self): appears on the first SCF cycle, it is probably not serious, because the initial-guess density matrix is sometimes not idempotent. """ - warning = 'Relative error' + warning = "Relative error" warning_line = line if error: - raise LogError(f'There was an error ({error}) with QChem output file {self.path} ' - f'due to line:\n{line}') + raise LogError(f"There was an error ({error}) with QChem output file {self.path} " f"due to line:\n{line}") if warning: - logging.warning(f'{warning} with QChem output file {self.path} due to line:\n' - f'{warning_line}\n' - f'{warn_message}') + logging.warning(f"{warning} with QChem output file {self.path} due to line:\n" f"{warning_line}\n" f"{warn_message}") def get_number_of_atoms(self): """ @@ -118,14 +116,14 @@ def get_number_of_atoms(self): """ n_atoms = 0 - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '' and n_atoms == 0: + while line != "" and n_atoms == 0: # Automatically determine the number of atoms - if 'Standard Nuclear Orientation' in line and n_atoms == 0: + if "Standard Nuclear Orientation" in line and n_atoms == 0: for i in range(3): line = f.readline() - while '----------------------------------------------------' not in line: + while "----------------------------------------------------" not in line: n_atoms += 1 line = f.readline() line = f.readline() @@ -144,12 +142,12 @@ def load_force_constant_matrix(self): n_atoms = self.get_number_of_atoms() n_rows = n_atoms * 3 - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": # Read force constant matrix - if 'Final Hessian.' in line or 'Hessian of the SCF Energy' in line: - force = np.zeros((n_rows, n_rows), np.float64) + if "Final Hessian." in line or "Hessian of the SCF Energy" in line: + force = np.zeros((n_rows, n_rows), float) for i in range(int(math.ceil(n_rows / 6.0))): # Header row line = f.readline() @@ -160,13 +158,12 @@ def load_force_constant_matrix(self): force[j, i * 6 + k] = float(data[k + 1]) # F[i*5+k,j] = F[j,i*5+k] # Convert from atomic units (Hartree/Bohr_radius^2) to J/m^2 - force *= 4.35974417e-18 / 5.291772108e-11 ** 2 + force *= 4.35974417e-18 / 5.291772108e-11**2 line = f.readline() return force def load_geometry(self): - """ Return the optimum geometry of the molecular configuration from the QChem log file. If multiple such geometries are identified, only the @@ -182,9 +179,9 @@ def load_geometry(self): geometry_flag = False for i in reversed(range(len(log))): line = log[i] - if 'Standard Nuclear Orientation' in line: - for line in log[(i + 3):]: - if '------------' not in line: + if "Standard Nuclear Orientation" in line: + for line in log[(i + 3) :]: + if "------------" not in line: data = line.split() atom.append(data[1]) coord.append([float(c) for c in data[2:]]) @@ -199,15 +196,15 @@ def load_geometry(self): mass1, num1 = get_element_mass(atom1) mass.append(mass1) number.append(num1) - coord = np.array(coord, np.float64) - number = np.array(number, np.int) - mass = np.array(mass, np.float64) + coord = np.array(coord, float) + number = np.array(number, int) + mass = np.array(mass, float) if len(number) == 0 or len(coord) == 0 or len(mass) == 0: - raise LogError('Unable to read atoms from QChem geometry output file {0}.'.format(self.path)) + raise LogError("Unable to read atoms from QChem geometry output file {0}.".format(self.path)) return coord, number, mass - def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=''): + def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=""): """ Load the molecular degree of freedom data from an output file created as the result of a QChem "Freq" calculation. As QChem's guess of the external symmetry number is not always correct, @@ -227,31 +224,29 @@ def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=Non optical_isomers = _optical_isomers if symmetry is None: symmetry = _symmetry - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": # Read spin multiplicity if not explicitly given - if '$molecule' in line and spin_multiplicity == 0: + if "$molecule" in line and spin_multiplicity == 0: line = f.readline() if len(line.split()) == 2: spin_multiplicity = int(float(line.split()[1])) - logging.debug( - 'Conformer {0} is assigned a spin multiplicity of {1}'.format(label, spin_multiplicity)) + logging.debug("Conformer {0} is assigned a spin multiplicity of {1}".format(label, spin_multiplicity)) # The rest of the data we want is in the Thermochemistry section of the output - elif 'VIBRATIONAL ANALYSIS' in line: + elif "VIBRATIONAL ANALYSIS" in line: modes = [] line = f.readline() - while line != '': - + while line != "": # This marks the end of the thermochemistry section - if 'Thank you very much for using Q-Chem.' in line: + if "Thank you very much for using Q-Chem." in line: break # Read vibrational modes - elif 'VIBRATIONAL FREQUENCIES (CM**-1)' in line: + elif "VIBRATIONAL FREQUENCIES (CM**-1)" in line: frequencies = [] - while 'STANDARD THERMODYNAMIC QUANTITIES AT' not in line: - if ' Frequency:' in line: + while "STANDARD THERMODYNAMIC QUANTITIES AT" not in line: + if " Frequency:" in line: if len(line.split()) == 4: frequencies.extend([float(d) for d in line.split()[-3:]]) elif len(line.split()) == 3: @@ -269,14 +264,14 @@ def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=Non # modes.append(vibration) freq.append(vibration) # Read molecular mass for external translational modes - elif 'Molecular Mass:' in line: + elif "Molecular Mass:" in line: mass = float(line.split()[2]) translation = IdealGasTranslation(mass=(mass, "amu")) # modes.append(translation) mmass.append(translation) # Read moments of inertia for external rotational modes, given in atomic units - elif 'Eigenvalues --' in line: + elif "Eigenvalues --" in line: inertia = [float(d) for d in line.split()[-3:]] # Read the next line in the file @@ -289,7 +284,7 @@ def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=Non if inertia[0] == 0.0: # If the first eigenvalue is 0, the rotor is linear inertia.remove(0.0) - logging.debug('inertia is {}'.format(str(inertia))) + logging.debug("inertia is {}".format(str(inertia))) for i in range(2): inertia[i] *= (constants.a0 / 1e-10) ** 2 inertia = np.sqrt(inertia[0] * inertia[1]) @@ -305,10 +300,12 @@ def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=Non inertia = [] # Take only the last modes found (in the event of multiple jobs) modes = mmass[-1:] + rot[-1:] + freq[-1:] - return Conformer(E0=(e0 * 0.001, "kJ/mol"), modes=modes, spin_multiplicity=spin_multiplicity, - optical_isomers=optical_isomers), unscaled_frequencies + return ( + Conformer(E0=(e0 * 0.001, "kJ/mol"), modes=modes, spin_multiplicity=spin_multiplicity, optical_isomers=optical_isomers), + unscaled_frequencies, + ) - def load_energy(self, zpe_scale_factor=1.): + def load_energy(self, zpe_scale_factor=1.0): """ Load the energy in J/mol from a QChem log file. Prioritize the energy from a converged geometry optimization. If the file does not contain an optimization job or if the optimization @@ -316,16 +313,16 @@ def load_energy(self, zpe_scale_factor=1.): The zero-point energy is *not* included in the returned value. """ e_elect = None - with open(self.path, 'r') as f: + with open(self.path, "r") as f: preferred_source = alternative_source = None for line in f: - if 'Final energy is' in line: + if "Final energy is" in line: preferred_source = float(line.split()[-1]) * constants.E_h * constants.Na - if 'Total energy in the final basis set' in line: + if "Total energy in the final basis set" in line: alternative_source = float(line.split()[-1]) * constants.E_h * constants.Na e_elect = preferred_source or alternative_source if e_elect is None: - raise LogError('Unable to find energy in QChem output file {0}.'.format(self.path)) + raise LogError("Unable to find energy in QChem output file {0}.".format(self.path)) return e_elect def load_zero_point_energy(self): @@ -333,15 +330,15 @@ def load_zero_point_energy(self): Load the unscaled zero-point energy in J/mol from a QChem output file. """ zpe = None - with open(self.path, 'r') as f: + with open(self.path, "r") as f: for line in f: - if 'Zero point vibrational energy' in line: + if "Zero point vibrational energy" in line: zpe = float(line.split()[4]) * 4184 # QChem's ZPE is in kcal/mol, convert to J/mol - logging.debug('ZPE is {}'.format(str(zpe))) + logging.debug("ZPE is {}".format(str(zpe))) if zpe is not None: return zpe else: - raise LogError('Unable to find zero-point energy in QChem output file {0}.'.format(self.path)) + raise LogError("Unable to find zero-point energy in QChem output file {0}.".format(self.path)) def load_scan_energies(self): """ @@ -351,20 +348,20 @@ def load_scan_energies(self): v_list = [] angle = [] read = False - with open(self.path, 'r') as f: + with open(self.path, "r") as f: for line in f: - if '-----------------' in line: + if "-----------------" in line: read = False if read: values = [float(item) for item in line.split()] angle.append(values[0]) v_list.append(values[1]) - if 'Summary of potential scan:' in line: - logging.info('found a successfully completed QChem Job') + if "Summary of potential scan:" in line: + logging.info("found a successfully completed QChem Job") read = True - logging.info(' Assuming {0} is the output from a QChem PES scan...'.format(os.path.basename(self.path))) + logging.info(" Assuming {0} is the output from a QChem PES scan...".format(os.path.basename(self.path))) - v_list = np.array(v_list, np.float64) + v_list = np.array(v_list, float) # check to see if the scanlog indicates that one of your reacting species may not be the lowest energy conformer check_conformer_energy(v_list, self.path) @@ -372,7 +369,7 @@ def load_scan_energies(self): # Also convert units from Hartree/particle to J/mol v_list -= np.min(v_list) v_list *= constants.E_h * constants.Na - angle = np.arange(0.0, 2 * math.pi + 0.00001, 2 * math.pi / (len(v_list) - 1), np.float64) + angle = np.arange(0.0, 2 * math.pi + 0.00001, 2 * math.pi / (len(v_list) - 1), float) return v_list, angle def load_negative_frequency(self): @@ -382,35 +379,35 @@ def load_negative_frequency(self): """ read_freqs = False num_freq_blocks = 0 - with open(self.path, 'r') as f: + with open(self.path, "r") as f: for line in f: # only read the first row from a frequency block - if read_freqs and ' Frequency:' in line: + if read_freqs and " Frequency:" in line: freqs = np.array([float(freq) for freq in line.split()[1:4]]) num_freq_blocks += 1 read_freqs = False - if 'VIBRATIONAL ANALYSIS' in line: + if "VIBRATIONAL ANALYSIS" in line: read_freqs = True - logging.info(f'Identified {num_freq_blocks} frequency block(s)...') - logging.info('Only examining frequencies from the last block...') + logging.info(f"Identified {num_freq_blocks} frequency block(s)...") + logging.info("Only examining frequencies from the last block...") neg_idx = np.where(freqs < 0)[0] if len(neg_idx) == 1: return freqs[neg_idx[0]] elif len(neg_idx) > 1: - logging.info('More than one imaginary frequency in QChem output file {0}.'.format(self.path)) + logging.info("More than one imaginary frequency in QChem output file {0}.".format(self.path)) return freqs[neg_idx[0]] else: - raise LogError('Unable to find imaginary frequency in QChem output file {0}.'.format(self.path)) + raise LogError("Unable to find imaginary frequency in QChem output file {0}.".format(self.path)) def load_scan_pivot_atoms(self): """Not implemented for QChem""" - raise NotImplementedError('The load_scan_pivot_atoms method is not implemented for QChem Logs') + raise NotImplementedError("The load_scan_pivot_atoms method is not implemented for QChem Logs") def load_scan_frozen_atoms(self): """Not implemented for QChem""" - raise NotImplementedError('The load_scan_frozen_atoms method is not implemented for QChem Logs') + raise NotImplementedError("The load_scan_frozen_atoms method is not implemented for QChem Logs") register_ess_adapter("QChemLog", QChemLog) diff --git a/arkane/ess/terachem.py b/arkane/ess/terachem.py index 7d0d1ddd34..b45b9ee538 100644 --- a/arkane/ess/terachem.py +++ b/arkane/ess/terachem.py @@ -62,21 +62,20 @@ def check_for_errors(self): Checks for common errors in a TeraChem log file. If any are found, this method will raise an error and crash. """ - with open(os.path.join(self.path), 'r') as f: + with open(os.path.join(self.path), "r") as f: lines = f.readlines() error = None for line in reversed(lines): # check for common error messages - if 'incorrect method' in line.lower(): - error = 'incorrect method' + if "incorrect method" in line.lower(): + error = "incorrect method" break - elif 'error: ' in line.lower(): + elif "error: " in line.lower(): # e.g.: "ERROR: Closed shell calculations can't have spin multiplicity 0." - error = 'multiplicity' + error = "multiplicity" break if error: - raise LogError(f'There was an error ({error}) with TeraChem output file {self.path} ' - f'due to line:\n{line}') + raise LogError(f"There was an error ({error}) with TeraChem output file {self.path} " f"due to line:\n{line}") def get_number_of_atoms(self): """ @@ -84,19 +83,18 @@ def get_number_of_atoms(self): Accepted output files: TeraChem's log file, xyz format file, TeraChem's output.geometry file. """ n_atoms = 0 - with open(self.path, 'r') as f: + with open(self.path, "r") as f: file_extension = os.path.splitext(self.path)[1] - if file_extension == '.xyz': + if file_extension == ".xyz": n_atoms = int(f.readline()) else: line = f.readline() while line and n_atoms == 0: - if 'Total atoms:' in line: + if "Total atoms:" in line: n_atoms = int(line.split()[-1]) - elif '****** QM coordinates ******' in line \ - or 'Type X Y Z Mass' in line: + elif "****** QM coordinates ******" in line or "Type X Y Z Mass" in line: line = f.readline() - while line != '\n': + while line != "\n": n_atoms += 1 line = f.readline() line = f.readline() @@ -113,12 +111,12 @@ def load_force_constant_matrix(self): n_atoms = self.get_number_of_atoms() n_rows = n_atoms * 3 - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": # Read force constant matrix - if '*** Hessian Matrix (Hartree/Bohr^2) ***' in line: - force = np.zeros((n_rows, n_rows), np.float64) + if "*** Hessian Matrix (Hartree/Bohr^2) ***" in line: + force = np.zeros((n_rows, n_rows), float) for i in range(int(math.ceil(n_rows / 6.0))): # Matrix element rows for j in range(n_rows): @@ -130,7 +128,7 @@ def load_force_constant_matrix(self): for k in range(len(data) - 1): force[j, i * 6 + k] = float(data[k + 1]) # Convert from atomic units (Hartree/Bohr^2) to SI (J/m^2) - force *= 4.35974417e-18 / 5.291772108e-11 ** 2 + force *= 4.35974417e-18 / 5.291772108e-11**2 line = f.readline() return force @@ -147,7 +145,7 @@ def load_geometry(self): lines = f.readlines() num_of_atoms = None # used to verify the result - if os.path.splitext(self.path)[1] == '.xyz': + if os.path.splitext(self.path)[1] == ".xyz": skip_line = False for line in lines: if not skip_line and line.rstrip(): @@ -165,7 +163,7 @@ def load_geometry(self): coords, numbers, masses = list(), list(), list() else: for i, line in enumerate(lines): - if 'Type X Y Z Mass' in line: + if "Type X Y Z Mass" in line: # this is an output.geometry file j = i + 1 while lines[j].strip(): @@ -177,7 +175,7 @@ def load_geometry(self): numbers.append(list(symbol_by_number.keys())[list(symbol_by_number.values()).index(splits[0])]) j += 1 break - if '*** Reference Geometry ***' in line: + if "*** Reference Geometry ***" in line: # this is an output.out file, e.g., from a freq run j = i + 2 while lines[j].strip(): @@ -190,20 +188,25 @@ def load_geometry(self): j += 1 break - coords = np.array(coords, np.float64) - numbers = np.array(numbers, np.int) - masses = np.array(masses, np.float64) - if len(coords) == 0 or len(numbers) == 0 or len(masses) == 0 \ - or ((len(coords) != num_of_atoms or len(numbers) != num_of_atoms or len(masses) != num_of_atoms) - and num_of_atoms is not None): - raise LogError(f'Unable to read atoms from TeraChem geometry output file {self.path}. ' - f'If this is a TeraChem optimization log file, try using either the ' - f'frequencies calculation log file (important if torsion modes exist) or ' - f'the "output.geometry" or a ".xyz" file instead.') + coords = np.array(coords, float) + numbers = np.array(numbers, int) + masses = np.array(masses, float) + if ( + len(coords) == 0 + or len(numbers) == 0 + or len(masses) == 0 + or ((len(coords) != num_of_atoms or len(numbers) != num_of_atoms or len(masses) != num_of_atoms) and num_of_atoms is not None) + ): + raise LogError( + f"Unable to read atoms from TeraChem geometry output file {self.path}. " + f"If this is a TeraChem optimization log file, try using either the " + f"frequencies calculation log file (important if torsion modes exist) or " + f'the "output.geometry" or a ".xyz" file instead.' + ) return coords, numbers, masses - def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=''): + def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=None, label=""): """ Load the molecular degree of freedom data from an output file created as the result of a TeraChem "Freq" calculation. As TeraChem's guess of the external symmetry number might not always correct, @@ -217,37 +220,36 @@ def load_conformer(self, symmetry=None, spin_multiplicity=0, optical_isomers=Non if optical_isomers is None: optical_isomers = _optical_isomers - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": # Read spin multiplicity if not explicitly given - if 'Spin multiplicity' in line and spin_multiplicity == 0 and len(line.split()) == 3: + if "Spin multiplicity" in line and spin_multiplicity == 0 and len(line.split()) == 3: spin_multiplicity = int(float(line.split()[-1])) - logging.debug(f'Conformer {label} is assigned a spin multiplicity of {spin_multiplicity}') + logging.debug(f"Conformer {label} is assigned a spin multiplicity of {spin_multiplicity}") # Read vibrational modes - elif 'Mode Eigenvalue(AU) Frequency(cm-1)' in line: + elif "Mode Eigenvalue(AU) Frequency(cm-1)" in line: line = f.readline() - while line != '\n': + while line != "\n": # example: # 'Mode Eigenvalue(AU) Frequency(cm-1) Intensity(km/mol) Vib.Temp(K) ZPE(AU) ...' # ' 1 0.0331810528 170.5666870932 52.2294230772 245.3982965841 0.0003885795 ...' - if 'i' not in line.split()[2]: + if "i" not in line.split()[2]: # only consider non-imaginary frequencies in this function unscaled_freqs.append(float(line.split()[2])) line = f.readline() - if 'Vibrational Frequencies/Thermochemical Analysis' in line: + if "Vibrational Frequencies/Thermochemical Analysis" in line: converged = True line = f.readline() if not len(unscaled_freqs): - raise LogError(f'Could not read frequencies from TeraChem log file {self.path}') + raise LogError(f"Could not read frequencies from TeraChem log file {self.path}") if not converged: - raise LogError(f'TeraChem job {self.path} did not converge.') + raise LogError(f"TeraChem job {self.path} did not converge.") modes.append(HarmonicOscillator(frequencies=(unscaled_freqs, "cm^-1"))) - return Conformer(E0=(0.0, "kJ/mol"), modes=modes, spin_multiplicity=spin_multiplicity, - optical_isomers=optical_isomers), unscaled_freqs + return Conformer(E0=(0.0, "kJ/mol"), modes=modes, spin_multiplicity=spin_multiplicity, optical_isomers=optical_isomers), unscaled_freqs - def load_energy(self, zpe_scale_factor=1.): + def load_energy(self, zpe_scale_factor=1.0): """ Load the energy in J/mol from a TeraChem log file. Only the last energy in the file is returned, unless the log file represents a frequencies calculation, @@ -255,22 +257,22 @@ def load_energy(self, zpe_scale_factor=1.): in the returned value. """ e_elect, return_first = None, False - with open(self.path, 'r') as f: + with open(self.path, "r") as f: lines = f.readlines() for i, line in enumerate(lines): - if 'FREQUENCY ANALYSIS' in line: + if "FREQUENCY ANALYSIS" in line: return_first = True - if 'Ground state energy (a.u.):' in line: + if "Ground state energy (a.u.):" in line: e_elect = float(lines[i + 1].strip()) if return_first: break - if 'FINAL ENERGY:' in line: + if "FINAL ENERGY:" in line: # example: 'FINAL ENERGY: -114.5008455547 a.u.' e_elect = float(line.split()[2]) if return_first: break if e_elect is None: - raise LogError(f'Unable to find energy in TeraChem output file {self.path}.') + raise LogError(f"Unable to find energy in TeraChem output file {self.path}.") return e_elect * constants.E_h * constants.Na def load_zero_point_energy(self): @@ -278,33 +280,33 @@ def load_zero_point_energy(self): Load the unscaled zero-point energy in J/mol from a TeraChem log file. """ zpe = None - with open(self.path, 'r') as f: + with open(self.path, "r") as f: for line in f: - if 'Vibrational zero-point energy (ZPE)' in line: + if "Vibrational zero-point energy (ZPE)" in line: # example: # 'Vibrational zero-point energy (ZPE) = 243113.467652369843563065 J/mol = 0.09259703 AU' - zpe = float(line.split('J/mol')[0].split()[-1]) - logging.debug(f'ZPE is {zpe}') + zpe = float(line.split("J/mol")[0].split()[-1]) + logging.debug(f"ZPE is {zpe}") if zpe is not None: return zpe else: - raise LogError(f'Unable to find zero-point energy in TeraChem output file {self.path}.') + raise LogError(f"Unable to find zero-point energy in TeraChem output file {self.path}.") def load_scan_energies(self): """ Extract the optimized energies in J/mol from a TeraChem torsional scan log file. """ v_list = list() - with open(self.path, 'r') as f: + with open(self.path, "r") as f: lines = f.readlines() v_index, expected_num_of_points = 0, 0 for line in lines: - if 'Scan Cycle' in line: + if "Scan Cycle" in line: # example: '-=#=- Scan Cycle 5/37 -=#=-' v_index += 1 if not expected_num_of_points: - expected_num_of_points = int(line.split()[3].split('/')[1]) - if 'Optimized Energy:' in line: + expected_num_of_points = int(line.split()[3].split("/")[1]) + if "Optimized Energy:" in line: # example: '-=#=- Optimized Energy: -155.0315243910 a.u.' v = float(line.split()[3]) if len(v_list) == v_index - 1: @@ -316,10 +318,10 @@ def load_scan_energies(self): v_list.extend([None] * (v_index - 1 - len(v_list))) else: # we added more points that we should have, something is wrong with the log file or this method - raise LogError(f'Could not parse scan energies from {self.path}') - logging.info(' Assuming {0} is the output from a TeraChem PES scan...'.format(os.path.basename(self.path))) + raise LogError(f"Could not parse scan energies from {self.path}") + logging.info(" Assuming {0} is the output from a TeraChem PES scan...".format(os.path.basename(self.path))) - v_list = np.array(v_list, np.float64) + v_list = np.array(v_list, float) # check to see if the scanlog indicates that one of the reacting species may not be the lowest energy conformer check_conformer_energy(v_list, self.path) @@ -328,7 +330,7 @@ def load_scan_energies(self): # Also convert units from Hartree/particle to J/mol v_list -= np.min(v_list) v_list *= constants.E_h * constants.Na - angles = np.arange(0.0, 2 * math.pi + 0.00001, 2 * math.pi / (len(v_list) - 1), np.float64) + angles = np.arange(0.0, 2 * math.pi + 0.00001, 2 * math.pi / (len(v_list) - 1), float) # remove None's: indices_to_pop = [v_list.index[entry] for entry in v_list if entry is None] @@ -336,8 +338,7 @@ def load_scan_energies(self): v_list.pop(i) angles.pop(i) if v_index != expected_num_of_points: - raise LogError(f'Expected to find {expected_num_of_points} scan points in TeraChem scan log file ' - f'{self.path}, but found: {v_index}') + raise LogError(f"Expected to find {expected_num_of_points} scan points in TeraChem scan log file " f"{self.path}, but found: {v_index}") return v_list, angles @@ -347,16 +348,16 @@ def load_negative_frequency(self): calculation in cm^-1. """ frequencies = [] - with open(self.path, 'r') as f: + with open(self.path, "r") as f: line = f.readline() - while line != '': + while line != "": # Read vibrational modes - if 'Mode Eigenvalue(AU) Frequency(cm-1)' in line: + if "Mode Eigenvalue(AU) Frequency(cm-1)" in line: line = f.readline() # example: # 'Mode Eigenvalue(AU) Frequency(cm-1) Intensity(km/mol) Vib.Temp(K) ZPE(AU) ...' # ' 1 0.0331810528 170.5666870932i 52.2294230772 245.3982965841 0.0003885795 ...' - while 'i' in line: + while "i" in line: frequencies.append(-1 * float(line.split()[2][:-1])) # remove 'i' line = f.readline() break @@ -364,18 +365,18 @@ def load_negative_frequency(self): if len(frequencies) == 1: return frequencies[0] elif len(frequencies) > 1: - logging.info('More than one imaginary frequency in TeraChem output file {0}.'.format(self.path)) + logging.info("More than one imaginary frequency in TeraChem output file {0}.".format(self.path)) return frequencies[0] else: - raise LogError(f'Unable to find imaginary frequency in TeraChem output file {self.path}.') + raise LogError(f"Unable to find imaginary frequency in TeraChem output file {self.path}.") def load_scan_pivot_atoms(self): """Not implemented for TeraChem""" - raise NotImplementedError('The load_scan_pivot_atoms method is not implemented for TeraChem Logs') + raise NotImplementedError("The load_scan_pivot_atoms method is not implemented for TeraChem Logs") def load_scan_frozen_atoms(self): """Not implemented for TeraChem""" - raise NotImplementedError('The load_scan_frozen_atoms method is not implemented for TeraChem Logs') + raise NotImplementedError("The load_scan_frozen_atoms method is not implemented for TeraChem Logs") register_ess_adapter("TeraChemLog", TeraChemLog) diff --git a/arkane/ess/terachemTest.py b/arkane/ess/terachemTest.py deleted file mode 100644 index 198e441317..0000000000 --- a/arkane/ess/terachemTest.py +++ /dev/null @@ -1,418 +0,0 @@ -#!/usr/bin/env python3 - -############################################################################### -# # -# RMG - Reaction Mechanism Generator # -# # -# Copyright (c) 2002-2021 Prof. William H. Green (whgreen@mit.edu), # -# Prof. Richard H. West (r.west@neu.edu) and the RMG Team (rmg_dev@mit.edu) # -# # -# Permission is hereby granted, free of charge, to any person obtaining a # -# copy of this software and associated documentation files (the 'Software'), # -# to deal in the Software without restriction, including without limitation # -# the rights to use, copy, modify, merge, publish, distribute, sublicense, # -# and/or sell copies of the Software, and to permit persons to whom the # -# Software is furnished to do so, subject to the following conditions: # -# # -# The above copyright notice and this permission notice shall be included in # -# all copies or substantial portions of the Software. # -# # -# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # -# DEALINGS IN THE SOFTWARE. # -# # -############################################################################### - -""" -This module contains unit tests of the :mod:`arkane.ess.terachem` module. -""" - -import os -import unittest - -import numpy as np - -from rmgpy.statmech.conformer import Conformer - -from arkane.exceptions import LogError -from arkane.ess.terachem import TeraChemLog - -################################################################################ - - -class TeraChemLogTest(unittest.TestCase): - """ - Contains unit tests for the terachem module, used for parsing TeraChem files. - """ - @classmethod - def setUpClass(cls): - """ - A method that is run before all unit tests in this class. - """ - cls.data_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'terachem') - - def test_get_number_of_atoms(self): - """Uses various TeraChem log files to test that number of atoms can be properly read.""" - log_file = TeraChemLog(os.path.join(self.data_path, 'ethane_minimize_output.out')) - self.assertEqual(log_file.get_number_of_atoms(), 6) - log_file = TeraChemLog(os.path.join(self.data_path, 'ethane_coords.xyz')) - self.assertEqual(log_file.get_number_of_atoms(), 6) - log_file = TeraChemLog(os.path.join(self.data_path, 'formaldehyde_coords.xyz')) - self.assertEqual(log_file.get_number_of_atoms(), 4) - log_file = TeraChemLog(os.path.join(self.data_path, 'ethane_output.geometry')) - self.assertEqual(log_file.get_number_of_atoms(), 6) - log_file = TeraChemLog(os.path.join(self.data_path, 'formaldehyde_output.geometry')) - self.assertEqual(log_file.get_number_of_atoms(), 4) - - def test_load_force_constant_matrix(self): - """Test loading the Hessian""" - log_file = TeraChemLog(os.path.join(self.data_path, 'ethanol_freq_output.out')) - hessian = log_file.load_force_constant_matrix() - self.assertEqual(len(hessian), 27) # 9 atoms * 3 dof = 27 - self.assertEqual(len(hessian[0]), 27) - - log_file = TeraChemLog(os.path.join(self.data_path, 'ethylamine_freq_output.out')) - hessian = log_file.load_force_constant_matrix() - expected_hessian = [ - [914.9859609920952, -86.71893487707963, 25.53304366219221, -229.64170366910673, 9.65273601863364, - 122.37178242977485, -2.4910286499699716, 0.6227571624924929, 14.323414737327337, -42.347487049489516, - -13.389278993588597, 43.90437995572075, -0.7784464531156161, -3.7365429749549572, 1.8682714874774786, - -0.6227571624924929, -0.15568929062312323, -1.8682714874774786, 3.1137858124624644, -0.46706787186936966, - -0.15568929062312323, 2.802407231216218, 0.6227571624924929, -0.31137858124624646, -99.64114599879886, - 27.40131514966969, 10.742561052995502, -545.6909636340469, 67.10208425856611, -216.40811396614131], - [-86.71893487707963, 853.1773126147153, -244.12080769705722, -20.395297071629145, -150.7072333231833, - 73.7967237553604, 16.035996934181693, 13.389278993588597, -12.61083254047298, 9.808425309256764, - 4.982057299939943, -11.521007506111118, -41.5690405963739, -9.497046728010517, 43.74869066509763, - 0.7784464531156161, 3.1137858124624644, 0.6227571624924929, -0.6227571624924929, -0.6227571624924929, - 1.0898250343618625, -0.0, -0.46706787186936966, -6.227571624924929, 2.9580965218393414, -620.7332017143923, - 60.09606618052557, 119.72506448918175, -94.5033994082358, 95.28184586135141], - [25.53304366219221, -244.12080769705722, 432.81622793228263, 54.95831958996249, 23.820461465337853, - -210.49192092246258, 48.88643725566069, 14.167725446704214, -49.04212654628382, -1.5568929062312322, - -2.9580965218393414, 11.676696796734241, 16.81444338729731, 9.808425309256764, -15.724618352935446, - -0.6227571624924929, 0.9341357437387393, 4.82636800931682, 0.31137858124624646, 2.179650068723725, - 4.982057299939943, -0.7784464531156161, -6.850328787417422, -11.988075377980488, 11.365318215487996, - 143.23414737327337, -40.94628343388141, -155.06653346063072, 59.784687599279316, -126.88677185784543], - [-229.64170366910673, -20.395297071629145, 54.95831958996249, 880.2672491831387, -45.92834073382135, - 71.77276297725982, -131.55745057653914, -13.54496828421172, -26.311490115307823, -390.1573623015468, - 81.1141204146472, -143.3898366638965, -84.38359551773279, 16.50306480605106, -4.35930013744745, - -0.6227571624924929, 0.6227571624924929, 3.7365429749549572, -7.4730859499099145, -26.62286869655407, - -44.838515699459485, 4.35930013744745, 20.550986362252267, 34.563022518333355, 2.3353393593468486, - 0.6227571624924929, 4.514989428070574, -42.970244211982006, -12.14376466860361, 49.04212654628382], - [9.65273601863364, -150.7072333231833, 23.820461465337853, -45.92834073382135, 917.1656110608188, - -66.63501638669673, -16.191686224804815, -175.15045195101362, -61.80864837737992, 74.57517020847602, - -101.50941748627633, 38.455254783911435, 25.377354371569083, -464.4211539287766, 50.59901945251505, - -8.874289565518025, -26.1558008246847, -47.95230151192195, 0.46706787186936966, 6.383260915548052, - 10.275493181126134, 3.7365429749549572, 7.7844645311561615, 17.4372005497898, -48.73074796503757, - -17.125821968543555, 47.64092293067571, 5.449125171809313, 4.047921556201204, -11.521007506111118], - [122.37178242977485, 73.7967237553604, -210.49192092246258, 71.77276297725982, -66.63501638669673, - 700.1347399321851, -33.00612961210212, -67.41346283981235, -223.8811999160512, -132.95865419214724, - 37.209740458926454, -132.80296490152412, -7.317396659286792, 58.383483983671205, -85.1620419708484, - 2.179650068723725, 3.2694751030855875, 6.071882334301805, -2.802407231216218, -11.209628924864871, - -20.706675652875386, -3.8922322655780808, -11.521007506111118, -23.353393593468482, 7.628775240533038, - -6.850328787417422, -19.616850618513528, -22.73063643097599, -9.65273601863364, 9.808425309256764], - [-2.4910286499699716, 16.035996934181693, 48.88643725566069, -131.55745057653914, -16.191686224804815, - -33.00612961210212, 886.3391315174406, -7.4730859499099145, -21.796500687237252, -4.35930013744745, - -24.910286499699716, -42.03610846824327, 1.7125821968543555, 2.6467179405930947, 2.3353393593468486, - -78.62309176467723, 21.329432815367884, -2.179650068723725, -394.04959456712487, 80.49136325215471, - -137.00657574834844, -278.68383021539057, -74.57517020847602, 184.64749867902415, 0.15568929062312323, - 1.8682714874774786, -2.3353393593468486, 2.6467179405930947, 1.0898250343618625, 2.4910286499699716], - [0.6227571624924929, 13.389278993588597, 14.167725446704214, -13.54496828421172, -175.15045195101362, - -67.41346283981235, -7.4730859499099145, 850.219216092876, -70.37155936165169, 1.2455143249849858, - 5.916193043678683, 9.341357437387394, -8.7186002748949, -25.065975790322838, -46.86247647756009, - 22.1078792684835, -467.22356115999276, 54.17987313684688, 78.77878105530036, -95.9046030238439, - 38.76663336515768, -74.41948091785291, -107.42561052995504, 66.79070567731986, 0.46706787186936966, - 3.2694751030855875, 2.802407231216218, 0.6227571624924929, -1.2455143249849858, -0.9341357437387393], - [14.323414737327337, -12.61083254047298, -49.04212654628382, -26.311490115307823, -61.80864837737992, - -223.8811999160512, -21.796500687237252, -70.37155936165169, 750.2666915128308, -6.538950206171175, - -13.389278993588597, -20.083918490382896, -0.9341357437387393, 0.46706787186936966, 5.916193043678683, - -3.1137858124624644, 55.26969817120874, -86.09617771458714, -138.40777936395656, 37.98818691204207, - -132.33589702965475, 185.27025584151662, 66.32363780545049, -249.72562215948963, -0.46706787186936966, - -1.7125821968543555, 1.8682714874774786, -1.8682714874774786, 1.2455143249849858, 3.580853684331834], - [-42.347487049489516, 9.808425309256764, -1.5568929062312322, -390.1573623015468, 74.57517020847602, - -132.95865419214724, -4.35930013744745, 1.2455143249849858, -6.538950206171175, 437.17552806973, - -82.82670261150155, 139.34191510769529, 0.31137858124624646, 2.802407231216218, -1.0898250343618625, - 0.15568929062312323, -0.46706787186936966, 0.46706787186936966, 1.8682714874774786, -0.6227571624924929, - -0.7784464531156161, 0.46706787186936966, -0.15568929062312323, -0.9341357437387393, 0.15568929062312323, - -0.46706787186936966, -1.2455143249849858, -3.580853684331834, -4.514989428070574, 5.2934358811861895], - [-13.389278993588597, 4.982057299939943, -2.9580965218393414, 81.1141204146472, -101.50941748627633, - 37.209740458926454, -24.910286499699716, 5.916193043678683, -13.389278993588597, -82.82670261150155, - 97.46149593007515, -36.58698329643396, 46.239719315067596, -9.18566814676427, 18.83840416539791, - -0.46706787186936966, 1.401203615608109, 1.5568929062312322, -1.5568929062312322, -1.2455143249849858, - -5.449125171809313, 0.6227571624924929, 0.7784464531156161, 0.7784464531156161, -0.46706787186936966, - 0.6227571624924929, -1.401203615608109, -4.047921556201204, 0.9341357437387393, 1.0898250343618625], - [43.90437995572075, -11.521007506111118, 11.676696796734241, -143.3898366638965, 38.455254783911435, - -132.80296490152412, -42.03610846824327, 9.341357437387394, -20.083918490382896, 139.34191510769529, - -36.58698329643396, 150.39585474193703, -7.006018078040545, 1.7125821968543555, -2.6467179405930947, - 0.9341357437387393, 0.15568929062312323, 2.3353393593468486, -0.46706787186936966, -5.760503753055559, - -7.4730859499099145, 0.46706787186936966, 0.7784464531156161, 1.5568929062312322, -0.6227571624924929, - -0.0, 1.2455143249849858, 8.25153240302553, 3.2694751030855875, -4.047921556201204], - [-0.7784464531156161, -41.5690405963739, 16.81444338729731, -84.38359551773279, 25.377354371569083, - -7.317396659286792, 1.7125821968543555, -8.7186002748949, -0.9341357437387393, 0.31137858124624646, - 46.239719315067596, -7.006018078040545, 85.47342055209465, -14.323414737327337, -4.982057299939943, - 1.401203615608109, -1.5568929062312322, -0.9341357437387393, 0.31137858124624646, -0.46706787186936966, - 1.2455143249849858, 0.31137858124624646, 0.31137858124624646, -0.31137858124624646, -5.760503753055559, - -4.35930013744745, 5.2934358811861895, 1.2455143249849858, -1.0898250343618625, -1.8682714874774786], - [-3.7365429749549572, -9.497046728010517, 9.808425309256764, 16.50306480605106, -464.4211539287766, - 58.383483983671205, 2.6467179405930947, -25.065975790322838, 0.46706787186936966, 2.802407231216218, - -9.18566814676427, 1.7125821968543555, -14.323414737327337, 503.8105444564267, -65.54519135233488, - -0.7784464531156161, -0.15568929062312323, -5.137746590563067, -0.6227571624924929, 1.2455143249849858, - 0.46706787186936966, 0.31137858124624646, 0.7784464531156161, -0.15568929062312323, -2.4910286499699716, - 2.4910286499699716, 0.31137858124624646, -0.15568929062312323, 0.15568929062312323, -0.6227571624924929], - [1.8682714874774786, 43.74869066509763, -15.724618352935446, -4.35930013744745, 50.59901945251505, - -85.1620419708484, 2.3353393593468486, -46.86247647756009, 5.916193043678683, -1.0898250343618625, - 18.83840416539791, -2.6467179405930947, -4.982057299939943, -65.54519135233488, 103.84475684562318, - -1.5568929062312322, -5.2934358811861895, -8.25153240302553, 0.15568929062312323, 1.401203615608109, - 2.023960778100602, 0.15568929062312323, 1.5568929062312322, 2.3353393593468486, 7.7844645311561615, - 2.179650068723725, -3.580853684331834, -0.31137858124624646, -0.6227571624924929, 1.0898250343618625], - [-0.6227571624924929, 0.7784464531156161, -0.6227571624924929, -0.6227571624924929, -8.874289565518025, - 2.179650068723725, -78.62309176467723, 22.1078792684835, -3.1137858124624644, 0.15568929062312323, - -0.46706787186936966, 0.9341357437387393, 1.401203615608109, -0.7784464531156161, -1.5568929062312322, - 75.50930595221476, -22.73063643097599, 4.514989428070574, -0.9341357437387393, 47.018165768183216, - -6.071882334301805, 2.802407231216218, -37.67680833079582, 3.425164393708711, 0.6227571624924929, - 0.15568929062312323, 0.6227571624924929, 0.0, 0.31137858124624646, 0.0], - [-0.15568929062312323, 3.1137858124624644, 0.9341357437387393, 0.6227571624924929, -26.1558008246847, - 3.2694751030855875, 21.329432815367884, -467.22356115999276, 55.26969817120874, -0.46706787186936966, - 1.401203615608109, 0.15568929062312323, -1.5568929062312322, -0.15568929062312323, -5.2934358811861895, - -22.73063643097599, 508.4812231751205, -57.760726821178714, 1.2455143249849858, -9.18566814676427, - 2.023960778100602, 0.31137858124624646, -11.05393963424175, 2.179650068723725, 1.0898250343618625, - 0.6227571624924929, -1.0898250343618625, 0.31137858124624646, -0.0, 0.15568929062312323], - [-1.8682714874774786, 0.6227571624924929, 4.82636800931682, 3.7365429749549572, -47.95230151192195, - 6.071882334301805, -2.179650068723725, 54.17987313684688, -86.09617771458714, 0.46706787186936966, - 1.5568929062312322, 2.3353393593468486, -0.9341357437387393, -5.137746590563067, -8.25153240302553, - 4.514989428070574, -57.760726821178714, 89.8327206895421, 0.46706787186936966, 19.77253990913665, - -3.7365429749549572, -4.670678718693697, 33.31750819334837, -4.514989428070574, 0.31137858124624646, - 1.401203615608109, -0.31137858124624646, 0.31137858124624646, -0.15568929062312323, -0.15568929062312323], - [3.1137858124624644, -0.6227571624924929, 0.31137858124624646, -7.4730859499099145, 0.46706787186936966, - -2.802407231216218, -394.04959456712487, 78.77878105530036, -138.40777936395656, 1.8682714874774786, - -1.5568929062312322, -0.46706787186936966, 0.31137858124624646, -0.6227571624924929, 0.15568929062312323, - -0.9341357437387393, 1.2455143249849858, 0.46706787186936966, 426.4329670167345, -87.18600274894901, - 155.37791204187698, -30.670790252755275, 8.7186002748949, -14.167725446704214, 0.15568929062312323, - 0.31137858124624646, 0.15568929062312323, 0.7784464531156161, 0.31137858124624646, -0.6227571624924929], - [-0.46706787186936966, -0.6227571624924929, 2.179650068723725, -26.62286869655407, 6.383260915548052, - -11.209628924864871, 80.49136325215471, -95.9046030238439, 37.98818691204207, -0.6227571624924929, - -1.2455143249849858, -5.760503753055559, -0.46706787186936966, 1.2455143249849858, 1.401203615608109, - 47.018165768183216, -9.18566814676427, 19.77253990913665, -87.18600274894901, 96.06029231446702, - -37.67680833079582, -13.07790041234235, 2.179650068723725, -7.161707368663668, 0.31137858124624646, - 0.15568929062312323, -0.15568929062312323, 0.9341357437387393, 0.6227571624924929, 0.46706787186936966], - [-0.15568929062312323, 1.0898250343618625, 4.982057299939943, -44.838515699459485, 10.275493181126134, - -20.706675652875386, -137.00657574834844, 38.76663336515768, -132.33589702965475, -0.7784464531156161, - -5.449125171809313, -7.4730859499099145, 1.2455143249849858, 0.46706787186936966, 2.023960778100602, - -6.071882334301805, 2.023960778100602, -3.7365429749549572, 155.37791204187698, -37.67680833079582, - 143.23414737327337, 30.048033090262784, -9.497046728010517, 15.724618352935446, -0.15568929062312323, - 0.31137858124624646, -0.31137858124624646, 1.8682714874774786, -0.46706787186936966, -1.7125821968543555], - [2.802407231216218, -0.0, -0.7784464531156161, 4.35930013744745, 3.7365429749549572, -3.8922322655780808, - -278.68383021539057, -74.41948091785291, 185.27025584151662, 0.46706787186936966, 0.6227571624924929, - 0.46706787186936966, 0.31137858124624646, 0.31137858124624646, 0.15568929062312323, 2.802407231216218, - 0.31137858124624646, -4.670678718693697, -30.670790252755275, -13.07790041234235, 30.048033090262784, - 297.67792367141163, 82.98239190212468, -205.50986362252266, 0.0, -0.15568929062312323, - -0.15568929062312323, 0.7784464531156161, -0.6227571624924929, -0.9341357437387393], - [0.6227571624924929, -0.46706787186936966, -6.850328787417422, 20.550986362252267, 7.7844645311561615, - -11.521007506111118, -74.57517020847602, -107.42561052995504, 66.32363780545049, -0.15568929062312323, - 0.7784464531156161, 0.7784464531156161, 0.31137858124624646, 0.7784464531156161, 1.5568929062312322, - -37.67680833079582, -11.05393963424175, 33.31750819334837, 8.7186002748949, 2.179650068723725, - -9.497046728010517, 82.98239190212468, 107.58129982057814, -73.32965588349104, 0.0, -0.15568929062312323, - -1.0898250343618625, -0.7784464531156161, 0.0, 0.31137858124624646], - [-0.31137858124624646, -6.227571624924929, -11.988075377980488, 34.563022518333355, 17.4372005497898, - -23.353393593468482, 184.64749867902415, 66.79070567731986, -249.72562215948963, -0.9341357437387393, - 0.7784464531156161, 1.5568929062312322, -0.31137858124624646, -0.15568929062312323, 2.3353393593468486, - 3.425164393708711, 2.179650068723725, -4.514989428070574, -14.167725446704214, -7.161707368663668, - 15.724618352935446, -205.50986362252266, -73.32965588349104, 268.40833703426443, -0.6227571624924929, - -0.6227571624924929, 1.2455143249849858, -0.7784464531156161, 0.0, 0.31137858124624646], - [-99.64114599879886, 2.9580965218393414, 11.365318215487996, 2.3353393593468486, -48.73074796503757, - 7.628775240533038, 0.15568929062312323, 0.46706787186936966, -0.46706787186936966, 0.15568929062312323, - -0.46706787186936966, -0.6227571624924929, -5.760503753055559, -2.4910286499699716, 7.7844645311561615, - 0.6227571624924929, 1.0898250343618625, 0.31137858124624646, 0.15568929062312323, 0.31137858124624646, - -0.15568929062312323, 0.0, 0.0, -0.6227571624924929, 103.06631039250756, -18.83840416539791, - -13.389278993588597, -0.9341357437387393, 65.38950206171175, -11.676696796734241], - [27.40131514966969, -620.7332017143923, 143.23414737327337, 0.6227571624924929, -17.125821968543555, - -6.850328787417422, 1.8682714874774786, 3.2694751030855875, -1.7125821968543555, -0.46706787186936966, - 0.6227571624924929, -0.0, -4.35930013744745, 2.4910286499699716, 2.179650068723725, 0.15568929062312323, - 0.6227571624924929, 1.401203615608109, 0.31137858124624646, 0.15568929062312323, 0.31137858124624646, - -0.15568929062312323, -0.15568929062312323, -0.6227571624924929, -18.83840416539791, 647.5117597015695, - -131.86882915778537, -6.383260915548052, -14.946171899819829, -6.227571624924929], - [10.742561052995502, 60.09606618052557, -40.94628343388141, 4.514989428070574, 47.64092293067571, - -19.616850618513528, -2.3353393593468486, 2.802407231216218, 1.8682714874774786, -1.2455143249849858, - -1.401203615608109, 1.2455143249849858, 5.2934358811861895, 0.31137858124624646, -3.580853684331834, - 0.6227571624924929, -1.0898250343618625, -0.31137858124624646, 0.15568929062312323, -0.15568929062312323, - -0.31137858124624646, -0.15568929062312323, -1.0898250343618625, 1.2455143249849858, -13.389278993588597, - -131.86882915778537, 68.03622000230486, -4.047921556201204, 24.754597209076593, -7.628775240533038], - [-545.6909636340469, 119.72506448918175, -155.06653346063072, -42.970244211982006, 5.449125171809313, - -22.73063643097599, 2.6467179405930947, 0.6227571624924929, -1.8682714874774786, -3.580853684331834, - -4.047921556201204, 8.25153240302553, 1.2455143249849858, -0.15568929062312323, -0.31137858124624646, 0.0, - 0.31137858124624646, 0.31137858124624646, 0.7784464531156161, 0.9341357437387393, 1.8682714874774786, - 0.7784464531156161, -0.7784464531156161, -0.7784464531156161, -0.9341357437387393, -6.383260915548052, - -4.047921556201204, 587.8827613929133, -116.14421080484992, 174.68338407914425], - [67.10208425856611, -94.5033994082358, 59.784687599279316, -12.14376466860361, 4.047921556201204, - -9.65273601863364, 1.0898250343618625, -1.2455143249849858, 1.2455143249849858, -4.514989428070574, - 0.9341357437387393, 3.2694751030855875, -1.0898250343618625, 0.15568929062312323, -0.6227571624924929, - 0.31137858124624646, -0.0, -0.15568929062312323, 0.31137858124624646, 0.6227571624924929, - -0.46706787186936966, -0.6227571624924929, 0.0, 0.0, 65.38950206171175, -14.946171899819829, - 24.754597209076593, -116.14421080484992, 105.09027117060819, -78.15602389280787], - [-216.40811396614131, 95.28184586135141, -126.88677185784543, 49.04212654628382, -11.521007506111118, - 9.808425309256764, 2.4910286499699716, -0.9341357437387393, 3.580853684331834, 5.2934358811861895, - 1.0898250343618625, -4.047921556201204, -1.8682714874774786, -0.6227571624924929, 1.0898250343618625, 0.0, - 0.15568929062312323, -0.15568929062312323, -0.6227571624924929, 0.46706787186936966, -1.7125821968543555, - -0.9341357437387393, 0.31137858124624646, 0.31137858124624646, -11.676696796734241, -6.227571624924929, - -7.628775240533038, 174.68338407914425, -78.15602389280787, 125.95263611410668]] - np.testing.assert_almost_equal(hessian, np.array(expected_hessian, np.float64)) - - def test_load_geometry(self): - """Test loading the geometry from a TeraChem xyz output file""" - log_file = TeraChemLog(os.path.join(self.data_path, 'ethane_coords.xyz')) - coords, numbers, masses = log_file.load_geometry() - np.testing.assert_almost_equal( - coords, np.array([[0.66409651, 0.00395265, 0.07100793], - [-0.66409647, -0.00395253, -0.0710079], - [1.24675866, 0.88983869, -0.1613784], - [1.19483972, -0.8753068, 0.42244414], - [-1.19483975, 0.87530673, -0.42244421], - [-1.24675868, -0.88983873, 0.16137844]], np.float64)) - np.testing.assert_almost_equal(numbers, np.array([6, 6, 1, 1, 1, 1], np.float64)) - np.testing.assert_almost_equal(masses, np.array( - [12., 12., 1.00782503, 1.00782503, 1.00782503, 1.00782503], np.float64)) - - log_file = TeraChemLog(os.path.join(self.data_path, 'formaldehyde_coords.xyz')) - coords, numbers, masses = log_file.load_geometry() - np.testing.assert_almost_equal( - coords, np.array([[-4.23756410e-03, 4.24348000e-05, -5.28516700e-04], - [1.19165823e+00, -1.75471911e-02, 1.58030931e-01], - [-5.96146428e-01, 9.38505681e-01, 4.33255558e-02], - [-5.91274235e-01, -9.21000915e-01, -2.00827970e-01]], np.float64)) - np.testing.assert_almost_equal(numbers, np.array([6, 8, 1, 1], np.float64)) - np.testing.assert_almost_equal( - masses, np.array([12., 15.99491462, 1.00782503, 1.00782503], np.float64)) - - log_file = TeraChemLog(os.path.join(self.data_path, 'ethane_output.geometry')) - coords, numbers, masses = log_file.load_geometry() - np.testing.assert_almost_equal( - coords, np.array([[0.66409651, 0.00395265, 0.07100793], - [-0.66409647, -0.00395253, -0.0710079], - [1.24675866, 0.88983869, -0.1613784], - [1.19483972, -0.8753068, 0.42244414], - [-1.19483975, 0.87530673, -0.42244421], - [-1.24675868, -0.88983873, 0.16137844]], np.float64)) - np.testing.assert_almost_equal(numbers, np.array([6, 6, 1, 1, 1, 1], np.float64)) - np.testing.assert_almost_equal( - masses, np.array([12., 12., 1.00782504, 1.00782504, 1.00782504, 1.00782504], np.float64)) - - log_file = TeraChemLog(os.path.join(self.data_path, 'formaldehyde_output.geometry')) - coords, numbers, masses = log_file.load_geometry() - np.testing.assert_almost_equal( - coords, np.array([[-1.2224100e-02, 1.8041000e-04, -1.6211600e-03], - [1.2016482e+00, -1.7734170e-02, 1.5936241e-01], - [-5.9716440e-01, 9.3272817e-01, 4.2440100e-02], - [-5.9225970e-01, -9.1517440e-01, -2.0018135e-01]], np.float64)) - np.testing.assert_almost_equal(numbers, np.array([6, 8, 1, 1], np.float64)) - np.testing.assert_almost_equal( - masses, np.array([12., 15.99491464, 1.00782504, 1.00782504], np.float64)) - - log_file = TeraChemLog(os.path.join(self.data_path, 'ethylamine_freq_output.out')) - coords, numbers, masses = log_file.load_geometry() - np.testing.assert_almost_equal( - coords, np.array([[2.370236, 0.065481, -1.194536], - [0.512276, -0.516064, 0.779232], - [0.859257, 0.87292, 3.300986], - [-1.367578, -0.100279, 0.008089], - [0.56292, -2.564216, 1.100445], - [0.755566, 2.927958, 3.038153], - [2.705598, 0.43874, 4.141338], - [-0.600934, 0.336582, 4.672435], - [2.352825, 1.959707, -1.552162], - [4.141389, -0.322693, -0.540207]], np.float64)) - np.testing.assert_almost_equal(numbers, np.array([7, 6, 6, 1, 1, 1, 1, 1, 1, 1], np.float64)) - np.testing.assert_almost_equal( - masses, np.array([14.003074, 12., 12., 1.00782503, 1.00782503, 1.00782503, 1.00782503, - 1.00782503, 1.00782503, 1.00782503], np.float64)) - - log_file = TeraChemLog(os.path.join(self.data_path, 'formaldehyde_freq_output.out')) - coords, numbers, masses = log_file.load_geometry() - np.testing.assert_almost_equal( - coords, np.array([[2.261989e+00, -1.149050e-01, 1.783170e-01], - [-8.108000e-03, 2.710000e-04, -6.880000e-04], - [-1.038653e+00, 1.827038e+00, -6.398200e-02], - [-1.215229e+00, -1.712404e+00, -1.136470e-01]], np.float64)) - np.testing.assert_almost_equal(numbers, np.array([8, 6, 1, 1], np.float64)) - np.testing.assert_almost_equal( - masses, np.array([15.99491462, 12., 1.00782503, 1.00782503], np.float64)) - - def test_load_conformer(self): - """ - Test parsing frequencies and spin multiplicity from a TeraChem log file. - Translation and rotation modes are not read from the TeraCHem log, and are instead added in statmech. - """ - log_file = TeraChemLog(os.path.join(self.data_path, 'formaldehyde_freq_output.out')) - conformer, unscaled_freqs = log_file.load_conformer() - self.assertIsInstance(conformer, Conformer) - self.assertEqual(len(conformer.modes), 1) - np.testing.assert_almost_equal(conformer.modes[0].frequencies.value_si, unscaled_freqs) - expected_freqs = [1198.6352081, 1276.1991058, 1563.6275932, 1893.2440765, - 2916.3917533, 2965.8683956] - np.testing.assert_almost_equal(conformer.modes[0].frequencies.value_si, expected_freqs) - self.assertEqual(conformer.spin_multiplicity, 1) - - failed_log_file = TeraChemLog(os.path.join(self.data_path, 'failed_freq_job.out')) - with self.assertRaises(LogError): - failed_log_file.load_conformer() - - def test_load_energy(self): - """Test loading the energy in J/mol from a TeraChem output.out or results.dat file""" - output_file = TeraChemLog(os.path.join(self.data_path, 'formaldehyde_sp_terachem_output.out')) - results_file = TeraChemLog(os.path.join(self.data_path, 'formaldehyde_sp_terachem_results.dat')) - freq_file = TeraChemLog(os.path.join(self.data_path, 'formaldehyde_freq_output.out')) - opt_file = TeraChemLog(os.path.join(self.data_path, 'ethane_minimize_output.out')) - e_elect_1 = output_file.load_energy() - e_elect_2 = results_file.load_energy() - e_elect_3 = freq_file.load_energy() - e_elect_4 = opt_file.load_energy() - self.assertEqual(e_elect_1, e_elect_2) - self.assertEqual(e_elect_1, e_elect_3) - self.assertAlmostEqual(e_elect_1, -300621953.7863082) - self.assertAlmostEqual(e_elect_4, -206346606.4271929) - - def test_load_zero_point_energy(self): - """Test loading the ZPE from a TeraChem freq output file""" - log_file = TeraChemLog(os.path.join(self.data_path, 'ethylamine_freq_output.out')) - zpe = log_file.load_zero_point_energy() - self.assertAlmostEqual(zpe, 243113.46765236984) - - log_file = TeraChemLog(os.path.join(self.data_path, 'formaldehyde_freq_output.out')) - zpe = log_file.load_zero_point_energy() - self.assertAlmostEqual(zpe, 70663.2091453692) - - def test_load_scan_energies(self): - """Test loading a PES scan from a TeraCHem log file""" - log_file = TeraChemLog(os.path.join(self.data_path, 'ethanol_scan_terachem_output.out')) - v_list, angles = log_file.load_scan_energies() - print(angles) - expected_v_list = [3.31469351e+00, 5.61670297e+02, 2.28894412e+03, 5.02988537e+03, - 8.06230147e+03, 1.09146826e+04, 1.31616066e+04, 1.44091777e+04, - 1.42173813e+04, 1.28403610e+04, 1.07514495e+04, 7.96656078e+03, - 4.81040645e+03, 2.42069223e+03, 7.90256554e+02, 4.20132486e+00, - 4.54592173e+02, 2.06279144e+03, 4.67391931e+03, 7.62857835e+03, - 1.04970774e+04, 1.27455046e+04, 1.42866289e+04, 1.43930501e+04, - 1.31587081e+04, 1.10047441e+04, 8.24254519e+03, 5.10264086e+03, - 2.56880350e+03, 7.56736797e+02, 1.30067263e+00, 5.19872864e+02, - 2.30963595e+03, 5.02046166e+03, 7.97285489e+03, 1.06923710e+04, - 1.29244615e+04, 1.43422341e+04, 1.43905580e+04, 1.32047110e+04, - 1.12088126e+04, 8.31162367e+03, 5.06568695e+03, 2.54966151e+03, - 8.50076205e+02, 0.00000000e+00] - expected_angles = [0., 0.13962634, 0.27925268, 0.41887902, 0.55850536, 0.6981317, - 0.83775804, 0.97738438, 1.11701072, 1.25663706, 1.3962634, 1.53588974, - 1.67551608, 1.81514242, 1.95476876, 2.0943951, 2.23402144, 2.37364778, - 2.51327412, 2.65290046, 2.7925268, 2.93215314, 3.07177948, 3.21140582, - 3.35103216, 3.4906585, 3.63028484, 3.76991118, 3.90953752, 4.04916386, - 4.1887902, 4.32841654, 4.46804289, 4.60766923, 4.74729557, 4.88692191, - 5.02654825, 5.16617459, 5.30580093, 5.44542727, 5.58505361, 5.72467995, - 5.86430629, 6.00393263, 6.14355897, 6.28318531] # radians - np.testing.assert_almost_equal(v_list, expected_v_list, 4) - np.testing.assert_almost_equal(angles, expected_angles) - -################################################################################ - - -if __name__ == '__main__': - unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arkane/inputTest.py b/arkane/inputTest.py deleted file mode 100644 index bb936bf69f..0000000000 --- a/arkane/inputTest.py +++ /dev/null @@ -1,268 +0,0 @@ -#!/usr/bin/env python3 - -############################################################################### -# # -# RMG - Reaction Mechanism Generator # -# # -# Copyright (c) 2002-2021 Prof. William H. Green (whgreen@mit.edu), # -# Prof. Richard H. West (r.west@neu.edu) and the RMG Team (rmg_dev@mit.edu) # -# # -# Permission is hereby granted, free of charge, to any person obtaining a # -# copy of this software and associated documentation files (the 'Software'), # -# to deal in the Software without restriction, including without limitation # -# the rights to use, copy, modify, merge, publish, distribute, sublicense, # -# and/or sell copies of the Software, and to permit persons to whom the # -# Software is furnished to do so, subject to the following conditions: # -# # -# The above copyright notice and this permission notice shall be included in # -# all copies or substantial portions of the Software. # -# # -# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # -# DEALINGS IN THE SOFTWARE. # -# # -############################################################################### - -""" -This module contains unit tests of the :mod:`arkane.input` module. -""" - -import os -import unittest - -import rmgpy -from rmgpy.exceptions import InputError -from rmgpy.kinetics.tunneling import Eckart -from rmgpy.pdep.collision import SingleExponentialDown -from rmgpy.statmech.rotation import NonlinearRotor -from rmgpy.statmech.translation import IdealGasTranslation -from rmgpy.statmech.vibration import HarmonicOscillator -from rmgpy.thermo.nasa import NASAPolynomial, NASA -from rmgpy.transport import TransportData - -from arkane.input import species, transitionState, reaction, SMILES, load_input_file, process_model_chemistry -from arkane.modelchem import LevelOfTheory, CompositeLevelOfTheory - -################################################################################ - - -class InputTest(unittest.TestCase): - """ - Contains unit tests for the Arkane input module - """ - - def test_species(self): - """ - Test loading a species from input file-like kew word arguments - """ - label0 = 'CH2O' - kwargs = {'E0': (28.69, 'kcal/mol'), - 'structure': SMILES('C=O'), - 'collisionModel': TransportData(sigma=(3.69e-10, 'm'), epsilon=(4.0, 'kJ/mol')), - 'energyTransferModel': SingleExponentialDown(alpha0=(0.956, 'kJ/mol'), T0=(300, 'K'), n=0.95), - 'spinMultiplicity': 1, - 'opticalIsomers': 1, - 'modes': [HarmonicOscillator(frequencies=([1180, 1261, 1529, 1764, 2931, 2999], 'cm^-1')), - NonlinearRotor(rotationalConstant=([1.15498821005263, 1.3156969584727, 9.45570474524524], - "cm^-1"), symmetry=2, quantum=False), - IdealGasTranslation(mass=(30.0106, "g/mol")), - ]} - - spc0 = species(label0, **kwargs) - self.assertEqual(spc0.label, 'CH2O') - self.assertEqual(spc0.smiles, 'C=O') - self.assertAlmostEqual(spc0.conformer.E0.value_si, 120038.96) - self.assertEqual(spc0.conformer.spin_multiplicity, 1) - self.assertEqual(spc0.conformer.optical_isomers, 1) - self.assertEqual(len(spc0.conformer.modes), 3) - self.assertIsInstance(spc0.transport_data, TransportData) - self.assertIsInstance(spc0.energy_transfer_model, SingleExponentialDown) - - def test_species_atomic_nasa_polynomial(self): - """ - Test loading a atom with NASA polynomials - """ - label0 = "H(1)" - kwargs = {"structure": SMILES('[H]'), - "thermo": NASA(polynomials=[ - NASAPolynomial(coeffs=[2.5, 0, 0, 0, 0, 25473.7, -0.446683], Tmin=(200, 'K'), Tmax=(1000, 'K')), - NASAPolynomial(coeffs=[2.5, 0, 0, 0, 0, 25473.7, -0.446683], Tmin=(1000, 'K'), Tmax=(6000, 'K'))], - Tmin=(200, 'K'), Tmax=(6000, 'K'), comment="""Thermo library: FFCM1(-)"""), - "energyTransferModel": SingleExponentialDown(alpha0=(3.5886, 'kJ/mol'), T0=(300, 'K'), n=0.85)} - spc0 = species(label0, **kwargs) - self.assertEqual(spc0.label, label0) - self.assertEqual(spc0.smiles, '[H]') - self.assertTrue(spc0.has_statmech()) - self.assertEqual(spc0.thermo, kwargs['thermo']) - - def test_species_polyatomic_nasa_polynomial(self): - """ - Test loading a species with NASA polynomials - """ - label0 = "benzyl" - kwargs = {"structure": SMILES('[c]1ccccc1'), - "thermo": NASA(polynomials=[NASAPolynomial( - coeffs=[2.78632, 0.00784632, 7.97887e-05, -1.11617e-07, 4.39429e-11, 39695, 11.5114], - Tmin=(100, 'K'), Tmax=(943.73, 'K')), - NASAPolynomial( - coeffs=[13.2455, 0.0115667, -2.49996e-06, 4.66496e-10, -4.12376e-14, - 35581.1, -49.6793], Tmin=(943.73, 'K'), Tmax=(5000, 'K'))], - Tmin=(100, 'K'), Tmax=(5000, 'K'), - comment="""Thermo library: Fulvene_H + radical(CbJ)"""), - "energyTransferModel": SingleExponentialDown(alpha0=(3.5886, 'kJ/mol'), T0=(300, 'K'), n=0.85)} - spc0 = species(label0, **kwargs) - self.assertEqual(spc0.label, label0) - self.assertTrue(spc0.has_statmech()) - self.assertEqual(spc0.thermo, kwargs['thermo']) - - def test_transition_state(self): - """ - Test loading a transition state from input file-like kew word arguments - """ - label0 = 'TS1' - kwargs = {'E0': (39.95, 'kcal/mol'), - 'spinMultiplicity': 2, - 'opticalIsomers': 1, - 'frequency': (-1934, 'cm^-1'), - 'modes': [HarmonicOscillator(frequencies=([792, 987, 1136, 1142, 1482, 2441, 3096, 3183], 'cm^-1')), - NonlinearRotor(rotationalConstant=([0.928, 0.962, 5.807], "cm^-1"), symmetry=1, - quantum=False), - IdealGasTranslation(mass=(31.01843, "g/mol"))]} - - ts0 = transitionState(label0, **kwargs) - self.assertEqual(ts0.label, 'TS1') - self.assertAlmostEqual(ts0.conformer.E0.value_si, 167150.8) - self.assertEqual(ts0.conformer.spin_multiplicity, 2) - self.assertEqual(ts0.conformer.optical_isomers, 1) - self.assertEqual(ts0.frequency.value_si, -1934.0) - self.assertEqual(len(ts0.conformer.modes), 3) - - def test_reaction(self): - """ - Test loading a reaction from input file-like kew word arguments - """ - - species( - label='methoxy', - structure=SMILES('C[O]'), - E0=(9.44, 'kcal/mol'), - modes=[ - HarmonicOscillator(frequencies=([758, 960, 1106, 1393, 1403, 1518, 2940, 3019, 3065], 'cm^-1')), - NonlinearRotor(rotationalConstant=([0.916, 0.921, 5.251], "cm^-1"), symmetry=3, quantum=False), - IdealGasTranslation(mass=(31.01843, "g/mol"))], - spinMultiplicity=2, - opticalIsomers=1, - molecularWeight=(31.01843, 'amu'), - collisionModel=TransportData(sigma=(3.69e-10, 'm'), epsilon=(4.0, 'kJ/mol')), - energyTransferModel=SingleExponentialDown(alpha0=(0.956, 'kJ/mol'), T0=(300, 'K'), n=0.95)) - - species( - label='formaldehyde', - E0=(28.69, 'kcal/mol'), - molecularWeight=(30.0106, "g/mol"), - collisionModel=TransportData(sigma=(3.69e-10, 'm'), epsilon=(4.0, 'kJ/mol')), - energyTransferModel=SingleExponentialDown(alpha0=(0.956, 'kJ/mol'), T0=(300, 'K'), n=0.95), - spinMultiplicity=1, - opticalIsomers=1, - modes=[HarmonicOscillator(frequencies=([1180, 1261, 1529, 1764, 2931, 2999], 'cm^-1')), - NonlinearRotor(rotationalConstant=([1.15498821005263, 1.3156969584727, 9.45570474524524], "cm^-1"), - symmetry=2, quantum=False), - IdealGasTranslation(mass=(30.0106, "g/mol"))]) - - species( - label='H', - E0=(0.000, 'kcal/mol'), - molecularWeight=(1.00783, "g/mol"), - collisionModel=TransportData(sigma=(3.69e-10, 'm'), epsilon=(4.0, 'kJ/mol')), - energyTransferModel=SingleExponentialDown(alpha0=(0.956, 'kJ/mol'), T0=(300, 'K'), n=0.95), - modes=[IdealGasTranslation(mass=(1.00783, "g/mol"))], - spinMultiplicity=2, - opticalIsomers=1) - - transitionState( - label='TS3', - E0=(34.1, 'kcal/mol'), - spinMultiplicity=2, - opticalIsomers=1, - frequency=(-967, 'cm^-1'), - modes=[HarmonicOscillator(frequencies=([466, 581, 1169, 1242, 1499, 1659, 2933, 3000], 'cm^-1')), - NonlinearRotor(rotationalConstant=([0.970, 1.029, 3.717], "cm^-1"), symmetry=1, quantum=False), - IdealGasTranslation(mass=(31.01843, "g/mol"))]) - - reactants = ['formaldehyde', 'H'] - products = ['methoxy'] - tunneling = 'Eckart' - - rxn = reaction('CH2O+H=Methoxy', reactants, products, 'TS3', tunneling=tunneling) - self.assertEqual(rxn.label, 'CH2O+H=Methoxy') - self.assertEqual(len(rxn.reactants), 2) - self.assertEqual(len(rxn.products), 1) - self.assertAlmostEqual(rxn.reactants[0].conformer.E0.value_si, 0) - self.assertAlmostEqual(rxn.reactants[1].conformer.E0.value_si, 120038.96) - self.assertAlmostEqual(rxn.products[0].conformer.E0.value_si, 39496.96) - self.assertAlmostEqual(rxn.transition_state.conformer.E0.value_si, 142674.4) - self.assertAlmostEqual(rxn.transition_state.frequency.value_si, -967.0) - self.assertIsInstance(rxn.transition_state.tunneling, Eckart) - - def test_load_input_file(self): - """Test loading an Arkane input file""" - path = os.path.join(os.path.dirname(os.path.dirname(rmgpy.__file__)), 'examples', 'arkane', 'networks', - 'acetyl+O2', 'input.py') - job_list, reaction_dict, species_dict, transition_state_dict, network_dict, model_chemistry \ - = load_input_file(path) - - self.assertEqual(len(job_list), 1) - - self.assertEqual(len(reaction_dict), 5) - self.assertTrue('entrance1' in reaction_dict) - self.assertTrue('exit2' in reaction_dict) - - self.assertEqual(len(species_dict), 9) - self.assertTrue('acetyl' in species_dict) - self.assertTrue('hydroperoxyl' in species_dict) - - self.assertEqual(len(transition_state_dict), 5) - self.assertTrue('entrance1' in transition_state_dict) - self.assertTrue('isom1' in transition_state_dict) - - self.assertEqual(len(network_dict), 1) - self.assertTrue('acetyl + O2' in network_dict) - - self.assertIsNone(model_chemistry) - - def test_process_model_chemistry(self): - """ - Test processing the model chemistry to derive the sp and freq levels - """ - mc = 'ccsd(t)-f12a/aug-cc-pvtz//b3lyp/6-311++g(3df,3pd)' - lot = process_model_chemistry(mc) - self.assertIsInstance(lot, CompositeLevelOfTheory) - self.assertEqual(lot.energy, LevelOfTheory('ccsd(t)-f12a', 'aug-cc-pvtz')) - self.assertEqual(lot.freq, LevelOfTheory('b3lyp', '6-311++g(3df,3pd)')) - - mc = 'b3lyp-d3/def2-tzvp' - lot = process_model_chemistry(mc) - self.assertIsInstance(lot, LevelOfTheory) - self.assertEqual(lot, LevelOfTheory('b3lyp-d3', 'def2-tzvp')) - - mc = 'cbs-qb3' - lot = process_model_chemistry(mc) - self.assertIsInstance(lot, LevelOfTheory) - self.assertEqual(lot, LevelOfTheory('cbs-qb3')) - - mc = LevelOfTheory('test') - lot = process_model_chemistry(mc) - self.assertIs(mc, lot) - - with self.assertRaises(InputError): - process_model_chemistry('CCSD(T)-F12a/aug-cc-pVTZ//CCSD(T)-F12a/aug-cc-pVTZ//B3LYP/6-311++G(3df,3pd)') - -################################################################################ - - -if __name__ == '__main__': - unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arkane/kinetics.py b/arkane/kinetics.py index 17ad493053..6b221e7339 100644 --- a/arkane/kinetics.py +++ b/arkane/kinetics.py @@ -62,21 +62,20 @@ class KineticsJob(object): equation is used. """ - def __init__(self, reaction, Tmin=None, Tmax=None, Tlist=None, Tcount=0, sensitivity_conditions=None, - three_params=True): + def __init__(self, reaction, Tmin=None, Tmax=None, Tlist=None, Tcount=0, sensitivity_conditions=None, three_params=True): self.usedTST = False - self.Tmin = Tmin if Tmin is not None else (298, 'K') - self.Tmax = Tmax if Tmax is not None else (2500, 'K') + self.Tmin = Tmin if Tmin is not None else (298, "K") + self.Tmax = Tmax if Tmax is not None else (2500, "K") self.Tcount = Tcount if Tcount > 3 else 50 self.three_params = three_params if Tlist is not None: self.Tlist = Tlist - self.Tmin = (min(self.Tlist.value_si), 'K') - self.Tmax = (max(self.Tlist.value_si), 'K') + self.Tmin = (min(self.Tlist.value_si), "K") + self.Tmax = (max(self.Tlist.value_si), "K") self.Tcount = len(self.Tlist.value_si) else: - self.Tlist = (1 / np.linspace(1 / self.Tmax.value_si, 1 / self.Tmin.value_si, self.Tcount), 'K') + self.Tlist = (1 / np.linspace(1 / self.Tmax.value_si, 1 / self.Tmin.value_si, self.Tcount), "K") self.reaction = reaction self.k_units = None @@ -128,27 +127,24 @@ def execute(self, output_directory=None, plot=True): try: self.write_output(output_directory) except Exception as e: - logging.warning("Could not write kinetics output file due to error: " - "{0} in reaction {1}".format(e, self.reaction.label)) + logging.warning("Could not write kinetics output file due to error: " "{0} in reaction {1}".format(e, self.reaction.label)) try: self.write_chemkin(output_directory) except Exception as e: - logging.warning("Could not write kinetics chemkin output due to error: " - "{0} in reaction {1}".format(e, self.reaction.label)) + logging.warning("Could not write kinetics chemkin output due to error: " "{0} in reaction {1}".format(e, self.reaction.label)) if plot: try: self.plot(output_directory) except Exception as e: - logging.warning("Could not plot kinetics due to error: " - "{0} in reaction {1}".format(e, self.reaction.label)) + logging.warning("Could not plot kinetics due to error: " "{0} in reaction {1}".format(e, self.reaction.label)) try: self.draw(output_directory) except Exception as e: logging.warning("Could not draw reaction {1} due to error: {0}".format(e, self.reaction.label)) if self.sensitivity_conditions is not None: - logging.info('\n\nRunning sensitivity analysis...') + logging.info("\n\nRunning sensitivity analysis...") SensAnalysis(self, output_directory) - logging.debug('Finished kinetics job for reaction {0}.'.format(self.reaction)) + logging.debug("Finished kinetics job for reaction {0}.".format(self.reaction)) logging.debug(repr(self.reaction)) def generate_kinetics(self): @@ -159,36 +155,34 @@ def generate_kinetics(self): if isinstance(self.reaction.kinetics, Arrhenius): return None self.usedTST = True - kinetics_class = 'Arrhenius' + kinetics_class = "Arrhenius" tunneling = self.reaction.transition_state.tunneling if isinstance(tunneling, Wigner) and tunneling.frequency is None: tunneling.frequency = (self.reaction.transition_state.frequency.value_si, "cm^-1") elif isinstance(tunneling, Eckart) and tunneling.frequency is None: tunneling.frequency = (self.reaction.transition_state.frequency.value_si, "cm^-1") - tunneling.E0_reac = (sum([reactant.conformer.E0.value_si - for reactant in self.reaction.reactants]) * 0.001, "kJ/mol") + tunneling.E0_reac = (sum([reactant.conformer.E0.value_si for reactant in self.reaction.reactants]) * 0.001, "kJ/mol") tunneling.E0_TS = (self.reaction.transition_state.conformer.E0.value_si * 0.001, "kJ/mol") - tunneling.E0_prod = (sum([product.conformer.E0.value_si - for product in self.reaction.products]) * 0.001, "kJ/mol") + tunneling.E0_prod = (sum([product.conformer.E0.value_si for product in self.reaction.products]) * 0.001, "kJ/mol") elif tunneling is not None: if tunneling.frequency is not None: # Frequency was given by the user pass else: - raise ValueError('Unknown tunneling model {0!r} for reaction {1}.'.format(tunneling, self.reaction)) - logging.debug('Generating {0} kinetics model for {1}...'.format(kinetics_class, self.reaction)) + raise ValueError("Unknown tunneling model {0!r} for reaction {1}.".format(tunneling, self.reaction)) + logging.debug("Generating {0} kinetics model for {1}...".format(kinetics_class, self.reaction)) klist = np.zeros_like(self.Tlist.value_si) for i, t in enumerate(self.Tlist.value_si): klist[i] = self.reaction.calculate_tst_rate_coefficient(t) order = len(self.reaction.reactants) klist *= 1e6 ** (order - 1) - self.k_units = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[order] - self.K_eq_units = {2: 'mol^2/cm^6', 1: 'mol/cm^3', 0: ' ', -1: 'cm^3/mol', -2: 'cm^6/mol^2'}[ - len(self.reaction.products) - len(self.reaction.reactants)] - self.k_r_units = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[len(self.reaction.products)] - self.reaction.kinetics = Arrhenius().fit_to_data(self.Tlist.value_si, klist, kunits=self.k_units, - three_params=self.three_params) + self.k_units = {1: "s^-1", 2: "cm^3/(mol*s)", 3: "cm^6/(mol^2*s)"}[order] + self.K_eq_units = {2: "mol^2/cm^6", 1: "mol/cm^3", 0: " ", -1: "cm^3/mol", -2: "cm^6/mol^2"}[ + len(self.reaction.products) - len(self.reaction.reactants) + ] + self.k_r_units = {1: "s^-1", 2: "cm^3/(mol*s)", 3: "cm^6/(mol^2*s)"}[len(self.reaction.products)] + self.reaction.kinetics = Arrhenius().fit_to_data(self.Tlist.value_si, klist, kunits=self.k_units, three_params=self.three_params) self.reaction.elementary_high_p = True def write_output(self, output_directory): @@ -200,19 +194,19 @@ def write_output(self, output_directory): ks, k0s, k0_revs, k_revs = [], [], [], [] - logging.info('Saving kinetics for {0}...'.format(reaction)) + logging.info("Saving kinetics for {0}...".format(reaction)) order = len(self.reaction.reactants) factor = 1e6 ** (order - 1) - f = open(os.path.join(output_directory, 'output.py'), 'a') + f = open(os.path.join(output_directory, "output.py"), "a") if self.usedTST: # If TST is not used, eg. it was given in 'reaction', then this will throw an error. - f.write('# ======= =========== =========== =========== ===============\n') - f.write('# Temp. k (TST) Tunneling k (TST+T) Units\n') - f.write('# ======= =========== =========== =========== ===============\n') + f.write("# ======= =========== =========== =========== ===============\n") + f.write("# Temp. k (TST) Tunneling k (TST+T) Units\n") + f.write("# ======= =========== =========== =========== ===============\n") if self.Tlist is None: t_list = np.array([300, 400, 500, 600, 800, 1000, 1500, 2000]) @@ -233,22 +227,23 @@ def write_output(self, output_directory): except (SpeciesError, ZeroDivisionError): k = reaction.get_rate_coefficient(T) kappa = 0 - logging.info("The species in reaction {0} do not have adequate information for TST, " - "using default kinetics values.".format(reaction)) + logging.info( + "The species in reaction {0} do not have adequate information for TST, " "using default kinetics values.".format(reaction) + ) tunneling = reaction.transition_state.tunneling ks.append(k) k0s.append(k0) - f.write('# {0:4g} K {1:11.3e} {2:11g} {3:11.3e} {4}\n'.format(T, k0, kappa, k, self.k_units)) - f.write('# ======= =========== =========== =========== ===============\n') - f.write('\n\n') + f.write("# {0:4g} K {1:11.3e} {2:11g} {3:11.3e} {4}\n".format(T, k0, kappa, k, self.k_units)) + f.write("# ======= =========== =========== =========== ===============\n") + f.write("\n\n") - f.write('# ======= ============ =========== ============ ============= =========\n') - f.write('# Temp. Kc (eq) Units k_rev (TST) k_rev (TST+T) Units\n') - f.write('# ======= ============ =========== ============ ============= =========\n') + f.write("# ======= ============ =========== ============ ============= =========\n") + f.write("# Temp. Kc (eq) Units k_rev (TST) k_rev (TST+T) Units\n") + f.write("# ======= ============ =========== ============ ============= =========\n") # Initialize Object for Converting Units - if self.K_eq_units != ' ': + if self.K_eq_units != " ": keq_unit_converter = quantity.Units(self.K_eq_units).get_conversion_factor_from_si() else: keq_unit_converter = 1 @@ -261,29 +256,27 @@ def write_output(self, output_directory): k_rev = k / K_eq k0_revs.append(k0_rev) k_revs.append(k_rev) - f.write('# {0:4g} K {1:11.3e} {2} {3:11.3e} {4:11.3e} {5}\n'.format( - T, K_eq, self.K_eq_units, k0_rev, k_rev, self.k_r_units)) + f.write( + "# {0:4g} K {1:11.3e} {2} {3:11.3e} {4:11.3e} {5}\n".format(T, K_eq, self.K_eq_units, k0_rev, k_rev, self.k_r_units) + ) - f.write('# ======= ============ =========== ============ ============= =========\n') - f.write('\n\n') + f.write("# ======= ============ =========== ============ ============= =========\n") + f.write("\n\n") - kinetics_0_rev = Arrhenius().fit_to_data(t_list, np.array(k0_revs), kunits=self.k_r_units, - three_params=self.three_params) - kinetics_rev = Arrhenius().fit_to_data(t_list, np.array(k_revs), kunits=self.k_r_units, - three_params=self.three_params) + kinetics_0_rev = Arrhenius().fit_to_data(t_list, np.array(k0_revs), kunits=self.k_r_units, three_params=self.three_params) + kinetics_rev = Arrhenius().fit_to_data(t_list, np.array(k_revs), kunits=self.k_r_units, three_params=self.three_params) - f.write('# k_rev (TST) = {0} \n'.format(kinetics_0_rev)) - f.write('# k_rev (TST+T) = {0} \n\n'.format(kinetics_rev)) + f.write("# k_rev (TST) = {0} \n".format(kinetics_0_rev)) + f.write("# k_rev (TST+T) = {0} \n\n".format(kinetics_rev)) if self.three_params: - f.write('# kinetics fitted using the modified three-parameter Arrhenius equation ' - 'k = A * (T/T0)^n * exp(-Ea/RT) \n') + f.write("# kinetics fitted using the modified three-parameter Arrhenius equation " "k = A * (T/T0)^n * exp(-Ea/RT) \n") else: - f.write('# kinetics fitted using the two-parameter Arrhenius equation k = A * exp(-Ea/RT) \n') + f.write("# kinetics fitted using the two-parameter Arrhenius equation k = A * exp(-Ea/RT) \n") # Reaction path degeneracy is INCLUDED in the kinetics itself! - rxn_str = 'kinetics(label={0!r}, kinetics={1!r})'.format(reaction.label, reaction.kinetics) - f.write('{0}\n\n'.format(prettify(rxn_str))) + rxn_str = "kinetics(label={0!r}, kinetics={1!r})".format(reaction.label, reaction.kinetics) + f.write("{0}\n\n".format(prettify(rxn_str))) f.close() @@ -298,37 +291,38 @@ def write_chemkin(self, output_directory): reaction = self.reaction kinetics = reaction.kinetics - rxn_str = '' + rxn_str = "" if reaction.kinetics.comment: for line in reaction.kinetics.comment.split("\n"): rxn_str += "! {0}\n".format(line) - rxn_str += '{0!s:51} {1:9.3e} {2:9.3f} {3:9.3f}\n'.format( + rxn_str += "{0!s:51} {1:9.3e} {2:9.3f} {3:9.3f}\n".format( reaction, kinetics.A.value_si * factor, kinetics.n.value_si, - kinetics.Ea.value_si / 4184., + kinetics.Ea.value_si / 4184.0, ) - with open(os.path.join(output_directory, 'chem.inp'), 'a') as f: - f.write('{0}\n'.format(rxn_str)) + with open(os.path.join(output_directory, "chem.inp"), "a") as f: + f.write("{0}\n".format(rxn_str)) def save_yaml(self, output_directory): """ Save a YAML file for TSs if structures of the respective reactant/s and product/s are known """ - if all([spc.molecule is not None and len(spc.molecule) - for spc in self.reaction.reactants + self.reaction.products]): + if all([spc.molecule is not None and len(spc.molecule) for spc in self.reaction.reactants + self.reaction.products]): self.arkane_species.update_species_attributes(self.reaction.transition_state) self.arkane_species.reaction_label = self.reaction.label - self.arkane_species.reactants = [{'label': spc.label, 'adjacency_list': spc.molecule[0].to_adjacency_list()} - for spc in self.reaction.reactants] - self.arkane_species.products = [{'label': spc.label, 'adjacency_list': spc.molecule[0].to_adjacency_list()} - for spc in self.reaction.products] + self.arkane_species.reactants = [ + {"label": spc.label, "adjacency_list": spc.molecule[0].to_adjacency_list()} for spc in self.reaction.reactants + ] + self.arkane_species.products = [ + {"label": spc.label, "adjacency_list": spc.molecule[0].to_adjacency_list()} for spc in self.reaction.products + ] self.arkane_species.save_yaml(path=output_directory) def plot(self, output_directory): """ - Plot both the raw kinetics data and the Arrhenius fit versus + Plot both the raw kinetics data and the Arrhenius fit versus temperature. The plot is saved to the file ``kinetics.pdf`` in the output directory. The plot is not generated if ``matplotlib`` is not installed. @@ -350,26 +344,28 @@ def plot(self, output_directory): klist *= 1e6 ** (order - 1) klist2 *= 1e6 ** (order - 1) t_list = [1000.0 / t for t in t_list] - plt.semilogy(t_list, klist, 'ob', label='TST calculation') - plt.semilogy(t_list, klist2, '-k', label='Fitted rate') + plt.semilogy(t_list, klist, "ob", label="TST calculation") + plt.semilogy(t_list, klist2, "-k", label="Fitted rate") plt.legend() - reaction_str = '{0} {1} {2}'.format( - ' + '.join([reactant.label for reactant in self.reaction.reactants]), - '<=>', ' + '.join([product.label for product in self.reaction.products])) + reaction_str = "{0} {1} {2}".format( + " + ".join([reactant.label for reactant in self.reaction.reactants]), + "<=>", + " + ".join([product.label for product in self.reaction.products]), + ) plt.title(reaction_str) - plt.xlabel('1000 / Temperature (K^-1)') - plt.ylabel('Rate coefficient ({0})'.format(self.k_units)) + plt.xlabel("1000 / Temperature (K^-1)") + plt.ylabel("Rate coefficient ({0})".format(self.k_units)) - plot_path = os.path.join(output_directory, 'plots') + plot_path = os.path.join(output_directory, "plots") if not os.path.exists(plot_path): os.mkdir(plot_path) valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits) - filename = ''.join(c for c in reaction_str if c in valid_chars) + '.pdf' + filename = "".join(c for c in reaction_str if c in valid_chars) + ".pdf" plt.savefig(os.path.join(plot_path, filename)) plt.close() - def draw(self, output_directory, file_format='pdf'): + def draw(self, output_directory, file_format="pdf"): """ Generate a PDF drawing of the reaction. This requires that Cairo and its Python wrapper be available; if not, @@ -379,15 +375,17 @@ def draw(self, output_directory, file_format='pdf'): one of the following: `pdf`, `svg`, `png`. """ - drawing_path = os.path.join(output_directory, 'paths') + drawing_path = os.path.join(output_directory, "paths") if not os.path.exists(drawing_path): os.mkdir(drawing_path) valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits) - reaction_str = '{0} {1} {2}'.format( - ' + '.join([reactant.label for reactant in self.reaction.reactants]), - '<=>', ' + '.join([product.label for product in self.reaction.products])) - filename = ''.join(c for c in reaction_str if c in valid_chars) + '.pdf' + reaction_str = "{0} {1} {2}".format( + " + ".join([reactant.label for reactant in self.reaction.reactants]), + "<=>", + " + ".join([product.label for product in self.reaction.products]), + ) + filename = "".join(c for c in reaction_str if c in valid_chars) + ".pdf" path = os.path.join(drawing_path, filename) KineticsDrawer().draw(self.reaction, file_format=file_format, path=path) @@ -407,16 +405,16 @@ class KineticsDrawer(object): def __init__(self, options=None): self.options = { - 'structures': True, - 'fontFamily': 'sans', - 'fontSizeNormal': 12, - 'Eunits': 'kJ/mol', - 'padding': 16, - 'wellWidth': 64, - 'wellSpacing': 64, - 'Eslope': 1.5, - 'TSwidth': 16, - 'E0offset': 0.0, + "structures": True, + "fontFamily": "sans", + "fontSizeNormal": 12, + "Eunits": "kJ/mol", + "padding": 16, + "wellWidth": 64, + "wellSpacing": 64, + "Eslope": 1.5, + "TSwidth": 16, + "E0offset": 0.0, } if options: self.options.update(options) @@ -441,11 +439,11 @@ def _get_energy_range(self): e0_max = max(self.wells[0].E0, self.wells[1].E0, self.reaction.transition_state.conformer.E0.value_si) if e0_max - e0_min > 5e5: # the energy barrier in one of the reaction directions is larger than 500 kJ/mol, warn the user - logging.warning('The energy differences between the stationary points of reaction {0} ' - 'seems too large.'.format(self.reaction)) - logging.warning('Got the following energies:\nWell 1: {0} kJ/mol\nTS: {1} kJ/mol\nWell 2: {2}' - ' kJ/mol'.format(self.wells[0].E0 / 1000., self.wells[1].E0 / 1000., - self.reaction.transition_state.conformer.E0.value_si / 1000.)) + logging.warning("The energy differences between the stationary points of reaction {0} " "seems too large.".format(self.reaction)) + logging.warning( + "Got the following energies:\nWell 1: {0} kJ/mol\nTS: {1} kJ/mol\nWell 2: {2}" + " kJ/mol".format(self.wells[0].E0 / 1000.0, self.wells[1].E0 / 1000.0, self.reaction.transition_state.conformer.E0.value_si / 1000.0) + ) return e0_min, e0_max def _use_structure_for_label(self, configuration): @@ -455,7 +453,7 @@ def _use_structure_for_label(self, configuration): """ # Initialize with the current user option value - use_structures = self.options['structures'] + use_structures = self.options["structures"] # But don't use structures if one or more species in the configuration # do not have structure data @@ -466,7 +464,7 @@ def _use_structure_for_label(self, configuration): return use_structures - def _get_text_size(self, text, padding=2, file_format='pdf'): + def _get_text_size(self, text, padding=2, file_format="pdf"): try: import cairocffi as cairo except ImportError: @@ -475,7 +473,7 @@ def _get_text_size(self, text, padding=2, file_format='pdf'): # Use dummy surface to determine text extents surface = create_new_surface(file_format) cr = cairo.Context(surface) - cr.set_font_size(self.options['fontSizeNormal']) + cr.set_font_size(self.options["fontSizeNormal"]) extents = cr.text_extents(text) width = extents[2] + 2 * padding height = extents[3] + 2 * padding @@ -483,7 +481,7 @@ def _get_text_size(self, text, padding=2, file_format='pdf'): def _draw_text(self, text, cr, x0, y0, padding=2): cr.save() - cr.set_font_size(self.options['fontSizeNormal']) + cr.set_font_size(self.options["fontSizeNormal"]) extents = cr.text_extents(text) cr.move_to(x0 - extents[0] - padding, y0 - extents[1] + padding) cr.set_source_rgba(0.0, 0.0, 0.0, 1.0) @@ -493,7 +491,7 @@ def _draw_text(self, text, cr, x0, y0, padding=2): height = extents[3] + 2 * padding return [0, 0, width, height] - def _get_label_size(self, configuration, file_format='pdf'): + def _get_label_size(self, configuration, file_format="pdf"): width = 0 height = 0 bounding_rects = [] @@ -505,7 +503,7 @@ def _get_label_size(self, configuration, file_format='pdf'): for spec in configuration.species_list: bounding_rects.append(self._get_text_size(spec.label, file_format=file_format)) - plus_rect = self._get_text_size('+', file_format=file_format) + plus_rect = self._get_text_size("+", file_format=file_format) for rect in bounding_rects: if width < rect[2]: @@ -515,8 +513,7 @@ def _get_label_size(self, configuration, file_format='pdf'): return [0, 0, width, height] - def _draw_label(self, configuration, cr, x0, y0, file_format='pdf'): - + def _draw_label(self, configuration, cr, x0, y0, file_format="pdf"): bounding_rect = self._get_label_size(configuration, file_format=file_format) padding = 2 @@ -524,9 +521,9 @@ def _draw_label(self, configuration, cr, x0, y0, file_format='pdf'): y = y0 for i, spec in enumerate(configuration.species_list): if i > 0: - rect = self._get_text_size('+', padding=padding, file_format=file_format) + rect = self._get_text_size("+", padding=padding, file_format=file_format) x = x0 - 0.5 * (rect[2] - bounding_rect[2]) + 2 * padding - self._draw_text('+', cr, x, y) + self._draw_text("+", cr, x, y) y += rect[3] if use_structures: @@ -559,7 +556,7 @@ def draw(self, reaction, file_format, path=None): try: import cairo except ImportError: - logging.warning('Cairo not found; potential energy surface will not be drawn.') + logging.warning("Cairo not found; potential energy surface will not be drawn.") return self.reaction = reaction @@ -576,24 +573,23 @@ def draw(self, reaction, file_format, path=None): e0_max *= 0.001 # Drawing parameters - padding = self.options['padding'] - well_width = self.options['wellWidth'] - well_spacing = self.options['wellSpacing'] - e_slope = self.options['Eslope'] - ts_width = self.options['TSwidth'] + padding = self.options["padding"] + well_width = self.options["wellWidth"] + well_spacing = self.options["wellSpacing"] + e_slope = self.options["Eslope"] + ts_width = self.options["TSwidth"] - e0_offset = self.options['E0offset'] * 0.001 + e0_offset = self.options["E0offset"] * 0.001 # Choose multiplier to convert energies to desired units (on figure only) - e_units = self.options['Eunits'] + e_units = self.options["Eunits"] try: - e_mult = {'J/mol': 1.0, 'kJ/mol': 0.001, 'cal/mol': 1.0 / 4.184, 'kcal/mol': 1.0 / 4184., - 'cm^-1': 1.0 / 11.962}[e_units] + e_mult = {"J/mol": 1.0, "kJ/mol": 0.001, "cal/mol": 1.0 / 4.184, "kcal/mol": 1.0 / 4184.0, "cm^-1": 1.0 / 11.962}[e_units] except KeyError: raise InputError('Invalid value "{0}" for Eunits parameter.'.format(e_units)) # Determine height required for drawing - e_height = self._get_text_size('0.0', file_format=file_format)[3] + 6 + e_height = self._get_text_size("0.0", file_format=file_format)[3] + 6 y_e0 = (e0_max - 0.0) * e_slope + padding + e_height height = (e0_max - e0_min) * e_slope + 2 * padding + e_height + 6 for i in range(len(self.wells)): @@ -602,7 +598,7 @@ def draw(self, reaction, file_format, path=None): break # Determine naive position of each well (one per column) - coordinates = np.zeros((len(self.wells), 2), np.float64) + coordinates = np.zeros((len(self.wells), 2), float) x = padding for i in range(len(self.wells)): well = self.wells[i] @@ -628,7 +624,7 @@ def draw(self, reaction, file_format, path=None): # Squish columns together from the left where possible until an isomer is encountered old_left = np.min(coordinates[:, 0]) - n_left = - 1 + n_left = -1 columns = [] for i in range(n_left, -1, -1): top = well_rects[i][1] @@ -693,12 +689,12 @@ def draw(self, reaction, file_format, path=None): # Some global settings cr.select_font_face("sans") - cr.set_font_size(self.options['fontSizeNormal']) + cr.set_font_size(self.options["fontSizeNormal"]) # Fill the background with white cr.set_source_rgba(1.0, 1.0, 1.0, 1.0) cr.paint() - self._draw_text('E0 ({0})'.format(e_units), cr, 15, 10, padding=2) # write units + self._draw_text("E0 ({0})".format(e_units), cr, 15, 10, padding=2) # write units # Draw reactions e0_reac = self.wells[0].E0 * 0.001 - e0_offset @@ -722,8 +718,8 @@ def draw(self, reaction, file_format, path=None): else: x0 = 0.5 * (x1 + x2) y0 = y_e0 - (e0_ts + e0_offset) * e_slope - width1 = (x0 - x1) - width2 = (x2 - x0) + width1 = x0 - x1 + width2 = x2 - x0 # Draw horizontal line for TS cr.set_source_rgba(0.0, 0.0, 0.0, 1.0) cr.set_line_width(2.0) @@ -731,7 +727,7 @@ def draw(self, reaction, file_format, path=None): cr.line_to(x0 + ts_width / 2.0, y0) cr.stroke() # Add background and text for energy - e0 = "{0:.1f}".format(e0_ts * 1000. * e_mult) + e0 = "{0:.1f}".format(e0_ts * 1000.0 * e_mult) extents = cr.text_extents(e0) x = x0 - extents[2] / 2.0 y = y0 - 6.0 @@ -750,7 +746,7 @@ def draw(self, reaction, file_format, path=None): cr.curve_to(x0 + width2 / 8.0 + ts_width / 2.0, y0, x2 - width2 / 8.0, y2, x2, y2) cr.stroke() else: - width = (x2 - x1) + width = x2 - x1 # Draw Bezier curve connecting reactants and products through TS cr.set_source_rgba(0.0, 0.0, 0.0, 0.5) cr.set_line_width(1.0) @@ -769,7 +765,7 @@ def draw(self, reaction, file_format, path=None): cr.stroke() # Add background and text for energy e0 = well.E0 * 0.001 - e0_offset - e0 = "{0:.1f}".format(e0 * 1000. * e_mult) + e0 = "{0:.1f}".format(e0 * 1000.0 * e_mult) extents = cr.text_extents(e0) x = x0 - extents[2] / 2.0 y = y0 - 6.0 @@ -788,7 +784,7 @@ def draw(self, reaction, file_format, path=None): self._draw_label(well, cr, x, y, file_format=file_format) # Finish Cairo drawing - if file_format == 'png': + if file_format == "png": surface.write_to_png(path) else: surface.finish() diff --git a/arkane/modelchemTest.py b/arkane/modelchemTest.py deleted file mode 100644 index 2c16f6b8b3..0000000000 --- a/arkane/modelchemTest.py +++ /dev/null @@ -1,308 +0,0 @@ -#!/usr/bin/env python3 - -############################################################################### -# # -# RMG - Reaction Mechanism Generator # -# # -# Copyright (c) 2002-2021 Prof. William H. Green (whgreen@mit.edu), # -# Prof. Richard H. West (r.west@neu.edu) and the RMG Team (rmg_dev@mit.edu) # -# # -# Permission is hereby granted, free of charge, to any person obtaining a # -# copy of this software and associated documentation files (the 'Software'), # -# to deal in the Software without restriction, including without limitation # -# the rights to use, copy, modify, merge, publish, distribute, sublicense, # -# and/or sell copies of the Software, and to permit persons to whom the # -# Software is furnished to do so, subject to the following conditions: # -# # -# The above copyright notice and this permission notice shall be included in # -# all copies or substantial portions of the Software. # -# # -# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # -# DEALINGS IN THE SOFTWARE. # -# # -############################################################################### - -""" -This script contains unit tests for the :mod:`arkane.modelchem` module. -""" - -import unittest -from dataclasses import FrozenInstanceError - -from arkane.modelchem import (LOT, LevelOfTheory, CompositeLevelOfTheory, - model_chem_to_lot, str_to_lot, get_software_id) - -# Instances for use in tests -FREQ = LevelOfTheory( - method='wB97X-D', - basis='def2-TZVP', - software='Gaussian 16', - args='very-tight' -) -ENERGY = LevelOfTheory( - method='DLPNO-CCSD(T)-F12', - basis='def2-TZVP', - software='Orca' -) -COMPOSITE = CompositeLevelOfTheory( - freq=FREQ, - energy=ENERGY -) - -# Representations corresponding to instances -FREQ_REPR = "LevelOfTheory(method='wb97xd',basis='def2tzvp',software='gaussian',args=('verytight',))" -ENERGY_REPR = "LevelOfTheory(method='dlpnoccsd(t)f12',basis='def2tzvp',software='orca')" -COMPOSITE_REPR = f"CompositeLevelOfTheory(freq={FREQ_REPR},energy={ENERGY_REPR})" - -# Dictionaries corresponding to instances -FREQ_DICT = { - 'class': 'LevelOfTheory', - 'method': 'wb97xd', - 'basis': 'def2tzvp', - 'software': 'gaussian', - 'args': ['verytight'] # This is a list instead of tuple because that's what YAML files expect -} -ENERGY_DICT = { - 'class': 'LevelOfTheory', - 'method': 'dlpnoccsd(t)f12', - 'basis': 'def2tzvp', - 'software': 'orca', -} -COMPOSITE_DICT = { - 'class': 'CompositeLevelOfTheory', - 'freq': FREQ_DICT, - 'energy': ENERGY_DICT -} - -# Model chemistries corresponding to instances -FREQ_MODELCHEM = 'wb97xd/def2tzvp' -ENERGY_MODELCHEM = 'dlpnoccsd(t)f12/def2tzvp' -COMPOSITE_MODELCHEM = f'{ENERGY_MODELCHEM}//{FREQ_MODELCHEM}' - - -class TestLevelOfTheory(unittest.TestCase): - """ - A class for testing that the LevelOfTheory class functions properly. - """ - - def test_attrs(self): - """ - Test that instance behaves correctly. - """ - self.assertEqual(FREQ.method, 'wb97xd') - self.assertEqual(FREQ.basis, 'def2tzvp') - self.assertEqual(FREQ.software, 'gaussian') - self.assertTupleEqual(FREQ.args, ('verytight',)) - with self.assertRaises(FrozenInstanceError): - FREQ.method = '' - - self.assertEqual(repr(FREQ), FREQ_REPR) - self.assertEqual(repr(ENERGY), ENERGY_REPR) - - with self.assertRaises(ValueError): - _ = LevelOfTheory(method=FREQ.method) - lot = LevelOfTheory(method=FREQ.method, software=FREQ.software) - self.assertIsNone(lot.basis) - self.assertIsNone(lot.auxiliary_basis) - self.assertIsNone(lot.cabs) - self.assertIsNone(lot.software_version) - self.assertIsNone(lot.solvent) - self.assertIsNone(lot.solvation_method) - self.assertIsNone(lot.args) - - self.assertIsInstance(FREQ, LOT) - - def test_comparison(self): - """ - Test comparisons between instances. - """ - self.assertIsInstance(hash(FREQ), int) - self.assertNotEqual(FREQ, ENERGY) - with self.assertRaises(TypeError): - _ = ENERGY > FREQ - - # Test args in different order - lot1 = LevelOfTheory('method', args=('arg1', 'arg2')) - lot2 = LevelOfTheory('method', args=('arg2', 'arg1')) - self.assertEqual(lot1, lot2) - - def test_simple(self): - """ - Test that simple level of theory can be obtained. - """ - lot = FREQ.simple() - self.assertIsNot(lot, FREQ) - self.assertEqual(lot.method, FREQ.method) - self.assertEqual(lot.basis, FREQ.basis) - self.assertEqual(lot.software, FREQ.software) - for attr, val in lot.__dict__.items(): - if attr not in {'method', 'basis', 'software'}: - self.assertIsNone(val) - - def test_to_model_chem(self): - """ - Test conversion to model chemistry. - """ - self.assertEqual(FREQ.to_model_chem(), FREQ_MODELCHEM) - self.assertEqual(ENERGY.to_model_chem(), ENERGY_MODELCHEM) - - lot = LevelOfTheory( - method='CBS-QB3', - software='g16' - ) - self.assertEqual(lot.to_model_chem(), 'cbsqb3') - - def test_update(self): - """ - Test updating attributes. - """ - lot = FREQ.update(software='Q-Chem') - self.assertIsNot(lot, FREQ) - self.assertEqual(lot.software, 'qchem') - with self.assertRaises(TypeError): - FREQ.update(test='test') - - def test_as_dict(self): - """ - Test conversion to dictionary. - """ - self.assertDictEqual(FREQ.as_dict(), FREQ_DICT) - self.assertDictEqual(ENERGY.as_dict(), ENERGY_DICT) - - -class TestCompositeLevelOfTheory(unittest.TestCase): - """ - A class for testing that the CompositeLevelOfTheory class functions properly. - """ - - def test_attrs(self): - """ - Test that instance behaves correctly. - """ - self.assertIs(COMPOSITE.freq, FREQ) - self.assertIs(COMPOSITE.energy, ENERGY) - self.assertEqual(repr(COMPOSITE), COMPOSITE_REPR) - with self.assertRaises(FrozenInstanceError): - COMPOSITE.energy = '' - - self.assertIsInstance(COMPOSITE, LOT) - - def test_comparison(self): - """ - Test comparisons between instances. - """ - other = CompositeLevelOfTheory(freq=ENERGY, energy=FREQ) - self.assertIsInstance(hash(COMPOSITE), int) - self.assertNotEqual(COMPOSITE, other) - with self.assertRaises(TypeError): - _ = COMPOSITE > other - - def test_simple(self): - """ - Test that simple level of theory can be obtained. - """ - lot = COMPOSITE.simple() - self.assertIsNot(lot, COMPOSITE) - self.assertEqual(lot.freq.method, COMPOSITE.freq.method) - self.assertEqual(lot.freq.basis, COMPOSITE.freq.basis) - self.assertEqual(lot.freq.software, COMPOSITE.freq.software) - self.assertEqual(lot.energy.method, COMPOSITE.energy.method) - self.assertEqual(lot.energy.basis, COMPOSITE.energy.basis) - for attr, val in lot.freq.__dict__.items(): - if attr not in {'method', 'basis', 'software'}: - self.assertIsNone(val) - for attr, val in lot.energy.__dict__.items(): - if attr not in {'method', 'basis'}: - self.assertIsNone(val) - - def test_to_model_chem(self): - """ - Test conversion to model chemistry. - """ - self.assertEqual(COMPOSITE.to_model_chem(), COMPOSITE_MODELCHEM) - - def test_as_dict(self): - """ - Test conversion to dictionary. - """ - self.assertDictEqual(COMPOSITE.as_dict(), COMPOSITE_DICT) - - -class TestFuncs(unittest.TestCase): - """ - A class for testing that the functions in the modelchem module work. - """ - - def test_model_chem_to_lot(self): - """ - Test model chemistry to quantum calculation settings conversion. - """ - self.assertEqual( - model_chem_to_lot(FREQ_MODELCHEM, software='gaussian', args='verytight'), - FREQ - ) - self.assertEqual( - model_chem_to_lot(FREQ_MODELCHEM, - freq_settings={'software': 'gaussian', 'args': 'verytight'}), - FREQ - ) - self.assertEqual( - model_chem_to_lot(FREQ_MODELCHEM, - freq_settings={'software': 'gaussian', 'args': 'verytight'}, - energy_settings={'unused setting': None}), - FREQ - ) - self.assertEqual( - model_chem_to_lot(ENERGY_MODELCHEM, energy_settings={'software': 'orca'}), - ENERGY - ) - self.assertEqual( - model_chem_to_lot(COMPOSITE_MODELCHEM, - freq_settings={'software': 'gaussian', 'args': 'verytight'}, - energy_settings={'software': 'orca'}), - COMPOSITE - ) - - def test_str_to_lot(self): - """ - Test key to quantum calculation settings conversion. - """ - self.assertEqual(str_to_lot(FREQ_REPR), FREQ) - self.assertEqual(str_to_lot(ENERGY_REPR), ENERGY) - self.assertEqual(str_to_lot(COMPOSITE_REPR), COMPOSITE) - - def test_get_software_id(self): - """ - Test standardized software identifiers. - """ - test_names = ['gaussian', 'Gaussian 09', 'g-16', 'Gau 03'] - for name in test_names: - self.assertEqual(get_software_id(name), 'gaussian') - - test_names = ['qchem', 'QChem', 'Q-Chem'] - for name in test_names: - self.assertEqual(get_software_id(name), 'qchem') - - test_names = ['molpro', 'Molpro', 'MOLPRO'] - for name in test_names: - self.assertEqual(get_software_id(name), 'molpro') - - test_names = ['orca', 'Orca', 'ORCA'] - for name in test_names: - self.assertEqual(get_software_id(name), 'orca') - - test_names = ['terachem', 'Terachem', 'TeraChem', 'Tera-Chem', 'Tera Chem'] - for name in test_names: - self.assertEqual(get_software_id(name), 'terachem') - - with self.assertRaises(ValueError): - get_software_id('g') - - -if __name__ == '__main__': - unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arkane/pdep.py b/arkane/pdep.py index 580892373b..dc99610a48 100644 --- a/arkane/pdep.py +++ b/arkane/pdep.py @@ -101,24 +101,41 @@ class PressureDependenceJob(object): RMG mode should be turned off by default except in RMG jobs. """ - def __init__(self, network, - Tmin=None, Tmax=None, Tcount=0, Tlist=None, - Pmin=None, Pmax=None, Pcount=0, Plist=None, - maximumGrainSize=None, minimumGrainCount=0, - method=None, interpolationModel=None, maximumAtoms=None, - activeKRotor=True, activeJRotor=True, rmgmode=False, sensitivity_conditions=None, - sensitivity_perturbation=0): - + def __init__( + self, + network, + Tmin=None, + Tmax=None, + Tcount=0, + Tlist=None, + Pmin=None, + Pmax=None, + Pcount=0, + Plist=None, + maximumGrainSize=None, + minimumGrainCount=0, + method=None, + interpolationModel=None, + maximumAtoms=None, + activeKRotor=True, + activeJRotor=True, + rmgmode=False, + sensitivity_conditions=None, + sensitivity_perturbation=0, + ): if network and network.products and len(network.products) > 0: if "simulation least squares" in method: - raise InputError("""The current simulation least squares implementation should not be + raise InputError( + """The current simulation least squares implementation should not be used with product channels. Please specify all bimolecular channels as - reactant channels.""") + reactant channels.""" + ) elif method == "chemically-significant eigenvalues georgievskii": - raise InputError("""The chemically-significant eigenvalues georgievskii method cannot be + raise InputError( + """The chemically-significant eigenvalues georgievskii method cannot be used with product channels. Please specify all bimolecular channels as - reactant channels.""") - + reactant channels.""" + ) self.network = network @@ -126,7 +143,7 @@ def __init__(self, network, self.Tmax = Tmax self.Tcount = Tcount if sensitivity_perturbation == 0: - self.sensitivity_perturbation = quantity.Quantity(2.0,'kcal/mol') + self.sensitivity_perturbation = quantity.Quantity(2.0, "kcal/mol") else: self.sensitivity_perturbation = quantity.Quantity(sensitivity_perturbation) @@ -166,8 +183,7 @@ def __init__(self, network, if sensitivity_conditions is not None: if not isinstance(sensitivity_conditions[0], list): sensitivity_conditions = [sensitivity_conditions] # allow `[T, P]` as conditions input - self.sensitivity_conditions = [[quantity.Quantity(condition[0]), quantity.Quantity(condition[1])] - for condition in sensitivity_conditions] + self.sensitivity_conditions = [[quantity.Quantity(condition[0]), quantity.Quantity(condition[1])] for condition in sensitivity_conditions] else: self.sensitivity_conditions = None @@ -262,23 +278,27 @@ def copy(self): rmgmode=self.rmgmode, ) - def execute(self, output_file, plot, file_format='pdf', print_summary=True): + def execute(self, output_file, plot, file_format="pdf", print_summary=True): """Execute a PressureDependenceJob""" for config in self.network.isomers + self.network.reactants + self.network.products: for spec in config.species: if spec.conformer.E0 is None: - raise AttributeError('species {0} is missing energy for its conformer'.format(spec.label)) + raise AttributeError("species {0} is missing energy for its conformer".format(spec.label)) # set transition state Energy if not set previously using same method as RMG pdep for reaction in self.network.path_reactions: transition_state = reaction.transition_state if transition_state.conformer and transition_state.conformer.E0 is None: - transition_state.conformer.E0 = (sum([spec.conformer.E0.value_si for spec in reaction.reactants]) - + reaction.kinetics.Ea.value_si, 'J/mol') - logging.info('Approximated transitions state E0 for reaction {3} from kinetics ' - 'A={0}, n={1}, Ea={2} J/mol'.format(reaction.kinetics.A.value_si, - reaction.kinetics.n.value_si, - reaction.kinetics.Ea.value_si, reaction.label)) + transition_state.conformer.E0 = ( + sum([spec.conformer.E0.value_si for spec in reaction.reactants]) + reaction.kinetics.Ea.value_si, + "J/mol", + ) + logging.info( + "Approximated transitions state E0 for reaction {3} from kinetics " + "A={0}, n={1}, Ea={2} J/mol".format( + reaction.kinetics.A.value_si, reaction.kinetics.n.value_si, reaction.kinetics.Ea.value_si, reaction.label + ) + ) if print_summary: self.network.log_summary() @@ -296,9 +316,9 @@ def execute(self, output_file, plot, file_format='pdf', print_summary=True): if plot: self.plot(os.path.dirname(output_file)) if self.sensitivity_conditions is not None: - logging.info('\n\nRunning sensitivity analysis...') + logging.info("\n\nRunning sensitivity analysis...") SensAnalysis(deepcopy(self), os.path.dirname(output_file), perturbation=self.sensitivity_perturbation) - logging.debug('Finished pdep job for reaction {0}.'.format(self.network.label)) + logging.debug("Finished pdep job for reaction {0}.".format(self.network.label)) logging.debug(repr(self.network)) def generate_T_list(self): @@ -315,9 +335,9 @@ def generate_T_list(self): Tmax = self.Tmax.value_si Tcount = self.Tcount if self.Tlist is None: - if self.interpolation_model[0].lower() == 'chebyshev': + if self.interpolation_model[0].lower() == "chebyshev": # Distribute temperatures on a Gauss-Chebyshev grid - Tlist = np.zeros(Tcount, np.float64) + Tlist = np.zeros(Tcount, float) for i in range(Tcount): T = -math.cos((2 * i + 1) * math.pi / (2 * self.Tcount)) T = 2.0 / ((1.0 / Tmax - 1.0 / Tmin) * T + 1.0 / Tmax + 1.0 / Tmin) @@ -335,25 +355,27 @@ def initialize(self): tunneling = reaction.transition_state.tunneling # throw descriptive error if tunneling not allowed if tunneling and reaction.transition_state.frequency is None and reaction.kinetics is not None: - raise ValueError("""Cannot apply tunneling for reaction {0} when inverse laplace is used. + raise ValueError( + """Cannot apply tunneling for reaction {0} when inverse laplace is used. Either remove tunnelling parameter or input transitionState - frequencies/quantum file""".format(reaction.label)) + frequencies/quantum file""".format( + reaction.label + ) + ) # add tunneling parameters if isinstance(tunneling, Wigner) and tunneling.frequency is None: tunneling.frequency = (reaction.transition_state.frequency.value_si, "cm^-1") elif isinstance(tunneling, Eckart) and tunneling.frequency is None: tunneling.frequency = (reaction.transition_state.frequency.value_si, "cm^-1") - tunneling.E0_reac = (sum([reactant.conformer.E0.value_si - for reactant in reaction.reactants]) * 0.001, "kJ/mol") + tunneling.E0_reac = (sum([reactant.conformer.E0.value_si for reactant in reaction.reactants]) * 0.001, "kJ/mol") tunneling.E0_TS = (reaction.transition_state.conformer.E0.value_si * 0.001, "kJ/mol") - tunneling.E0_prod = (sum([product.conformer.E0.value_si - for product in reaction.products]) * 0.001, "kJ/mol") + tunneling.E0_prod = (sum([product.conformer.E0.value_si for product in reaction.products]) * 0.001, "kJ/mol") elif tunneling is not None: if tunneling.frequency is not None: # Frequency was given by the user pass else: - raise ValueError('Unknown tunneling model {0!r} for path reaction {1}.'.format(tunneling, reaction)) + raise ValueError("Unknown tunneling model {0!r} for path reaction {1}.".format(tunneling, reaction)) maximum_grain_size = self.maximum_grain_size.value_si if self.maximum_grain_size is not None else 0.0 @@ -387,9 +409,9 @@ def generate_P_list(self): Pcount = self.Pcount if self.Plist is not None: pass - elif self.interpolation_model[0].lower() == 'chebyshev': + elif self.interpolation_model[0].lower() == "chebyshev": # Distribute pressures on a Gauss-Chebyshev grid - Plist = np.zeros(Pcount, np.float64) + Plist = np.zeros(Pcount, float) for i in range(Pcount): P = -math.cos((2 * i + 1) * math.pi / (2 * self.Pcount)) P = 10 ** (0.5 * ((math.log10(Pmax) - math.log10(Pmin)) * P + math.log10(Pmax) + math.log10(Pmin))) @@ -419,14 +441,13 @@ def fit_interpolation_models(self): for reac in range(n_reac): if reac == prod: continue - reaction = Reaction(reactants=configurations[reac].species, - products=configurations[prod].species) + reaction = Reaction(reactants=configurations[reac].species, products=configurations[prod].species) kdata = self.K[:, :, prod, reac].copy() order = len(reaction.reactants) kdata *= 1e6 ** (order - 1) - k_units = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[order] - logging.debug('Fitting master eqn data to kinetics for reaction {}.'.format(reaction)) + k_units = {1: "s^-1", 2: "cm^3/(mol*s)", 3: "cm^6/(mol^2*s)"}[order] + logging.debug("Fitting master eqn data to kinetics for reaction {}.".format(reaction)) reaction.kinetics = self.fit_interpolation_model(Tdata, Pdata, kdata, k_units) self.network.net_reactions.append(reaction) @@ -440,21 +461,21 @@ def fit_interpolation_model(self, Tdata, Pdata, kdata, k_units): model = self.interpolation_model[0].lower() - if model == 'chebyshev': - kinetics = Chebyshev().fit_to_data(Tdata, Pdata, kdata, k_units, - self.interpolation_model[1], self.interpolation_model[2], - Tmin, Tmax, Pmin, Pmax) - elif model == 'pdeparrhenius': + if model == "chebyshev": + kinetics = Chebyshev().fit_to_data( + Tdata, Pdata, kdata, k_units, self.interpolation_model[1], self.interpolation_model[2], Tmin, Tmax, Pmin, Pmax + ) + elif model == "pdeparrhenius": kinetics = PDepArrhenius().fit_to_data(Tdata, Pdata, kdata, k_units) else: - raise PressureDependenceError('Invalid interpolation model {0!r}.'.format(self.interpolation_model[0])) + raise PressureDependenceError("Invalid interpolation model {0!r}.".format(self.interpolation_model[0])) return kinetics def save(self, output_file): """Save the output of a pressure dependent job""" - logging.info('Saving pressure dependence results for network {0}...'.format(self.network.label)) - f = open(output_file, 'a') - f_chemkin = open(os.path.join(os.path.dirname(output_file), 'chem.inp'), 'a') + logging.info("Saving pressure dependence results for network {0}...".format(self.network.label)) + f = open(output_file, "a") + f_chemkin = open(os.path.join(os.path.dirname(output_file), "chem.inp"), "a") n_reac = self.network.n_isom + self.network.n_reac n_prod = n_reac + self.network.n_prod @@ -489,8 +510,7 @@ def save(self, output_file): reaction = self.network.net_reactions[count] count += 1 # make sure we aren't double counting any reactions - if not any([reaction.is_isomorphic(other_rxn, check_only_label=True) - for other_rxn in printed_reactions]): + if not any([reaction.is_isomorphic(other_rxn, check_only_label=True) for other_rxn in printed_reactions]): duplicate = False # add reaction to printed reaction printed_reactions.append(reaction) @@ -500,43 +520,43 @@ def save(self, output_file): # write chemkin output. string = write_kinetics_entry(reaction, species_list=None, verbose=False, commented=duplicate) - f_chemkin.write('{0}\n'.format(string)) + f_chemkin.write("{0}\n".format(string)) # write to 'output.py' kdata = self.K[:, :, prod, reac].copy() order = len(reaction.reactants) kdata *= 1e6 ** (order - 1) - k_units = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[order] - - f.write('# =========== ') - f.write('=========== ' * Pcount) - f.write('\n') - f.write('# T / P ') - f.write(' '.join(['{0:11.3e}'.format(P * 1e-5) for P in Plist])) - f.write('\n') - f.write('# =========== ') - f.write('=========== ' * Pcount) - f.write('\n') + k_units = {1: "s^-1", 2: "cm^3/(mol*s)", 3: "cm^6/(mol^2*s)"}[order] + + f.write("# =========== ") + f.write("=========== " * Pcount) + f.write("\n") + f.write("# T / P ") + f.write(" ".join(["{0:11.3e}".format(P * 1e-5) for P in Plist])) + f.write("\n") + f.write("# =========== ") + f.write("=========== " * Pcount) + f.write("\n") for t in range(Tcount): - f.write('# {0:11g}'.format(Tlist[t])) + f.write("# {0:11g}".format(Tlist[t])) for p in range(Pcount): - f.write(' {0:11.3e}'.format(kdata[t, p])) - f.write('\n') + f.write(" {0:11.3e}".format(kdata[t, p])) + f.write("\n") - f.write('# =========== ') - f.write('=========== ' * Pcount) - f.write('\n') + f.write("# =========== ") + f.write("=========== " * Pcount) + f.write("\n") - string = 'pdepreaction(reactants={0!r}, products={1!r}, kinetics={2!r})'.format( + string = "pdepreaction(reactants={0!r}, products={1!r}, kinetics={2!r})".format( [reactant.label for reactant in reaction.reactants], [product.label for product in reaction.products], reaction.kinetics, ) - pdep_function = '{0}\n\n'.format(prettify(string)) + pdep_function = "{0}\n\n".format(prettify(string)) if duplicate: # add comments to the start of the string - pdep_function = '# ' + pdep_function.replace('\n', '\n# ') + pdep_function = "# " + pdep_function.replace("\n", "\n# ") f.write(pdep_function) f.close() @@ -551,6 +571,7 @@ def plot(self, output_directory): return import matplotlib.cm + cm = matplotlib.cm.jet n_reac = self.network.n_isom + self.network.n_reac @@ -570,10 +591,10 @@ def plot(self, output_directory): reaction = self.network.net_reactions[count] count += 1 - reaction_str = '{0} {1} {2}'.format( - ' + '.join([reactant.label for reactant in reaction.reactants]), - '<=>' if prod < n_reac else '-->', - ' + '.join([product.label for product in reaction.products]), + reaction_str = "{0} {1} {2}".format( + " + ".join([reactant.label for reactant in reaction.reactants]), + "<=>" if prod < n_reac else "-->", + " + ".join([product.label for product in reaction.products]), ) fig = plt.figure(figsize=(10, 6)) @@ -588,37 +609,41 @@ def plot(self, output_directory): order = len(reaction.reactants) K *= 1e6 ** (order - 1) K2 *= 1e6 ** (order - 1) - k_units = {1: 's^-1', 2: 'cm^3/(mol*s)', 3: 'cm^6/(mol^2*s)'}[order] + k_units = {1: "s^-1", 2: "cm^3/(mol*s)", 3: "cm^6/(mol^2*s)"}[order] plt.subplot(1, 2, 1) for p in range(Pcount): - plt.semilogy(1000.0 / Tlist, K[:, p], color=cm(1. * p / (Pcount - 1)), marker='o', linestyle='', - label=str('%.2e' % (Plist[p] / 1e+5)) + ' bar') + plt.semilogy( + 1000.0 / Tlist, + K[:, p], + color=cm(1.0 * p / (Pcount - 1)), + marker="o", + linestyle="", + label=str("%.2e" % (Plist[p] / 1e5)) + " bar", + ) if reaction.kinetics is not None: - plt.semilogy(1000.0 / Tlist, K2[:, p], color=cm(1. * p / (Pcount - 1)), marker='', - linestyle='-') - plt.xlabel('1000 / Temperature (K^-1)') - plt.ylabel('Rate coefficient ({0})'.format(k_units)) + plt.semilogy(1000.0 / Tlist, K2[:, p], color=cm(1.0 * p / (Pcount - 1)), marker="", linestyle="-") + plt.xlabel("1000 / Temperature (K^-1)") + plt.ylabel("Rate coefficient ({0})".format(k_units)) plt.title(reaction_str) plt.legend() plt.subplot(1, 2, 2) for t in range(Tcount): - plt.loglog(Plist * 1e-5, K[t, :], color=cm(1. * t / (Tcount - 1)), marker='o', linestyle='', - label=str('%.0d' % Tlist[t]) + ' K') - plt.loglog(Plist * 1e-5, K2[t, :], color=cm(1. * t / (Tcount - 1)), marker='', linestyle='-') - plt.xlabel('Pressure (bar)') - plt.ylabel('Rate coefficient ({0})'.format(k_units)) + plt.loglog(Plist * 1e-5, K[t, :], color=cm(1.0 * t / (Tcount - 1)), marker="o", linestyle="", label=str("%.0d" % Tlist[t]) + " K") + plt.loglog(Plist * 1e-5, K2[t, :], color=cm(1.0 * t / (Tcount - 1)), marker="", linestyle="-") + plt.xlabel("Pressure (bar)") + plt.ylabel("Rate coefficient ({0})".format(k_units)) plt.title(reaction_str) plt.legend() fig.subplots_adjust(left=0.10, bottom=0.13, right=0.95, top=0.92, wspace=0.3, hspace=0.3) - if not os.path.exists(os.path.join(output_directory, 'plots', '')): - os.mkdir(os.path.join(output_directory, 'plots', '')) - plt.savefig(os.path.join(output_directory, 'plots', 'kinetics_{0:d}.pdf'.format(count))) + if not os.path.exists(os.path.join(output_directory, "plots", "")): + os.mkdir(os.path.join(output_directory, "plots", "")) + plt.savefig(os.path.join(output_directory, "plots", "kinetics_{0:d}.pdf".format(count))) plt.close() - def draw(self, output_directory, file_format='pdf'): + def draw(self, output_directory, file_format="pdf"): """ Generate a PDF drawing of the pressure-dependent reaction network. This requires that Cairo and its Python wrapper be available; if not, @@ -639,7 +664,7 @@ def draw(self, output_directory, file_format='pdf'): from rmgpy.pdep.draw import NetworkDrawer - path = os.path.join(output_directory, 'network.' + file_format) + path = os.path.join(output_directory, "network." + file_format) NetworkDrawer().draw(self.network, file_format=file_format, path=path) @@ -652,117 +677,117 @@ def save_input_file(self, path): # Add labels for species, reactions, transition states that don't have them for i, spec in enumerate(species_list): if not spec.label: - spec.label = 'species{0:d}'.format(i + 1) + spec.label = "species{0:d}".format(i + 1) for i, rxn in enumerate(self.network.path_reactions): if not rxn.label: - rxn.label = 'reaction{0:d}'.format(i + 1) + rxn.label = "reaction{0:d}".format(i + 1) if not rxn.transition_state.label: - rxn.transition_state.label = 'TS{0:d}'.format(i + 1) + rxn.transition_state.label = "TS{0:d}".format(i + 1) if not self.network.label: - self.network.label = 'network' + self.network.label = "network" - with open(path, 'w') as f: + with open(path, "w") as f: # Write species for spec in species_list: - f.write('species(\n') - f.write(' label = {0!r},\n'.format(str(spec))) + f.write("species(\n") + f.write(" label = {0!r},\n".format(str(spec))) if len(spec.molecule) > 0: f.write(f' structure = adjacencyList("""{spec.molecule[0].to_adjacency_list()}"""),\n') if spec.conformer is not None: if spec.conformer.E0 is not None: - f.write(' E0 = {0!r},\n'.format(spec.conformer.E0)) + f.write(" E0 = {0!r},\n".format(spec.conformer.E0)) if len(spec.conformer.modes) > 0: - f.write(' modes = [\n') + f.write(" modes = [\n") for mode in spec.conformer.modes: - f.write(' {0!r},\n'.format(mode)) - f.write(' ],\n') - f.write(' spinMultiplicity = {0:d},\n'.format(spec.conformer.spin_multiplicity)) - f.write(' opticalIsomers = {0:d},\n'.format(spec.conformer.optical_isomers)) + f.write(" {0!r},\n".format(mode)) + f.write(" ],\n") + f.write(" spinMultiplicity = {0:d},\n".format(spec.conformer.spin_multiplicity)) + f.write(" opticalIsomers = {0:d},\n".format(spec.conformer.optical_isomers)) if spec.molecular_weight is not None: - f.write(' molecularWeight = {0!r},\n'.format(spec.molecular_weight)) + f.write(" molecularWeight = {0!r},\n".format(spec.molecular_weight)) if spec.transport_data is not None: - f.write(' collisionModel = {0!r},\n'.format(spec.transport_data)) + f.write(" collisionModel = {0!r},\n".format(spec.transport_data)) if spec.energy_transfer_model is not None: - f.write(' energyTransferModel = {0!r},\n'.format(spec.energy_transfer_model)) + f.write(" energyTransferModel = {0!r},\n".format(spec.energy_transfer_model)) if spec.thermo is not None: - f.write(' thermo = {0!r},\n'.format(spec.thermo)) - f.write(')\n\n') + f.write(" thermo = {0!r},\n".format(spec.thermo)) + f.write(")\n\n") # Write transition states for rxn in self.network.path_reactions: ts = rxn.transition_state - f.write('transitionState(\n') - f.write(' label = {0!r},\n'.format(ts.label)) + f.write("transitionState(\n") + f.write(" label = {0!r},\n".format(ts.label)) if ts.conformer is not None: if ts.conformer.E0 is not None: - f.write(' E0 = {0!r},\n'.format(ts.conformer.E0)) + f.write(" E0 = {0!r},\n".format(ts.conformer.E0)) if len(ts.conformer.modes) > 0: - f.write(' modes = [\n') + f.write(" modes = [\n") for mode in ts.conformer.modes: - f.write(' {0!r},\n'.format(mode)) - f.write(' ],\n') - f.write(' spinMultiplicity = {0:d},\n'.format(ts.conformer.spin_multiplicity)) - f.write(' opticalIsomers = {0:d},\n'.format(ts.conformer.optical_isomers)) + f.write(" {0!r},\n".format(mode)) + f.write(" ],\n") + f.write(" spinMultiplicity = {0:d},\n".format(ts.conformer.spin_multiplicity)) + f.write(" opticalIsomers = {0:d},\n".format(ts.conformer.optical_isomers)) if ts.frequency is not None: - f.write(' frequency = {0!r},\n'.format(ts.frequency)) - f.write(')\n\n') + f.write(" frequency = {0!r},\n".format(ts.frequency)) + f.write(")\n\n") # Write reactions for rxn in self.network.path_reactions: ts = rxn.transition_state - f.write('reaction(\n') - f.write(' label = {0!r},\n'.format(rxn.label)) - f.write(' reactants = [{0}],\n'.format(', '.join([repr(str(spec)) for spec in rxn.reactants]))) - f.write(' products = [{0}],\n'.format(', '.join([repr(str(spec)) for spec in rxn.products]))) - f.write(' transitionState = {0!r},\n'.format(rxn.transition_state.label)) + f.write("reaction(\n") + f.write(" label = {0!r},\n".format(rxn.label)) + f.write(" reactants = [{0}],\n".format(", ".join([repr(str(spec)) for spec in rxn.reactants]))) + f.write(" products = [{0}],\n".format(", ".join([repr(str(spec)) for spec in rxn.products]))) + f.write(" transitionState = {0!r},\n".format(rxn.transition_state.label)) if rxn.kinetics is not None: - if isinstance(rxn, LibraryReaction) and 'Reaction library:' not in rxn.kinetics.comment: - rxn.kinetics.comment += 'Reaction library: {0!r}'.format(rxn.library) + if isinstance(rxn, LibraryReaction) and "Reaction library:" not in rxn.kinetics.comment: + rxn.kinetics.comment += "Reaction library: {0!r}".format(rxn.library) if rxn.network_kinetics is not None: - f.write(' kinetics = {0!r},\n'.format(rxn.network_kinetics)) + f.write(" kinetics = {0!r},\n".format(rxn.network_kinetics)) else: - f.write(' kinetics = {0!r},\n'.format(rxn.kinetics)) + f.write(" kinetics = {0!r},\n".format(rxn.kinetics)) if ts.tunneling is not None: - f.write(' tunneling = {0!r},\n'.format(ts.tunneling.__class__.__name__)) - f.write(')\n\n') + f.write(" tunneling = {0!r},\n".format(ts.tunneling.__class__.__name__)) + f.write(")\n\n") # Write network - f.write('network(\n') - f.write(' label = {0!r},\n'.format(self.network.label)) - f.write(' isomers = [\n') + f.write("network(\n") + f.write(" label = {0!r},\n".format(self.network.label)) + f.write(" isomers = [\n") for isomer in self.network.isomers: - f.write(' {0!r},\n'.format(str(isomer.species[0]))) - f.write(' ],\n') - f.write(' reactants = [\n') + f.write(" {0!r},\n".format(str(isomer.species[0]))) + f.write(" ],\n") + f.write(" reactants = [\n") for reactants in self.network.reactants: - f.write(' ({0}),\n'.format(', '.join([repr(str(spec)) for spec in reactants.species]))) - f.write(' ],\n') - f.write(' bathGas = {\n') + f.write(" ({0}),\n".format(", ".join([repr(str(spec)) for spec in reactants.species]))) + f.write(" ],\n") + f.write(" bathGas = {\n") for spec, frac in self.network.bath_gas.items(): - f.write(' {0!r}: {1:g},\n'.format(str(spec), frac)) - f.write(' },\n') - f.write(')\n\n') + f.write(" {0!r}: {1:g},\n".format(str(spec), frac)) + f.write(" },\n") + f.write(")\n\n") # Write pressure dependence - f.write('pressureDependence(\n') - f.write(' label = {0!r},\n'.format(self.network.label)) - f.write(' Tmin = {0!r},\n'.format(self.Tmin)) - f.write(' Tmax = {0!r},\n'.format(self.Tmax)) - f.write(' Tcount = {0:d},\n'.format(self.Tcount)) - f.write(' Tlist = {0!r},\n'.format(self.Tlist)) - f.write(' Pmin = {0!r},\n'.format(self.Pmin)) - f.write(' Pmax = {0!r},\n'.format(self.Pmax)) - f.write(' Pcount = {0:d},\n'.format(self.Pcount)) - f.write(' Plist = {0!r},\n'.format(self.Plist)) + f.write("pressureDependence(\n") + f.write(" label = {0!r},\n".format(self.network.label)) + f.write(" Tmin = {0!r},\n".format(self.Tmin)) + f.write(" Tmax = {0!r},\n".format(self.Tmax)) + f.write(" Tcount = {0:d},\n".format(self.Tcount)) + f.write(" Tlist = {0!r},\n".format(self.Tlist)) + f.write(" Pmin = {0!r},\n".format(self.Pmin)) + f.write(" Pmax = {0!r},\n".format(self.Pmax)) + f.write(" Pcount = {0:d},\n".format(self.Pcount)) + f.write(" Plist = {0!r},\n".format(self.Plist)) if self.maximum_grain_size is not None: - f.write(' maximumGrainSize = {0!r},\n'.format(self.maximum_grain_size)) + f.write(" maximumGrainSize = {0!r},\n".format(self.maximum_grain_size)) if self.minimum_grain_count != 0: - f.write(' minimumGrainCount = {0:d},\n'.format(self.minimum_grain_count)) - f.write(' method = {0!r},\n'.format(self.method)) + f.write(" minimumGrainCount = {0:d},\n".format(self.minimum_grain_count)) + f.write(" method = {0!r},\n".format(self.method)) if self.interpolation_model is not None: - f.write(' interpolationModel = {0!r},\n'.format(self.interpolation_model)) - f.write(' activeKRotor = {0!r},\n'.format(self.active_k_rotor)) - f.write(' activeJRotor = {0!r},\n'.format(self.active_j_rotor)) + f.write(" interpolationModel = {0!r},\n".format(self.interpolation_model)) + f.write(" activeKRotor = {0!r},\n".format(self.active_k_rotor)) + f.write(" activeJRotor = {0!r},\n".format(self.active_j_rotor)) if self.rmgmode: - f.write(' rmgmode = {0!r},\n'.format(self.rmgmode)) - f.write(')\n\n') + f.write(" rmgmode = {0!r},\n".format(self.rmgmode)) + f.write(")\n\n") diff --git a/arkane/statmechTest.py b/arkane/statmechTest.py deleted file mode 100644 index 35daffcc1c..0000000000 --- a/arkane/statmechTest.py +++ /dev/null @@ -1,385 +0,0 @@ -#!/usr/bin/env python3 - -############################################################################### -# # -# RMG - Reaction Mechanism Generator # -# # -# Copyright (c) 2002-2021 Prof. William H. Green (whgreen@mit.edu), # -# Prof. Richard H. West (r.west@neu.edu) and the RMG Team (rmg_dev@mit.edu) # -# # -# Permission is hereby granted, free of charge, to any person obtaining a # -# copy of this software and associated documentation files (the 'Software'), # -# to deal in the Software without restriction, including without limitation # -# the rights to use, copy, modify, merge, publish, distribute, sublicense, # -# and/or sell copies of the Software, and to permit persons to whom the # -# Software is furnished to do so, subject to the following conditions: # -# # -# The above copyright notice and this permission notice shall be included in # -# all copies or substantial portions of the Software. # -# # -# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # -# DEALINGS IN THE SOFTWARE. # -# # -############################################################################### - -""" -This module contains unit tests of the :mod:`arkane.statmech` module. -""" - -import os -import unittest - -import numpy as np - -from rmgpy.species import Species -from rmgpy.exceptions import InputError - -from arkane import Arkane -from arkane.ess.qchem import QChemLog -from arkane.modelchem import LevelOfTheory -from arkane.statmech import ScanLog, StatMechJob, determine_rotor_symmetry, is_linear - -################################################################################ - - -class TestStatmech(unittest.TestCase): - """ - Contains unit tests of the StatmechJob class. - """ - - @classmethod - def setUp(cls): - """A method that is run before each unit test in this class""" - arkane = Arkane() - cls.job_list = arkane.load_input_file(os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'data', 'Benzyl', 'input.py')) - - def test_gaussian_log_file_error(self): - """Test that the proper error is raised if gaussian geometry and frequency file paths are not the same""" - job = self.job_list[-2] - self.assertTrue(isinstance(job, StatMechJob)) - with self.assertRaises(InputError): - job.load() - - def test_rotor_symmetry_determination(self): - """ - Test that the correct symmetry number is determined for rotor potential scans. - """ - path1 = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', 'NCC_NRotor.out') - path2 = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', 'NCC_CRotor.out') - scan_log1 = QChemLog(path1) - scan_log2 = QChemLog(path2) - v_list1, angle = scan_log1.load_scan_energies() - v_list2, angle = scan_log2.load_scan_energies() - symmetry1 = determine_rotor_symmetry(energies=v_list1, label='NCC', pivots=[]) - symmetry2 = determine_rotor_symmetry(energies=v_list2, label='NCC', pivots=[]) - self.assertEqual(symmetry1, 1) - self.assertEqual(symmetry2, 3) - - def test_is_linear(self): - """Test that we can determine the linearity of a molecule from it's coordinates""" - xyz1 = np.array([ - [0.000000, 0.000000, 0.000000], - [0.000000, 0.000000, 1.159076], - [0.000000, 0.000000, -1.159076]]) # a trivial case - xyz2 = np.array([ - [-0.06618943, -0.12360663, -0.07631983], - [-0.79539707, 0.86755487, 1.02675668], - [-0.68919931, 0.25421823, -1.34830853], - [0.01546439, -1.54297548, 0.44580391], - [1.94428095, 0.40772394, 1.03719428], - [2.20318015, -0.14715186, -0.64755729], - [1.59252246, 1.51178950, -0.33908352], - [-0.87856890, -2.02453514, 0.38494433], - [-1.34135876, 1.49608206, 0.53295071]]) # a non-linear multi-atom molecule - xyz3 = np.array([ - [0.0000000000, 0.0000000000, 0.3146069129], - [-1.0906813653, 0.0000000000, -0.1376405244], - [1.0906813653, 0.0000000000, -0.1376405244]]) # NO2, a non-linear 3-atom molecule - xyz4 = np.array([ - [0.0000000000, 0.0000000000, 0.1413439534], - [-0.8031792912, 0.0000000000, -0.4947038368], - [0.8031792912, 0.0000000000, -0.4947038368]]) # NH2, a non-linear 3-atom molecule - xyz5 = np.array([ - [-0.5417345330, 0.8208150346, 0.0000000000], - [0.9206183692, 1.6432038228, 0.0000000000], - [-1.2739176462, 1.9692549926, 0.0000000000]]) # HSO, a non-linear 3-atom molecule - xyz6 = np.array([ - [1.18784533, 0.98526702, 0.00000000], - [0.04124533, 0.98526702, 0.00000000], - [-1.02875467, 0.98526702, 0.00000000]]) # HCN, a linear 3-atom molecule - xyz7 = np.array([ - [-4.02394116, 0.56169428, 0.00000000], - [-5.09394116, 0.56169428, 0.00000000], - [-2.82274116, 0.56169428, 0.00000000], - [-1.75274116, 0.56169428, 0.00000000]]) # C2H2, a linear 4-atom molecule - xyz8 = np.array([ - [-1.02600933, 2.12845307, 0.00000000], - [-0.77966935, 0.95278385, 0.00000000], - [-1.23666197, 3.17751246, 0.00000000], - [-0.56023545, -0.09447399, 0.00000000]]) # C2H2, just 0.5 degree off from linearity, so NOT linear - xyz9 = np.array([ - [-1.1998, 0.1610, 0.0275], - [-1.4021, 0.6223, -0.8489], - [-1.48302, 0.80682, -1.19946]]) # just 3 points in space on a straight line (not a physical molecule) - xyz10 = np.array([ - [-1.1998, 0.1610, 0.0275]]) # mono-atomic species, non-linear - xyz11 = np.array([ - [1.06026500, -0.07706800, 0.03372800], - [3.37340700, -0.07706800, 0.03372800], - [2.21683600, -0.07706800, 0.03372800]]) # CO2 at wb97xd/6-311+g(d,p), linear - xyz12 = np.array([ - [1.05503600, -0.00335000, 0.09823600], - [2.42816800, -0.00335000, 0.09823600], - [-0.14726400, -0.00335000, 0.09823600], - [3.63046800, -0.00335000, 0.09823600], - [-1.21103500, -0.00335000, 0.09823600], - [4.69423900, -0.00335000, 0.09823600]]) # C#CC#C at wb97xd/6-311+g(d,p), linear - - self.assertTrue(is_linear(xyz1)) - self.assertTrue(is_linear(xyz6)) - self.assertTrue(is_linear(xyz7)) - self.assertTrue(is_linear(xyz9)) - self.assertTrue(is_linear(xyz11)) - self.assertTrue(is_linear(xyz12)) - self.assertFalse(is_linear(xyz2)) - self.assertFalse(is_linear(xyz3)) - self.assertFalse(is_linear(xyz4)) - self.assertFalse(is_linear(xyz5)) - self.assertFalse(is_linear(xyz8)) - self.assertFalse(is_linear(xyz10)) - - def test_specifying_absolute_file_paths(self): - """Test specifying absolute file paths of statmech files""" - h2o2_input = """#!/usr/bin/env python -# -*- coding: utf-8 -*- - -bonds = {{'H-O': 2, 'O-O': 1}} - -externalSymmetry = 2 - -spinMultiplicity = 1 - -opticalIsomers = 1 - -energy = {{'b3lyp/6-311+g(3df,2p)': Log('{energy}')}} - -geometry = Log('{freq}') - -frequencies = Log('{freq}') - -rotors = [HinderedRotor(scanLog=Log('{scan}'), pivots=[1, 2], top=[1, 3], symmetry=1, fit='fourier')] - -""" - abs_arkane_path = os.path.abspath(os.path.dirname(__file__)) # this is the absolute path to `.../RMG-Py/arkane` - energy_path = os.path.join('arkane', 'data', 'H2O2', 'sp_a19032.out') - freq_path = os.path.join('arkane', 'data', 'H2O2', 'freq_a19031.out') - scan_path = os.path.join('arkane', 'data', 'H2O2', 'scan_a19034.out') - h2o2_input = h2o2_input.format(energy=energy_path, freq=freq_path, scan=scan_path) - h2o2_path = os.path.join(abs_arkane_path, 'data', 'H2O2', 'H2O2.py') - if not os.path.exists(os.path.dirname(h2o2_path)): - os.makedirs(os.path.dirname(h2o2_path)) - with open(h2o2_path, 'w') as f: - f.write(h2o2_input) - h2o2 = Species(label='H2O2', smiles='OO') - self.assertIsNone(h2o2.conformer) - statmech_job = StatMechJob(species=h2o2, path=h2o2_path) - statmech_job.level_of_theory = LevelOfTheory('b3lyp', '6-311+g(3df,2p)') - statmech_job.load(pdep=False, plot=False) - self.assertAlmostEqual(h2o2.conformer.E0.value_si, -146031.49933673252) - os.remove(h2o2_path) - - def test_hinder_rotor_from_1d_array(self): - """Test assigning hindered rotor 1D PES profile directly to HinderedRotor1DArray""" - h2o2_input = """#!/usr/bin/env python -# -*- coding: utf-8 -*- - -bonds = {{'H-O': 2, 'O-O': 1}} - -externalSymmetry = 2 - -spinMultiplicity = 1 - -opticalIsomers = 1 - -energy = {{'b3lyp/6-311+g(3df,2p)': Log('{energy}')}} - -geometry = Log('{freq}') - -frequencies = Log('{freq}') - -rotors = [HinderedRotor1DArray( - angles=[0. , 0.17453293, 0.34906585, 0.52359878, 0.6981317 , - 0.87266463, 1.04719755, 1.22173048, 1.3962634 , 1.57079633, - 1.74532925, 1.91986218, 2.0943951 , 2.26892803, 2.44346095, - 2.61799388, 2.7925268 , 2.96705973, 3.14159265, 3.31612558, - 3.4906585 , 3.66519143, 3.83972435, 4.01425728, 4.1887902 , - 4.36332313, 4.53785606, 4.71238898, 4.88692191, 5.06145483, - 5.23598776, 5.41052068, 5.58505361, 5.75958653, 5.93411946, - 6.10865238, 6.28318531], - energies=[0.00000000e+00, 3.09449290e+02, 1.07459871e+03, 2.05925305e+03, - 3.02877926e+03, 3.79724994e+03, 4.23486826e+03, 4.26190303e+03, - 3.88196432e+03, 3.15173930e+03, 2.20016363e+03, 1.20431941e+03, - 3.94499732e+02, 7.23850312e+00, 2.77854025e+02, 1.40711827e+03, - 3.50375319e+03, 6.57899330e+03, 1.05208190e+04, 1.50847596e+04, - 1.99269611e+04, 2.46164740e+04, 2.86972097e+04, 3.17430074e+04, - 3.34148312e+04, 3.35267510e+04, 3.20643922e+04, 2.91936786e+04, - 2.52325029e+04, 2.06007483e+04, 1.57531541e+04, 1.11268684e+04, - 7.08120679e+03, 3.87554760e+03, 1.63995547e+03, 3.80256396e+02, - 6.14367036e-01], - pivots=[1, 2], top=[1, 3], symmetry=1, fit='fourier')] -""" - angles = np.array([0. , 0.17453293, 0.34906585, 0.52359878, 0.6981317 , - 0.87266463, 1.04719755, 1.22173048, 1.3962634 , 1.57079633, - 1.74532925, 1.91986218, 2.0943951 , 2.26892803, 2.44346095, - 2.61799388, 2.7925268 , 2.96705973, 3.14159265, 3.31612558, - 3.4906585 , 3.66519143, 3.83972435, 4.01425728, 4.1887902 , - 4.36332313, 4.53785606, 4.71238898, 4.88692191, 5.06145483, - 5.23598776, 5.41052068, 5.58505361, 5.75958653, 5.93411946, - 6.10865238, 6.28318531]) - energies = np.array([0.00000000e+00, 3.09449290e+02, 1.07459871e+03, 2.05925305e+03, - 3.02877926e+03, 3.79724994e+03, 4.23486826e+03, 4.26190303e+03, - 3.88196432e+03, 3.15173930e+03, 2.20016363e+03, 1.20431941e+03, - 3.94499732e+02, 7.23850312e+00, 2.77854025e+02, 1.40711827e+03, - 3.50375319e+03, 6.57899330e+03, 1.05208190e+04, 1.50847596e+04, - 1.99269611e+04, 2.46164740e+04, 2.86972097e+04, 3.17430074e+04, - 3.34148312e+04, 3.35267510e+04, 3.20643922e+04, 2.91936786e+04, - 2.52325029e+04, 2.06007483e+04, 1.57531541e+04, 1.11268684e+04, - 7.08120679e+03, 3.87554760e+03, 1.63995547e+03, 3.80256396e+02, - 6.14367036e-01]) - abs_arkane_path = os.path.abspath(os.path.dirname(__file__)) # this is the absolute path to `.../RMG-Py/arkane` - energy_path = os.path.join('arkane', 'data', 'H2O2', 'sp_a19032.out') - freq_path = os.path.join('arkane', 'data', 'H2O2', 'freq_a19031.out') - h2o2_input = h2o2_input.format(energy=energy_path, freq=freq_path, angles=angles, energies=energies) - h2o2_path = os.path.join(abs_arkane_path, 'data', 'H2O2', 'H2O2_PES.py') - os.makedirs(os.path.dirname(h2o2_path), exist_ok=True) - with open(h2o2_path, 'w') as f: - f.write(h2o2_input) - h2o2 = Species(label='H2O2', smiles='OO') - self.assertIsNone(h2o2.conformer) - statmech_job = StatMechJob(species=h2o2, path=h2o2_path) - statmech_job.level_of_theory = LevelOfTheory('b3lyp', '6-311+g(3df,2p)') - statmech_job.load(pdep=False, plot=False) - self.assertEqual(len(statmech_job.raw_hindered_rotor_data), 1) - self.assertEqual(statmech_job.raw_hindered_rotor_data[0][2], 1) - self.assertTrue(np.allclose(statmech_job.raw_hindered_rotor_data[0][3], angles, atol=1e-6)) - self.assertTrue(np.allclose(statmech_job.raw_hindered_rotor_data[0][4], energies, atol=1e-6)) - self.assertAlmostEqual(h2o2.conformer.E0.value_si, -146031.49933673252) - os.remove(h2o2_path) - - def test_scanlog_class(self): - """ - Test scanlog works for various input format and returns the correct PES profiles. - """ - angles = np.array([0. , 0.17453293, 0.34906585, 0.52359878, 0.6981317 , - 0.87266463, 1.04719755, 1.22173048, 1.3962634 , 1.57079633, - 1.74532925, 1.91986218, 2.0943951 , 2.26892803, 2.44346095, - 2.61799388, 2.7925268 , 2.96705973, 3.14159265, 3.31612558, - 3.4906585 , 3.66519143, 3.83972435, 4.01425728, 4.1887902 , - 4.36332313, 4.53785606, 4.71238898, 4.88692191, 5.06145483, - 5.23598776, 5.41052068, 5.58505361, 5.75958653, 5.93411946, - 6.10865238, 6.28318531]) - energies = np.array([0.00000000e+00, 3.09449290e+02, 1.07459871e+03, 2.05925305e+03, - 3.02877926e+03, 3.79724994e+03, 4.23486826e+03, 4.26190303e+03, - 3.88196432e+03, 3.15173930e+03, 2.20016363e+03, 1.20431941e+03, - 3.94499732e+02, 7.23850312e+00, 2.77854025e+02, 1.40711827e+03, - 3.50375319e+03, 6.57899330e+03, 1.05208190e+04, 1.50847596e+04, - 1.99269611e+04, 2.46164740e+04, 2.86972097e+04, 3.17430074e+04, - 3.34148312e+04, 3.35267510e+04, 3.20643922e+04, 2.91936786e+04, - 2.52325029e+04, 2.06007483e+04, 1.57531541e+04, 1.11268684e+04, - 7.08120679e+03, 3.87554760e+03, 1.63995547e+03, 3.80256396e+02, - 6.14367036e-01]) - abs_arkane_path = os.path.abspath(os.path.dirname(__file__)) - scanpath1 = os.path.join(abs_arkane_path, 'data', 'H2O2', 'scan.txt') - scanlog1 = ScanLog(scanpath1) - angles1, energies1 = scanlog1.load() - self.assertTrue(np.allclose(angles, angles1, atol=1e-6)) - self.assertTrue(np.allclose(energies, energies1, atol=1e-6)) - - scanpath2 = os.path.join(abs_arkane_path, 'data', 'H2O2', 'scan.yml') - scanlog2 = ScanLog(scanpath2) - angles2, energies2 = scanlog2.load() - self.assertTrue(np.allclose(angles, angles2, atol=1e-6)) - print(energies, energies2) - self.assertTrue(np.allclose(energies, energies2, atol=1e-6)) - - scanpath3 = os.path.join(abs_arkane_path, 'data', 'H2O2', 'scan.csv') - scanlog3 = ScanLog(scanpath3) - angles3, energies3 = scanlog3.load() - self.assertTrue(np.allclose(angles, angles3, atol=1e-6)) - self.assertTrue(np.allclose(energies, energies3, atol=1e-6)) - - def test_hindered_rotor_from_scan_logs(self): - """ - Test assigning hindered rotor 1D PES profile via ScanLog to HinderedRotor in statmech jobs. - """ - angles = np.array([0. , 0.17453293, 0.34906585, 0.52359878, 0.6981317 , - 0.87266463, 1.04719755, 1.22173048, 1.3962634 , 1.57079633, - 1.74532925, 1.91986218, 2.0943951 , 2.26892803, 2.44346095, - 2.61799388, 2.7925268 , 2.96705973, 3.14159265, 3.31612558, - 3.4906585 , 3.66519143, 3.83972435, 4.01425728, 4.1887902 , - 4.36332313, 4.53785606, 4.71238898, 4.88692191, 5.06145483, - 5.23598776, 5.41052068, 5.58505361, 5.75958653, 5.93411946, - 6.10865238, 6.28318531]) - energies = np.array([0.00000000e+00, 3.09449290e+02, 1.07459871e+03, 2.05925305e+03, - 3.02877926e+03, 3.79724994e+03, 4.23486826e+03, 4.26190303e+03, - 3.88196432e+03, 3.15173930e+03, 2.20016363e+03, 1.20431941e+03, - 3.94499732e+02, 7.23850312e+00, 2.77854025e+02, 1.40711827e+03, - 3.50375319e+03, 6.57899330e+03, 1.05208190e+04, 1.50847596e+04, - 1.99269611e+04, 2.46164740e+04, 2.86972097e+04, 3.17430074e+04, - 3.34148312e+04, 3.35267510e+04, 3.20643922e+04, 2.91936786e+04, - 2.52325029e+04, 2.06007483e+04, 1.57531541e+04, 1.11268684e+04, - 7.08120679e+03, 3.87554760e+03, 1.63995547e+03, 3.80256396e+02, - 6.14367036e-01]) - h2o2_input = """#!/usr/bin/env python -# -*- coding: utf-8 -*- - -bonds = {{'H-O': 2, 'O-O': 1}} - -externalSymmetry = 2 - -spinMultiplicity = 1 - -opticalIsomers = 1 - -energy = {{'b3lyp/6-311+g(3df,2p)': Log('{energy}')}} - -geometry = Log('{freq}') - -frequencies = Log('{freq}') - -rotors = [HinderedRotor(scanLog=ScanLog('{scan}'), pivots=[1, 2], top=[1, 3], symmetry=1, fit='fourier')] - -""" - abs_arkane_path = os.path.abspath(os.path.dirname(__file__)) # this is the absolute path to `.../RMG-Py/arkane` - energy_path = os.path.join(abs_arkane_path, 'data', 'H2O2', 'sp_a19032.out') - freq_path = os.path.join(abs_arkane_path, 'data', 'H2O2', 'freq_a19031.out') - h2o2_path = os.path.join(abs_arkane_path, 'data', 'H2O2', 'H2O2.py') - h2o2 = Species(label='H2O2', smiles='OO') - os.makedirs(os.path.dirname(h2o2_path), exist_ok=True) - - for file in ['scan.txt', 'scan.csv', 'scan.yml']: - scan_path = os.path.join(abs_arkane_path, 'data', 'H2O2', file) - h2o2_input_tmp = h2o2_input.format(energy=energy_path, freq=freq_path, scan=scan_path) - with open(h2o2_path, 'w') as f: - f.write(h2o2_input_tmp) - statmech_job = StatMechJob(species=h2o2, path=h2o2_path) - statmech_job.level_of_theory = LevelOfTheory('b3lyp', '6-311+g(3df,2p)') - statmech_job.load(pdep=False, plot=False) - self.assertEqual(len(statmech_job.raw_hindered_rotor_data), 1) - self.assertTrue(np.allclose(statmech_job.raw_hindered_rotor_data[0][3], angles, atol=1e-6)) - self.assertTrue(np.allclose(statmech_job.raw_hindered_rotor_data[0][4], energies, atol=1e-6)) - os.remove(h2o2_path) - - - -################################################################################ - - -if __name__ == '__main__': - unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arkane/thermo.py b/arkane/thermo.py index e73a3ac7c3..ee22c23fc2 100644 --- a/arkane/thermo.py +++ b/arkane/thermo.py @@ -78,16 +78,13 @@ def execute(self, output_directory=None, plot=False): try: self.write_output(output_directory) except Exception as e: - logging.warning("Could not write output file due to error: " - "{0} for species {1}".format(e, self.species.label)) + logging.warning("Could not write output file due to error: " "{0} for species {1}".format(e, self.species.label)) try: self.arkane_species.chemkin_thermo_string = self.write_chemkin(output_directory) except Exception as e: - logging.warning("Could not write chemkin output due to error: " - "{0} for species {1}".format(e, self.species.label)) + logging.warning("Could not write chemkin output due to error: " "{0} for species {1}".format(e, self.species.label)) if self.species.molecule is None or len(self.species.molecule) == 0: - logging.debug("Not generating a YAML file for species {0}, since its structure wasn't" - " specified".format(self.species.label)) + logging.debug("Not generating a YAML file for species {0}, since its structure wasn't" " specified".format(self.species.label)) else: # We're saving a YAML file for species iff Thermo is called and they're structure is known self.arkane_species.update_species_attributes(self.species) @@ -96,8 +93,7 @@ def execute(self, output_directory=None, plot=False): try: self.plot(output_directory) except Exception as e: - logging.warning("Could not create plots due to error: " - "{0} for species {1}".format(e, self.species.label)) + logging.warning("Could not create plots due to error: " "{0} for species {1}".format(e, self.species.label)) def generate_thermo(self): """ @@ -105,26 +101,26 @@ def generate_thermo(self): desired heat capacity model (as specified in the `thermo_class` attribute). """ - if self.thermo_class.lower() not in ['wilhoit', 'nasa']: + if self.thermo_class.lower() not in ["wilhoit", "nasa"]: raise InputError('Unknown thermodynamic model "{0}".'.format(self.thermo_class)) species = self.species - logging.debug('Generating {0} thermo model for {1}...'.format(self.thermo_class, species)) + logging.debug("Generating {0} thermo model for {1}...".format(self.thermo_class, species)) if species.thermo is not None: logging.info("Thermo already generated for species {}. Skipping thermo generation.".format(species)) return None - Tlist = np.arange(10.0, 3001.0, 10.0, np.float64) + Tlist = np.arange(10.0, 3001.0, 10.0, float) Cplist = np.zeros_like(Tlist) H298 = 0.0 S298 = 0.0 conformer = self.species.conformer for i in range(Tlist.shape[0]): Cplist[i] += conformer.get_heat_capacity(Tlist[i]) - H298 += conformer.get_enthalpy(298.) + conformer.E0.value_si - S298 += conformer.get_entropy(298.) + H298 += conformer.get_enthalpy(298.0) + conformer.E0.value_si + S298 += conformer.get_entropy(298.0) if not any([isinstance(mode, (LinearRotor, NonlinearRotor)) for mode in conformer.modes]): # Monatomic species @@ -144,7 +140,7 @@ def generate_thermo(self): if n_freq == 0 and n_rotors == 0: wilhoit.Cp0 = (Cplist[0], "J/(mol*K)") wilhoit.CpInf = (Cplist[0], "J/(mol*K)") - wilhoit.B = (500., "K") + wilhoit.B = (500.0, "K") wilhoit.H0 = (0.0, "J/mol") wilhoit.S0 = (0.0, "J/(mol*K)") wilhoit.H0 = (H298 - wilhoit.get_enthalpy(298.15), "J/mol") @@ -152,7 +148,7 @@ def generate_thermo(self): else: wilhoit.fit_to_data(Tlist, Cplist, Cp0, CpInf, H298, S298, B0=500.0) - if self.thermo_class.lower() == 'nasa': + if self.thermo_class.lower() == "nasa": species.thermo = wilhoit.to_nasa(Tmin=10.0, Tmax=3000.0, Tint=500.0) else: species.thermo = wilhoit @@ -163,32 +159,32 @@ def write_output(self, output_directory): in `output_directory`. """ species = self.species - output_file = os.path.join(output_directory, 'output.py') - logging.info('Saving thermo for {0}...'.format(species.label)) + output_file = os.path.join(output_directory, "output.py") + logging.info("Saving thermo for {0}...".format(species.label)) - with open(output_file, 'a') as f: - f.write('# Thermodynamics for {0}:\n'.format(species.label)) - H298 = species.get_thermo_data().get_enthalpy(298) / 4184. + with open(output_file, "a") as f: + f.write("# Thermodynamics for {0}:\n".format(species.label)) + H298 = species.get_thermo_data().get_enthalpy(298) / 4184.0 S298 = species.get_thermo_data().get_entropy(298) / 4.184 - f.write('# Enthalpy of formation (298 K) = {0:9.3f} kcal/mol\n'.format(H298)) - f.write('# Entropy of formation (298 K) = {0:9.3f} cal/(mol*K)\n'.format(S298)) - f.write('# =========== =========== =========== =========== ===========\n') - f.write('# Temperature Heat cap. Enthalpy Entropy Free energy\n') - f.write('# (K) (cal/mol*K) (kcal/mol) (cal/mol*K) (kcal/mol)\n') - f.write('# =========== =========== =========== =========== ===========\n') + f.write("# Enthalpy of formation (298 K) = {0:9.3f} kcal/mol\n".format(H298)) + f.write("# Entropy of formation (298 K) = {0:9.3f} cal/(mol*K)\n".format(S298)) + f.write("# =========== =========== =========== =========== ===========\n") + f.write("# Temperature Heat cap. Enthalpy Entropy Free energy\n") + f.write("# (K) (cal/mol*K) (kcal/mol) (cal/mol*K) (kcal/mol)\n") + f.write("# =========== =========== =========== =========== ===========\n") for T in [300, 400, 500, 600, 800, 1000, 1500, 2000, 2400]: try: Cp = species.get_thermo_data().get_heat_capacity(T) / 4.184 - H = species.get_thermo_data().get_enthalpy(T) / 4184. + H = species.get_thermo_data().get_enthalpy(T) / 4184.0 S = species.get_thermo_data().get_entropy(T) / 4.184 - G = species.get_thermo_data().get_free_energy(T) / 4184. - f.write('# {0:11g} {1:11.3f} {2:11.3f} {3:11.3f} {4:11.3f}\n'.format(T, Cp, H, S, G)) + G = species.get_thermo_data().get_free_energy(T) / 4184.0 + f.write("# {0:11g} {1:11.3f} {2:11.3f} {3:11.3f} {4:11.3f}\n".format(T, Cp, H, S, G)) except ValueError: logging.debug("Valid thermo for {0} is outside range for temperature {1}".format(species, T)) - f.write('# =========== =========== =========== =========== ===========\n') + f.write("# =========== =========== =========== =========== ===========\n") - thermo_string = 'thermo(label={0!r}, thermo={1!r})'.format(species.label, species.get_thermo_data()) - f.write('{0}\n\n'.format(prettify(thermo_string))) + thermo_string = "thermo(label={0!r}, thermo={1!r})".format(species.label, species.get_thermo_data()) + f.write("{0}\n\n".format(prettify(thermo_string))) def write_chemkin(self, output_directory): """ @@ -196,36 +192,36 @@ def write_chemkin(self, output_directory): `species_dictionary.txt` within the `outut_directory` specified """ species = self.species - with open(os.path.join(output_directory, 'chem.inp'), 'a') as f: + with open(os.path.join(output_directory, "chem.inp"), "a") as f: if isinstance(species, Species): if species.molecule and isinstance(species.molecule[0], Molecule): element_counts = get_element_count(species.molecule[0]) else: try: - element_counts = species.props['element_counts'] + element_counts = species.props["element_counts"] except KeyError: element_counts = self.element_count_from_conformer() else: - element_counts = {'C': 0, 'H': 0} + element_counts = {"C": 0, "H": 0} chemkin_thermo_string = write_thermo_entry(species, element_counts=element_counts, verbose=True) - f.write('{0}\n'.format(chemkin_thermo_string)) + f.write("{0}\n".format(chemkin_thermo_string)) # write species dictionary if isinstance(species, Species): if species.molecule and isinstance(species.molecule[0], Molecule): - spec_dict_path = os.path.join(output_directory, 'species_dictionary.txt') + spec_dict_path = os.path.join(output_directory, "species_dictionary.txt") is_species_in_dict = False if os.path.isfile(spec_dict_path): - with open(spec_dict_path, 'r') as f: + with open(spec_dict_path, "r") as f: # check whether the species dictionary contains this species, in which case do not re-append for line in f.readlines(): if species.label == line.strip(): is_species_in_dict = True break if not is_species_in_dict: - with open(spec_dict_path, 'a') as f: + with open(spec_dict_path, "a") as f: f.write(species.molecule[0].to_adjacency_list(remove_h=False, label=species.label)) - f.write('\n') + f.write("\n") return chemkin_thermo_string def element_count_from_conformer(self): @@ -249,7 +245,7 @@ def plot(self, output_directory): """ Plot the heat capacity, enthapy, entropy, and Gibbs free energy of the fitted thermodynamics model, along with the same values from the - statistical mechanics model that the thermodynamics model was fitted + statistical mechanics model that the thermodynamics model was fitted to. The plot is saved to the file ``thermo.pdf`` in the output directory. The plot is not generated if ``matplotlib`` is not installed. """ @@ -285,35 +281,35 @@ def plot(self, output_directory): continue fig = plt.figure(figsize=(10, 8)) - fig.suptitle('{0}'.format(self.species.label)) + fig.suptitle("{0}".format(self.species.label)) plt.subplot(2, 2, 1) - plt.plot(Tlist, Cplist / 4.184, '-r', Tlist, Cplist1 / 4.184, '-b') - plt.xlabel('Temperature (K)') - plt.ylabel('Heat capacity (cal/mol*K)') - plt.legend(['statmech', 'fitted'], loc=4) + plt.plot(Tlist, Cplist / 4.184, "-r", Tlist, Cplist1 / 4.184, "-b") + plt.xlabel("Temperature (K)") + plt.ylabel("Heat capacity (cal/mol*K)") + plt.legend(["statmech", "fitted"], loc=4) plt.subplot(2, 2, 2) - plt.plot(Tlist, Slist / 4.184, '-r', Tlist, Slist1 / 4.184, '-b') - plt.xlabel('Temperature (K)') - plt.ylabel('Entropy (cal/mol*K)') + plt.plot(Tlist, Slist / 4.184, "-r", Tlist, Slist1 / 4.184, "-b") + plt.xlabel("Temperature (K)") + plt.ylabel("Entropy (cal/mol*K)") plt.subplot(2, 2, 3) - plt.plot(Tlist, Hlist / 4.184, '-r', Tlist, Hlist1 / 4.184, '-b') - plt.xlabel('Temperature (K)') - plt.ylabel('Enthalpy (kcal/mol)') + plt.plot(Tlist, Hlist / 4.184, "-r", Tlist, Hlist1 / 4.184, "-b") + plt.xlabel("Temperature (K)") + plt.ylabel("Enthalpy (kcal/mol)") plt.subplot(2, 2, 4) - plt.plot(Tlist, Glist / 4.184, '-r', Tlist, Glist1 / 4.184, '-b') - plt.xlabel('Temperature (K)') - plt.ylabel('Gibbs free energy (kcal/mol)') + plt.plot(Tlist, Glist / 4.184, "-r", Tlist, Glist1 / 4.184, "-b") + plt.xlabel("Temperature (K)") + plt.ylabel("Gibbs free energy (kcal/mol)") fig.subplots_adjust(left=0.10, bottom=0.08, right=0.95, top=0.95, wspace=0.35, hspace=0.20) - plot_path = os.path.join(output_directory, 'plots') + plot_path = os.path.join(output_directory, "plots") if not os.path.exists(plot_path): os.mkdir(plot_path) valid_chars = "-_.()<=> %s%s" % (string.ascii_letters, string.digits) - filename = ''.join(c for c in self.species.label if c in valid_chars) + '.pdf' + filename = "".join(c for c in self.species.label if c in valid_chars) + ".pdf" plt.savefig(os.path.join(plot_path, filename)) plt.close() diff --git a/documentation/make.bat b/documentation/make.bat deleted file mode 100644 index 91ef85e170..0000000000 --- a/documentation/make.bat +++ /dev/null @@ -1,170 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\RMGPy.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\RMGPy.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -:end diff --git a/documentation/source/reference/chemkin/index.rst b/documentation/source/reference/chemkin/index.rst index 555b603bb9..5ed52f572c 100644 --- a/documentation/source/reference/chemkin/index.rst +++ b/documentation/source/reference/chemkin/index.rst @@ -41,8 +41,6 @@ Function Description :func:`save_chemkin_file` Save a reaction mechanism to a Chemkin file :func:`save_species_dictionary` Save a species dictionary to a file :func:`save_transport_file` Save a Chemkin transport properties file -:func:`save_html_file` Save an HTML file representing a Chemkin mechanism -:func:`save_java_kinetics_library` Save a mechanism to a (Chemkin-like) kinetics library for RMG-Java ---------------------------------- ------------------------------------------------ :func:`get_species_identifier` Return the Chemkin-valid identifier for a given species :func:`mark_duplicate_reactions` Find and mark all duplicate reactions in a mechanism diff --git a/documentation/source/reference/chemkin/writer.rst b/documentation/source/reference/chemkin/writer.rst index b09f105ce8..bfb287e2a8 100644 --- a/documentation/source/reference/chemkin/writer.rst +++ b/documentation/source/reference/chemkin/writer.rst @@ -13,10 +13,6 @@ Main functions .. autofunction:: rmgpy.chemkin.save_transport_file -.. autofunction:: rmgpy.chemkin.save_html_file - -.. autofunction:: rmgpy.chemkin.save_java_kinetics_library - Helper functions ================ diff --git a/documentation/source/users/rmg/database/modification.rst b/documentation/source/users/rmg/database/modification.rst index 1fa567022f..dce49a25bf 100644 --- a/documentation/source/users/rmg/database/modification.rst +++ b/documentation/source/users/rmg/database/modification.rst @@ -61,7 +61,6 @@ There are several places in the RMG-database and RMG-Py source code where reacti * ``getReactionPairs``: figuring out which species becomes which for flux analyses * ``__generateReactions``: correcting degeneracy eg. dividing by 2 for radical recombination * rmgpy.data.kinetics.rules - * ``processOldLibraryEntry``: determining units when importing RMG-Java database * ``getAllRules``: for radical recombination add reverse templates * rmgpy.data.kinetics.groups * ``getReactionTemplate``: for radical recombination duplicate the template diff --git a/documentation/source/users/rmg/installation/dependencies.rst b/documentation/source/users/rmg/installation/dependencies.rst index 1e257148f9..ba8a3ad66c 100644 --- a/documentation/source/users/rmg/installation/dependencies.rst +++ b/documentation/source/users/rmg/installation/dependencies.rst @@ -32,7 +32,7 @@ Briefly, RMG depends on the following packages, almost all of which can be found * **mpmath:** for arbitrary-precision arithmetic used in Arkane * **muq:** (optional) MIT Uncertainty Quantification library, used for global uncertainty analysis * **networkx:** (optional) network analysis for reaction-path analysis IPython notebook -* **nose:** advanced unit test controls +* **pytest:** advanced unit test controls * **numpy:** fast matrix operations * **openbabel:** chemical toolbox for speaking the many languages of chemical data * **psutil:** system utilization diagnostic tool @@ -58,7 +58,7 @@ License Restrictions on Dependencies All of RMG's dependencies except the ones listed below are freely available and compatible with RMG's open source MIT license (though the specific nature of their licenses vary). -* **pydas**: The DAE solvers used in the simulations come from `Linda Petzold’s research group `_ at UCSB. For running sensitivity analysis in RMG, the DASPK 3.1 solver is required, which "is subject to copyright restrictions” for non-academic use. Please visit their website for more details. To run RMG without this restriction, one may switch to compiling with the DASSL solver instead in RMG, which is "available in the public domain.” +* **pydas**: The DAE solvers used in the simulations come from `Linda Petzold's research group `_ at UCSB. For running sensitivity analysis in RMG, the DASPK 3.1 solver is required, which "is subject to copyright restrictions” for non-academic use. Please visit their website for more details. To run RMG without this restriction, one may switch to compiling with the DASSL solver instead in RMG, which is "available in the public domain.” If you wish to do on-the-fly quantum chemistry calculations of thermochemistry (advisable for fused cyclic species in particular, where the ring corrections to group additive estimates are lacking), the then you will need the third-party software for the QM calculations: diff --git a/documentation/source/users/rmg/modules/convertFAME.rst b/documentation/source/users/rmg/modules/convertFAME.rst deleted file mode 100644 index f709206826..0000000000 --- a/documentation/source/users/rmg/modules/convertFAME.rst +++ /dev/null @@ -1,29 +0,0 @@ -.. _convertFAME: - -*********************************** -Convert FAME to Arkane Input File -*********************************** - -This module is utilized to convert FAME file types (used in RMG-Java) to Arkane (formerly called CanTherm) objects -(used in RMG-Py) for pressure dependent calculations. - -FAME is an early version of the pdep code in Arkane, and it is written in Fortran and used by RMG-Java. -This script enables importing FAME input files into Arkane. Note that it is mostly designed to load the FAME input files -generated automatically by RMG-Java, and may not load hand-crafted FAME input files. If you specify a `moleculeDict`, -then this script will use it to associate the species with their structures. :: - - python convertFAME.py fame_object - -where ``fame_object`` is the FAME file used to be converted into the Arkane object. - -Some additional options involve adding an RMG dictionary to process with the file. The syntax for this is :: - - python convertFAME.py -d RMG_dictionary.txt fame_object - -where ``RMG_dictionary.txt`` is the dictionary to process with the file. - -A max energy cuttoff is also possible when converting the file formats. :: - - python convertFAME.py -d RMG_dictionary.txt -x value units value units fame_object - -where ``value`` represents the max energy amount and ``units`` represents its units diff --git a/documentation/source/users/rmg/modules/generateFluxDiagram.rst b/documentation/source/users/rmg/modules/generateFluxDiagram.rst index d4d7c1bc07..4fc19f0428 100644 --- a/documentation/source/users/rmg/modules/generateFluxDiagram.rst +++ b/documentation/source/users/rmg/modules/generateFluxDiagram.rst @@ -10,7 +10,7 @@ that shows interconnected arrows between species that represent fluxes. To use this method, you just need a Chemkin input file and an RMG species dictionary. The syntax is as follows:: - python generateFluxDiagram.py [-h] [--java] [--no-dlim] [-s SPECIES] [-f] + python generateFluxDiagram.py [-h] [--no-dlim] [-s SPECIES] [-f] [-n N] [-e N] [-c TOL] [-r TOL] [-t S] INPUT CHEMKIN DICTIONARY [CHEMKIN_OUTPUT] @@ -24,7 +24,6 @@ Positional arguments:: Optional arguments:: -h, --help show this help message and exit - --java process RMG-Java model --no-dlim Turn off diffusion-limited rates -s DIR, --species DIR Path to folder containing species images -f, --foreign Not an RMG generated Chemkin file (will be checked for duplicates) diff --git a/documentation/source/users/rmg/modules/index.rst b/documentation/source/users/rmg/modules/index.rst index 5db2c622c3..3bb8d7c200 100644 --- a/documentation/source/users/rmg/modules/index.rst +++ b/documentation/source/users/rmg/modules/index.rst @@ -18,7 +18,6 @@ otherwise. simulate generateFluxDiagram thermoEstimation - convertFAME databaseScripts standardizeModelSpeciesNames isotopes diff --git a/environment.yml b/environment.yml index f1c805a979..e9c5e8c071 100644 --- a/environment.yml +++ b/environment.yml @@ -63,7 +63,15 @@ dependencies: - pyparsing - pyyaml - networkx - - nose + - pytest + - pytest-cov + # we use a the pytest-check plugin, which is on Conda and PyPI, but the + # version compatible with Python 3.7 is only on PyPI + # switch to the conda version after upgrading to 3.11 + # - conda-forge::pytest-check + - pip + - pip: + - pytest-check - matplotlib >=1.5 - mpmath - pandas diff --git a/examples/arkane/species/Benzyl/BenzylEnergy.log b/examples/arkane/species/Benzyl/BenzylEnergy.log old mode 100755 new mode 100644 diff --git a/examples/arkane/species/Benzyl/BenzylFreq.log b/examples/arkane/species/Benzyl/BenzylFreq.log old mode 100755 new mode 100644 diff --git a/examples/arkane/species/Benzyl/BenzylRot1.log b/examples/arkane/species/Benzyl/BenzylRot1.log old mode 100755 new mode 100644 diff --git a/examples/arkane/species/Toulene_Free_Rotor/TolueneEnergy.log b/examples/arkane/species/Toulene_Free_Rotor/TolueneEnergy.log old mode 100755 new mode 100644 diff --git a/examples/arkane/species/Toulene_Free_Rotor/TolueneFreq.log b/examples/arkane/species/Toulene_Free_Rotor/TolueneFreq.log old mode 100755 new mode 100644 diff --git a/examples/arkane/species/Toulene_Hindered_Rotor/TolueneEnergy.log b/examples/arkane/species/Toulene_Hindered_Rotor/TolueneEnergy.log old mode 100755 new mode 100644 diff --git a/examples/arkane/species/Toulene_Hindered_Rotor/TolueneFreq.log b/examples/arkane/species/Toulene_Hindered_Rotor/TolueneFreq.log old mode 100755 new mode 100644 diff --git a/examples/arkane/species/Toulene_Hindered_Rotor/TolueneRot1.log b/examples/arkane/species/Toulene_Hindered_Rotor/TolueneRot1.log old mode 100755 new mode 100644 diff --git a/examples/arkane/species/Toulene_Hindered_Rotor_SemiClassicalND/TolueneEnergy.log b/examples/arkane/species/Toulene_Hindered_Rotor_SemiClassicalND/TolueneEnergy.log old mode 100755 new mode 100644 diff --git a/examples/arkane/species/Toulene_Hindered_Rotor_SemiClassicalND/TolueneFreq.log b/examples/arkane/species/Toulene_Hindered_Rotor_SemiClassicalND/TolueneFreq.log old mode 100755 new mode 100644 diff --git a/examples/arkane/species/Toulene_Hindered_Rotor_SemiClassicalND/TolueneRot1.log b/examples/arkane/species/Toulene_Hindered_Rotor_SemiClassicalND/TolueneRot1.log old mode 100755 new mode 100644 diff --git a/examples/arkane/species/Toulene_Hindered_Rotor_SemiClassicalND/input.py b/examples/arkane/species/Toulene_Hindered_Rotor_SemiClassicalND/input.py old mode 100755 new mode 100644 diff --git a/examples/arkane/species/Toulene_Hindered_Rotor_SemiClassicalND/toluene_HinderedRotor.py b/examples/arkane/species/Toulene_Hindered_Rotor_SemiClassicalND/toluene_HinderedRotor.py old mode 100755 new mode 100644 index fe8f299ac8..78d0c3cad8 --- a/examples/arkane/species/Toulene_Hindered_Rotor_SemiClassicalND/toluene_HinderedRotor.py +++ b/examples/arkane/species/Toulene_Hindered_Rotor_SemiClassicalND/toluene_HinderedRotor.py @@ -1,9 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- bonds = { - 'C-C': 4, - 'C-H': 8, - 'C=C': 3, + "C-C": 4, + "C-H": 8, + "C=C": 3, } externalSymmetry = 1 @@ -12,12 +12,10 @@ opticalIsomers = 1 -energy = { - 'CBS-QB3': Log('TolueneEnergy.log') -} +energy = {"CBS-QB3": Log("TolueneEnergy.log")} -geometry = Log('TolueneFreq.log') +geometry = Log("TolueneFreq.log") -frequencies = Log('TolueneFreq.log') +frequencies = Log("TolueneFreq.log") -rotors = [HinderedRotorClassicalND(calcPath='TolueneRot1.log', pivots=[[3,12]], tops=[[12,13,14,15]], sigmas=[6], semiclassical=True)] +rotors = [HinderedRotorClassicalND(calc_path="TolueneRot1.log", pivots=[[3, 12]], tops=[[12, 13, 14, 15]], sigmas=[6], semiclassical=True)] diff --git a/examples/arkane/species/thermo_demo/input.py b/examples/arkane/species/thermo_demo/input.py index 6e46762ac8..7afab82532 100644 --- a/examples/arkane/species/thermo_demo/input.py +++ b/examples/arkane/species/thermo_demo/input.py @@ -2,31 +2,33 @@ # encoding: utf-8 # Define the level of theory ("model chemistry"): -modelChemistry = LevelOfTheory(method='CCSD(T)-F12', basis='cc-pVTZ-F12', software='molpro') +modelChemistry = LevelOfTheory(method="CCSD(T)-F12", basis="cc-pVTZ-F12", software="molpro") useHinderedRotors = True useBondCorrections = True +# FIXME: While switching to pytest, the commented-out examples stopped working. I suspect they have not +# been run in some time and fell vitim to tech. debt. + # Define the species: -species('methoxy', 'data/methoxy.py', structure=SMILES('C[O]')) -species('1,2-butadiene', 'data/1,2-butadiene.py', structure=SMILES('C=C=CC')) -species('aziridine', 'data/aziridine.py', structure=SMILES('C1NC1')) -species('hydrazino', 'data/hydrazino.py', structure=SMILES('N[NH]')) -species('1-propene-12-diol', 'data/1-propene-12-diol.py', structure=SMILES('CC(O)=CO')) -species('hydroxyiminomethyl', 'data/hydroxyiminomethyl.py', structure=SMILES('[CH]=NO')) -species('2-methyl-2-propanamine', 'data/2-methyl-2-propanamine.py', structure=SMILES('CC(C)(C)N')) -species('nitrosodioxaziridine', 'data/nitrosodioxaziridine.py', structure=SMILES('O=NN1OO1')) -species('ethynol', 'data/ethynol.py', structure=SMILES('C#CO')) -species('2-iminoethyl', 'data/2-iminoethyl.py', structure=SMILES('[CH2]C=N')) +species("methoxy", "data/methoxy.py", structure=SMILES("C[O]")) +# species("1,2-butadiene", "data/1,2-butadiene.py", structure=SMILES("C=C=CC")) +species("aziridine", "data/aziridine.py", structure=SMILES("C1NC1")) +species("hydrazino", "data/hydrazino.py", structure=SMILES("N[NH]")) +species("1-propene-12-diol", "data/1-propene-12-diol.py", structure=SMILES("CC(O)=CO")) +species("hydroxyiminomethyl", "data/hydroxyiminomethyl.py", structure=SMILES("[CH]=NO")) +species("2-methyl-2-propanamine", "data/2-methyl-2-propanamine.py", structure=SMILES("CC(C)(C)N")) +# species("nitrosodioxaziridine", "data/nitrosodioxaziridine.py", structure=SMILES("O=NN1OO1")) +species("ethynol", "data/ethynol.py", structure=SMILES("C#CO")) +species("2-iminoethyl", "data/2-iminoethyl.py", structure=SMILES("[CH2]C=N")) # Request thermodynamic property calculation with a NASA polynomial output: -thermo('methoxy', 'NASA') -thermo('1,2-butadiene', 'NASA') -thermo('aziridine', 'NASA') -thermo('hydrazino', 'NASA') -thermo('1-propene-12-diol', 'NASA') -thermo('hydroxyiminomethyl', 'NASA') -thermo('2-methyl-2-propanamine', 'NASA') -thermo('nitrosodioxaziridine', 'NASA') -thermo('ethynol', 'NASA') -thermo('2-iminoethyl', 'NASA') - +thermo("methoxy", "NASA") +# thermo("1,2-butadiene", "NASA") +thermo("aziridine", "NASA") +thermo("hydrazino", "NASA") +thermo("1-propene-12-diol", "NASA") +thermo("hydroxyiminomethyl", "NASA") +thermo("2-methyl-2-propanamine", "NASA") +# thermo("nitrosodioxaziridine", "NASA") +thermo("ethynol", "NASA") +thermo("2-iminoethyl", "NASA") diff --git a/examples/rmg/1,3-hexadiene/RMG.bat b/examples/rmg/1,3-hexadiene/RMG.bat deleted file mode 100644 index 61fc6b8960..0000000000 --- a/examples/rmg/1,3-hexadiene/RMG.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off - -REM RMG-Py Windows batch script for RMG execution -REM Put me in the directory containing the input file and double-click to run RMG. -REM This assumes that the condition file is called input.py. -REM Output from RMG will be logged to the file RMG.log. - -echo Running RMG... -python "..\..\..\rmg.py" input.py -echo RMG job completed. - -:end -pause diff --git a/examples/rmg/TEOS/RMG.bat b/examples/rmg/TEOS/RMG.bat deleted file mode 100644 index 61fc6b8960..0000000000 --- a/examples/rmg/TEOS/RMG.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off - -REM RMG-Py Windows batch script for RMG execution -REM Put me in the directory containing the input file and double-click to run RMG. -REM This assumes that the condition file is called input.py. -REM Output from RMG will be logged to the file RMG.log. - -echo Running RMG... -python "..\..\..\rmg.py" input.py -echo RMG job completed. - -:end -pause diff --git a/examples/rmg/c3h4/RMG.bat b/examples/rmg/c3h4/RMG.bat deleted file mode 100644 index 61fc6b8960..0000000000 --- a/examples/rmg/c3h4/RMG.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off - -REM RMG-Py Windows batch script for RMG execution -REM Put me in the directory containing the input file and double-click to run RMG. -REM This assumes that the condition file is called input.py. -REM Output from RMG will be logged to the file RMG.log. - -echo Running RMG... -python "..\..\..\rmg.py" input.py -echo RMG job completed. - -:end -pause diff --git a/examples/rmg/ch3no2/RMG.bat b/examples/rmg/ch3no2/RMG.bat deleted file mode 100644 index 61fc6b8960..0000000000 --- a/examples/rmg/ch3no2/RMG.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off - -REM RMG-Py Windows batch script for RMG execution -REM Put me in the directory containing the input file and double-click to run RMG. -REM This assumes that the condition file is called input.py. -REM Output from RMG will be logged to the file RMG.log. - -echo Running RMG... -python "..\..\..\rmg.py" input.py -echo RMG job completed. - -:end -pause diff --git a/examples/rmg/diesel/RMG.bat b/examples/rmg/diesel/RMG.bat deleted file mode 100644 index 61fc6b8960..0000000000 --- a/examples/rmg/diesel/RMG.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off - -REM RMG-Py Windows batch script for RMG execution -REM Put me in the directory containing the input file and double-click to run RMG. -REM This assumes that the condition file is called input.py. -REM Output from RMG will be logged to the file RMG.log. - -echo Running RMG... -python "..\..\..\rmg.py" input.py -echo RMG job completed. - -:end -pause diff --git a/examples/rmg/e85/RMG.bat b/examples/rmg/e85/RMG.bat deleted file mode 100644 index 61fc6b8960..0000000000 --- a/examples/rmg/e85/RMG.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off - -REM RMG-Py Windows batch script for RMG execution -REM Put me in the directory containing the input file and double-click to run RMG. -REM This assumes that the condition file is called input.py. -REM Output from RMG will be logged to the file RMG.log. - -echo Running RMG... -python "..\..\..\rmg.py" input.py -echo RMG job completed. - -:end -pause diff --git a/examples/rmg/gri_mech_rxn_lib/RMG.bat b/examples/rmg/gri_mech_rxn_lib/RMG.bat deleted file mode 100644 index ac88ed3cd4..0000000000 --- a/examples/rmg/gri_mech_rxn_lib/RMG.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off - -REM RMG-Py Windows batch script for RMG execution -REM Put me in the directory containing the input file and double-click to run RMG. -REM This assumes that the condition file is called input.py. -REM Output from RMG will be logged to the file RMG.log. - -echo Running RMG... -python "..\..\..\rmg.py" input.py -echo RMG job completed. - -:end -pause diff --git a/examples/rmg/liquid_phase/RMG.bat b/examples/rmg/liquid_phase/RMG.bat deleted file mode 100644 index 61fc6b8960..0000000000 --- a/examples/rmg/liquid_phase/RMG.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off - -REM RMG-Py Windows batch script for RMG execution -REM Put me in the directory containing the input file and double-click to run RMG. -REM This assumes that the condition file is called input.py. -REM Output from RMG will be logged to the file RMG.log. - -echo Running RMG... -python "..\..\..\rmg.py" input.py -echo RMG job completed. - -:end -pause diff --git a/examples/rmg/liquid_phase_constSPC/RMG.bat b/examples/rmg/liquid_phase_constSPC/RMG.bat deleted file mode 100644 index 61fc6b8960..0000000000 --- a/examples/rmg/liquid_phase_constSPC/RMG.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off - -REM RMG-Py Windows batch script for RMG execution -REM Put me in the directory containing the input file and double-click to run RMG. -REM This assumes that the condition file is called input.py. -REM Output from RMG will be logged to the file RMG.log. - -echo Running RMG... -python "..\..\..\rmg.py" input.py -echo RMG job completed. - -:end -pause diff --git a/examples/rmg/methylformate/RMG.bat b/examples/rmg/methylformate/RMG.bat deleted file mode 100644 index 61fc6b8960..0000000000 --- a/examples/rmg/methylformate/RMG.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off - -REM RMG-Py Windows batch script for RMG execution -REM Put me in the directory containing the input file and double-click to run RMG. -REM This assumes that the condition file is called input.py. -REM Output from RMG will be logged to the file RMG.log. - -echo Running RMG... -python "..\..\..\rmg.py" input.py -echo RMG job completed. - -:end -pause diff --git a/examples/rmg/minimal/RMG.bat b/examples/rmg/minimal/RMG.bat deleted file mode 100644 index 61fc6b8960..0000000000 --- a/examples/rmg/minimal/RMG.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off - -REM RMG-Py Windows batch script for RMG execution -REM Put me in the directory containing the input file and double-click to run RMG. -REM This assumes that the condition file is called input.py. -REM Output from RMG will be logged to the file RMG.log. - -echo Running RMG... -python "..\..\..\rmg.py" input.py -echo RMG job completed. - -:end -pause diff --git a/examples/rmg/minimal_sensitivity/RMG.bat b/examples/rmg/minimal_sensitivity/RMG.bat deleted file mode 100644 index 61fc6b8960..0000000000 --- a/examples/rmg/minimal_sensitivity/RMG.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off - -REM RMG-Py Windows batch script for RMG execution -REM Put me in the directory containing the input file and double-click to run RMG. -REM This assumes that the condition file is called input.py. -REM Output from RMG will be logged to the file RMG.log. - -echo Running RMG... -python "..\..\..\rmg.py" input.py -echo RMG job completed. - -:end -pause diff --git a/examples/rmg/minimal_surface/RMG.bat b/examples/rmg/minimal_surface/RMG.bat deleted file mode 100644 index 61fc6b8960..0000000000 --- a/examples/rmg/minimal_surface/RMG.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off - -REM RMG-Py Windows batch script for RMG execution -REM Put me in the directory containing the input file and double-click to run RMG. -REM This assumes that the condition file is called input.py. -REM Output from RMG will be logged to the file RMG.log. - -echo Running RMG... -python "..\..\..\rmg.py" input.py -echo RMG job completed. - -:end -pause diff --git a/external/README b/external/README deleted file mode 100644 index 00c7aba3fb..0000000000 --- a/external/README +++ /dev/null @@ -1,48 +0,0 @@ -This folder ('external') is for external programs that RMG depends on but are -not part of the RMG code itself. In most cases you can get them elsewhere. -Nevertheless, they are stored here because - (1) they may be hard to find elsewhere - (2) we know that that these versions work (but ones from elsewhere may not) - -ctml_writer.py -comes from Cantera 1.7 (https://sourceforge.net/projects/cantera/) -which we think uses the New BSD License -http://www.opensource.org/licenses/bsd-license.php - -pydot.py -comes from Pydot (http://www.dkbza.org/pydot.html) -which uses the MIT license -http://opensource.org/licenses/mit-license.html -the version here is based on 1.0.2 but contains a bug fix patch - - -cinfony/webel.py -from http://cinfony.googlecode.com/svn/trunk/cinfony/webel.py - -jquery.min.js -from http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js -Used by the HTML output of RMG. Useful to have it locally if you do not have -internet access when you want to look at your results. - - -symmetry -Brute force symmetry determination program and the associated test set. -(C) 1996, 2003 S. Patchkovskii, Serguei.Patchkovskii@sympatico.ca -Published under GNU General Public License -http://www.cobalt.chem.ucalgary.ca/ps/symmetry/ -(now mirrored at https://github.com/nquesada/symmetry) -This is based on Revision 1.16 2003/04/04 -This program is no longer stored in the external programs folder but is instead found on the anaconda platform. - - -cclib -An open source library, written in Python, for parsing and interpreting -the results of computational chemistry packages. -N. M. O'Boyle, A. L. Tenderholt, K. M. Langner, -"cclib: a library for package-independent computational chemistry algorithms", -J. Comp. Chem. 29 (5), pp. 839-845, 2008 -cclib is licensed under the LGPL -http://cclib.sourceforge.net -This is based on version 1.0 but with some modifications, -probably made in the RMG-Java project. -We should endeavor to get the modifications merged upstream and use the main cclib. \ No newline at end of file diff --git a/external/__init__.py b/external/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/external/jquery.min.js b/external/jquery.min.js deleted file mode 100644 index 0c7294c90a..0000000000 --- a/external/jquery.min.js +++ /dev/null @@ -1,152 +0,0 @@ -/*! - * jQuery JavaScript Library v1.4.1 - * http://jquery.com/ - * - * Copyright 2010, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2010, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Mon Jan 25 19:43:33 2010 -0500 - */ -(function(z,v){function la(){if(!c.isReady){try{r.documentElement.doScroll("left")}catch(a){setTimeout(la,1);return}c.ready()}}function Ma(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,i){var j=a.length;if(typeof b==="object"){for(var n in b)X(a,n,b[n],f,e,d);return a}if(d!==v){f=!i&&f&&c.isFunction(d);for(n=0;n-1){i=j.data;i.beforeFilter&&i.beforeFilter[a.type]&&!i.beforeFilter[a.type](a)||f.push(j.selector)}else delete x[o]}i=c(a.target).closest(f, -a.currentTarget);m=0;for(s=i.length;m)[^>]*$|^#([\w-]+)$/,Qa=/^.[^:#\[\.,]*$/,Ra=/\S/,Sa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Ta=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,O=navigator.userAgent, -va=false,P=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,Q=Array.prototype.slice,wa=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(typeof a==="string")if((d=Pa.exec(a))&&(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:r;if(a=Ta.exec(a))if(c.isPlainObject(b)){a=[r.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=ra([d[1]], -[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}}else{if(b=r.getElementById(d[2])){if(b.id!==d[2])return S.find(a);this.length=1;this[0]=b}this.context=r;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=r;a=r.getElementsByTagName(a)}else return!b||b.jquery?(b||S).find(a):c(b).find(a);else if(c.isFunction(a))return S.ready(a);if(a.selector!==v){this.selector=a.selector;this.context=a.context}return c.isArray(a)?this.setArray(a):c.makeArray(a, -this)},selector:"",jquery:"1.4.1",length:0,size:function(){return this.length},toArray:function(){return Q.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){a=c(a||null);a.prevObject=this;a.context=this.context;if(b==="find")a.selector=this.selector+(this.selector?" ":"")+d;else if(b)a.selector=this.selector+"."+b+"("+d+")";return a},setArray:function(a){this.length=0;ba.apply(this,a);return this},each:function(a,b){return c.each(this, -a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(r,c);else P&&P.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(Q.apply(this,arguments),"slice",Q.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice}; -c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,i,j,n;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a";var e=d.getElementsByTagName("*"),i=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!i)){c.support= -{leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(i.getAttribute("style")),hrefNormalized:i.getAttribute("href")==="/a",opacity:/^0.55$/.test(i.style.opacity),cssFloat:!!i.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:r.createElement("select").appendChild(r.createElement("option")).selected,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null}; -b.type="text/javascript";try{b.appendChild(r.createTextNode("window."+f+"=1;"))}catch(j){}a.insertBefore(b,a.firstChild);if(z[f]){c.support.scriptEval=true;delete z[f]}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function n(){c.support.noCloneEvent=false;d.detachEvent("onclick",n)});d.cloneNode(true).fireEvent("onclick")}d=r.createElement("div");d.innerHTML="";a=r.createDocumentFragment();a.appendChild(d.firstChild); -c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var n=r.createElement("div");n.style.width=n.style.paddingLeft="1px";r.body.appendChild(n);c.boxModel=c.support.boxModel=n.offsetWidth===2;r.body.removeChild(n).style.display="none"});a=function(n){var o=r.createElement("div");n="on"+n;var m=n in o;if(!m){o.setAttribute(n,"return;");m=typeof o[n]==="function"}return m};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=i=null}})();c.props= -{"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ua=0,xa={},Va={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==z?xa:a;var f=a[G],e=c.cache;if(!b&&!f)return null;f||(f=++Ua);if(typeof b==="object"){a[G]=f;e=e[f]=c.extend(true, -{},b)}else e=e[f]?e[f]:typeof d==="undefined"?Va:(e[f]={});if(d!==v){a[G]=f;e[b]=d}return typeof b==="string"?e[b]:e}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==z?xa:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{try{delete a[G]}catch(i){a.removeAttribute&&a.removeAttribute(G)}delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this, -a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===v){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===v&&this.length)f=c.data(this[0],a);return f===v&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d); -return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===v)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]|| -a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var ya=/[\n\t]/g,ca=/\s+/,Wa=/\r/g,Xa=/href|src|style/,Ya=/(button|input)/i,Za=/(button|input|object|select|textarea)/i,$a=/^(a|area)$/i,za=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(o){var m= -c(this);m.addClass(a.call(this,o,m.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===v){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value|| -{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var i=b?d:0;for(d=b?d+1:e.length;i=0;else if(c.nodeName(this,"select")){var x=c.makeArray(s);c("option",this).each(function(){this.selected=c.inArray(c(this).val(),x)>=0});if(!x.length)this.selectedIndex=-1}else this.value=s}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return v;if(f&&b in c.attrFn)return c(a)[b](d); -f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==v;b=f&&c.props[b]||b;if(a.nodeType===1){var i=Xa.test(b);if(b in a&&f&&!i){if(e){b==="type"&&Ya.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:Za.test(a.nodeName)||$a.test(a.nodeName)&&a.href?0:v;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText= -""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&i?a.getAttribute(b,2):a.getAttribute(b);return a===null?v:a}return c.style(a,b,d)}});var ab=function(a){return a.replace(/[^\w\s\.\|`]/g,function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==z&&!a.frameElement)a=z;if(!d.guid)d.guid=c.guid++;if(f!==v){d=c.proxy(d);d.data=f}var e=c.data(a,"events")||c.data(a,"events",{}),i=c.data(a,"handle"),j;if(!i){j= -function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(j.elem,arguments):v};i=c.data(a,"handle",j)}if(i){i.elem=a;b=b.split(/\s+/);for(var n,o=0;n=b[o++];){var m=n.split(".");n=m.shift();if(o>1){d=c.proxy(d);if(f!==v)d.data=f}d.type=m.slice(0).sort().join(".");var s=e[n],x=this.special[n]||{};if(!s){s=e[n]={};if(!x.setup||x.setup.call(a,f,m,d)===false)if(a.addEventListener)a.addEventListener(n,i,false);else a.attachEvent&&a.attachEvent("on"+n,i)}if(x.add)if((m=x.add.call(a, -d,f,m,s))&&c.isFunction(m)){m.guid=m.guid||d.guid;m.data=m.data||d.data;m.type=m.type||d.type;d=m}s[d.guid]=d;this.global[n]=true}a=null}}},global:{},remove:function(a,b,d){if(!(a.nodeType===3||a.nodeType===8)){var f=c.data(a,"events"),e,i,j;if(f){if(b===v||typeof b==="string"&&b.charAt(0)===".")for(i in f)this.remove(a,i+(b||""));else{if(b.type){d=b.handler;b=b.type}b=b.split(/\s+/);for(var n=0;i=b[n++];){var o=i.split(".");i=o.shift();var m=!o.length,s=c.map(o.slice(0).sort(),ab);s=new RegExp("(^|\\.)"+ -s.join("\\.(?:.*\\.)?")+"(\\.|$)");var x=this.special[i]||{};if(f[i]){if(d){j=f[i][d.guid];delete f[i][d.guid]}else for(var A in f[i])if(m||s.test(f[i][A].type))delete f[i][A];x.remove&&x.remove.call(a,o,j);for(e in f[i])break;if(!e){if(!x.teardown||x.teardown.call(a,o)===false)if(a.removeEventListener)a.removeEventListener(i,c.data(a,"handle"),false);else a.detachEvent&&a.detachEvent("on"+i,c.data(a,"handle"));e=null;delete f[i]}}}}for(e in f)break;if(!e){if(A=c.data(a,"handle"))A.elem=null;c.removeData(a, -"events");c.removeData(a,"handle")}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();this.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return v;a.result=v;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d, -b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(i){}if(!a.isPropagationStopped()&&f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){d=a.target;var j;if(!(c.nodeName(d,"a")&&e==="click")&&!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()])){try{if(d[e]){if(j=d["on"+e])d["on"+e]=null;this.triggered=true;d[e]()}}catch(n){}if(j)d["on"+e]=j;this.triggered=false}}},handle:function(a){var b, -d;a=arguments[0]=c.event.fix(a||z.event);a.currentTarget=this;d=a.type.split(".");a.type=d.shift();b=!d.length&&!a.exclusive;var f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)");d=(c.data(this,"events")||{})[a.type];for(var e in d){var i=d[e];if(b||f.test(i.type)){a.handler=i;a.data=i.data;i=i.apply(this,arguments);if(i!==v){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), -fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||r;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=r.documentElement;d=r.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop|| -d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==v)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a,b){c.extend(a,b||{});a.guid+=b.selector+b.live;b.liveProxy=a;c.event.add(this,b.live,na,b)},remove:function(a){if(a.length){var b= -0,d=new RegExp("(^|\\.)"+a[0]+"(\\.|$)");c.each(c.data(this,"events").live||{},function(){d.test(this.type)&&b++});b<1&&c.event.remove(this,a[0],na)}},special:{}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true}; -c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y};var Aa=function(a){for(var b= -a.relatedTarget;b&&b!==this;)try{b=b.parentNode}catch(d){break}if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}},Ba=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ba:Aa,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ba:Aa)}}});if(!c.support.submitBubbles)c.event.special.submit={setup:function(a,b,d){if(this.nodeName.toLowerCase()!== -"form"){c.event.add(this,"click.specialSubmit."+d.guid,function(f){var e=f.target,i=e.type;if((i==="submit"||i==="image")&&c(e).closest("form").length)return ma("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit."+d.guid,function(f){var e=f.target,i=e.type;if((i==="text"||i==="password")&&c(e).closest("form").length&&f.keyCode===13)return ma("submit",this,arguments)})}else return false},remove:function(a,b){c.event.remove(this,"click.specialSubmit"+(b?"."+b.guid:""));c.event.remove(this, -"keypress.specialSubmit"+(b?"."+b.guid:""))}};if(!c.support.changeBubbles){var da=/textarea|input|select/i;function Ca(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d}function ea(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Ca(d);if(a.type!=="focusout"|| -d.type!=="radio")c.data(d,"_change_data",e);if(!(f===v||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}}c.event.special.change={filters:{focusout:ea,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return ea.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return ea.call(this,a)},beforeactivate:function(a){a= -a.target;a.nodeName.toLowerCase()==="input"&&a.type==="radio"&&c.data(a,"_change_data",Ca(a))}},setup:function(a,b,d){for(var f in T)c.event.add(this,f+".specialChange."+d.guid,T[f]);return da.test(this.nodeName)},remove:function(a,b){for(var d in T)c.event.remove(this,d+".specialChange"+(b?"."+b.guid:""),T[d]);return da.test(this.nodeName)}};var T=c.event.special.change.filters}r.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this, -f)}c.event.special[b]={setup:function(){this.addEventListener(a,d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var i in d)this[b](i,f,d[i],e);return this}if(c.isFunction(f)){e=f;f=v}var j=b==="one"?c.proxy(e,function(n){c(this).unbind(n,j);return e.apply(this,arguments)}):e;return d==="unload"&&b!=="one"?this.one(d,f,e):this.each(function(){c.event.add(this,d,j,f)})}});c.fn.extend({unbind:function(a, -b){if(typeof a==="object"&&!a.preventDefault){for(var d in a)this.unbind(d,a[d]);return this}return this.each(function(){c.event.remove(this,a,b)})},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},toggle:function(a){for(var b=arguments,d=1;d0){y=t;break}}t=t[g]}l[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,i=Object.prototype.toString,j=false,n=true;[0,0].sort(function(){n=false;return 0});var o=function(g,h,k,l){k=k||[];var q=h=h||r;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g|| -typeof g!=="string")return k;for(var p=[],u,t,y,R,H=true,M=w(h),I=g;(f.exec(""),u=f.exec(I))!==null;){I=u[3];p.push(u[1]);if(u[2]){R=u[3];break}}if(p.length>1&&s.exec(g))if(p.length===2&&m.relative[p[0]])t=fa(p[0]+p[1],h);else for(t=m.relative[p[0]]?[h]:o(p.shift(),h);p.length;){g=p.shift();if(m.relative[g])g+=p.shift();t=fa(g,t)}else{if(!l&&p.length>1&&h.nodeType===9&&!M&&m.match.ID.test(p[0])&&!m.match.ID.test(p[p.length-1])){u=o.find(p.shift(),h,M);h=u.expr?o.filter(u.expr,u.set)[0]:u.set[0]}if(h){u= -l?{expr:p.pop(),set:A(l)}:o.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=u.expr?o.filter(u.expr,u.set):u.set;if(p.length>0)y=A(t);else H=false;for(;p.length;){var D=p.pop();u=D;if(m.relative[D])u=p.pop();else D="";if(u==null)u=h;m.relative[D](y,u,M)}}else y=[]}y||(y=t);y||o.error(D||g);if(i.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))k.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&& -y[g].nodeType===1&&k.push(t[g]);else k.push.apply(k,y);else A(y,k);if(R){o(R,q,k,l);o.uniqueSort(k)}return k};o.uniqueSort=function(g){if(C){j=n;g.sort(C);if(j)for(var h=1;h":function(g,h){var k=typeof h==="string";if(k&&!/\W/.test(h)){h=h.toLowerCase();for(var l=0,q=g.length;l=0))k||l.push(u);else if(k)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&& -"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,k,l,q,p){h=g[1].replace(/\\/g,"");if(!p&&m.attrMap[h])g[1]=m.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,k,l,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=o(g[3],null,null,h);else{g=o.filter(g[3],h,k,true^q);k||l.push.apply(l,g);return false}else if(m.match.POS.test(g[0])||m.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true); -return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,k){return!!o(k[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"=== -g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,h){return h===0},last:function(g,h,k,l){return h===l.length-1},even:function(g,h){return h%2=== -0},odd:function(g,h){return h%2===1},lt:function(g,h,k){return hk[3]-0},nth:function(g,h,k){return k[3]-0===h},eq:function(g,h,k){return k[3]-0===h}},filter:{PSEUDO:function(g,h,k,l){var q=h[1],p=m.filters[q];if(p)return p(g,k,h,l);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=h[3];k=0;for(l=h.length;k= -0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var k=h[1];g=m.attrHandle[k]?m.attrHandle[k](g):g[k]!=null?g[k]:g.getAttribute(k);k=g+"";var l=h[2];h=h[4];return g==null?l==="!=":l==="="?k===h:l==="*="?k.indexOf(h)>=0:l==="~="?(" "+k+" ").indexOf(h)>=0:!h?k&&g!==false:l==="!="?k!==h:l==="^="? -k.indexOf(h)===0:l==="$="?k.substr(k.length-h.length)===h:l==="|="?k===h||k.substr(0,h.length+1)===h+"-":false},POS:function(g,h,k,l){var q=m.setFilters[h[2]];if(q)return q(g,k,h,l)}}},s=m.match.POS;for(var x in m.match){m.match[x]=new RegExp(m.match[x].source+/(?![^\[]*\])(?![^\(]*\))/.source);m.leftMatch[x]=new RegExp(/(^(?:.|\r|\n)*?)/.source+m.match[x].source.replace(/\\(\d+)/g,function(g,h){return"\\"+(h-0+1)}))}var A=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g}; -try{Array.prototype.slice.call(r.documentElement.childNodes,0)}catch(B){A=function(g,h){h=h||[];if(i.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var k=0,l=g.length;k";var k=r.documentElement;k.insertBefore(g,k.firstChild);if(r.getElementById(h)){m.find.ID=function(l,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(l[1]))?q.id===l[1]||typeof q.getAttributeNode!=="undefined"&&q.getAttributeNode("id").nodeValue===l[1]?[q]:v:[]};m.filter.ID=function(l,q){var p=typeof l.getAttributeNode!=="undefined"&&l.getAttributeNode("id"); -return l.nodeType===1&&p&&p.nodeValue===q}}k.removeChild(g);k=g=null})();(function(){var g=r.createElement("div");g.appendChild(r.createComment(""));if(g.getElementsByTagName("*").length>0)m.find.TAG=function(h,k){k=k.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var l=0;k[l];l++)k[l].nodeType===1&&h.push(k[l]);k=h}return k};g.innerHTML="";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")m.attrHandle.href=function(h){return h.getAttribute("href", -2)};g=null})();r.querySelectorAll&&function(){var g=o,h=r.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){o=function(l,q,p,u){q=q||r;if(!u&&q.nodeType===9&&!w(q))try{return A(q.querySelectorAll(l),p)}catch(t){}return g(l,q,p,u)};for(var k in g)o[k]=g[k];h=null}}();(function(){var g=r.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length=== -0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){m.order.splice(1,0,"CLASS");m.find.CLASS=function(h,k,l){if(typeof k.getElementsByClassName!=="undefined"&&!l)return k.getElementsByClassName(h[1])};g=null}}})();var E=r.compareDocumentPosition?function(g,h){return g.compareDocumentPosition(h)&16}:function(g,h){return g!==h&&(g.contains?g.contains(h):true)},w=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},fa=function(g,h){var k=[], -l="",q;for(h=h.nodeType?[h]:h;q=m.match.PSEUDO.exec(g);){l+=q[0];g=g.replace(m.match.PSEUDO,"")}g=m.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var i=d;i0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,i={},j;if(f&&a.length){e=0;for(var n=a.length;e --1:c(f).is(e)){d.push({selector:j,elem:f});delete i[j]}}f=f.parentNode}}return d}var o=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(m,s){for(;s&&s.ownerDocument&&s!==b;){if(o?o.index(s)>-1:c(s).is(a))return s;s=s.parentNode}return null})},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(), -a);return this.pushStack(pa(a[0])||pa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")}, -nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);bb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e): -e;if((this.length>1||db.test(f))&&cb.test(a))e=e.reverse();return this.pushStack(e,a,Q.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===v||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!== -b&&d.push(a);return d}});var Fa=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ga=/(<([\w:]+)[^>]*?)\/>/g,eb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,Ha=/<([\w:]+)/,fb=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"], -col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==v)return this.empty().append((this[0]&&this[0].ownerDocument||r).createTextNode(a));return c.getText(this)}, -wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length? -d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments, -false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&& -!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Fa,"").replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){qa(this,b);qa(this.find("*"),b.find("*"))}return b},html:function(a){if(a===v)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Fa,""):null;else if(typeof a==="string"&&!/