├── .gitignore ├── DETAILS.md ├── LICENSE.txt ├── README.md ├── img ├── claisen_scheme.png └── claisen_table4.png ├── scripts ├── SNIP.md ├── pyquiver.ipynb ├── skodgetruhlar.ipynb └── snip.awk ├── src ├── AUTOQUIVER.md ├── autoquiver.py ├── config.py ├── constants.py ├── kie.py ├── orca.py ├── quiver.py ├── settings.py ├── utility.py └── weights.dat └── tutorial ├── TUTORIAL.md ├── gaussian ├── claisen_demo.config ├── claisen_gs.out └── claisen_ts.out ├── orca ├── claisen_demo.config ├── claisen_gs.xyz ├── claisen_gs_freq.hess ├── claisen_gs_freq.inp ├── claisen_gs_freq.out ├── claisen_ts.xyz ├── claisen_ts_freq.hess ├── claisen_ts_freq.inp └── claisen_ts_freq.out └── pyquiver ├── claisen_demo.config ├── claisen_gs.qin └── claisen_ts.qin /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *#* 3 | .ipynb_checkpoints/* 4 | *.pyc 5 | *.csv 6 | *DS_Store* 7 | .ipynb_checkpoints 8 | -------------------------------------------------------------------------------- /DETAILS.md: -------------------------------------------------------------------------------- 1 | ## Technical Details 2 | 3 | - [Compatibility with QUIVER](#compatibility-with-quiver) 4 | - [How to Invoke PyQuiver](#how-to-invoke-pyquiver) 5 | - [`.config` Files](#config-files) 6 | - [Input Files](#input-files) 7 | - [PyQuiver Standard](#pyquiver-standard) 8 | - [Miscellaneous Notes](#miscellaneous-notes) 9 | - [References](#references) 10 | 11 | ### Compatibility with QUIVER 12 | 13 | The development of *PyQuiver* was inspired by the [original](#ref2) Fortran program [QUIVER](https://github.com/ekwan/quiver). *PyQuiver* is designed to be as compatible as possible with the original QUIVER program, but to clarify some ambiguity in choice of masses, configuration files need to be updated for use with *PyQuiver*. See the [Configuration](#config-files) section for detail. 14 | 15 | ### How to Invoke PyQuiver 16 | 17 | *PyQuiver* can be controlled from the command line or its Python API. 18 | 19 | To run *PyQuiver* from the command line, issue the following command from the `src/` directory: 20 | 21 | ``` 22 | python quiver.py config_file ground_state_file transition_state_file 23 | ``` 24 | 25 | For more details, run `python quiver.py -h` to display the following help message: 26 | 27 | ``` 28 | usage: quiver.py [-h] [-v] [-s STYLE] config gs ts 29 | 30 | PyQuiver calculates KIEs and EIEs based on a ground and transition state file. 31 | 32 | positional arguments: 33 | config configuration file path 34 | gs ground state file path 35 | ts transition state file path 36 | 37 | optional arguments: 38 | -h, --help show this help message and exit 39 | -v, --verbose when the verbose flag is set debug information is 40 | printed 41 | -s STYLE, --style STYLE 42 | style of input files (g09, orca, or pyquiver) 43 | ``` 44 | 45 | This command will calculate the KIEs or EIEs associated with the isotopic substitutions specified in the configuration file. For details, see the tutorial above. 46 | 47 | *PyQuiver* also has a Python API. The advantage of this interface is that it exposes the underlying Python objects. This allows you to run custom calculations, automate routine calculations, and inspect the internal data directly. 48 | 49 | An IPython Notebook is provided as a demonstration. To try it out, move to the `scripts/` directory and run the command `ipython notebook`. Then open the `pyquiver.ipynb` notebook file. An IPython Notebook `skodgetruhlar.ipynb` is also provided to calculate KIEs corrected with the Skodge Truhlar tunnelling equation. (see ref. 4) 50 | 51 | ### .config Files 52 | 53 | Calculations performed in *PyQuiver* require a configuration file to specify the parameters (such as scaling factor and temperature) and isotopologue substitution rules. 54 | 55 | Each configuration file is a plain text file with the following properties: 56 | 57 | * blank lines and lines starting with `#` are ignored 58 | * anything after `#` within a line is assumed to be a comment 59 | * fields in directives are separated by spaces. 60 | 61 | Valid configuration files have all of the following directives: 62 | 63 | * `scaling`: a linear factor by which to scale the frequencies 64 | * `imag_threshold`: the threshold (in units cm^-1) that defines the cutoff between small and large imaginary frequencies used to distinguish EIE from KIE calculations (typical value: 50) 65 | * `temperature`: the temperature in Kelvin 66 | * `reference_isoto[pomer/logue]`: possible values are `none` or the name of an isotopologue. If `none` is specified, the absolute KIEs will be reported. If the name of an isotopologue is specified, all KIEs will be divided the KIE values for this isotopologue. 67 | * `mass_override_isot[pomer/logue]`: possible values are "default" or the name of an isotopologue. If the value "default" is specified, the masses of the light isotopologue will be the defaults found in `weights.dat`. If the name of an isotopolgoue is given, then that isotopologue is then used to replace the default mass behaviour of PyQuiver at a particular atom. For example, if the isotopomer `C2` replaces carbon 2 with 13C, then specifying `mass_overide_isotopomer C2` will place carbon-13 at C2 *for every KIE calculation*. 68 | * `isoto[pomer/logue]`: the rule used for isotopic substitution. The expected fields are `name ground_state_atom_number transition_state_atom_number substitution`. The final field, `substitution` must correspond to a valid substitution weight. These weights are specified in `weights.dat` (e.g., `13C`, `18O`, `2D`). 69 | 70 | ### Input Files 71 | 72 | *PyQuiver* assumes that the ground state file and transition state file are the outputs of a Gaussian09 `freq` job. To change this assumption *PyQuiver* can accept an additional command-line argument corresponding to the input file style. 73 | 74 | Currently, *PyQuiver* can automatically read output files from the following formats: 75 | * Gaussian 2009. Style name: `g09` (This also works with Gaussian 2016.) 76 | * ORCA. Style name: `orca` (Reads the `.hess` files generated by any frequency job.) 77 | * PyQuiver Standard. Style name: `pyquiver` 78 | 79 | To specify a format other than `g09` from the command line, run with the `-s` flag. For instance: 80 | 81 | ``` 82 | python quiver.py -s pyquiver ../tutorial/pyquiver/claisen_demo.config ../tutorial/pyquiver/claisen_gs.qin ../pyquiver/claisen_ts.qin 83 | ``` 84 | 85 | would execute the example *PyQuiver* job on the Claisen system using the *PyQuiver* standard input files. This allows you to adapt PyQuiver to other electronic structure programs. (We would be pleased to offer some advice on how to accomplish this.) 86 | 87 | ### PyQuiver Standard 88 | 89 | The *PyQuiver* Standard is a generic format for the output of an electronic structure program in plain-text outlined as follows: 90 | 91 | * The first line of a file contains the number of atoms (*n*). 92 | * The next *n* lines define the geometry. Each line should be of the form: 93 | 94 | ``` 95 | CenterNumber,AtomicNumber,XPosition,YPosition,ZPosition 96 | ``` 97 | 98 | The positions should be provided in units of Angstroms. The center number is the atom index (ranging from 0 and *n-1*, inclusive). 99 | * The next line should contain the lower triangular Cartesian Hessian matrix with no line breaks. In particular, if *H* is the Hessian matrix then *H3p+i,3q+j* corresponds to taking derivatives with respect to atom *p* moving in the *i*-th coordinate and atom *q* moving in the *j*-th coordinate (*i* and *j* run across the three cartesian coordinates). The entries in the serialized form should be separated by commas. The serialization should occur by stringing together the rows (truncated at the main diagonal). For example, suppose the following is the lower-right triangular form of the Cartesian Hessian for a one atom system: 100 | 101 | ``` 102 | 1.0 103 | 2.0 3.0 104 | 4.0 5.0 6.0 105 | ``` 106 | 107 | then the *PyQuiver* would expect the following line: 108 | 109 | ``` 110 | 1.0,2.0,3.0,4.0,5.0,6.0 111 | ``` 112 | 113 | * Example *PyQuiver* standard input files are available in the `tutorial/pyquiver/` directory. The files `claisen_gs.qin` and `claisen_ts.qin` are *PyQuiver* input files corresponding to the example Claisen system discussed in the tutorial. 114 | 115 | If input files are provided in a known format other than the *PyQuiver* standard, *PyQuiver* can dump the appropriate *PyQuiver* input files. To do this load the appropriate system (ex. `gs = System("./ground_state.g09")`) and then run `gs.dump_pyquiver_input_file()` which will create the appropriate input file at the same path with the extension `.qin`. 116 | 117 | ### Miscellaneous Notes 118 | 119 | The internal workings of *PyQuiver* are relatively simple. Essentially, a KIE calculation involves parsing the Hessian, mass-weighting, diagonalization with `np.eigvalsh`, calculation of the reduced isotopic partition functions, substitution into the Bigeleisen-Mayer equation, and tunnelling corrections. The tunnelling corrections are expected to work well for heavy atoms, but poorly for hydrogen, especially when considering primary KIEs. 120 | 121 | The frequencies can optionally be scaled (see ref. 3), but this will probably not help much (the harmonic approximation is usually quite adequate). Note that *PyQuiver* is recognizees linear vs. non-linear molecules and removes the appropriate number of small translational/rotational modes. (The `frequency_threshold` keyword has been deprecated and is now ignored.) 122 | 123 | The performance of *PyQuiver* is generally excellent, even for large systems. This is largely because of the efficiency of the `np.eighvalsh` implementation. Note that when multiple isotopomers are calculated using the same configuration file, *PyQuiver* will recalculate the frequencies for the reference isotopomer repeatedly (i.e., once for every isotopomer). This should not be relevant for routine use. However, it could be avoided by using the *PyQuiver* API. 124 | 125 | There is a known issue with getting PyQuiver to work on Cygwin systems due to a problem processing file paths correctly. Additionally it seems to be hard to get NumPy installed properly on such systems. We are working on a fix--please contact me if you require this urgently. (The program works properly on all other kinds unix/linux, as far as we know). 126 | 127 | Please note that the verbose flag should be set in the Gaussian route card. For example: 128 | ` #p b3lyp 6-31g* freq=noraman` 129 | This will ensure that Gaussian places the Hessian in the archive string at the end of the file. (Not including the verbose flag will cause an error.) 130 | 131 | Tunnelling corrections work best for heavy-atom KIEs. H/D KIEs are more challenging (see the work of the Singleton and Truhlar groups for more details). 132 | 133 | ### References 134 | 135 | 1. **Bigeleisen-Mayer theory:** 136 | * Bigeleisen, J.; Mayer, M.G. *J. Chem. Phys.* **1947**, *15*, 261. 137 | * Wolfsberg, M. *Acc. Chem. Res.*, **1972**, *5*, 225. 138 | * Wolfsberg, M. *et al.* Isotope Effects in the Chemical, Geological, and Bio Sciences 139 | 2. **QUIVER:** 140 | * Saunders, M.; Laidig, K.E. Wolfsberg, M. *J. Am. Chem. Soc.*, **1988**, *111*, 8989. 141 | 3. **Scaling Factors:** 142 | * Wong *et al.* *Chem. Phys. Lett.* **1996**, *256*, 391-399. 143 | * Radom *et al.* *J Phys. Chem.* **1996**, *100*, 16502. 144 | 4. **Tunnelling Corrections:** 145 | * Bell, R.P. *Chem. Soc. Rev.* **1974**, *3*, 513. 146 | * Skodge, R.T.; Truhlar, D.G. *J. Phys. Chem.* **1981**, *85*, 624-628 147 | 5. **Claisen Rearragenent KIEs:** 148 | * Meyer, M.P.; DelMonte, A.J.; Singleton, D.A. *J. Am. Chem. Soc.*, **1999**, *121*, 10865-10874. 149 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2016 Thayer L. Anderson and Eugene E. Kwan 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # *PyQuiver* 2 | 3 | *A user-friendly program for calculating isotope effects.* 4 | 5 | *PyQuiver* is an open-source Python program for calculating kinetic isotope effects (KIEs) and equilibrium isotope effects (EIEs) using harmonic frequencies and the Bigeleisen-Mayer equation. *PyQuiver* requires Cartesian Hessian matrices, which can be calculated using any electronic structure program. 6 | 7 | ### Features 8 | 9 | * automatically read frequencies from [`Gaussian`](http://www.gaussian.com/g_prod/g09.htm) and [`ORCA`](https://orcaforum.kofo.mpg.de/app.php/portal) output files 10 | * excellent performance for larger systems 11 | * custom isotopic substitutions and arbitrary temperature 12 | * tunnelling corrections: Wigner and Bell inverted parabola 13 | * run via command line or simple Python API 14 | 15 | ### Installation 16 | 17 | *PyQuiver* requires Python 3 and [`numpy`](https://numpy.org). No other libraries are necessary. 18 | 19 | 1. Install [Python](https://www.continuum.io/downloads) if necesary. The standard Anaconda distribution will contain the necessary dependencies. 20 | 2. Install [git](https://git-scm.com/downloads). git comes pre-installed on most Linux distributions and Macs. 21 | 3. Clone the repository: `git clone https://github.com/ekwan/PyQuiver.git`. (If you don't want to deal with `git`, click on the green "clone or download" button on this github repository page and click on "Download ZIP" to receive an archive.) 22 | 23 | *PyQuiver* has been tested on PC, Mac, and Linux platforms. 24 | 25 | ## Tutorial 26 | 27 | To learn how to use *PyQuiver*, please look at the [tutorial](tutorial/TUTORIAL.md). 28 | 29 | ## Further Reading 30 | 31 | * [Technical Details](DETAILS.md) (compatibility with QUIVER, how to invoke PyQuiver, `.config` files, input files, the PyQuiver Standard format, miscellaneous notes) 32 | * [AutoQuiver](src/AUTOQUIVER.md) (how to run PyQuiver on many related files) 33 | * [Snipping Utility](scripts/SNIP.md) (how to "snip" out only the relevant parts of a Gaussian output file for publication or archiving purposes) 34 | 35 | ## Authors 36 | 37 | *PyQuiver* was written by Thayer Anderson and Eugene Kwan. Please email `ekwan16@gmail.com` with any questions. We will gladly try to help you. 38 | 39 | We thank Gregor Giesen for writing the code to read ORCA output. We thank Corin Wagen for miscellaneous optimization. We also thank Christoph Riplinger and Giovanni Bistoni for valuable discussions. 40 | 41 | ## How to Cite 42 | 43 | Anderson, T.L.; Kwan, E.E. *PyQuiver* **2020**, `www.github.com/ekwan/PyQuiver` 44 | 45 | ## License 46 | 47 | This project is licensed under the Apache License, Version 2.0. See `LICENSE.txt` for full terms and conditions. 48 | 49 | *Copyright 2020 by Thayer L. Anderson and Eugene E. Kwan* 50 | -------------------------------------------------------------------------------- /img/claisen_scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekwan/PyQuiver/1e01d59d4ee54fdce5c1719ad74eeea651a00867/img/claisen_scheme.png -------------------------------------------------------------------------------- /img/claisen_table4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekwan/PyQuiver/1e01d59d4ee54fdce5c1719ad74eeea651a00867/img/claisen_table4.png -------------------------------------------------------------------------------- /scripts/SNIP.md: -------------------------------------------------------------------------------- 1 | ### Snipping Utility 2 | 3 | The AWK script `snip.awk` is provided to convert Gaussian `.out` files into `.snip` files. These "snips" are contain only the geometry, energies, and frequencies, but retain enough of the Gaussian format for PyQuiver to read them. This feature is useful if you have many files and want to "compress" them to save room. 4 | 5 | To convert a collection of `.out` files to their corresponding `.snip` files: 6 | 7 | `awk -f snip.awk *.out` 8 | 9 | Note that this will overwrite any existing snips with the same name without warning. (`awk` is a standard scripting langauge available on any platform.) 10 | -------------------------------------------------------------------------------- /scripts/pyquiver.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# *PyQuiver*\n", 8 | "This is an IPython Notebook interface for the *PyQuiver* package. The code below will guide you through using *PyQuiver* through a native Python interface. The same steps could be reproduced in the Python interpreter or in a `.py` file." 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 1, 14 | "metadata": { 15 | "collapsed": false 16 | }, 17 | "outputs": [ 18 | { 19 | "name": "stdout", 20 | "output_type": "stream", 21 | "text": [ 22 | "Read atomic weight data for 32 elements.\n" 23 | ] 24 | } 25 | ], 26 | "source": [ 27 | "# import the necessary package elements\n", 28 | "import numpy as np\n", 29 | "import sys\n", 30 | "sys.path.append(\"../src\")\n", 31 | "from kie import KIE_Calculation" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "## A Simple Calculation\n", 39 | "\n", 40 | "We can reproduce the command-line interface by creating a KIE_Calculation object:" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": 2, 46 | "metadata": { 47 | "collapsed": false 48 | }, 49 | "outputs": [ 50 | { 51 | "name": "stdout", 52 | "output_type": "stream", 53 | "text": [ 54 | "\n", 55 | "Reading configuration from ../tutorial/gaussian/claisen_demo.config\n", 56 | "Reading data from ../tutorial/gaussian/claisen_gs.out... with style g09\n", 57 | "Molecule is not linear.\n", 58 | "Reading data from ../tutorial/gaussian/claisen_ts.out... with style g09\n", 59 | "Molecule is not linear.\n", 60 | "Config file: ../tutorial/gaussian/claisen_demo.config\n", 61 | "Temperature: 393.0 K\n", 62 | "Scaling: 0.961\n", 63 | "Reference Isotopologue: C5\n", 64 | "Imag threshold (cm-1): 50\n", 65 | " Isotopologue C5, replacement 1: replace gs atom 5 and ts atom 5 with 13C\n", 66 | " Isotopologue C1, replacement 1: replace gs atom 1 and ts atom 1 with 13C\n", 67 | " Isotopologue C2, replacement 1: replace gs atom 2 and ts atom 2 with 13C\n", 68 | " Isotopologue C4, replacement 1: replace gs atom 4 and ts atom 4 with 13C\n", 69 | " Isotopologue C6, replacement 1: replace gs atom 6 and ts atom 6 with 13C\n", 70 | " Isotopologue H/D, replacement 1: replace gs atom 7 and ts atom 7 with 2D\n", 71 | " Isotopologue H/D, replacement 2: replace gs atom 8 and ts atom 8 with 2D\n", 72 | " Isotopologue O3, replacement 1: replace gs atom 3 and ts atom 3 with 17O\n" 73 | ] 74 | } 75 | ], 76 | "source": [ 77 | "claisen_calculation = KIE_Calculation(\"../tutorial/gaussian/claisen_demo.config\", \"../tutorial/gaussian/claisen_gs.out\", \"../tutorial/gaussian/claisen_ts.out\", style=\"g09\")" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "To print the KIEs:" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 3, 90 | "metadata": { 91 | "collapsed": false 92 | }, 93 | "outputs": [ 94 | { 95 | "name": "stdout", 96 | "output_type": "stream", 97 | "text": [ 98 | "The claisen_calculation object:\n", 99 | "\n", 100 | "=== PyQuiver Analysis ===\n", 101 | "Isotopologue uncorrected Wigner inverted parabola\n", 102 | " KIE KIE KIE\n", 103 | "Isotopologue C1 1.0108 1.0125 1.0128 \n", 104 | "Isotopologue C2 1.0000 1.0000 1.0000 \n", 105 | "Isotopologue O3 1.0169 1.0184 1.0187 \n", 106 | "Isotopologue C4 1.0278 1.0305 1.0310 \n", 107 | "Isotopologue C6 1.0129 1.0146 1.0149 \n", 108 | "Isotopologue H/D 0.9530 0.9545 0.9547 \n", 109 | "\n", 110 | "KIEs referenced to isotopologue C5, whose absolute KIEs are:\n", 111 | "Isotopologue C5 1.0019 1.0019 1.0019 \n" 112 | ] 113 | } 114 | ], 115 | "source": [ 116 | "print(\"The claisen_calculation object:\")\n", 117 | "print(claisen_calculation)" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": {}, 123 | "source": [ 124 | "We can also access the KIEs directly via the `KIE` dictionary belonging to our `KIE_Calculation`. This can be useful for automating KIE analyses over a large number of files. This functionality is further developed in the `autoquiver` routine (see below)." 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": 4, 130 | "metadata": { 131 | "collapsed": false 132 | }, 133 | "outputs": [ 134 | { 135 | "name": "stdout", 136 | "output_type": "stream", 137 | "text": [ 138 | "Iterating through the KIEs dictionary:\n", 139 | "Isotopologue C1 1.0108 1.0125 1.0128 \n", 140 | "\n", 141 | "C1\n", 142 | "[1.01083044 1.0124856 1.01277832]\n" 143 | ] 144 | } 145 | ], 146 | "source": [ 147 | "print(\"Iterating through the KIEs dictionary:\")\n", 148 | "for name,kie_object in claisen_calculation.KIES.items():\n", 149 | " # pretty print the underlying KIE object:\n", 150 | " print(kie_object)\n", 151 | " # or pull the name and value directly:\n", 152 | " print(type(kie_object))\n", 153 | " print(kie_object.name)\n", 154 | " print(kie_object.value)\n", 155 | " break" 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "metadata": {}, 161 | "source": [ 162 | "## `System` objects\n", 163 | "\n", 164 | "We didn't have to specify file paths as the targets of our `KIE_Calculation` constructor. Instead, we can work directly with `System` objects, which contain the geometry and Hessian as fields. Below, we load the Claisen ground state and transition state and print the position of the first atom in the ground state and print the first row of the transition state Hessian." 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": 5, 170 | "metadata": { 171 | "collapsed": false 172 | }, 173 | "outputs": [ 174 | { 175 | "name": "stdout", 176 | "output_type": "stream", 177 | "text": [ 178 | "Reading data from ../tutorial/gaussian/claisen_gs.out... with style g09\n", 179 | "Molecule is not linear.\n", 180 | "Reading data from ../tutorial/gaussian/claisen_ts.out... with style g09\n", 181 | "Molecule is not linear.\n", 182 | "Position of atom 0 in the ground state:\n", 183 | "[ 4.77979210e+00 6.82814755e-01 -3.77945233e-05]\n", 184 | "First row of the Carteisan transition state Hessian:\n", 185 | "[ 2.5705678e-01 -2.3007491e-01 -1.6192179e-01 -7.6561780e-02\n", 186 | " 8.4893130e-02 4.6126500e-03 2.1299140e-02 2.0270870e-02\n", 187 | " -9.6391200e-03 -2.2122500e-02 -1.9531000e-02 2.2995300e-03\n", 188 | " -2.5246920e-02 3.7391980e-02 3.6582000e-04 4.6899040e-02\n", 189 | " -4.4453100e-03 2.2383900e-03 -1.5100128e-01 1.2099792e-01\n", 190 | " 8.6293350e-02 -4.7478530e-02 3.2266700e-03 7.3894740e-02\n", 191 | " 6.5812100e-03 -8.0738400e-03 5.6873800e-03 2.4898000e-04\n", 192 | " -2.9655000e-04 2.0240000e-04 1.2257700e-03 3.9790000e-04\n", 193 | " 4.2782000e-04 7.7369000e-04 2.6580000e-05 1.3262000e-04\n", 194 | " -4.6166200e-03 -1.5256600e-03 -2.7586400e-03 -7.0569600e-03\n", 195 | " -3.2577800e-03 -1.8351500e-03]\n" 196 | ] 197 | } 198 | ], 199 | "source": [ 200 | "from quiver import System\n", 201 | "\n", 202 | "gs = System(\"../tutorial/gaussian/claisen_gs.out\")\n", 203 | "ts = System(\"../tutorial/gaussian/claisen_ts.out\")\n", 204 | "print(\"Position of atom 0 in the ground state:\")\n", 205 | "print(gs.positions[0])\n", 206 | "print(\"First row of the Carteisan transition state Hessian:\")\n", 207 | "print(ts.hessian[0])" 208 | ] 209 | }, 210 | { 211 | "cell_type": "markdown", 212 | "metadata": {}, 213 | "source": [ 214 | "### Running calculations with `System` objects\n", 215 | "\n", 216 | "To run a KIE calculation with two `System` objects, we simply pass them into the relevant fields of a `KIE_Calculation` object:" 217 | ] 218 | }, 219 | { 220 | "cell_type": "code", 221 | "execution_count": 6, 222 | "metadata": { 223 | "collapsed": false 224 | }, 225 | "outputs": [ 226 | { 227 | "name": "stdout", 228 | "output_type": "stream", 229 | "text": [ 230 | "\n", 231 | "Reading configuration from ../tutorial/gaussian/claisen_demo.config\n", 232 | "Config file: ../tutorial/gaussian/claisen_demo.config\n", 233 | "Temperature: 393.0 K\n", 234 | "Scaling: 0.961\n", 235 | "Reference Isotopologue: C5\n", 236 | "Imag threshold (cm-1): 50\n", 237 | " Isotopologue C5, replacement 1: replace gs atom 5 and ts atom 5 with 13C\n", 238 | " Isotopologue C1, replacement 1: replace gs atom 1 and ts atom 1 with 13C\n", 239 | " Isotopologue C2, replacement 1: replace gs atom 2 and ts atom 2 with 13C\n", 240 | " Isotopologue C4, replacement 1: replace gs atom 4 and ts atom 4 with 13C\n", 241 | " Isotopologue C6, replacement 1: replace gs atom 6 and ts atom 6 with 13C\n", 242 | " Isotopologue H/D, replacement 1: replace gs atom 7 and ts atom 7 with 2D\n", 243 | " Isotopologue H/D, replacement 2: replace gs atom 8 and ts atom 8 with 2D\n", 244 | " Isotopologue O3, replacement 1: replace gs atom 3 and ts atom 3 with 17O\n", 245 | "\n", 246 | "=== PyQuiver Analysis ===\n", 247 | "Isotopologue uncorrected Wigner inverted parabola\n", 248 | " KIE KIE KIE\n", 249 | "Isotopologue C1 1.0108 1.0125 1.0128 \n", 250 | "Isotopologue C2 1.0000 1.0000 1.0000 \n", 251 | "Isotopologue O3 1.0169 1.0184 1.0187 \n", 252 | "Isotopologue C4 1.0278 1.0305 1.0310 \n", 253 | "Isotopologue C6 1.0129 1.0146 1.0149 \n", 254 | "Isotopologue H/D 0.9530 0.9545 0.9547 \n", 255 | "\n", 256 | "KIEs referenced to isotopologue C5, whose absolute KIEs are:\n", 257 | "Isotopologue C5 1.0019 1.0019 1.0019 \n" 258 | ] 259 | } 260 | ], 261 | "source": [ 262 | "claisen_calculation2 = KIE_Calculation(\"../tutorial/gaussian/claisen_demo.config\", gs, ts)\n", 263 | "print(claisen_calculation2)" 264 | ] 265 | }, 266 | { 267 | "cell_type": "markdown", 268 | "metadata": {}, 269 | "source": [ 270 | "## Building `Isotopologue`s\n", 271 | "Once we have access to the underlying `System` objects, it is easy to make substituted `Isotopologue`s and perform frequency calculations ourselves. To make an `Isotopologue` we need to provide a name, a corresponding `System`, and a list of masses - one for each atom in the `System`.\n", 272 | "\n", 273 | "Let's build the default light ground state `Isotopologue`:" 274 | ] 275 | }, 276 | { 277 | "cell_type": "code", 278 | "execution_count": 7, 279 | "metadata": { 280 | "collapsed": false 281 | }, 282 | "outputs": [ 283 | { 284 | "name": "stdout", 285 | "output_type": "stream", 286 | "text": [ 287 | "Isotopologue: the light ground state\n", 288 | " masses: [12.0, 12.0, 15.9949146, 12.0, 12.0, 12.0, 1.007825, 1.007825, 1.007825, 1.007825, 1.007825, 1.007825, 1.007825, 1.007825]\n" 289 | ] 290 | } 291 | ], 292 | "source": [ 293 | "from quiver import Isotopologue\n", 294 | "from constants import DEFAULT_MASSES\n", 295 | "\n", 296 | "# we build the default masses by using the DEFAULT_MASSES dictionary which maps from atomic numbers to masses\n", 297 | "# default masses in DEFAULT_MASSES are the average atomic weight of each element\n", 298 | "gs_masses = [DEFAULT_MASSES[z] for z in gs.atomic_numbers]\n", 299 | "\n", 300 | "gs_light = Isotopologue(\"the light ground state\", gs, gs_masses)\n", 301 | "print(gs_light)" 302 | ] 303 | }, 304 | { 305 | "cell_type": "markdown", 306 | "metadata": {}, 307 | "source": [ 308 | "### `Isotopologue`s with substitutions\n", 309 | "Now that we know how to make `Isotopologue`s it's very easy to specify isotopic substitutions. Let's put the mysterious isotope carbon-5000 at atom 4:" 310 | ] 311 | }, 312 | { 313 | "cell_type": "code", 314 | "execution_count": 8, 315 | "metadata": { 316 | "collapsed": false 317 | }, 318 | "outputs": [], 319 | "source": [ 320 | "# make a copy of gs_masses\n", 321 | "sub_masses = list(gs_masses)\n", 322 | "# index 3 corresponds to atom number 4\n", 323 | "sub_masses[3] = 5000.0\n", 324 | "\n", 325 | "gs_heavy = Isotopologue(\"the super heavy ground state\", gs, sub_masses)" 326 | ] 327 | }, 328 | { 329 | "cell_type": "markdown", 330 | "metadata": {}, 331 | "source": [ 332 | "### Calculating Reduced Isotopic Partition Function Ratios\n", 333 | "\n", 334 | "KIEs are essentially ratios of reduced isotopic partition functions (RPFRs). To calculate these, we use the function `calculate_rpfr` from the `kie` module. This function takes a tuple of the form `(light, heavy)`, a frequency threshold, a scaling factor, and a temperature. (All of these are discussed in detail in the README.)\n", 335 | "\n", 336 | "`calculate_rpfr` returns four values in a tuple: the first value is the RPFR, the second value is a ratio of large imaginary frequencies (if present), the third value is the frequencies in the light isotopomer, and the fourth value is the frequencies in the heavy isotopomer.\n", 337 | "\n", 338 | "To print the individual contributions to the RPFR, set `quiver.DEBUG = True` in `quiver.py` and restart the kernel. " 339 | ] 340 | }, 341 | { 342 | "cell_type": "code", 343 | "execution_count": 9, 344 | "metadata": { 345 | "collapsed": false 346 | }, 347 | "outputs": [ 348 | { 349 | "name": "stdout", 350 | "output_type": "stream", 351 | "text": [ 352 | "11.208123665562292\n", 353 | "[ 61.631434 131.33940975 149.25478244 235.05384691 253.61000919\n", 354 | " 388.68321807 538.23713827 573.1098416 689.54514791 693.15891096\n", 355 | " 713.75792653 824.87106694 863.15186782 950.01325243 1000.01764911\n", 356 | " 1000.40305289 1034.40340406 1075.63192671 1229.08835092 1266.54977859\n", 357 | " 1298.58236833 1332.35577789 1376.57176191 1456.12070386 1466.42176654\n", 358 | " 1501.64974966 1712.50915119 1738.46586076 2871.66940343 2922.94354739\n", 359 | " 3159.73242676 3181.30165023 3195.17469476 3209.76123403 3263.87810693\n", 360 | " 3278.47225297]\n" 361 | ] 362 | } 363 | ], 364 | "source": [ 365 | "from kie import calculate_rpfr\n", 366 | "gs_rpfr, gs_imag_ratio, gs_light_freqs, gs_heavy_freqs = calculate_rpfr((gs_light, gs_heavy), 50.0, 1.0, 273)\n", 367 | "print(gs_rpfr)\n", 368 | "print(gs_light_freqs)" 369 | ] 370 | }, 371 | { 372 | "cell_type": "markdown", 373 | "metadata": {}, 374 | "source": [ 375 | "## Calculating KIEs from Isotopologues\n", 376 | "It's nice to be able to calculate RPFRs by hand, but there is also an object available to calculate KIEs automatically: the `KIE` class.\n", 377 | "\n", 378 | "We need to create a `KIE` object by passing it a pair of ground states (light and heavy) and a pair of transition states (light and heavy) as well as some temperature information.\n", 379 | "\n", 380 | "We will make the desired substitution at Carbon 4 in the transition state and use a `KIE` object to calculate the KIE." 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": 10, 386 | "metadata": { 387 | "collapsed": false 388 | }, 389 | "outputs": [ 390 | { 391 | "name": "stdout", 392 | "output_type": "stream", 393 | "text": [ 394 | "Isotopologue: the light transition state\n", 395 | " masses: [12.0, 12.0, 15.9949146, 12.0, 12.0, 12.0, 1.007825, 1.007825, 1.007825, 1.007825, 1.007825, 1.007825, 1.007825, 1.007825]\n", 396 | "\n", 397 | "Isotopologue: the heavy transition state\n", 398 | " masses: [12.0, 12.0, 15.9949146, 5000.0, 12.0, 12.0, 1.007825, 1.007825, 1.007825, 1.007825, 1.007825, 1.007825, 1.007825, 1.007825]\n" 399 | ] 400 | } 401 | ], 402 | "source": [ 403 | "ts_masses = [DEFAULT_MASSES[z] for z in gs.atomic_numbers]\n", 404 | "ts_light = Isotopologue(\"the light transition state\", ts, ts_masses)\n", 405 | "print(ts_light)\n", 406 | "print()\n", 407 | "ts_sub_masses = list(ts_masses)\n", 408 | "ts_sub_masses[3] = 5000.0\n", 409 | "ts_heavy = Isotopologue(\"the heavy transition state\", ts, ts_sub_masses)\n", 410 | "print(ts_heavy)\n", 411 | "\n", 412 | "gs_tuple = (gs_light, gs_heavy)\n", 413 | "ts_tuple = (ts_light, ts_heavy)\n", 414 | "\n", 415 | "from kie import KIE\n", 416 | "\n", 417 | "# we make a KIE object using the gs and ts tuples from above for a calculation at 273 degrees K, with a scaling factor of 1.0, and a frequency threshold of 50 cm^-1\n", 418 | "carbon5000_kie = KIE(\"Carbon 5000 at C4 KIE\", gs_tuple, ts_tuple, 273, 1.0, 50.0)" 419 | ] 420 | }, 421 | { 422 | "cell_type": "markdown", 423 | "metadata": {}, 424 | "source": [ 425 | "### Displaying KIEs\n", 426 | "Now, just like before, we have access to a KIE object (earlier we pulled them from the KIES dictionary). These can be printed prettily or you can take the value directly. As expected, we have large normal isotope effects because we used a carbon-5000 nucleus! (The three numbers correspond to the uncorrected, Wigner, and Bell KIEs.)" 427 | ] 428 | }, 429 | { 430 | "cell_type": "code", 431 | "execution_count": 11, 432 | "metadata": { 433 | "collapsed": false 434 | }, 435 | "outputs": [ 436 | { 437 | "name": "stdout", 438 | "output_type": "stream", 439 | "text": [ 440 | "Isotopologue Carbon 5000 at C4 KIE 2.1324 2.3449 2.4192 \n", 441 | "[2.13240475 2.34485535 2.41923705]\n" 442 | ] 443 | } 444 | ], 445 | "source": [ 446 | "print(carbon5000_kie)\n", 447 | "print(carbon5000_kie.value)" 448 | ] 449 | }, 450 | { 451 | "cell_type": "markdown", 452 | "metadata": {}, 453 | "source": [ 454 | "## *PyQuiver* with multiple files: `autoquiver.py`\n", 455 | "A common problem when computing KIEs is the need to screen a large number of levels of theory with numerous ground and transition state files. So far, we have only seen how to make a `KIE_Calculation` object that corresponds to a single configuration, ground state, and transition state.\n", 456 | "\n", 457 | "The `autoquiver` module implements functionality to perform screening over a large number of files. Its command line use is described in depth in the README. Here we will see how to leverage Python to have more sophisticated uses for `autoquiver`. Suppose we have the following files that we want to use as inputs to `PyQuiver`:\n", 458 | "\n", 459 | "```\n", 460 | "config.config\n", 461 | "\n", 462 | "ground_state1.out ts-1.out\n", 463 | "gs1rotomer.out tsrotomer1.out\n", 464 | "GROUNDSTATE2.out transition_state2.out\n", 465 | "gs3.out ts3.out\n", 466 | "```\n", 467 | "\n", 468 | "We want to run *PyQuiver* on pair of files above in the same row using the configuration file `config.config`. The command line functionality of `autoquiver` is great when the file names have a consistent pattern, but here it's not obvious how to generate the proper pairings or detect which files are ground or transition states." 469 | ] 470 | }, 471 | { 472 | "cell_type": "markdown", 473 | "metadata": {}, 474 | "source": [ 475 | "### The Arguments\n", 476 | "The function `autoquiver` (which is the only object available in the `autoquiver` module) requires the following arguments:\n", 477 | "\n", 478 | "* `filepath`: this is the target directory.\n", 479 | "* `config_path`: the location of the configuration file to use for all *PyQuiver* calculations\n", 480 | "* `input_extension`: the file extension for ground and transition state files (default=`.out`)\n", 481 | "* `style`: the style of the ground and transition state files (default=`g09`)\n", 482 | "\n", 483 | "In the README, we discussed the command:\n", 484 | "\n", 485 | "```\n", 486 | "python src/autoquiver.py -e .output auto/ auto/substitutions.config gs ts -\n", 487 | "```\n", 488 | "\n", 489 | "The strings \"gs\" and \"ts\" were used to find ground and transition files. A ground state file needed to have \"gs\" as a substring and likewise for \"gs\" and transition state files. The character \"-\" was used as a field delimiter and \"gs\" and \"ts\" matched if all fields after the first were identical.\n", 490 | "\n", 491 | "That is some sensible default behaviour, but for more complex cases, we can provide the following functions:\n", 492 | "\n", 493 | "* `gs_p`: a function that takes a filename and returns a boolean value. `True` if the filename corresponds to a ground state and `False` if it does not.\n", 494 | "* `ts_p`: a function that takes a filename and returns a boolean value. `True` if the filename corresponds to a transition state and `False` if it does not.\n", 495 | "* `gs_ts_match_p`: a function that takes a ground state filename and a transition state filename and returns a boolean value. `True` if the ground state and transition state match and `Fase` if they do not.\n", 496 | "\n", 497 | "#### `gs_p` and `ts_p`\n", 498 | "For the previous example, we need to detect that all of the following are ground state files:\n", 499 | "\n", 500 | "```\n", 501 | "ground_state1.out\n", 502 | "gs1_rotomer.out\n", 503 | "GROUNDSTATE2.out\n", 504 | "gs3.out\n", 505 | "```\n", 506 | "\n", 507 | "We can accomplish this with the following Python function:" 508 | ] 509 | }, 510 | { 511 | "cell_type": "code", 512 | "execution_count": 12, 513 | "metadata": { 514 | "collapsed": false 515 | }, 516 | "outputs": [], 517 | "source": [ 518 | "def gs_p(filename):\n", 519 | " # convert to lowercase\n", 520 | " filename = filename.lower()\n", 521 | " # remove the _ character\n", 522 | " filename = filename.replace(\"_\", \"\")\n", 523 | " # replace groundstate with gs\n", 524 | " filename = filename.replace(\"groundstate\", \"gs\")\n", 525 | " # check if the modified filename begins with the string \"gs\"\n", 526 | " if filename[:2] == \"gs\":\n", 527 | " return True\n", 528 | " else:\n", 529 | " return False" 530 | ] 531 | }, 532 | { 533 | "cell_type": "markdown", 534 | "metadata": {}, 535 | "source": [ 536 | "Let's test this on all the filenames in the example:" 537 | ] 538 | }, 539 | { 540 | "cell_type": "code", 541 | "execution_count": 13, 542 | "metadata": { 543 | "collapsed": false 544 | }, 545 | "outputs": [ 546 | { 547 | "name": "stdout", 548 | "output_type": "stream", 549 | "text": [ 550 | "Checking ground state matches:\n", 551 | "True\n", 552 | "True\n", 553 | "True\n", 554 | "True\n", 555 | "Checking transition state matches:\n", 556 | "False\n", 557 | "False\n", 558 | "False\n", 559 | "False\n" 560 | ] 561 | } 562 | ], 563 | "source": [ 564 | "gs_filenames = [\"ground_state1.out\", \"gs1_rotomer.out\", \"GROUNDSTATE2.out\", \"gs3.out\"]\n", 565 | "ts_filenames = [\"ts-1.out\", \"tsrotomer1.out\", \"transition_state2.out\", \"ts3.out\"]\n", 566 | "\n", 567 | "print(\"Checking ground state matches:\")\n", 568 | "for n in gs_filenames:\n", 569 | " print(gs_p(n))\n", 570 | "print(\"Checking transition state matches:\")\n", 571 | "for n in ts_filenames:\n", 572 | " print(gs_p(n))" 573 | ] 574 | }, 575 | { 576 | "cell_type": "markdown", 577 | "metadata": {}, 578 | "source": [ 579 | "Lo and behold! The function successfully separated the ground state and transition state files. We can similarly implement `ts_p`:" 580 | ] 581 | }, 582 | { 583 | "cell_type": "code", 584 | "execution_count": 14, 585 | "metadata": { 586 | "collapsed": true 587 | }, 588 | "outputs": [], 589 | "source": [ 590 | "def ts_p(filename):\n", 591 | " # convert to lowercase\n", 592 | " filename = filename.lower()\n", 593 | " # remove the _ character\n", 594 | " filename = filename.replace(\"_\", \"\")\n", 595 | " # replace groundstate with gs\n", 596 | " filename = filename.replace(\"transitionstate\", \"ts\")\n", 597 | " # check if the modified filename begins with the string \"gs\"\n", 598 | " if filename[:2] == \"ts\":\n", 599 | " return True\n", 600 | " else:\n", 601 | " return False" 602 | ] 603 | }, 604 | { 605 | "cell_type": "markdown", 606 | "metadata": {}, 607 | "source": [ 608 | "#### `gs_ts_match_p`\n", 609 | "Now all we have to do is write a function `gs_ts_match_p` that can take two filenames (one ground state and one transition state) and detect if they are matches. There are numerous ways to accomplish this. We will use a basic regular expression to highlight a common method for writing these functions." 610 | ] 611 | }, 612 | { 613 | "cell_type": "code", 614 | "execution_count": 15, 615 | "metadata": { 616 | "collapsed": false 617 | }, 618 | "outputs": [ 619 | { 620 | "name": "stdout", 621 | "output_type": "stream", 622 | "text": [ 623 | "Match: ground_state1.out ts-1.out\n", 624 | "Match: ground_state1.out tsrotomer1.out\n", 625 | "Match: gs1_rotomer.out ts-1.out\n", 626 | "Match: gs1_rotomer.out tsrotomer1.out\n", 627 | "Match: GROUNDSTATE2.out transition_state2.out\n", 628 | "Match: gs3.out ts3.out\n" 629 | ] 630 | } 631 | ], 632 | "source": [ 633 | "gs_filenames = [\"ground_state1.out\", \"gs1_rotomer.out\", \"GROUNDSTATE2.out\", \"gs3.out\"]\n", 634 | "ts_filenames = [\"ts-1.out\", \"tsrotomer1.out\", \"transition_state2.out\", \"ts3.out\"]\n", 635 | "\n", 636 | "import re\n", 637 | "def gs_ts_p(gs_name, ts_name):\n", 638 | " # a regular expression that finds and extracts the first integer substring in a filename\n", 639 | " gs_match = re.search(\".*([0-9]+).*\", gs_name)\n", 640 | " gs_number = gs_match.group(1)\n", 641 | " \n", 642 | " ts_match = re.search(\".*([0-9]+).*\", ts_name)\n", 643 | " ts_number = ts_match.group(1)\n", 644 | " \n", 645 | " if ts_number == gs_number:\n", 646 | " return True\n", 647 | " else:\n", 648 | " return False\n", 649 | " \n", 650 | "import itertools\n", 651 | "for gs_n, ts_n in itertools.product(gs_filenames, ts_filenames):\n", 652 | " if gs_ts_p(gs_n, ts_n):\n", 653 | " print(\"Match: {0} {1}\".format(gs_n, ts_n))" 654 | ] 655 | }, 656 | { 657 | "cell_type": "code", 658 | "execution_count": 16, 659 | "metadata": { 660 | "collapsed": true 661 | }, 662 | "outputs": [], 663 | "source": [ 664 | "# missing example of how to actually use autoquiver" 665 | ] 666 | } 667 | ], 668 | "metadata": { 669 | "kernelspec": { 670 | "display_name": "python_3_7", 671 | "language": "python", 672 | "name": "python_3_7" 673 | }, 674 | "language_info": { 675 | "codemirror_mode": { 676 | "name": "ipython", 677 | "version": 3 678 | }, 679 | "file_extension": ".py", 680 | "mimetype": "text/x-python", 681 | "name": "python", 682 | "nbconvert_exporter": "python", 683 | "pygments_lexer": "ipython3", 684 | "version": "3.7.1" 685 | } 686 | }, 687 | "nbformat": 4, 688 | "nbformat_minor": 2 689 | } 690 | -------------------------------------------------------------------------------- /scripts/skodgetruhlar.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "# *Skodge Truhlar KIE correction*\n", 9 | "This is an IPython Notebook interface to calculate tunnelling corrections via the Skodge Truhlar equations (*J. Phys. Chem.* **1981**, 85, 624-626.) using the *PyQuiver* and *cctk* packages. The code below will guide you through using *PyQuiver* and *cctk* through a native Python interface. The same steps could be reproduced in the Python interpreter or in a `.py` file.\n", 10 | "\n", 11 | "The initial KIE calculation requires hessians for the transition state and the starting material. The Skodge Truhlar equation additionally requires the energies of each SM/PR/TS (single point electronic energies). For Gaussian calculations this will require only the output files for the transition state and pre-transition state calculations. For Orca calculations the .hess and .out files are necessary" 12 | ] 13 | }, 14 | { 15 | "attachments": {}, 16 | "cell_type": "markdown", 17 | "metadata": {}, 18 | "source": [ 19 | "### Setup\n", 20 | "Import necessary package elements.\n", 21 | "sys.path.append should point to the src directory of PyQuiver. Note: Paths for all operating systems (including Windows) should use '/'\n", 22 | "\n", 23 | "cctk can be installed via \"pip install cctk\" or from github (ekwan/cctk)" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "import numpy as np\n", 33 | "import sys\n", 34 | "sys.path.append(\"../src\")\n", 35 | "from kie import KIE_Calculation\n", 36 | "import cctk\n", 37 | "\n", 38 | "\"\"\"Constants are imported from PHYSICAL_CONSTANTS (PyQuiver/src)\n", 39 | "\n", 40 | "Constants:\n", 41 | " h: Planck's constant in J * s\n", 42 | " c: speed of light in cm / s\n", 43 | " kB: Boltzmann's constant in J / K\n", 44 | " Eh: energy of a hartree in J\n", 45 | "\"\"\"\n", 46 | "from constants import PHYSICAL_CONSTANTS\n", 47 | "h = PHYSICAL_CONSTANTS[\"h\"]\n", 48 | "c = PHYSICAL_CONSTANTS[\"c\"]\n", 49 | "kB = PHYSICAL_CONSTANTS[\"kB\"]\n", 50 | "Eh = PHYSICAL_CONSTANTS[\"Eh\"]" 51 | ] 52 | }, 53 | { 54 | "attachments": {}, 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "### Extracting SPE energy from electronic structure calculations\n", 59 | "\n", 60 | "Electronic structure output files are read by cctk depending on the value of the *software* variable. The final molecule is chosen, and its energy is returned." 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "metadata": {}, 67 | "outputs": [], 68 | "source": [ 69 | "def get_energy(software,path):\n", 70 | " \"\"\"Function to get energy from electronic structure calculation\n", 71 | " Args:\n", 72 | " software (string): the electronic structure package used. supported values: `orca` or `gauss`\n", 73 | " path (string): relative or exact path to output file \n", 74 | " \n", 75 | " Returns:\n", 76 | " float: energy in Eh\n", 77 | "\n", 78 | " Raises:\n", 79 | " Exception: if software value passed is not as expected\n", 80 | " \"\"\"\n", 81 | " if software == 'orca':\n", 82 | " ensemble = cctk.OrcaFile.read_file(path).ensemble\n", 83 | " elif software == 'gauss':\n", 84 | " ensemble = cctk.GaussianFile.read_file(path).ensemble\n", 85 | " else:\n", 86 | " raise Exception(\"Check software variable passed\")\n", 87 | " molecule = ensemble.molecules[-1]\n", 88 | " properties_dict = ensemble.get_properties_dict(molecule)\n", 89 | " return properties_dict[\"energy\"]" 90 | ] 91 | }, 92 | { 93 | "attachments": {}, 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "### The Skodge Truhlar equation\n", 98 | "\n", 99 | "The Skodge Truhlar equation is an alternative approximation for calculating transmission coefficients. (*J. Phys. Chem.* **1981**, 85, 624-626.) This strategy might be more applicable in cases of very large frequencies.\n", 100 | "\n", 101 | "The the natural vibrational frequency is calculated from the reduced mass ($\\mu$) and the force constant (k) of the imaginary mode. This is handled automatically in PyQuiver.\n", 102 | "$$ \\nu^\\ddag = {1 \\over{2 \\pi}} * \\sqrt{k^\\ddag \\over \\mu^\\ddag} $$\n", 103 | "\n", 104 | "The ST equation is definited by two key terms $\\alpha$ and $\\beta$, where $ \\alpha = {{2\\pi}\\over{h\\nu^\\ddag}} $ and $ \\beta = {1\\over{k_BT}}$ as well as the barrier height (electronic energy) in the exothermic direction ($V = V_{TS} - max(V_{SM},V_{PR})$):\n", 105 | "\n", 106 | "\n", 107 | "\n", 108 | "The ST equation has two forms depending on the vibrational frequency and temperature.\n", 109 | "\n", 110 | "In cases of a relatively small $\\nu^\\ddag$ and high T ($\\alpha > \\beta$):\n", 111 | "$$ \\kappa = {{\\beta\\pi/\\alpha}\\over{sin(\\beta\\pi/\\alpha)}} - {\\beta\\over{\\alpha - \\beta}}e^{(\\beta-\\alpha)V} $$\n", 112 | "\n", 113 | "Conversely, in cases of a relatively large $\\nu^\\ddag$ and low T ($\\alpha < \\beta$):\n", 114 | "\n", 115 | "$$ \\kappa = {\\beta\\over{\\beta-\\alpha}}(e^{(\\beta-\\alpha)V}-1) $$\n", 116 | "\n", 117 | "In states where $\\alpha = \\beta$ :\n", 118 | "$$ \\kappa = {{\\beta\\pi/\\alpha}\\over{sin(\\beta\\pi/\\alpha)}}$$\n", 119 | "\n", 120 | "The tunnelling correction is defined as $\\kappa_{light}/\\kappa_{heavy}$\n" 121 | ] 122 | }, 123 | { 124 | "cell_type": "code", 125 | "execution_count": null, 126 | "metadata": {}, 127 | "outputs": [], 128 | "source": [ 129 | "def st_factor(imag_cm,temp_K,sm_energy_Eh,pr_energy_Eh,ts_energy_Eh):\n", 130 | " \"\"\"This function calculates the Skodge Truhlar transmission coefficient\n", 131 | " Args:\n", 132 | " imag_cm (float): natural vibrational frequency of the transition state mode in 1 / cm\n", 133 | " temp_K (int): temperature of reaction in K \n", 134 | " sm_energy_Eh (float): energy of starting structure in hartree\n", 135 | " pr_energy_Eh (float): energy of product structure in hartree\n", 136 | " ts_energy_Eh (float): energy of transition state in hartree\n", 137 | "\n", 138 | " Returns:\n", 139 | " float: Skodge Truhlar transmission coefficient\n", 140 | " \"\"\"\n", 141 | "\n", 142 | " #: natural vibrational frequency is converted to hertz with the speed of light and made positive\n", 143 | " imag_hz = np.abs(imag_cm * c)\n", 144 | "\n", 145 | " #: sm/pr/ts energies are converted to J\n", 146 | " pr_energy_J = pr_energy_Eh * Eh\n", 147 | " ts_energy_J = ts_energy_Eh * Eh\n", 148 | " sm_energy_J = sm_energy_Eh * Eh\n", 149 | "\n", 150 | " #: barrier is calculated in the exothermic direction in J\n", 151 | " barr_J = ts_energy_J - max(pr_energy_J,sm_energy_J)\n", 152 | "\n", 153 | " #: ST parameters alpha and beta are calculated both in J**-1\n", 154 | " alpha = 2 * np.pi / (h * imag_hz)\n", 155 | " beta = 1 / (kB * temp_K)\n", 156 | "\n", 157 | " #: Equation used is dependent on relation of alpha and beta, both are unitless\n", 158 | " if alpha == beta:\n", 159 | " return (beta * np.pi / alpha)/(np.sin(beta * np.pi / alpha))\n", 160 | " elif alpha > beta:\n", 161 | " return (beta * np.pi / alpha)/(np.sin(beta * np.pi / alpha)) - beta/(alpha-beta)*np.exp((beta-alpha)*barr_J)\n", 162 | " else:\n", 163 | " return beta/(beta-alpha)*(np.exp((beta-alpha)*barr_J)-1)\n", 164 | "\n", 165 | "def st_corrrection(imag_light_cm,imag_heavy_cm,temp_K,sm_energy_Eh,pr_energy_Eh,ts_energy_Eh):\n", 166 | " \"\"\"This function uses st_factor to calculate the ratio of tunnelling coefficient for the light and heavy isotopes\n", 167 | " Args:\n", 168 | " imag_light_cm (float): light isotope natural vibrational frequency of the transition state mode in 1 / cm\n", 169 | " imag_heavy_cm (float): heavy isotope natural vibrational frequency of the transition state mode in 1 / cm\n", 170 | " temp_K (int): temperature of reaction in K \n", 171 | " sm_energy_Eh (float): energy of starting structure in hartree\n", 172 | " pr_energy_Eh (float): energy of product structure in hartree\n", 173 | " ts_energy_Eh (float): energy of transition state in hartree\n", 174 | "\n", 175 | " Returns:\n", 176 | " float: Skodge Truhlar tunnelling correction factor\n", 177 | " \"\"\"\n", 178 | " light_factor = st_factor(imag_light_cm,temp_K,sm_energy_Eh,pr_energy_Eh,ts_energy_Eh)\n", 179 | " heavy_factor = st_factor(imag_heavy_cm,temp_K,sm_energy_Eh,pr_energy_Eh,ts_energy_Eh)\n", 180 | " return light_factor/heavy_factor" 181 | ] 182 | }, 183 | { 184 | "attachments": {}, 185 | "cell_type": "markdown", 186 | "metadata": {}, 187 | "source": [ 188 | "### PyQuiver implementation\n", 189 | "The following function uses the above function for the ST tunnelling correction to a PyQuiver generated list of KIEs." 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": null, 195 | "metadata": {}, 196 | "outputs": [], 197 | "source": [ 198 | "def calc_frequency(kie,kiecalc):\n", 199 | " \"\"\"This function uses PyQuiver to calculate the natural vibrational frequencies\n", 200 | " Args:\n", 201 | " kie (KIE): isotopologue to calculate frequency of\n", 202 | " kiecalc (KIE_CALCULATION): kie calculation to extract configuration parameters from\n", 203 | "\n", 204 | " Returns:\n", 205 | " light_imag_freq (float), heavy_imag_freq (float): light and heavy imaginary frequencies in 1 / cm\n", 206 | " \"\"\"\n", 207 | "\n", 208 | " light_imag_freqs = kie.ts_tuple[0].calculate_frequencies(kiecalc.config.imag_threshold, scaling=kiecalc.config.scaling)[1]\n", 209 | " heavy_imag_freqs = kie.ts_tuple[1].calculate_frequencies(kiecalc.config.imag_threshold, scaling=kiecalc.config.scaling)[1]\n", 210 | " return min(light_imag_freqs), min(heavy_imag_freqs)\n", 211 | "\n", 212 | "def st_KIE(kiecalc,sm_energy_Eh,pr_energy_Eh,ts_energy_Eh):\n", 213 | " \"\"\"This function calculates Skodge Truhlar corrected KIEs from a KIE_CALCULATION\n", 214 | " Args:\n", 215 | " kiecalc (KIE_Calculation): KIE Calculation\n", 216 | " sm_energy_Eh (float): energy of starting structure in hartree\n", 217 | " pr_energy_Eh (float): energy of product structure in hartree\n", 218 | " ts_energy_Eh (float): energy of transition state in hartree\n", 219 | "\n", 220 | " Returns:\n", 221 | " dictionary{isotopologue}: dictionary of isotopologues\n", 222 | "\n", 223 | " Raise:\n", 224 | " Exception: if KIE calculation is determined to be an EIE calculation\n", 225 | " \"\"\"\n", 226 | "\n", 227 | " sk_kie_dict = {}\n", 228 | "\n", 229 | " if kiecalc.eie_flag != 0:\n", 230 | " raise Exception(\"ST Correction not appropriate for EIE calculation\")\n", 231 | "\n", 232 | " #: Check whether to use relative or absolute KIEs, calculate reference tunnelling correction if relative\n", 233 | " if kiecalc.config.reference_isotopologue != \"default\" and kiecalc.config.reference_isotopologue != \"none\":\n", 234 | " ref = kiecalc.KIES[kiecalc.config.reference_isotopologue]\n", 235 | " imag_light_cm, imag_heavy_cm = calc_frequency(ref,kiecalc)\n", 236 | " ref_tunnel_corr = st_corrrection(imag_light_cm,imag_heavy_cm,kiecalc.config.temperature,sm_energy_Eh,pr_energy_Eh,ts_energy_Eh)\n", 237 | " sk_kie_dict[kiecalc.config.reference_isotopologue] = ref.value[0] * ref_tunnel_corr\n", 238 | " else:\n", 239 | " ref_tunnel_corr = 1\n", 240 | "\n", 241 | " for key, value in kiecalc.KIES.items():\n", 242 | " if key != kiecalc.config.reference_isotopologue:\n", 243 | " #: extract light and heavy imaginary frequencies from pyquiver\n", 244 | " imag_light_cm, imag_heavy_cm = calc_frequency(value,kiecalc)\n", 245 | " \n", 246 | " #: calculate tunnelling correction and print result\n", 247 | " tuncorrection = st_corrrection(imag_light_cm,imag_heavy_cm,kiecalc.config.temperature,sm_energy_Eh,pr_energy_Eh,ts_energy_Eh) / ref_tunnel_corr\n", 248 | " sk_kie_dict[key] = value.value[0] * tuncorrection\n", 249 | "\n", 250 | " return sk_kie_dict\n", 251 | "\n", 252 | "def print_st(kiecalc,sm_energy_Eh,pr_energy_Eh,ts_energy_Eh):\n", 253 | " \"\"\"This function prints Skodge Truhlar corrected KIEs from a KIE_CALCULATION\n", 254 | " Args:\n", 255 | " kiecalc (KIE_Calculation): KIE Calculation\n", 256 | " sm_energy_Eh (float): energy of starting structure in hartree\n", 257 | " pr_energy_Eh (float): energy of product structure in hartree\n", 258 | " ts_energy_Eh (float): energy of transition state in hartree\n", 259 | " \"\"\"\n", 260 | " \n", 261 | " print(\"Skodge Truhlar Corrected Absolute KIE:\")\n", 262 | " print(\"Isotopologue uncorrected Skodge Truhlar\")\n", 263 | " print(\" KIE KIE\")\n", 264 | "\n", 265 | " sk_kie_dict = st_KIE(kiecalc,sm_energy_Eh,pr_energy_Eh,ts_energy_Eh)\n", 266 | "\n", 267 | " #: Iterate through Isotopologues to print uncorrected and ST corrected KIEs\n", 268 | " for key, value in kiecalc.KIES.items():\n", 269 | " if key != kiecalc.config.reference_isotopologue:\n", 270 | " #: extract light and heavy imaginary frequencies from pyquiver\n", 271 | " \n", 272 | " spaces = \" \" * max((10 - len(key)),1)\n", 273 | " print(\"isotopologue:\" + spaces + key, end =\" \")\n", 274 | " print(\"%0.4f\" % value.value[0], end =\" \")\n", 275 | " print(\"%0.4f\" % sk_kie_dict[key])\n", 276 | " \n", 277 | " #: display reference information\n", 278 | " if kiecalc.config.reference_isotopologue != \"default\" and kiecalc.config.reference_isotopologue != \"none\":\n", 279 | " print()\n", 280 | " print(\"KIEs referenced to isotopologue \"+ kiecalc.config.reference_isotopologue +\", whose absolute KIEs are:\")\n", 281 | " spaces = \" \" * max((10 - len(kiecalc.config.reference_isotopologue)),1)\n", 282 | " print(\"isotopologue:\" + spaces + kiecalc.config.reference_isotopologue, end =\" \")\n", 283 | " print(\"%0.4f\" % kiecalc.KIES[kiecalc.config.reference_isotopologue].value[0], end =\" \")\n", 284 | " print(\"%0.4f\" % sk_kie_dict[kiecalc.config.reference_isotopologue])\n" 285 | ] 286 | }, 287 | { 288 | "attachments": {}, 289 | "cell_type": "markdown", 290 | "metadata": {}, 291 | "source": [ 292 | "### Running calculation\n", 293 | "With the necessary functions defined, the correction can be run on electronic structure data. Here the paths reference the tutorial data for the Claisen reaction. Note that for this reaction the ST corrected KIEs nearly exactly match the inverted parabola values.\n", 294 | "\n", 295 | "First the software is set which allows cctk to appropriately extract the energies. Then, the path to the *PyQuiver* configuration file is defined (see tutorial for details). Finally, relevent electronic structure calculations are set. For generality the extension is ommitted and added as needed. Alternatively, these paths can of course be manually set below when calling the appropriate function.\n", 296 | "\n", 297 | "The first example is for Orca and the second for Gaussian:" 298 | ] 299 | }, 300 | { 301 | "cell_type": "code", 302 | "execution_count": null, 303 | "metadata": {}, 304 | "outputs": [], 305 | "source": [ 306 | "# set computational software\n", 307 | "# gauss for Gaussian\n", 308 | "# orca for Orca\n", 309 | "software = \"orca\"\n", 310 | "\n", 311 | "#location of pyquiver configuration file, see documentation/tutorial for details\n", 312 | "pyquivconf = \"../tutorial/orca/claisen_demo.config\"\n", 313 | "\n", 314 | "#include basename not extension\n", 315 | "smpath = \"../tutorial/orca/claisen_gs_freq\"\n", 316 | "prpath = \"../tutorial/orca/claisen_gs_freq\"\n", 317 | "tspath = \"../tutorial/orca/claisen_ts_freq\"\n", 318 | "\n", 319 | "kiecalc = KIE_Calculation(pyquivconf, smpath + \".hess\", tspath + \".hess\", style=\"orca\")\n", 320 | "print(kiecalc)\n", 321 | "print()\n", 322 | "print_st(kiecalc,get_energy(software,smpath+\".out\"),get_energy(software,prpath+\".out\"),get_energy(software,tspath+\".out\"))" 323 | ] 324 | }, 325 | { 326 | "attachments": {}, 327 | "cell_type": "markdown", 328 | "metadata": {}, 329 | "source": [ 330 | "It is also possible to directly extract the KIEs (in addition to printing them) as a dictionary of isotopologues (relative to reference isotopologue, which is recorded in the dictionary as an absolute KIE)\n", 331 | "\n", 332 | "Here we extract and print the ST corrected C4 KIE:" 333 | ] 334 | }, 335 | { 336 | "cell_type": "code", 337 | "execution_count": null, 338 | "metadata": {}, 339 | "outputs": [], 340 | "source": [ 341 | "st = st_KIE(kiecalc,get_energy(software,smpath+\".out\"),get_energy(software,prpath+\".out\"),get_energy(software,tspath+\".out\"))\n", 342 | "print(\"%0.4f\" % st[\"C4\"])" 343 | ] 344 | }, 345 | { 346 | "attachments": {}, 347 | "cell_type": "markdown", 348 | "metadata": {}, 349 | "source": [ 350 | "Here is the same calculation as above, except using Gaussian output files" 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": null, 356 | "metadata": {}, 357 | "outputs": [], 358 | "source": [ 359 | "# set computational software\n", 360 | "# gauss for Gaussian\n", 361 | "# orca for Orca\n", 362 | "software = \"gauss\"\n", 363 | "\n", 364 | "#location of pyquiver configuration file, see documentation/tutorial for details\n", 365 | "pyquivconf = \"../tutorial/gaussian/claisen_demo.config\"\n", 366 | "\n", 367 | "#In this case the paths can include the extension since pyquiver and gaussian both use the gaussian output files\n", 368 | "smpath = \"../tutorial/gaussian/claisen_gs.out\"\n", 369 | "prpath = \"../tutorial/gaussian/claisen_gs.out\"\n", 370 | "tspath = \"../tutorial/gaussian/claisen_ts.out\"\n", 371 | "\n", 372 | "kiecalc = KIE_Calculation(pyquivconf, smpath, tspath)\n", 373 | "print(kiecalc)\n", 374 | "print()\n", 375 | "print_st(kiecalc,get_energy(software,smpath),get_energy(software,prpath),get_energy(software,tspath))" 376 | ] 377 | } 378 | ], 379 | "metadata": { 380 | "kernelspec": { 381 | "display_name": "cctk", 382 | "language": "python", 383 | "name": "python3" 384 | }, 385 | "language_info": { 386 | "codemirror_mode": { 387 | "name": "ipython", 388 | "version": 3 389 | }, 390 | "file_extension": ".py", 391 | "mimetype": "text/x-python", 392 | "name": "python", 393 | "nbconvert_exporter": "python", 394 | "pygments_lexer": "ipython3", 395 | "version": "3.8.16" 396 | }, 397 | "orig_nbformat": 4, 398 | "vscode": { 399 | "interpreter": { 400 | "hash": "50c68f4a39ea646ed7d3e4e0d35a44bd89267cbb0b9077d25aee25f802178e8f" 401 | } 402 | } 403 | }, 404 | "nbformat": 4, 405 | "nbformat_minor": 2 406 | } 407 | -------------------------------------------------------------------------------- /scripts/snip.awk: -------------------------------------------------------------------------------- 1 | # Generates .snip files from g09 .out files. 2 | # 3 | # usage: awk -f snip.awk *.out 4 | # 5 | # Takes all *.out files and turns them into *.snip files. 6 | # (Will overwrite any existing .snips) 7 | # 8 | # These files can be used with PyQuiver, but not with the 9 | # old version of Quiver. 10 | BEGIN { 11 | if ( ARGC < 2 ) { 12 | print "Error: please specify some filenames." 13 | print "" 14 | print "snip.awk: generates .snip files from Gaussian .out files" 15 | print "These files will be compatible with PyQuiver." 16 | print "" 17 | print "usage: awk -f snip.awk *.out" 18 | exit 19 | } 20 | } 21 | 22 | FNR == 1 { 23 | fileCount++ 24 | filenames[fileCount]=FILENAME 25 | } 26 | 27 | /Standard orientation/,/Rotational constants/ { 28 | if ( NF == 6 && match($0,"[a-zA-Z]") == 0 ) 29 | { 30 | symbol[fileCount,$1]=$2 31 | x[fileCount,$1]=$4 32 | y[fileCount,$1]=$5 33 | z[fileCount,$1]=$6 34 | atoms[fileCount]=$1 35 | } 36 | } 37 | 38 | /SCF Done/ { 39 | energy[fileCount]=$5 40 | } 41 | 42 | /NAtoms=/ { 43 | split($0, natomsplit, "NAtoms=") 44 | split(natomsplit[2], natomsplitsplit, " ") 45 | numberOfAtoms[fileCount]=natomsplitsplit[1] 46 | } 47 | 48 | /Zero-point correction/ { 49 | energy_field_count = 0 50 | } 51 | 52 | /Zero-point correction/,/Sum of electronic and thermal Free Energies/ { 53 | split($0, line, "=") 54 | #print trim(line[2]) 55 | energy_field[fileCount, energy_field_count] = trim(line[2]) 56 | energy_field_count ++ 57 | } 58 | 59 | /l9999.exe/ { 60 | archive[fileCount] = "" 61 | } 62 | 63 | /l9999.exe/,/\\\@/ { 64 | #archive[fileCount] = archive[fileCount] trim($0) 65 | s = $0 66 | gsub(/%/, "%%", s) 67 | #n = split(s, terminations, "l9999") 68 | #archiveString = "l9999" terminations[n] 69 | archive[fileCount] = archive[fileCount] s 70 | } 71 | 72 | END { 73 | if ( exitCode != 0 ) 74 | exit 75 | 76 | split("Zero-point correction=@ Thermal correction to Energy=@ Thermal correction to Enthalpy=@ Thermal correction to Gibbs Free Energy=@ Sum of electronic and zero-point Energies=@ Sum of electronic and thermal Energies=@ Sum of electronic and thermal Enthalpies=@ Sum of electronic and thermal Free Energies=", energy_lines, "@") 77 | for (i=1; i <= fileCount; i++) 78 | { 79 | filename = filenames[i] 80 | gsub(".out",".snip",filename) 81 | print filename 82 | 83 | print "" > filename 84 | 85 | printf "Electronic energy: %15.8f\n", energy[i] >> filename 86 | 87 | for (j=0; j<=8; j++) 88 | printf "%s %s\n", energy_lines[j+1], energy_field[i,j] >> filename 89 | 90 | print "Standard orientation" >> filename 91 | for (j=1; j <= atoms[i]; j++) 92 | printf "%d %2d 0 %15.8f %15.8f %15.8f\n", j, symbol[i,j], x[i,j], y[i,j], z[i,j] >> filename 93 | print "Rotational constants (GHZ)" >> filename 94 | printf "NAtoms= %d \n", numberOfAtoms[i] >> filename 95 | printf "SCF Done Field3 Field4 %15.8f \n", energy[i] >> filename 96 | printf archive[i] >> filename 97 | printf "\n\n" >> filename 98 | } 99 | } 100 | 101 | # functions to trim whitespace 102 | function ltrim(s) { sub(/^[ \t\r\n]+/, "", s); return s } 103 | function rtrim(s) { sub(/[ \t\r\n]+$/, "", s); return s } 104 | function trim(s) { return rtrim(ltrim(s)); } 105 | -------------------------------------------------------------------------------- /src/AUTOQUIVER.md: -------------------------------------------------------------------------------- 1 | ### `autoquiver.py` 2 | 3 | `autoquiver` allows KIEs to be calculated over many ground state and transition state files that share a common set of desired isotopic substitutions. (The `autoquiver` module has additional functionality when used through a Python interface (read the `scripts/pyquiver.ipynb` if interested) but the command line interface should suffice for most purposes.) 4 | 5 | Suppose we have a directory, `auto/`, with the following files: 6 | 7 | ``` 8 | substitutions.config 9 | gs-type1.output ts-type1.output 10 | gs-type2.output ts-type2.output 11 | gs-type3.output ts-type3.output 12 | gs-type4.output ts-type4.output 13 | ``` 14 | 15 | We might want to run *PyQuiver* using the `substitutions.config` file on all pairs of ground states and transition states. For example, these pairs may be the same calculation run at many levels of theory. Note that it is crucial that all files have a consistent atom numbering scheme. 16 | 17 | To accomplish this we run `autoquiver.py` as follows: 18 | 19 | ``` 20 | python src/autoquiver.py -e .output auto/ auto/substitutions.config gs ts - 21 | ``` 22 | 23 | The arguments are: 24 | 25 | * `-e .output`: a flag to look for files with the extension `.output` as the frequency jobs for the ground and transitions states. 26 | * `auto/`: look for files in the `auto/` directory. 27 | * `auto/substitutions.config`: use `auto/substitutions.config` as the configuration file. 28 | * `gs`: use the string "gs" to find ground state files. All files with the appropriate extension that contain the substring "gs" will be treated as ground state files. 29 | * `ts`: use the string "ts" to find transition state files. 30 | * `-`: use the field delimiter "-" to test if a ground state and transition states match. All fields after the first "-" must be identical. This means that `gs-type1.output` and `ts-type1.output` will match but `gs-type1.output` and `ts-type2.output` won't. 31 | 32 | The output of autoquiver is directed to a number of csv files corresponding to each configuration file given. These filenames are printed when autoquiver exits. 33 | 34 | For more information, the output of `python autoquiver.py -h` has been reproduced below: 35 | 36 | ``` 37 | usage: autoquiver.py [-h] [-v] [-s STYLE] [-e EXT] 38 | config target gs_p ts_p delimiter 39 | 40 | A program to automatically run PyQuiver on a config file and all ground state 41 | and transition states matching certain constraints. 42 | 43 | positional arguments: 44 | config configuration file path 45 | target target directory file path (where the ground and 46 | transition state files live 47 | gs_p substring in ground state files 48 | ts_p substring in transition state files 49 | delimiter delimiter used to match ground and transition state 50 | files (all fields separated by the delimiter after the 51 | first must match) 52 | 53 | optional arguments: 54 | -h, --help show this help message and exit 55 | -v, --verbose when the verbose flag is set debug information is 56 | printed 57 | -s STYLE, --style STYLE 58 | style of input files 59 | -e EXT, --extension EXT 60 | extension of input files 61 | ``` 62 | -------------------------------------------------------------------------------- /src/autoquiver.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import re 4 | import subprocess 5 | import argparse 6 | 7 | import settings 8 | import quiver 9 | 10 | from kie import KIE_Calculation 11 | 12 | import glob 13 | 14 | def autoquiver(filepath, config_path, gs_p, ts_p, gs_ts_match_p, input_extension='.out', style='g09', report_tunnelling=False): 15 | if type(gs_p) is str: 16 | gs_str = gs_p 17 | gs_p = lambda x: (gs_str in x) 18 | elif hasattr(gs_p, '__call__'): 19 | pass 20 | else: 21 | raise TypeError("gs_p must either be a string or a function that returns a boolean value.") 22 | if type(ts_p) is str: 23 | ts_str = ts_p 24 | ts_p = lambda x: (ts_str in x) 25 | elif hasattr(ts_p, '__call__'): 26 | pass 27 | else: 28 | raise TypeError("ts_p must either be a string or a function that returns a boolean value.") 29 | if type(gs_ts_match_p) is str: 30 | delimiter = gs_ts_match_p 31 | gs_ts_match_p = lambda x,y: (x.split(delimiter)[1:] == y.split(delimiter)[1:]) 32 | elif hasattr(ts_p, '__call__'): 33 | pass 34 | else: 35 | raise TypeError("gs_ts_p must either be a string or a function that returns a boolean value.") 36 | 37 | os.chdir(filepath) 38 | print("Starting AutoQuiver analysis...\n") 39 | for config in glob.glob("*.config"): 40 | if os.path.samefile(config_path, config): 41 | eie_flag = -1 42 | title_start = "ground_state,transition_state," 43 | title = title_start 44 | table = "" 45 | for gs in glob.glob("*"+input_extension): 46 | if gs_p(gs): 47 | for ts in glob.glob("*"+input_extension): 48 | if ts_p(ts) and gs_ts_match_p(gs,ts): 49 | print("%-50s - %-50s : " % (gs[-50:], ts[-50:]), end=' ') 50 | try: 51 | kie = KIE_Calculation(config, gs, ts, style=style) 52 | except: 53 | print("error") 54 | continue 55 | title_row, row, eie_p = kie.get_row(report_tunnelling=report_tunnelling) 56 | 57 | # print the KIEs (minus the comma at the end) 58 | print(row[:-1]) 59 | 60 | if eie_flag == -1: 61 | eie_flag = eie_p 62 | else: 63 | if eie_flag != eie_p: 64 | raise ValueError("some calculations represented EIEs and others represented KIEs.") 65 | if title is title_start: 66 | title = title + title_row 67 | table = title + "\n" + table 68 | else: 69 | if title_start + title_row != title: 70 | raise ValueError("the alignment of the table columns is incorrect.") 71 | 72 | table += gs + "," + ts + "," + row + "\n" 73 | 74 | with open(os.path.splitext(config)[0]+"-kies.csv", 'w') as f: 75 | f.write(table) 76 | print("\nAutoQuiver has completed execution.\nResult written to {0}.".format(os.path.splitext(config)[0]+"-kies.csv")) 77 | 78 | if __name__ == "__main__": 79 | parser = argparse.ArgumentParser(description="A program to automatically run PyQuiver on a config file and all ground state and transition states matching certain constraints.") 80 | parser.add_argument('-v', '--verbose', dest="debug", help='when the verbose flag is set debug information is printed', action='count') 81 | parser.add_argument('-s', '--style', dest="style", default='g09', help='style of input files') 82 | parser.add_argument('-e', '--extension', dest="ext", default='.out', help='extension of input files') 83 | parser.add_argument('-t', '--report_tunnelling', dest="report_tunnelling", action='store_true', help='report the tunnelling correction') 84 | parser.add_argument('config', help='configuration file path') 85 | parser.add_argument('target', help='target directory file path (where the ground and transition state files live') 86 | parser.add_argument('gs_p', help='substring in ground state files') 87 | parser.add_argument('ts_p', help='substring in transition state files') 88 | parser.add_argument('delimiter', help='delimiter used to match ground and transition state files (all fields separated by the delimiter after the first must match)') 89 | parser.set_defaults(report_tunnelling=False) 90 | 91 | args = parser.parse_args() 92 | settings.DEBUG = 0 93 | if args.debug: 94 | settings.DEBUG += args.debug 95 | print("Debug level is %d" % settings.DEBUG) 96 | 97 | autoquiver(args.target, args.config, args.gs_p, args.ts_p, args.delimiter, style=args.style, input_extension=args.ext, report_tunnelling=args.report_tunnelling) 98 | -------------------------------------------------------------------------------- /src/config.py: -------------------------------------------------------------------------------- 1 | # This file reads PyQuiver configuration files. 2 | import sys 3 | import re 4 | import settings 5 | from constants import REPLACEMENTS, REPLACEMENTS_Z 6 | from collections import OrderedDict 7 | 8 | # Reads PyQuiver .config files. 9 | class Config(object): 10 | def __init__(self,filename): 11 | expected_fields = "scaling temperature mass_override_isotopologue reference_isotopologue imag_threshold frequency_threshold".split(" ") 12 | config = { i : None for i in expected_fields } 13 | config["filename"] = filename 14 | 15 | print_message = True 16 | try: 17 | f=list(sys._current_frames().values())[0] 18 | if "autoquiver" in f.f_back.f_back.f_globals['__file__']: 19 | print_message = False 20 | except: 21 | pass 22 | if print_message and settings.DEBUG >= 2: 23 | print("\nReading configuration from {0}".format(filename)) 24 | 25 | # a list of isotopologues 26 | # each entry is a list of tuples 27 | # each tuple is (from_atom_number, to_atom_number, replacement_isotope) 28 | # this format allows for multiple replacements in one isotopologue 29 | isotopologues = OrderedDict() 30 | 31 | # read file 32 | for line in open(filename, "r"): 33 | # ignore comments and blank lines 34 | line = line.strip() 35 | if len(line) == 0 or line[0] == "#": 36 | continue 37 | line = line.split("#", 1)[0] 38 | 39 | # read space-delimited data 40 | fields = [_f for _f in line.split(" ") if _f] 41 | 42 | # for backwards compatibility with quiver 43 | if fields[0] == "isotopomer": 44 | fields[0] = "isotopologue" 45 | elif fields[0] == "reference_isotopomer": 46 | fields[0] = "reference_isotopologue" 47 | elif fields[0] == "mass_override_isotopomer": 48 | fields[0] = "mass_override_isotopologue" 49 | 50 | 51 | # parse 52 | if fields[0] == "isotopologue": 53 | # parse isotopologues, checking for sanity 54 | if len(fields) != 5: 55 | raise ValueError("unexpected number of fields for isotopologue in config file:\n%s" % line) 56 | isotopologue_id, from_atom_number, to_atom_number, replacement = str(fields[1]), int(fields[2]), int(fields[3]), str(fields[4]) 57 | if isotopologue_id == "default": 58 | raise ValueError("name default is reserved.") 59 | if from_atom_number < 1 or to_atom_number < 1: 60 | raise ValueError("check atom numbers:\n%s" % line) 61 | if replacement not in list(REPLACEMENTS.keys()): 62 | raise ValueError("invalid isotopic replacement:\n%s" % line) 63 | 64 | # allows for the fact that isotopologues can make multiple replacements 65 | try: 66 | isotopologues[isotopologue_id].append((from_atom_number, to_atom_number, replacement)) 67 | except KeyError: 68 | isotopologues[isotopologue_id] = [(from_atom_number, to_atom_number, replacement)] 69 | 70 | elif len(fields) == 2: 71 | # read regular configuration fields that have only one entry 72 | fields = [ str(i) for i in fields ] 73 | if fields[0] not in expected_fields: 74 | raise ValueError("unexpected config field:\n%s" % line) 75 | config[fields[0]] = fields[1] 76 | else: 77 | raise ValueError("unexpected number of fields in config file:\n%s" % line) 78 | 79 | # ensure we have all the fields we are supposed to 80 | for k,v in config.items(): 81 | if k == "frequency_threshold" and v is not None: 82 | print("*** Warning: frequency_threshold is now deprecated and will be ignored. ***") 83 | if v is None: 84 | if k == "frequency_threshold": 85 | config["frequency_threshold"] = 0.0 86 | else: 87 | raise ValueError("missing config file field: %s" % k) 88 | if len(isotopologues) == 0: 89 | raise ValueError("must specify at least one isotopologue") 90 | 91 | # check some of the other fields 92 | config["temperature"] = float(config["temperature"]) 93 | if config["temperature"] < 0.0: 94 | raise ValueError("check temperature") 95 | config["scaling"] = float(config["scaling"]) 96 | if config["scaling"] < 0.5 or config["scaling"] > 1.5: 97 | raise ValueError("check scaling factor") 98 | 99 | try: 100 | config["reference_isotopologue"] 101 | except KeyError: 102 | raise ValueError("check reference isotopologue is valid") 103 | config["reference_isotopologue"] = str(config["reference_isotopologue"]) 104 | 105 | try: 106 | config["mass_override_isotopologue"] 107 | except KeyError: 108 | raise ValueError("check reference isotopologue is valid") 109 | config["mass_override_isotopologue"] = str(config["mass_override_isotopologue"]) 110 | 111 | config["frequency_threshold"] = float(config["frequency_threshold"]) 112 | 113 | config["imag_threshold"] = float(config["imag_threshold"]) 114 | if config["imag_threshold"] > 300.0: 115 | raise ValueError("imag threshold is too high") 116 | 117 | # store all the information in the object dictionary 118 | config["isotopologues"] = isotopologues 119 | self.__dict__ = config 120 | 121 | # checks if this config file is compatible with a pair of ground and transition state systems 122 | def check(self, gs, ts, verbose=False): 123 | n_gs_atoms = len(gs.atomic_numbers) 124 | n_ts_atoms = len(ts.atomic_numbers) 125 | # check that the isotopic replacements make sense 126 | isotopologues = self.isotopologues 127 | for i,isotopologue in isotopologues.items(): 128 | for r in range(len(isotopologue)): 129 | # this replacement changes gs atom number from_atom 130 | # and ts atom number to_atom 131 | # to replacement (like "2D") 132 | from_atom = isotopologue[r][0] 133 | to_atom = isotopologue[r][1] 134 | replacement = isotopologue[r][2] 135 | replacement_string = "%s %d %d %s" % (i, from_atom, to_atom, replacement) 136 | # get the atomic numbers of from_atom and to_atom 137 | # must subtract one to convert from atom numbers to indices 138 | from_atomZ = gs.atomic_numbers[from_atom-1] 139 | to_atomZ = ts.atomic_numbers[to_atom-1] 140 | replacementZ = REPLACEMENTS_Z[replacement] 141 | if from_atomZ != replacementZ: 142 | raise ValueError("gs atomic number and replacement atomic number do not match for {0}\n".format(replacement_string)) 143 | if to_atomZ != replacementZ: 144 | raise ValueError("ts atomic number and replacement atomic number do not match for {0}\n".format(replacement_string)) 145 | if (from_atomZ != to_atomZ): 146 | raise ValueError("gs and ts atomic number do not match for\n" % replacement_string) 147 | 148 | if verbose: 149 | print("Config file %s makes sense with gs file %s and ts file %s.\n" % (self.filename, gs.filename, ts.filename)) 150 | 151 | # convert to human-readable format 152 | def __str__(self): 153 | to_string = "Config file: %s\nTemperature: %.1f K\nScaling: %.3f\nReference Isotopologue: %s\nImag threshold (cm-1): %d\n" % \ 154 | (self.filename, self.temperature, self.scaling, self.reference_isotopologue, self.imag_threshold) 155 | if self.frequency_threshold != 0: 156 | to_string += "Frequency threshold (cm-1): %d\n" % self.frequency_threshold 157 | 158 | keys = list(self.isotopologues.keys()) 159 | if self.reference_isotopologue != "default" and self.reference_isotopologue != "none": 160 | try: 161 | keys.remove(self.reference_isotopologue) 162 | except: 163 | print("\nCould not find the following reference isotopologue: %s" % self.reference_isotopologue) 164 | sys.exit(1) 165 | keys.sort() 166 | 167 | if self.reference_isotopologue == "default" or self.reference_isotopologue == "none": 168 | to_string += " No reference isotopologue.\n" 169 | else: 170 | keys = [self.reference_isotopologue] + keys 171 | for i in keys: 172 | isotopologue = self.isotopologues[i] 173 | 174 | for j in range(len(isotopologue)): 175 | to_string += " Isotopologue {0: >10s}, replacement {1: >2d}: replace gs atom {2: ^3d} and ts atom {3: ^3d} with {4: >3s}\n".format(i, j+1, isotopologue[j][0], isotopologue[j][1], isotopologue[j][2]) 176 | 177 | return to_string[:-1] 178 | -------------------------------------------------------------------------------- /src/constants.py: -------------------------------------------------------------------------------- 1 | # This file holds physical constants and reads atomic weights. 2 | import sys 3 | import re 4 | import os 5 | import inspect 6 | ############### 7 | 8 | # Physical Constants 9 | 10 | PHYSICAL_CONSTANTS = { 11 | 'h' : 6.626070E-34, # Planck's constants in J * s 12 | 'c' : 2.997925E+10, # speed of light in units of cm/s 13 | 'Eh' : 4.359745E-18, # energy of a hartree in units of J = kg m^2/s^2 14 | 'a0' : 5.291772E-11, # bohr radius in m 15 | 'atb': 5.291772E-01, # angstroms per bohr 16 | 'amu': 1.660468E-27, # atomic mass unit in units kg 17 | 'kB' : 1.380649E-23 # Boltzmann's constant in J/K 18 | } 19 | #CM/2.998E10/,EM/1.440E13/,HBC/1.4387/ 20 | 21 | ############### 22 | 23 | # Atomic Weight Information 24 | 25 | class Element(object): 26 | def __init__(self, full_name, atomic_number, symbol, default_mass): 27 | # the name of this element, like "hydrogen" 28 | full_name = str(full_name) 29 | self.full_name = full_name 30 | if re.match("[^a-z]", full_name): 31 | print("Unexpected non-lowercase character in element name: %s" % full_name) 32 | print("Quitting.") 33 | sys.exit(1) 34 | 35 | # the symbol of this element, like "H" 36 | symbol = str(symbol) 37 | self.symbol = symbol 38 | if re.match("[^a-zA-Z]", symbol): 39 | print("Unexpected non-letter character in element symbol: %s" % symbol) 40 | print("Quitting.") 41 | sys.exit(1) 42 | if len(symbol) < 1 or len(symbol) > 2: 43 | print("Unexpected length of element symbol (must be 1 or 2): %s" % symbol) 44 | print("Quitting.") 45 | sys.exit(1) 46 | 47 | # the atomic number of this element, like 1 48 | atomic_number = int(atomic_number) 49 | self.atomic_number = atomic_number 50 | if atomic_number < 1 or atomic_number > 200: 51 | print("Unexpected atomic number: %d" % atomic_number) 52 | print("Quitting.") 53 | sys.exit(1) 54 | 55 | # the average weight for this element, like 1.00783 56 | default_mass = float(default_mass) 57 | self.default_mass = default_mass 58 | if default_mass < 0.0 or default_mass > 500.0: 59 | print("Unexpected default mass: %d" % default_mass) 60 | print("Quitting.") 61 | sys.exit(1) 62 | 63 | # pairs of tuples strings (like "2H") to masses (like 2.0141) 64 | self.replacements = [] 65 | 66 | def __str__(self): 67 | string = "%s (%s, Z=%d, default mass = %.4f" % (self.full_name.capitalize(), self.symbol, self.atomic_number, self.default_mass) 68 | if len(self.replacements) == 0: 69 | string += ", no isotopic replacements possible)\n" 70 | else: 71 | string += ")\n" 72 | for s,m in self.replacements: 73 | string += " %2s : %.4f\n" % (s,m) 74 | return string[:-1] 75 | 76 | def add_replacement(self, symbol, mass): 77 | symbol = str(symbol) 78 | if re.match("[^a-zA-Z0-9]", symbol): 79 | print("Unexpected non-letter character in isotopic replacement symbol: %s" % symbol) 80 | print("Quitting.") 81 | sys.exit(1) 82 | if len(symbol) < 1 or len(symbol) > 4: 83 | print("Unexpected length of element symbol in replacement (must be 1-4 inclusive, found %d): %s" % (len(symbol), symbol)) 84 | print("Quitting.") 85 | sys.exit(1) 86 | for s,m in self.replacements: 87 | if s == symbol: 88 | print("Must use a unique symbol for every isotopic replacement: %s" % s) 89 | sys.exit(1) 90 | mass = float(mass) 91 | if mass < 0.0 or mass > 500.0: 92 | print("Unexpected isotopic replacement mass: %f" % mass) 93 | sys.exit(1) 94 | self.replacements.append((symbol,mass)) 95 | 96 | # read in atomic weight data 97 | elements = [] 98 | 99 | root = os.path.split(os.path.abspath(__file__))[0] 100 | 101 | for line in open(root + "/weights.dat", "r"): 102 | # ignore comments and blank lines 103 | line = line.strip() 104 | if len(line) == 0 or line[0] == "#": 105 | continue 106 | line = line.split("#",1)[0] 107 | 108 | # parse 109 | fields = line.split(",") #line.encode("ascii","ignore").split(",") 110 | if len(fields) < 4: 111 | print("Error: not enough data on this line of weights.dat:") 112 | print(line) 113 | print("\nQuitting.") 114 | sys.exit(1) 115 | element = Element(*fields[0:4]) 116 | if (len(fields)-4) % 2 != 0: 117 | print("Unexpected number of isotopic replacement fields on this line of weights.dat.") 118 | print("The number of fields after the first four must be a multiple of 2 (found %d)." % (len(fields)-4)) 119 | print(line) 120 | print("\nQuitting.") 121 | sys.exit(1) 122 | if (len(fields) > 4): 123 | for i in range(4, len(fields), 2): 124 | element.add_replacement(fields[i], fields[i+1]) 125 | elements.append(element) 126 | #print element 127 | print("Read atomic weight data for %d elements." % len(elements)) 128 | 129 | # map from atomic number to default masses 130 | DEFAULT_MASSES = { e.atomic_number : e.default_mass for e in elements } 131 | 132 | # map from valid isotopic replacements to masses 133 | REPLACEMENTS = {} 134 | for e in elements: 135 | for replacement,mass in e.replacements: 136 | REPLACEMENTS[replacement] = mass 137 | 138 | # map from isotopic replacements to atomic numbers 139 | REPLACEMENTS_Z = {} 140 | for e in elements: 141 | for replacement,mass in e.replacements: 142 | REPLACEMENTS_Z[replacement]=e.atomic_number 143 | 144 | 145 | # threshold to separate linear molecules from non-linear molecules 146 | LINEARITY_THRESHOLD = 1e-06 147 | DROP_NUM_LINEAR = 5 148 | # DROP_NUM_NONLINEAR = 6 149 | -------------------------------------------------------------------------------- /src/kie.py: -------------------------------------------------------------------------------- 1 | # This calculates KIEs based on the Bigeleisen-Mayer equation. 2 | import numpy as np 3 | #from quiver import System, Isotopologue, DEBUG 4 | import quiver 5 | import settings 6 | from config import Config 7 | from constants import DEFAULT_MASSES 8 | from collections import OrderedDict 9 | 10 | # load constants 11 | from constants import PHYSICAL_CONSTANTS, REPLACEMENTS 12 | h = PHYSICAL_CONSTANTS["h"] # in J . s 13 | c = PHYSICAL_CONSTANTS["c"] # in cm . s 14 | kB = PHYSICAL_CONSTANTS["kB"] # in J/K 15 | 16 | class KIE_Calculation(object): 17 | def __init__(self, config, gs, ts, style="g09"): 18 | # check the types of config, gs, and ts parsing files if necessary and copying fields if not 19 | if type(config) is str: 20 | self.config = Config(config) 21 | elif type(config) is Config: 22 | self.config = Config 23 | else: 24 | raise TypeError("config argument must be either a filepath or Config object.") 25 | 26 | if type(gs) is str: 27 | self.gs_system = quiver.System(gs, style=style) 28 | elif type(gs) is quiver.System: 29 | self.gs_system = gs 30 | else: 31 | raise TypeError("gs argument must be either a filepath or quiver.System object.") 32 | 33 | if type(ts) is str: 34 | self.ts_system = quiver.System(ts, style=style) 35 | elif type(ts) is quiver.System: 36 | self.ts_system = ts 37 | else: 38 | raise TypeError("ts argument must be either a filepath or quiver.System object.") 39 | 40 | # set the eie_flag to the recognized uninitialized value (used for checking if there are inconsistent calculation types) 41 | self.eie_flag = -1 42 | 43 | if settings.DEBUG != 0: 44 | print(self.config) 45 | 46 | KIES = OrderedDict() 47 | 48 | if self.config.frequency_threshold: 49 | print("WARNING: config file uses the frequency_threshold parameter. This has been deprecated and low frequencies are dropped by linearity detection.") 50 | 51 | for p in self.make_isotopologues(): 52 | gs_tuple, ts_tuple = p 53 | name = gs_tuple[1].name 54 | kie = KIE(name, gs_tuple, ts_tuple, self.config.temperature, self.config.scaling, self.config.imag_threshold) 55 | KIES[name] = kie 56 | 57 | for name,k in KIES.items(): 58 | if name != self.config.reference_isotopologue: 59 | if self.eie_flag == -1: 60 | eie_flag_iso = name 61 | self.eie_flag = k.eie_flag 62 | else: 63 | if self.eie_flag != k.eie_flag: 64 | if eie_flag == 1: 65 | raise ValueError("quiver attempted to run a KIE calculation (isotopomer {0}) after an EIE calculation (isotopomer {1}). Check the frequency threshold.".format(name, eie_flag_iso)) 66 | else: 67 | raise ValueError("quiver attempted to run an EIE calculation (isotopomer {0}) after a KIE calculation (isotopomer {1}). Check the frequency threshold.".format(name, eie_flag_iso)) 68 | 69 | if self.config.reference_isotopologue != "default" and self.config.reference_isotopologue != "none": 70 | k.apply_reference(KIES[self.config.reference_isotopologue]) 71 | 72 | self.KIES = KIES 73 | 74 | # retrieves KIEs for autoquiver output 75 | # if report_tunnelling = True, the first number will be the inverted parabola KIE 76 | # and the second number will be the tunnelling correction 77 | def get_row(self, report_tunnelling=False): 78 | title_row = "" 79 | row = "" 80 | keys = list(self.KIES.keys()) 81 | 82 | # don't report the reference isotoplogue 83 | if self.config.reference_isotopologue != "default" and self.config.reference_isotopologue != "none": 84 | keys.remove(self.config.reference_isotopologue) 85 | 86 | if self.config.mass_override_isotopologue != "default" and self.config.reference_isotopologue != "none": 87 | keys.remove(self.config.mass_override_isotopologue) 88 | 89 | # for each isotopologue, add the KIEs 90 | for name in keys: 91 | if report_tunnelling: 92 | title_row += "%s,%s," % (name + "_uncorr", name + "_inf_para") 93 | else: 94 | title_row += "{0},".format(name) 95 | 96 | if self.eie_flag == 0: 97 | # this is a KIE calculation 98 | if report_tunnelling: 99 | row += "%.4f,%.4f," % ( self.KIES[name].value[-1], self.KIES[name].value[-1]-self.KIES[name].value[-3] ) 100 | else: 101 | row += "{0:.4f},".format(self.KIES[name].value[-1]) 102 | else: 103 | # this is an EIE calculation 104 | row += "{0:.4f},".format(self.KIES[name].value) 105 | #if settings.DEBUG >= 2: 106 | # if self.eie_flag == 0: 107 | # print "KIE calculation detected using {0}th tunneling correction".format(len(self.KIES[name].value)) 108 | # row += "{0:.3f},".format(self.KIES[name].value[-1]) 109 | # else: 110 | # row += "{0:.3f},".format(self.KIES[name].value) 111 | 112 | return (title_row, row, self.eie_flag) 113 | 114 | def build_mass_override_masses(self): 115 | config = self.config 116 | gs_system = self.gs_system 117 | ts_system = self.ts_system 118 | 119 | gs_masses = self.build_default_masses(gs_system) 120 | ts_masses = self.build_default_masses(ts_system) 121 | 122 | if config.mass_override_isotopologue != "default": 123 | iso = self.config.isotopologues[config.mass_override_isotopologue] 124 | gs_rules, ts_rules = self.compile_mass_rules(iso) 125 | 126 | gs_masses = self.apply_mass_rules(gs_masses, gs_rules) 127 | ts_masses = self.apply_mass_rules(ts_masses, ts_rules) 128 | 129 | return gs_masses, ts_masses 130 | 131 | def build_default_masses(self, system): 132 | masses = [] 133 | #print "---start---" 134 | #for k in DEFAULT_MASSES: 135 | # print k, DEFAULT_MASSES[k] 136 | #print "---" 137 | for i in range(system.number_of_atoms): 138 | #print i, system.atomic_numbers[i], DEFAULT_MASSES[system.atomic_numbers[i]] 139 | if not system.atomic_numbers[i] in DEFAULT_MASSES: 140 | raise ValueError("Default mass not available for atomic number %d at atom number %d in %s!" % (system.atomic_numbers[i], i+1, system.filename)) 141 | masses.append(DEFAULT_MASSES[system.atomic_numbers[i]]) 142 | #print "--end--" 143 | return np.array(masses) 144 | 145 | def apply_mass_rules(self, prev_masses, rules): 146 | masses = list(prev_masses) 147 | for k,v in rules.items(): 148 | masses[k] = v 149 | return masses 150 | 151 | def compile_mass_rules(self, iso_rules): 152 | gs_rules = {} 153 | ts_rules = {} 154 | for replacement in iso_rules: 155 | gs_atom_number, ts_atom_number, replacement_label = replacement 156 | replacement_mass = REPLACEMENTS[replacement_label] 157 | gs_rules[gs_atom_number-1] = replacement_mass 158 | ts_rules[ts_atom_number-1] = replacement_mass 159 | return gs_rules, ts_rules 160 | 161 | # make the requested isotopic substitutions 162 | # yields tuples of tuples of the form ((gs_sub, gs_ref), (ts_sub, ts_ref)) 163 | def make_isotopologues(self): 164 | config = self.config 165 | gs_system = self.gs_system 166 | ts_system = self.ts_system 167 | config.check(gs_system, ts_system) 168 | 169 | mass_override_gs_masses, mass_override_ts_masses = self.build_mass_override_masses() 170 | 171 | default_gs = quiver.Isotopologue("default", gs_system, mass_override_gs_masses) 172 | default_ts = quiver.Isotopologue("default", ts_system, mass_override_ts_masses) 173 | 174 | for id_,iso in config.isotopologues.items(): 175 | if id_ != config.mass_override_isotopologue: 176 | gs_rules, ts_rules = self.compile_mass_rules(iso) 177 | gs_masses = self.apply_mass_rules(mass_override_gs_masses, gs_rules) 178 | ts_masses = self.apply_mass_rules(mass_override_ts_masses, ts_rules) 179 | sub_gs = quiver.Isotopologue(id_, gs_system, gs_masses) 180 | sub_ts = quiver.Isotopologue(id_, ts_system, ts_masses) 181 | yield ((default_gs, sub_gs), (default_ts, sub_ts)) 182 | 183 | def __str__(self): 184 | string = "\n=== PyQuiver Analysis ===\n" 185 | if self.eie_flag == 0: 186 | string += "Isotopologue uncorrected Wigner inverted parabola\n" 187 | string += " KIE KIE KIE" 188 | else: 189 | string += "Isotopologue EIE" 190 | keys = list(self.KIES.keys()) 191 | if self.config.reference_isotopologue != "default" and self.config.reference_isotopologue != "none": 192 | keys.remove(self.config.reference_isotopologue) 193 | if self.config.mass_override_isotopologue != "default": 194 | keys.remove(self.config.mass_override_isotopologue) 195 | #keys.sort() 196 | for name in keys: 197 | string += "\n" + str(self.KIES[name]) 198 | 199 | if self.config.reference_isotopologue != "default" and self.config.reference_isotopologue != "none": 200 | string += "\n\nKIEs referenced to isotopologue {0}, whose absolute KIEs are:".format(self.config.reference_isotopologue) 201 | string += "\n" + str(self.KIES[self.config.reference_isotopologue]) 202 | else: 203 | string += "\n\nAbsolute KIEs are given." 204 | 205 | return string 206 | 207 | class KIE(object): 208 | # the constructor expects a tuple of the form yielded by make_isotopologue 209 | def __init__(self, name, gs_tuple, ts_tuple, temperature, scaling, imag_threshold): 210 | # copy fields 211 | # the associated calculation object useful for pulling config fields etc. 212 | self.eie_flag = -1 213 | self.name = name 214 | self.imag_threshold = imag_threshold 215 | self.gs_tuple, self.ts_tuple = gs_tuple, ts_tuple 216 | self.temperature = temperature 217 | self.scaling = scaling 218 | 219 | if settings.DEBUG >= 2: 220 | print("Calculating KIE for isotopologue {0}.".format(name)) 221 | self.value = self.calculate_kie() 222 | 223 | def calculate_kie(self): 224 | if settings.DEBUG >= 2: 225 | print(" Calculating Reduced Partition Function Ratio for Ground State.") 226 | rpfr_gs, gs_imag_ratios, gs_heavy_freqs, gs_light_freqs = calculate_rpfr(self.gs_tuple, self.imag_threshold, self.scaling, self.temperature) 227 | if settings.DEBUG >= 2: 228 | print(" rpfr_gs:", np.prod(rpfr_gs)) 229 | if settings.DEBUG >= 2: 230 | print(" Calculating Reduced Partition Function Ratio for Transition State.") 231 | 232 | rpfr_ts, ts_imag_ratios, ts_heavy_freqs, ts_light_freqs = calculate_rpfr(self.ts_tuple, self.imag_threshold, self.scaling, self.temperature) 233 | if settings.DEBUG >= 2: 234 | print(" rpfr_ts:", np.prod(rpfr_ts)) 235 | 236 | if ts_imag_ratios is not None: 237 | if self.eie_flag == -1: 238 | self.eie_flag = 0 239 | else: 240 | raise ValueError("quiver attempted to run a KIE calculation after an EIE calculation. Check the frequency threshold.") 241 | 242 | kies = ts_imag_ratios * rpfr_gs/rpfr_ts 243 | return kies 244 | else: 245 | if self.eie_flag == -1: 246 | self.eie_flag = 1 247 | else: 248 | raise ValueError("quiver attempted to run a KIE calculation after an EIE calculation. Check the frequency threshold.") 249 | 250 | eie = rpfr_gs/rpfr_ts 251 | return eie 252 | 253 | def apply_reference(self, reference_kie): 254 | self.value /= reference_kie.value 255 | return self.value 256 | 257 | def __str__(self): 258 | if self.value is not None: 259 | if self.eie_flag == 1: 260 | return "Isotopologue {1: >10s} {0: >33s} {2: ^12.4f} ".format("", self.name, self.value) 261 | else: 262 | return "Isotopologue {1: >10s} {0: >33s} {2: ^12.4f} {3: ^14.4f} {4: ^17.4f}".format("", self.name, self.value[0], self.value[1], self.value[2]) 263 | else: 264 | "KIE Object for isotopomer {0}. No value has been calculated yet.".format(self.name) 265 | 266 | # utility function to calculate u terms 267 | # u = hv/kT where h = Planck's constant, v = frequency, kB = Boltzmann's constant, T = temperature 268 | # if using v in wavenumber, w 269 | # w = v/c, with c in cm/s 270 | # v = cw 271 | # u = hcw/kT, with c in cm/s 272 | def u(wavenumber, temperature): 273 | return h*c*wavenumber/(kB*temperature) 274 | 275 | # calculates the reduced isotopic function ratio for a species (Wolfsberg eqn 4.79) 276 | # assuming the symmetry ratio is 1/1 277 | # uses the Teller-Redlich product rule 278 | # returns 1 x n array of the partition function ratios, where n is the number of frequencies 279 | # frequencies below frequency_threshold will be ignored and will not be included in the array 280 | def partition_components(freqs_heavy, freqs_light, temperature): 281 | components = [] 282 | i = 0 283 | for wavenumber_light, wavenumber_heavy in zip(freqs_light, freqs_heavy): 284 | i += 1 285 | product_factor = wavenumber_heavy/wavenumber_light 286 | u_light = u(wavenumber_light, temperature) 287 | u_heavy = u(wavenumber_heavy, temperature) 288 | excitation_factor = (1.0-np.exp(-u_light))/(1.0-np.exp(-u_heavy)) 289 | ZPE_factor = np.exp(0.5*(u_light-u_heavy)) 290 | components.append([product_factor,excitation_factor,ZPE_factor]) 291 | if settings.DEBUG >= 3: 292 | overall_factor = product_factor * excitation_factor * ZPE_factor 293 | print("MODE %3d LIGHT: %9.3f cm-1 HEAVY: %9.3f cm-1 FRQ RATIO: %9.5f ZPE FACTOR: %9.5f CONTRB TO RIPF: %9.5f" % (i, wavenumber_light, wavenumber_heavy, product_factor, ZPE_factor, overall_factor)) 294 | #if overall_factor < 0.99 or overall_factor > 1.01: 295 | # print " *" 296 | #else: 297 | # print 298 | return np.array(components) 299 | 300 | # tup is a tuple of a form (light_isotopologue, heavy_isotopologue) 301 | def calculate_rpfr(tup, imag_threshold, scaling, temperature): 302 | # calculate_frequencies gives tuples of the form (small_freqs, imaginary_freqs, freqs) 303 | light_small_freqs, light_imag_freqs, light_freqs, light_num_small = tup[0].calculate_frequencies(imag_threshold, scaling=scaling) 304 | heavy_small_freqs, heavy_imag_freqs, heavy_freqs, heavy_num_small = tup[1].calculate_frequencies(imag_threshold, scaling=scaling) 305 | 306 | if len(heavy_freqs) != len(light_freqs): 307 | raise ValueError("mismatch in the number of frequencies between isotopomers!") 308 | if len(light_imag_freqs) != len(heavy_imag_freqs): 309 | print("WARNING: mismatch in the number of imaginary frequencies between isotopomers, ignoring imaginary mode") 310 | light_imag_freqs = [] 311 | heavy_imag_freqs = [] 312 | 313 | if settings.DEBUG > 2: 314 | print("light imaginary frequencies: ", end=' ') 315 | if len(light_imag_freqs) == 0: 316 | print("none", end=' ') 317 | for i in light_imag_freqs: 318 | print("%.3f " % i, end=' ') 319 | print() 320 | print("light small frequencies: ", end=' ') 321 | for i in light_small_freqs: 322 | print("%.1f " % i, end=' ') 323 | print() 324 | print("heavy imaginary frequencies: ", end=' ') 325 | if len(heavy_imag_freqs) == 0: 326 | print("none", end=' ') 327 | for i in heavy_imag_freqs: 328 | print("%.3f " % i, end=' ') 329 | print() 330 | print("heavy small frequencies: ", end=' ') 331 | for i in heavy_small_freqs: 332 | print("%.1f " % i, end=' ') 333 | print() 334 | 335 | raw_imag_ratio = None 336 | imag_ratios = None 337 | try: 338 | raw_imag_ratio = light_imag_freqs[0]/heavy_imag_freqs[0] 339 | except IndexError: 340 | pass 341 | 342 | if raw_imag_ratio: 343 | wigner_imag_ratio = raw_imag_ratio * wigner(heavy_imag_freqs[0], light_imag_freqs[0], temperature) 344 | bell_imag_ratio = raw_imag_ratio * bell(heavy_imag_freqs[0], light_imag_freqs[0], temperature) 345 | imag_ratios = np.array([raw_imag_ratio, wigner_imag_ratio, bell_imag_ratio]) 346 | 347 | partition_factors = partition_components(heavy_freqs, light_freqs, temperature) 348 | 349 | if settings.DEBUG >= 2: 350 | factors = np.prod(partition_factors, axis=0) 351 | print("{3: ^8}Product Factor: {0}\n{3: ^8}Excitation Factor: {1}\n{3: ^8}ZPE Factor: {2}".format(factors[0], factors[1], factors[2], "")) 352 | 353 | return (np.prod(partition_factors), imag_ratios, np.array(heavy_freqs), np.array(light_freqs)) 354 | 355 | # calculates the Wigner tunnelling correction 356 | # multiplies the KIE by a factor of (1+u_H^2/24)/(1+u_D^2/24) 357 | # assumes the frequencies are sorted in ascending order 358 | def wigner(ts_imag_heavy, ts_imag_light, temperature): 359 | # calculate correction factor 360 | if ts_imag_heavy < 0.0 and ts_imag_light < 0.0: 361 | u_H = u(ts_imag_light, temperature) 362 | u_D = u(ts_imag_heavy, temperature) 363 | correction_factor = (1.0 + u_H**2 / 24.0) / (1.0 + u_D**2 / 24.0) 364 | else: 365 | raise ValueError("imaginary frequency passed to Wigner correction was real") 366 | 367 | return correction_factor 368 | 369 | # calculates the Bell inverted parabola tunneling correction 370 | # multiplies the KIE by a factor of (u_H/u_D)*(sin(u_D/2)/sin(u_H/2)) 371 | # assumes the frequencies are sorted in ascending order 372 | def bell(ts_imag_heavy, ts_imag_light, temperature): 373 | # calculate correction factor 374 | if ts_imag_heavy < 0.0 and ts_imag_light < 0.0: 375 | u_H = u(ts_imag_light, temperature) 376 | u_D = u(ts_imag_heavy, temperature) 377 | correction_factor = (u_H/u_D)*(np.sin(u_D/2.0)/np.sin(u_H/2.0)) 378 | else: 379 | raise ValueError("imaginary frequency passed to Bell correction was real") 380 | return correction_factor 381 | -------------------------------------------------------------------------------- /src/orca.py: -------------------------------------------------------------------------------- 1 | import re 2 | import numpy as np 3 | 4 | from io import StringIO 5 | 6 | DEBUG = False 7 | 8 | elemToNum = { 9 | "H": 1, "He": 2, 10 | "Li": 3, "Be": 4, "B": 5, "C": 6, "N": 7, "O": 8, "F": 9, "Ne": 10, 11 | "Na": 11, "Mg": 12, "Al": 13, "Si": 14, "P": 15, "S": 16, "Cl": 17, "Ar": 18, 12 | "K": 19, "Ca": 20, 13 | "Sc": 21, "Ti": 22, "V": 23, "Cr": 24, "Mn": 25, "Fe": 26, "Co": 27, "Ni": 28, "Cu": 29, "Zn": 30, 14 | "Ga": 31, "Ge": 32, "As": 33, "Se": 34, "Br": 35, "Kr": 36, 15 | "Rb": 37, "Sr": 38, 16 | "Y": 39, "Zr": 40, "Nb": 41, "Mo": 42, "Tc": 43, "Ru": 44, "Rh": 45, "Pd": 46, "Ag": 47, "Cd": 48, 17 | "In": 49, "Sn": 50, "Sb": 51, "Te": 52, "I": 53, "Xe": 54, 18 | "Cs": 55, "Ba": 56, 19 | "La": 57, "Ce": 58, "Pr": 59, "Nd": 60, "Pm": 61, "Sm": 62, "Eu": 63, "Gd": 64, "Tb": 65, "Dy": 66, "Ho": 67, "Er": 68, "Tm": 69, "Yb": 70, "Lu": 71, 20 | "Hf": 72, "Ta": 73, "W": 74, "Re": 75, "Os": 76, "Ir": 77, "Pt": 78, "Au": 79, "Hg": 80, 21 | "Tl": 81, "Pb": 82, "Bi": 83, "Po": 84, "At": 85, "Rn": 86, 22 | "Fr": 87, "Ra": 88, 23 | "Ac": 89, "Th": 90, "Pa": 91, "U": 92, "Np": 93, "Pu": 94, "Am": 95, "Cm": 96, "Bk": 97, "Cf": 98, "Es": 99, "Fm": 100, "Md": 101, "No": 102, "Lr": 103, 24 | "Rf": 104, "Db": 105, "Sg": 106, "Bh": 107, "Hs": 108, "Mt": 109, "Ds": 110, "Rg": 111, "Cn": 112 25 | } 26 | 27 | 28 | def debug(s): 29 | if DEBUG: 30 | print("DEBUG: {}".format(s)) 31 | 32 | def bohr2angstrom(length): 33 | return length*0.529177249 34 | 35 | def parse_orca_output(out_data): 36 | lines = out_data.split('\n') 37 | 38 | # Molecule position data 39 | try: 40 | atoms_line = next( 41 | (n for n, line in enumerate(lines) 42 | if line.startswith('$atoms'))) 43 | except StopIteration: 44 | raise ValueError("Could not find '$atoms' in output data.") 45 | 46 | number_of_atoms = int(lines[atoms_line+1]) 47 | debug("number_of_atoms={}".format(number_of_atoms)) 48 | 49 | atomic_numbers = [] 50 | positions = np.zeros(shape=(number_of_atoms, 3)) 51 | for i, line in enumerate( 52 | lines[atoms_line+2:atoms_line+number_of_atoms+2]): 53 | _, atom, _, x, y, z = re.split(r'\s+', line) 54 | 55 | atomic_numbers.append(elemToNum[atom]) 56 | positions[i][0] = bohr2angstrom(float(x)) 57 | positions[i][1] = bohr2angstrom(float(y)) 58 | positions[i][2] = bohr2angstrom(float(z)) 59 | 60 | debug("atomic_numbers={}".format(atomic_numbers)) 61 | debug("positions={}".format(positions)) 62 | 63 | try: 64 | hessian_line = next( 65 | (n for n, line in enumerate(lines) 66 | if line.startswith('$hessian'))) 67 | except StopIteration: 68 | raise ValueError("Could not find '$hessian' in output data.") 69 | 70 | hessian_size = int(lines[hessian_line+1]) 71 | debug("Size of hessian: {}".format(hessian_size)) 72 | 73 | hessian = None 74 | 75 | for i, batch in enumerate(range(0, hessian_size, 5)): 76 | batch_size = min(5, hessian_size-batch) 77 | 78 | s = StringIO(u"\n".join( 79 | lines[hessian_line+(i*(hessian_size+1)+2): 80 | hessian_line+((i+1)*(hessian_size+1))+2])) 81 | 82 | h = np.loadtxt(s, skiprows=1, 83 | converters=dict((i+1, float) for i in range(batch_size)), 84 | usecols=tuple(i+1 for i in range(batch_size)), 85 | ndmin=2) 86 | 87 | hessian = h if hessian is None else np.hstack((hessian, h)) 88 | 89 | hessian = (hessian + hessian.T)/2 90 | debug("Hessian: {}".format(hessian)) 91 | 92 | return atomic_numbers, positions, hessian 93 | 94 | 95 | if __name__ == '__main__': 96 | DEBUG = True 97 | import sys 98 | with open(sys.argv[1], 'r') as f: 99 | out_data = f.read() 100 | parse_orca_output(out_data) 101 | -------------------------------------------------------------------------------- /src/quiver.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | import re 4 | import os 5 | import pickle 6 | import math 7 | 8 | import numpy as np 9 | 10 | import settings 11 | from utility import proj, normalize, test_orthogonality, schmidt 12 | from constants import DEFAULT_MASSES, PHYSICAL_CONSTANTS, LINEARITY_THRESHOLD, DROP_NUM_LINEAR 13 | from config import Config 14 | 15 | # represents a geometric arrangement of atoms with specific masses 16 | class Isotopologue(object): 17 | def __init__(self, id_, system, masses): 18 | self.name = id_ 19 | self.system = system 20 | self.masses = masses 21 | self.frequencies = None 22 | 23 | self.number_of_atoms = system.number_of_atoms 24 | self.mw_hessian = self.calculate_mw_hessian() 25 | 26 | def __str__(self): 27 | returnString = "Isotopologue: %s\n" % self.name 28 | returnString += " masses: %s" % self.masses.__str__() 29 | return returnString 30 | 31 | def dump_debug(self, name, obj): 32 | pass 33 | #f = open(name+'_'+(self.system.filename)+'.pickle', 'w') 34 | #ret = pickle.dump(obj, f) 35 | #f.close() 36 | 37 | def calculate_mw_hessian(self): 38 | #### old code - this is very inefficient because we calculate sqrt about a zillion times 39 | #### better to call sqrt on the whole matrix and let numpy vectorize it 40 | 41 | #mw_hessian2 = np.zeros_like(hessian) 42 | #mass_weights2=[] 43 | #for i in range(0, 3*self.number_of_atoms): 44 | # for j in range(0, 3*self.number_of_atoms): 45 | # mass_weights2.append(1/(np.sqrt(masses3[i]*masses3[j]))) 46 | # mw_hessian2[i,j] = hessian[i,j] / np.sqrt(masses3[i] * masses3[j] ) 47 | 48 | #### new code, a bit more efficient 49 | 50 | # scatter masses into 2D array of combinations 51 | masses_ij = np.outer(np.array(self.masses), np.array(self.masses)) 52 | 53 | # call sqrt once! 54 | # if we wanted we could take advantage of the symmetry of the matrix here... 55 | # but numpy is fast enough that it really doesn't matter 56 | inv_sqrt_masses = 1 / np.sqrt(masses_ij) 57 | 58 | # now we triple each dimension, to match the dimension of the hessian 59 | inv_sqrt_masses = np.repeat(inv_sqrt_masses, 3, 0) 60 | inv_sqrt_masses = np.repeat(inv_sqrt_masses, 3, 1) 61 | 62 | # and use matrix multiplication to generate mw_hessian 63 | mass_weights = np.ravel(inv_sqrt_masses) 64 | mw_hessian = self.system.hessian * inv_sqrt_masses 65 | 66 | #assert np.allclose(mw_hessian, mw_hessian2) 67 | #assert all([a == b for a, b in zip(mass_weights, mass_weights2)]) 68 | 69 | if settings.DEBUG >= 3: 70 | self.dump_debug("mw", mass_weights) 71 | self.dump_debug("mw_hessian", mw_hessian) 72 | 73 | return mw_hessian 74 | 75 | def calculate_frequencies(self, imag_threshold, scaling=1.0, method="mass weighted hessian"): 76 | # short circuit if frequencies have already been calculated 77 | if self.frequencies is not None: 78 | return self.frequencies 79 | 80 | if method == "mass weighted hessian": 81 | imaginary_freqs = [] 82 | small_freqs = [] 83 | conv_factor = PHYSICAL_CONSTANTS['Eh']/(PHYSICAL_CONSTANTS['a0']**2 * PHYSICAL_CONSTANTS['amu']) 84 | 85 | v = np.linalg.eigvalsh(self.mw_hessian*conv_factor) 86 | 87 | constant = scaling / (2*np.pi*PHYSICAL_CONSTANTS['c']) 88 | freqs = np.sqrt(np.abs(v)) * np.sign(v) * constant 89 | 90 | #freqs2 = [ np.copysign(np.sqrt(np.abs(freq)),freq) * constant for freq in v ] 91 | #assert np.allclose(np.array(freqs), freqs2) 92 | 93 | freqs.sort() 94 | 95 | imaginary_freqs = [] 96 | small_freqs = [] 97 | regular_freqs = [] 98 | 99 | # detect imaginary frequencies 100 | for f in freqs: 101 | if f < -imag_threshold: 102 | imaginary_freqs.append(f) 103 | 104 | if len(imaginary_freqs) > 1 and settings.DEBUG >= 2: 105 | print("WARNING: multiple imaginaries") 106 | 107 | # strip the imaginary frequencies 108 | freqs = freqs[len(imaginary_freqs):] 109 | 110 | if self.system.is_linear: 111 | small_freqs = freqs[:DROP_NUM_LINEAR] 112 | regular_freqs = freqs[DROP_NUM_LINEAR:] 113 | else: 114 | small_freqs = freqs[:1+DROP_NUM_LINEAR] 115 | regular_freqs = freqs[1+DROP_NUM_LINEAR:] 116 | 117 | # bugfix 2/6/20: third argument is regular_freqs, not freqs! 118 | self.frequencies = (small_freqs, imaginary_freqs, np.array(regular_freqs), len(small_freqs)) 119 | if settings.DEBUG >= 3: 120 | self.dump_debug("freqs", self.frequencies) 121 | return self.frequencies 122 | else: 123 | raise ValueError("unknown frequency calculation type") 124 | 125 | 126 | class System(object): 127 | def __init__(self, outfile, style="g09"): 128 | self.positions_angstrom = False 129 | self.positions = False 130 | self.atomic_numbers = False 131 | self.number_of_atoms = False 132 | self.hessian = False 133 | self.is_linear = True 134 | self.filename = outfile 135 | 136 | valid_styles = ["g09", "pyquiver", "orca"] 137 | if style not in valid_styles: 138 | raise ValueError("specified style, {0}, not supported".format(style)) 139 | 140 | if settings.DEBUG >= 1: 141 | print("Reading data from {0}... with style {1}".format(outfile, style)) 142 | 143 | # assumes snip files worked correctly 144 | if style == "g09" and not outfile.endswith(".snip") and not "Normal termination" in tail(outfile): 145 | raise ValueError("Gaussian job %s terminated in an error" % outfile) 146 | 147 | self.filename = outfile 148 | with open(outfile, 'r') as f: 149 | out_data = f.read() 150 | if style == "pyquiver": 151 | lines = out_data.split("\n") 152 | try: 153 | number_of_atoms = int(lines[0]) 154 | except ValueError: 155 | raise ValueError("first line must contain integer number of atoms.") 156 | self.number_of_atoms = number_of_atoms 157 | 158 | atomic_numbers = [0 for i in range(number_of_atoms)] 159 | positions = np.zeros(shape=(number_of_atoms,3)) 160 | 161 | for l in lines[1:number_of_atoms+1]: 162 | fields = l.split(',') 163 | try: 164 | center_number, atomic_number, x, y, z = fields 165 | except ValueError: 166 | raise ValueError("the following line in the geometry did not have the appropriate number of fields: {0}".format(l)) 167 | center_number = int(center_number) 168 | atomic_number = int(atomic_number) 169 | 170 | atomic_numbers[center_number] = atomic_number 171 | positions[center_number][0] = x 172 | positions[center_number][1] = y 173 | positions[center_number][2] = z 174 | 175 | fcm_fields = lines[number_of_atoms+1].split(',') 176 | hessian = self._parse_serial_lower_hessian(fcm_fields) 177 | 178 | if style == "g09": 179 | # check that the verbose output option has been set 180 | verbose_flag_present = re.search(r" *#[pP] ", out_data) 181 | if verbose_flag_present == None and not outfile.lower().endswith(".snip"): 182 | print() 183 | print("Error: Gaussian output file %s" % outfile) 184 | print("was not run with the verbose flag, so it does not contain enough information for") 185 | print("PyQuiver to run. Please re-run this calculation with a route card that starts with #p") 186 | print() 187 | sys.exit(1) 188 | 189 | # read in the number of atoms 190 | m = re.search("NAtoms\= +([0-9]+)", out_data) 191 | if m: 192 | number_of_atoms = int(m.group(1)) 193 | else: 194 | raise AttributeError("Number of atoms not detected.") 195 | m = None 196 | self.number_of_atoms = number_of_atoms 197 | 198 | # read in the last geometry (assumed cartesian coordinates) 199 | atomic_numbers = [0 for i in range(number_of_atoms)] 200 | positions = np.zeros(shape=(number_of_atoms,3)) 201 | 202 | # use standard orientation if possible 203 | for m in re.finditer("Standard orientation(.+?)Rotational constants \(GHZ\)", out_data, re.DOTALL): 204 | pass 205 | 206 | # for input files with nosymm keyword, use input orientation 207 | if m is None: 208 | for m in re.finditer("Input orientation(.+?)Distance matrix", out_data, re.DOTALL): 209 | pass 210 | if not m is None: 211 | print("Couldn't find standard orientation so used input orientation instead.") 212 | 213 | if m is None: 214 | for m in re.finditer("Input orientation(.+?)Rotational constants \(GHZ\)", out_data, re.DOTALL): 215 | pass 216 | if not m is None: 217 | print("Couldn't find standard orientation so used input orientation instead.") 218 | 219 | # still couldn't find any geometries 220 | if m is None: 221 | raise AttributeError("Geometry table not detected.") 222 | 223 | def valid_geom_line_p(split_line): 224 | if len(split_line) == 6: 225 | try: 226 | int(split_line[0]) 227 | int(split_line[1]) 228 | int(split_line[2]) 229 | return True 230 | except ValueError: 231 | return False 232 | return False 233 | 234 | for l in m.group(1).split('\n'): 235 | raw_geom_line = l.split() 236 | raw_geom_line = [_f for _f in l.split(' ') if _f] 237 | 238 | if valid_geom_line_p(raw_geom_line): 239 | center_number = int(raw_geom_line[0]) - 1 240 | atomic_numbers[center_number] = int(raw_geom_line[1]) 241 | for e in range(0,3): 242 | positions[center_number][e] = raw_geom_line[3+e] 243 | 244 | # units = hartrees/bohr^2 245 | hessian = self._parse_g09_hessian(out_data) 246 | 247 | elif style == "orca": 248 | from orca import parse_orca_output 249 | atomic_numbers, positions, hessian = parse_orca_output(out_data) 250 | self.number_of_atoms = len(atomic_numbers) 251 | 252 | 253 | # copy fields 254 | self.hessian = hessian 255 | 256 | self.positions_angstrom = positions 257 | # convert position in angstroms to needed bohr 258 | self.positions = positions/PHYSICAL_CONSTANTS['atb'] 259 | 260 | # detect if molecule is linear 261 | # method: take every difference of two centers (that share center 0) 262 | # calculate the dot product with other differences 263 | 264 | if self.number_of_atoms > 2: 265 | detected_indep = 0 266 | for i in range(1,len(positions)-1): 267 | for j in range(i+1, len(positions)): 268 | # compares how parallel the unit vectors are 269 | diff0i = positions[0] - positions[i] 270 | diff0j = positions[0] - positions[j] 271 | u = diff0i/np.linalg.norm(diff0i) 272 | v = diff0j/np.linalg.norm(diff0j) 273 | # if the vectors are (probably) linearly indep, we break 274 | if 1 - np.abs(np.inner(u,v)) > LINEARITY_THRESHOLD: 275 | self.is_linear = False 276 | detected_indep = 1 277 | break 278 | 279 | if detected_indep: 280 | break 281 | linear_string = "linear" if self.is_linear else "not linear" 282 | if settings.DEBUG >= 2: 283 | print("Molecule is %s." % linear_string) 284 | self.atomic_numbers = atomic_numbers 285 | 286 | def dump_debug(self, obj): 287 | f = open('freqs_'+self.name+'.json', 'w') 288 | out = json.dumps(self.frequencies) 289 | f.write(out) 290 | f.close() 291 | 292 | def _lower_triangle_serial_triangle(self,row,col): 293 | if col > row: 294 | return self._lower_triangle_serial_triangle(col,row) 295 | triangle = lambda n: n*(n+1)/2 296 | return int(triangle(row) + col) 297 | 298 | # search for the archive at the end of the file 299 | # then extract the force constant matrix 300 | def _parse_g09_hessian(self, data): 301 | #print("\n\nparsing\n\n") 302 | # regex for finding text between 1\1\GINC and \@ 303 | # DOTALL means that . will match newlines 304 | # there are two capture groups here, which is why we have to use archive[0] later 305 | raw_archive = re.findall(r"1\\1\\GINC(.+?)\\(\s*)@", data, re.DOTALL) 306 | found_frequencies = False 307 | for archive in raw_archive: 308 | archive = re.sub('[\s+]', '', archive[0]) 309 | #print(archive[:1000]) 310 | #print("...") 311 | #print(archive[-1000:]) 312 | #print("---") 313 | archive = re.search("NImag\=(.+?)$", archive, re.DOTALL) 314 | #print(archive) 315 | #print("*") 316 | #print() 317 | if archive: 318 | found_frequencies = True 319 | break 320 | if not found_frequencies: 321 | raise ValueError(f"No frequency job detected in {self.filename}.") 322 | 323 | raw_fcm = archive.group(0).split('\\')[2].split(',') 324 | #print(raw_fcm) 325 | self.raw_fcm = raw_fcm 326 | fcm = self._parse_serial_lower_hessian(raw_fcm) 327 | #print("\n\nsuccess\n\n") 328 | return fcm 329 | 330 | def _parse_serial_lower_hessian(self, fields): 331 | #fcm = np.zeros(shape=(3*self.number_of_atoms, 3*self.number_of_atoms)) 332 | #for i in range(3*self.number_of_atoms): 333 | # for j in range(3*self.number_of_atoms): 334 | # fcm[i,j] = fields[self._lower_triangle_serial_triangle(i,j)] 335 | 336 | # compute index over entire grid at once 337 | range1d = np.array(range(3*self.number_of_atoms), dtype=int) 338 | xgrid, ygrid = np.meshgrid(range1d, range1d) 339 | agrid = np.minimum(xgrid, ygrid) 340 | bgrid = np.maximum(xgrid, ygrid) 341 | idxs2 = (bgrid*(bgrid+1)/2 + agrid).astype(int) 342 | 343 | fcm2 = np.array(fields, dtype=float)[idxs2] 344 | #assert np.allclose(fcm, fcm2) 345 | 346 | return fcm2 347 | 348 | def _make_serial_hessian(self): 349 | serial = "" 350 | for i in range(self.number_of_atoms*3): 351 | for j in range(0,i+1): 352 | serial += str(self.hessian[i,j]) + "," 353 | return serial 354 | 355 | def _make_serial_geometry(self): 356 | serial = "" 357 | for i in range(self.number_of_atoms): 358 | serial += "{0},{1},{2},{3},{4}\n".format(i, self.atomic_numbers[i], self.positions_angstrom[i,0], self.positions_angstrom[i,1], self.positions_angstrom[i,2]) 359 | return serial 360 | 361 | 362 | def dump_pyquiver_input_file(self, extension=".qin"): 363 | path = os.path.splitext(self.filename)[0] + extension 364 | serial = str(self.number_of_atoms) + "\n" 365 | serial += self._make_serial_geometry() 366 | serial += self._make_serial_hessian() 367 | 368 | with open(path, 'w') as f: 369 | f.write(serial) 370 | 371 | return serial 372 | 373 | if __name__ == "__main__": 374 | parser = argparse.ArgumentParser(description="PyQuiver calculates KIEs and EIEs based on a ground and transition state file.") 375 | parser.add_argument('-v', '--verbose', dest="debug", help='when the verbose flag is set debug information is printed', action='count') 376 | parser.add_argument('-s', '--style', dest="style", default='g09', help='style of input files (g09, orca, or pyquiver)') 377 | parser.add_argument('config', help='configuration file path') 378 | parser.add_argument('gs', help='ground state file path') 379 | parser.add_argument('ts', help='transition state file path') 380 | 381 | args = parser.parse_args() 382 | if args.debug: 383 | settings.DEBUG = args.debug + 1 384 | 385 | from kie import KIE_Calculation 386 | calc = KIE_Calculation(args.config, args.gs, args.ts, style=args.style) 387 | print(calc) 388 | 389 | 390 | def slugify(value): 391 | return "".join(x for x in value if x.isalnum()) 392 | 393 | def tail(filename): 394 | # with open(filename) as f: 395 | # content = f.readlines() 396 | # return content[-1].strip() 397 | 398 | # https://stackoverflow.com/questions/46258499/how-to-read-the-last-line-of-a-file-in-python 399 | with open(filename, 'rb') as f: 400 | try: # catch OSError in case of a one line file 401 | f.seek(-2, os.SEEK_END) 402 | while f.read(1) != b'\n': 403 | f.seek(-2, os.SEEK_CUR) 404 | except OSError: 405 | f.seek(0) 406 | return f.readline().decode() 407 | -------------------------------------------------------------------------------- /src/settings.py: -------------------------------------------------------------------------------- 1 | # DEBUG global variable 2 | # 3 = dump files into cwd 3 | # 2 = extra verbose - corresponds to -v flag on quiver or -vv flag on auto 4 | # 1 = reasonable level of printing 5 | # 0 = minimal printing 6 | DEBUG = 0 7 | -------------------------------------------------------------------------------- /src/utility.py: -------------------------------------------------------------------------------- 1 | ### This file contains utility functions used in quiver calculations. 2 | 3 | # Linear Algebra utility function 4 | 5 | def proj(u,v): 6 | # project u onto v 7 | return np.inner(v,u)/np.inner(u,u) * u 8 | 9 | def normalize(v): 10 | norm=np.linalg.norm(v) 11 | if norm < 1.0E-5: 12 | raise ValueError 13 | return v/norm 14 | 15 | def test_orthogonality(vectors): 16 | mat = np.zeros(shape=(len(vectors),len(vectors))) 17 | for i,u in enumerate(vectors): 18 | for j,v in enumerate(vectors): 19 | inner = np.inner(u,v) 20 | if inner != 0.0 or inner != 1.0: 21 | mat[i][j] = inner 22 | print("Orthogonality:") 23 | for l in mat: 24 | print(l) 25 | 26 | def schmidt(seed_vectors, rest_vectors, dimension): 27 | vectors = list(seed_vectors) 28 | test_vectors = list(rest_vectors) 29 | while len(vectors) < dimension: 30 | try: 31 | test_vector = test_vectors.pop() 32 | for v in vectors: 33 | test_vector -= proj(v, test_vector) 34 | try: 35 | vectors.append(normalize(test_vector)) 36 | except ValueError: 37 | pass 38 | 39 | except IndexError: 40 | raise ValueError("Could not fill the appropriate dimensional space") 41 | if len(vectors) < dimension: 42 | raise ValueError("Could not fill the appropriate dimensional space") 43 | else: 44 | return vectors 45 | -------------------------------------------------------------------------------- /src/weights.dat: -------------------------------------------------------------------------------- 1 | # This file stores the atomic weights and masses that will be used in the mass-weighted Hessian calculation. 2 | # 3 | # format is: 4 | # element_name,atomic_number,atomic_symbol,atomic_weight, [atomic_number_of_replacement, symbol_of_replacement] 5 | # 6 | # if no replacements are specified, the atomic weight will be used to calculate the frequencies and 7 | # no KIEs can be calculated at that position 8 | # 9 | # comment lines begin with # and will be ignored 10 | # anything after a # is also ignored 11 | # 12 | # choices here are taken to match those of quiver 13 | # otherwise, data taken from http://physics.nist.gov/cgi-bin/Compositions/stand_alone.pl 14 | # 15 | # as a safety feature, you must specify the data in ascending order of atomic number 16 | #hydrogen,1,H,1.00783,1H,1.00783,2D,2.0141,3T,3.016049 17 | hydrogen,1,H,1.0078250,1H,1.00783,2D,2.0141,3T,3.016049 18 | lithium,3,Li,6.94 19 | boron,5,B,10.811 20 | carbon,6,C,12.0,12C,12.0,13C,13.00335,14C,14.0031 21 | nitrogen,7,N,14.0067,14N,14.0031,15N,15.0001 22 | #oxygen,8,O,15.9994,16O,15.9949,17O,16.9991,18O,17.9992 23 | oxygen,8,O,15.9949146,16O,15.9949,17O,16.9991,18O,17.9992 24 | fluorine,9,F,18.9984,18F,18.0009,19F,18.9984 25 | sodium,11,Na,22.9898 26 | magnesium,12,Mg,24.305 27 | aluminum,13,Al,26.9815 28 | silicon,14,Si,28.0855 29 | phosphorus,15,P,30.9738 30 | sulfur,16,S,32.453 31 | chlorine,17,Cl,35.453 32 | potassium,19,K,39.0983 33 | titanium,22,Ti,47.867 34 | vanadium,23,V,50.9439595 35 | iron,26,Fe,55.845 36 | cobalt,27,Co,58.9332 37 | nickel,28,Ni,58.6934 38 | copper,29,Cu,63.546 39 | zinc,30,Zn,65.39 40 | selenium,34,Se,78.96 41 | bromine,35,Br,79.9 42 | rhodium,45,Rh,102.9055 43 | palladium,46,Pd,106.42 44 | silver,47,Ag,107.8682 45 | tin,50,Sn,118.71 46 | iodine,53,I,126.9045 47 | cesium,55,Cs,132.9055 48 | platinum,78,Pt,195.078 49 | gold,79,Au,196.9665 50 | -------------------------------------------------------------------------------- /tutorial/TUTORIAL.md: -------------------------------------------------------------------------------- 1 | ## Tutorial 2 | 3 | In this tutorial, we reproduce the B3LYP/6-31G* KIE predictions for the following Claisen rearrangement reported by Singleton ([reference 5](../DETAILS.md#references)): 4 | 5 | 6 | 7 | 8 | 9 | (This is Table 4 in the paper.) 10 | 11 | ### File Locations 12 | 13 | All the files associated with this tutorial are available in the `tutorial/` directory. There are separate folders for running with Gaussian, ORCA, or PyQuiver standard files. 14 | 15 | Here, we'll focus on the use of PyQuiver with Gaussian. This tutorial requires the *PyQuiver* configuration file `claisen_demo.config` and the g09 output files `claisen_gs.out` and `claisen_ts.out`, representing the ground and transition state frequency calculations, respectively. 16 | 17 | If running with ORCA, use the `.hess` files instead of the output files. If running with Quiver Standard files, use the `.qin` files. 18 | 19 | Note that the tutorial results will differ slightly with the provided ORCA files due to subtle differences in geometry. Thse differences are not experimentally meaningful and can be ignored. 20 | 21 | ### Getting Started 22 | 23 | In general, all KIEs are defined as rate constant(light)/rate constant(heavy). For example, the absolute KIE at C1 is defined as the rate of the rearrangement with carbon-12 at C1 divided by the rate with carbon-13 at C1. This definition is given by this line of the `claisen_demo.config` file: 24 | 25 | ``` 26 | isotopomer C1 1 1 13C 27 | ``` 28 | 29 | `C1` is an arbitrary label for the KIE of interest (it can be any string without a space character). In general, we may want to calculate multiple KIEs using one configuration file. For example, the next few lines define the KIEs at C2 and the oxygen: 30 | 31 | ``` 32 | isotopomer C2 2 2 13C 33 | isotopomer O3 3 3 17O 34 | ``` 35 | 36 | In each case, the isotopomer definition is followed by three parameters: the atom number in the ground state, the atom number in the transition state, and the isotope to substitute with. For example, for C1, atom 1 in the ground state and atom 1 in the transition state will be substituted with carbon-13. In general, *PyQuiver* will try to prevent you from entering isotopomers that do not make sense. 37 | 38 | The definition of `13C` is drawn from `src/weights.dat`: 39 | 40 | ``` 41 | carbon,6,C,12.0,12C,12.0,13C,13.00335,14C,14.0031 42 | ``` 43 | 44 | In English, this says that carbon has an atomic number of 6 and has the symbol `C`. In all cases (`C1`, `C2`, `O`, etc.), whenever carbon appears in the "light" isotopomer, it is defined to have a mass of `12.0`. This is called the "default mass." If the "heavy" replacement is specified as `13C`, it is given a mass of `13.00335`. Although a number of common replacements are already defined (e.g., `2D` (deuterium) or `18O` (oxygen-18)), it is easy to add more definitions. 45 | 46 | Note that KIEs can be defined for multiple isotopic replacements by repeating the label of the isotopomer. For example, these entries replace two hydrogens with two deuteriums: 47 | 48 | ``` 49 | isotopomer H/D 7 7 2D 50 | isotopomer H/D 8 8 2D 51 | ``` 52 | 53 | Now we are ready to calculate the KIEs! Enter in the following: 54 | 55 | ``` 56 | cd src/ 57 | python quiver.py ../tutorial/gaussian/claisen_demo.config ../tutorial/gaussian/claisen_gs.out ../tutorial/gaussian/claisen_ts.out 58 | ``` 59 | 60 | When run from the command line, *PyQuiver* expects the names (in order) of the configuration file, the ground state file, and the transition state file. The expected output is: 61 | 62 | ``` 63 | Read atomic weight data for 30 elements. 64 | 65 | Reading configuration from claisen_demo.config 66 | Reading data from claisen_gs.out... with style g09 67 | Reading data from claisen_ts.out... with style g09 68 | Config file: claisen_demo.config 69 | Temperature: 393.0 K 70 | Scaling: 0.961 71 | Reference Isotopologue: C5 72 | Imag threshold (cm-1): 50 73 | Isotopologue C5, replacement 1: replace gs atom 5 and ts atom 5 with 13C 74 | Isotopologue C1, replacement 1: replace gs atom 1 and ts atom 1 with 13C 75 | Isotopologue C2, replacement 1: replace gs atom 2 and ts atom 2 with 13C 76 | Isotopologue C4, replacement 1: replace gs atom 4 and ts atom 4 with 13C 77 | Isotopologue C6, replacement 1: replace gs atom 6 and ts atom 6 with 13C 78 | Isotopologue H/D, replacement 1: replace gs atom 7 and ts atom 7 with 2D 79 | Isotopologue H/D, replacement 2: replace gs atom 8 and ts atom 8 with 2D 80 | Isotopologue O3, replacement 1: replace gs atom 3 and ts atom 3 with 17O 81 | 82 | === PyQuiver Analysis === 83 | Isotopologue uncorrected Wigner inverted parabola 84 | KIE KIE KIE 85 | Isotopologue C1 1.0091 1.0107 1.0110 86 | Isotopologue C2 1.0000 1.0000 1.0000 87 | Isotopologue O3 1.0153 1.0168 1.0171 88 | Isotopologue C4 1.0249 1.0276 1.0281 89 | Isotopologue C6 1.0111 1.0128 1.0131 90 | Isotopologue H/D 0.9515 0.9529 0.9532 91 | 92 | KIEs referenced to isotopologue C5, whose absolute KIEs are: 93 | Isotopologue C5 1.0019 1.0019 1.0019 94 | ``` 95 | 96 | Note that these KIEs are *relative* to the KIE at `C5`. This is controlled by this line of the config file: 97 | 98 | ``` 99 | reference_isotopomer C5 100 | ``` 101 | 102 | This means that all absolute KIEs will be divided by this one to give relative KIEs. Use `none` to calculate absolute KIEs only. 103 | 104 | These numbers agree closely with the predictions reported by Singleton. There are small (0.001) differences that arise from roundoff errors, differing values of physical constants, slight changes in the way masses are handled, and the way the original QUIVER program handles small rotational/translational frequencies with a threshold system. These small differences should not affect any chemical conclusions. 105 | 106 | ### Summary 107 | 108 | The above captures the basic workflow of a *PyQuiver* calculation: 109 | 110 | * locate ground and transition states (if using Gaussian, turn on the verbose `#p` flag) 111 | * run a frequency calculations 112 | * specify the desired isotopic substitutions in a configuration file 113 | * run `python quiver.py` on the configuration, ground state, and transition state files 114 | 115 | If EIEs are desired, simply replace the transition state with the equilibrium state of interest. 116 | -------------------------------------------------------------------------------- /tutorial/gaussian/claisen_demo.config: -------------------------------------------------------------------------------- 1 | # This file will control the behavior of PyQuiver. 2 | # Blank lines and lines starting with # will be ignored. 3 | 4 | # scaling factor for frequencies 5 | # frequencies will be multiplied by this value 6 | scaling 0.9614 7 | 8 | ### deprecated: this keyword will now be ignored ### 9 | #frequency_threshold 50 10 | 11 | # imaginaries less than this value in i*cm-1 will be ignored for the transition mode 12 | imag_threshold 50 13 | 14 | # temperature in K 15 | temperature 393 16 | 17 | # specifies the masses used for the light isotopomer 18 | # specify the name of an isotopomer or 19 | # use "default" to use the default masses in weights.dat 20 | mass_override_isotopologue default 21 | 22 | # all KIEs will be divided by the KIE at this position 23 | # specify the name of an isotopomer or 24 | # use "default" or "none" to skip this step 25 | reference_isotopomer C5 26 | # Uncomment to calculate absolute KIEs only. 27 | #reference_isotopomer none 28 | 29 | # define the isotopomers here 30 | # 31 | # isotopomer name, atom number in ground state, atom number in transition state, valid replacement atomic weight (must be specified in weights.dat) 32 | # 33 | # for example, 'isotopomer abc 2 4 C13' replaces atom 2 in the gs and atom 4 in the ts with carbon-13 34 | # and calls the resulting KIE "abc" 35 | # 36 | # add additional isotopomer lines with the same name to give multiple replacements within a single isotopomer 37 | 38 | # the name reference was selected above as the reference isotopomer 39 | 40 | isotopomer C1 1 1 13C 41 | isotopomer C2 2 2 13C 42 | isotopomer O3 3 3 17O 43 | isotopomer C4 4 4 13C 44 | 45 | # this is identical to the reference isotopomer and therefore ought to produce a value of 1.00 (which it does) 46 | isotopomer C5 5 5 13C 47 | isotopomer C6 6 6 13C 48 | 49 | # example of doing a multiple replacement 50 | isotopomer H/D 7 7 2D 51 | isotopomer H/D 8 8 2D 52 | -------------------------------------------------------------------------------- /tutorial/orca/claisen_demo.config: -------------------------------------------------------------------------------- 1 | # This file will control the behavior of PyQuiver. 2 | # Blank lines and lines starting with # will be ignored. 3 | 4 | # scaling factor for frequencies 5 | # frequencies will be multiplied by this value 6 | scaling 0.9614 7 | 8 | ### deprecated: this keyword will now be ignored ### 9 | #frequency_threshold 50 10 | 11 | # imaginaries less than this value in i*cm-1 will be ignored for the transition mode 12 | imag_threshold 50 13 | 14 | # temperature in K 15 | temperature 393 16 | 17 | # specifies the masses used for the light isotopomer 18 | # specify the name of an isotopomer or 19 | # use "default" to use the default masses in weights.dat 20 | mass_override_isotopologue default 21 | 22 | # all KIEs will be divided by the KIE at this position 23 | # specify the name of an isotopomer or 24 | # use "default" or "none" to skip this step 25 | reference_isotopomer C5 26 | # Uncomment to calculate absolute KIEs only. 27 | #reference_isotopomer none 28 | 29 | # define the isotopomers here 30 | # 31 | # isotopomer name, atom number in ground state, atom number in transition state, valid replacement atomic weight (must be specified in weights.dat) 32 | # 33 | # for example, 'isotopomer abc 2 4 C13' replaces atom 2 in the gs and atom 4 in the ts with carbon-13 34 | # and calls the resulting KIE "abc" 35 | # 36 | # add additional isotopomer lines with the same name to give multiple replacements within a single isotopomer 37 | 38 | # the name reference was selected above as the reference isotopomer 39 | 40 | isotopomer C1 1 1 13C 41 | isotopomer C2 2 2 13C 42 | isotopomer O3 3 3 17O 43 | isotopomer C4 4 4 13C 44 | 45 | # this is identical to the reference isotopomer and therefore ought to produce a value of 1.00 (which it does) 46 | isotopomer C5 5 5 13C 47 | isotopomer C6 6 6 13C 48 | 49 | # example of doing a multiple replacement 50 | isotopomer H/D 7 7 2D 51 | isotopomer H/D 8 8 2D 52 | -------------------------------------------------------------------------------- /tutorial/orca/claisen_gs.xyz: -------------------------------------------------------------------------------- 1 | 14 2 | claisen_gs 3 | C 2.533366 0.354528 -0.000003 4 | C 1.645119 -0.643361 -0.000007 5 | O 0.289757 -0.572633 -0.000001 6 | C -0.300955 0.716222 -0.000005 7 | C -1.797864 0.595382 -0.000005 8 | C -2.478428 -0.549716 -0.000009 9 | H 3.589823 0.107677 -0.000008 10 | H 2.263112 1.404966 0.000008 11 | H 1.954434 -1.685673 -0.000016 12 | H 0.038878 1.281578 -0.884124 13 | H 0.038876 1.281582 0.884111 14 | H -2.323822 1.550451 0.000000 15 | H -3.565344 -0.560946 -0.000007 16 | H -1.969201 -1.507986 -0.000015 17 | -------------------------------------------------------------------------------- /tutorial/orca/claisen_gs_freq.inp: -------------------------------------------------------------------------------- 1 | !B3LYP 6-31G* RIJCOSX def2/J Freq 2 | !pal4 3 | *xyzfile 0 1 claisen_gs.xyz 4 | -------------------------------------------------------------------------------- /tutorial/orca/claisen_ts.xyz: -------------------------------------------------------------------------------- 1 | 14 2 | claisen_ts 3 | C -1.474032 0.786774 -0.283331 4 | C -1.296078 -0.476777 0.256320 5 | O -0.512902 -1.359242 -0.257853 6 | C 1.291759 -0.901845 0.187721 7 | C 1.379160 0.410519 -0.300035 8 | C 0.673744 1.429519 0.315326 9 | H -2.153042 1.487010 0.198833 10 | H -1.239230 0.973314 -1.325004 11 | H -1.694692 -0.663780 1.269444 12 | H 1.774509 -1.712494 -0.347055 13 | H 1.175597 -1.072994 1.253923 14 | H 1.706592 0.552626 -1.328300 15 | H 0.639070 2.423886 -0.122641 16 | H 0.448795 1.383584 1.376553 17 | -------------------------------------------------------------------------------- /tutorial/orca/claisen_ts_freq.inp: -------------------------------------------------------------------------------- 1 | !B3LYP 6-31G* RIJCOSX def2/J Freq 2 | !pal4 3 | *xyzfile 0 1 claisen_ts.xyz 4 | -------------------------------------------------------------------------------- /tutorial/pyquiver/claisen_demo.config: -------------------------------------------------------------------------------- 1 | # This file will control the behavior of PyQuiver. 2 | # Blank lines and lines starting with # will be ignored. 3 | 4 | # scaling factor for frequencies 5 | # frequencies will be multiplied by this value 6 | scaling 0.9614 7 | 8 | ### deprecated: this keyword will now be ignored ### 9 | #frequency_threshold 50 10 | 11 | # imaginaries less than this value in i*cm-1 will be ignored for the transition mode 12 | imag_threshold 50 13 | 14 | # temperature in K 15 | temperature 393 16 | 17 | # specifies the masses used for the light isotopomer 18 | # specify the name of an isotopomer or 19 | # use "default" to use the default masses in weights.dat 20 | mass_override_isotopologue default 21 | 22 | # all KIEs will be divided by the KIE at this position 23 | # specify the name of an isotopomer or 24 | # use "default" or "none" to skip this step 25 | reference_isotopomer C5 26 | # Uncomment to calculate absolute KIEs only. 27 | #reference_isotopomer none 28 | 29 | # define the isotopomers here 30 | # 31 | # isotopomer name, atom number in ground state, atom number in transition state, valid replacement atomic weight (must be specified in weights.dat) 32 | # 33 | # for example, 'isotopomer abc 2 4 C13' replaces atom 2 in the gs and atom 4 in the ts with carbon-13 34 | # and calls the resulting KIE "abc" 35 | # 36 | # add additional isotopomer lines with the same name to give multiple replacements within a single isotopomer 37 | 38 | # the name reference was selected above as the reference isotopomer 39 | 40 | isotopomer C1 1 1 13C 41 | isotopomer C2 2 2 13C 42 | isotopomer O3 3 3 17O 43 | isotopomer C4 4 4 13C 44 | 45 | # this is identical to the reference isotopomer and therefore ought to produce a value of 1.00 (which it does) 46 | isotopomer C5 5 5 13C 47 | isotopomer C6 6 6 13C 48 | 49 | # example of doing a multiple replacement 50 | isotopomer H/D 7 7 2D 51 | isotopomer H/D 8 8 2D 52 | -------------------------------------------------------------------------------- /tutorial/pyquiver/claisen_gs.qin: -------------------------------------------------------------------------------- 1 | 14 2 | 0,6,2.529357,0.36133,-2e-05 3 | 1,6,1.650985,-0.646172,-2e-06 4 | 2,8,0.294333,-0.586301,2.9e-05 5 | 3,6,-0.296838,0.704706,2.1e-05 6 | 4,6,-1.792958,0.591589,-9e-06 7 | 5,6,-2.486162,-0.545043,-1.7e-05 8 | 6,1,3.588621,0.131739,-4.5e-05 9 | 7,1,2.248176,1.40772,-1.3e-05 10 | 8,1,1.967584,-1.685756,-9e-06 11 | 9,1,0.043549,1.268971,-0.883806 12 | 10,1,0.043514,1.268967,0.883863 13 | 11,1,-2.309465,1.551442,-2e-05 14 | 12,1,-3.572272,-0.540878,-3.7e-05 15 | 13,1,-1.990668,-1.510251,-4e-06 16 | 0.74230619,0.09529476,0.77708958,-1.491e-05,-1.33e-06,0.08257369,-0.28456899,-0.20271375,5e-06,0.70304478,-0.20577085,-0.38135502,4.32e-06,0.11298793,0.84228138,5.12e-06,4.1e-06,-0.04827938,-1.206e-05,-2.97e-06,0.13540834,-0.05484909,-0.00871881,1.52e-06,-0.3155381,-0.01124513,6.64e-06,0.52342495,-0.02617547,0.01424471,5.9e-07,0.02363705,-0.11163132,-1.3e-07,-0.08433554,0.37527311,1.21e-06,3.9e-07,0.01172492,5.8e-06,8.3e-07,-0.04564858,-8.38e-06,-1.89e-06,0.07882105,0.00266724,0.00186237,-6e-08,-0.03710831,0.04392886,7e-08,-0.09730855,0.01566473,7.7e-07,0.53316703,-0.01304485,0.00321988,3.5e-07,0.02691053,-0.00987549,-4e-07,0.04525067,-0.20216681,3.2e-07,0.03590686,0.50063556,-5e-08,-1.4e-07,-0.00158362,1.5e-07,-9.7e-07,-0.00301636,9e-08,1.76e-06,-0.0682566,-1.73e-06,1.42e-06,0.55973916,0.00039868,0.00058112,0.0,-0.00073999,0.00041141,-1.6e-07,-0.02289439,0.00963081,-3e-07,-0.21897493,-0.00833414,-2.88e-06,0.62781544,-0.00023844,-0.00027274,-3e-08,-0.00168271,-0.00740187,3.2e-07,0.04925904,-0.00388215,5.1e-07,-0.02156852,-0.09131716,-2.1e-07,0.11252764,0.84124942,7e-08,-4e-08,0.00046045,2e-07,0.0,5.151e-05,-4.5e-07,2e-07,0.00211877,-2.88e-06,-4e-08,-0.07618293,8.55e-06,2e-07,0.14246815,-0.00052318,2.505e-05,0.0,0.00022051,-0.00113669,-1e-08,0.0009982,-0.00202618,5e-08,-0.02075263,-0.03122247,-3.2e-07,-0.23614687,-0.20511284,-2.75e-06,0.71537499,-0.00010622,-0.00015564,0.0,0.00104342,-0.000161,-2e-08,-0.00124758,-0.00086342,0.0,-0.01042053,0.00346079,-1.7e-07,-0.19997456,-0.45401901,-2.17e-06,0.09711983,0.81360047,-1e-08,0.0,6.907e-05,0.0,-2e-08,-0.00011752,1e-08,0.0,0.00036836,-5.2e-07,-6.7e-07,0.00593798,-2.69e-06,-2.15e-06,-0.04684332,1.092e-05,-1.01e-06,0.10434748,-0.33586344,0.06123752,7.32e-06,-0.0177423,0.00877674,3.6e-07,5.354e-05,-0.00285066,2.5e-07,0.00085008,-0.00110173,0.0,6.057e-05,8.04e-06,0.0,-0.00010546,-3.246e-05,0.0,0.35503734,0.0670774,-0.07013313,-1.62e-06,-0.02547168,0.00844064,6e-07,-0.00414368,-0.00315285,7e-08,0.00127663,-0.00206353,6e-08,-0.00021381,0.00055069,-3e-08,0.00016753,-4.436e-05,0.0,-0.06565159,0.06706971,7.45e-06,-1.44e-06,-0.02340353,1.9e-07,-2.8e-07,-0.00380091,2.9e-07,1.8e-07,0.01188271,-8e-08,-3e-08,0.00095079,0.0,-4e-08,0.00024163,0.0,0.0,-6.68e-06,-7.88e-06,1.59e-06,0.02012529,-0.07812401,0.07685992,1.31e-06,0.01140799,-0.0215606,-2.1e-07,0.00082339,0.00246423,-1.4e-07,-0.001965,-0.00083855,-3e-08,-0.00018258,-0.00024748,0.0,8.054e-05,2.636e-05,0.0,-0.00305901,0.02700544,1e-07,0.07552879,0.07163049,-0.3275014,-1.92e-06,0.01012707,-0.01988579,-2.2e-07,0.00095827,0.00167463,-3e-08,0.00111958,-0.0007768,1e-07,-0.00025647,0.00098243,-4e-08,0.00036347,-7.571e-05,0.0,-0.00018694,-0.00200388,2e-08,-0.08116076,0.34835311,1.29e-06,-1.94e-06,-0.02703889,-8e-08,5e-07,0.00450606,-2.2e-07,-1e-07,-0.00565882,0.0,1e-08,-0.00097856,2e-08,4e-08,-0.0006078,0.0,0.0,-1.448e-05,8e-08,-6.5e-07,0.00020147,-1.22e-06,2.1e-06,0.01942911,0.00971839,-0.02025405,-1.1e-07,-0.07069332,0.06696124,7.4e-07,-0.02576705,0.03939997,3.4e-07,-0.00404762,-0.00305432,3.5e-07,-8.733e-05,0.00189164,2e-08,-2.39e-05,5.131e-05,0.0,0.00072463,2.805e-05,-1.4e-07,-0.00224613,-0.00323579,2.7e-07,0.09325234,0.00771224,-0.01799486,-1.8e-07,0.07297591,-0.32214518,-1.59e-06,0.00592869,0.00028651,-7e-08,-0.00113712,0.0035218,-3e-08,-0.0001076,-0.00026323,-2e-08,8.908e-05,5.222e-05,0.0,-0.00036096,0.00082157,0.0,-0.00285191,-0.00131517,8e-08,-0.08217533,0.33727977,-1.5e-07,2.9e-07,0.00616416,7.4e-07,-1.44e-06,-0.04139622,6.2e-07,-8.7e-07,0.00078558,1.2e-07,1.8e-07,0.0040008,0.0,-8e-08,0.00027034,0.0,0.0,-4.279e-05,-1.5e-07,1e-08,-0.00588242,2.6e-07,7e-08,0.0103025,-1.42e-06,1.8e-06,0.02599702,-0.00044948,-0.00206844,3.91e-05,0.00537277,0.00235403,-0.00161223,0.00079321,0.01364636,-0.01354133,-0.07495745,-0.03841715,0.06832437,-0.013104,-0.01926472,0.02629488,-0.00298613,0.00228375,1.225e-05,4.408e-05,-2.93e-05,0.00078551,-0.00116736,0.00031705,-0.0016661,-7.419e-05,-8.404e-05,0.000518,0.08175683,0.00148803,0.00143336,-0.00054752,-0.00812643,9.427e-05,0.00053116,0.003593,-0.03636867,0.02677514,-0.03344531,-0.10072535,0.08926957,-0.00546302,-0.000415,0.00280248,0.00045927,0.00118084,-4.407e-05,3.72e-05,0.00019982,-0.00013511,0.00015556,0.00025178,0.00042369,0.00031726,-0.0001069,-0.00055617,0.03748573,0.12448224,-0.0002016,0.00093363,-0.00025596,0.00201701,-0.00013508,0.00090199,-0.0053682,0.00422267,0.00674284,0.06238111,0.0929774,-0.21410871,0.0002197,-0.00143797,0.00398842,-0.00053256,-0.00089619,-6.789e-05,4.832e-05,-0.00050505,-0.00012731,-0.00017568,-0.00071185,-0.00014206,-0.00022894,4.15e-06,-0.00010531,-0.06791437,-0.10653895,0.22752767,-0.00044916,-0.00206838,-3.918e-05,0.00537273,0.00235396,0.00161186,0.00079284,0.01364655,0.01354182,-0.07495259,-0.03841339,-0.06831965,-0.01310306,-0.01926476,-0.0262957,-0.00298611,0.00228375,-1.236e-05,4.405e-05,-2.934e-05,-0.00078544,-0.00116689,0.00031703,0.00166596,-7.426e-05,-8.401e-05,-0.00051805,0.00442995,0.0037757,0.00902454,0.08175081,0.00148816,0.0014333,0.00054734,-0.0081263,9.419e-05,-0.0005309,0.00359407,-0.03636844,-0.02677524,-0.03344212,-0.10072435,-0.08927143,-0.00546298,-0.00041495,-0.00280279,0.00045927,0.00118083,4.411e-05,3.721e-05,0.0001998,0.00013511,0.00015562,0.00025172,-0.00042372,0.00031735,-0.00010691,0.0005562,0.00377528,0.00973468,0.01210429,0.03748143,0.12448124,0.00020158,-0.00093355,-0.00025593,-0.00201694,0.00013506,0.00090194,0.00536822,-0.00422226,0.00674366,-0.06237655,-0.09297915,-0.21411564,-0.00022038,0.00143714,0.00398727,0.00053243,0.00089629,-6.787e-05,-4.83e-05,0.00050501,-0.00012732,0.00017562,0.00071175,-0.0001421,0.00022892,-4.16e-06,-0.00010533,-0.00902331,-0.01210392,-0.02428917,0.06790939,0.10654077,0.22753497,-0.00038039,-4.482e-05,2e-08,0.00125914,0.00177727,-1.3e-07,-0.00851825,-0.00114813,-4e-08,-0.00839499,0.02821446,-2.3e-07,-0.12352292,0.11661093,-1.86e-06,0.01192872,-0.01752148,1.7e-07,-5.144e-05,-8.316e-05,1e-08,6.599e-05,-6.229e-05,-1e-08,-0.0006182,7.792e-05,2e-08,0.00067448,-0.00053471,0.0008102,0.00067446,-0.00053474,-0.00081018,0.12709882,0.00010638,2.236e-05,0.0,-0.00048416,0.00040017,2e-08,-0.00128067,0.00107713,-5e-08,-0.00238366,0.00238878,-5e-08,0.11493097,-0.26528732,2.51e-06,0.0149517,-0.02277016,3.5e-07,1.562e-05,-7.1e-06,0.0,-2.232e-05,-3.92e-05,0.0,-0.0001133,1.522e-05,0.0,0.00075202,0.00018983,2.752e-05,0.00075203,0.0001898,-2.75e-05,-0.12373657,0.28521407,0.0,1e-08,-7.364e-05,-2e-08,6e-08,0.00059346,-1.3e-07,-1.1e-07,0.00103748,-1.6e-07,6e-07,0.00133027,-1.83e-06,2.41e-06,-0.03794671,8e-08,-1.5e-07,0.00544732,0.0,0.0,-6.202e-05,0.0,0.0,8.032e-05,-2e-08,0.0,-4.846e-05,-0.00081569,-0.00014428,0.0001267,0.00081573,0.00014424,0.00012669,2.09e-06,-2.76e-06,0.02533882,-0.00025803,-9.357e-05,0.0,0.00068142,-0.00012812,-2e-08,-0.00025311,-0.00026174,1e-08,0.00145902,-0.00162686,-2e-07,-0.01311968,0.00218911,-3.2e-07,-0.3437295,0.00465586,-5.71e-06,-3.337e-05,5.36e-06,0.0,1.529e-05,5.157e-05,0.0,-3.716e-05,3.515e-05,0.0,-0.00042981,-0.00011033,-3.565e-05,-0.0004299,-0.00011033,3.564e-05,0.00122517,-0.00022035,1.7e-07,0.36372586,7.296e-05,-4e-05,0.0,-0.0001804,0.00028551,0.0,0.00118959,0.00076134,2e-08,-7.176e-05,-0.00475081,7e-08,-0.03132155,0.00236823,-6e-07,0.00921279,-0.05820429,3.1e-07,8.37e-06,5.525e-05,0.0,1.853e-05,8.064e-05,0.0,-8.354e-05,8.95e-06,0.0,-0.00069947,-2.8e-05,1.483e-05,-0.00069945,-2.8e-05,-1.486e-05,9.282e-05,0.00138512,-4e-08,-0.00242482,0.05429418,-2e-08,0.0,-8.205e-05,0.0,-1e-08,0.00016533,0.0,0.0,-2.56e-06,-2.4e-07,1e-08,0.01391137,-1.4e-07,6e-08,0.00251666,-5.76e-06,1.8e-07,-0.03309177,0.0,0.0,-6.259e-05,0.0,0.0,8.407e-05,0.0,0.0,-2.036e-05,-0.00262734,-0.00020282,-0.00032278,0.00262737,0.00020282,-0.00032267,1.7e-07,-3e-08,-0.00817451,6.26e-06,-1.8e-07,0.02358444,0.00037526,0.00010109,-1e-08,-0.00096832,0.00028994,4e-08,-0.00175761,-0.00129198,-8e-08,0.00031868,-0.00022906,1.3e-07,0.01360107,-0.01510692,2.4e-07,-0.1213492,0.12183855,-2.05e-06,4.073e-05,6.213e-05,0.0,-1.101e-05,1.772e-05,0.0,-2.62e-05,-1.802e-05,0.0,9.711e-05,0.00036806,-4.388e-05,9.715e-05,0.00036808,4.388e-05,-0.00144058,-0.00326769,-2.3e-07,-0.0088162,0.02488592,-3e-07,0.11983912,0.00046543,9.59e-06,-1e-08,-0.0008965,0.00085953,3e-08,0.00119809,0.00111624,0.0,0.00270999,-0.0008265,0.0,0.01305218,-0.02187733,3.2e-07,0.11665018,-0.28318156,3.15e-06,6.365e-05,6.736e-05,0.0,-4.04e-06,3.65e-06,0.0,-5.047e-05,-5.379e-05,0.0,-5.111e-05,7.709e-05,-5.939e-05,-5.113e-05,7.709e-05,5.939e-05,-0.0031075,-0.00277872,0.0,-0.00196094,0.00381188,-5e-08,-0.12801783,0.30269548,1e-08,0.0,-1.931e-05,0.0,2e-08,-0.00026966,-3e-08,-3e-08,-0.0006588,1.4e-07,0.0,-0.00762795,1e-07,-1.9e-07,0.00547755,-2.06e-06,3.22e-06,-0.0359179,0.0,0.0,7.089e-05,0.0,0.0,-2.082e-05,0.0,0.0,8.049e-05,0.00122627,0.00047081,0.00013155,-0.00122628,-0.0004708,0.0001315,-2.3e-07,-2e-08,0.01222428,-1.9e-07,4.5e-07,0.00181741,2.26e-06,-3.44e-06,0.02458077, -------------------------------------------------------------------------------- /tutorial/pyquiver/claisen_ts.qin: -------------------------------------------------------------------------------- 1 | 14 2 | 0,6,-1.466309,0.784167,-0.289984 3 | 1,6,-1.291057,-0.472056,0.261839 4 | 2,8,-0.511026,-1.358456,-0.252221 5 | 3,6,1.28557,-0.900284,0.182034 6 | 4,6,1.374702,0.412704,-0.301796 7 | 5,6,0.668678,1.425295,0.322236 8 | 6,1,-2.145897,1.492397,0.178794 9 | 7,1,-1.2254,0.953689,-1.332994 10 | 8,1,-1.676663,-0.646152,1.281812 11 | 9,1,1.766395,-1.711905,-0.35332 12 | 10,1,1.17354,-1.069495,1.248816 13 | 11,1,1.692496,0.557034,-1.332675 14 | 12,1,0.629026,2.424945,-0.101698 15 | 13,1,0.445204,1.368175,1.383056 16 | 0.25705678,-0.23007491,0.59966183,-0.16192179,-0.11022057,0.60984932,-0.07656178,0.02905976,0.00818469,0.38536877,0.08489313,-0.28912002,0.12578789,-0.1718946,0.6209069,0.00461265,0.14824441,-0.16908357,-0.16472155,-0.098638,0.57828378,0.02129914,0.07754609,-0.00422419,-0.17015337,0.09617573,0.09482815,0.16536912,0.02027087,-0.07166135,-0.01297006,0.13915825,-0.24134984,-0.10996954,-0.16849674,0.34267992,-0.00963912,-0.01499756,0.02378521,0.09050501,-0.0933176,-0.158732,-0.10268931,0.14672479,0.16345721,-0.0221225,-0.04109546,0.00730978,-0.06439803,0.08184619,0.01326556,0.08524428,-0.02282316,-0.00916157,0.14670341,-0.019531,-0.03045864,0.00238464,-0.01838502,0.04823174,0.0141267,0.05750403,-0.04121265,-0.01711401,-0.16842249,0.65496507,0.00229953,0.00232178,-0.00079117,0.00850703,-0.00026777,0.0016706,-0.00808117,-0.00825758,-0.02369709,-0.13633424,-0.0363327,0.62891745,-0.02524692,-0.0156395,0.00336173,-0.02043449,0.01182777,0.0028556,-0.0005058,-0.01429652,-0.00395659,-0.06552562,-0.01453662,0.02796684,0.2762643,0.03739198,0.0418173,-0.00384168,0.02748232,-0.06676882,-0.00686828,-0.09076673,0.01202529,0.00843405,0.05269007,-0.29691773,0.09441583,-0.10216991,0.65309583,0.00036582,0.00468424,0.00112247,0.00128574,-0.00203214,0.0024345,-0.00334434,-0.00538533,0.00082345,0.02372186,0.10483561,-0.14394418,-0.20449141,-0.0348432,0.61884646,0.04689904,0.05212237,-0.00210577,0.01564304,-0.07740371,-0.00538065,-0.06035684,0.02495034,0.01196147,0.04281365,0.04678661,-0.00662745,-0.11538547,0.07797382,0.09314637,0.16151212,-0.00444531,-0.01990304,0.00072068,-0.00603052,0.00892431,-0.00189121,0.01817104,-0.00647995,-0.00114526,0.00114205,-0.05078609,-0.01228219,0.12652134,-0.25703366,-0.10569097,-0.14758652,0.67816421,0.00223839,-0.00281183,-0.01641099,-0.00282684,0.00593585,0.00094566,0.01077087,0.00022989,0.00040712,-0.01405324,-0.00364663,0.01354625,0.08954691,-0.09221556,-0.17696918,-0.16350257,-0.01222856,0.62413085,-0.15100128,0.13007792,0.08663379,-0.00116816,-0.00440801,0.00083449,0.00528337,0.00486085,0.0022378,0.00016892,-0.00115465,0.00083071,-0.00181961,0.00021058,-0.00116851,-0.00202633,0.00298733,0.00269021,0.15355173,0.12099792,-0.16997833,-0.07223236,0.01928998,-0.01059982,-0.02007791,0.00326028,-0.00479126,0.00551072,-0.0010468,-0.00047277,6.105e-05,-0.0003752,0.00016689,-0.00018777,-0.00143305,0.00167075,0.00106179,-0.13471698,0.17852932,0.08629335,-0.07467755,-0.10981185,-0.01086322,0.00584247,0.00652383,0.00164687,0.00488845,0.002458,0.00060053,9.561e-05,0.00025462,-0.00050935,-0.00039245,-0.00049127,-0.00180365,0.00162926,0.00130688,-0.09141951,0.08254975,0.10637724,-0.04747853,0.00628881,0.0696131,0.00474543,-0.0075854,-0.0004927,-0.00789211,0.00082985,-0.00098084,0.00357308,0.00356128,6.688e-05,0.00130598,-0.00541154,-0.00230306,-0.01050806,0.00127975,-0.00754824,-0.00188462,-0.00481932,0.0165796,0.05389317,0.00322667,-0.05563386,0.04214745,-0.00661184,-0.00325946,0.031485,-0.00592968,0.00343364,-0.00387208,0.0024284,0.00219809,0.0002179,0.0022017,-0.00285086,0.00030161,-0.00436972,0.00074915,-0.00303478,0.00275057,0.00523044,-0.01900306,0.00383267,0.05423344,0.07389474,0.0509024,-0.32758926,0.00576226,-0.00424363,-0.00971995,-0.0037907,-0.00052339,0.0017222,0.00143939,0.00177601,0.00015288,0.00133838,-0.00197354,0.00086066,-0.00261522,0.00027987,-0.00216576,0.0007238,0.00301855,-0.00669474,-0.07753628,-0.05160817,0.3415908,0.00658121,0.00119538,-0.00240111,-0.0724735,-0.01301009,0.06421691,-0.0127614,0.01010101,0.02925247,0.0037432,0.00196,0.0014788,0.00159966,-0.00052063,0.00071516,0.00089302,-7.224e-05,0.00080043,-0.00183085,-0.0020625,-0.00100095,0.00518907,0.00318967,0.00144499,0.06965618,-0.00807384,-0.00182509,0.02945502,-0.01145381,-0.06744401,0.046502,0.01739224,0.00397043,-0.0293159,-0.00145912,-0.00020157,-0.00066607,0.00089481,0.00133209,0.00090772,0.00165776,0.00019739,-0.00019257,-0.00070032,9.69e-06,-0.00115537,0.00205992,-0.00389638,0.00299364,-0.00037638,0.06780926,0.00568738,0.00089518,-0.01135277,0.06644581,0.05886039,-0.25094851,0.02066737,-0.0153437,-0.0128089,-0.00227065,-0.00055835,-0.00063618,0.00034204,0.000701,0.00110183,0.00061692,-1.899e-05,-0.0005511,-0.00155369,-4.86e-05,-0.00012129,0.00232401,0.00354912,0.00148128,-0.09345172,-0.04836724,0.27302845,0.00024898,0.00055293,0.00034231,-0.00239271,-0.00183138,-0.0026484,-0.01157912,0.00843832,0.00594383,-0.09327079,0.10445925,0.06989208,0.00743513,-0.00576022,-0.00226478,0.00326622,-0.00018931,0.00250599,0.0001149,3.767e-05,3.949e-05,-0.00018824,-0.00010463,-9.136e-05,-4.408e-05,-5.118e-05,0.00012618,0.09765155,-0.00029655,0.00033813,2.659e-05,-0.00051503,-0.00018627,-0.0001818,-0.00346204,0.00286979,0.00174091,0.09965311,-0.21847246,-0.10760503,0.01505771,-0.01541954,-0.01694176,-0.00055631,-0.00478267,0.00406401,5.29e-06,1.124e-05,1.432e-05,-1.497e-05,-4.472e-05,-5.732e-05,-4.398e-05,-2.305e-05,3.934e-05,-0.10624683,0.22933397,0.0002024,0.00050212,0.00019267,-0.00096546,-0.00090771,-0.00116292,-0.00518075,0.0037062,0.00254186,0.07360785,-0.10897079,-0.13200429,-0.00590767,0.00923864,0.00854369,0.00085357,0.00207575,0.00016092,7.524e-05,6.216e-05,1.34e-05,-0.00013808,-5.85e-05,-5.272e-05,-0.0001546,7.743e-05,0.00024672,-0.07423093,0.1161847,0.13039443,0.00122577,0.00393338,0.00014749,0.00469087,-0.00779704,-0.00500254,-0.02108556,0.00106486,-0.0125555,-0.03360648,0.01079156,0.03630763,0.0036914,-0.00900075,0.0052679,-0.00675157,0.00138412,-0.00066029,0.00015616,0.00020052,-1.98e-05,-0.00046775,-0.00030463,-0.00013718,-0.00022987,0.00029563,0.00081717,0.00210573,-0.0031461,0.01360827,0.04405622,0.0003979,0.00100634,-4.251e-05,0.0019506,-0.00120303,0.00034037,-0.00364326,0.00059686,-0.0024793,0.00250877,-0.0621684,0.04013015,-0.00381517,-0.00238015,0.03078061,-0.00288453,0.00181223,0.00074079,2.916e-05,1.199e-05,-1.94e-05,-0.00014872,-4.672e-05,-8.856e-05,-0.00025765,6.038e-05,4.9e-06,0.00166778,0.00617777,-0.02215912,0.00239574,0.06083531,0.00042782,0.00080501,0.0001261,0.00048668,-0.00097771,5.844e-05,-0.00389694,-0.00091574,-3.898e-05,0.03968183,0.04518383,-0.33477178,0.00262309,-0.00120788,-0.00737869,-0.00093371,-0.00097516,0.00102058,6.917e-05,2.13e-06,1.597e-05,-4.39e-05,-9.627e-05,-3.424e-05,-0.00059414,1.136e-05,3.898e-05,0.00140196,0.00382982,-0.01030897,-0.03957618,-0.04807654,0.34943577,0.00077369,0.00056209,0.00063772,0.00049402,-2.193e-05,0.00059764,0.00031646,0.00034394,2.122e-05,0.00205467,-5.404e-05,-0.00033556,-0.06064652,-0.00592155,0.07727645,-0.0028649,-0.00165904,0.01588696,5.758e-05,3.53e-06,-0.00016277,-0.00022206,-7.998e-05,-0.00037547,-0.00022155,-0.00012861,-0.00019944,-0.00299664,-0.00104951,-0.00130054,0.00521456,0.00160847,0.00081944,0.05701135,2.658e-05,0.0004126,0.00020567,0.00039168,-0.00039736,0.00012799,0.00026596,0.00066194,-0.00044387,-0.00772792,-0.0029473,0.03198312,-0.0060762,-0.061172,0.03457968,0.00662207,0.00493471,-0.02586671,-0.00013703,-5.218e-05,-4.007e-05,0.00012187,0.00010212,4.8e-07,-3.889e-05,-8.633e-05,-4.114e-05,-0.00049499,0.00104521,-0.00064368,-0.00015311,-0.00465974,0.00162053,0.00415275,0.06389217,0.00013262,0.00033695,-0.00014934,-0.00010869,-0.00035252,0.00024243,-0.00026779,0.00011591,0.00052788,0.0026576,0.00043021,-0.00926896,0.08601391,0.03684867,-0.30325001,0.00264641,0.00273113,-0.01220891,-3.971e-05,-3.596e-05,8.451e-05,0.00022483,-3.397e-05,0.00013384,-7.354e-05,-8.235e-05,-9.34e-06,-0.00141499,-0.00027911,0.00054955,0.00196597,0.00215725,0.00129165,-0.09360758,-0.04002967,0.32038098,-0.00461662,-0.00771261,0.00188714,-0.00012599,0.00293809,-0.00050322,0.00226644,-0.00166537,-0.00052402,0.00129945,0.00282751,0.00486925,0.00419668,0.01877601,-0.00857295,-0.03413825,0.00801225,0.00467264,0.00012821,0.00028546,0.00031149,0.00080131,5.636e-05,4.136e-05,-0.00022648,0.00017357,-2.344e-05,-0.00034535,0.00064806,-0.00038405,-9.386e-05,-0.00022219,0.00033123,-0.00364267,-0.00172427,-0.00176005,0.03292826,-0.00152566,-0.00167141,0.00092125,4.817e-05,0.00032445,-0.00089986,0.00046,0.00026597,0.00021491,0.00412663,-0.00188576,-0.00239814,-0.00221187,-0.01391488,0.011324,0.01758414,-0.29819113,0.10661694,-0.00016784,9.595e-05,7.068e-05,0.00019134,-4.9e-05,-0.00030713,-0.00013169,6.375e-05,-2.185e-05,-0.00054295,-0.0010298,0.00051602,-2.582e-05,0.00015588,0.00012884,-0.00122929,0.00066317,-0.00057371,-0.01712954,0.31329577,-0.00275864,-0.00441683,0.00094647,-0.00088614,0.00215674,-0.00085342,0.00211019,-0.00108179,-0.00035678,0.00188836,-0.00217874,-0.00018263,-0.00086018,-0.01321627,0.00865825,0.0056429,0.10740635,-0.10191718,0.00038441,0.00023753,5.293e-05,0.00027348,-8.114e-05,0.00018121,-7.8e-06,6.662e-05,0.0002831,1.868e-05,-0.00084807,0.00049493,0.00011293,-0.00043825,0.00040666,-0.0019111,-0.00014186,0.00045137,-0.00309753,-0.11464968,0.09936042,-0.00705696,-0.00681625,-0.00746487,-0.00323413,0.00627125,-0.00246194,0.00455539,-0.00273648,-0.00041486,-0.00667724,-0.00580641,-0.0008403,-0.00492871,0.00502655,0.02036576,-0.03899569,0.00048506,0.05947876,0.00026998,0.00037849,0.00030793,-0.00086668,-0.00028556,-9.871e-05,0.00012538,-0.00023067,0.00047208,-5.57e-06,-3.284e-05,-8.524e-05,0.00109438,0.00041311,-0.00079636,0.00467201,0.00477149,0.00363103,0.00156888,0.00055439,-0.00090958,0.04947896,-0.00325778,-0.00298447,-0.00234201,-0.00248992,0.00194122,-0.00229988,0.00152308,-0.0010088,6.02e-05,-0.00182025,0.00012845,-0.00132036,0.00261767,0.00802024,-0.0223323,0.00653674,-0.05927622,0.02134737,0.00036313,0.00016809,0.00019735,-0.00018554,-0.00016587,-0.00016922,6.798e-05,3.345e-05,0.00034995,6.554e-05,0.00018241,0.00037678,0.00036162,-0.00019871,0.00066777,0.00347317,-0.00239701,-0.00123281,-0.00526332,0.00187704,0.02718539,-0.00199212,0.05368018,-0.00183515,-0.00156775,-0.0008333,-0.00080532,0.00215372,0.00034113,0.00145176,-0.00121813,-8.919e-05,-0.00235304,-3.136e-05,0.00075449,0.00167669,0.00492065,-0.01035798,0.06810138,0.01938931,-0.33129514,-0.00029821,7.893e-05,3.176e-05,-3.879e-05,8.689e-05,0.00013381,-0.00022489,-0.00023429,0.00024772,0.00037993,1.34e-05,0.00039073,-0.00027587,-0.0008504,0.00013852,0.00265303,-0.00131047,0.00122436,0.00275216,-0.00094227,-0.00752532,-0.07118368,-0.02048823,0.34683841, --------------------------------------------------------------------------------