From 0583445caadd4046404210442562b1fd35a00d8f Mon Sep 17 00:00:00 2001 From: fabothch <49951809+fabothch@users.noreply.github.com> Date: Thu, 14 Jan 2021 11:06:18 +0100 Subject: [PATCH] CENSO version 1.0.0 --- .gitignore | 19 + README.rst | 177 ++ censo-runner.py | 10 + censo_assets/censo_solvents.json | 218 ++ censo_qm/__init__.py | 0 censo_qm/__main__.py | 4 + censo_qm/adf_job.py | 3 + censo_qm/censo.py | 234 ++ censo_qm/cfg.py | 270 +++ censo_qm/cheapscreening.py | 534 +++++ censo_qm/datastructure.py | 544 +++++ censo_qm/ensembledata.py | 48 + censo_qm/inputhandling.py | 3440 ++++++++++++++++++++++++++++++ censo_qm/nmrproperties.py | 765 +++++++ censo_qm/opticalrotation.py | 585 +++++ censo_qm/optimization.py | 1945 +++++++++++++++++ censo_qm/orca_job.py | 881 ++++++++ censo_qm/parallel.py | 131 ++ censo_qm/prescreening.py | 1076 ++++++++++ censo_qm/qm_job.py | 611 ++++++ censo_qm/refinement.py | 903 ++++++++ censo_qm/setupcenso.py | 637 ++++++ censo_qm/tm_job.py | 1458 +++++++++++++ censo_qm/utilities.py | 3095 +++++++++++++++++++++++++++ docs/documentation.rst | 5 + docs/example.rst | 0 docs/src/solvents.png | Bin 0 -> 312571 bytes docs/src/solvents.svg | 580 +++++ setup.cfg | 28 + setup.py | 3 + 30 files changed, 18204 insertions(+) create mode 100644 .gitignore create mode 100644 README.rst create mode 100644 censo-runner.py create mode 100644 censo_assets/censo_solvents.json create mode 100755 censo_qm/__init__.py create mode 100644 censo_qm/__main__.py create mode 100644 censo_qm/adf_job.py create mode 100644 censo_qm/censo.py create mode 100644 censo_qm/cfg.py create mode 100644 censo_qm/cheapscreening.py create mode 100755 censo_qm/datastructure.py create mode 100644 censo_qm/ensembledata.py create mode 100755 censo_qm/inputhandling.py create mode 100644 censo_qm/nmrproperties.py create mode 100644 censo_qm/opticalrotation.py create mode 100755 censo_qm/optimization.py create mode 100644 censo_qm/orca_job.py create mode 100644 censo_qm/parallel.py create mode 100755 censo_qm/prescreening.py create mode 100644 censo_qm/qm_job.py create mode 100755 censo_qm/refinement.py create mode 100755 censo_qm/setupcenso.py create mode 100644 censo_qm/tm_job.py create mode 100755 censo_qm/utilities.py create mode 100644 docs/documentation.rst create mode 100644 docs/example.rst create mode 100644 docs/src/solvents.png create mode 100644 docs/src/solvents.svg create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..44b8c21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Created by https://www.toptal.com/developers/gitignore/api/vscode +# Edit at https://www.toptal.com/developers/gitignore?templates=vscode + +### vscode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# End of https://www.toptal.com/developers/gitignore/api/vscode + +###pycache## +__pycache__/ + + +# packaging +*.egg-info/ diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..74aede3 --- /dev/null +++ b/README.rst @@ -0,0 +1,177 @@ +CENSO - Commandline ENergetic SOrting of Conformer Rotamer Ensembles +==================================================================== + +This repository hosts the `CENSO` code for the refinement of Conformer Rotamer +Ensembles (CRE) as obtained from `CREST`. + + +Installation +------------ + +There are several options possible. The easiest is to use the packaged censo programs +(by use of Pyinstaller) which can be found at the release section. The packaged +censo is linked against GLIBC version 2.19 and will work for GLIBC version 2.19 and above. + +Other options to use censo are shown below: + + +Download the git repository and run: + +.. code:: + + $ pip install --upgrade pip + $ pip install --editable . + $ censo arg1 arg2 + + +Flexible Invocation +------------------- + +1) Treating the censo directory as a package and as the main script:: + + $ python3 -m censo arg1 arg2 + +2) Using the censo-runner.py wrapper:: + + $ ./censo-runner.py arg1 arg2 + +3) After installation with pip:: + + $ censo arg1 arg2 + + + +Getting started: +---------------- + +Create the remote configuration file .censorc where the user can adjust default +settings and provide paths to the external programs e.g. `xtb`, `crest`, `orca` ... + +.. code:: + + $ censo -newconfig + $ cp censorc-new /home/$USER/.censorc + # edit .censorc + vi /home/$USER/.ensorc + + +First explainations on the commandline arguments can be printed by: + +.. code:: + + $ censo --help + +The molecule numbering from the input structure ensemble is kept throughout the +entire program. There are several program parts which can be used to filter a structure +ensemble: + +0) Cheap prescreening (part0): Very fast DFT energies in order to improve upon the energy + description of the SQM method used to generate the input structure ensemble. + The (free) energies are evaluated on the input geometries (DFT unoptimized). + +1) Prescreening (part1): Improved DFT energies and accurate solvation energies (if needed). + The free energies are evaluated on the input geometries (DFT unoptimized). + +2) Optimization (part2): efficient structure ensemble optimization and + free energy calculation on DFT optimized geometries. + +3) Refinement (part3): Optional free energy refinement (on DFT optimized geometries). + +4) NMR properties (part4): Optional calculation of shielding and coupling constants on + populated conformers. + +5) Optical Rotation (part5): Optional calculation of optical rotatory dispersion + for the populated ensemble. + + +Usage: + +.. code:: + + # check if settings combinations match: + $ censo -inp structure_ensemble.xyz -part2 on -solvent h2o --checkinput + # start the calculation: + $ censo -inp structure_ensemble.xyz -part2 on -solvent h2o > censo.out 2> error.censo & + +Requirements: +------------- + +* newest xtb (currently: https://github.com/grimme-lab/xtb/releases/tag/bleed ) +* newest cefine https://github.com/grimme-lab/cefine/releases +* ORCA > version 4.1 + + +Furter information (will be ordered later on): + +* the file .censorc can be used in the current working directory and will be preferred to + the global configuration file in ~/.censorc +* a folder ~/.censo_assets/ will be created upon usage of censo +* ORCA has not been used extensively so please be careful, test calculations + and report possible "bad" settings +* To be efficient COSMO-RS calculations are not performed with BP86 but whith the functionals + for energy evaluation. + + + + +License +------- + +LGPL3 + + +Available solvation models: +--------------------------- + +Solvation models available for implicit effect on properties e.g. the +geometry (SM). And "additive" solvation models which return a solvation contribution +to free energy (Gibbs energy) of the choosen geometry (SMGSOLV). + +.. csv-table:: + :header: "programs", "solvation models", "comment" + + "Turbomole","COSMO", "(SM)" + "", "DCOSMO-RS","(SM)" + "COSMO-RS","COSMO-RS","(SMGSOLV) (only solvent model for evaluation at different temperatures)" + "ORCA", "CPCM", "(SM)" + "","SMD","(SM)" + "","SMD_GSOLV", "(SMGSOLV)" + "xTB","GBSA_Gsolv","(SMGSOLV)" + "","ALPB_Gsolv","(SMGSOLV)" + + + +For Turbomole user: +------------------- + +The amount of *ricore* for each calculation can be set in your `.cefinerc`. The same +holds for *maxcor* and/or *rpacor*. + +.. code:: + + $ echo "ricore 4000" > .cefinerc + $ echo "maxcor 4000" >> .cefinerc + $ echo "rpacor 4000" >> .cefinerc + + +Solvents: +--------- + +CENSO uses several QM-packages and not all solvents are available for all solvation +models throughout the QM-packages. +For this reason a user editable file is created in the folder: + + $ ~/.censo_assets/censo_solvents.json + +which contains a dictionary of all available solvent models and solvents. +If a solvent is not available with a certain solvent model, the user can then choose +a replacement solvent. E.g. if CCl4 is not available choose CHCl3. + +.. figure:: docs/src/solvents.png + :scale: 25% + :align: center + :alt: censo_solvents.json + + +The solvent file is directly used in `CENSO` and typos will cause calculations to crash! +Adding a new solvent is as easy as adding a new dictionary to the file. diff --git a/censo-runner.py b/censo-runner.py new file mode 100644 index 0000000..3a5a4d9 --- /dev/null +++ b/censo-runner.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 + +""" +Convenience wrapper for running censo directly from the source tree. +""" +import sys +from censo_qm.censo import main + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/censo_assets/censo_solvents.json b/censo_assets/censo_solvents.json new file mode 100644 index 0000000..0df1187 --- /dev/null +++ b/censo_assets/censo_solvents.json @@ -0,0 +1,218 @@ +{ + "acetone":{ + "cosmors": ["propanone_c0", "propanone_c0"], + "dcosmors": ["propanone", "propanone"], + "xtb": ["acetone", "acetone"], + "cpcm": ["acetone", "acetone"], + "smd": ["ACETONE", "ACETONE"], + "DC": 20.7 + }, + "chcl3":{ + "cosmors": ["chcl3_c0", "chcl3_c0"], + "dcosmors": ["chcl3", "chcl3"], + "xtb": ["chcl3", "chcl3"], + "cpcm": ["chloroform","chloroform"], + "smd": ["CHLOROFORM", "CHLOROFORM"], + "DC": 4.8 + }, + "acetonitrile":{ + "cosmors": ["acetonitrile_c0", "acetonitrile_c0"], + "dcosmors": ["acetonitrile", "acetonitrile"], + "xtb": ["acetonitrile", "acetonitrile"], + "cpcm": ["acetonitrile", "acetonitrile"], + "smd": ["ACETONITRILE", "ACETONITRILE"], + "DC": 36.6 + }, + "ch2cl2":{ + "cosmors": ["ch2cl2_c0", "ch2cl2_c0"], + "dcosmors": [null, "chcl3"], + "xtb": ["ch2cl2", "ch2cl2"], + "cpcm": ["CH2Cl2", "CH2Cl2"], + "smd": ["DICHLOROMETHANE", "DICHLOROMETHANE"], + "DC": 9.1 + }, + "dmso":{ + "cosmors": ["dimethylsulfoxide_c0", "dimethylsulfoxide_c0"], + "dcosmors": ["dimethylsulfoxide", "dimethylsulfoxide"], + "xtb": ["dmso", "dmso"], + "cpcm": ["DMSO", "DMSO"], + "smd": ["DIMETHYLSULFOXIDE", "DIMETHYLSULFOXIDE"], + "DC": 47.2 + }, + "h2o":{ + "cosmors": ["h2o_c0", "h2o_c0"], + "dcosmors": ["h2o", "h2o"], + "xtb": ["h2o", "h2o"], + "cpcm": ["Water", "Water"], + "smd": ["WATER", "WATER"], + "DC": 80.1 + }, + "methanol":{ + "cosmors": ["methanol_c0", "methanol_c0"], + "dcosmors": ["methanol", "methanol"], + "xtb": ["methanol", "methanol"], + "cpcm": ["Methanol", "Methanol"], + "smd": ["METHANOL", "METHANOL"], + "DC": 32.7 + }, + "thf":{ + "cosmors": ["thf_c0", "thf_c0"], + "dcosmors": ["thf", "thf"], + "xtb": ["thf", "thf"], + "cpcm": ["THF", "THF"], + "smd": ["TETRAHYDROFURAN", "TETRAHYDROFURAN"], + "DC": 7.6 + }, + "toluene":{ + "cosmors": ["toluene_c0", "toluene_c0"], + "dcosmors": ["toluene", "toluene"], + "xtb": ["toluene", "toluene"], + "cpcm": ["Toluene", "Toluene"], + "smd": ["TOLUENE", "TOLUENE"], + "DC": 2.4 + }, + "octanol":{ + "cosmors": ["1-octanol_c0", "1-octanol_c0"], + "dcosmors": ["octanol", "octanol"], + "xtb": ["octanol", "octanol"], + "cpcm": ["Octanol", "Octanol"], + "smd": ["1-OCTANOL", "1-OCTANOL"], + "DC": 9.9 + }, + "woctanol":{ + "cosmors": [null, "woctanol"], + "dcosmors": ["wet-otcanol", "wet-octanol"], + "xtb": ["woctanol", "woctanol"], + "cpcm": [null, "Octanol"], + "smd": [null, "1-OCTANOL"], + "DC": 8.1 + }, + "hexadecane":{ + "cosmors": ["n-hexadecane_c0", "n-hexadecane_c0"], + "dcosmors": ["hexadecane", "hexadecane"], + "xtb": ["hexadecane", "hexadecane"], + "cpcm": [null, "Hexane"], + "smd": ["N-HEXADECANE", "N-HEXADECANE"], + "DC": 2.1 + }, + "dmf":{ + "cosmors": ["dimethylformamide_c0","dimethylformamide_c0"], + "dcosmors": [null, "dimethylsulfoxide"], + "xtb": ["dmf", "dmf"], + "cpcm": ["DMF", "DMF"], + "smd": ["N,N-DIMETHYLFORMAMIDE", "N,N-DIMETHYLFORMAMIDE"], + "DC": 38.3 + }, + "aniline":{ + "cosmors": ["aniline_c0", "aniline_c0"], + "dcosmors": ["aniline", "aniline"], + "xtb": ["aniline", "aniline"], + "cpcm": [null,"Pyridine"], + "smd": ["ANILINE", "ANILINE"], + "DC": 6.9 + }, + "cyclohexane":{ + "cosmors": ["cyclohexane_c0", "cyclohexane_c0"], + "dcosmors": ["cyclohexane", "cyclohexane"], + "xtb": [null, "hexane"], + "cpcm": ["Cyclohexane", "Cyclohexane"], + "smd": ["CYCLOHEXANE", "CYCLOHEXANE"], + "DC": 2.0 + }, + "ccl4":{ + "cosmors": ["ccl4_c0", "ccl4_c0"], + "dcosmors": ["ccl4", "ccl4"], + "xtb": ["ccl4", "ccl4"], + "cpcm": ["CCl4", "CCl4"], + "smd": ["CARBON TETRACHLORIDE", "CARBON TETRACHLORIDE"], + "DC": 2.2 + }, + "diethylether":{ + "cosmors": ["diethylether_c0", "diethylether_c0"], + "dcosmors": ["diethylether", "diethylether"], + "xtb": ["ether", "ether"], + "cpcm": [null, "THF"], + "smd": ["DIETHYL ETHER", "DIETHYL ETHER"], + "DC": 4.4 + }, + "ethanol":{ + "cosmors": ["ethanol_c0", "ethanol_c0"], + "dcosmors": ["ethanol", "ethanol"], + "xtb": [null, "methanol"], + "cpcm": [null, "Methanol"], + "smd": ["ETHANOL", "ETHANOL"], + "DC": 24.6 + }, + "hexane":{ + "cosmors": ["hexane_c0", "hexane_c0"], + "dcosmors": ["hexane", "hexane"], + "xtb": ["hexane", "hexane"], + "cpcm": ["Hexane", "Hexane"], + "smd": ["N-HEXANE", "N-HEXANE"], + "DC": 1.9 + }, + "nitromethane":{ + "cosmors": ["nitromethane_c0", "nitromethane_c0"], + "dcosmors": ["nitromethane", "nitromethane"], + "xtb": ["nitromethane", "nitromethane"], + "cpcm": [null, "methanol"], + "smd": "", + "DC": 38.2 + }, + "benzaldehyde":{ + "cosmors": ["benzaldehyde_c0", "benzaldehyde_c0"], + "dcosmors": [null, "propanone"], + "xtb": ["benzaldehyde", "benzaldehyde"], + "cpcm": [null, "Pyridine"], + "smd": ["BENZALDEHYDE", "BENZALDEHYDE"], + "DC": 18.2 + }, + "benzene":{ + "cosmors": ["benzene_c0", "benzene_c0"], + "dcosmors": [null, "toluene"], + "xtb": ["benzene", "benzene"], + "cpcm": ["Benzene", "Benzene"], + "smd": ["BENZENE", "BENZENE"], + "DC": 2.3 + }, + "cs2":{ + "cosmors": ["cs2_c0", "cs2_c0"], + "dcosmors": [null, "ccl4"], + "xtb": ["cs2", "cs2"], + "cpcm": [null, "CCl4"], + "smd": ["CARBON DISULFIDE", "CARBON DISULFIDE"], + "DC": 2.6 + }, + "dioxane":{ + "cosmors": ["dioxane_c0", "dioxane_c0"], + "dcosmors": [null, "diethylether"], + "xtb": ["dioxane", "dioxane"], + "cpcm": [null, "Cyclohexane"], + "smd": ["1,4-DIOXANE", "1,4-DIOXANE"], + "DC": 2.2 + }, + "ethylacetate":{ + "cosmors": ["ethylacetate_c0", "ethylacetate_c0"], + "dcosmors": [null, "diethylether"], + "xtb": ["ethylacetate", "ethylacetate"], + "cpcm": [null, "THF"], + "smd": ["ETHYL ETHANOATE", "ETHYL ETHANOATE"], + "DC": 5.9 + }, + "furan":{ + "cosmors": ["furane_c0", "furane_c0"], + "dcosmors": [null, "diethylether"], + "xtb": ["furane", "furane"], + "cpcm": [null, "THF"], + "smd": [null, "THF"], + "DC": 3.0 + }, + "phenol":{ + "cosmors": ["phenol_c0", "phenol_c0"], + "dcosmors": [null, "thf"], + "xtb": ["phenol", "phenol"], + "cpcm": [null, "THF"], + "smd": [null, "THIOPHENOL"], + "DC": 8.0 + } +} \ No newline at end of file diff --git a/censo_qm/__init__.py b/censo_qm/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/censo_qm/__main__.py b/censo_qm/__main__.py new file mode 100644 index 0000000..a446323 --- /dev/null +++ b/censo_qm/__main__.py @@ -0,0 +1,4 @@ +from .censo import main + +if __name__ == "__main__": + main() diff --git a/censo_qm/adf_job.py b/censo_qm/adf_job.py new file mode 100644 index 0000000..714b9d0 --- /dev/null +++ b/censo_qm/adf_job.py @@ -0,0 +1,3 @@ +# adf_job.py + +# for J and S calculation only \ No newline at end of file diff --git a/censo_qm/censo.py b/censo_qm/censo.py new file mode 100644 index 0000000..90217be --- /dev/null +++ b/censo_qm/censo.py @@ -0,0 +1,234 @@ +""" +CENSO run code: +- reading commandline input --> cml() +- parsing remote configuration file, reading conformer ensemble, + checking parameters and creating or reading enso.json conformer information + --> enso_startup() +- run cheap_screeing --> part0() +- run prescreening --> part1() +- run optimization --> part2() +- run refinement --> part3() +- run nmrproperties --> part4() or +- run optical_rotation --part5() +""" +from os import getcwd +from time import perf_counter +import sys +from traceback import print_exc +from .cfg import PLENGTH, DESCR, __version__ +from .inputhandling import cml, internal_settings +from .setupcenso import enso_startup +from .cheapscreening import part0 +from .prescreening import part1 +from .optimization import part2 +from .refinement import part3 +from .nmrproperties import part4 +from .opticalrotation import part5 +from .utilities import print + + +def main(argv=None): + """ + Execute the CENSO code. + """ + # get commandline input: + args = cml(DESCR, internal_settings(), argv) + if args.version: + print(__version__) + sys.exit(0) + + # setup conformers and process input: cml >> conifgfile > internal defaults + args, config, conformers, ensembledata = enso_startup(getcwd(), args) + + # RUNNING PART0 + if config.part0: + tic = perf_counter() + try: + config, conformers, store_confs, ensembledata = part0( + config, conformers, ensembledata + ) + except Exception as error: + print("ERROR in part0!") + print("\nThe error-message is {}\n".format(error)) + print("Traceback for debugging:".center(PLENGTH, "*")) + print_exc() + print("".center(PLENGTH, "*")) + print("Going to exit!") + sys.exit(1) + toc = perf_counter() + ensembledata.part_info["part0"] = toc - tic + print(f"Ran part0 in {ensembledata.part_info['part0']:0.4f} seconds") + + # RUNNING PART1 + if config.part1: + tic = perf_counter() + try: + store_confs + except NameError: + store_confs = [] + try: + config, conformers, store_confs, ensembledata = part1( + config, conformers, store_confs, ensembledata + ) + except Exception as error: + print("ERROR in part1!") + print("\nThe error-message is {}\n".format(error)) + print("Traceback for debugging:".center(PLENGTH, "*")) + print_exc() + print("".center(PLENGTH, "*")) + print("Going to exit!") + sys.exit(1) + toc = perf_counter() + ensembledata.part_info["part1"] = toc - tic + print(f"Ran part1 in {ensembledata.part_info['part1']:0.4f} seconds") + + # RUNNING PART2 + if config.part2: + tic = perf_counter() + try: + store_confs + except NameError: + store_confs = [] + try: + config, conformers, store_confs, ensembledata = part2( + config, conformers, store_confs, ensembledata + ) + except Exception as error: + print("ERROR in part2!") + print("\nThe error-message is {}\n".format(error)) + print("Traceback for debugging:".center(PLENGTH, "*")) + print_exc() + print("".center(PLENGTH, "*")) + print("Going to exit!") + sys.exit(1) + toc = perf_counter() + ensembledata.part_info["part2"] = toc - tic + print(f"Ran part2 in {ensembledata.part_info['part2']:0.4f} seconds") + + # RUNNING PART3 + if config.part3: + tic = perf_counter() + try: + store_confs + except NameError: + store_confs = [] + try: + config, conformers, store_confs, ensembledata = part3( + config, conformers, store_confs, ensembledata + ) + except Exception as error: + print("ERROR in part3!") + print("\nThe error-message is {}\n".format(error)) + print("Traceback for debugging:".center(PLENGTH, "*")) + print_exc() + print("".center(PLENGTH, "*")) + print("Going to exit!") + sys.exit(1) + toc = perf_counter() + ensembledata.part_info["part3"] = toc - tic + print(f"Ran part3 in {ensembledata.part_info['part3']:0.4f} seconds") + + # RUNNING PART4 + if config.part4: + tic = perf_counter() + try: + store_confs + except NameError: + store_confs = [] + try: + config, conformers, store_confs, ensembledata = part4( + config, conformers, store_confs, ensembledata + ) + except Exception as error: + print("ERROR in part4!") + print("\nThe error-message is {}\n".format(error)) + print("Traceback for debugging:".center(PLENGTH, "*")) + print_exc() + print("".center(PLENGTH, "*")) + print("Going to exit!") + sys.exit(1) + toc = perf_counter() + ensembledata.part_info["part4"] = toc - tic + print(f"Ran part4 in {ensembledata.part_info['part4']:0.4f} seconds") + + # RUNNING PART5 + if config.optical_rotation: + tic = perf_counter() + try: + store_confs + except NameError: + store_confs = [] + try: + config, conformers, store_confs, ensembledata = part5( + config, conformers, store_confs, ensembledata + ) + except Exception as error: + print("ERROR in part5!") + print("\nThe error-message is {}\n".format(error)) + print("Traceback for debugging:".center(PLENGTH, "*")) + print_exc() + print("".center(PLENGTH, "*")) + print("Going to exit!") + sys.exit(1) + toc = perf_counter() + ensembledata.part_info["part5"] = toc - tic + print(f"Ran part5 in {ensembledata.part_info['part5']:0.4f} seconds") + + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in conformers] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + + # END of CENSO + timings = 0.0 + if len(str(config.nconf)) > 5: + conflength = len(str(config.nconf)) + else: + conflength = 5 + + print(f"\n\n{'Part':20}: {'#conf':>{conflength}} time") + print("".ljust(int(PLENGTH / 2), "-")) + print(f"{'Input':20}: {ensembledata.nconfs_per_part['starting']:{conflength}} -") + if config.part0: + print( + f"{'Part0_all':20}: {ensembledata.nconfs_per_part['part0']:{conflength}} {ensembledata.part_info['part0']:.2f}s" + ) + timings += ensembledata.part_info["part0"] + if config.part1: + print( + f"{'Part1_initial_sort':20}: {ensembledata.nconfs_per_part['part1_firstsort']:{conflength}} -" + ) + print( + f"{'Part1_all':20}: {ensembledata.nconfs_per_part['part1_firstsort']:{conflength}} {ensembledata.part_info['part1']:.2f}s" + ) + timings += ensembledata.part_info["part1"] + if config.part2: + print( + f"{'Part2_opt':20}: {ensembledata.nconfs_per_part['part2_opt']:{conflength}} -" + ) + print( + f"{'Part2_all':20}: {ensembledata.nconfs_per_part['part2']:{conflength}} {ensembledata.part_info['part2']:.2f}s" + ) + timings += ensembledata.part_info["part2"] + if config.part3: + print( + f"{'Part3_all':20}: {ensembledata.nconfs_per_part['part3']:{conflength}} {ensembledata.part_info['part3']:.2f}s" + ) + timings += ensembledata.part_info["part3"] + if config.part4: + print( + f"{'Part4':20}: {'':{conflength}} {ensembledata.part_info['part4']:.2f}s" + ) + timings += ensembledata.part_info["part4"] + if config.optical_rotation: + print( + f"{'Part5':20}: {'':{conflength}} {ensembledata.part_info['part5']:.2f}s" + ) + timings += ensembledata.part_info["part5"] + print("".ljust(int(PLENGTH / 2), "-")) + print(f"{'All parts':20}: {'':{conflength}} {timings:.2f}s") + print("\nCENSO all done!") diff --git a/censo_qm/cfg.py b/censo_qm/cfg.py new file mode 100644 index 0000000..cde2e22 --- /dev/null +++ b/censo_qm/cfg.py @@ -0,0 +1,270 @@ +""" +Storing constants for the use in all CENSO modules. +Storing program paths --> still in transition +Storing censo_solvent_db solvent database across all solvation models (as fallback) +""" +import os + +__version__ = "1.0.0" +DESCR = f""" + ______________________________________________________________ + | | + | | + | CENSO - Commandline ENSO | + | v {__version__:<{19}} | + | energetic sorting of CREST Conformer Rotamer Ensembles | + | University of Bonn, MCTC | + | June 2020 | + | based on ENSO version 2.0.1 | + | F. Bohle and S. Grimme | + | | + |______________________________________________________________| + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +""" +global ENVIRON +ENVIRON = os.environ.copy() +CODING = "ISO-8859-1" +DIGILEN = 60 +PLENGTH = 100 +AU2J = 4.3597482e-18 # a.u.(hartree/mol) to J +KB = 1.3806485279e-23 # J/K +AU2KCAL = 627.50947428 +BOHR2ANG = 0.52917721067 + +# program paths: + +external_paths = {} +external_paths["orcapath"] = "" +external_paths["orcaversion"] = "" +external_paths["xtbpath"] = "" +external_paths["crestpath"] = "" +external_paths["cosmorssetup"] = "" +external_paths["dbpath"] = "" +external_paths["cosmothermversion"] = "" +external_paths["mpshiftpath"] = "" +external_paths["escfpath"] = "" +external_paths["cefinepath"] = "" + + +# censo solvent database to chose solvents across all available solvent models +censo_solvent_db = { + "acetone": { + "cosmors": ["propanone_c0", "propanone_c0"], + "dcosmors": ["propanone", "propanone"], + "xtb": ["acetone", "acetone"], + "cpcm": ["acetone", "acetone"], + "smd": ["ACETONE", "ACETONE"], + "DC": 20.7, + }, + "chcl3": { + "cosmors": ["chcl3_c0", "chcl3_c0"], + "dcosmors": ["chcl3", "chcl3"], + "xtb": ["chcl3", "chcl3"], + "cpcm": ["chloroform", "chloroform"], + "smd": ["CHLOROFORM", "CHLOROFORM"], + "DC": 4.8, + }, + "acetonitrile": { + "cosmors": ["acetonitrile_c0", "acetonitrile_c0"], + "dcosmors": ["acetonitrile", "acetonitrile"], + "xtb": ["acetonitrile", "acetonitrile"], + "cpcm": ["acetonitrile", "acetonitrile"], + "smd": ["ACETONITRILE", "ACETONITRILE"], + "DC": 36.6, + }, + "ch2cl2": { + "cosmors": ["ch2cl2_c0", "ch2cl2_c0"], + "dcosmors": [None, "chcl3"], + "xtb": ["ch2cl2", "ch2cl2"], + "cpcm": ["CH2Cl2", "CH2Cl2"], + "smd": ["DICHLOROMETHANE", "DICHLOROMETHANE"], + "DC": 9.1, + }, + "dmso": { + "cosmors": ["dimethylsulfoxide_c0", "dimethylsulfoxide_c0"], + "dcosmors": ["dimethylsulfoxide", "dimethylsulfoxide"], + "xtb": ["dmso", "dmso"], + "cpcm": ["DMSO", "DMSO"], + "smd": ["DIMETHYLSULFOXIDE", "DIMETHYLSULFOXIDE"], + "DC": 47.2, + }, + "h2o": { + "cosmors": ["h2o_c0", "h2o_c0"], + "dcosmors": ["h2o", "h2o"], + "xtb": ["h2o", "h2o"], + "cpcm": ["Water", "Water"], + "smd": ["WATER", "WATER"], + "DC": 80.1, + }, + "methanol": { + "cosmors": ["methanol_c0", "methanol_c0"], + "dcosmors": ["methanol", "methanol"], + "xtb": ["methanol", "methanol"], + "cpcm": ["Methanol", "Methanol"], + "smd": ["METHANOL", "METHANOL"], + "DC": 32.7, + }, + "thf": { + "cosmors": ["thf_c0", "thf_c0"], + "dcosmors": ["thf", "thf"], + "xtb": ["thf", "thf"], + "cpcm": ["THF", "THF"], + "smd": ["TETRAHYDROFURAN", "TETRAHYDROFURAN"], + "DC": 7.6, + }, + "toluene": { + "cosmors": ["toluene_c0", "toluene_c0"], + "dcosmors": ["toluene", "toluene"], + "xtb": ["toluene", "toluene"], + "cpcm": ["Toluene", "Toluene"], + "smd": ["TOLUENE", "TOLUENE"], + "DC": 2.4, + }, + "octanol": { + "cosmors": ["1-octanol_c0", "1-octanol_c0"], + "dcosmors": ["octanol", "octanol"], + "xtb": ["octanol", "octanol"], + "cpcm": ["Octanol", "Octanol"], + "smd": ["1-OCTANOL", "1-OCTANOL"], + "DC": 9.9, + }, + "woctanol": { + "cosmors": [None, "woctanol"], + "dcosmors": ["wet-otcanol", "wet-octanol"], + "xtb": ["woctanol", "woctanol"], + "cpcm": [None, "Octanol"], + "smd": [None, "1-OCTANOL"], + "DC": 8.1, + }, + "hexadecane": { + "cosmors": ["n-hexadecane_c0", "n-hexadecane_c0"], + "dcosmors": ["hexadecane", "hexadecane"], + "xtb": ["hexadecane", "hexadecane"], + "cpcm": [None, "Hexane"], + "smd": ["N-HEXADECANE", "N-HEXADECANE"], + "DC": 2.1, + }, + "dmf": { + "cosmors": ["dimethylformamide_c0", "dimethylformamide_c0"], + "dcosmors": [None, "dimethylsulfoxide"], + "xtb": ["dmf", "dmf"], + "cpcm": ["DMF", "DMF"], + "smd": ["N,N-DIMETHYLFORMAMIDE", "N,N-DIMETHYLFORMAMIDE"], + "DC": 38.3, + }, + "aniline": { + "cosmors": ["aniline_c0", "aniline_c0"], + "dcosmors": ["aniline", "aniline"], + "xtb": ["aniline", "aniline"], + "cpcm": [None, "Pyridine"], + "smd": ["ANILINE", "ANILINE"], + "DC": 6.9, + }, + "cyclohexane": { + "cosmors": ["cyclohexane_c0", "cyclohexane_c0"], + "dcosmors": ["cyclohexane", "cyclohexane"], + "xtb": [None, "hexane"], + "cpcm": ["Cyclohexane", "Cyclohexane"], + "smd": ["CYCLOHEXANE", "CYCLOHEXANE"], + "DC": 2.0, + }, + "ccl4": { + "cosmors": ["ccl4_c0", "ccl4_c0"], + "dcosmors": ["ccl4", "ccl4"], + "xtb": ["ccl4", "ccl4"], + "cpcm": ["CCl4", "CCl4"], + "smd": ["CARBON TETRACHLORIDE", "CARBON TETRACHLORIDE"], + "DC": 2.2, + }, + "diethylether": { + "cosmors": ["diethylether_c0", "diethylether_c0"], + "dcosmors": ["diethylether", "diethylether"], + "xtb": ["ether", "ether"], + "cpcm": [None, "THF"], + "smd": ["DIETHYL ETHER", "DIETHYL ETHER"], + "DC": 4.4, + }, + "ethanol": { + "cosmors": ["ethanol_c0", "ethanol_c0"], + "dcosmors": ["ethanol", "ethanol"], + "xtb": [None, "methanol"], + "cpcm": [None, "Methanol"], + "smd": ["ETHANOL", "ETHANOL"], + "DC": 24.6, + }, + "hexane": { + "cosmors": ["hexane_c0", "hexane_c0"], + "dcosmors": ["hexane", "hexane"], + "xtb": ["hexane", "hexane"], + "cpcm": ["Hexane", "Hexane"], + "smd": ["N-HEXANE", "N-HEXANE"], + "DC": 1.9, + }, + "nitromethane": { + "cosmors": ["nitromethane_c0", "nitromethane_c0"], + "dcosmors": ["nitromethane", "nitromethane"], + "xtb": ["nitromethane", "nitromethane"], + "cpcm": [None, "methanol"], + "smd": "", + "DC": 38.2, + }, + "benzaldehyde": { + "cosmors": ["benzaldehyde_c0", "benzaldehyde_c0"], + "dcosmors": [None, "propanone"], + "xtb": ["benzaldehyde", "benzaldehyde"], + "cpcm": [None, "Pyridine"], + "smd": ["BENZALDEHYDE", "BENZALDEHYDE"], + "DC": 18.2, + }, + "benzene": { + "cosmors": ["benzene_c0", "benzene_c0"], + "dcosmors": [None, "toluene"], + "xtb": ["benzene", "benzene"], + "cpcm": ["Benzene", "Benzene"], + "smd": ["BENZENE", "BENZENE"], + "DC": 2.3, + }, + "cs2": { + "cosmors": ["cs2_c0", "cs2_c0"], + "dcosmors": [None, "ccl4"], + "xtb": ["cs2", "cs2"], + "cpcm": [None, "CCl4"], + "smd": ["CARBON DISULFIDE", "CARBON DISULFIDE"], + "DC": 2.6, + }, + "dioxane": { + "cosmors": ["dioxane_c0", "dioxane_c0"], + "dcosmors": [None, "diethylether"], + "xtb": ["dioxane", "dioxane"], + "cpcm": [None, "Cyclohexane"], + "smd": ["1,4-DIOXANE", "1,4-DIOXANE"], + "DC": 2.2, + }, + "ethylacetate": { + "cosmors": ["ethylacetate_c0", "ethylacetate_c0"], + "dcosmors": [None, "diethylether"], + "xtb": ["ethylacetate", "ethylacetate"], + "cpcm": [None, "THF"], + "smd": ["ETHYL ETHANOATE", "ETHYL ETHANOATE"], + "DC": 5.9, + }, + "furan": { + "cosmors": ["furane_c0", "furane_c0"], + "dcosmors": [None, "diethylether"], + "xtb": ["furane", "furane"], + "cpcm": [None, "THF"], + "smd": [None, "THF"], + "DC": 3.0, + }, + "phenol": { + "cosmors": ["phenol_c0", "phenol_c0"], + "dcosmors": [None, "thf"], + "xtb": ["phenol", "phenol"], + "cpcm": [None, "THF"], + "smd": [None, "THIOPHENOL"], + "DC": 8.0, + }, +} diff --git a/censo_qm/cheapscreening.py b/censo_qm/cheapscreening.py new file mode 100644 index 0000000..23c3502 --- /dev/null +++ b/censo_qm/cheapscreening.py @@ -0,0 +1,534 @@ +""" +prescreening == part0, calculate cheap free energy on GFNn-xTB input geometry +The idea is to improve on the description of E with a very fast DFT method. +""" +import os +import sys +import math +from multiprocessing import JoinableQueue as Queue +from .cfg import PLENGTH, DIGILEN, AU2KCAL, CODING +from .parallel import run_in_parallel +from .orca_job import OrcaJob +from .tm_job import TmJob +from .utilities import ( + check_for_folder, + print_block, + new_folders, + last_folders, + ensemble2coord, + printout, + move_recursively, + write_trj, + check_tasks, + calc_std_dev, + spearman, + print, + calc_boltzmannweights, +) + + +def part0(config, conformers, ensembledata): + """ + Cheap prescreening of the ensemble, with single-points on combined ensemble + geometries. + Input: + - config [conifg_setup object] contains all settings + - conformers [list of molecule_data objects] each conformer is represented + - ensembledata -> instance for saving ensemble (not conf) related data + Return: + -> config + -> conformers + -> store_confs + """ + save_errors = [] + print("\n" + "".ljust(PLENGTH, "-")) + print("CRE CHEAP-PRESCREENING - PART0".center(PLENGTH, " ")) + print("".ljust(PLENGTH, "-") + "\n") + # print flags for part1 + info = [] + info.append(["prog", "program"]) + info.append(["func0", "functional for part0"]) + info.append(["basis0", "basis set for part0"]) + info.append(["part0_threshold", "threshold"]) + info.append(["nconf", "starting number of considered conformers"]) + + optionsexchange = {True: "on", False: "off"} + for item in info: + if item[0] == "justprint": + print(item[1:][0]) + else: + if item[0] == "printoption": + option = item[2] + else: + option = getattr(config, item[0]) + if option is True or option is False: + option = optionsexchange[option] + elif isinstance(option, list): + option = ", ".join(option) + print( + "{}: {:{digits}} {}".format( + item[1], "", option, digits=DIGILEN - len(item[1]) + ) + ) + print("") + # end print + + calculate = [] # has to be calculated in this run + prev_calculated = [] # was already calculated in a previous run + store_confs = [] # stores all confs which are sorted out! + + print("Calculating efficient gas-phase single-point energies:") + + # setup queues + q = Queue() + resultq = Queue() + + if config.prog == "tm": + job = TmJob + elif config.prog == "orca": + job = OrcaJob + + for conf in list(conformers): + conf = conformers.pop(conformers.index(conf)) + if conf.removed: + store_confs.append(conf) + print(f"CONF{conf.id} is removed as requested by the user.") + continue + if conf.id > config.nconf: + store_confs.append(conf) + continue + if conf.cheap_prescreening_sp_info["info"] == "not_calculated": + calculate.append(conf) + elif conf.cheap_prescreening_sp_info["info"] == "failed": + store_confs.append(conf) + print(f"Calculation of CONF{conf.id} failed in the previous run.") + elif conf.cheap_prescreening_sp_info["info"] == "calculated": + conf.job["success"] = True + prev_calculated.append(conf) + else: + print("ERROR: UNEXPECTED BEHAVIOUR") + if not calculate and not prev_calculated: + print("ERROR: No conformers left!") + if prev_calculated: + check_for_folder(config.cwd, [i.id for i in prev_calculated], config.func) + print("The efficient gas-phase single-point was calculated before for:") + print_block(["CONF" + str(i.id) for i in prev_calculated]) + pl = config.lenconfx + 4 + len(str("/" + config.func)) + + if config.solvent != "gas": + instruction = { + "jobtype": "alpb_gsolv", + "func": config.func0, + "basis": getattr( + config, + "basis0", + config.func_basis_default.get(config.func0, "def2-SV(P)"), + ), + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": config.solvent, + "sm": config.sm_rrho, + "omp": config.omp, + "gfn_version": config.part0_gfnv, + "energy": 0.0, + "energy2": 0.0, + "success": False, + "xtb_driver_path": config.external_paths["xtbpath"], + } + + tmp_disp = "" + if config.prog == "tm": + instruction["prepinfo"] = ["clear", "-grid", "1", "-scfconv", "5", "DOGCP"] + if config.func0 == "b97-d": + instruction["prepinfo"].append("-zero") + tmp_disp = "D3(0)" + + elif config.prog == "orca": + instruction["progpath"] = config.external_paths["orcapath"] + instruction["prepinfo"] = ["low", "DOGCP"] + + instruction["method"], instruction["method2"], = config.get_method_name( + instruction["jobtype"], + func=instruction["func"], + basis=instruction["basis"], + sm=instruction["sm"], + solvent=instruction["solvent"], + prog=config.prog, + disp=tmp_disp, + gfn_version=instruction["gfn_version"], + ) + elif config.solvent == "gas": + instruction = { + "jobtype": "sp", + "func": config.func0, + "basis": getattr( + config, + "basis0", + config.func_basis_default.get(config.func0, "def2-SV(P)"), + ), + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": "gas", + "sm": "gas-phase", + "omp": config.omp, + "energy": 0.0, + "energy2": 0.0, + "success": False, + } + + tmp_disp = "" + if config.prog == "tm": + instruction["prepinfo"] = ["clear", "-grid", "1", "-scfconv", "5", "DOGCP"] + if config.func0 == "b97-d": + instruction["prepinfo"].append("-zero") + tmp_disp = "D3(0)" + + elif config.prog == "orca": + instruction["progpath"] = config.external_paths["orcapath"] + instruction["prepinfo"] = ["low"] + + instruction["method"], instruction["method2"], = config.get_method_name( + instruction["jobtype"], + func=instruction["func"], + basis=instruction["basis"], + sm=instruction["sm"], + solvent=instruction["solvent"], + prog=config.prog, + disp=tmp_disp, + ) + + name = "efficient gas-phase single-point" + folder = "part0_sp" + check = {True: "was successful", False: "FAILED"} + if calculate: + print(f"The {name} is calculated for:") + print_block(["CONF" + str(i.id) for i in calculate]) + # create folders: + save_errors, store_confs, calculate = new_folders( + config.cwd, calculate, folder, save_errors, store_confs + ) + # write coord to folder + calculate, store_confs, save_errors = ensemble2coord( + config, folder, calculate, store_confs, save_errors + ) + + # parallel calculation: + calculate = run_in_parallel( + config, q, resultq, job, config.maxthreads, calculate, instruction, folder + ) + + for conf in list(calculate): + if instruction["jobtype"] == "alpb_gsolv": + line = ( + f"The {name} {check[conf.job['success']]}" + f" for {last_folders(conf.job['workdir'], 2):>{pl}}: " + f"E(DFT) = {conf.job['energy']:>.8f}" + f" Gsolv = {conf.job['energy2']:>.8f}" + ) + print(line) + if not conf.job["success"]: + save_errors.append(line) + conf.cheap_prescreening_sp_info["info"] = "failed" + conf.cheap_prescreening_sp_info["method"] = conf.job["method"] + conf.cheap_prescreening_gsolv_info["info"] = "failed" + conf.cheap_prescreening_gsolv_info["method"] = conf.job["method"] + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.cheap_prescreening_sp_info["energy"] = conf.job["energy"] + conf.cheap_prescreening_sp_info["info"] = "calculated" + conf.cheap_prescreening_sp_info["method"] = conf.job["method"] + conf.cheap_prescreening_gsolv_info["energy"] = conf.job["energy2"] + conf.cheap_prescreening_gsolv_info["info"] = "calculated" + conf.cheap_prescreening_gsolv_info["method"] = conf.job["method2"] + conf.cheap_prescreening_gsolv_info["gas-energy"] = conf.job[ + "energy_xtb_gas" + ] + conf.cheap_prescreening_gsolv_info["solv-energy"] = conf.job[ + "energy_xtb_solv" + ] + elif instruction["jobtype"] == "sp": + line = ( + f"The {name} calculation {check[conf.job['success']]}" + f" for {last_folders(conf.job['workdir'], 2):>{pl}}: " + f"E(DFT) = {conf.job['energy']:>.8f}" + ) + print(line) + if not conf.job["success"]: + save_errors.append(line) + conf.cheap_prescreening_sp_info["info"] = "failed" + conf.cheap_prescreening_sp_info["method"] = conf.job["method"] + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.cheap_prescreening_sp_info["energy"] = conf.job["energy"] + conf.cheap_prescreening_sp_info["info"] = "calculated" + conf.cheap_prescreening_sp_info["method"] = conf.job["method"] + else: + print( + f'UNEXPECTED BEHAVIOUR: {conf.job["success"]} {conf.job["jobtype"]}' + ) + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + check_tasks(calculate, config.check) + else: + print("No conformers are considered additionally.") + if prev_calculated: + # adding conformers calculated before: + for conf in list(prev_calculated): + conf.job["workdir"] = os.path.normpath( + os.path.join(config.cwd, "CONF" + str(conf.id), config.func) + ) + if instruction["jobtype"] == "alpb_gsolv": + print( + f"The {name} {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"E(DFT) = {conf.cheap_prescreening_sp_info['energy']:>.8f}" + f" Gsolv = {conf.cheap_prescreening_gsolv_info['energy']:>.8f}" + ) + elif instruction["jobtype"] == "sp": + print( + f"The {name} calculation {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"E(DFT) = {conf.cheap_prescreening_sp_info['energy']:>.8f}" + ) + calculate.append(prev_calculated.pop(prev_calculated.index(conf))) + for conf in calculate: + conf.reset_job_info() + if not calculate: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + # *************************************************************************** + # sorting by E + # (remove high lying conformers above part0_threshold) + print("\n" + "".ljust(int(PLENGTH), "-")) + print( + "Removing high lying conformers by improved energy description".center( + int(PLENGTH), " " + ) + ) + print("".ljust(int(PLENGTH), "-") + "\n") + + if config.solvent != "gas": + solvation = "cheap_prescreening_gsolv_info" + else: + solvation = None + rrho = None + energy = "cheap_prescreening_sp_info" + for conf in calculate: + conf.calc_free_energy(e=energy, solv=solvation, rrho=rrho) + try: + minfree = min([i.free_energy for i in calculate if i is not None]) + except ValueError: + raise + for conf in calculate: + if conf.free_energy == minfree: + ensembledata.bestconf["part0"] = conf.id + lowest_e = conf.cheap_prescreening_sp_info["energy"] + lowest_gsolv = conf.cheap_prescreening_gsolv_info["energy"] + if config.solvent != "gas": + try: + minfree_gfnx = min( + [ + i.cheap_prescreening_gsolv_info["solv-energy"] + for i in calculate + if i is not None + ] + ) + except ValueError: + raise + for conf in calculate: + conf.rel_free_energy = (conf.free_energy - minfree) * AU2KCAL + if config.solvent != "gas": + conf.tmp_rel_xtb = ( + conf.cheap_prescreening_gsolv_info["solv-energy"] - minfree_gfnx + ) * AU2KCAL + conf.tmp_rel_e = ( + conf.cheap_prescreening_sp_info["energy"] - lowest_e + ) * AU2KCAL + conf.tmp_rel_gsolv = ( + conf.cheap_prescreening_gsolv_info["energy"] - lowest_gsolv + ) * AU2KCAL + + try: + maxreldft = max([i.rel_free_energy for i in calculate if i is not None]) + except ValueError: + print("ERROR: No conformer left or Error in maxreldft!") + # print sorting + columncall = [ + lambda conf: "CONF" + str(getattr(conf, "id")), + lambda conf: getattr(conf, "cheap_prescreening_gsolv_info")["solv-energy"], + lambda conf: getattr(conf, "tmp_rel_xtb"), + lambda conf: getattr(conf, "cheap_prescreening_sp_info")["energy"], + lambda conf: getattr(conf, "cheap_prescreening_gsolv_info")["energy"], + lambda conf: getattr(conf, "free_energy"), + lambda conf: getattr(conf, "tmp_rel_e"), + lambda conf: getattr(conf, "tmp_rel_gsolv"), + lambda conf: getattr(conf, "rel_free_energy"), + ] + columnheader = [ + "CONF#", + f"G [Eh]", + f"ΔG [kcal/mol]", + "E [Eh]", + "Gsolv [Eh]", + "Gtot", + "ΔE(DFT)", + "ΔGsolv", + "ΔGtot", + ] + columndescription = [ + "", + "", + "", + "", + "[Eh]", + "[Eh]", + "[kcal/mol]", + "[kcal/mol]", + "[kcal/mol]", + ] + columnformat = [ + "", + (12, 7), + (5, 2), + (12, 7), + (12, 7), + (12, 7), + (5, 2), + (5, 2), + (5, 2), + ] + columndescription[1] = f"{config.part0_gfnv.upper()}-xTB[{config.sm_rrho.upper()}]" + columndescription[2] = f"{config.part0_gfnv.upper()}-xTB[{config.sm_rrho.upper()}]" + columndescription[3] = instruction["method"] + columndescription[4] = instruction["method2"] + if config.solvent == "gas": + columncall[1] = lambda conf: getattr(conf, "xtb_energy") + columncall[2] = lambda conf: getattr(conf, "rel_xtb_energy") + columnheader[1] = "G(GFNn-xTB)" + columnheader[2] = "ΔG(GFNn-xTB)" + columndescription[1] = "[Eh]" + columndescription[2] = "[kcal/mol]" + columndescription[4] = "gas-phase" + + calculate.sort(key=lambda x: int(x.id)) + printout( + os.path.join(config.cwd, "part0.dat"), + columncall, + columnheader, + columndescription, + columnformat, + calculate, + minfree, + ) + print("".ljust(int(PLENGTH), "-")) + # -------------------------------------------------------------------------- + + # write to enso.json + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + # --------------------------------------------------------------------------- + # sorting + if maxreldft > config.part0_threshold: + print("\n" + "".ljust(int(PLENGTH / 2), "-")) + print("Conformers considered further".center(int(PLENGTH / 2), " ")) + print("".ljust(int(PLENGTH / 2), "-") + "\n") + for conf in list(calculate): + if conf.rel_free_energy <= config.part0_threshold: + conf.part_info["part0"] = "passed" + else: + conf.part_info["part0"] = "refused" + store_confs.append(calculate.pop(calculate.index(conf))) + if calculate: + print( + f"These conformers are below the {config.part0_threshold:.3f} " + f"kcal/mol threshold.\n" + ) + print_block(["CONF" + str(i.id) for i in calculate]) + else: + print("Error: There are no more conformers left!") + else: + for conf in list(calculate): + conf.part_info["part0"] = "passed" + print( + "\nAll relative (free) energies are below the initial threshold " + f"of {config.part0_threshold} kcal/mol.\nAll conformers are " + "considered further." + ) + ensembledata.nconfs_per_part["part0"] = len(calculate) + + ################################################################################ + # calculate average G correction + print( + "\nCalculating Boltzmann averaged (free) energy of ensemble on input " + "geometries (not DFT optimized)!\n" + ) + # calculate Boltzmannweights + print(f"{'temperature /K:':<15} {'avE(T) /a.u.':>14} " f"{'avG(T) /a.u.':>14} ") + print("".ljust(int(PLENGTH), "-")) + + calculate = calc_boltzmannweights(calculate, "free_energy", config.temperature) + avG = 0.0 + avE = 0.0 + for conf in calculate: + avG += conf.bm_weight * conf.free_energy + avE += conf.bm_weight * conf.cheap_prescreening_sp_info["energy"] + # printout: + print(f"{config.temperature:^15} {avE:>14.7f} {avG:>14.7f} " " <<==part0==") + print("".ljust(int(PLENGTH), "-")) + print("") + ################################################################################ + + # reset + for conf in calculate: + conf.free_energy = 0.0 + conf.rel_free_energy = None + conf.bm_weight = 0.0 + conf.tmp_rel_xtb = 0.0 + conf.tmp_rel_e = 0.0 + conf.tmp_rel_gsolv = 0.0 + conf.reset_job_info() + + # write to enso.json + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + + if save_errors: + print( + "***---------------------------------------------------------***", + file=sys.stderr, + ) + print( + "Printing most relevant errors again, just for user convenience:", + file=sys.stderr, + ) + for _ in list(save_errors): + print(save_errors.pop(), file=sys.stderr) + print( + "***---------------------------------------------------------***", + file=sys.stderr, + ) + + tmp = int((PLENGTH - len("END of Part0")) / 2) + print("\n" + "".ljust(tmp, ">") + "END of Part0" + "".rjust(tmp, "<")) + return config, calculate, store_confs, ensembledata diff --git a/censo_qm/datastructure.py b/censo_qm/datastructure.py new file mode 100755 index 0000000..0b06fb1 --- /dev/null +++ b/censo_qm/datastructure.py @@ -0,0 +1,544 @@ +""" +contains molecule_data class for storing all thermodyn. properties of the +conformer. +""" +from collections import OrderedDict +from .utilities import print + + +class MoleculeData: + """ + molecule_data contains all thermodynamic properties of a conformer e.g. + energy, gsolv, grrho + """ + + def __init__( + self, + rank, + chrg=0, + uhf=0, + xtb_energy=None, + xtb_energy_unbiased=None, + xtb_free_energy=None, + rel_xtb_energy=None, + rel_xtb_free_energy=None, + sym="c1", + gi=1.0, + removed=False, + free_energy=0.0, + temperature_info={"temperature": 298.15, "range": None}, + cheap_prescreening_sp_info={ + "energy": None, + "info": "not_calculated", + "method": None, + "prev_methods": None, + }, + cheap_prescreening_gsolv_info={ + "energy": None, + "gas-energy": None, + "solv-energy": None, + "info": "not_calculated", + "method": None, + "prev_methods": None, + }, + prescreening_sp_info={ + "energy": None, + "info": "not_calculated", + "method": None, + "prev_methods": None, + }, + lowlevel_sp_info={ + "energy": None, + "info": "not_calculated", + "method": None, + "prev_methods": None, + }, + highlevel_sp_info={ + "energy": None, + "info": "not_calculated", + "method": None, + "prev_methods": None, + }, + prescreening_grrho_info={ + "energy": None, + "info": "not_calculated", + "method": None, + "fuzzythr": 0.0, + "rmsd": None, + "prev_methods": None, + }, + lowlevel_grrho_info={ + "energy": None, + "range": None, + "info": "not_calculated", + "method": None, + "rmsd": None, + "prev_methods": None, + }, + lowlevel_hrrho_info={ + "energy": None, + "range": None, + "info": "not_calculated", + "method": None, + "rmsd": None, + "prev_methods": None, + }, + highlevel_grrho_info={ + "energy": None, + "range": None, + "info": "not_calculated", + "method": None, + "rmsd": None, + "prev_methods": None, + }, + highlevel_hrrho_info={ + "energy": None, + "range": None, + "info": "not_calculated", + "method": None, + "rmsd": None, + "prev_methods": None, + }, + prescreening_gsolv_info={ + "energy": None, + "gas-energy": None, + "info": "not_calculated", + "method": None, + "prev_methods": None, + }, + lowlevel_gsolv_info={ + "energy": None, + "gas-energy": None, + "range": None, + "info": "not_calculated", + "method": None, + "prev_methods": None, + }, + lowlevel_gsolv_compare_info={ + "energy": None, + "range": None, + "info": "not_calculated", + "method": None, + "prev_methods": None, + "std_dev": None, + }, + highlevel_gsolv_info={ + "energy": None, + "gas-energy": None, + "range": None, + "info": "not_calculated", + "method": None, + "prev_methods": None, + }, + optimization_info={ + "energy": None, + "convergence": "not_converged", + "cregen_sort": "pass", # pass and removed + "info": "not_calculated", + "cycles": 0, + "ecyc": [], + "decyc": [], + "energy_rrho": 0.0, + "method_rrho": None, + "info_rrho": "not_calculated", + }, + nmr_coupling_info={ + "info": "not_calculated", + "method": None, + "h_active": False, + "c_active": False, + "f_active": False, + "si_active": False, + "p_active": False, + }, + nmr_shielding_info={ + "info": "not_calculated", + "method": None, + "h_active": False, + "c_active": False, + "f_active": False, + "si_active": False, + "p_active": False, + }, + part_info={ + "part0": None, + "part1": None, + "part2": None, + "part3": None, + "part4": None, + "part5": None, + }, + comment=[], + optical_rotation_info={ + "range": None, + "info": "not_calculated", + "method": None, + "prev_methods": None, + }, + ): + """ + molecule_data: Creates a molecule instance where all thermodynamic data + concerning the molecule is stored. + Input: + - rank [int] --> rank of the molecule in the input ensemble e.g. CONF(X) + - temperature [float] --> evaluation at this temperature + - trange [list(float)] --> list of temperatures for evaluation at + multiple temperatures + - chrg [int] --> charge of the molecule + - uhf [int] --> number of unpaired electrons + - xtb_energy [float] a.u.--> initial energy taken from the input ensemble + - rel_xtb_energy [float] kcal/mol --> relative initial energy taken + from the input ensemble + - sym [string] --> schoenflies notation of pointgroup + - gi [float] --> degeneracy of conformer + - prescreening_sp [float] --> single point energy of the preescreening + procecure + - lowlevel_sp [float] --> single point energy of the optimization + - highlevel_sp [float] --> high level single point energy at hybrid + level with larger basis set + - prescreening_grrho [float] --> thermostatistical contribution of + prescreening procedure + - grrho [float] --> thermostatistical (G) contribution on optimized DFT + geometry + - hrrho [float] --> thermostatistical (H) contribution on optimized DFT + geometry + - part_info [string] --> partx : passed/refused/not_calculated + + + *_info = {'info': calculated/not_calculated/failed/skipped/removed/prep-failed} + """ + # check default arguments: + for store in ( + prescreening_grrho_info, + lowlevel_grrho_info, + lowlevel_hrrho_info, + highlevel_grrho_info, + highlevel_hrrho_info, + prescreening_gsolv_info, + lowlevel_gsolv_info, + lowlevel_gsolv_compare_info, + highlevel_gsolv_info, + optical_rotation_info, + prescreening_sp_info, + lowlevel_sp_info, + highlevel_sp_info, + cheap_prescreening_sp_info, + cheap_prescreening_gsolv_info, + ): + if store.get("prev_methods", None) is None: + store["prev_methods"] = {} + if temperature_info.get("range") is None: + temperature_info["range"] = [] + if xtb_energy is None: + xtb_energy = 100.0 + if xtb_energy_unbiased is None: + xtb_energy_unbiased = 100.0 + if xtb_free_energy is None: + xtb_free_energy = 100.0 + if rel_xtb_energy is None: + rel_xtb_energy = 100.0 + if rel_xtb_free_energy is None: + rel_xtb_free_energy = 100.0 + if cheap_prescreening_sp_info.get("energy") is None: + cheap_prescreening_sp_info["energy"] = 0.0 + if cheap_prescreening_gsolv_info.get("energy") is None: + cheap_prescreening_gsolv_info["energy"] = 0.0 + if prescreening_sp_info.get("energy") is None: + prescreening_sp_info["energy"] = 0.0 + if lowlevel_sp_info.get("energy") is None: + lowlevel_sp_info["energy"] = 0.0 + if highlevel_sp_info.get("energy") is None: + highlevel_sp_info["energy"] = 0.0 + if prescreening_grrho_info.get("energy") is None: + prescreening_grrho_info["energy"] = 0.0 + if lowlevel_grrho_info.get("energy") is None: + lowlevel_grrho_info["energy"] = 0.0 + self._initialize(lowlevel_grrho_info) + if lowlevel_hrrho_info.get("energy") is None: + lowlevel_hrrho_info["energy"] = 0.0 + self._initialize(lowlevel_hrrho_info) + if prescreening_gsolv_info.get("energy") is None: + prescreening_gsolv_info["energy"] = 0.0 + if lowlevel_gsolv_info.get("energy") is None: + lowlevel_gsolv_info["energy"] = 0.0 + self._initialize(lowlevel_gsolv_info) + if lowlevel_gsolv_compare_info.get("energy") is None: + lowlevel_gsolv_compare_info["energy"] = 0.0 + # if lowlevel_gsolv_compare_info.get("std_dev") is None: + # lowlevel_gsolv_compare_info["std_dev"] = 0.0 + self._initialize(lowlevel_gsolv_compare_info) + if highlevel_gsolv_info.get("energy") is None: + highlevel_gsolv_info["energy"] = 0.0 + self._initialize(highlevel_gsolv_info) + for key in part_info.keys(): + if part_info.get(key) is None: + part_info[key] = "not_calculated" + # highlevel_grrho + if highlevel_grrho_info.get("energy") is None: + highlevel_grrho_info["energy"] = 0.0 + self._initialize(highlevel_grrho_info) + # highlevel_hrrho + if highlevel_hrrho_info.get("energy") is None: + highlevel_hrrho_info["energy"] = 0.0 + self._initialize(highlevel_hrrho_info) + # optical_rotation_info + self._initialize(optical_rotation_info) + + # exceptions: + if not isinstance(rank, int): + raise TypeError( + "Please input an integer. The id is the rank of the " + "molecule in the input ensemble!" + ) + if type(temperature_info.get("temperature", None)) != float: + raise TypeError( + "Please input an float. Thermodynamic properties are" + "evaluated at this temperature!" + ) + if not isinstance(temperature_info.get("range", None), list): + raise ValueError("Please provide a list with temperatures!") + elif any([type(i) != float for i in temperature_info.get("range")]): + raise TypeError("Please provide temperatures as float!") + if not isinstance(chrg, int): + raise TypeError("Please provide charge as integer!") + if not isinstance(uhf, int): + raise TypeError( + "Please provide number of unpaired electrons as " "integer!" + ) + if not isinstance(xtb_energy, float): + raise TypeError("Please provide energy from input ensemble as float!") + if not isinstance(rel_xtb_energy, float): + raise TypeError( + "Please provide rel. energy from input ensemble as " "float!" + ) + if not isinstance(sym, str): + raise TypeError("Please provide symmetry as string.") + if not isinstance(gi, float): + try: + gi = float(gi) + except (TypeError, ValueError): + raise "Please provide gi as float!" + if not isinstance(prescreening_sp_info.get("energy", None), float): + raise TypeError("Please provide preescreening sinlge point as float!") + if not isinstance(lowlevel_sp_info.get("energy", None), float): + raise TypeError("Please provide low level sinlge point as float!") + if not isinstance(highlevel_sp_info.get("energy", None), float): + raise TypeError("Please provide high level sinlge point as float!") + if not isinstance(prescreening_grrho_info.get("energy", None), float): + raise TypeError("Please provide G_RRHO as float!") + if type(lowlevel_grrho_info.get("energy", None)) != float: + raise TypeError("Please provide G_RRHO as float!") + if not isinstance(lowlevel_grrho_info["range"], dict): + raise TypeError("Please input a dict with Grrho values!") + if not isinstance(lowlevel_hrrho_info.get("energy", None), float): + raise TypeError("Please provide H_RRHO as float!") + if any([type(i) != float for i in lowlevel_grrho_info.get("range").values()]): + raise TypeError("Please provide Grrho values as float!") + if not isinstance(lowlevel_hrrho_info["range"], dict): + raise TypeError("Please input a dict with Hrrho values!") + if any([type(i) != float for i in lowlevel_hrrho_info.get("range").values()]): + raise TypeError("Please provide Hrrho values as float!") + if not isinstance(prescreening_gsolv_info.get("energy"), float): + raise TypeError("Please provide Gsolv as float!") + if not isinstance(lowlevel_gsolv_info.get("energy", None), float): + raise TypeError("Please provide Gsolv as float!") + if not isinstance(lowlevel_gsolv_info.get("range"), dict): + raise TypeError("Please input a dict with Gsolv values!") + if any([type(i) != float for i in lowlevel_gsolv_info.get("range").values()]): + raise TypeError("Please provide Gsolv values as float!") + if type(highlevel_gsolv_info.get("energy", None)) != float: + raise TypeError("Please provide Gsolv as float!") + if not isinstance(highlevel_gsolv_info.get("range", None), dict): + raise TypeError("Please input a dict with Gsolv values!") + if any([type(i) != float for i in highlevel_gsolv_info.get("range")]): + raise TypeError("Please provide Gsolv values as float!") + if not isinstance(removed, bool): + raise TypeError("Please provide removed with boolean true/false.") + if any([type(i) != str for i in part_info.values()]): + raise TypeError("Please provide part_info settings as str!") + # assignment: + self.id = rank # this is the rank from the input ensemble + self.temperature_info = temperature_info # temperature for general evaluation + self.chrg = chrg + self.uhf = uhf + self.xtb_energy = xtb_energy + self.xtb_energy_unbiased = xtb_energy_unbiased + self.xtb_free_energy = xtb_free_energy + self.rel_xtb_energy = rel_xtb_energy + self.rel_xtb_free_energy = rel_xtb_free_energy + self.sym = sym + self.gi = gi + self.cheap_prescreening_gsolv_info = cheap_prescreening_gsolv_info + self.cheap_prescreening_sp_info = cheap_prescreening_sp_info + self.prescreening_sp_info = prescreening_sp_info + self.lowlevel_sp_info = lowlevel_sp_info + self.highlevel_sp_info = highlevel_sp_info + self.prescreening_grrho_info = prescreening_grrho_info + self.lowlevel_grrho_info = lowlevel_grrho_info + self.lowlevel_hrrho_info = lowlevel_hrrho_info + self.highlevel_grrho_info = highlevel_grrho_info + self.highlevel_hrrho_info = highlevel_hrrho_info + self.prescreening_gsolv_info = prescreening_gsolv_info + self.lowlevel_gsolv_info = lowlevel_gsolv_info + self.lowlevel_gsolv_compare_info = lowlevel_gsolv_compare_info + self.highlevel_gsolv_info = highlevel_gsolv_info + self.optimization_info = optimization_info + self.nmr_coupling_info = nmr_coupling_info + self.nmr_shielding_info = nmr_shielding_info + self.removed = removed + self.free_energy = free_energy + self.part_info = part_info + self.comment = comment + self.optical_rotation_info = optical_rotation_info + + def _initialize(self, attr=None): + """ + json saves keys as string. Convert some keys to float. + """ + if attr is not None: + if attr.get("range") is None: + attr["range"] = {} + else: # check if keys are float + if isinstance(attr["range"], dict): + new = {} + for key, value in attr["range"].items(): + new[float(key)] = value + attr["range"] = new + for method in attr["prev_methods"]: + if isinstance(attr["prev_methods"][method].get("range"), dict): + new = {} + for key, value in attr["prev_methods"][method]["range"].items(): + new[float(key)] = value + attr["prev_methods"][method]["range"] = new + else: + attr["prev_methods"][method]["range"] = {} + + def reset_range_info(self, trange=None): + """ + Reset all dictionaries concerned with a temperature range and + set info to not calculated. (This is needed if the temperature range was + not calculated in a previous run and is requested in a current run). + trange -> list with temperatures + """ + attributes = [ + "lowlevel_grrho_info", + "lowlevel_hrrho_info", + "lowlevel_gsolv_info", + "highlevel_gsolv_info", + ] + # reset only if not all temperatures are found which are needed in trange + for data in attributes: + reset = False + if trange is not None: + for temp in trange: + if getattr(self, data)["range"].get(temp, None) is None: + reset = True + if reset: + getattr(self, data)["info"] = "not_calculated" + # keep only value at "normal" temperature + getattr(self, data)["range"] = { + self.temperature_info["temperature"]: getattr(self, data)["energy"] + } + # END--- + + def save_prev(self, attr, method): + """ + save dictionary with all information of + previously calculated data + """ + # store data under 'prev_methods'[method] + tmp = {method: {}} + attributes = vars(MoleculeData(0)).get(attr) + if getattr(self, attr)["info"] != "not_calculated": + for key in attributes.keys(): + if key != "prev_methods": + tmp[method][key] = getattr(self, attr).get(key) + getattr(self, attr)["prev_methods"].update(tmp) + + def load_prev(self, attr, method, saveto=None): + """ + load dictionary with all information from + previously calculated data, + if not previously calculated load presets + self --> conf object + attr --> e.g. lowlevel_sp_info + method --> method identifier e.g. func/basis[sm] + saveto --> optional if desired to save data somewhere else + e.g. highlevel_sp_info + """ + attributes = vars(MoleculeData(0)).get(attr) + # check if calculated previously + if getattr(self, attr)["prev_methods"].get(method, None) is not None: + tmp = {} + for key in attributes.keys(): + if key != "prev_methods": + tmp[key] = getattr(self, attr)["prev_methods"][method].get(key) + if saveto is not None: + attr = saveto + getattr(self, attr).update(tmp) + else: + # if not calculated previously reset + for key, value in attributes.items(): + if key != "prev_methods": + getattr(self, attr)[key] = value + + def provide_runinfo(self): + """ + Write dictionary with molecule data information: + """ + runinfo = [] + for key in vars(MoleculeData(0)).keys(): + runinfo.append((key, getattr(self, key))) + return OrderedDict(runinfo) + + def calc_free_energy(self, e=None, solv=None, rrho=None, t=None, out=False): + """ + Calculate free energy for molecule either at normal temperature, + or if the temperature is not None from the range of temperatures. + if out=False free energy is written to self.free_energy + if out=True free energy is simply returned + """ + if t is None: + try: + f = 0.0 + if e is not None: + if e in ("xtb_energy", "xtb_energy_unbiased"): + f += getattr(self, e, 0.0) + else: + f += getattr(self, e, {"energy": 0.0})["energy"] + if solv is not None: + f += getattr(self, solv, {"energy": 0.0})["energy"] + if rrho is not None: + f += getattr(self, rrho, {"energy": 0.0})["energy"] + if not out: + self.free_energy = f + else: + return f + except Exception as error: + print("ERROR", error) + if not out: + self.free_energy = None + else: + return f + else: + try: + f = 0.0 + if e is not None: + if e in ("xtb_energy", "xtb_energy_unbiased"): + f += getattr(self, e, 0.0) + else: + f += getattr(self, e)["energy"] + if solv is not None: + f += getattr(self, solv)["range"].get(t, 0.0) + if rrho is not None: + f += getattr(self, rrho)["range"].get(t, 0.0) + if not out: + self.free_energy = f + else: + return f + except (Exception, KeyError) as error: + print("ERROR in _calc_free_energy: ", error) + if not out: + self.free_energy = None + else: + return f diff --git a/censo_qm/ensembledata.py b/censo_qm/ensembledata.py new file mode 100644 index 0000000..a53df7a --- /dev/null +++ b/censo_qm/ensembledata.py @@ -0,0 +1,48 @@ +class EnsembleData: + def __init__( + self, + id="ensemble_info", + filename=None, + part_info={ + "part0": None, + "part1_firstsort": None, + "part1": None, + "part2_opt": None, + "part2": None, + "part3": None, + }, + avGcorrection=None, + comment=None, + bestconf={"part0": None, "part1": None, "part2": None, "part3": None}, + nconfs_per_part={ + "starting": None, + "part0": None, + "part1_firstsort": None, + "part1": None, + "part2_opt": None, + "part2": None, + "part3": None, + }, + ): + """ + ensemble_data: Creates an object where data + concerning the entire ensemble is stored. + Input: + filename = e.g. crest_conformers.xyz + part_info --> time passed to calculate part + avGcorrection --> information of higher lying conformers + bestconf --> id of best conf per part + nconfs_per_part --> how many confs have been evaluated in each part + + """ + if avGcorrection is None: + avGcorrection = {} + if comment is None: + comment = [] + self.id = id + self.filename = filename + self.part_info = part_info + self.avGcorrection = avGcorrection + self.comment = comment + self.bestconf = bestconf + self.nconfs_per_part = nconfs_per_part diff --git a/censo_qm/inputhandling.py b/censo_qm/inputhandling.py new file mode 100755 index 0000000..a06b0a8 --- /dev/null +++ b/censo_qm/inputhandling.py @@ -0,0 +1,3440 @@ +""" +defininition of internal defaults, checking of logic for parameter combinations, +cml parsing +""" +import argparse +import shutil +import os +import json +import csv +import time +import math +import sys +from copy import deepcopy +from collections import OrderedDict +from .cfg import ( + PLENGTH, + DIGILEN, + ENVIRON, + CODING, + censo_solvent_db, + external_paths, + __version__, +) +from .utilities import frange, format_line, print + + +def cml(startup_description, options, argv=None): + """ + Process commandline arguments + """ + + parser = argparse.ArgumentParser( + description=startup_description, + formatter_class=argparse.RawDescriptionHelpFormatter, + usage=argparse.SUPPRESS, + ) + group1 = parser.add_argument_group("GENERAL SETTINGS") + group1.add_argument( + "-inp", + "--input", + dest="inp", + type=os.path.abspath, + action="store", + # default="crest_conformers.xyz", + required=False, + metavar="", + help="Input name of ensemble file: e.g. crest_conformers.xyz ", + ) + group1.add_argument( + "-nc", + "--nconf", + dest="nconf", + type=int, + action="store", + required=False, + metavar="", + help="Number of conformers which are going to be considered (max number " + "of conformers are all conformers from the input file).", + ) + group1.add_argument( + "-chrg", + "--charge", + dest="charge", + action="store", + required=False, + metavar="", + help="Charge of the investigated molecule.", + ) + group1.add_argument( + "-u", + "--unpaired", + dest="unpaired", + action="store", + required=False, + type=int, + metavar="", + help="Integer number of unpaired electrons of the investigated molecule.", + ) + group1.add_argument( + "-T", + "--temperature", + dest="temperature", + action="store", + required=False, + metavar="", + help="Temperature in Kelvin for thermostatistical evaluation.", + ) + group1.add_argument( + "-multitemp", + "--multitemp", + dest="multitemp", + choices=["on", "off"], + required=False, + metavar="", + help="Needs to be turned on if a temperature range should be evaluated" + " (flag trange). Options for multitemp are: ['on' or 'off'].", + ) + group1.add_argument( + "-trange", + "--trange", + # default=[273.15, 378.15, 5], + dest="trange", + nargs=3, + required=False, + metavar=("start", "end", "step"), + type=float, + help="specify a temperature range [start, end, step] e.g.: 250.0 300.0 10.0" + " resulting in [250.0, 260.0, 270.0, 280.0, 290.0].", + ) + group1.add_argument( + "-bhess", + "--bhess", + dest="bhess", + choices=["on", "off"], + action="store", + required=False, + metavar="", + help="Applies structure constraint to input/DFT geometry for mRRHO calcuation." + "Options are: ['on' or 'off'].", + ) + group1.add_argument( + "-consider_sym", + "---consider_sym", + dest="-consider_sym", + choices=["on", "off"], + action="store", + required=False, + metavar="", + help="Consider symmetry in mRRHO calcuation (based on desy xtb threshold)." + "Options are: ['on' or 'off'].", + ) + group1.add_argument( + "-rmsdbias", + "--rmsdbias", + dest="rmsdbias", + choices=["on", "off"], + action="store", + required=False, + metavar="", + help="Applies constraint to rmsdpot.xyz to be consistent to CREST." + "Options are: ['on' or 'off'].", + ) + group1.add_argument( + "-sm_rrho", + "--sm_rrho", + dest="sm_rrho", + choices=["gbsa", "alpb"], + action="store", + required=False, + metavar="", + help="Solvation model used in xTB GmRRHO calculation. Applied if not in gas-phase. " + "Options are 'gbsa' or 'alpb'.", + ) + group1.add_argument( + "-evaluate_rrho", + "--evaluate_rrho", + dest="evaluate_rrho", + action="store", + choices=["on", "off"], + required=False, + metavar="", + help="Evaluate mRRHO contribution. Options: on or off.", + ) + group1.add_argument( + "-func", + "--functional", + dest="func", + choices=options.value_options["func"], + action="store", + required=False, + metavar="", + help="Functional for geometry optimization (used in part2) and " + "single-points in part1", + ) + group1.add_argument( + "-basis", + "--basis", + dest="basis", + action="store", + required=False, + metavar="", + help="Basis set employed together with the functional (func) for the " + "low level single point in part1 und optimization in part2.", + ) + group1.add_argument( + "-checkinput", + "--checkinput", + dest="checkinput", + action="store_true", + required=False, + help="Option to check if all necessary information for the ENSO " + "calculation are provided and check if certain setting combinations " + "make sence. Option to choose from : ['on' or 'off']", + ) + group1.add_argument( + "-solvent", + "--solvent", + dest="solvent", + choices=options.value_options["solvent"], + metavar="", + action="store", + required=False, + help="Solvent the molecule is solvated in, available solvents " + "are: {}. They can be extended in the " + "file ~/.censo_assets/censo_solvents.json .".format( + options.value_options["solvent"] + ), + ) + group1.add_argument( + "-prog", + "--prog", + choices=options.value_options["prog"], + dest="prog", + required=False, + metavar="", + help="QM-program used in part1 and part2 either 'orca' or 'tm'.", + ) + group1.add_argument( + "-prog_rrho", + "--prog_rrho", + choices=options.value_options["prog_rrho"], + dest="prog_rrho", + required=False, + metavar="", + help="QM-program for mRRHO contribution in part1 2 and 3, either 'xtb' or 'prog'.", + ) + group1.add_argument( + "-crestcheck", + "--crestcheck", + dest="crestcheck", + choices=["on", "off"], + action="store", + required=False, + metavar="", + help="Option to sort out conformers after DFT optimization which CREST " + "identifies as identical or rotamers of each other. \nThe identification/" + "analysis is always performed, but the removal of conformers has to " + "be the choice of the user. Options are: ['on' or 'off']", + ) + group1.add_argument( + "-check", + "--check", + dest="check", + choices=["on", "off"], + action="store", + required=False, + help="Option to terminate the ENSO-run if too many calculations/preparation" + " steps fail. Options are: ['on' or 'off'].", + ) + group1.add_argument( + "-version", + "--version", + dest="version", + action="store_true", + required=False, + help="Print CENSO version and exit.", + ) + group1.add_argument( + "-part3only", + "--part3only", + dest="part3only", + required=False, + action="store_true", + help="Option to turn off part1 and part2", + ) + group2 = parser.add_argument_group("SPECIAL RUN MODES") + group2.add_argument( + "-logK", + "--logK", + action="store_true", + required=False, + default=False, + help="Automatically set required settings for logK calculation. " + "Of course charge, solvent etc. has to be set by the user.", + ) + group10 = parser.add_argument_group("CRE CHEAP-PRESCREENING - PART0") + group10.add_argument( + "-part0", + "--part0", + choices=["on", "off"], + dest="part0", + action="store", + required=False, + metavar="", + help="Option to turn the CHEAP prescreening evaluation (part0) which " + "improves description of ΔE 'on' or 'off'.", + ) + group10.add_argument( + "-func0", + "--func0", + dest="func0", + choices=options.value_options["func0"], + action="store", + required=False, + metavar="", + help="Functional for fast single-point (used in part0)", + ) + group10.add_argument( + "-basis0", + "--basis0", + dest="basis0", + action="store", + required=False, + metavar="", + help="Basis set employed together with the functional (func0) for the " + "fast single point calculation in part0.", + ) + group10.add_argument( + "-part0_gfnv", + "--part0_gfnv", + dest="part0_gfnv", + choices=options.value_options["part0_gfnv"], + metavar="", + action="store", + required=False, + help="GFNn-xTB version employed for calculating the gas phase GFNn-xTB " + "single point in part0. " + f"Allowed values are [{', '.join(options.value_options['part0_gfnv'])}]", + ) + group10.add_argument( + "-part0_threshold", + "-thrpart0", + "--thresholdpart0", + dest="part0_threshold", + metavar="", + action="store", + required=False, + help=( + "Threshold in kcal/mol. All conformers in part0 (cheap single-point)" + " with a relativ energy below the threshold are considered for part1." + ), + ) + + group3 = parser.add_argument_group("CRE PRESCREENING - PART1") + group3.add_argument( + "-part1", + "--part1", + choices=["on", "off"], + dest="part1", + action="store", + required=False, + metavar="", + help="Option to turn the prescreening evaluation (part1) 'on' or 'off'.", + ) + group3.add_argument( + "-smgsolv1", + "--smgsolv1", + choices=options.value_options["smgsolv1"], + dest="smgsolv1", + action="store", + required=False, + metavar="", + help="Solvent model for the Gsolv evaluation in part1. This can either be" + " an implicit solvation or an additive solvation model. " + f"Allowed values are [{', '.join(options.value_options['smgsolv1'])}]", + ) + group3.add_argument( + "-part1_gfnv", + "--part1_gfnv", + dest="part1_gfnv", + choices=options.value_options["part1_gfnv"], + metavar="", + action="store", + required=False, + help="GFNn-xTB version employed for calculating the " + "mRRHO contribution in part1. " + f"Allowed values are [{', '.join(options.value_options['part1_gfnv'])}]", + ) + group3.add_argument( + "-thrpart1", + "--thresholdpart1", + "-part1_threshold", + dest="part1_threshold", + metavar="", + action="store", + required=False, + help=( + "Threshold in kcal/mol. All conformers in part1 (lax_single-point)" + " with a relativ energy below the threshold are considered for part2." + ), + ) + + group4 = parser.add_argument_group("CRE OPTIMIZATION - PART2") + group4.add_argument( + "-part2", + "--part2", + choices=["on", "off"], + dest="part2", + action="store", + required=False, + metavar="", + help="Option to turn the full optimization (part2) 'on' or 'off'.", + ) + group4.add_argument( + "-sm2", + "--solventmodel2", + choices=options.value_options.get("sm2"), + dest="sm2", + action="store", + required=False, + metavar="", + help="Solvent model employed during the geometry optimization part2." + "The solvent model sm2 is not used for Gsolv evaluation, but for the " + "implicit effect on a property (e.g. the optimization).", + ) + group4.add_argument( + "-smgsolv2", + "--smgsolv2", + choices=options.value_options["smgsolv2"], + dest="smgsolv2", + action="store", + required=False, + metavar="", + help="Solvent model for the Gsolv calculation in part2. Either the solvent" + " model of the optimization (sm) or an additive solvation model. " + f"Allowed values are [{', '.join(options.value_options['smgsolv2'])}]", + ) + group4.add_argument( + "-part2_gfnv", + "--part2_gfnv", + dest="part2_gfnv", + choices=options.value_options["part2_gfnv"], + metavar="", + action="store", + required=False, + help="GFNn-xTB version employed for calculating the " + "mRRHO contribution in part2. " + f"Allowed values are [{', '.join(options.value_options['part2_gfnv'])}]", + ) + group4.add_argument( + "-ancopt", + choices=["on", "off"], + dest="ancopt", + required=False, + metavar="", + help="Option to use xtb as driver for the xTB-optimizer in part2.", + ) + group4.add_argument( + "-opt_spearman", + choices=["on", "off"], + dest="opt_spearman", + required=False, + metavar="", + help="Option to use an optimizer which checks if the hypersurface of DFT and" + "xTB is parallel and optimizes mainly low lying conformers", + ) + group4.add_argument( + "-optlevel2", + "--optlevel2", + choices=options.value_options["optlevel2"], + dest="optlevel2", + default=None, + required=False, + metavar="", + help="Option to set the optlevel in part2, only if optimizing with the xTB-optimizer!" + "Allowed values are " + ", ".join(options.value_options["optlevel2"]), + ) + group4.add_argument( + "-optcycles", + "--optcycles", + dest="optcycles", + action="store", + required=False, + type=int, + metavar="", + help="number of cycles in ensemble optimizer.", + ) + group4.add_argument( + "-hlow", + "--hlow", + dest="hlow", + action="store", + required=False, + type=float, + metavar="", + help="Lowest force constant in ANC generation (real), used by xTB-optimizer.", + ) + group4.add_argument( + "-spearmanthr", + "-spearmanthr", + dest="spearmanthr", + action="store", + required=False, + metavar="", + help="Value between -1 and 1 for the spearman correlation coeffient threshold", + ) + group4.add_argument( + "-opt_limit", + "--opt_limit", + dest="opt_limit", + action="store", + required=False, + metavar="", + help=( + "Lower limit Threshold in kcal/mol. If the GFNn and DFT hypersurfaces are" + "assumed parallel, the conformers above the threshold are removed and not optimized further." + "The conformers in part2 with a relativ free energy below the " + "threshold are fully optimized." + ), + ) + group4.add_argument( + "-thrpart2", + "--thresholdpart2", + dest="part2_threshold", + action="store", + required=False, + metavar="", + help=( + "Boltzmann population sum threshold for part2 in %%. The conformers with " + "the highest Boltzmann weigths are summed up until the threshold is reached." + "E.g. all conformers up to a Boltzmann population of 90 %% are considered." + 'Example usage: "-thrpart2 99" --> considers a population of 99 %%' + ), + ) + group4.add_argument( + "-radsize", + "--radsize", + dest="radsize", + action="store", + required=False, + metavar="", + type=int, + help=("Radsize used in optimization and only for r2scan-3c!"), + ) + group5 = parser.add_argument_group("CRE REFINEMENT - PART3") + group5.add_argument( + "-part3", + "--part3", + choices=["on", "off"], + dest="part3", + action="store", + required=False, + metavar="", + help="Option to turn the high level free energy evaluation (part3) 'on' or 'off'.", + ) + group5.add_argument( + "-prog3", + "--prog3", + choices=options.value_options["prog3"], + dest="prog3", + required=False, + metavar="", + help="QM-program used in part3 either 'orca' or 'tm'.", + ) + group5.add_argument( + "-func3", + "--functionalpart3", + dest="func3", + # choices=func3, + action="store", + required=False, + metavar="", + help="Functional for the COSMO-RS calculation, use functional " + "names as recognized by cefine.", + ) + group5.add_argument( + "-basis3", + "--basis3", + dest="basis3", + action="store", + required=False, + metavar="", + help="Basis set employed together with the functional (func3) for the " + "high level single point in part3.", + ) + group5.add_argument( + "-smgsolv3", + "--smgsolv3", + choices=options.value_options["smgsolv3"], + dest="smgsolv3", + action="store", + required=False, + metavar="", + help="Solvent model for the Gsolv calculation in part3. Either the solvent" + " model of the optimization (sm2) or an additive solvation model.", + ) + group5.add_argument( + "-part3_gfnv", + "--part3_gfnv", + dest="part3_gfnv", + choices=options.value_options["part3_gfnv"], + metavar="", + action="store", + required=False, + help="GFNn-xTB version employed for calculating the " + "mRRHO contribution in part3. " + f"Allowed values are [{', '.join(options.value_options['part3_gfnv'])}]", + ) + group5.add_argument( + "-thrpart3", + "--thresholdpart3", + dest="part3_threshold", + action="store", + required=False, + metavar="", + help=( + "Boltzmann population sum threshold for part3 in %%. The conformers with " + "the highest Boltzmann weigths are summed up until the threshold is reached." + "E.g. all conformers up to a Boltzmann population of 90 %% are considered" + 'Example usage: "-thrpart3 99" --> considers a population of 99 %%' + ), + ) + group6 = parser.add_argument_group("NMR Mode") + group6.add_argument( + "-part4", + "--part4", + choices=["on", "off"], + dest="part4", + action="store", + required=False, + metavar="", + help="Option to turn the NMR property calculation mode (part4) 'on' or 'off'.", + ) + group6.add_argument( + "-couplings", + "--couplings", + dest="couplings", + required=False, + choices=["on", "off"], + metavar="", + help="Option to run coupling constant calculations. Options are 'on' or 'off'.", + ) + group6.add_argument( + "-prog4J", + "--prog4J", + # choices=options.value_options["prog"], + dest="prog4_j", + required=False, + metavar="", + help="QM-program for the calculation of coupling constants.", + ) + group6.add_argument( + "-funcJ", + "--funcJ", + dest="func_j", + # choices=func3, + action="store", + required=False, + metavar="", + help="Functional for the coupling constant calculation.", + ) + group6.add_argument( + "-basisJ", + "--basisJ", + dest="basis_j", + action="store", + required=False, + metavar="", + help="Basis set for the calculation of coupling constants.", + ) + group6.add_argument( + "-sm4_j", + "--sm4_j", + dest="sm4_j", + action="store", + required=False, + metavar="", + help="Solvation model used in the coupling constant calculation.", + ) + group6.add_argument( + "-shieldings", + "--shieldings", + dest="shieldings", + required=False, + choices=["on", "off"], + metavar="", + help="Option to run shielding constant calculations. Options are 'on' or 'off'.", + ) + group6.add_argument( + "-prog4S", + "--prog4S", + # choices=options.value_options["prog"], + dest="prog4_s", + required=False, + metavar="", + help="QM-program for the calculation of shielding constants.", + ) + group6.add_argument( + "-funcS", + "--funcS", + dest="func_s", + # choices=func3, + action="store", + required=False, + metavar="", + help="Functional for shielding constant calculation.", + ) + group6.add_argument( + "-basisS", + "--basisS", + dest="basis_s", + action="store", + required=False, + metavar="", + help="Basis set for the calculation of shielding constants.", + ) + group6.add_argument( + "-sm4_s", + "--sm4_s", + dest="sm4_s", + action="store", + required=False, + metavar="", + help="Solvation model used in the shielding constant calculation.", + ) + group6.add_argument( + "-hactive", + "--hactive", + # choices=options.value_options["prog"], + dest="h_active", + required=False, + metavar="", + help="Investigates hydrogen nuclei in coupling and shielding calculations.", + ) + group6.add_argument( + "-cactive", + "--cactive", + # choices=options.value_options["prog"], + dest="c_active", + required=False, + metavar="", + help="Investigates carbon nuclei in coupling and shielding calculations.", + ) + group6.add_argument( + "-factive", + "--factive", + # choices=options.value_options["prog"], + dest="f_active", + required=False, + metavar="", + help="Investigates fluorine nuclei in coupling and shielding calculations.", + ) + group6.add_argument( + "-siactive", + "--siactive", + # choices=options.value_options["prog"], + dest="si_active", + required=False, + metavar="", + help="Investigates silicon nuclei in coupling and shielding calculations.", + ) + group6.add_argument( + "-pactive", + "--pactive", + # choices=options.value_options["prog"], + dest="p_active", + required=False, + metavar="", + help="Investigates phosophorus nuclei in coupling and shielding calculations.", + ) + group9 = parser.add_argument_group("OPTICAL ROTATION MODE") + group9.add_argument( + "-OR", + "--OR", + "-part5", + choices=["on", "off"], + action="store", + dest="optical_rotation", + required=False, + help="Do optical rotation calculation.", + ) + group9.add_argument( + "-funcOR", + "--funcOR", + dest="func_or", + # choices=func_or, + action="store", + required=False, + metavar="", + help="Functional for optical rotation calculation.", + ) + group9.add_argument( + "-funcOR_SCF", + "--funcOR_SCF", + dest="func_or_scf", + # choices=func_or, + action="store", + required=False, + metavar="", + help="Functional used in SCF for optical rotation calculation.", + ) + group9.add_argument( + "-basisOR", + "--basisOR", + dest="basis_or", + # choices=func_or, + action="store", + required=False, + metavar="", + help="Basis set for optical rotation calculation.", + ) + group9.add_argument( + "-freqOR", + "--freqOR", + dest="freq_or", + nargs="*", + required=False, + type=float, + metavar="", + help="Frequencies to evaluate specific rotation at in nm. E.g. 589 " + "Or 589 700 to evaluate at 598 nm and 700 nm.", + ) + + group7 = parser.add_argument_group("OPTIONS FOR PARALLEL CALCULATIONS") + group7.add_argument( + "-O", + "--omp", + dest="omp", + type=int, + action="store", + metavar="", + help="Number of cores each thread can use. E.g. (maxthreads) 5 threads " + "with each (omp) 4 cores --> 20 cores need to be available on the machine.", + ) + group7.add_argument( + "-P", + "--maxthreads", + dest="maxthreads", + type=int, + action="store", + metavar="", + help="Number of threads during the ENSO calculation. E.g. (maxthreads) 5" + " threads with each (omp) 4 cores --> 20 cores need to be available on " + "the machine.", + ) + group8 = parser.add_argument_group("CREATION/DELETION OF FILES") + group8.add_argument( + "--debug", + "-debug", + dest="debug", + action="store_true", + default=False, + help=argparse.SUPPRESS, + ) + group8.add_argument( + "--restart", + "-restart", + dest="restart", + action="store_true", + default=False, + help=argparse.SUPPRESS, + ) + group8.add_argument( + "--cleanup", + "-cleanup", + dest="cleanup", + action="store_true", + default=False, + help="Delete unneeded files from current working directory.", + ) + group8.add_argument( + "--cleanup_all", + "-cleanup_all", + dest="cleanup_all", + action="store_true", + default=False, + help="Delete all unneeded files from current working directory. " + "Stronger than -cleanup !", + ) + group8.add_argument( + "-newconfig", + "-write_ensorc", + "--write_ensorc", + dest="writeconfig", + default=False, + action="store_true", + required=False, + help="Write new configuration file , which is placed into the current " + "directory.", + ) + + args = parser.parse_args(argv) + + # apply logK settings but don't override user input! + if args.logK: + logk_settings = OrderedDict( + [ + # general/cross-over settings + ("multitemp", "on"), + ("evaluate_rrho", "on"), + ("bhess", "on"), + ("crestcheck", "on"), + # part 1 + ("part1", "on"), + ("smgsolv1", "cosmors"), + # part2 + ("part2", "on"), + ("ancopt", "on"), + ("smgsolv2", "cosmors"), + ("opt_spearman", "on"), + ("spearmanthr", -4), + # part3 + ("smgsolv3", "cosmors"), + ] + ) + for key in logk_settings.keys(): + if not getattr(args, key): + setattr(args, key, logk_settings[key]) + # --------------------------end logK---------------------------------------- + if args.part3only: + setattr(args, "part0", "off") + setattr(args, "part1", "off") + setattr(args, "part2", "off") + return args + + +class internal_settings: + """ + All options are saved here. + """ + + # key in .censorc corresponds to name in cml + key_args_dict = { + "nconf": "nconf", + "charge": "charge", + "unpaired": "unpaired", + "solvent": "solvent", + "prog": "prog", + "ancopt": "ancopt", + "opt_spearman": "opt_spearman", + "evaluate_rrho": "evaluate_rrho", + "consider_sym": "consider_sym", + "prog_rrho": "prog_rrho", + "part0_gfnv": "part0_gfnv", + "part1_gfnv": "part1_gfnv", + "part2_gfnv": "part2_gfnv", + "part3_gfnv": "part3_gfnv", + "temperature": "temperature", + "multitemp": "multitemp", + "trange": "trange", + "prog3": "prog3", + "prog4_j": "prog4_j", + "prog4_s": "prog4_s", + "part0": "part0", + "part1": "part1", + "part2": "part2", + "part3": "part3", + "part4": "part4", + "func0": "func0", + "func": "func", + "basis0": "basis0", + "basis": "basis", + "func3": "func3", + "basis3": "basis3", + "couplings": "couplings", + "progJ": "prog4_j", + "funcJ": "func_j", + "basisJ": "basis_j", + "shieldings": "shieldings", + "progS": "prog4_s", + "funcS": "func_s", + "basisS": "basis_s", + "part0_threshold": "part0_threshold", + "part1_threshold": "part1_threshold", + "part2_threshold": "part2_threshold", + "part3_threshold": "part3_threshold", + "opt_limit": "opt_limit", + "smgsolv1": "smgsolv1", + "sm2": "sm2", + "smgsolv2": "smgsolv2", + "smgsolv3": "smgsolv3", + "sm4J": "sm4_j", + "sm4S": "sm4_s", + "check": "check", + "crestcheck": "crestcheck", + "maxthreads": "maxthreads", + "omp": "omp", + "1H_active": "h_active", + "13C_active": "c_active", + "19F_active": "f_active", + "31P_active": "p_active", + "29Si_active": "si_active", + "resonance_frequency": "resonance_frequency", + "reference_1H": "h_ref", + "reference_13C": "c_ref", + "reference_31P": "p_ref", + "reference_19F": "f_ref", + "reference_29Si": "si_ref", + "bhess": "bhess", + "sm_rrho": "sm_rrho", + "optcycles": "optcycles", + "optlevel2": "optlevel2", + "spearmanthr": "spearmanthr", + "optical_rotation": "optical_rotation", + "radsize": "radsize", + "frequency_optical_rot": "freq_or", + "funcOR": "func_or", + "basisOR": "basis_or", + "funcOR_SCF": "func_or_scf", + "hlow": "hlow", + "rmsdbias": "rmsdbias", + } + knownbasissets3 = [ + "SVP", + "SV(P)", + "TZVP", + "TZVPP", + "QZVP", + "QZVPP", + "def2-SV(P)", + "def2-mSVP", + "def2-SVP", + "def2-TZVP", + "def2-TZVPP", + "def2-mTZVP", + "def2-mTZVPP", + "def2-TZVPD", + "def-SVP", + "def-SV(P)", + "def2-QZVP", + "DZ", + "QZV", + "cc-pVDZ", + "cc-pVTZ", + "cc-pVQZ", + "cc-pV5Z", + "aug-cc-pVDZ", + "aug-cc-pVTZ", + "aug-cc-pVQZ", + "aug-cc-pV5Z", + "def2-QZVPP", + "minix", + ] + # information on functionals: + composite_method_basis = { + "pbeh-3c": "def2-mSVP", + "b97-3c": "def2-mTZVP", + "b973c": "def2-mTZVP", + "hf3c": "minix", + "hf-3c": "minix", + "r2scan-3c": "def2-mTZVPP", + } + composite_dfa = ( + "pbeh-3c", + "b97-3c", + "b973c", + "hf-3c", + "hf3c", + "r2scan-3c", + ) # + hf3c ; ) + gga_dfa = ("tpss", "pbe", "kt2") + hybrid_dfa = ( + "pbe0", + "pw6b95", + "wb97x-d3", + "cam-b3lyp", + "b3-lyp", + "pbeh-3c", + "m06x", + "bh-lyp", + "tpssh", + ) + dh_dfa = ("dsd-blyp",) + + knownbasissetsJ = knownbasissets3 + ["pcJ-0", "pcJ-1", "pcJ-2"] + knownbasissetsS = knownbasissets3 + [ + "pcSseg-0", + "pcSseg-1", + "pcSseg-2", + "pcSseg-3", + "x2c-SVPall-s", + "x2c-TZVPall-s", + ] + func_orca = ["pbeh-3c", "b97-3c", "tpss", "b97-d3", "pbe"] + func_tm = ["pbeh-3c", "b97-3c", "tpss", "r2scan-3c", "b97-d", "pbe"] + func3_orca = ["pw6b95", "pbe0", "wb97x", "dsd-blyp"] + func3_tm = ["pw6b95", "pbe0", "b97-d3", "r2scan-3c"] + func_j_tm = ["tpss", "pbe0", "pbeh-3c"] + func_j_orca = ["tpss", "pbe0", "pbeh-3c"] + func_s_tm = ["tpss", "pbe0", "pbeh-3c", "kt2"] + func_s_orca = ["tpss", "pbe0", "dsd-blyp", "pbeh-3c", "kt2"] + impgfnv = ["gfn1", "gfn2", "gfnff"] + tmp_smd_solvents = [ + "1,1,1-TRICHLOROETHANE", + "1,1,2-TRICHLOROETHANE", + "1,2,4-TRIMETHYLBENZENE", + "1,2-DIBROMOETHANE", + "1,2-DICHLOROETHANE", + "1,2-ETHANEDIOL", + "1,4-DIOXANE", + "1-BROMO-2-METHYLPROPANE", + "1-BROMOOCTANE", + "1-BROMOPENTANE", + "1-BROMOPROPANE", + "1-BUTANOL", + "1-CHLOROHEXANE", + "1-CHLOROPENTANE", + "1-CHLOROPROPANE", + "1-DECANOL", + "1-FLUOROOCTANE", + "1-HEPTANOL", + "1-HEXANOL", + "1-HEXENE", + "1-HEXYNE", + "1-IODOBUTANE", + "1-IODOHEXADECANE", + "1-IODOPENTANE", + "1-IODOPROPANE", + "1-NITROPROPANE", + "1-NONANOL", + "1-OCTANOL", + "1-PENTANOL", + "1-PENTENE", + "1-PROPANOL", + "2,2,2-TRIFLUOROETHANOL", + "2,2,4-TRIMETHYLPENTANE", + "2,4-DIMETHYLPENTANE", + "2,4-DIMETHYLPYRIDINE", + "2,6-DIMETHYLPYRIDINE", + "2-BROMOPROPANE", + "2-BUTANOL", + "2-CHLOROBUTANE", + "2-HEPTANONE", + "2-HEXANONE", + "2-METHOXYETHANOL", + "2-METHYL-1-PROPANOL", + "2-METHYL-2-PROPANOL", + "2-METHYLPENTANE", + "2-METHYLPYRIDINE", + "2-NITROPROPANE", + "2-OCTANONE", + "2-PENTANONE", + "2-PROPANOL", + "2-PROPEN-1-OL", + "E-2-PENTENE", + "3-METHYLPYRIDINE", + "3-PENTANONE", + "4-HEPTANONE", + "4-METHYL-2-PENTANONE", + "4-METHYLPYRIDINE", + "5-NONANONE", + "ACETIC ACID", + "ACETONE", + "ACETONITRILE", + "ACETOPHENONE", + "ANILINE", + "ANISOLE", + "BENZALDEHYDE", + "BENZENE", + "BENZONITRILE", + "BENZYL ALCOHOL", + "BROMOBENZENE", + "BROMOETHANE", + "BROMOFORM", + "BUTANAL", + "BUTANOIC ACID", + "BUTANONE", + "BUTANONITRILE", + "BUTYL ETHANOATE", + "BUTYLAMINE", + "N-BUTYLBENZENE", + "SEC-BUTYLBENZENE", + "TERT-BUTYLBENZENE", + "CARBON DISULFIDE", + "CARBON TETRACHLORIDE", + "CHLOROBENZENE", + "CHLOROFORM", + "A-CHLOROTOLUENE", + "O-CHLOROTOLUENE", + "M-CRESOL", + "O-CRESOL", + "CYCLOHEXANE", + "CYCLOHEXANONE", + "MeCN", + "CCl4", + "CYCLOPENTANE", + "CYCLOPENTANOL", + "CYCLOPENTANONE", + "DECALIN (CIS/TRANS MIXTURE)", + "CIS-DECALIN", + "N-DECANE", + "DIBROMOMETHANE", + "DIBUTYLETHER", + "O-DICHLOROBENZENE", + "E-1,2-DICHLOROETHENE", + "Z-1,2-DICHLOROETHENE", + "DICHLOROMETHANE", + "DIETHYL ETHER", + "DIETHYL SULFIDE", + "DIETHYLAMINE", + "DIIODOMETHANE", + "DIISOPROPYL ETHER", + "CIS-1,2-DIMETHYLCYCLOHEXANE", + "DIMETHYL DISULFIDE", + "N,N-DIMETHYLACETAMIDE", + "N,N-DIMETHYLFORMAMIDE", + "DIMETHYLSULFOXIDE", + "DIPHENYLETHER", + "DIPROPYLAMINE", + "N-DODECANE", + "ETHANETHIOL", + "ETHANOL", + "ETHYL ETHANOATE", + "ETHYL METHANOATE", + "ETHYL PHENYL ETHER", + "ETHYLBENZENE", + "FLUOROBENZENE", + "FORMAMIDE", + "FORMIC ACID", + "N-HEPTANE", + "N-HEXADECANE", + "N-HEXANE", + "HEXANOIC ACID", + "IODOBENZENE", + "IODOETHANE", + "IODOMETHANE", + "ISOPROPYLBENZENE", + "P-ISOPROPYLTOLUENE", + "MESITYLENE", + "METHANOL", + "METHYL BENZOATE", + "METHYL BUTANOATE", + "METHYL ETHANOATE", + "METHYL METHANOATE", + "METHYL PROPANOATE", + "N-METHYLANILINE", + "METHYLCYCLOHEXANE", + "N-METHYLFORMAMIDE", + "NITROBENZENE", + "NITROETHANE", + "NITROMETHANE", + "O-NITROTOLUENE", + "N-NONANE", + "N-OCTANE", + "N-PENTADECANE", + "PENTANAL", + "N-PENTANE", + "PENTANOIC ACID", + "PENTYL ETHANOATE", + "PENTYLAMINE", + "PERFLUOROBENZENE", + "PROPANAL", + "PROPANOIC ACID", + "PROPANONITRILE", + "PROPYL ETHANOATE", + "PROPYLAMINE", + "PYRIDINE", + "TETRACHLOROETHENE", + "TETRAHYDROFURAN", + "TETRAHYDROTHIOPHENE-S,S-DIOXIDE", + "TETRALIN", + "THIOPHENE", + "THIOPHENOL", + "TOLUENE", + "TRANS-DECALIN", + "TRIBUTYLPHOSPHATE", + "TRICHLOROETHENE", + "TRIETHYLAMINE", + "N-UNDECANE", + "WATER", + "XYLENE (MIXTURE)", + "M-XYLENE", + "O-XYLENE", + "P-XYLENE", + "DMF", + "DMSO", + "PhNO2", + "MeNO2", + "THF", + ] + solvents_smd = [i.lower() for i in tmp_smd_solvents] + solvents_xtb = [ + "acetone", + "acetonitrile", + "aniline", + "benzaldehyde", + "benzene", + "chcl3", + "ch2cl2", + "ccl4", + "cs2", + "dioxane", + "dmf", + "dmso", + "ether", + "ethylacetate", + "furane", + "hexadecane", + "hexane", + "h2o", + "water", + "methanol", + "nitromethane", + "thf", + "toluene", + "octanol", + "woctanol", + "phenol", + ] + solvents_cpcm = [ + "water", + "acetone", + "acetonitrile", + "ammonia", + "benzene", + "chloroform", + "ch2cl2", + "ccl4", + "cyclohexane", + "dmf", + "dmso", + "ethanol", + "hexane", + "methanol", + "octanol", + "pyridine", + "thf", + "toluene", + ] + solvents_cosmors = [ + "propanone_c0", + "chcl3_c0", + "acetonitrile_c0", + "ch2cl2_c0", + "dimethylsulfoxide_c0", + "h2o_c0", + "methanol_c0", + "thf_c0", + "toluene_c0", + "1-octanol_c0", + "woctanol", # this is a mixture and treated differently + "n-hexadecane_c0", + "dimethylformamide_c0", + "aniline_c0", + "cyclohexane_c0", + "ccl4_c0", + "diethylether_c0", + "ethanol_c0", + "hexane_c0", + "nitromethane_c0", + "benzaldehyde_c0", + "benzene_c0", + "cs2_c0", + "dioxane_c0", + "ethylacetate_c0", + "furane_c0", + "phenol_c0", + ] + + # only using the dielectric constant (DC) for cosmo + + # dcosmorsfile name = e.g. acetonitrile + '_25.pot' + solvents_dcosmors = [ + "acetonitrile", + "aniline", + "benzene", + "ccl4", + "chcl3", + "cyclohexane", + "diethylether", + "dimethylsulfoxide", + "ethanol", + "h2o", + "hexadecane", + "hexane", + "methanol", + "nitromethane", + "octanol", + "propanone", + "thf", + "toluene", + "wet-octanol", + ] + + smgsolv_1 = ["cosmors", "cosmors-fine", "gbsa_gsolv", "alpb_gsolv", "smd_gsolv"] + sm2_tm = ["cosmo", "dcosmors"] + sm2_orca = ["cpcm", "smd"] + smgsolv_2 = ["cosmors", "cosmors-fine", "gbsa_gsolv", "alpb_gsolv", "smd_gsolv"] + smgsolv3_tm = ["cosmo", "dcosmors"] + smgsolv3_orca = ["cpcm", "smd"] + smgsolv_3 = ["cosmors", "cosmors-fine", "gbsa_gsolv", "alpb_gsolv", "smd_gsolv"] + sm4_j_tm = ["cosmo", "dcosmors"] + sm4_s_tm = ["cosmo", "dcosmors"] + sm4_j_orca = ["cpcm", "smd"] + sm4_s_orca = ["cpcm", "smd"] + + imphref = ["TMS"] + impcref = ["TMS"] + impfref = ["CFCl3"] + imppref = ["TMP", "PH3"] + impsiref = ["TMS"] + + func_basis_default = { + "pbeh-3c": "def2-mSVP", + "b97-3c": "def2-mTZVP", + "b973c": "def2-mTZVP", + "tpss": "def2-TZVP", + "r2scan-3c": "def2-mTZVPP", + "hf-3c": "minix", + "hf3c": "minix", + } + + def __init__(self): + self.impfunc = list(set(self.func_orca + self.func_tm)) + self.impfunc3 = list(set(self.func3_orca + self.func3_tm)) + self.impfunc_j = list(set(self.func_j_orca + self.func_j_tm)) + self.impfunc_s = list(set(self.func_s_orca + self.func_s_tm)) + self.impsm2 = list(set(self.sm2_orca + self.sm2_tm + ["default"])) + self.impsmgsolv1 = list( + set(self.sm2_orca + self.sm2_tm + self.smgsolv_2 + ["sm2"]) + ) + self.impsmgsolv2 = list( + set(self.sm2_orca + self.sm2_tm + self.smgsolv_2 + ["sm2"]) + ) + self.impsmgsolv3 = list( + set(self.sm2_orca + self.sm2_tm + self.smgsolv_2 + ["sm2"]) + ) + self.impsm4_j = list(set(self.sm4_j_orca + self.sm4_j_tm)) + self.impsm4_s = list(set(self.sm4_s_orca + self.sm4_s_tm)) + + self.defaults_refine_ensemble_general = [ + # general settings + ("nconf", {"default": None, "type": int}), + ("charge", {"default": 0, "type": int}), + ("unpaired", {"default": 0, "type": int}), + ("solvent", {"default": "gas", "type": str}), + ("prog_rrho", {"default": "xtb", "type": str}), + ("temperature", {"default": 298.15, "type": float}), + ("trange", {"default": [273.15, 378.15, 5], "type": list}), + ("multitemp", {"default": True, "type": bool}), + ("evaluate_rrho", {"default": True, "type": bool}), + ("consider_sym", {"default": False, "type": bool}), + ("bhess", {"default": True, "type": bool}), + ("rmsdbias", {"default": False, "type": bool}), + ("sm_rrho", {"default": "alpb", "type": str}), + ("check", {"default": True, "type": bool}), + ("prog", {"default": "tm", "type": str}), + ("func", {"default": "r2scan-3c", "type": str}), + ("basis", {"default": "automatic", "type": str}), + ("maxthreads", {"default": 1, "type": int}), + ("omp", {"default": 1, "type": int}), + ] + self.defaults_refine_ensemble_part0 = [ + # part0 + ("part0", {"default": True, "type": bool}), + ("func0", {"default": "b97-d", "type": str}), + ("basis0", {"default": "def2-SV(P)", "type": str}), + ("part0_gfnv", {"default": "gfn2", "type": str}), + ("part0_threshold", {"default": 4.0, "type": float}), + ] + self.defaults_refine_ensemble_part1 = [ + # part1 + ("part1", {"default": True, "type": bool}), + ("smgsolv1", {"default": "cosmors", "type": str}), # previously sm2 + ("part1_gfnv", {"default": "gfn2", "type": str}), + ("part1_threshold", {"default": 3.5, "type": float}), + ] + self.defaults_refine_ensemble_part2 = [ + # part2 + ("part2", {"default": True, "type": bool}), + ("opt_limit", {"default": 2.5, "type": float}), + ("sm2", {"default": "default", "type": str}), + ("smgsolv2", {"default": "cosmors", "type": str}), # previously sm2 + ("part2_gfnv", {"default": "gfn2", "type": str}), + ("ancopt", {"default": True, "type": bool}), + ("hlow", {"default": 0.01, "type": float}), + ("opt_spearman", {"default": True, "type": bool}), + ("part2_threshold", {"default": 99, "type": float}), + ("optlevel2", {"default": "automatic", "type": str}), + ("optcycles", {"default": 8, "type": int}), + ("spearmanthr", {"default": -4.0, "type": float}), + ("radsize", {"default": 10, "type": int}), + ("crestcheck", {"default": False, "type": bool}), + ] + self.defaults_refine_ensemble_part3 = [ + # part3 + ("part3", {"default": False, "type": bool}), + ("prog3", {"default": "prog", "type": str}), + ("func3", {"default": "pw6b95", "type": str}), # previously b97-d + ("basis3", {"default": "def2-TZVPD", "type": str}), + ("smgsolv3", {"default": "cosmors", "type": str}), # previously sm2 + ("part3_gfnv", {"default": "gfn2 ", "type": str}), + ("part3_threshold", {"default": 99, "type": float}), + ] + self.defaults_nmrprop_part4 = [ + # part4 + ("part4", {"default": False, "type": bool}), + ("couplings", {"default": True, "type": bool}), + ("prog4_j", {"default": "prog", "type": str}), + ("func_j", {"default": "pbe0", "type": str}), + ("basis_j", {"default": "def2-TZVP", "type": str}), + ("sm4_j", {"default": "default", "type": str}), + ("shieldings", {"default": True, "type": bool}), + ("prog4_s", {"default": "prog", "type": str}), + ("func_s", {"default": "pbe0", "type": str}), + ("basis_s", {"default": "def2-TZVP", "type": str}), + ("sm4_s", {"default": "default", "type": str}), + ("h_ref", {"default": "TMS", "type": str}), + ("c_ref", {"default": "TMS", "type": str}), + ("f_ref", {"default": "CFCl3", "type": str}), + ("si_ref", {"default": "TMS", "type": str}), + ("p_ref", {"default": "TMP", "type": str}), + ("h_active", {"default": True, "type": bool}), + ("c_active", {"default": True, "type": bool}), + ("f_active", {"default": False, "type": bool}), + ("si_active", {"default": False, "type": bool}), + ("p_active", {"default": False, "type": bool}), + ("resonance_frequency", {"default": 300.0, "type": float}), + ] + self.defaults_optical_rotation_part5 = [ + # part5 + ("optical_rotation", {"default": False, "type": bool}), + ("func_or", {"default": "pbe", "type": str}), + ("func_or_scf", {"default": "r2scan-3c", "type": str}), + ("basis_or", {"default": "def2-SVPD", "type": str}), + ("freq_or", {"default": [589.0], "type": list}), + ] + + self.internal_defaults = OrderedDict( + self.defaults_refine_ensemble_general + + self.defaults_refine_ensemble_part0 + + self.defaults_refine_ensemble_part1 + + self.defaults_refine_ensemble_part2 + + self.defaults_refine_ensemble_part3 + + self.defaults_nmrprop_part4 + + self.defaults_optical_rotation_part5 + ) + + # update internal defaults specific to QM package + # orca + self.internal_defaults_orca = deepcopy(self.internal_defaults) + self.internal_defaults_orca["sm2"]["default"] = "smd" + self.internal_defaults_orca["smgsolv1"]["default"] = "smd" + self.internal_defaults_orca["smgsolv2"]["default"] = "smd" + self.internal_defaults_orca["smgsolv3"]["default"] = "smd" + self.internal_defaults_orca["sm4_j"]["default"] = "smd" + self.internal_defaults_orca["sm4_s"]["default"] = "smd" + self.internal_defaults_orca["basis"]["default"] = "def2-TZVP(-f)" + self.internal_defaults_orca["basis3"]["default"] = "def2-TZVP(-f)" + # tm + self.internal_defaults_tm = deepcopy(self.internal_defaults) + self.internal_defaults_tm["sm2"]["default"] = "dcosmors" + self.internal_defaults_tm["smgsolv1"]["default"] = "dcosmors" + self.internal_defaults_tm["smgsolv2"]["default"] = "dcosmors" + self.internal_defaults_tm["smgsolv3"]["default"] = "dcosmors" + self.internal_defaults_tm["sm4_j"]["default"] = "dcosmors" + self.internal_defaults_tm["sm4_s"]["default"] = "dcosmors" + + self.value_options = { + "nconf": ["all", "number e.g. 10 up to all conformers"], + "charge": ["number e.g. 0"], + "unpaired": ["number e.g. 0"], + "solvent": ["gas"] + [i for i in censo_solvent_db.keys()], + "prog": ["tm", "orca"], + "part0": ["on", "off"], + "part1": ["on", "off"], + "part2": ["on", "off"], + "part3": ["on", "off"], + "part4": ["on", "off"], + "optical_rotation": ["on", "off"], + "prog3": ["tm", "orca", "prog"], + "ancopt": ["on", "off"], + "opt_spearman": ["on", "off"], + "evaluate_rrho": ["on", "off"], + "consider_sym": ["on", "off"], + "prog_rrho": ["xtb", "prog"], + "part0_gfnv": self.impgfnv, + "part1_gfnv": self.impgfnv, + "part2_gfnv": self.impgfnv, + "part3_gfnv": self.impgfnv, + "temperature": ["temperature in K e.g. 298.15"], + "multitemp": ["on", "off"], + "trange": ["temperature range [start, end, step]"], + "func0": self.impfunc, + "basis0": ["automatic"] + list(self.func_basis_default.values()), + "func": self.impfunc, + "basis": ["automatic"] + list(self.func_basis_default.values()), + "func3": self.impfunc3, + "basis3": self.knownbasissets3, + "part0_threshold": ["number e.g. 4.0"], + "part1_threshold": ["number e.g. 5.0"], + "opt_limit": ["number e.g. 4.0"], + "part2_threshold": [ + "Boltzmann sum threshold in %. e.g. 95 (between 1 and 100)" + ], + "part3_threshold": [ + "Boltzmann sum threshold in %. e.g. 95 (between 1 and 100)" + ], + "sm2": self.impsm2, + "smgsolv3": self.impsmgsolv3, + "sm4_j": self.impsm4_j, + "sm4_s": self.impsm4_s, + "check": ["on", "off"], + "crestcheck": ["on", "off"], + "maxthreads": ["number of threads e.g. 2"], + "omp": ["number cores per thread e.g. 4"], + "smgsolv1": self.impsmgsolv1, + "smgsolv2": self.impsmgsolv2, + "bhess": ["on", "off"], + "rmsdbias": ["on", "off"], + "sm_rrho": ["alpb", "gbsa"], + "optcycles": ["number e.g. 5 or 10"], + "optlevel2": [ + "crude", + "sloppy", + "loose", + "lax", + "normal", + "tight", + "vtight", + "extreme", + "automatic", + ], + "spearmanthr": ["value between -1 and 1, if outside set automatically"], + "couplings": ["on", "off"], + "prog4_j": ["tm", "orca", "adf", "prog"], + "prog4_s": ["tm", "orca", "adf", "prog"], + "func_j": self.impfunc_j, + "basis_j": self.knownbasissetsJ, + "func_s": self.impfunc_s, + "basis_s": self.knownbasissetsS, + "h_ref": self.imphref, + "c_ref": self.impcref, + "f_ref": self.impfref, + "si_ref": self.impsiref, + "p_ref": self.imppref, + "h_active": ["on", "off"], + "c_active": ["on", "off"], + "f_active": ["on", "off"], + "p_active": ["on", "off"], + "si_active": ["on", "off"], + "resonance_frequency": [ + "MHz number of your experimental spectrometer setup" + ], + "shieldings": ["on", "off"], + "radsize": ["number e.g. 8 or 10"], + "func_or": ["functional for opt_rot e.g. pbe"], + "func_or_scf": ["functional for SCF in opt_rot e.g. r2scan-3c"], + "basis_or": ["basis set for opt_rot e.g. def2-SVPD"], + "freq_or": ["list of freq in nm to evaluate opt rot at e.g. [589, 700]"], + "hlow": ["lowest force constant in ANC generation, e.g. 0.01"], + } + # must not be changed if restart(concerning optimization) + self.restart_unchangeable = [ + "unpaired", + "charge", + "solvent", + "temperature", + "prog", + "ancopt", + "opt_spearman", + "optlevel2", + "func", + "basis", + "sm2", + "nat", + "radsize", + "consider_sym", + ] + # might be changed, but data may be lost/overwritten + self.restart_changeable = { + "multitemp": False, + # "temperature": False, # should not be changeable all solvent and rrho values depend on this + "trange": False, + "bhess": False, + "part1_gfnv": False, + "part2_gfnv": False, + "part3_gfnv": False, + "smgsolv1": False, + "smgsolv2": False, + "smgsolv3": False, + "func_or": False, + "basis_or": False, + "func_or_scf": False, + "freq_or": False, + # "consider_sym": False, # --> reset all rrho values! + } + + +class config_setup(internal_settings): + """ + Read or write configuration or input files. + """ + + def __init__(self, path=os.getcwd(), *args, **kwargs): + internal_settings.__init__(self, *args, **kwargs) + # settings just to calm down pylint, real assignment is dynamically done + # general settings + self.nconf = None + self.charge = 0 + self.unpaired = 0 + self.solvent = "gas" + self.prog_rrho = "xtb" + self.temperature = 298.15 + self.trange = [273.15, 378.15, 5] + self.multitemp = False + self.evaluate_rrho = True + self.bhess = True + self.consider_sym = False + self.sm_rrho = "alpb" + self.check = True + self.crestcheck = False + self.prog = "tm" + self.func = "b97-3c" + self.basis = "automatic" + self.maxthreads = 1 + self.omp = 1 + # part0 + self.part0 = False + self.part0_gfnv = "gfnff" + self.part0_threshold = 4.0 + self.func0 = "b97-d" + self.basis0 = "def2-SV(P)" + # part1 + self.part1 = True + self.smgsolv1 = "sm2" + self.part1_gfnv = "gfnff" + self.part1_threshold = 1.0 + # part2 + self.part2 = True + self.part2_threshold = 90 + self.sm2 = "default" + self.smgsolv2 = "sm2" + self.part2_gfnv = "gfnff" + self.ancopt = True + self.hlow = 0.01 + self.opt_spearman = False + self.optcycles = 5 + self.optlevel2 = "automatic" + self.spearmanthr = 0.9999 + self.radsize = 8 + # part3 + self.part3 = True + self.prog3 = "prog" + self.func3 = "b97-d" + self.basis3 = "def2-TZVPD" + self.smgsolv3 = "sm2" + self.part3_gfnv = "gfn2" + # part4 + self.part4 = False + self.prog4_j = "tm" + self.prog4_s = "tm" + self.couplings = True + self.func_j = "pbe0" + self.basis_j = "def2-TZVP" + self.shieldings = True + self.func_s = "pbe0" + self.basis_s = "def2-TZVP" + self.sm4_j = "default" + self.sm4_s = "default" + self.h_ref = "TMS" + self.c_ref = "TMS" + self.f_ref = "CFCl3" + self.si_ref = "TMS" + self.p_ref = "TMP" + self.resonance_frequency = 300.0 + # part5 + self.optical_rotation = False + self.func_or = "pbe" + self.func_or_scf = "r2scan-3c" + self.basis_or = "def2-SVPD" + self.freq_or = [589] + + # settings the program operates with updated to the defaults + for key in self.internal_defaults.keys(): + setattr(self, key, self.internal_defaults[key]["default"]) + + # workingdirectory + self.cwd = path + self.ensemblepath = "" + self.configpath = "" + self.jsonpath = "" + + # formatting: + self.lenconfx = 3 + + self.save_errors = [] + self.save_infos = [] + + self.startupinfokeys = ["nat", "md5", "maxconf", "run"] + self.nat = 0 + self.md5 = "" + self.maxconf = 0 + self.run = True + self.nmrmode = False + + # pathsdefaults: --> read_program_paths + self.external_paths = {} + self.external_paths["orcapath"] = "" + self.external_paths["orcaversion"] = "" + self.external_paths["xtbpath"] = "" + self.external_paths["crestpath"] = "" + self.external_paths["cosmorssetup"] = "" + self.external_paths["dbpath"] = "" + self.external_paths["cosmothermversion"] = "" + self.external_paths["mpshiftpath"] = "" + self.external_paths["escfpath"] = "" + + def cleanup_run(self, complete=False): + """ + Delete all unneeded files. + """ + files_in_cwd = [ + f for f in os.listdir(self.cwd) if os.path.isfile(os.path.join(self.cwd, f)) + ] + for file in files_in_cwd: + if ( + "enso.json." in file + or "enso_ensemble_part1.xyz." in file + or "enso_ensemble_part2.xyz." in file + or "enso_ensemble_part3.xyz." in file + ): + if int(file.split(".")[2]) > 1: + print(f"Removing: {file}") + os.remove(os.path.join(self.cwd, file)) + if complete: + if "enso.json" in files_in_cwd: + print(f"Removing: {'enso.json'}") + os.remove(os.path.join(self.cwd, "enso.json")) + if "enso.json.1" in files_in_cwd: + print(f"Removing: {'enso.json.1'}") + os.remove(os.path.join(self.cwd, "enso.json.1")) + if os.path.isdir(os.path.join(self.cwd, "conformer_rotamer_check")): + print("Removing conformer_rotamer_check") + shutil.rmtree(os.path.join(self.cwd, "conformer_rotamer_check")) + # for file in files_in_cwd: + # if 'mat.tmp' in file: + # print(f"Removing: {file}") + # os.remove(os.path.join(self.cwd,file)) + # remove *mat.tmp files + # ask if CONF folders should be removed + + def get_method_name( + self, + jobtype, + func=None, + basis=None, + sm=None, + gfn_version=None, + bhess=None, + solvent=None, + prog=None, + func2=None, + disp=None, + ): + """ + Create method name for storing and retrieving data + --> method energy + --> method2 gsolv + """ + if func is not None and basis is not None: + if func in self.composite_method_basis.keys(): + if basis == self.func_basis_default.get(func, None): + # composite method (e.g. r2scan-3c) + tmp_func_basis = func + elif disp is not None: + # FUNC/BASIS + tmp_func_basis = f"{func}-{disp}/{basis}" + else: + # FUNC-DISP/BASIS + tmp_func_basis = f"{func}/{basis}" + elif disp is not None: + # FUNC/BASIS + tmp_func_basis = f"{func}-{disp}/{basis}" + else: + # FUNC-DISP/BASIS + tmp_func_basis = f"{func}/{basis}" + if jobtype in ("cosmors",): + exc_name = {"cosmors": "COSMO-RS-normal", "cosmors-fine": "COSMO-RS-fine"} + # energy FUNC/BASIS + method = tmp_func_basis + # cosmors gsolv COSMO-RS[FUNC/BASIS] + method2 = f"{exc_name.get(sm)}[{tmp_func_basis}]" + elif jobtype in ("gbsa_gsolv", "alpb_gsolv"): + # energy FUNC/BASIS + method = tmp_func_basis + # e.g. ALPB_Gsolv[GFN2] + method2 = f"{sm}[{gfn_version}]" + elif jobtype == "sp": + # energy FUNC/BASIS + method = tmp_func_basis + elif jobtype == "sp_implicit": + # energy FUNC/BASIS[DCOSMORS] + method = f"{tmp_func_basis}[{str(sm).upper()}]" + method2 = "incl. in E" + elif jobtype == "smd_gsolv": + # energy FUNC/BASIS + method = tmp_func_basis + # SMD_gsolv SMD_GSOLV[FUNC/BASIS] + method2 = f"{sm}[{tmp_func_basis}]" + elif jobtype == "rrhoxtb": + # GFN2-bhess + if bhess: + if solvent != "gas": + method = f"{str(gfn_version).upper()}[{sm}]-bhess" + else: + method = f"{str(gfn_version).upper()}-bhess" + else: + if solvent != "gas": + method = f"{str(gfn_version).upper()}[{sm}]" + else: + method = f"{str(gfn_version).upper()}" + elif jobtype in ("opt", "xtbopt"): + if solvent == "gas": + # energy FUNC/BASIS + method = tmp_func_basis + else: + # energy FUNC/BASIS[DCOSMORS] + method = f"{tmp_func_basis}[{str(sm).upper()}]" + elif jobtype in ("couplings", "couplings_sp", "shieldings", "shieldings_sp"): + if solvent == "gas": + method = f"{tmp_func_basis}-{prog}" + else: + method = f"{tmp_func_basis}[{str(sm).upper()}]-{prog}" + elif jobtype in ("opt-rot", "opt-rot_sp"): + if solvent == "gas": + method = f"{tmp_func_basis}_[SCF={func2}]({prog})" + else: + method = f"{tmp_func_basis}[{str(sm).upper()}]_[SCF={func2}]({prog})" + else: + raise Exception(f"JOBTYPE {jobtype} not known in get_method_name") + try: + method2 + except NameError: + method2 = "" + return method, method2 + + def provide_runinfo(self, extend=True): + """ + Write dictionary structured like internal defaults. + And extenden with startup information. + """ + runinfo = [] + keys = list(self.internal_defaults.keys()) + if extend: + keys = keys + self.startupinfokeys + for key in keys: + runinfo.append((key, getattr(self, key))) + return OrderedDict(runinfo) + + def _decomment(self, csvfile): + """ + remove any comments from file before parsing with csv.DictReader + comment symbols are # and $ + """ + for row in csvfile: + raw = row.split("#")[0].strip() + raw2 = raw.split("$")[0].strip() + if raw2: + yield raw2 + + def _exchange_onoff(self, inp, reverse=False): + """ + Exchange on --> True, off--> False, backward if reverse=True + """ + exchange = {"on": True, "off": False} + if reverse: + if isinstance(inp, bool) and inp in {v: k for k, v in exchange.items()}: + return {v: k for k, v in exchange.items()}[inp] + else: + return inp + elif not reverse: + if isinstance(inp, str) and inp in exchange.keys(): + return exchange[inp] + else: + return inp + + def read_config(self, path, startread, args): + """ + Read from config data from file (here enso.inp or .censorc), + cml > .censorc > internal defaults + """ + rcdata = {} + with open(path, "r") as csvfile: + # skip header: + while True: + line = csvfile.readline() + if line.startswith(startread): + break + elif line == "": + # EOF + break + else: + pass + reader = csv.DictReader( + self._decomment(csvfile), + fieldnames=("key", "value"), + skipinitialspace=True, + delimiter=":", + ) + for row in reader: + if "end" in row["key"]: + break + else: + rcdata[row["key"]] = row["value"] + if "end" in rcdata: + del rcdata["end"] + + args_key = {v: k for k, v in self.key_args_dict.items()} + cmlflags = vars(args) + for key in cmlflags.keys(): + if key in args_key.keys(): + if cmlflags[key] is not None: + # print(f"SETTING cml: {key} to {cmlflags[key]}") + rcdata[key] = cmlflags[key] + # print(key, cmlflags[key]) + # end get commandline arguments + # update censorc-key to internal key + for key, value in list(rcdata.items()): + if key in self.key_args_dict.keys(): + if key != self.key_args_dict[key]: + # print(f"updating: {key} to {self.key_args_dict[key]} " + # "{value} {rcdata.get(self.key_args_dict[key])}") + rcdata[self.key_args_dict[key]] = rcdata.get( + self.key_args_dict[key], value + ) + del rcdata[key] + # end update censorc-key to internal key + + readinkeys = [] + for item in list(rcdata.keys()): + if item not in self.internal_defaults.keys(): + self.save_errors.append( + f"WARNING: {item} is not a known " + f"keyword in {os.path.basename(path)}." + ) + del rcdata[item] + else: + readinkeys.append(item) + diff = list(set(self.internal_defaults.keys()) - set(readinkeys)) + if diff: + self.save_errors.append( + "WARNING: These keywords were not found in the configuration " + "file {}\n and therefore default " + "values are taken for:".format(os.path.basename(path)) + ) + for item in diff: + self.save_errors.append(" {}".format(item)) + rcdata[item] = self.internal_defaults[item]["default"] + + for key in rcdata: + if rcdata[key] == "": + rcdata[key] = None + # -----> keys are checked, now check values!!!! + for key in rcdata: + # change on --> True , off --> False + rcdata[key] = self._exchange_onoff(rcdata[key]) + if key != "nconf": + if ( + not isinstance(rcdata[key], self.internal_defaults[key]["type"]) + and rcdata[key] is not None + ): + try: + if self.internal_defaults[key]["type"] == list: + tmp = rcdata[key].strip("[") + tmp = tmp.strip("]") + tmp = tmp.split(",") + rcdata[key] = [float(i) for i in tmp] + else: + rcdata[key] = self.internal_defaults[key]["type"]( + rcdata[key] + ) + except (ValueError, TypeError): + self.save_errors.append( + f"WARNING: {key}= {rcdata[key]}" + " could not be" + " converted and default values are set to " + f"{self.internal_defaults[key]['default']}" + ) + rcdata[key] = self.internal_defaults[key]["type"]( + self.internal_defaults[key]["default"] + ) + for key, value in rcdata.items(): + if key in vars(self).keys(): + setattr(self, key, value) + else: + print("ERROR", key) + self.save_errors.append(f"{key} not known in config!") + + def check_logic(self, error_logical=False, silent=False): + """ + Checks settings for impossible setting-comibinations, also checking + if calculations are possible with the requested qm_codes. + """ + if silent: + store_errors = self.save_errors + # if only one conformer! + # if self.nconf == 1 and self.maxconf == 1: + # self.part1 = False + # self.part2 = True + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle prog3: + if self.prog3 == "prog" and self.prog in self.value_options["prog"]: + self.prog3 = self.prog + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle prog4_j: + if self.prog4_j == "prog" and self.prog in self.value_options["prog"]: + self.prog4_j = self.prog + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle prog4: + if self.prog4_s == "prog" and self.prog in self.value_options["prog"]: + self.prog4_s = self.prog + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # set spearmanthr by number of atoms: + if self.spearmanthr < -1 or self.spearmanthr > 1: + self.spearmanthr = 1 / (math.exp(0.03 * (self.nat ** (1 / 4)))) + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle prog_rrho + if self.prog_rrho == "prog" and self.prog in self.value_options["prog"]: + self.prog_rrho = self.prog + if self.prog_rrho == "tm": + if shutil.which("thermo") is not None: + # need thermo for reading thermostatistical contribution + self.prog_rrho = "tm" + else: + self.prog_rrho = "xtb" + self.save_errors.append( + "WARNING: Currently are only GFNn-xTB " + "hessians possible and no TM hessians" + ) + elif not self.prog_rrho: + self.save_errors.append( + "WARNING: Thermostatistical contribution to " + "free energy will not be calculated, since prog_rrho ist set to 'off'!" + ) + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle func3 dsd-blyp with basis + if self.part3 and self.func3 == "dsd-blyp" and self.basis3 != "def2-TZVPP": + self.save_errors.append( + "WARNING: DSD-BLYP is only available with the " "basis set def2-TZVPP!" + ) + self.basis3 = "def2-TZVPP" + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle func0 + if self.prog == "orca" and self.func0 not in self.func_orca: + self.save_errors.append( + "\nERROR: The functional " + "(func0) {} is not implemented with the {} program package." + " Options are: {}".format(self.func0, self.prog, self.func_orca) + ) + error_logical = True + if self.prog == "tm" and self.func0 not in self.func_tm: + self.save_errors.append( + "\nERROR: The functional " + "(func0) {} is not implemented with the {} program package. " + "Options are: {}".format(self.func0, self.prog, self.func_tm) + ) + error_logical = True + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle basis0 for func0: + if self.basis0 == "None" or self.basis0 is None or self.basis0 == "automatic": + if self.prog == "tm": + default = self.internal_defaults_tm.get("basis0", "def2-SV(P)") + elif self.prog == "orca": + default = self.internal_defaults_orca.get("basis0", "def2-SV(P)") + else: + default = "def2-SV(P)" + self.basis0 = default + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle func + if self.prog == "orca" and self.func not in self.func_orca: + self.save_errors.append( + "\nERROR: The functional " + "(func) {} is not implemented with the {} program package." + " Options are: {}".format(self.func, self.prog, self.func_orca) + ) + error_logical = True + if self.prog == "tm" and self.func not in self.func_tm: + self.save_errors.append( + "\nERROR: The functional " + "(func) {} is not implemented with the {} program package. " + "Options are: {}".format(self.func, self.prog, self.func_tm) + ) + error_logical = True + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle basis for func: + if self.basis == "None" or self.basis is None or self.basis == "automatic": + if self.prog == "tm": + default = self.internal_defaults_tm.get("basis", "def2-TZVP") + elif self.prog == "orca": + default = self.internal_defaults_orca.get("basis", "def2-TZVP") + else: + default = "def2-TZVP" + self.basis = self.func_basis_default.get(self.func, default) + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle func3 + if self.part3 and self.func3 in ( + "pbeh-3c", + "b973c", + "b97-3c", + "hf3c", + "hf-3c", + "r2scan-3c", + ): + self.save_errors.append( + "Basis set (basis3) is fixed to be " + "def2-TZVPD, keep this in mind when using composite methods!" + ) + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + if self.part4 and (self.couplings or self.shieldings): + self.nmrmode = True + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle func_j + if self.prog4_j == "orca" and self.func_j not in self.func_j_orca: + self.save_errors.append( + "\nERROR: In part4 the functional (funcJ) {} " + "is not implemented in ENSO with the {} program package. Options " + "are: {}".format(self.func_j, self.prog4_j, self.func_j_orca) + ) + if not self.part4: + tmp = self.save_errors.pop().replace("\nERROR", "WARNING", 1) + self.save_errors.append(tmp) + else: + error_logical = True + if self.prog4_j == "tm" and self.func_j not in self.func_j_tm: + self.save_errors.append( + "\nERROR: In part4 the functional (funcJ) {} " + "is not implemented in ENSO with the {} program package. Options " + "are: {}".format(self.func_j, self.prog4_j, self.func_j_tm) + ) + if not self.part4: + tmp = self.save_errors.pop().replace("\nERROR", "WARNING", 1) + self.save_errors.append(tmp) + else: + error_logical = True + if self.func_j == "pbeh-3c": + self.basis_j = "def2-mSVP" + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle func_s + if self.prog4_s == "orca" and self.func_s not in self.func_s_orca: + self.save_errors.append( + "\nERROR: In part4 the functional (funcS) {}" + " is not implemented in ENSO with the {} program package. Options " + "are: {}".format(self.func_s, self.prog4_s, self.func_s_orca) + ) + if not self.part4: + tmp = self.save_errors.pop().replace("\nERROR", "WARNING", 1) + self.save_errors.append(tmp) + else: + error_logical = True + if self.prog4_s == "tm" and self.func_s not in self.func_s_tm: + self.save_errors.append( + "\nERROR: In part4 the functional (funcS) {}" + " is not implemented in ENSO with the {} program package. Options " + "are: {}".format(self.func_s, self.prog4_s, self.func_s_tm) + ) + if not self.part4: + tmp = self.save_errors.pop().replace("\nERROR", "WARNING", 1) + self.save_errors.append(tmp) + else: + error_logical = True + if self.func_s == "pbeh-3c": + self.basis_s = "def2-mSVP" + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # no unpaired electrons in coupling or shiedling calculations! + if self.unpaired > 0: + if self.part4 and (self.couplings or self.shieldings): + self.save_errors.append( + "ERROR: Coupling and shift calculations " + "(part4) are only available for closed-shell systems!" + ) + error_logical = True + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Solvation: + if self.solvent == "gas": + self.smgsolv1 = "gas-phase" + self.sm2 = "gas-phase" + self.smgsolv2 = "gas-phase" + self.smgsolv3 = "gas-phase" + self.sm4_j = "gas-phase" + self.sm4_s = "gas-phase" + else: + # Handle sm2 --> solvent model in optimization: + exchange_sm = { + "cosmo": "cpcm", + "cpcm": "cosmo", + "dcosmors": "smd", + "smd": "dcosmors", + } + if self.sm2 not in self.impsm2: + self.save_errors.append( + f"ERROR: The solvent model {self.sm2}" " is not implemented!" + ) + error_logical = True + if self.prog == "orca": + if self.sm2 in self.sm2_tm: + self.save_errors.append( + "WARNING: {} is not available with " + "{}! Therefore {} is used!".format( + self.sm2, self.prog, exchange_sm[self.sm2] + ) + ) + self.sm2 = exchange_sm[self.sm2] + elif self.sm2 == "default": + self.sm2 = self.internal_defaults_orca["sm2"]["default"] + if self.prog == "tm": + if self.sm2 in self.sm2_orca: + self.save_errors.append( + "WARNING: {} is not available with " + "{}! Therefore {} is used!".format( + self.sm2, self.prog, exchange_sm[self.sm2] + ) + ) + self.sm2 = exchange_sm[self.sm2] + elif self.sm2 == "default": + self.sm2 = self.internal_defaults_tm["sm2"]["default"] + # Check if solvent-information is available for solventmodel + ### + # Check which solvation models are applied: + + check_for = { + "xtb": False, + "cosmors": False, + "dcosmors": False, + "cpcm": False, + "smd": False, + "DC": False, + } + applied_solventmodels = [] + if self.evaluate_rrho: + applied_solventmodels.append(self.sm_rrho) + if self.part1: + applied_solventmodels.append(self.smgsolv1) + if self.part2: + applied_solventmodels.append(self.sm2) + applied_solventmodels.append(self.smgsolv2) + if self.part3: + applied_solventmodels.append(self.smgsolv3) + if self.part4: + applied_solventmodels.append(self.sm4_j) + applied_solventmodels.append(self.sm4_s) + if self.optical_rotation: + applied_solventmodels.append("cosmo") + + for solventmodel in list(set(applied_solventmodels)): + if solventmodel in ("alpb", "gbsa", "alpb_gsolv", "gbsa_gsolv"): + check_for["xtb"] = True + elif solventmodel in ("cosmors", "cosmors-fine"): + check_for["cosmors"] = True + elif solventmodel in ("dcosmors",): + check_for["dcosmors"] = True + elif solventmodel in ("cosmo",): + check_for["DC"] = True + elif solventmodel in ("cpcm",): + check_for["cpcm"] = True + elif solventmodel in ("smd", "smd_gsolv"): + check_for["smd"] = True + else: + print("unexpected behaviour") + lookup = { + "xtb": "solvents_xtb", + "cosmors": "solvents_cosmors", + "dcosmors": "solvents_dcosmors", + "cpcm": "solvents_cpcm", + "smd": "solvents_smd", + "DC": "", + } + # check if solvent in censo_solvent_db + if censo_solvent_db.get(self.solvent, "not_found") == "not_found": + self.save_errors.append( + f"ERROR: The solvent {self.solvent} is not found!" + ) + error_logical = True + for key, value in check_for.items(): + if value: + if ( + censo_solvent_db[self.solvent].get(key, "nothing_found") + == "nothing_found" + ): + self.save_errors.append( + f"ERROR: The solvent for solventmodel in {key} is not found!" + ) + error_logical = True + if key == "DC": + try: + if not ( + float( + censo_solvent_db[self.solvent].get( + key, "nothing_found" + ) + ) + > 0.0 + and float( + censo_solvent_db[self.solvent].get( + key, "nothing_found" + ) + ) + < 150.0 + ): + self.save_errors.append( + f"ERROR: The dielectric constant can not be converted." + ) + error_logical = True + except ValueError: + self.save_errors.append( + f"ERROR: The dielectric constant can not be converted." + ) + error_logical = True + elif key in ("smd", "cpcm"): + if censo_solvent_db[self.solvent].get(key, "nothing_found")[ + 1 + ].lower() not in getattr(self, lookup[key]): + self.save_errors.append( + f"WARNING: The solvent " + f"{censo_solvent_db[self.solvent].get(key, 'nothing_found')[1]}" + f" for solventmodel/program {key} can not be checked but is used anyway." + ) + else: + if censo_solvent_db[self.solvent].get(key, "nothing_found")[ + 1 + ] not in getattr(self, lookup[key]): + self.save_errors.append( + f"WARNING: The solvent " + f"{censo_solvent_db[self.solvent].get(key, 'nothing_found')[1]} " + f"for solventmodel/program {key} can not be checked but is used anyway." + ) + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle smgsolv1 + exchange_sm = { + "cosmo": "cpcm", + "cpcm": "cosmo", + "dcosmors": "smd", + "smd": "dcosmors", + } + if self.smgsolv1 not in self.impsmgsolv1: + self.save_errors.append( + f"ERROR: The solvent model {self.smgsolv1}" + " is not implemented for smgsolv1 !" + ) + error_logical = True + if self.smgsolv1 == "sm2": + self.smgsolv1 = self.sm2 + if self.prog == "tm" and self.smgsolv1 in self.sm2_orca: + self.save_errors.append( + "WARNING: {} is not available with " + "{}! Therefore {} is used!".format( + self.smgsolv1, self.prog, exchange_sm[self.smgsolv1] + ) + ) + self.smgsolv1 = exchange_sm[self.smgsolv1] + if self.prog == "orca" and self.smgsolv1 in self.sm2_tm: + self.save_errors.append( + "WARNING: {} is not available with " + "{}! Therefore {} is used!".format( + self.smgsolv1, self.prog, exchange_sm[self.smgsolv1] + ) + ) + self.smgsolv1 = exchange_sm[self.smgsolv1] + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle smgsolv2 + exchange_sm = { + "cosmo": "cpcm", + "cpcm": "cosmo", + "dcosmors": "smd", + "smd": "dcosmors", + } + if self.smgsolv2 not in self.impsmgsolv2: + self.save_errors.append( + f"ERROR: The solvent model {self.smgsolv2}" + " is not implemented for smgsolv2 !" + ) + error_logical = True + if self.smgsolv2 == "sm2": + self.smgsolv2 = self.sm2 + if self.prog == "tm" and self.smgsolv2 in self.sm2_orca: + self.save_errors.append( + "WARNING: {} is not available with " + "{}! Therefore {} is used!".format( + self.smgsolv2, self.prog, exchange_sm[self.smgsolv2] + ) + ) + self.smgsolv2 = exchange_sm[self.smgsolv2] + if self.prog == "orca" and self.smgsolv2 in self.sm2_tm: + self.save_errors.append( + "WARNING: {} is not available with " + "{}! Therefore {} is used!".format( + self.smgsolv2, self.prog, exchange_sm[self.smgsolv2] + ) + ) + self.smgsolv2 = exchange_sm[self.smgsolv2] + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle smgsolv3 + exchange_sm = { + "cosmo": "cpcm", + "cpcm": "cosmo", + "dcosmors": "smd", + "smd": "dcosmors", + } + if self.smgsolv3 not in self.impsmgsolv3: + self.save_errors.append( + f"ERROR: The solvent model {self.smgsolv3}" + " is not implemented for smgsolv3 !" + ) + error_logical = True + if self.smgsolv3 == "sm2": + self.smgsolv3 = self.sm2 + if self.prog == "tm" and self.smgsolv3 in self.sm2_orca: + self.save_errors.append( + "WARNING: {} is not available with " + "{}! Therefore {} is used!".format( + self.smgsolv3, self.prog, exchange_sm[self.smgsolv3] + ) + ) + self.smgsolv3 = exchange_sm[self.smgsolv3] + if self.prog == "orca" and self.smgsolv3 in self.sm2_tm: + self.save_errors.append( + "WARNING: {} is not available with " + "{}! Therefore {} is used!".format( + self.smgsolv3, self.prog, exchange_sm[self.smgsolv3] + ) + ) + self.smgsolv3 = exchange_sm[self.smgsolv3] + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle sm4_j + if self.prog4_j == "orca": + if self.sm4_j in self.sm4_j_tm: + self.save_errors.append( + "WARNING: {} is not available with {}!" + " Therefore {} is used!".format( + self.sm4_j, self.prog4_j, exchange_sm[self.sm4_j] + ) + ) + self.sm4_j = exchange_sm[self.sm4_j] + elif self.sm4_j == "default": + self.sm4_j = self.internal_defaults_orca["sm4_j"]["default"] + if self.prog4_j == "tm": + if self.sm4_j in self.sm4_j_orca: + self.save_errors.append( + "WARNING: {} is not available with {}!" + " Therefore {} is used!".format( + self.sm4_j, self.prog4_j, exchange_sm[self.sm4_j] + ) + ) + self.sm4_j = exchange_sm[self.sm4_j] + elif self.sm4_j == "default": + self.sm4_j = self.internal_defaults_tm["sm4_j"]["default"] + # Handle sm4_s + if self.prog4_s == "orca": + if self.sm4_s in self.sm4_s_tm: + self.save_errors.append( + "WARNING: {} is not available with {}!" + " Therefore {} is used!".format( + self.sm4_s, self.prog4_s, exchange_sm[self.sm4_s] + ) + ) + self.sm4_s = exchange_sm[self.sm4_s] + elif self.sm4_s == "default": + self.sm4_s = self.internal_defaults_orca["sm4_s"]["default"] + if self.prog4_s == "tm": + if self.sm4_s in self.sm4_s_orca: + self.save_errors.append( + "WARNING: {} is not available with {}!" + " Therefore {} is used!".format( + self.sm4_s, self.prog4_s, exchange_sm[self.sm4_s] + ) + ) + self.sm4_s = exchange_sm[self.sm4_s] + elif self.sm4_s == "default": + self.sm4_s = self.internal_defaults_tm["sm4_s"]["default"] + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Handle optlevel2: + # sm2 needs to be set (not default!) + if self.optlevel2 in ("None", None, "automatic"): + if self.sm2 in ("smd", "dcosmors") and self.solvent != "gas": + self.optlevel2 = "lax" + else: + # gas phase + self.optlevel2 = "normal" + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + if self.part4 and not self.couplings and not self.shieldings: + self.part4 = False + self.save_errors.append( + "WARNING: Neither calculating coupling nor " + "shielding constants is activated! Part 4 is not executed." + ) + elif not any( + [ + getattr(self, flag) + for flag in ( + "h_active", + "c_active", + "f_active", + "si_active", + "p_active", + ) + ] + ): + if self.part4: + self.save_errors.append( + "WARNING: No type of NMR spectrum is " + "activated in the .censorc! Therefore all nuclei are calculated!" + ) + self.part4 = True + else: + self.save_errors.append( + "WARNING: No type of NMR spectrum is activated in the .censorc!" + ) + if silent: + self.save_errors = store_errors + return error_logical + + def print_parameters(self): + """ + print settings at startup + """ + + # print parameter setting + print("\n" + "".ljust(PLENGTH, "-")) + print("PARAMETERS".center(PLENGTH, " ")) + print("".ljust(PLENGTH, "-") + "\n") + + print( + f"The config file {os.path.basename(self.configpath)} is read " + f"from {self.configpath}." + ) + print(f"Reading conformer rotamer ensemble from: {self.ensemblepath}.") + if self.save_infos: + for _ in list(self.save_infos): + print(self.save_infos.pop(0)) + if self.save_errors: + print("") + for _ in list(self.save_errors): + print(self.save_errors.pop(0)) + info = [] + info.append(["justprint", "\n" + "".ljust(int(PLENGTH / 2), "-")]) + info.append(["justprint", "CRE SORTING SETTINGS".center(int(PLENGTH / 2), " ")]) + info.append(["justprint", "".ljust(int(PLENGTH / 2), "-") + "\n"]) + info.append(["nat", "number of atoms in system"]) + info.append(["nconf", "number of considered conformers"]) + info.append(["maxconf", "number of all conformers from input"]) + info.append(["charge", "charge"]) + info.append(["unpaired", "unpaired"]) + info.append(["solvent", "solvent"]) + info.append(["temperature", "temperature"]) + if self.multitemp: + info.append(["multitemp", "evalulate at different temperatures"]) + info.append( + [ + "printoption", + "temperature range", + [i for i in frange(self.trange[0], self.trange[1], self.trange[2])], + ] + ) + info.append(["evaluate_rrho", "calculate mRRHO contribution"]) + info.append(["consider_sym", "consider symmetry for mRRHO contribution"]) + info.append(["check", "cautious checking for error and failed calculations"]) + info.append(["crestcheck", "checking the DFT-ensemble using CREST"]) + info.append(["maxthreads", "maxthreads"]) + info.append(["omp", "omp"]) + + # PART0: + info.append(["justprint", "\n" + "".ljust(int(PLENGTH / 2), "-")]) + info.append( + [ + "justprint", + "CRE CHEAP-PRESCREENING - PART0".center(int(PLENGTH / 2), " "), + ] + ) + info.append(["justprint", "".ljust(int(PLENGTH / 2), "-")]) + info.append(["part0", "part0"]) + info.append(["nconf", "starting number of considered conformers"]) + info.append(["prog", "program for part0"]) + info.append(["func0", "functional for fast single-point"]) + info.append(["basis0", "basis set for fast single-point"]) + info.append(["part0_threshold", "threshold for sorting in part0"]) + + tmp_func_basis, _ = self.get_method_name( + "sp", func=getattr(self, "func0"), basis=getattr(self, "basis0"), disp="D3" + ) + info.append( + [ + "justprint", + f"\nshort-notation:\n{tmp_func_basis} " "// GFNn-xTB (Input geometry)", + ] + ) + + # PART1: + info.append(["justprint", "\n" + "".ljust(int(PLENGTH / 2), "-")]) + info.append( + ["justprint", "CRE PRESCREENING - PART1".center(int(PLENGTH / 2), " ")] + ) + info.append(["justprint", "".ljust(int(PLENGTH / 2), "-")]) + info.append(["part1", "part1"]) + info.append(["nconf", "starting number of considered conformers"]) + info.append(["prog", "program for part1"]) + info.append(["func", "functional for initial evaluation"]) + info.append(["basis", "basis set for initial evaluation"]) + info.append(["evaluate_rrho", "calculate mRRHO contribution"]) + if self.evaluate_rrho: + info.append(["prog_rrho", "program for mRRHO contribution"]) + if self.prog_rrho == "xtb" or self.smgsolv2 == "gbsa_gsolv": + info.append(["part1_gfnv", "GFN version for mRRHO and/or GBSA_Gsolv"]) + info.append( + [ + "bhess", + "Apply constraint to input geometry during mRRHO calculation", + ] + ) + info.append(["printoption", "evalulate at different temperatures", "off"]) + info.append(["part1_threshold", "threshold for sorting in part1"]) + if self.solvent != "gas": + info.append(["smgsolv1", "solvent model for Gsolv contribution of part1"]) + # shortnotation: + tmp_rrho_method, _ = self.get_method_name( + "rrhoxtb", + bhess=self.bhess, + gfn_version=self.part1_gfnv, + sm=self.sm_rrho, + solvent=self.solvent, + ) + tmp_func_basis, _ = self.get_method_name( + "sp", func=getattr(self, "func"), basis=getattr(self, "basis"), disp="D3" + ) + if self.solvent != "gas": + info.append( + [ + "justprint", + f"\nshort-notation:\n{tmp_func_basis} + " + f"{str(getattr(self, 'smgsolv1')).upper()}[{self.solvent}] " + f"+ GmRRHO({tmp_rrho_method}) " + f"// GFNn-xTB (Input geometry)", + ] + ) + else: + info.append( + [ + "justprint", + f"\nshort-notation:\n{tmp_func_basis} " + f"+ GmRRHO({str(getattr(self, 'part1_gfnv')).upper()}) " + "// GFNn-xTB (Input geometry)", + ] + ) + if self.part2: + # PART2: + info.append(["justprint", "\n" + "".ljust(int(PLENGTH / 2), "-")]) + info.append( + ["justprint", "CRE OPTIMIZATION - PART2".center(int(PLENGTH / 2), " ")] + ) + info.append(["justprint", "".ljust(int(PLENGTH / 2), "-")]) + info.append(["part2", "part2"]) + info.append(["prog", "program"]) + info.append(["func", "functional for part2"]) + info.append(["basis", "basis set for part2"]) + info.append(["ancopt", "using xTB-optimizer for optimization"]) + if self.opt_spearman: + info.append(["opt_spearman", "using the new ensemble optimizer"]) + info.append( + [ + "opt_limit", + "completely optimize all conformers below this threshold", + ] + ) + info.append(["printoption", "spearmanthr", f"{self.spearmanthr:.3f}"]) + if self.ancopt and self.optlevel2 is not None: + info.append(["optlevel2", "optimization level in part2"]) + if self.solvent != "gas": + info.append(["sm2", "solvent model applied in the optimization"]) + if self.smgsolv2 not in (None, "sm2"): + info.append(["smgsolv2", "solvent model for Gsolv contribution"]) + info.append(["multitemp", "evalulate at different temperatures"]) + info.append( + ["part2_threshold", "Boltzmann sum threshold for sorting in part2"] + ) + info.append(["evaluate_rrho", "calculate mRRHO contribution"]) + if self.evaluate_rrho: + info.append(["prog_rrho", "program for mRRHO contribution"]) + if self.prog_rrho == "xtb": + info.append( + ["part2_gfnv", "GFN version for mRRHO and/or GBSA_Gsolv"] + ) + if self.bhess: + info.append( + [ + "bhess", + "Apply constraint to input geometry " + "during mRRHO calculation", + ] + ) + # shortnotation: + tmp_rrho_method, _ = self.get_method_name( + "rrhoxtb", + bhess=self.bhess, + gfn_version=self.part2_gfnv, + sm=self.sm_rrho, + solvent=self.solvent, + ) + tmp_func_basis, _ = self.get_method_name( + "sp", func=getattr(self, "func"), basis=getattr(self, "basis") + ) + if self.solvent != "gas": + info.append( + [ + "justprint", + f"\nshort-notation:\n{tmp_func_basis} + " + f"{str(getattr(self, 'smgsolv2')).upper()}[{self.solvent}] " + f"+ GmRRHO({tmp_rrho_method}) // " + f"{tmp_func_basis}" + f"[{str(getattr(self, 'sm2')).upper()}] ", + ] + ) + else: + info.append( + [ + "justprint", + f"\nshort-notation:\n{tmp_func_basis} " + f"+ GmRRHO({str(getattr(self, 'part2_gfnv')).upper()}) " + f"// {tmp_func_basis}", + ] + ) + # PART3: + if self.part3: + info.append(["justprint", "\n" + "".ljust(int(PLENGTH / 2), "-")]) + info.append( + ["justprint", "CRE REFINEMENT - PART3".center(int(PLENGTH / 2), " ")] + ) + info.append(["justprint", "".ljust(int(PLENGTH / 2), "-")]) + info.append(["part3", "part3"]) + info.append(["part3_threshold", "Boltzmann sum threshold employed"]) + info.append(["prog3", "program for part3"]) + info.append(["func3", "functional for part3"]) + info.append(["basis3", "basis set for part3"]) + if self.solvent != "gas": + info.append(["smgsolv3", "solvent model"]) + info.append(["multitemp", "evalulate at different temperatures"]) + info.append(["prog_rrho", "program for mRRHO contribution"]) + if self.prog_rrho == "xtb": + info.append(["part3_gfnv", "GFN version for mRRHO and/or GBSA_Gsolv"]) + if self.bhess: + info.append( + [ + "bhess", + "Apply constraint to input geometry during mRRHO calculation", + ] + ) + # shortnotation: + tmp_rrho_method, _ = self.get_method_name( + "rrhoxtb", + bhess=self.bhess, + gfn_version=self.part3_gfnv, + sm=self.sm_rrho, + solvent=self.solvent, + ) + tmp_func3_basis3, _ = self.get_method_name( + "sp", + func=getattr(self, "func3"), + basis=getattr(self, "basis3"), + disp="D3", + ) + tmp_func_basis, _ = self.get_method_name( + "sp", func=getattr(self, "func"), basis=getattr(self, "basis") + ) + if self.solvent != "gas": + info.append( + [ + "justprint", + f"\nshort-notation:\n{tmp_func3_basis3} + " + f"{str(getattr(self, 'smgsolv3')).upper()}[{self.solvent}] " + f"+ GmRRHO({tmp_rrho_method}) // " + f"{tmp_func_basis}" + f"[{str(getattr(self, 'sm2')).upper()}] ", + ] + ) + else: + info.append( + [ + "justprint", + f"\nshort-notation:\n{tmp_func3_basis3}" + f" + GmRRHO({str(getattr(self, 'part3_gfnv')).upper()}) " + f"// {tmp_func_basis}", + ] + ) + # NMR MODE + if self.nmrmode: + info.append(["justprint", "\n" + "".ljust(int(PLENGTH / 2), "-")]) + info.append( + ["justprint", " NMR MODE SETTINGS".center(int(PLENGTH / 2), " ")] + ) + info.append(["justprint", "".ljust(int(PLENGTH / 2), "-")]) + info.append(["part4", "part4"]) + info.append(["couplings", "calculate couplings (J)"]) + if self.couplings: + info.append(["prog4_j", "program for coupling calculations"]) + if self.solvent != "gas": + info.append(["sm4_j", "solvation model for coupling calculations"]) + info.append(["func_j", "functional for coupling calculation"]) + info.append(["basis_j", "basis set for coupling calculation"]) + info.append(["justprint", ""]) + info.append(["shieldings", "calculate shieldings (S)"]) + if self.shieldings: + info.append(["prog4_s", "program for shielding calculations"]) + if self.solvent != "gas": + info.append(["sm4_s", "solvation model for shielding calculations"]) + info.append(["func_s", "functional for shielding calculation"]) + info.append(["basis_s", "basis set for shielding calculation"]) + info.append(["justprint", ""]) + if getattr(self, "h_active"): + info.append(["h_active", "Calculating proton spectrum"]) + info.append(["h_ref", "reference for 1H"]) + if getattr(self, "c_active"): + info.append(["c_active", "Calculating carbon spectrum"]) + info.append(["c_ref", "reference for 13C"]) + if getattr(self, "f_active"): + info.append(["f_active", "Calculating fluorine spectrum"]) + info.append(["f_ref", "reference for 19F"]) + if getattr(self, "si_active"): + info.append(["si_active", "Calculating silicon spectrum"]) + info.append(["si_ref", "reference for 29Si"]) + if getattr(self, "p_active"): + info.append(["p_active", "Calculating phosphorus spectrum"]) + info.append(["p_ref", "reference for 31P"]) + info.append(["resonance_frequency", "resonance frequency"]) + # short notation: + + if self.optical_rotation: + info.append(["justprint", "\n" + "".ljust(int(PLENGTH / 2), "-")]) + info.append( + [ + "justprint", + "OPTICAL ROTATION MODE - PART5".center(int(PLENGTH / 2), " "), + ] + ) + info.append(["justprint", "".ljust(int(PLENGTH / 2), "-")]) + info.append(["optical_rotation", "part5"]) + info.append(["freq_or", "frequency in [nm]"]) + info.append(["func_or_scf", "functional for SCF"]) + info.append(["func_or", "functional for optical rotation"]) + info.append(["basis_or", "basis set for optical rotation"]) + if not self.part3: + info.append(["part2_threshold", "Boltzmann sum threshold employed"]) + elif self.part3: + info.append(["part3_threshold", "Boltzmann sum threshold employed"]) + + optionsexchange = {True: "on", False: "off"} + for item in info: + if item[0] == "justprint": + # print everything after justprint + print(item[1:][0]) + else: + if item[0] == "printoption": + option = item[2] + else: + option = getattr(self, item[0]) + if option is True or option is False: + option = optionsexchange[option] + elif isinstance(option, list): + option = [str(i) for i in option] + if len(str(option)) > 40: + length = 0 + reduced = [] + for i in option: + length += len(i) + 2 + if length < 40: + reduced.append(i) + reduced.append("...") + option = reduced + length = 0 + option = ", ".join(option) + print( + "{}: {:{digits}} {}".format( + item[1], "", option, digits=DIGILEN - len(item[1]) + ) + ) + print("END of parameters\n") + + def read_program_paths(self, configpath): + """ + Get absolute paths of external programs employed in enso + Read from the configuration file .censorc + """ + with open(configpath, "r") as inp: + stor = inp.readlines() + for line in stor: + if "ctd =" in line: + try: + self.external_paths["cosmorssetup"] = str(line.rstrip(os.linesep)) + except: + print( + "WARNING: Could not read settings for COSMO-RS from .censorc!" + ) + try: + normal = "DATABASE-COSMO/BP-TZVP-COSMO" + fine = "DATABASE-COSMO/BP-TZVPD-FINE" + if "fine" in self.external_paths["cosmorssetup"].lower(): + tmpdb = fine + else: + tmpdb = normal + self.external_paths["dbpath"] = os.path.join( + os.path.split( + self.external_paths["cosmorssetup"].split()[5].strip('"') + )[0], + tmpdb, + ) + os.path.isdir(self.external_paths["dbpath"]) + except Exception as e: + print(e) + print( + "WARNING: Could not read settings for COSMO-RS from " + ".censorc!\nMost probably there is a user " + "input error." + ) + if "cosmothermversion:" in line: + try: + self.external_paths["cosmothermversion"] = int(line.split()[1]) + except: + print( + "WARNING: Cosmothermversion could not be read! This " + "is necessary to prepare the cosmotherm.inp! " + ) + if "ORCA:" in line: + try: + self.external_paths["orcapath"] = str(line.split()[1]) + except: + print("WARNING: Could not read path for ORCA from .censorc!.") + if "ORCA version:" in line: + try: + tmp = line.split()[2] + tmp = tmp.split(".") + tmp.insert(1, ".") + tmp = "".join(tmp) + self.external_paths["orcaversion"] = tmp + except: + print("WARNING: Could not read ORCA version from .censorc!") + if "GFN-xTB:" in line: + try: + self.external_paths["xtbpath"] = str(line.split()[1]) + except: + print("WARNING: Could not read path for GFNn-xTB from .censorc!") + if shutil.which("xtb") is not None: + self.external_paths["xtbpath"] = shutil.which("xtb") + print( + "Going to use {} instead.".format( + self.external_paths["xtbpath"] + ) + ) + if "CREST:" in line: + try: + self.external_paths["crestpath"] = str(line.split()[1]) + except: + print("WARNING: Could not read path for CREST from .censorc!") + if shutil.which("crest") is not None: + self.external_paths["crestpath"] = shutil.which("crest") + print( + "Going to use {} instead.".format( + self.external_paths["crestpath"] + ) + ) + if "mpshift:" in line: + try: + self.external_paths["mpshiftpath"] = str(line.split()[1]) + except: + print("ẂARNING: Could not read path for mpshift from .censorc!") + if "escf:" in line: + try: + self.external_paths["escfpath"] = str(line.split()[1]) + except: + print("WARNING: Could not read path for escf from .censorc!") + if "$ENDPROGRAMS" in line: + break + + def needed_external_programs(self, config): + """ + Automatically checks which external programs are required for the + current run. + """ + requirements = {} + # xTB + if ( + config.prog_rrho == "xtb" + or config.part0 + or config.ancopt + or config.smgsolv2 in ("gbsa_gsolv", "alpb_gsolv") + ): + requirements["needxtb"] = True + # TM + if ( + config.prog == "tm" + or config.prog3 == "tm" + or config.prog4_j == "tm" + or config.prog4_s == "tm" + or config.smgsolv1 in ("cosmors", "cosmors-fine") + or config.smgsolv2 in ("cosmors", "cosmors-fine") + or config.smgsolv3 in ("cosmors", "cosmors-fine") + ): + requirements["needtm"] = True + requirements["needcefine"] = True + if config.part4 and (config.prog4_j == "tm" or config.prog4_s == "tm"): + if config.couplings: + requirements["needescf"] = True + if config.shieldings: + requirements["needmpshift"] = True + # COSMORS + if "cosmors" in {config.smgsolv1, config.smgsolv2, config.smgsolv3}: + requirements["needcosmors"] = True + elif "cosmors-fine" in {config.smgsolv1, config.smgsolv2, config.smgsolv3}: + requirements["needcosmors"] = True + # ORCA + if ( + config.prog == "orca" + or config.prog3 == "orca" + or config.prog4_j == "orca" + or config.prog4_s == "orca" + or config.smgsolv1 == "smd_gsolv" + or config.smgsolv2 == "smd_gsolv" + or config.smgsolv3 == "smd_gsolv" + ): + requirements["needorca"] = True + if config.run: + requirements["startenso"] = True + return requirements + + def _updateEnvironsettings(self, newsettings=None): + """ + Update the environmentsettings which is needed for e.g. Turbomole + calculations and is provided in each subroutine call. + """ + if newsettings is not None: + for key, value in newsettings.items(): + ENVIRON[key] = str(value) + + def processQMpaths(self, requirements, error_logical): + """ + print path at startup and return error if programs don't exist + """ + # print relevant Program paths: + print("\n" + "".ljust(DIGILEN, "-")) + print("PATHS of external QM programs".center(DIGILEN, " ")) + print("".ljust(DIGILEN, "-") + "\n") + print("The following program paths are used:") + if requirements.get("needorca", False): + print(" ORCA: {}".format(self.external_paths["orcapath"])) + print(" ORCA Version: {}".format(self.external_paths["orcaversion"])) + if requirements.get("needxtb", False): + print(" xTB: {}".format(self.external_paths["xtbpath"])) + if requirements.get("needcrest", False): + print(" CREST: {}".format(self.external_paths["crestpath"])) + if requirements.get("needtm", False): + tmpath = shutil.which("ridft") + if tmpath is not None: + tmpath = os.path.dirname(tmpath) + else: + tmpath = "None" + print(" TURBOMOLE: {}".format(tmpath)) + if requirements.get("needescf", False): + print(" escf: {}".format(self.external_paths["escfpath"])) + if requirements.get("needmpshift", False): + print(" mpshift: {}".format(self.external_paths["mpshiftpath"])) + if requirements.get("needcosmors", False): + try: + tmp = self.external_paths["cosmorssetup"].split() + if len(tmp) == 9: + print(" Setup of COSMO-RS:") + print(" {}".format(" ".join(tmp[0:3]))) + print(" {}".format(" ".join(tmp[3:6]))) + print(" {}".format(" ".join(tmp[6:9]))) + else: + print( + f" Setup of COSMO-RS: {str(self.external_paths['cosmorssetup'])}" + ) + except: + print( + " Setup of COSMO-RS: {}".format( + str(self.external_paths["cosmorssetup"]) + ) + ) + print( + f" Using {self.external_paths['dbpath']}\n" + " as path to the COSMO-RS DATABASE." + ) + print("") + # Check if paths of needed programs exist: + if requirements.get("needcrest", False): + if ( + self.external_paths["crestpath"] is None + or shutil.which(self.external_paths["crestpath"]) is None + ): + print("ERROR: path for CREST is not correct!") + error_logical = True + # xTB + if requirements.get("needxtb", False): + if ( + self.external_paths["xtbpath"] is None + or shutil.which(self.external_paths["xtbpath"]) is None + ): + print("ERROR: path for xTB is not correct!") + error_logical = True + try: + ENVIRON["OMP_NUM_THREADS"] = "{:d}".format(self.omp) + except: + print("ERROR: can not set omp for xTB calculation!") + # ORCA + if requirements.get("needorca", False): + if ( + self.external_paths["orcapath"] is None + or shutil.which(os.path.join(self.external_paths["orcapath"], "orca")) + is None + ): + print("ERROR: path for ORCA is not correct!") + error_logical = True + # cefine + if requirements.get("needcefine", False): + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + # print('running in a PyInstaller bundle') + bundle_dir = getattr( + sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__)) + ) + path_to_cefine = os.path.abspath(os.path.join(bundle_dir, "cefine")) + if not os.path.exists(path_to_cefine): + path_to_cefine = shutil.which("cefine") + else: + # print('running in a normal Python process') + path_to_cefine = shutil.which("cefine") + + if os.path.exists(path_to_cefine): + print(" Using cefine from {}".format(path_to_cefine)) + self.external_paths["cefinepath"] = path_to_cefine + else: + print( + "ERROR: cefine (the commandline program for define) has not been found!" + ) + print(f"{'':{7}}all programs needing TM can not start!") + error_logical = True + # TM + if requirements.get("needtm", False): + # preparation of parallel calculation with TM + try: + if ENVIRON.get("PARA_ARCH", None) == "SMP": + try: + ENVIRON["PARNODES"] = str(self.omp) + ENVIRON["OMP_NUM_THREADS"] = "{:d}".format(self.omp) + print( + " PARNODES for TM or COSMO-RS calculation was set " + "to {}".format(ENVIRON["PARNODES"]) + ) + except: + print("ERROR: PARNODES can not be changed!") + error_logical = True + raise + else: + print( + "ERROR: PARA_ARCH has to be set to SMP for parallel TM " + "calculations!" + ) + if self.run: + error_logical = True + except: + print( + "ERROR: PARA_ARCH has to be set to SMP and PARNODES have to " + "be set\n for parallel TM calculations!." + ) + if requirements.get("startenso", False): + error_logical = True + raise + if requirements.get("needescf", False): + if ( + self.external_paths["escfpath"] is None + or shutil.which(self.external_paths["escfpath"]) is None + ): + print("ERROR: path for escf is not correct!") + error_logical = True + if requirements.get("needmpshift", False): + if ( + self.external_paths["mpshiftpath"] is None + or shutil.which(self.external_paths["mpshiftpath"]) is None + ): + print("ERROR: path for mpshift is not correct!") + error_logical = True + # COSMORS + if requirements.get("needcosmors", False): + if self.external_paths["cosmorssetup"] is None: + print("ERROR: Set up for COSMO-RS has to be written to .censorc!") + error_logical = True + if self.external_paths["cosmothermversion"] is None: + print("ERROR: Version of COSMO-RS has to be written to .censorc!") + error_logical = True + if shutil.which("cosmotherm") is not None: + print(" Using COSMOtherm from {}".format(shutil.which("cosmotherm"))) + else: + print("ERROR: COSMOtherm has not been found!") + error_logical = True + # update cfg.external paths + external_paths.update(self.external_paths) + return error_logical + + def write_rcfile(self, pathtofile): + """ + write new global configruation file into the current directroy. + """ + args_key = {v: k for k, v in self.key_args_dict.items()} + with open(pathtofile, "w", newline=None) as outdata: + outdata.write("$CENSO global configuration file: .censorc\n") + outdata.write(f"$VERSION:{__version__} \n") + outdata.write("\n") + outdata.write("ORCA: /path/excluding/binary/\n") + outdata.write("ORCA version: 4.2.1\n") + outdata.write("GFN-xTB: /path/including/binary/xtb-binary\n") + outdata.write("CREST: /path/including/binary/crest-binary\n") + outdata.write("mpshift: /path/including/binary/mpshift-binary\n") + outdata.write("escf: /path/including/binary/escf-binary\n") + outdata.write("\n") + outdata.write("#COSMO-RS\n") + outdata.write( + "ctd = BP_TZVP_C30_1601.ctd cdir = " + '"/software/cluster/COSMOthermX16/COSMOtherm/CTDATA-FILES" ldir = ' + '"/software/cluster/COSMOthermX16/COSMOtherm/CTDATA-FILES"\n' + ) + outdata.write("cosmothermversion: 16\n") + outdata.write("$ENDPROGRAMS\n\n") + outdata.write("$CRE SORTING SETTINGS:\n") + outdata.write("$GENERAL SETTINGS:\n") + for key in OrderedDict(self.defaults_refine_ensemble_general): + value = self._exchange_onoff( + OrderedDict(self.defaults_refine_ensemble_general)[key]["default"], + reverse=True, + ) + options = self.value_options.get(key, "possibilities") + if key == "nconf" and value is None: + value = "all" + key = args_key.get(key, key) + outdata.write(format_line(key, value, options)) + outdata.write("\n$PART0 - CHEAP-PRESCREENING - SETTINGS:\n") + for key in OrderedDict(self.defaults_refine_ensemble_part0): + value = self._exchange_onoff( + OrderedDict(self.defaults_refine_ensemble_part0)[key]["default"], + reverse=True, + ) + options = self.value_options.get(key, "possibilities") + key = args_key.get(key, key) + outdata.write(format_line(key, value, options)) + outdata.write("\n$PART1 - PRESCREENING - SETTINGS:\n") + outdata.write("# func and basis is set under GENERAL SETTINGS\n") + for key in OrderedDict(self.defaults_refine_ensemble_part1): + value = self._exchange_onoff( + OrderedDict(self.defaults_refine_ensemble_part1)[key]["default"], + reverse=True, + ) + options = self.value_options.get(key, "possibilities") + key = args_key.get(key, key) + outdata.write(format_line(key, value, options)) + outdata.write("\n$PART2 - OPTIMIZATION - SETTINGS:\n") + outdata.write("# func and basis is set under GENERAL SETTINGS\n") + for key in OrderedDict(self.defaults_refine_ensemble_part2): + value = self._exchange_onoff( + OrderedDict(self.defaults_refine_ensemble_part2)[key]["default"], + reverse=True, + ) + options = self.value_options.get(key, "possibilities") + key = args_key.get(key, key) + outdata.write(format_line(key, value, options)) + outdata.write("\n$PART3 - REFINEMENT - SETTINGS:\n") + for key in OrderedDict(self.defaults_refine_ensemble_part3): + value = self._exchange_onoff( + OrderedDict(self.defaults_refine_ensemble_part3)[key]["default"], + reverse=True, + ) + options = self.value_options.get(key, "possibilities") + key = args_key.get(key, key) + outdata.write(format_line(key, value, options)) + outdata.write("\n$NMR PROPERTY SETTINGS:\n") + outdata.write("$PART4 SETTINGS:\n") + for key in OrderedDict(self.defaults_nmrprop_part4): + value = self._exchange_onoff( + OrderedDict(self.defaults_nmrprop_part4)[key]["default"], + reverse=True, + ) + options = self.value_options.get(key, "possibilities") + key = args_key.get(key, key) + outdata.write(format_line(key, value, options)) + outdata.write("\n$OPTICAL ROTATION PROPERTY SETTINGS:\n") + outdata.write("$PART5 SETTINGS:\n") + for key in OrderedDict(self.defaults_optical_rotation_part5): + value = self._exchange_onoff( + OrderedDict(self.defaults_optical_rotation_part5)[key]["default"], + reverse=True, + ) + options = self.value_options.get(key, "possibilities") + key = args_key.get(key, key) + outdata.write(format_line(key, value, options)) + outdata.write("$END CENSORC\n") + + def write_enso_inp(self, path=None): + """ + Write file "enso.inp" which is the control file for the calculation + """ + if path is None: + path = self.cwd + with open(os.path.join(path, "enso.inp"), "w", newline=None) as inp: + inp.write("$ File: enso.inp settings of current calculation\n") + data = self.provide_runinfo(extend=False) + for key in data.keys(): + value = data[key] + options = self.value_options.get(key, "possibilities") + if key == "nconf" and value is None: + value = "all" + value = self._exchange_onoff(value, reverse=True) + # limit printout of possibilities + if len(str(options)) > 80: + length = 0 + reduced = [] + for item in options: + length += len(item) + 2 + if length < 80: + reduced.append(item) + reduced.append("...") + options = reduced + length = 0 + inp.write( + "{}: {:{digits}} # {}\n".format( + str(key), str(value), options, digits=30 - len(str(key)) + ) + ) + inp.write("$end\n") + + def read_json(self, path, silent=False): + """ + Reading stored data on conformers and information on settings of + previous run. + """ + if os.path.isfile(path): + if not silent: + print("Reading file: {}\n".format(os.path.basename(path))) + try: + with open(path, "r", encoding=CODING, newline=None) as inp: + save_data = json.load(inp, object_pairs_hook=OrderedDict) + except (ValueError, TypeError, FileNotFoundError): + print("Your Jsonfile (enso.json) is corrupted!\n") + time.sleep(0.02) + raise + return save_data + + def write_json(self, path, conformers, settings, outfile="enso.json"): + """ + Dump conformer data and settings information of current run to json file + """ + data = {} + if not isinstance(settings, OrderedDict): + data["settings"] = vars(settings) + else: + data["settings"] = settings + try: + conformers.sort(key=lambda x: int(x["id"])) + except: + pass + for conf in conformers: + if not isinstance(conf, OrderedDict): + data[conf.id] = vars(conf) + else: + data[conf["id"]] = conf + with open(os.path.join(path, outfile), "w") as out: + json.dump(data, out, indent=4, sort_keys=False) diff --git a/censo_qm/nmrproperties.py b/censo_qm/nmrproperties.py new file mode 100644 index 0000000..bbddf4e --- /dev/null +++ b/censo_qm/nmrproperties.py @@ -0,0 +1,765 @@ +""" +module for the calculation of shiedling and coupling constants +""" + +import os +import shutil +import sys +from random import normalvariate +from multiprocessing import JoinableQueue as Queue +from .cfg import PLENGTH, DIGILEN, AU2KCAL +from .parallel import run_in_parallel +from .orca_job import OrcaJob +from .tm_job import TmJob +from .utilities import ( + calc_boltzmannweights, + printout, + print_block, + new_folders, + last_folders, + print, + write_anmrrc, + calc_std_dev, +) + + +def read_chemeq(path): + """read chemeq from anmr_nucinfo""" + with open(path, "r") as inp: + data = inp.readlines() + nat = int(data[0].split()[0]) + tmpeq = {} + for i in range(1, nat * 2 + 1): + if i % 2 != 0: + nextatom = int(data[i].split()[0]) + elif i % 2 == 0: + equalatoms = [int(x) for x in data[i].split()] + tmpeq[nextatom] = sorted(equalatoms) + return tmpeq + + +def read_exp_ref(path): + """read experimental reference shifts""" + with open(os.path.join(path, ".ref"), "r") as inp: + data = inp.readlines() + expref = {} + for line in data[1:]: + if line not in ["\n", "\r\n"]: + try: + expref[int(line.split()[0])] = float(line.split()[1]) + except ValueError: + pass + return expref + + +def get_atom(path): + """read coord""" + with open(os.path.join(path, "coord"), "r") as inp: + data = inp.readlines() + element = {} + i = 1 + for line in data[1:]: + if "$" in line: # stop at $end ... + break + element[i] = str(line.split()[3].lower()) + i += 1 + return element + + +def average_shieldings(config, calculate, element_ref_shield, energy, solv, rrho): + """ + Read chemical equivalence and Boltzmann average the calculated shielding constants + """ + path_anmr_nucinfo = os.path.join(config.cwd, "anmr_nucinfo") + if not os.path.isfile(path_anmr_nucinfo): + print(f"File anmr_nucinfo (generated by CREST) is not present!") + return + chemeq = read_chemeq(path_anmr_nucinfo) + averaged = {} + element = {} + + sigma_std_dev = {} + for i in range(1, config.nat + 1): + sigma_std_dev[i] = [] + + for conf in calculate: + # get shielding constants + if not element: + element = get_atom( + os.path.normpath(os.path.join(config.cwd, "CONF" + str(conf.id), "NMR")) + ) + for atom in conf.shieldings.keys(): + sigma = sum( + [conf.shieldings.get(eq_atom, 0.0) for eq_atom in chemeq[atom]] + ) / len(chemeq[atom]) + averaged[atom] = conf.bm_weight * sigma + averaged.get(atom, 0.0) + + for _ in range(1000): + for conf in calculate: + conf.calc_free_energy(e=energy, solv=solv, rrho=rrho) + conf.free_energy += normalvariate( + 0.0, conf.lowlevel_gsolv_compare_info["std_dev"] + ) + calculate = calc_boltzmannweights(calculate, "free_energy", config.temperature) + for conf in calculate: + # get shielding constants + if not element: + element = get_atom( + os.path.normpath( + os.path.join(config.cwd, "CONF" + str(conf.id), "NMR") + ) + ) + for atom in conf.shieldings.keys(): + sigma = sum( + [conf.shieldings.get(eq_atom, 0.0) for eq_atom in chemeq[atom]] + ) / len(chemeq[atom]) + sigma_std_dev[atom].append( + conf.bm_weight * sigma + averaged.get(atom, 0.0) + ) + + print("\nAveraged shielding constants:") + print("# in coord element σ(sigma) SD(σ based on SD Gsolv) shift") + print("".ljust(int(70), "-")) + maxsigma = max([len(str(sigma).split(".")[0]) for sigma in averaged.values()]) + 5 + make_shift = ( + lambda atom: f"{-sigma+element_ref_shield.get(element[atom], 0.0):> {maxsigma}.2f}" + if (element_ref_shield.get(element[atom], None) is not None) + else "None" + ) + for atom, sigma in averaged.items(): + try: + std_dev = calc_std_dev(sigma_std_dev[atom]) + except Exception as e: + print(e) + std_dev = 0.0 + try: + print( + f"{atom:< {10}} {element[atom]:^{7}} {sigma:> {maxsigma}.2f} " + f"{std_dev:^ 24.6f} {make_shift(atom):>5}" + ) + except: + print(f"{atom:< {10}} {element[atom]:^{7}} {sigma:> {maxsigma}.2f}") + print("".ljust(int(70), "-")) + + +def part4(config, conformers, store_confs, ensembledata): + """ + Calculate nmr properties: shielding and coupling constants on the populated + conformers (either directly from part2 OPTIMIZATION or after REFINEMENT + (part3)) + """ + save_errors = [] + print("\n" + "".ljust(PLENGTH, "-")) + print("NMR MODE - PART4".center(PLENGTH, " ")) + print("".ljust(PLENGTH, "-") + "\n") + # print flags for part3 + info = [] + info.append(["couplings", "calculate coupling constants"]) + if config.couplings: + info.append(["prog4_j", "prog4J - program for coupling constant calculation"]) + info.append(["func_j", "funcJ - functional for coupling constant calculation"]) + info.append(["basis_j", "basisJ - basis for coupling constant calculation"]) + if config.solvent != "gas": + info.append(["sm4_j", "sm4J - solvent model for the coupling calculation"]) + info.append(["shieldings", "calculate shielding constants σ"]) + if config.shieldings: + info.append(["prog4_s", "prog4S - program for shielding constant calculation"]) + info.append(["func_s", "funcS - functional for shielding constant calculation"]) + info.append(["basis_s", "basisS - basis for shielding constant calculation"]) + if config.solvent != "gas": + info.append(["sm4_s", "sm4S - solvent model for the shielding calculation"]) + info.append(["resonance_frequency", "spectrometer frequency"]) + # active nuclei + + optionsexchange = {True: "on", False: "off"} + for item in info: + if item[0] == "justprint": + print(item[1:][0]) + else: + if item[0] == "printoption": + option = item[2] + else: + option = getattr(config, item[0]) + if option is True or option is False: + option = optionsexchange[option] + elif isinstance(option, list): + option = [str(i) for i in option] + if len(str(option)) > 40: + length = 0 + reduced = [] + for i in option: + length += len(i) + 2 + if length < 40: + reduced.append(i) + reduced.append("...") + option = reduced + length = 0 + option = ", ".join(option) + print( + "{}: {:{digits}} {}".format( + item[1], "", option, digits=DIGILEN - len(item[1]) + ) + ) + print("") + # end print + + calculate = [] # has to be calculated in this run + prev_calculated = [] # was already calculated in a previous run + try: + store_confs + except NameError: + store_confs = [] # stores all confs which are sorted out! + + # setup queues + q = Queue() + resultq = Queue() + + # sort conformers: + for conf in list(conformers): + if conf.removed: + store_confs.append(conformers.pop(conformers.index(conf))) + print(f"CONF{conf.id} is removed as requested by the user!") + continue + if ( + conf.part_info["part2"] == "passed" + and conf.optimization_info["info"] == "calculated" + ): + if not config.part3: + # part3 is not calculated use boltzmann weights directly from part2 + energy = "lowlevel_sp_info" + rrho = "lowlevel_grrho_info" + gsolv = "lowlevel_gsolv_info" + boltzmannthr = config.part2_threshold + elif config.part3: + # calc boltzmann weights from part3 + energy = "highlevel_sp_info" + rrho = "highlevel_grrho_info" + gsolv = "highlevel_gsolv_info" + boltzmannthr = config.part3_threshold + else: + print("UNEXPECTED BEHAVIOUR") + mol = conformers.pop(conformers.index(conf)) + if getattr(conf, energy)["info"] != "calculated": + store_confs.append(mol) + continue + elif getattr(conf, rrho)["info"] != "calculated" and config.evaluate_rrho: + store_confs.append(mol) + continue + elif ( + getattr(conf, gsolv)["info"] != "calculated" and config.solvent != "gas" + ): + store_confs.append(mol) + continue + else: + calculate.append(mol) + else: + print( + f"WARNING: CONF{conf.id} has not been optimized (part2)! " + f"Removing CONF{conf.id}" + ) + conf = conformers.pop(conformers.index(conf)) + store_confs.append(conf) + + if not calculate and not prev_calculated: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + calculate.sort(key=lambda x: int(x.id)) + print("Considering the following conformers:") + print_block(["CONF" + str(i.id) for i in calculate]) + + # Calculate boltzmann weight for confs: + if not config.part3: + if not config.evaluate_rrho: + rrho = None + else: + rrho_method, _ = config.get_method_name( + "rrhoxtb", + bhess=config.bhess, + gfn_version=config.part2_gfnv, + sm=config.sm_rrho, + solvent=config.solvent, + ) + if config.solvent == "gas": + gsolv = None + energy_method, _ = config.get_method_name( + "xtbopt", + func=config.func, + basis=config.basis, + sm=config.smgsolv2, + gfn_version=config.part2_gfnv, + solvent=config.solvent, + ) + else: + if config.smgsolv2 in ("cosmors", "cosmors-fine"): + tmp_name = "cosmors" + elif config.smgsolv2 in ("alpb_gsolv", "gbsa_gsolv", "smd_gsolv"): + tmp_name = config.smgsolv2 + else: + tmp_name = "sp_implicit" + energy_method, solv_method = config.get_method_name( + tmp_name, + func=config.func, + basis=config.basis, + sm=config.smgsolv2, + gfn_version=config.part2_gfnv, + solvent=config.solvent, + ) + elif config.part3: + if not config.evaluate_rrho: + rrho = None + else: + rrho_method, _ = config.get_method_name( + "rrhoxtb", + bhess=config.bhess, + gfn_version=config.part3_gfnv, + sm=config.sm_rrho, + solvent=config.solvent, + ) + if config.solvent == "gas": + gsolv = None + energy_method, _ = config.get_method_name( + "xtbopt", + func=config.func3, + basis=config.basis3, + sm=config.smgsolv3, + gfn_version=config.part3_gfnv, + solvent=config.solvent, + ) + else: + if config.smgsolv3 in ("cosmors", "cosmors-fine"): + tmp_name = "cosmors" + elif config.smgsolv3 in ("alpb_gsolv", "gbsa_gsolv", "smd_gsolv"): + tmp_name = config.smgsolv3 + else: + tmp_name = "sp_implicit" + energy_method, solv_method = config.get_method_name( + tmp_name, + func=config.func3, + basis=config.basis3, + sm=config.smgsolv3, + gfn_version=config.part3_gfnv, + solvent=config.solvent, + ) + + for conf in calculate: + conf.calc_free_energy(e=energy, solv=gsolv, rrho=rrho) + calculate = calc_boltzmannweights(calculate, "free_energy", config.temperature) + try: + minfree = min([i.free_energy for i in calculate if i is not None]) + except ValueError: + raise + for conf in calculate: + conf.rel_free_energy = (conf.free_energy - minfree) * AU2KCAL + calculate.sort(key=lambda x: int(x.id)) + + # printout for part4 ------------------------------------------------------- + print("\n" + "".ljust(int(PLENGTH / 2), "-")) + print("* Gibbs free energies used in part4 *".center(int(PLENGTH / 2), " ")) + print("".ljust(int(PLENGTH / 2), "-") + "\n") + columncall = [ + lambda conf: "CONF" + str(getattr(conf, "id")), + lambda conf: getattr(conf, energy)["energy"], + lambda conf: getattr(conf, gsolv)["energy"], + lambda conf: getattr(conf, rrho)["energy"], + lambda conf: getattr(conf, "free_energy"), + lambda conf: getattr(conf, "rel_free_energy"), + lambda conf: getattr(conf, "bm_weight") * 100, + ] + columnheader = [ + "CONF#", + "E [Eh]", + "Gsolv [Eh]", + "GmRRHO [Eh]", + "Gtot", + "ΔGtot", + "Boltzmannweight", + ] + columndescription = [ + "", + "", + "", + "", + "[Eh]", + "[kcal/mol]", + f" % at {config.temperature:.2f} K", + ] + columnformat = ["", (12, 7), (12, 7), (12, 7), (12, 7), (5, 2), (5, 2)] + columndescription[1] = energy_method + columndescription[2] = solv_method + columndescription[3] = rrho_method + if not config.evaluate_rrho or config.solvent == "gas": + if not config.evaluate_rrho: + # ignore rrho in printout + columncall.pop(3) + columnheader.pop(3) + columndescription.pop(3) + columnformat.pop(3) + if config.solvent == "gas": + columncall.pop(2) + columnheader.pop(2) + columndescription.pop(2) + columnformat.pop(2) + + printout( + os.path.join(config.cwd, "part4.dat"), + columncall, + columnheader, + columndescription, + columnformat, + calculate, + minfree, + ) + calculate.sort(reverse=True, key=lambda x: float(x.bm_weight)) + sumup = 0.0 + for conf in list(calculate): + sumup += conf.bm_weight + if sumup >= boltzmannthr: + if conf.bm_weight < (1 - boltzmannthr): + store_confs.append(calculate.pop(calculate.index(conf))) + print(f"\nConformers that are below the Boltzmann-thr of {boltzmannthr}:") + print_block(["CONF" + str(i.id) for i in calculate]) + + # create NMR folder + folder = "NMR" + save_errors, store_confs, calculate = new_folders( + config.cwd, calculate, folder, save_errors, store_confs + ) + # need to copy optimized coord to folder + for conf in list(calculate): + tmp1 = os.path.join(config.cwd, "CONF" + str(conf.id), config.func, "coord") + tmp2 = os.path.join("CONF" + str(conf.id), folder, "coord") + try: + shutil.copy(tmp1, tmp2) + except FileNotFoundError: + print("ERROR can't copy optimized geometry!") + store_confs.append(calculate.pop(calculate.index(conf))) + if config.couplings: + print("\nPerforming coupling constant calculations:") + # check if J calculated before! + for conf in list(calculate): + if getattr(conf, "nmr_coupling_info")["info"] == "calculated": + prev_calculated.append(calculate.pop(calculate.index(conf))) + elif getattr(conf, "nmr_coupling_info")["info"] == "failed": + store_confs.append(calculate.pop(calculate.index(conf))) + else: + # still in calculate + pass + + if not calculate + prev_calculated: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + instruction_j = { + "jobtype": "couplings_sp", + "prepinfo": ["high+"], + "func": config.func_j, + "basis": config.basis_j, + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": config.solvent, + "sm": config.sm4_j, + "success": False, + "omp": config.omp, + # nmractive nuclei + "h_active": config.h_active, + "c_active": config.c_active, + "f_active": config.f_active, + "p_active": config.p_active, + "si_active": config.si_active, + } + if config.prog4_j == "orca": + job = OrcaJob + instruction_j["progpath"] = config.external_paths["orcapath"] + instruction_j["method"], _ = config.get_method_name( + instruction_j["jobtype"], + func=instruction_j["func"], + basis=instruction_j["basis"], + sm=instruction_j["sm"], + solvent=instruction_j["solvent"], + prog=config.prog4_j, + ) + elif config.prog4_j == "tm": + job = TmJob + instruction_j["progpath"] = config.external_paths["escfpath"] + instruction_j["method"], _ = config.get_method_name( + instruction_j["jobtype"], + func=instruction_j["func"], + basis=instruction_j["basis"], + sm=instruction_j["sm"], + solvent=instruction_j["solvent"], + prog=config.prog4_j, + ) + # escf no mgrid!!!! + elif config.prog4_j == "adf": + instruction_j["method"], _ = config.get_method_name( + instruction_j["jobtype"], + func=instruction_j["func"], + basis=instruction_j["basis"], + sm=instruction_j["sm"], + solvent=instruction_j["solvent"], + prog=config.prog4_j, + ) + check = {True: "was successful", False: "FAILED"} + pl = config.lenconfx + 4 + len(str("/" + folder)) + if calculate: + calculate = run_in_parallel( + config, + q, + resultq, + job, + config.maxthreads, + calculate, + instruction_j, + folder, + ) + for conf in list(calculate): + line = ( + f"Coupling constant calculation {check[conf.job['success']]}" + f" for {last_folders(conf.job['workdir'], 2):>{pl}}: " + ) + print(line) + if not conf.job["success"]: + save_errors.append(line) + conf.nmr_coupling_info["info"] = "failed" + conf.nmr_coupling_info["method"] = instruction_j["method"] + conf.nmr_coupling_info["h_active"] = instruction_j["h_active"] + conf.nmr_coupling_info["c_active"] = instruction_j["c_active"] + conf.nmr_coupling_info["f_active"] = instruction_j["f_active"] + conf.nmr_coupling_info["si_active"] = instruction_j["si_active"] + conf.nmr_coupling_info["p_active"] = instruction_j["p_active"] + conf.part_info["part4"] = "refused" + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.nmr_coupling_info["info"] = "calculated" + conf.nmr_coupling_info["method"] = instruction_j["method"] + conf.nmr_coupling_info["h_active"] = instruction_j["h_active"] + conf.nmr_coupling_info["c_active"] = instruction_j["c_active"] + conf.nmr_coupling_info["f_active"] = instruction_j["f_active"] + conf.nmr_coupling_info["si_active"] = instruction_j["si_active"] + conf.nmr_coupling_info["p_active"] = instruction_j["p_active"] + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + + if prev_calculated: + for conf in list(prev_calculated): + conf.job["workdir"] = os.path.normpath( + os.path.join(config.cwd, "CONF" + str(conf.id), folder) + ) + line = ( + f"Coupling constant calculation {check[True]}" + f" for {last_folders(conf.job['workdir'], 2):>{pl}}: " + ) + print(line) + calculate.append(prev_calculated.pop(prev_calculated.index(conf))) + + for conf in calculate: + conf.reset_job_info() + if not calculate: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + if config.shieldings: + print("\nPerforming shielding constant calculations:") + # start shielding constants + # check if S calculated before! + for conf in list(calculate): + if getattr(conf, "nmr_shielding_info")["info"] == "calculated": + prev_calculated.append(calculate.pop(calculate.index(conf))) + elif getattr(conf, "nmr_shielding_info")["info"] == "failed": + store_confs.append(calculate.pop(calculate.index(conf))) + for conf in calculate: + conf.reset_job_info() + if not calculate + prev_calculated: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + instruction_s = { + "jobtype": "shieldings_sp", + "prepinfo": ["high+"], + "func": config.func_s, + "basis": config.basis_s, + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": config.solvent, + "sm": config.sm4_s, + "success": False, + "omp": config.omp, + # nmractive nuclei + "h_active": config.h_active, + "c_active": config.c_active, + "f_active": config.f_active, + "p_active": config.p_active, + "si_active": config.si_active, + } + + if config.basis_j != config.basis_s: + # do a new calculation + # cefine if turbomole + instruction_s["prepinfo"] = ["high+"] + if config.prog4_j != config.prog4_s: + # do a new calculation + # cefine if turbomole + instruction_s["prepinfo"] = ["high+"] + if (config.basis_j == config.basis_s) and (config.prog4_j == config.prog4_s): + if config.func_j == config.func_s and config.couplings: + instruction_s["prepinfo"] = [] + # don't do single-point + instruction_s["jobtype"] = "shieldings" + elif config.func_j != config.func_s and config.couplings: + instruction_s["prepinfo"] = ["high+"] + # use already converged mos as start mos + if config.prog4_s == "tm": + instruction_s["copymos"] = "mos_j" + instruction_s["jobtype"] = "shieldings_sp" + + if config.prog4_s == "orca": + job = OrcaJob + instruction_s["progpath"] = config.external_paths["orcapath"] + instruction_s["method"], _ = config.get_method_name( + instruction_s["jobtype"], + func=instruction_s["func"], + basis=instruction_s["basis"], + sm=instruction_s["sm"], + solvent=instruction_s["solvent"], + prog=config.prog4_s, + ) + elif config.prog4_s == "tm": + job = TmJob + instruction_s["progpath"] = config.external_paths["mpshiftpath"] + instruction_s["method"], _ = config.get_method_name( + instruction_s["jobtype"], + func=instruction_s["func"], + basis=instruction_s["basis"], + sm=instruction_s["sm"], + solvent=instruction_s["solvent"], + prog=config.prog4_j, + ) + elif config.prog4_s == "adf": + instruction_s["method"], _ = config.get_method_name( + instruction_s["jobtype"], + func=instruction_s["func"], + basis=instruction_s["basis"], + sm=instruction_s["sm"], + solvent=instruction_s["solvent"], + prog=config.prog4_j, + ) + check = {True: "was successful", False: "FAILED"} + pl = config.lenconfx + 4 + len(str("/" + folder)) + if calculate: + calculate = run_in_parallel( + config, + q, + resultq, + job, + config.maxthreads, + calculate, + instruction_s, + folder, + ) + for conf in list(calculate): + line = ( + f"Shielding constant calculation {check[conf.job['success']]}" + f" for {last_folders(conf.job['workdir'], 2):>{pl}}: " + ) + print(line) + if not conf.job["success"]: + save_errors.append(line) + conf.nmr_shielding_info["info"] = "failed" + conf.nmr_shielding_info["method"] = instruction_s["method"] + conf.nmr_shielding_info["h_active"] = instruction_s["h_active"] + conf.nmr_shielding_info["c_active"] = instruction_s["c_active"] + conf.nmr_shielding_info["f_active"] = instruction_s["f_active"] + conf.nmr_shielding_info["si_active"] = instruction_s["si_active"] + conf.nmr_shielding_info["p_active"] = instruction_s["p_active"] + conf.part_info["part4"] = "refused" + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.nmr_shielding_info["info"] = "calculated" + conf.nmr_shielding_info["method"] = instruction_s["method"] + conf.nmr_shielding_info["h_active"] = instruction_s["h_active"] + conf.nmr_shielding_info["c_active"] = instruction_s["c_active"] + conf.nmr_shielding_info["f_active"] = instruction_s["f_active"] + conf.nmr_shielding_info["si_active"] = instruction_s["si_active"] + conf.nmr_shielding_info["p_active"] = instruction_s["p_active"] + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + if prev_calculated: + for conf in list(prev_calculated): + conf.job["workdir"] = os.path.normpath( + os.path.join(config.cwd, "CONF" + str(conf.id), folder) + ) + line = ( + f"Shielding constant calculation {check[True]}" + f" for {last_folders(conf.job['workdir'], 2):>{pl}}: " + ) + print(line) + calculate.append(prev_calculated.pop(prev_calculated.index(conf))) + + # write anmr_enso output! + print("\nGenerating file anmr_enso for processing with the ANMR program.") + for conf in calculate: + conf.calc_free_energy(e=energy, solv=gsolv, rrho=rrho) + calculate = calc_boltzmannweights(calculate, "free_energy", config.temperature) + try: + length = max([str(i.id) for i in calculate]) + if length < 4: + length = 4 + fmtenergy = max([len("{:.7f}".format(i.free_energy)) for i in calculate]) + except: + length = 6 + fmtenergy = 10 + with open(os.path.join(config.cwd, "anmr_enso"), "w", newline=None) as out: + out.write( + f"{'ONOFF':5} {'NMR':^{length}} {'CONF':^{length}} {'BW':6} " + f"{'Energy':{fmtenergy}} {'Gsolv':7} {'mRRHO':7} {'gi':7}\n" + ) + for conf in calculate: + out.write( + f"{1:<5} {conf.id:{length}} {conf.id:{length}} " + f"{conf.bm_weight:.4f} {getattr(conf, energy)['energy']:.5f} " + f"{getattr(conf, gsolv)['energy']:.5f} " + f"{getattr(conf, rrho)['energy']:.5f} " + f"{conf.gi:.3f}\n" + ) + + # write .anmrrc + print("\nWriting .anmrrc!") + element_ref_shield = write_anmrrc(config) + + print("\nGenerating plain nmrprop.dat files for each populated conformer.") + print("These files contain all calculated shielding and coupling constants.") + print("The files can be read by ANMR using the keyword '-plain'.\n") + # write generic: + instructgeneric = {"jobtype": "genericout", "nat": int(config.nat)} + calculate = run_in_parallel( + config, q, resultq, job, config.maxthreads, calculate, instructgeneric, folder + ) + + # printout the averaged shielding constants + average_shieldings(config, calculate, element_ref_shield, energy, gsolv, rrho) + + for conf in calculate: + conf.reset_job_info() + + # end printout for part4 + print("\n\n") + return config, calculate, store_confs, ensembledata diff --git a/censo_qm/opticalrotation.py b/censo_qm/opticalrotation.py new file mode 100644 index 0000000..5eb73b4 --- /dev/null +++ b/censo_qm/opticalrotation.py @@ -0,0 +1,585 @@ +""" +module for the calculation of optical rotation +""" + +import os +import shutil +import sys +from random import normalvariate +from multiprocessing import JoinableQueue as Queue +from .cfg import PLENGTH, DIGILEN, AU2KCAL +from .parallel import run_in_parallel +from .orca_job import OrcaJob +from .tm_job import TmJob +from .utilities import ( + calc_boltzmannweights, + printout, + print_block, + new_folders, + last_folders, + print, + calc_std_dev, + ensemble2coord, +) + + +def part5(config, conformers, store_confs, ensembledata): + """ + Calculate optical rotation on the populated + conformers (either directly from part2 OPTIMIZATION or after REFINEMENT + (part3)) + """ + save_errors = [] + print("\n" + "".ljust(PLENGTH, "-")) + print("OPTICAL ROTATION MODE - PART5".center(PLENGTH, " ")) + print("".ljust(PLENGTH, "-") + "\n") + # print flags for part5 + info = [] + info.append(["optical_rotation", "Part5"]) + info.append(["freq_or", "frequency in [nm]"]) + info.append(["func_or_scf", "functional for SCF"]) + info.append(["func_or", "functional for optical rotation"]) + info.append(["basis_or", "basis set for optical rotation"]) + if config.part3: + info.append(["part3_threshold", "Boltzmann sum threshold employed"]) + elif config.part2: + info.append(["part2_threshold", "Boltzmann sum threshold employed"]) + elif config.part1: + info.append(["part2_threshold", "Boltzmann sum threshold employed"]) + if config.solvent != "gas": + info.append(["solvent", "solvent"]) + if config.prog == "tm": + info.append(["printoption", "solvation model", "cosmo"]) + optionsexchange = {True: "on", False: "off"} + for item in info: + if item[0] == "justprint": + print(item[1:][0]) + else: + if item[0] == "printoption": + option = item[2] + else: + option = getattr(config, item[0]) + if option is True or option is False: + option = optionsexchange[option] + elif isinstance(option, list): + option = [str(i) for i in option] + if len(str(option)) > 40: + length = 0 + reduced = [] + for i in option: + length += len(i) + 2 + if length < 40: + reduced.append(i) + reduced.append("...") + option = reduced + length = 0 + option = ", ".join(option) + print( + "{}: {:{digits}} {}".format( + item[1], "", option, digits=DIGILEN - len(item[1]) + ) + ) + print("") + # # end print + + calculate = [] # has to be calculated in this run + prev_calculated = [] # was already calculated in a previous run + try: + store_confs + except NameError: + store_confs = [] # stores all confs which are sorted out! + + # setup queues + q = Queue() + resultq = Queue() + + unoptimized_warning = False + # sort conformers: + for conf in list(conformers): + if conf.removed: + store_confs.append(conformers.pop(conformers.index(conf))) + print(f"CONF{conf.id} is removed as requested by the user!") + continue + if ( + conf.part_info["part2"] != "passed" + and conf.optimization_info["info"] != "calculated" + ): + unoptimized_warning = True + + if config.part3: + # calc boltzmann weights from part3 + energy = "highlevel_sp_info" + rrho = "highlevel_grrho_info" + gsolv = "highlevel_gsolv_info" + boltzmannthr = config.part3_threshold + elif config.part2: + # part3 is not calculated use boltzmann weights directly from part2 + energy = "lowlevel_sp_info" + rrho = "lowlevel_grrho_info" + gsolv = "lowlevel_gsolv_info" + boltzmannthr = config.part2_threshold + elif config.part1: + # part2 is not calculated use boltzmann weights directly from part1 + #--> misappropriate config.part2_threshold + # This means starting from not DFT optimized geometries! + energy = "prescreening_sp_info" + rrho = "prescreening_grrho_info" + gsolv = "prescreening_gsolv_info" + boltzmannthr = config.part2_threshold + else: + print("UNEXPECTED BEHAVIOUR") + mol = conformers.pop(conformers.index(conf)) + if getattr(conf, energy)["info"] != "calculated": + store_confs.append(mol) + continue + elif getattr(conf, rrho)["info"] != "calculated" and config.evaluate_rrho: + store_confs.append(mol) + continue + elif ( + getattr(conf, gsolv)["info"] != "calculated" and config.solvent != "gas" + ): + store_confs.append(mol) + continue + else: + calculate.append(mol) + + if unoptimized_warning: + print(f"WARNING: Conformers have not been optimized at DFT level!!!\n" + f" Use results with care!\n" + ) + + if not calculate and not prev_calculated: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + calculate.sort(key=lambda x: int(x.id)) + print("Considering the following conformers:") + print_block(["CONF" + str(i.id) for i in calculate]) + + # Calculate boltzmann weight for confs: + if config.part3: + if not config.evaluate_rrho: + rrho = None + else: + rrho_method, _ = config.get_method_name( + "rrhoxtb", + bhess=config.bhess, + gfn_version=config.part3_gfnv, + sm=config.sm_rrho, + solvent=config.solvent, + ) + if config.solvent == "gas": + gsolv = None + energy_method, _ = config.get_method_name( + "xtbopt", + func=config.func3, + basis=config.basis3, + sm=config.smgsolv3, + gfn_version=config.part3_gfnv, + solvent=config.solvent, + ) + else: + if config.smgsolv3 in ("cosmors", "cosmors-fine"): + tmp_name = "cosmors" + elif config.smgsolv3 in ("alpb_gsolv", "gbsa_gsolv", "smd_gsolv"): + tmp_name = config.smgsolv3 + else: + tmp_name = "sp_implicit" + energy_method, solv_method = config.get_method_name( + tmp_name, + func=config.func3, + basis=config.basis3, + sm=config.smgsolv3, + gfn_version=config.part3_gfnv, + solvent=config.solvent, + ) + elif config.part2: + if not config.evaluate_rrho: + rrho = None + else: + rrho_method, _ = config.get_method_name( + "rrhoxtb", + bhess=config.bhess, + gfn_version=config.part2_gfnv, + sm=config.sm_rrho, + solvent=config.solvent, + ) + if config.solvent == "gas": + gsolv = None + energy_method, _ = config.get_method_name( + "xtbopt", + func=config.func, + basis=config.basis, + sm=config.smgsolv2, + gfn_version=config.part2_gfnv, + solvent=config.solvent, + ) + else: + if config.smgsolv2 in ("cosmors", "cosmors-fine"): + tmp_name = "cosmors" + elif config.smgsolv2 in ("alpb_gsolv", "gbsa_gsolv", "smd_gsolv"): + tmp_name = config.smgsolv2 + else: + tmp_name = "sp_implicit" + energy_method, solv_method = config.get_method_name( + tmp_name, + func=config.func, + basis=config.basis, + sm=config.smgsolv2, + gfn_version=config.part2_gfnv, + solvent=config.solvent, + ) + elif config.part1: + # on DFT unoptimized geometries! + if not config.evaluate_rrho: + rrho = None + else: + rrho_method, _ = config.get_method_name( + "rrhoxtb", + bhess=config.bhess, + gfn_version=config.part1_gfnv, + sm=config.sm_rrho, + solvent=config.solvent, + ) + if config.solvent == "gas": + gsolv = None + energy_method, _ = config.get_method_name( + "xtbopt", + func=config.func, + basis=config.basis, + sm=config.smgsolv1, + gfn_version=config.part1_gfnv, + solvent=config.solvent, + ) + else: + if config.smgsolv1 in ("cosmors", "cosmors-fine"): + tmp_name = "cosmors" + elif config.smgsolv2 in ("alpb_gsolv", "gbsa_gsolv", "smd_gsolv"): + tmp_name = config.smgsolv1 + else: + tmp_name = "sp_implicit" + energy_method, solv_method = config.get_method_name( + tmp_name, + func=config.func, + basis=config.basis, + sm=config.smgsolv1, + gfn_version=config.part1_gfnv, + solvent=config.solvent, + ) + + for conf in calculate: + conf.calc_free_energy(e=energy, solv=gsolv, rrho=rrho) + calculate = calc_boltzmannweights(calculate, "free_energy", config.temperature) + try: + minfree = min([i.free_energy for i in calculate if i is not None]) + except ValueError: + raise + for conf in calculate: + conf.rel_free_energy = (conf.free_energy - minfree) * AU2KCAL + calculate.sort(key=lambda x: int(x.id)) + + # printout for part4 ------------------------------------------------------- + print("\n" + "".ljust(int(PLENGTH / 2), "-")) + print("* Gibbs free energies used in part5 *".center(int(PLENGTH / 2), " ")) + print("".ljust(int(PLENGTH / 2), "-") + "\n") + columncall = [ + lambda conf: "CONF" + str(getattr(conf, "id")), + lambda conf: getattr(conf, energy)["energy"], + lambda conf: getattr(conf, gsolv)["energy"], + lambda conf: getattr(conf, rrho)["energy"], + lambda conf: getattr(conf, "free_energy"), + lambda conf: getattr(conf, "rel_free_energy"), + lambda conf: getattr(conf, "bm_weight") * 100, + ] + columnheader = [ + "CONF#", + "E [Eh]", + "Gsolv [Eh]", + "GmRRHO [Eh]", + "Gtot", + "ΔGtot", + "Boltzmannweight", + ] + columndescription = [ + "", + "", + "", + "", + "[Eh]", + "[kcal/mol]", + f" % at {config.temperature:.2f} K", + ] + columnformat = ["", (12, 7), (12, 7), (12, 7), (12, 7), (5, 2), (5, 2)] + columndescription[1] = energy_method + if config.solvent != "gas": + columndescription[2] = solv_method + columndescription[3] = rrho_method + + if not config.evaluate_rrho or config.solvent == "gas": + if not config.evaluate_rrho: + # ignore rrho in printout + columncall.pop(3) + columnheader.pop(3) + columndescription.pop(3) + columnformat.pop(3) + if config.solvent == "gas": + columncall.pop(2) + columnheader.pop(2) + columndescription.pop(2) + columnformat.pop(2) + + printout( + os.path.join(config.cwd, "part5.dat"), + columncall, + columnheader, + columndescription, + columnformat, + calculate, + minfree, + ) + calculate.sort(reverse=True, key=lambda x: float(x.bm_weight)) + sumup = 0.0 + for conf in list(calculate): + sumup += conf.bm_weight + if sumup >= boltzmannthr: + if conf.bm_weight < (1 - boltzmannthr): + store_confs.append(calculate.pop(calculate.index(conf))) + print(f"\nConformers that are below the Boltzmann-thr of {boltzmannthr}:") + print_block(["CONF" + str(i.id) for i in calculate]) + + # create NMR folder + folder = "OR" + save_errors, store_confs, calculate = new_folders( + config.cwd, calculate, folder, save_errors, store_confs + ) + if config.part3 or config.part2: + # need to copy optimized coord to folder + for conf in list(calculate): + tmp1 = os.path.join(config.cwd, "CONF" + str(conf.id), config.func, "coord") + tmp2 = os.path.join("CONF" + str(conf.id), folder, "coord") + try: + shutil.copy(tmp1, tmp2) + except FileNotFoundError: + print("ERROR can't copy optimized geometry!") + store_confs.append(calculate.pop(calculate.index(conf))) + elif config.part1: + # do not use coord from folder config.func it could be optimized if + # part2 has ever been run, take coord from ensemble file + # write coord to folder + calculate, store_confs, save_errors = ensemble2coord( + config, folder, calculate, store_confs, save_errors + ) + + if not calculate: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + # check if OR calculated before! + for conf in list(calculate): + if getattr(conf, "optical_rotation_info")["info"] == "calculated": + prev_calculated.append(calculate.pop(calculate.index(conf))) + elif getattr(conf, "optical_rotation_info")["info"] == "failed": + store_confs.append(calculate.pop(calculate.index(conf))) + + instruction_or = { + "jobtype": "opt-rot_sp", # opt-rot only escf ; opt-rot_sp SP then escf + "func": config.func_or_scf, + "func2": config.func_or, + "basis": getattr( + config, + "basis_or", + config.func_basis_default.get(config.func, "def2-mTZVPP"), + ), + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": "gas", + "sm": "cosmo", + "success": False, + "omp": config.omp, + "freq_or": config.freq_or, + } + if config.prog == "orca": + print("Can't calculate OR with ORCA! Use TM instead.") + # ORCA can't calculate optical rotation!!! --> + job = OrcaJob + instruction_or["progpath"] = config.external_paths["orcapath"] + if config.solvent != "gas": + instruction_or["solvent"] = config.solvent + instruction_or["sm"] = "cpcm" + if config.prog == "tm": + job = TmJob + instruction_or["prepinfo"] = ["clear", "-grid", "2", "-scfconv", "6"] + instruction_or["progpath"] = config.external_paths["escfpath"] + if config.basis == config.basis_or: + instruction_or["copymos"] = config.func + instruction_or["jobtype"] = "opt-rot" + if config.solvent != "gas": + instruction_or["solvent"] = config.solvent + instruction_or["sm"] = "cosmo" + + instruction_or["method"], _ = config.get_method_name( + instruction_or["jobtype"], + func=instruction_or["func2"], + basis=instruction_or["basis"], + solvent=instruction_or["solvent"], + prog=config.prog, + func2=instruction_or["func"], + sm=instruction_or["sm"], + ) + + print(f"\nOptical-rotation is calculated at {instruction_or['method']} level.\n") + check = {True: "was successful", False: "FAILED"} + pl = config.lenconfx + 4 + len(str("/" + folder)) + + if calculate: + calculate = run_in_parallel( + config, + q, + resultq, + job, + config.maxthreads, + calculate, + instruction_or, + folder, + ) + calculate.sort(key=lambda x: int(x.id)) + try: + max_fmt = max( + [ + len(str(item.job["erange1"].get(config.freq_or[0])).split(".")[0]) + for item in calculate + ] + ) + max_fmt += 9 + except: + max_fmt = 16 + for conf in list(calculate): + line = ( + f"Optical-rotation calculation {check[conf.job['success']]}" + f" for {last_folders(conf.job['workdir'], 2):>{pl}} at {config.freq_or[0]} nm: " + f"{conf.job['erange1'].get(config.freq_or[0], 0.00):> {max_fmt}.7f}" + f" populated to {conf.bm_weight*100:.2f} %" + ) + print(line) + if not conf.job["success"]: + save_errors.append(line) + conf.optical_rotation_info["info"] = "failed" + conf.optical_rotation_info["method"] = instruction_or["method"] + conf.part_info["part5"] = "refused" + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.optical_rotation_info["range"] = conf.job["erange1"] + conf.optical_rotation_info["info"] = "calculated" + conf.optical_rotation_info["method"] = instruction_or["method"] + conf.part_info["part5"] = "passed" + + if prev_calculated: + try: + max_fmt = max( + [ + len( + str( + item.optical_rotation_info["range"].get(config.freq_or[0]) + ).split(".")[0] + ) + for item in prev_calculated + ] + ) + max_fmt += 9 + except Exception as e: + print(e) + max_fmt = 16 + prev_calculated.sort(key=lambda x: int(x.id)) + for conf in list(prev_calculated): + conf.job["workdir"] = os.path.normpath( + os.path.join(config.cwd, "CONF" + str(conf.id), folder) + ) + print( + f"Optical-rotation calculation {check[True]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}} at {config.freq_or[0]} nm: " + f"{conf.optical_rotation_info['range'].get(config.freq_or[0], 0.0000):> {max_fmt}.7f}" + f" populated to {conf.bm_weight*100:.2f} % " + ) + calculate.append(prev_calculated.pop(prev_calculated.index(conf))) + for freq in config.freq_or: + averaged_or = 0.0 + for conf in calculate: + averaged_or += conf.bm_weight * conf.optical_rotation_info["range"].get( + freq, 0.0 + ) + print( + f"\nAveraged specific rotation at {freq} nm : " + f"{averaged_or: .3f} in deg*[dm(g/cc)]^(-1)" + ) + + if all( + [conf.lowlevel_gsolv_compare_info["std_dev"] is not None for conf in calculate] + ): + for freq in config.freq_or: + all_or = [] + for _ in range(1000): + averaged_or = 0.0 + for conf in calculate: + conf.calc_free_energy(e=energy, solv=gsolv, rrho=rrho) + conf.free_energy += normalvariate( + 0.0, conf.lowlevel_gsolv_compare_info["std_dev"] + ) + calculate = calc_boltzmannweights( + calculate, "free_energy", config.temperature + ) + for conf in calculate: + averaged_or += conf.bm_weight * conf.optical_rotation_info[ + "range" + ].get(freq) + all_or.append(averaged_or) + try: + max_fmt = max( + [ + len( + str( + item.optical_rotation_info["range"].get( + config.freq_or[0] + ) + ).split(".")[0] + ) + for item in calculate + ] + ) + max_fmt += 9 + except Exception as e: + print(e) + max_fmt = 16 + print( + f" SD based on SD of Gsolv (part2) " + f": {calc_std_dev(all_or):> {max_fmt}.3f} in deg*[dm(g/cc)]^(-1)" + ) + + for conf in calculate: + conf.reset_job_info() + + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + + if unoptimized_warning: + # Repeat for user to see! + print(f"\nWARNING: Conformers have not been optimized at DFT level!!!\n" + f" Use results with care!\n" + ) + if not calculate: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + # end printout for part5 + tmp = int((PLENGTH - len("END of Part5")) / 2) + print("\n" + "".ljust(tmp, ">") + "END of Part5" + "".rjust(tmp, "<")) + return config, calculate, store_confs, ensembledata diff --git a/censo_qm/optimization.py b/censo_qm/optimization.py new file mode 100755 index 0000000..52fe83b --- /dev/null +++ b/censo_qm/optimization.py @@ -0,0 +1,1945 @@ +""" +Optimization == part2 +performing optimization of the CRE and provide low level free energies. +""" +from multiprocessing import JoinableQueue as Queue +import shutil +import time +import os +import sys +from copy import deepcopy +from .cfg import PLENGTH, CODING, AU2KCAL, DIGILEN +from .utilities import ( + check_for_folder, + print_block, + new_folders, + last_folders, + ensemble2coord, + frange, + calc_boltzmannweights, + spearman, + printout, + move_recursively, + write_trj, + crest_routine, + check_tasks, + print, + calc_weighted_std_dev, +) +from .orca_job import OrcaJob +from .tm_job import TmJob +from .parallel import run_in_parallel + + +def part2(config, conformers, store_confs, ensembledata): + """ + Optimization of the ensemble, at DFT level (possibly with implicit solvation) + Calculate low level free energies with COSMO-RS single-point and gsolv + contribution and GFNFF-bhess thermostatistical contribution on DFT optimized + geometries + Input: + - config [conifg_setup object] contains all settings + - conformers [list of molecule_data objects] each conformer is represented + Return: + -> config + -> conformers + """ + save_errors = [] + print("\n" + "".ljust(PLENGTH, "-")) + print("CRE OPTIMIZATION - PART2".center(PLENGTH, " ")) + print("".ljust(PLENGTH, "-") + "\n") + # print flags for part2 + info = [] + info.append(["prog", "program"]) + info.append(["func", "functional for part2"]) + info.append(["basis", "basis set for part2"]) + info.append(["ancopt", "using the xTB-optimizer for optimization"]) + if config.opt_spearman: + info.append(["opt_spearman", "using the new ensemble optimizer"]) + info.append( + ["opt_limit", "completely optimize all conformers below this threshold"] + ) + info.append(["printoption", "Spearman threshold", f"{config.spearmanthr:.3f}"]) + info.append(["optcycles", "number of optimization iterations"]) + if config.func == "r2scan-3c": + info.append(["radsize", "radsize"]) + if config.ancopt and config.optlevel2 is not None: + info.append(["optlevel2", "optimization level in part2"]) + if config.solvent != "gas": + info.append(["solvent", "solvent"]) + info.append(["sm2", "solvent model applied in the optimization"]) + if config.smgsolv2 not in (None, "sm"): + info.append(["smgsolv2", "solvent model for Gsolv contribution"]) + info.append(["temperature", "temperature"]) + if config.multitemp: + info.append(["multitemp", "evalulate at different temperatures"]) + info.append( + [ + "printoption", + "temperature range", + [ + i + for i in frange( + config.trange[0], config.trange[1], config.trange[2] + ) + ], + ] + ) + info.append(["part2_threshold", "Boltzmann sum threshold for sorting in part2"]) + info.append(["evaluate_rrho", "calculate mRRHO contribution"]) + if config.evaluate_rrho: + info.append(["prog_rrho", "program for mRRHO contribution"]) + if config.prog_rrho == "xtb": + info.append(["part2_gfnv", "GFN version for mRRHO and/or GBSA_Gsolv"]) + if config.bhess: + info.append( + [ + "bhess", + "Apply constraint to input geometry during mRRHO calculation", + ] + ) + optionsexchange = {True: "on", False: "off"} + for item in info: + if item[0] == "justprint": + print(item[1:][0]) + else: + if item[0] == "printoption": + option = item[2] + else: + option = getattr(config, item[0]) + if option is True or option is False: + option = optionsexchange[option] + elif isinstance(option, list): + option = [str(i) for i in option] + if len(str(option)) > 40: + length = 0 + reduced = [] + for i in option: + length += len(i) + 2 + if length < 40: + reduced.append(i) + reduced.append("...") + option = reduced + length = 0 + option = ", ".join(option) + print( + "{}: {:{digits}} {}".format( + item[1], "", option, digits=DIGILEN - len(item[1]) + ) + ) + print("") + # end print + + calculate = [] # has to be calculated in this run + prev_calculated = [] # was already calculated in a previous run + try: + store_confs + except NameError: + store_confs = [] # stores all confs which are sorted out! + + if config.solvent == "gas": + print("Optimizing geometries at DFT level!") + else: + print("Optimizing geometries at DFT level with implicit solvation!") + + # setup queues + q = Queue() + resultq = Queue() + + if config.prog == "tm": + job = TmJob + elif config.prog == "orca": + job = OrcaJob + + for conf in list(conformers): + if conf.removed: + store_confs.append(conformers.pop(conformers.index(conf))) + print(f"CONF{conf.id} is removed as requested by the user!") + continue + if conf.id > config.nconf: + store_confs.append(conformers.pop(conformers.index(conf))) + continue + if conf.optimization_info["info"] == "not_calculated": + conf = conformers.pop(conformers.index(conf)) + calculate.append(conf) + elif conf.optimization_info["info"] == "failed": + conf = conformers.pop(conformers.index(conf)) + store_confs.append(conf) + print(f"Optimization of CONF{conf.id} failed in the previous run!") + elif conf.optimization_info["info"] == "prep-failed": + print( + f"Preparation for the optimization of CONF{conf.id} failed in the " + "previous run and is tried again!" + ) + conf = conformers.pop(conformers.index(conf)) + elif conf.optimization_info["info"] == "calculated": + conf = conformers.pop(conformers.index(conf)) + if conf.optimization_info["convergence"] == "converged": + conf.job["success"] = True + conf.job["ecyc"] = conf.optimization_info["ecyc"] + if conf.optimization_info.get("cregen_sort", "pass") == "pass": + prev_calculated.append(conf) + elif conf.optimization_info.get("cregen_sort", "pass") == "removed": + print( + f"CONF{conf.id} has been sorted out by CREGEN in a previous run." + ) + store_confs.append(conf) + else: + # "not_converged" or "stopped_before_converged" + store_confs.append(conf) + if not calculate and not prev_calculated: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + if prev_calculated: + check_for_folder(config.cwd, [i.id for i in prev_calculated], config.func) + print("The optimization was performed before for:") + print_block(["CONF" + str(i.id) for i in prev_calculated]) + pl = config.lenconfx + 4 + len(str("/" + config.func)) + + instruction_prep = { + "jobtype": "prep", + "func": config.func, + "basis": getattr(config, "basis", config.func_basis_default[config.func]), + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": config.solvent, + "sm": config.sm2, + "optlevel": config.optlevel2, + "omp": config.omp, + "copymos": "", + "energy": 0.0, + "energy2": 0.0, + "success": False, + } + + # INSTRUCTION OPT !!!! + instruction_opt = { + "func": config.func, + "basis": getattr(config, "basis", config.func_basis_default[config.func]), + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": config.solvent, + "fullopt": True, # output to opt-part2.out + "converged": False, + "hlow": config.hlow, + "sm": config.sm2, + "omp": config.omp, + "optcycles": config.optcycles, + "optlevel": config.optlevel2, + "multiTemp": False, + "energy": 0.0, + "energy2": 0.0, + "success": False, + } + + instruction_rrho_crude = { + "jobtype": "rrhoxtb", + "func": getattr(config, "part2_gfnv"), + "gfn_version": getattr(config, "part2_gfnv"), + "temperature": config.temperature, + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": config.solvent, + "omp": config.omp, + "progpath": config.external_paths["xtbpath"], + "bhess": config.bhess, + "sm_rrho": config.sm_rrho, + "rmsdbias": config.rmsdbias, + "cwd": config.cwd, + "consider_sym": config.consider_sym, + "energy": 0.0, + "energy2": 0.0, + "success": False, + } + instruction_rrho_crude["method"], _ = config.get_method_name( + "rrhoxtb", + bhess=config.bhess, + gfn_version=instruction_rrho_crude["gfn_version"], + sm=instruction_rrho_crude["sm_rrho"], + solvent=instruction_rrho_crude["solvent"], + ) + + # Set optlevel and scfconv stuff --------------------------------------- + # r2scan-3c has additional settings in tm_job._prep_cefine! + if config.optlevel2 in ("crude", "sloppy", "loose"): + instruction_prep["prepinfo"] = ["low"] + instruction_opt["prepinfo"] = ["low"] + elif config.optlevel2 == "lax": + instruction_prep["prepinfo"] = ["low"] + instruction_opt["prepinfo"] = ["low"] + elif config.optlevel2 == "normal": + instruction_prep["prepinfo"] = ["low+"] + instruction_opt["prepinfo"] = ["low+"] + elif config.optlevel2 in ("tight", "vtight", "extreme"): + instruction_prep["prepinfo"] = ["high"] + instruction_opt["prepinfo"] = ["high"] + else: + instruction_prep["prepinfo"] = ["low+"] + instruction_opt["prepinfo"] = ["low+"] + # ----------------------------------------------------------------------- + if config.ancopt: + instruction_opt["jobtype"] = "xtbopt" + instruction_opt["xtb_driver_path"] = config.external_paths["xtbpath"] + else: + instruction_opt["jobtype"] = "opt" + + if config.func == "r2scan-3c": + instruction_prep["prepinfo"].extend(["-radsize", str(config.radsize)]) + + instruction_opt["method"], _ = config.get_method_name( + instruction_opt["jobtype"], + func=instruction_opt["func"], + basis=instruction_opt["basis"], + solvent=instruction_opt["solvent"], + sm=instruction_opt["sm"], + ) + + check = {True: "was successful", False: "FAILED"} + if calculate: + print("The optimization is calculated for:") + print_block(["CONF" + str(i.id) for i in calculate]) + # create folders: + save_errors, store_confs, calculate = new_folders( + config.cwd, calculate, config.func, save_errors, store_confs + ) + # write coord to folder + calculate, store_confs, save_errors = ensemble2coord( + config, config.func, calculate, store_confs, save_errors + ) + + # parallel prep execution + calculate = run_in_parallel( + config, + q, + resultq, + job, + config.maxthreads, + calculate, + instruction_prep, + config.func, + ) + # check if too many calculations failed + + for conf in list(calculate): + if instruction_prep["jobtype"] == "prep": + line = ( + f"Preparation in {last_folders(conf.job['workdir'], 2):>{pl}} " + f"{check[conf.job['success']]}." + ) + if not conf.job["success"]: + save_errors.append(line) + conf.optimization_info["info"] = "prep-failed" + store_confs.append(calculate.pop(calculate.index(conf))) + + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + check_tasks(calculate, config.check) + else: + print("No conformers are considered additionally.") + # reset + for conf in calculate: + conf.reset_job_info() + # *************************************************************************** + # NEW ENSEMBLE OPTIMIZER: + if calculate: + print("Starting optimizations".center(70, "*")) + ### settings in instruction_opt are overwriting conf.job everytime,(while loop) + ### therefore dont write information which has to be reaccessed to it! + + run = 1 + timings = [] # time per cycle + cycle_spearman = [] # spearmanthr used in evaluation per cycle + nconf_cycle = [] # number of conformers at end of each cycle + + do_increase = 0.6 + if config.opt_limit * do_increase >= 1.5: + ewin_increase = config.opt_limit * do_increase + print( + f"\nStarting threshold is set to {config.opt_limit} + " + f"{do_increase*100} % = {config.opt_limit + ewin_increase} kcal/mol\n" + ) + else: + ewin_increase = 1.5 + print( + f"\nStarting threshold is set to {config.opt_limit} + " + f"{ewin_increase} kcal/mol = {config.opt_limit + ewin_increase} kcal/mol\n" + ) + ewin_initial = config.opt_limit + ewin_increase + ewin = config.opt_limit + ewin_increase + + print(f"Lower limit is set to {config.opt_limit} kcal/mol\n") + lower_limit = config.opt_limit + maxecyc_prev = 1 + maxecyc = 0 + converged_run1 = [] + if config.nat > 200: + # stopcycle = don't optimize more than stopcycle cycles + stopcycle = config.nat * 2 + else: + stopcycle = 200 + if config.opt_spearman: + while calculate: + tic = time.perf_counter() + print(f"CYCLE {str(run)}".center(70, "*")) + # if len(calculate) == 1: + + # if stopcycle - maxecyc <= 0: + # limit = config.optcycles + # else: + # limit = stopcycle - maxecyc + # instruction_opt["optcycles"] = limit + + # calculate batch of optimizations + calculate = run_in_parallel( + config, + q, + resultq, + job, + config.maxthreads, + calculate, + instruction_opt, + config.func, + ) + + # check if optimization crashed + for conf in list(calculate): + if not conf.job["success"]: + print(f"removing CONF{conf.id} because optimization crashed.") + conf.optimization_info["info"] = "failed" + conf.optimization_info["convergence"] = "not_converged" + conf.optimization_info["method"] = instruction_opt["method"] + store_confs.append(calculate.pop(calculate.index(conf))) + elif conf.job["success"]: + if conf.job["converged"]: + # don't optimize further: + print( + f"Geometry optimization converged for: " + f"CONF{conf.id} within {conf.job['cycles']:>3} cycles" + ) + conf.optimization_info["info"] = "calculated" + conf.optimization_info["energy"] = conf.job["energy"] + conf.optimization_info["cycles"] = conf.job["cycles"] + conf.optimization_info["ecyc"] = conf.job["ecyc"] + conf.optimization_info["decyc"] = conf.job["decyc"] + conf.optimization_info["convergence"] = "converged" + conf.optimization_info["method"] = instruction_opt["method"] + if run == 1: + converged_run1.append(conf.id) + else: + prev_calculated.append( + calculate.pop(calculate.index(conf)) + ) + else: + conf.optimization_info["energy"] = conf.job["energy"] + # optimization cycles didn't result in convergence + # optimize further + if not calculate: + toc = time.perf_counter() + timings.append(toc - tic) + cycle_spearman.append("") + nconf_cycle.append(len(calculate) + len(prev_calculated)) + break + if run == 1 and calculate and config.evaluate_rrho: + # run GmRRHO on crudely optimized geometry + folder_rrho_crude = os.path.join(config.func, "rrho_crude") + # create folders: + save_errors, store_confs, calculate = new_folders( + config.cwd, + calculate, + folder_rrho_crude, + save_errors, + store_confs, + ) + # copy optimized geoms to folder + for conf in list(calculate): + try: + tmp_from = os.path.join( + config.cwd, "CONF" + str(conf.id), config.func + ) + tmp_to = os.path.join( + config.cwd, "CONF" + str(conf.id), folder_rrho_crude + ) + shutil.copy( + os.path.join(tmp_from, "coord"), + os.path.join(tmp_to, "coord"), + ) + except shutil.SameFileError: + pass + except FileNotFoundError: + if not os.path.isfile(os.path.join(tmp_from, "coord")): + print( + "ERROR: while copying the coord file from {}! " + "The corresponding file does not exist.".format( + tmp_from + ) + ) + elif not os.path.isdir(tmp_to): + print( + "ERROR: Could not create folder {}!".format(tmp_to) + ) + print("ERROR: Removing conformer {}!".format(conf.name)) + conf.lowlevel_grrho_info["info"] = "prep-failed" + store_confs.append(calculate.pop(calculate.index(conf))) + save_errors.append( + f"CONF{conf.id} was removed, because IO failed!" + ) + # parallel execution: + calculate = run_in_parallel( + config, + q, + resultq, + job, + config.maxthreads, + calculate, + instruction_rrho_crude, + folder_rrho_crude, + ) + check = {True: "was successful", False: "FAILED"} + # check if too many calculations failed + ### + for conf in list(calculate): + print( + f"The G_mRRHO calculation on crudely optimized DFT " + f"geometry @ {conf.job['symmetry']} " + f"{check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 3):>{pl}}: " + f"{conf.job['energy']:>.8f}" + ) + if not conf.job["success"]: + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.optimization_info["energy_rrho"] = conf.job["energy"] + conf.optimization_info[ + "method_rrho" + ] = instruction_rrho_crude["method"] + conf.optimization_info["info_rrho"] = "calculated" + + for conf in list(calculate): + if conf.id in converged_run1: + prev_calculated.append(calculate.pop(calculate.index(conf))) + if not calculate: + toc = time.perf_counter() + timings.append(toc - tic) + cycle_spearman.append("") + nconf_cycle.append(len(calculate) + len(prev_calculated)) + break + if run >= 2 and config.crestcheck: + # do sorting with cregen! + calculate, prev_calculated, store_confs = crest_routine( + config, + calculate, + config.func, + store_confs, + prev_calculated=prev_calculated, + ) + if not calculate: + toc = time.perf_counter() + timings.append(toc - tic) + cycle_spearman.append("") + nconf_cycle.append(len(calculate) + len(prev_calculated)) + break + maxecyc = max([len(conf.job["ecyc"]) for conf in calculate]) + print(f"Max number of performed iterations: {maxecyc}") + if len(calculate + prev_calculated) == 1: + # can't do spearman with only one conf + run_spearman = False + elif len(calculate) > 1 and run > 1: + run_spearman = True + else: + run_spearman = True + gesc = True # gESC with already good sorting + if run == 1 and gesc: + # only evaluate spearman starting from second cycle + print("Spearman rank evaluation is performed in the next cycle.") + cycle_spearman.append("") + run_spearman = False + + elif run == 1 and not gesc: + # only evaluate spearman starting from second cycle + print("Spearman rank evaluation is performed in the next cycle.") + cycle_spearman.append("") + run_spearman = False + run += 1 + toc = time.perf_counter() + timings.append(toc - tic) + nconf_cycle.append(len(calculate) + len(prev_calculated)) + print(f"CYCLE {run} performed in {toc -tic:0.4f} seconds") + continue + + # lists of equal lenght: + for conf in sorted( + calculate + prev_calculated, key=lambda x: int(x.id) + ): + if len(conf.job["ecyc"]) < maxecyc: + for _ in range(maxecyc - len(conf.job["ecyc"])): + conf.job["ecyc"].append(conf.job["ecyc"][-1]) + + # calculate min of each cycle: + minecyc = [] + if config.evaluate_rrho: + rrho_energy = "energy_rrho" + else: + rrho_energy = "axqzv" # to get 0.0 contribution + for i in range(maxecyc): + try: + minecyc.append( + min( + [ + conf.job["ecyc"][i] + + getattr(conf, "optimization_info").get( + rrho_energy, 0.0 + ) + for conf in calculate + prev_calculated + if conf.job["ecyc"][i] is not None + ] + ) + ) + except (ValueError) as e: + minecyc.append(0.0) + print(e) + # evalulate ΔE + for conf in sorted( + calculate + prev_calculated, key=lambda x: int(x.id) + ): + conf.job["decyc"] = [] + for i in range(maxecyc): + conf.job["decyc"].append( + ( + conf.job["ecyc"][i] + + getattr(conf, "optimization_info").get( + rrho_energy, 0.0 + ) + - minecyc[i] + ) + * AU2KCAL + ) + if run == 1: + print("") + for conf in sorted( + calculate + prev_calculated, key=lambda x: int(x.id) + ): + print( + f"CONF{conf.id :<{config.lenconfx}} initial ΔG = " + f"{conf.job['decyc'][0]:^5.2f} kcal/mol and " + f"current ΔG = {conf.job['decyc'][-1]:^5.2f} kcal/mol." + f" ({conf.optimization_info['convergence']})" + ) + previouscycle = maxecyc + print("") + else: + print("") + for conf in sorted( + calculate + prev_calculated, key=lambda x: int(x.id) + ): + print( + f"CONF{conf.id :<{config.lenconfx}} previous ΔG = " + f"{conf.job['decyc'][previouscycle-1]:^5.2f} kcal/mol and " + f"current ΔG = {conf.job['decyc'][-1]:^5.2f} kcal/mol." + f" ({conf.optimization_info['convergence']})" + ) + previouscycle = maxecyc + print("") + if run_spearman: + num_eval = 3 + try: + toevaluate = [] + for i in range(maxecyc_prev, maxecyc): + if i + num_eval <= maxecyc: + toevaluate.append(i) + _ = max(toevaluate) + digits1 = 4 + except ValueError: + # need to do another optimization cycle + run += 1 + toc = time.perf_counter() + timings.append(toc - tic) + cycle_spearman.append("") + nconf_cycle.append(len(calculate) + len(prev_calculated)) + print(f"CYCLE {run} performed in {toc -tic:0.4f} seconds") + continue + + evalspearman = [] + for i in toevaluate: + deprevious = [ + conf.job["decyc"][i - 1] + for conf in sorted( + calculate + prev_calculated, key=lambda x: int(x.id) + ) + ] + decurrent = [ + conf.job["decyc"][i - 1 + num_eval] + for conf in sorted( + calculate + prev_calculated, key=lambda x: int(x.id) + ) + ] + + spearman_v = spearman(deprevious, decurrent) + if i in toevaluate[-2:]: + print( + f"Evaluating Spearman coeff. from {i:>{digits1}} --> " + f"{i+num_eval:>{digits1}}" + f" = {spearman_v:>.4f}" + ) + evalspearman.append(spearman_v) + else: + print( + f"{'':>10} Spearman coeff. from {i:>{digits1}} --> " + f"{i+num_eval:>{digits1}}" + f" = {spearman_v:>.4f}" + ) + print( + f"Final averaged Spearman correlation coefficient: " + f"{(sum(evalspearman)/2):>.4f}" + ) + + if ( + len(evalspearman) >= 2 + and sum(evalspearman) / 2 >= config.spearmanthr + ): + print("\nPES is assumed to be parallel") + # adjust threshold Ewin: + if ewin > lower_limit: + if (ewin - (ewin_increase / 3)) < lower_limit: + ewin = lower_limit + else: + ewin += -(ewin_increase / 3) + print( + f"Updated optimization threshold to: {ewin:.2f} kcal/mol" + ) + else: + print( + f"Current optimization threshold: {ewin:.2f} kcal/mol" + ) + cycle_spearman.append(f"{sum(evalspearman)/2:.3f}") + + for conf in list(calculate): + if conf.job["decyc"][-1] > ewin and conf.job["grad_norm"] < 0.01: + print( + f"CONF{conf.id} is above {ewin} kcal/mol and gradient " + f"norm ({conf.job['grad_norm']}) is below {0.01}." + ) + if conf.job["decyc"][-1] < ewin_initial: + print( + f"CONF{conf.id} is removed because of the " + "lowered threshold!" + ) + # transfer energies and remove conf + conf.optimization_info["energy"] = conf.job["energy"] + conf.optimization_info["info"] = "calculated" + conf.optimization_info["cycles"] = conf.job["cycles"] + conf.optimization_info["ecyc"] = conf.job["ecyc"] + conf.optimization_info["decyc"] = conf.job["decyc"] + conf.optimization_info["method"] = instruction_opt["method"] + conf.optimization_info[ + "convergence" + ] = "stopped_before_converged" + print( + f"CONF{conf.id} is above threshold, dont optimize " + f"further and remove conformer." + ) + store_confs.append(calculate.pop(calculate.index(conf))) + elif conf.job["decyc"][-1] > ewin and conf.job["grad_norm"] > 0.01: + print( + f"CONF{conf.id} is above {ewin} kcal/mol but " + f"gradient norm ({conf.job['grad_norm']}) is " + f"above {0.01} --> not sorted out!" + ) + toc = time.perf_counter() + timings.append(toc - tic) + nconf_cycle.append(len(calculate) + len(prev_calculated)) + print(f"\nCYCLE {run} performed in { toc - tic:0.4f} seconds") + # + if maxecyc >= stopcycle: + print("") + for conf in list(calculate): + # don't optimize further: + print( + f"!!! Geometry optimization STOPPED because of " + f"optcycle limit of {stopcycle} cycles reached for: " + f"CONF{conf.id} within {conf.job['cycles']:>3} cycles" + ) + conf.optimization_info["info"] = "calculated" + conf.optimization_info["energy"] = conf.job["energy"] + conf.optimization_info["cycles"] = conf.job["cycles"] + conf.optimization_info["ecyc"] = conf.job["ecyc"] + conf.optimization_info["decyc"] = conf.job["decyc"] + conf.optimization_info[ + "convergence" + ] = "converged" #### THIS IS NOT CORRECT! + conf.optimization_info["method"] = instruction_opt["method"] + prev_calculated.append(calculate.pop(calculate.index(conf))) + # + maxecyc_prev = maxecyc + run += 1 + # END while loop + else: + # use standard optimization! + # update instruct_opt + tic = time.perf_counter() + del instruction_opt["optcycles"] + # calculate first round + calculate = run_in_parallel( + config, + q, + resultq, + job, + config.maxthreads, + calculate, + instruction_opt, + config.func, + ) + # check if optimization crashed + for conf in list(calculate): + if not conf.job["success"]: + print(f"removing CONF{conf.id} because optimization crashed.") + conf.optimization_info["info"] = "failed" + conf.optimization_info["convergence"] = "not_converged" + conf.optimization_info["method"] = instruction_opt["method"] + store_confs.append(calculate.pop(calculate.index(conf))) + elif conf.job["success"]: + if conf.job["converged"]: + # don't optimize further: + conf.optimization_info["info"] = "calculated" + conf.optimization_info["energy"] = conf.job["energy"] + conf.optimization_info["cycles"] = conf.job["cycles"] + conf.optimization_info["ecyc"] = conf.job["ecyc"] + conf.optimization_info["decyc"] = conf.job["decyc"] + conf.optimization_info["convergence"] = "converged" + conf.optimization_info["method"] = instruction_opt["method"] + # prev_calculated to keep it consistent with new ensemble optimizer + prev_calculated.append(calculate.pop(calculate.index(conf))) + else: + print(f"ERROR! CONF{conf.id} fell through sorting") + toc = time.perf_counter() + timings.append(toc - tic) + # ********************end standard optimization ********************* + print("Finished optimizations!".center(70, "*")) + if config.opt_spearman and ( + len(timings) == len(cycle_spearman) == len(nconf_cycle) + ): + try: + tl = max([len(f"{i: .2f}") for i in timings]) + if tl > 7: + tmp1 = tl + else: + tmp1 = 7 + print("Timings:") + print(f"Cycle: [s] {'#nconfs':^{tmp1}} Spearman coeff.") + for i in range(len(timings)): + print( + f"{i+1:>4} {timings[i]:> {tl}.2f} {nconf_cycle[i]:^{tmp1}} {cycle_spearman[i]}" + ) + print("sum: {:> .2f}".format(sum(timings))) + except Exception as e: + print(e) + else: + print("Timings:") + print("Cycle: [s]") + for i in timings: + print("{:4} {:>.2f}".format(timings.index(i) + 1, i)) + print("sum: {:>.2f}".format(sum(timings))) + print("\nCONVERGED optimizations for the following remaining conformers:") + prev_calculated.sort(key=lambda x: int(x.id)) + # end if calculate-- + for conf in list(prev_calculated): + print( + f"Converged optimization for {'CONF' + str(conf.id):{config.lenconfx+4}} " + f"after {conf.optimization_info['cycles'] :>3} cycles: " + f"{conf.optimization_info['energy']:>.7f}" + ) + calculate.append(prev_calculated.pop(prev_calculated.index(conf))) + + ensembledata.nconfs_per_part["part2_opt"] = len(calculate) + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + if config.crestcheck: + calculate, prev_calculated, store_confs = crest_routine( + config, calculate, config.func, store_confs + ) + + # reset + for conf in calculate: + conf.reset_job_info() + if not calculate and not prev_calculated: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + # ******************************Optimization done**************************** + # Start Gsolv (COSMO-RS) calculation (or only gas phase single-point) + instruction_gsolv = { + "func": config.func, + "prepinfo": ["low+"], # TM m4 scfconv6 + "basis": getattr(config, "basis", config.func_basis_default[config.func]), + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": config.solvent, + "sm": config.smgsolv2, + "omp": config.omp, + "temperature": config.temperature, + "energy": 0.0, + "energy2": 0.0, + "success": False, + "gfn_version": config.part2_gfnv, + } + if config.multitemp: + instruction_gsolv["trange"] = [ + i for i in frange(config.trange[0], config.trange[1], config.trange[2]) + ] + else: + instruction_gsolv["trange"] = [] + if config.solvent == "gas": + print("\nCalculating single-point energies!") + instruction_gsolv["jobtype"] = "sp" + # instruction_gsolv["prepinfo"] = ["low+"] + instruction_gsolv["method"], _ = config.get_method_name( + instruction_gsolv["jobtype"], + func=instruction_gsolv["func"], + basis=instruction_gsolv["basis"], + ) + folder = instruction_gsolv["func"] + name = "lowlevel single-point" + else: + print( + "\nCalculating single-point energies and solvation contribution (G_solv)!" + ) + if config.smgsolv2 in config.smgsolv_2: + # additive Gsolv + # COSMO-RS + if "cosmors" in config.smgsolv2 and config.smgsolv2 != "dcosmors": + job = TmJob + instruction_gsolv["prepinfo"] = ["low+"] + exc_fine = {"cosmors": "normal", "cosmors-fine": "fine"} + tmp = { + "jobtype": "cosmors", + "cosmorssetup": config.external_paths["cosmorssetup"], + "cosmorsparam": exc_fine.get(config.smgsolv2, "normal"), + "cosmothermversion": config.external_paths["cosmothermversion"], + "copymos": str(instruction_gsolv["func"]), + } + instruction_gsolv.update(tmp) + instruction_gsolv["method"], instruction_gsolv[ + "method2" + ] = config.get_method_name( + "cosmors", + func=instruction_gsolv["func"], + basis=instruction_gsolv["basis"], + sm=instruction_gsolv["sm"], + ) + folder = str(instruction_gsolv["func"]) + "/COSMO" + name = "lowlevel COSMO-RS" + # GBSA-Gsolv / ALPB-Gsolv + elif config.smgsolv2 in ("gbsa_gsolv", "alpb_gsolv"): + instruction_gsolv["jobtype"] = instruction_gsolv["sm"] + if config.prog == "orca": + instruction_gsolv["progpath"] = config.external_paths["orcapath"] + instruction_gsolv["xtb_driver_path"] = config.external_paths["xtbpath"] + instruction_gsolv["method"], instruction_gsolv[ + "method2" + ] = config.get_method_name( + instruction_gsolv["jobtype"], + func=instruction_gsolv["func"], + basis=instruction_gsolv["basis"], + sm=instruction_gsolv["sm"], + gfn_version=instruction_gsolv["gfn_version"], + ) + if ( + conf.lowlevel_sp_info["info"] == "calculated" + and conf.lowlevel_sp_info["method"] == instruction_gsolv["method"] + ): + # do not calculate gas phase sp again! + instruction_gsolv["energy"] = conf.lowlevel_sp_info["energy"] + instruction_gsolv["prepinfo"] = [] + # else: + # instruction_gsolv["prepinfo"] = ["low+"] + name = "lowlevel additive solvation" + folder = str(instruction_gsolv["func"]) + "/Gsolv2" + # SMD_Gsolv + elif config.smgsolv2 == "smd_gsolv": + job = OrcaJob + instruction_gsolv["jobtype"] = "smd_gsolv" + # instruction_gsolv["prepinfo"] = ["low+"] + instruction_gsolv["progpath"] = config.external_paths["orcapath"] + instruction_gsolv["method"], instruction_gsolv[ + "method2" + ] = config.get_method_name( + "smd_gsolv", + func=instruction_gsolv["func"], + basis=instruction_gsolv["basis"], + sm=instruction_gsolv["sm"], + ) + name = "lowlevel SMD_Gsolv" + folder = str(instruction_gsolv["func"]) + "/Gsolv2" + else: + # with implicit solvation + instruction_gsolv["jobtype"] = "sp_implicit" + # instruction_gsolv["prepinfo"] = ["low+"] + if config.prog == "orca": + instruction_gsolv["progpath"] = config.external_paths["orcapath"] + instruction_gsolv["method"], instruction_gsolv[ + "method2" + ] = config.get_method_name( + "sp_implicit", + func=instruction_gsolv["func"], + basis=instruction_gsolv["basis"], + sm=instruction_gsolv["sm"], + ) + name = "lowlevel single-point" + folder = instruction_gsolv["func"] + + for conf in list(calculate): + if conf.removed: + store_confs.append(calculate.pop(calculate.index(conf))) + print(f"CONF{conf.id} is removed as requested by the user!") + continue + if conf.lowlevel_sp_info["info"] == "failed": + conf = calculate.pop(calculate.index(conf)) + store_confs.append(conf) + print(f"Calculation of CONF{conf.id} failed in the previous run!") + elif conf.lowlevel_sp_info["info"] == "not_calculated": + # has to be calculated now + # take opt sp as lowlevel sp ! + if conf.optimization_info["method"] == instruction_gsolv["method"]: + if conf.optimization_info.get("method", "not_found") == "calculated": + conf = calculate.pop(calculate.index(conf)) + conf.lowlevel_sp_info["info"] = "calculated" + conf.lowlevel_sp_info["method"] = instruction_gsolv["method"] + conf.lowlevel_sp_info["energy"] = conf.optimization_info["energy"] + conf.job["success"] = True + prev_calculated.append(conf) + continue + elif conf.lowlevel_sp_info["info"] == "prep-failed": + print( + f"Preparation step for CONF{conf.id} failed in the previous " + "run and is retried now!" + ) + # is retried now! + elif conf.lowlevel_sp_info["info"] == "calculated": + conf = calculate.pop(calculate.index(conf)) + if config.solvent != "gas": + # check if solvation calculation is calculated as well + if conf.lowlevel_gsolv_info["info"] == "failed": + store_confs.append(conf) + print( + f"Calculation of the solvation contribution for CONF" + f"{conf.id} failed in the previous run!" + ) + elif conf.lowlevel_gsolv_info["info"] == "not_calculated": + calculate.append(conf) + elif conf.lowlevel_gsolv_info["info"] == "calculated": + conf.job["success"] = True + prev_calculated.append(conf) + else: + print("UNEXPECTED BEHAVIOUR") + elif config.solvent == "gas": + conf.job["success"] = True + prev_calculated.append(conf) + else: + print("\nMISSING STUFF!\n") + + if not calculate and not prev_calculated: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + if prev_calculated: + check_for_folder(config.cwd, [i.id for i in prev_calculated], folder) + if config.solvent == "gas": + print("The low level_single-point was calculated before for:") + else: + print("The low level gsolv calculation was calculated before for:") + print_block(["CONF" + str(i.id) for i in prev_calculated]) + pl = config.lenconfx + 4 + len(str("/" + folder)) + + check = {True: "was successful", False: "FAILED"} + if calculate: + if config.solvent == "gas": + print("The low level_single-point is now calculated for:") + if config.solvent != "gas" and config.smgsolv2 in config.smgsolv_2: + print("The low level gsolv calculation is now calculated for:") + # need to create folders + save_errors, store_confs, calculate = new_folders( + config.cwd, calculate, folder, save_errors, store_confs + ) + # need to copy optimized coord to COSMO/GSOLV2 folder + for conf in calculate: + tmp1 = os.path.join( + config.cwd, + "CONF" + str(conf.id), + instruction_gsolv["func"], + "coord", + ) + tmp2 = os.path.join("CONF" + str(conf.id), folder, "coord") + try: + shutil.copy(tmp1, tmp2) + except FileNotFoundError: + print("ERROR can't copy optimized geometry!") + print_block(["CONF" + str(i.id) for i in calculate]) + # parallel execution: + calculate = run_in_parallel( + config, + q, + resultq, + job, + config.maxthreads, + calculate, + instruction_gsolv, + folder, + ) + + for conf in list(calculate): + if instruction_gsolv["jobtype"] == "sp": + line = ( + f"{name} calculation {check[conf.job['success']]}" + f" for {last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.job['energy']:>.8f}" + ) + print(line) + if not conf.job["success"]: + save_errors.append(line) + conf.lowlevel_sp_info["method"] = instruction_gsolv["method"] + conf.lowlevel_sp_info["info"] = "failed" + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.lowlevel_sp_info["energy"] = conf.job["energy"] + conf.lowlevel_sp_info["info"] = "calculated" + conf.lowlevel_sp_info["method"] = instruction_gsolv["method"] + elif instruction_gsolv["jobtype"] == "sp_implicit": + line = ( + f"{name} calculation {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.job['energy']:>.8f}" + ) + print(line) + if not conf.job["success"]: + save_errors.append(line) + conf.lowlevel_sp_info["info"] = "failed" + conf.lowlevel_sp_info["method"] = conf.job["method"] + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.lowlevel_sp_info["energy"] = conf.job["energy"] + conf.lowlevel_sp_info["info"] = "calculated" + conf.lowlevel_sp_info["method"] = conf.job["method"] + elif instruction_gsolv["jobtype"] in ( + "cosmors", + "smd_gsolv", + "gbsa_gsolv", + "alpb_gsolv", + ): + line = ( + f"{name} calculation {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 3):>{pl}}: " + f"{conf.job['energy2']:>.8f}" + ) + print(line) + if not conf.job["success"]: + save_errors.append(line) + conf.lowlevel_sp_info["info"] = "failed" + conf.lowlevel_sp_info["method"] = conf.job["method"] + conf.lowlevel_gsolv_info["info"] = "failed" + conf.lowlevel_gsolv_info["method"] = conf.job["method2"] + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.lowlevel_sp_info["energy"] = conf.job["energy"] + conf.lowlevel_sp_info["info"] = "calculated" + conf.lowlevel_sp_info["method"] = instruction_gsolv["method"] + conf.lowlevel_gsolv_info["energy"] = conf.job["energy2"] + conf.lowlevel_gsolv_info["gas-energy"] = conf.job["energy"] + conf.lowlevel_gsolv_info["info"] = "calculated" + conf.lowlevel_gsolv_info["method"] = instruction_gsolv["method2"] + conf.lowlevel_gsolv_info["range"] = conf.job["erange1"] + else: + print( + f'UNEXPECTED BEHAVIOUR: {conf.job["success"]} {conf.job["jobtype"]}' + ) + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + check_tasks(calculate, config.check) + else: + print("No conformers are considered additionally.") + # adding conformers calculated before: + if prev_calculated: + for conf in list(prev_calculated): + conf.job["workdir"] = os.path.normpath( + os.path.join(config.cwd, "CONF" + str(conf.id), folder) + ) + if instruction_gsolv["jobtype"] in ("sp", "sp_implicit"): + print( + f"{name} calculation {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.lowlevel_sp_info['energy']:>.7f}" + ) + elif instruction_gsolv["jobtype"] in ( + "cosmors", + "smd_gsolv", + "gbsa_gsolv", + "alpb_gsolv", + ): + print( + f"{name} calculation {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 3):>{pl}}: " + f"{conf.lowlevel_gsolv_info['energy']:>.7f}" + ) + calculate.append(prev_calculated.pop(prev_calculated.index(conf))) + # reset + for conf in calculate: + conf.reset_job_info() + if not calculate: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + # *************************************************************************** + # Starting grrho calculation on DFT geometry (bhess) + if config.evaluate_rrho: + if config.solvent == "gas": + print("\nCalculating lowlevel G_mRRHO on DFT geometry!") + else: + print( + "\nCalculating lowlevel G_mRRHO with implicit solvation " + "on DFT geometry!" + ) + for conf in list(calculate): + if conf.lowlevel_grrho_info["info"] == "not_calculated": + pass + if conf.lowlevel_grrho_info["info"] == "prep-failed": + # try again + pass + elif conf.lowlevel_grrho_info["info"] == "failed": + conf = calculate.pop(calculate.index(conf)) + conf.__class__ = job + store_confs.append(conf) + print(f"Calculation of CONF{conf.id} failed in the previous run!") + elif conf.lowlevel_grrho_info["info"] == "calculated": + conf = calculate.pop(calculate.index(conf)) + conf.__class__ = job + conf.job["success"] = True + prev_calculated.append(conf) + + if not calculate and not prev_calculated: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + folderrho = "rrho_part2" + if prev_calculated: + check_for_folder(config.cwd, [i.id for i in prev_calculated], folderrho) + print("The G_mRRHO calculation was performed before for:") + print_block(["CONF" + str(i.id) for i in prev_calculated]) + pl = config.lenconfx + 4 + len(str("/" + folderrho)) + instruction_rrho = { + "jobtype": "rrhoxtb", + "func": getattr(config, "part2_gfnv"), + "gfn_version": getattr(config, "part2_gfnv"), + "temperature": config.temperature, + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": config.solvent, + "omp": config.omp, + "progpath": config.external_paths["xtbpath"], + "bhess": config.bhess, + "sm_rrho": config.sm_rrho, + "rmsdbias": config.rmsdbias, + "cwd": config.cwd, + "consider_sym": config.consider_sym, + "energy": 0.0, + "energy2": 0.0, + "success": False, + } + instruction_rrho["method"], _ = config.get_method_name( + "rrhoxtb", + bhess=config.bhess, + gfn_version=instruction_rrho["gfn_version"], + sm=instruction_rrho["sm_rrho"], + solvent=instruction_rrho["solvent"], + ) + if config.multitemp: + instruction_rrho["trange"] = [ + i for i in frange(config.trange[0], config.trange[1], config.trange[2]) + ] + else: + instruction_rrho["trange"] = [] + + if calculate: + print("The lowlevel G_mRRHO calculation is now performed for:") + print_block(["CONF" + str(i.id) for i in calculate]) + # create folders: + save_errors, store_confs, calculate = new_folders( + config.cwd, calculate, folderrho, save_errors, store_confs + ) + # copy optimized geoms to folder + for conf in list(calculate): + try: + tmp_from = os.path.join( + config.cwd, "CONF" + str(conf.id), config.func + ) + tmp_to = os.path.join(config.cwd, "CONF" + str(conf.id), folderrho) + shutil.copy( + os.path.join(tmp_from, "coord"), os.path.join(tmp_to, "coord") + ) + except shutil.SameFileError: + pass + except FileNotFoundError: + if not os.path.isfile(os.path.join(tmp_from, "coord")): + print( + "ERROR: while copying the coord file from {}! " + "The corresponding file does not exist.".format(tmp_from) + ) + elif not os.path.isdir(tmp_to): + print("ERROR: Could not create folder {}!".format(tmp_to)) + print("ERROR: Removing conformer {}!".format(conf.name)) + conf.lowlevel_grrho_info["info"] = "prep-failed" + store_confs.append(calculate.pop(calculate.index(conf))) + save_errors.append(f"CONF{conf.id} was removed, because IO failed!") + # parallel execution: + calculate = run_in_parallel( + config, + q, + resultq, + job, + config.maxthreads, + calculate, + instruction_rrho, + folderrho, + ) + check = {True: "was successful", False: "FAILED"} + # check if too many calculations failed + + ### + for conf in list(calculate): + print( + f"The lowlevel G_mRRHO calculation @ {conf.job['symmetry']} " + f"{check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.job['energy']:>.8f}" + ) + if not conf.job["success"]: + conf.lowlevel_grrho_info["info"] = "failed" + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.sym = conf.job["symmetry"] + conf.lowlevel_grrho_info["rmsd"] = conf.job["rmsd"] + conf.lowlevel_grrho_info["energy"] = conf.job["energy"] + conf.lowlevel_grrho_info["info"] = "calculated" + conf.lowlevel_grrho_info["method"] = instruction_rrho["method"] + conf.lowlevel_grrho_info["range"] = conf.job["erange1"] + conf.lowlevel_hrrho_info["range"] = conf.job["erange2"] + conf.lowlevel_hrrho_info["info"] = "calculated" + conf.lowlevel_hrrho_info["method"] = instruction_rrho["method"] + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + check_tasks(calculate, config.check) + else: + print("No conformers are considered additionally.") + # adding conformers calculated before: + if prev_calculated: + for conf in list(prev_calculated): + conf.job["workdir"] = os.path.normpath( + os.path.join(config.cwd, "CONF" + str(conf.id), folderrho) + ) + print( + f"The lowlevel G_mRRHO calculation @ {conf.sym} " + f"{check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.lowlevel_grrho_info['energy']:>.8f}" + ) + calculate.append(prev_calculated.pop(prev_calculated.index(conf))) + if not calculate: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + # printout for part2 ------------------------------------------------------- + print("\n" + "".ljust(int(PLENGTH / 2), "-")) + print("* Gibbs free energies of part2 *".center(int(PLENGTH / 2), " ")) + print("".ljust(int(PLENGTH / 2), "-") + "\n") + columncall = [ + lambda conf: "CONF" + str(getattr(conf, "id")), + lambda conf: getattr(conf, "xtb_energy"), + lambda conf: getattr(conf, "rel_xtb_energy"), + lambda conf: getattr(conf, "lowlevel_sp_info")["energy"], + lambda conf: getattr(conf, "lowlevel_gsolv_info")["energy"], + lambda conf: getattr(conf, "lowlevel_grrho_info")["energy"], + lambda conf: getattr(conf, "free_energy"), + lambda conf: getattr(conf, "rel_free_energy"), + lambda conf: getattr(conf, "bm_weight") * 100, + ] + columnheader = [ + "CONF#", + "E(GFNn-xTB)", + "ΔE(GFNn-xTB)", + "E [Eh]", + "Gsolv [Eh]", + "GmRRHO [Eh]", + "Gtot", + "ΔGtot", + "Boltzmannweight", + ] + columndescription = [ + "", + "[a.u.]", + "[kcal/mol]", + "", + "", # Gsolv + "", + "[Eh]", + "[kcal/mol]", + f" % at {config.temperature:.2f} K", + ] + columndescription2 = ["", "", "", "", "", "", "", "", ""] + columnformat = [ + "", + (12, 7), + (5, 2), + (12, 7), + (12, 7), + (12, 7), + (12, 7), + (5, 2), + (5, 2), + ] + if config.solvent == "gas": + # Energy + columndescription[3] = instruction_gsolv["method"] + elif config.solvent != "gas": + # Energy + columndescription[3] = instruction_gsolv["method"] + # Gsolv + columndescription[4] = instruction_gsolv["method2"] + if config.evaluate_rrho: + # Grrho + columndescription[5] = instruction_rrho["method"] + if not config.evaluate_rrho or config.solvent == "gas": + if not config.evaluate_rrho: + # ignore rrho in printout + columncall.pop(5) + columnheader.pop(5) + columndescription.pop(5) + columnformat.pop(5) + if config.solvent == "gas": + # ignore Gsolv + columncall.pop(4) + columnheader.pop(4) + columndescription.pop(4) + columnformat.pop(4) + + for conf in calculate: + if not config.evaluate_rrho: + rrho = None + else: + rrho = "lowlevel_grrho_info" + if config.solvent == "gas": + solv = None + else: + solv = "lowlevel_gsolv_info" + e = "lowlevel_sp_info" + conf.calc_free_energy(e=e, solv=solv, rrho=rrho) + + calculate = calc_boltzmannweights(calculate, "free_energy", config.temperature) + try: + minfree = min([i.free_energy for i in calculate if i is not None]) + except ValueError: + raise ValueError + for conf in calculate: + conf.rel_free_energy = (conf.free_energy - minfree) * AU2KCAL + if conf.free_energy == minfree: + lowestconf = conf.id + ensembledata.bestconf["part2"] = conf.id + + calculate.sort(key=lambda x: int(x.id)) + printout( + os.path.join(config.cwd, "part2.dat"), + columncall, + columnheader, + columndescription, + columnformat, + calculate, + minfree, + columndescription2=columndescription2, + ) + # printout for part2 ------------------------------------------------------- + + # *************************************************************************** + # SD on solvation: + # DCOSMO-RS_GSOLV + instruction_gsolv_compare = { + "func": config.func, + "basis": getattr(config, "basis", config.func_basis_default[config.func]), + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": config.solvent, + "sm": "alpb_gsolv", + "omp": config.omp, + "temperature": config.temperature, + "energy": 0.0, + "energy2": 0.0, + "success": False, + "gfn_version": config.part2_gfnv, + } + instruction_gsolv_compare["trange"] = [] + instruction_gsolv_compare["prepinfo"] = [] + instruction_gsolv_compare["xtb_driver_path"] = config.external_paths["xtbpath"] + instruction_gsolv_compare["jobtype"] = instruction_gsolv_compare["sm"] + _, instruction_gsolv_compare["method"] = config.get_method_name( + instruction_gsolv_compare["jobtype"], + func=instruction_gsolv_compare["func"], + basis=instruction_gsolv_compare["basis"], + sm=instruction_gsolv_compare["sm"], + gfn_version=instruction_gsolv_compare["gfn_version"], + ) + folder_compare = "alpb_gsolv" + name = "alpb_gsolv".upper() + pl = config.lenconfx + 4 + len(str("/" + folder_compare)) + if ( + config.solvent != "gas" + and config.sm2 == "dcosmors" + and config.smgsolv2 in ("cosmors", "cosmors-fine") + ): + dorun = True + while dorun: + print( + "\nCalculating ALPB_Gsolv values for evaluation of the std. dev. of Gsolv." + ) + for conf in calculate: + if conf.id == ensembledata.bestconf["part2"]: + gsolv_min = conf.lowlevel_gsolv_info["energy"] + gsolv_min_id = conf.id + try: + dcosmors_gsolv_min = ( + conf.optimization_info["energy"] + - conf.lowlevel_gsolv_info["gas-energy"] + ) + except (TypeError, KeyError): + print( + "ERROR: Can't calculate DCOSMO-RS_gsolv. Skipping SD of Gsolv!" + ) + dorun = False + break + + for conf in list(calculate): + if conf.lowlevel_gsolv_compare_info["info"] == "not_calculated": + pass + if conf.lowlevel_gsolv_compare_info["info"] == "prep-failed": + # try again + pass + elif conf.lowlevel_gsolv_compare_info["info"] == "failed": + # dont remove conformer this is only to calculate SD + conf = calculate.pop(calculate.index(conf)) + conf.job["success"] = True + conf.lowlevel_gsolv_compare_info["energy"] = 0.0 + prev_calculated.append(conf) + print(f"Calculation of CONF{conf.id} failed in the previous run!") + elif conf.lowlevel_gsolv_compare_info["info"] == "calculated": + conf = calculate.pop(calculate.index(conf)) + conf.job["success"] = True + prev_calculated.append(conf) + # need to create folders + save_errors, store_confs, calculate = new_folders( + config.cwd, calculate, folder_compare, save_errors, store_confs + ) + # need to copy optimized coord to COSMO/GSOLV2 folder + for conf in calculate: + tmp1 = os.path.join( + config.cwd, + "CONF" + str(conf.id), + instruction_gsolv_compare["func"], + "coord", + ) + tmp2 = os.path.join("CONF" + str(conf.id), folder_compare, "coord") + try: + shutil.copy(tmp1, tmp2) + except FileNotFoundError: + print("ERROR can't copy optimized geometry!") + + if calculate: + calculate = run_in_parallel( + config, + q, + resultq, + job, + config.maxthreads, + calculate, + instruction_gsolv_compare, + folder_compare, + ) + for conf in calculate: + line = ( + f"{name} calculation {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.job['energy2']:>.8f}" + ) + print(line) + if not conf.job["success"]: + save_errors.append(line) + conf.lowlevel_gsolv_compare_info["info"] = "failed" + conf.lowlevel_gsolv_compare_info["method"] = conf.job["method"] + # store_confs.append(calculate.pop(calculate.index(conf))) + print("ERROR") + else: + conf.lowlevel_gsolv_compare_info["energy"] = conf.job["energy2"] + conf.lowlevel_gsolv_compare_info["info"] = "calculated" + conf.lowlevel_gsolv_compare_info["method"] = instruction_gsolv[ + "method" + ] + if prev_calculated: + for conf in list(prev_calculated): + conf.job["workdir"] = os.path.normpath( + os.path.join(config.cwd, "CONF" + str(conf.id), folder_compare) + ) + line = ( + f"{name} calculation {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.lowlevel_gsolv_compare_info['energy']:>.8f}" + ) + print(line) + calculate.append(prev_calculated.pop(prev_calculated.index(conf))) + for conf in calculate: + if conf.id == gsolv_min_id: + alpb_gsolv_min = conf.lowlevel_gsolv_compare_info["energy"] + + print("\nSD of solvation models (all units in kcal/mol):") + print( + f"CONFX ΔG(COSMO-RS) ΔG(DCOSMO-RS_gsolv) ΔG(ALPB_gsolv) " + f"SD(COSMO-RS 40%, DCOSMO-RS_gsolv 40%, ALPB_gsolv 20%)" + ) + print("".ljust(PLENGTH, "-")) + pl = max([len(str(conf.id)) for conf in calculate]) + for conf in calculate: + dgsolv = -gsolv_min + conf.lowlevel_gsolv_info["energy"] + dgdcosmors = -dcosmors_gsolv_min + ( + conf.optimization_info["energy"] + - conf.lowlevel_gsolv_info["gas-energy"] + ) + dgalpb = -alpb_gsolv_min + conf.lowlevel_gsolv_compare_info["energy"] + conf.lowlevel_gsolv_compare_info["std_dev"] = calc_weighted_std_dev( + [dgsolv, dgdcosmors, dgalpb], weights=[0.4, 0.4, 0.2] + ) + print( + f"CONF{conf.id:<{pl}} {dgsolv*AU2KCAL:^ 12.2f} " + f"{dgdcosmors*AU2KCAL:^ 19.2f} {dgalpb*AU2KCAL:^ 14.2f}" + f" {conf.lowlevel_gsolv_compare_info['std_dev']*AU2KCAL:^ 41.2f}" + ) + print("".ljust(PLENGTH, "-")) + dorun = False + break + # END SD Gsolv + + # calculate average G correction + print("\nCalculating Boltzmann averaged free energy of ensemble!\n") + avGcorrection = { + "avGcorrection": {}, + "avG": {}, + "avE": {}, + "avGsolv": {}, + "avGRRHO": {}, + } + if config.multitemp: + trange = [ + i for i in frange(config.trange[0], config.trange[1], config.trange[2]) + ] + else: + trange = [config.temperature] + # calculate Boltzmannweights + if not config.evaluate_rrho or config.solvent == "gas": + if not config.evaluate_rrho and config.solvent == "gas": + line = ( + f"{'temperature /K:':<15} {'avE(T) /a.u.':>14} " + f"{'avG(T) /a.u.':>14} " + # f"{'avGcorrection(T) /a.u.':>22}" + ) + elif not config.evaluate_rrho: + line = ( + f"{'temperature /K:':<15} {'avE(T) /a.u.':>14} " + f"{'avGsolv(T) /a.u.':>16} {'avG(T) /a.u.':>14} " + # f"{'avGcorrection(T) /a.u.':>22}" + ) + elif config.solvent == "gas": + line = ( + f"{'temperature /K:':<15} {'avE(T) /a.u.':>14} " + f"{'avGmRRHO(T) /a.u.':>16} {'avG(T) /a.u.':>14} " + # f"{'avGcorrection(T) /a.u.':>22}" + ) + else: + line = ( + f"{'temperature /K:':<15} {'avE(T) /a.u.':>14} " + f"{'avGmRRHO(T) /a.u.':>16} {'avGsolv(T) /a.u.':>16} " + f"{'avG(T) /a.u.':>14}" + # f" {'avGcorrection(T) /a.u.':>22}" + ) + print(line) + print("".ljust(int(PLENGTH), "-")) + for temperature in trange: + # get free energy at (T) + for conf in calculate: + if not config.evaluate_rrho: + rrho = None + else: + rrho = "lowlevel_grrho_info" + if config.solvent == "gas": + solv = None + else: + solv = "lowlevel_gsolv_info" + e = "lowlevel_sp_info" + conf.calc_free_energy(e=e, solv=solv, rrho=rrho, t=temperature) + try: + minfreeT = min( + [conf.free_energy for conf in calculate if conf.free_energy is not None] + ) + except ValueError: + raise ValueError + + calculate = calc_boltzmannweights(calculate, "free_energy", temperature) + avG = 0.0 + avE = 0.0 + avGRRHO = 0.0 + # avHRRHO but with new boltzmann weights? + avGsolv = 0.0 + for conf in calculate: + avG += conf.bm_weight * conf.free_energy + avE += conf.bm_weight * conf.lowlevel_sp_info["energy"] + avGRRHO += conf.bm_weight * conf.lowlevel_grrho_info["range"].get( + temperature, 0.0 + ) + avGsolv += conf.bm_weight * conf.lowlevel_gsolv_info["range"].get( + temperature, 0.0 + ) + + avGcorrection["avG"][temperature] = avG + avGcorrection["avE"][temperature] = avE + avGcorrection["avGRRHO"][temperature] = avGRRHO + avGcorrection["avGsolv"][temperature] = avGsolv + for conf in calculate: + if conf.free_energy == minfreeT: + avGcorrection["avGcorrection"][temperature] = avG - conf.free_energy + # printout: + if not config.evaluate_rrho or config.solvent == "gas": + if not config.evaluate_rrho and config.solvent == "gas": + line = ( + f"{temperature:^15} {avE:>14.7f} {avG:>14.7f} " + # f"{avGcorrection['avGcorrection'][temperature]:>22.7f}" + ) + elif not config.evaluate_rrho: + line = ( + f"{temperature:^15} {avE:>14.7f} {avGsolv:>16.7f} " + f"{avG:>14.7f} " + # f"{ avGcorrection['avGcorrection'][temperature]:>22.7f}" + ) + elif config.solvent == "gas": + line = ( + f"{temperature:^15} {avE:>14.7f} {avGRRHO:>16.7f} " + f"{avG:>14.7f} " + # f"{ avGcorrection['avGcorrection'][temperature]:>22.7f}" + ) + else: + line = ( + f"{temperature:^15} {avE:>14.7f} {avGRRHO:>16.7f} " + f"{avGsolv:>16.7f} {avG:>14.7f} " + # f"{ avGcorrection['avGcorrection'][temperature]:>22.7f}" + ) + if temperature == config.temperature: + print(line, " <<====") + else: + print(line) + print("".ljust(int(PLENGTH), "-")) + print("") + + # reset boltzmannweights to correct temperature + # get free energy at (T) + for conf in calculate: + if not config.evaluate_rrho: + rrho = None + else: + rrho = "lowlevel_grrho_info" + if config.solvent == "gas": + solv = None + else: + solv = "lowlevel_gsolv_info" + e = "lowlevel_sp_info" + conf.calc_free_energy(e=e, solv=solv, rrho=rrho) + # ensembledata is used to store avGcorrection + ensembledata.comment = [ + lowestconf, + "storage for avGcorrection of ensemble", + f"corresponding to CONF{lowestconf}", + ] + ensembledata.avGcorrection = avGcorrection + + if not calculate: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + calculate = calc_boltzmannweights(calculate, "free_energy", config.temperature) + + try: + minfree = min( + [conf.free_energy for conf in calculate if conf.free_energy is not None] + ) + except ValueError: + raise ValueError + for conf in calculate: + conf.rel_free_energy = (conf.free_energy - minfree) * AU2KCAL + + for conf in calculate: + if conf.free_energy == minfree: + ensembledata.bestconf["part2"] = conf.id + + # + print("") + onlyprintout = deepcopy(calculate) + onlyprintout.sort(reverse=True, key=lambda x: float(x.bm_weight)) + for i in (100, 95, 90, 80, 70): + sumup = 0.0 + for conf in list(onlyprintout): + sumup += conf.bm_weight + if sumup > (i / 100): + if conf.bm_weight <= (1 - (i / 100)): + onlyprintout.pop(onlyprintout.index(conf)) + + # write ensemble + outfile = f"enso_ensemble_part2_p_{i}.xyz" + # move_recursively(config.cwd, outfile) + kwargs = {"energy": "xtb_energy", "rrho": "lowlevel_grrho_info"} + write_trj( + sorted(onlyprintout, key=lambda x: float(x.free_energy)), + config.cwd, + outfile, + config.func, + config.nat, + "free_energy", + overwrite=True, + **kwargs, + ) + + # SORTING for the next part: + print("\n" + "".ljust(int(PLENGTH / 2), "-")) + print("Conformers considered further".center(int(PLENGTH / 2), " ")) + print("".ljust(int(PLENGTH / 2), "-") + "\n") + # evaluate conformer consideration based on Boltzmann-population + calculate.sort(reverse=True, key=lambda x: float(x.bm_weight)) + sumup = 0.0 + for conf in list(calculate): + sumup += conf.bm_weight + if sumup >= (config.part2_threshold / 100): + if conf.bm_weight < (1 - (config.part2_threshold / 100)): + mol = calculate.pop(calculate.index(conf)) + mol.part_info["part2"] = "refused" + store_confs.append(mol) + else: + conf.part_info["part2"] = "passed" + else: + conf.part_info["part2"] = "passed" + + ensembledata.nconfs_per_part["part2"] = len(calculate) + if calculate: + print( + f"\nConformers that are below the Boltzmann-thr of {config.part2_threshold}%:" + ) + print_block(["CONF" + str(i.id) for i in calculate]) + else: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + # write ensemble + move_recursively(config.cwd, "enso_ensemble_part2.xyz") + if config.evaluate_rrho: + kwargs = {"energy": "xtb_energy_unbiased", "rrho": "lowlevel_grrho_info"} + else: + kwargs = {"energy": "xtb_energy_unbiased"} + write_trj( + sorted(calculate, key=lambda x: float(x.free_energy)), + config.cwd, + "enso_ensemble_part2.xyz", + config.func, + config.nat, + "free_energy", + **kwargs, + ) + + # write coord.enso_best + for conf in calculate: + if conf.id == ensembledata.bestconf["part2"]: + # copy the lowest optimized conformer to file coord.enso_best + with open( + os.path.join("CONF" + str(conf.id), config.func, "coord"), + "r", + encoding=CODING, + newline=None, + ) as f: + coord = f.readlines() + with open( + os.path.join(config.cwd, "coord.enso_best"), "w", newline=None + ) as best: + best.write( + "$coord # {} {} !CONF{} \n".format( + conf.free_energy, conf.lowlevel_grrho_info["energy"], conf.id + ) + ) + for line in coord[1:]: + if "$" in line: # stop at $end ... + break + best.write(line) + best.write("$end \n") + + # reset + for conf in calculate: + conf.free_energy = 0.0 + conf.rel_free_energy = 0.0 + conf.bm_weight = 0.0 + conf.reset_job_info() + + if save_errors: + print( + "***---------------------------------------------------------***", + file=sys.stderr, + ) + print( + "Printing most relevant errors again, just for user convenience:", + file=sys.stderr, + ) + for _ in list(save_errors): + print(save_errors.pop(), file=sys.stderr) + print( + "***---------------------------------------------------------***", + file=sys.stderr, + ) + + tmp = int((PLENGTH - len("END of Part2")) / 2) + print("\n" + "".ljust(tmp, ">") + "END of Part2" + "".rjust(tmp, "<")) + return config, calculate, store_confs, ensembledata diff --git a/censo_qm/orca_job.py b/censo_qm/orca_job.py new file mode 100644 index 0000000..a3a66fa --- /dev/null +++ b/censo_qm/orca_job.py @@ -0,0 +1,881 @@ +""" +Contains OrcaJob class for calculating ORCA related properties of conformers. +""" +from collections import OrderedDict +import os +import time +import subprocess +import shutil +from .cfg import CODING, ENVIRON, censo_solvent_db, external_paths +from .utilities import last_folders, t2x, x2t, print +from .qm_job import QmJob + + +class OrcaJob(QmJob): + """ + Perform calculations with ORCA + - create orca.inp input + - single-point calculation + - smd_gsolv calculation + - optimization with xTB as driver + - shielding constant calculations + - coupling constant calculations + - writing of generic output for shielding and coupling constants + """ + + def __init__(self, rank, *args, **kwargs): + QmJob.__init__(self, rank, *args, **kwargs) + + def _prep_input(self, xyzfile=False, returndict=False): + """ + cefine preparation step analogue + + use: + xyzfile --> if set : * xyzfile ... xyzfile.xyz + returns + call --> list with settings + """ + + # low => grid3 loosescf + # low+ >> grid3 scfconv6 + # high --> + # input generation + # if optlevel == 'crude': + # inp.write("! grid3 loosescf \n") + # elif optlevel == 'lax': + # inp.write("! grid4 scfconv6 \n") + # else: + # inp.write("! grid4 \n") + + orcainput_start = OrderedDict( + [ + ("functional", None), + ("disp", None), + ("basis", None), + ("gcp", None), + ("RI-approx", None), + ("grid", None), + ("scfconv", None), + ("frozencore", None), + ("mp2", None), + ("default", None), + ("job", None), + ("optthreshold", None), + ("parallel", None), + ("solvation", None), + ("geom", None), + ("couplings", None), + ("shieldings", None), + ] + ) + if "nmr" in self.job["prepinfo"]: + nmrprop = True + else: + nmrprop = False + + # definitions: + composite_dfa = ("pbeh-3c", "b97-3c", "b973c", "hf-3c", "hf3c", "r2scan-3c") + ggadfa = ("tpss", "pbe", "kt2", "b97-d3") + # B97-D3 Grimme’s GGA including D3 dispersion correction + hybriddfa = ("pbe0", "pw6b95", "wb97x-d3") + dhdfa = ("dsd-blyp",) + + disp_already_included_in_func = () + + # build up call: + default_call = [ + "! smallprint printgap noloewdin", + "! NOSOSCF", + "%MaxCore 8000", + "%output", + " print[P_BondOrder_M] 1", + " print[P_Mayer] 1", + " print[P_basis] 2", + "end", + ] + + orcainput = orcainput_start.copy() + orcainput["default"] = default_call + # set functional + if self.job["func"] in composite_dfa: + orcainput["functional"] = [f"! {self.job['func']}"] + else: + if self.job["func"] == "kt2": + orcainput["functional"] = [ + "%method", + " method dft", + " functional gga_xc_kt2", + "end", + ] + elif self.job["func"] == "dsd-blyp": + orcainput["functional"] = [f"! ri-{self.job['func']}"] + else: + orcainput["functional"] = [f"! {self.job['func']}"] + # set basis set + orcainput["basis"] = [f"! {self.job['basis']}"] + # set gcp: + if "DOGCP" in self.job['prepinfo']: + gcp_keywords = { + 'minis': "MINIS", + "sv": "SV", + "6-31g(d)": "631GD", + 'def2-sv(p)': "SV(P)", + 'def2-svp': "SVP", + 'def2-tzvp': "TZ", + } + if self.job['basis'].lower() in gcp_keywords.keys(): + orcainput["gcp"] = [f"! GCP(DFT/{gcp_keywords[self.job['basis'].lower()]})"] + # set RI def2/J, RIJCOSX def2/J gridx6 NOFINALGRIDX, RIJK def2/JK + if self.job["func"] in dhdfa: + if nmrprop: + orcainput["frozencore"] = ["!NOFROZENCORE"] + else: + orcainput["frozencore"] = ["! Frozencore"] + def2cbasis = ("Def2-SVP", "Def2-TZVP", "Def2-TZVPP", "Def2-QZVPP") + if str(self.job["basis"]).upper() in def2cbasis: + # --> decide cosx or RIJK + orcainput["RI-approx"] = [ + f"! def2/J {str(self.job['basis'])}/C RIJCOSX GRIDX7 NOFINALGRIDX" + ] + # call.append(f"! RIJK def2/JK {str(self.job['basis'])}/C") + else: + orcainput["RI-approx"] = [ + f"! def2/J def2-TZVPP/C RIJCOSX GRIDX7 NOFINALGRIDX" + ] + # call.append(f"! RIJK def2/JK def2-TZVPP/C ") + if nmrprop: + orcainput["mp2"] = [ + "%mp2", + " RI true", + " density relaxed", + "end", + ] + else: + orcainput["mp2"] = ["%mp2", " RI true", "end"] + elif self.job["func"] in hybriddfa: + orcainput["RI-approx"] = [f"! def2/J RIJCOSX GRIDX6 NOFINALGRIDX"] + elif self.job["func"] in composite_dfa: + pass + else: # essentially gga + orcainput["RI-approx"] = ["! RI def2/J"] + # set grid + if self.job["func"] in dhdfa or self.job["func"] in hybriddfa: + orcainput["grid"] = ["! grid5 nofinalgrid"] + else: + orcainput["grid"] = ["! grid4 nofinalgrid"] + + orcainput["scfconv"] = ["! scfconv6"] + # set scfconv or convergence threshold e.g. loosescf or scfconv6 + + extension = { + "low": {"grid": ["! grid4 nofinalgrid"], "scfconv": ["! loosescf"]}, + "low+": {"grid": ["! grid4 nofinalgrid"], "scfconv": ["! scfconv6"]}, + "high": {"grid": ["! grid4 nofinalgrid"], "scfconv": ["! scfconv7"]}, + "high+": {"grid": ["! grid5 nofinalgrid"], "scfconv": ["! scfconv7"]}, + } + if self.job["prepinfo"]: + if isinstance(self.job["prepinfo"], list): + if self.job["prepinfo"][0] in extension.keys(): + orcainput["grid"] = extension[self.job["prepinfo"][0]]["grid"] + orcainput["scfconv"] = extension[self.job["prepinfo"][0]]["scfconv"] + else: + pass + + # add dispersion + if self.job["func"] not in composite_dfa: + orcainput["disp"] = ["! d3bj"] + # optimization ancopt or pure orca + if self.job["jobtype"] == "xtbopt": + orcainput["job"] = ["! ENGRAD"] + elif self.job["jobtype"] == "opt": + orcainput["job"] = ["! OPT"] + # add thresholds + orcainput["optthreshold"] = [] + # nprocs + if int(self.job["omp"]) >= 1: + orcainput["parallel"] = [ + "%pal", + " nprocs {}".format(self.job["omp"]), + "end", + ] + # solvent model + # upd_solvent = { + # "chcl3": "chloroform", + # "h2o": "water", + # "ch2cl2":"dichloromethane", + # "octanol": "1-octanol", + # "hexadecane": "N-HEXADECANE", + # } + # solventexch = { + # "acetone": "Acetone", + # "chcl3": "Chloroform", + # "acetonitrile": "Acetonitrile", + # "ch2cl2": "CH2Cl2", + # "dmso": "DMSO", + # "h2o": "Water", + # "methanol": "Methanol", + # "thf": "THF", + # "toluene": "Toluene", + # "octanol": "Octanol", + # } + # if self.job['solvent'] != 'gas': + # if self.job['sm'] in ('smd', 'smd_gsolv'): + # self.job['solvent'] = upd_solvent.get(self.job['solvent'], self.job['solvent']) + # orcainput['solvation'] = [ + # '%cpcm', + # ' smd true', + # (f' smdsolvent ' + # f'"{solventexch.get(self.job["solvent"],self.job["solvent"])}"'), + # 'end', + # ] + # elif self.job['sm'] == 'cpcm': + # orcainput['solvation'] = [( + # f"! CPCM(" + # f"{solventexch.get(self.job['solvent'],self.job['solvent'])})" + # ), + # ] + if self.job["solvent"] != "gas": + if self.job["sm"] in ("smd", "smd_gsolv"): + orcainput["solvation"] = [ + "%cpcm", + " smd true", + ( + f" smdsolvent " + f'"{censo_solvent_db[self.job["solvent"]]["smd"][1]}"' + ), + "end", + ] + elif self.job["sm"] == "cpcm": + orcainput["solvation"] = [ + (f"! CPCM(" f"{censo_solvent_db[self.job['solvent']]['cpcm'][1]})") + ] + # unpaired, charge, and coordinates + if xyzfile: + orcainput["geom"] = [ + ( + f"* xyzfile {self.job['charge']} " + f"{self.job['unpaired']+1} {str(xyzfile)}" + ) + ] + else: + # xyz geometry + geom, _ = t2x(self.job["workdir"]) + orcainput["geom"] = [f"*xyz {self.job['charge']} {self.job['unpaired']+1}"] + orcainput["geom"].extend(geom) + orcainput["geom"].append("*") + # couplings + if nmrprop and "nmrJ" in self.job["prepinfo"]: + tmp = [] + tmp.append("%eprnmr") + if self.job["hactive"]: + tmp.append(" Nuclei = all H { ssfc }") + if self.job["cactive"]: + tmp.append(" Nuclei = all C { ssfc }") + if self.job["factive"]: + tmp.append(" Nuclei = all F { ssfc }") + if self.job["siactive"]: + tmp.append(" Nuclei = all Si { ssfc }") + if self.job["pactive"]: + tmp.append(" Nuclei = all P { ssfc }") + tmp.append(" SpinSpinRThresh 8.0") + tmp.append("end") + orcainput["couplings"] = tmp + # shielding + if nmrprop and "nmrS" in self.job["prepinfo"]: + tmp = [] + tmp.append("%eprnmr") + if self.job["hactive"]: + tmp.append(" Nuclei = all H { shift }") + if self.job["cactive"]: + tmp.append(" Nuclei = all C { shift }") + if self.job["factive"]: + tmp.append(" Nuclei = all F { shift }") + if self.job["siactive"]: + tmp.append(" Nuclei = all Si { shift }") + if self.job["pactive"]: + tmp.append(" Nuclei = all P { shift }") + tmp.append(" origin giao") + tmp.append(" giao_2el giao_2el_same_as_scf") + tmp.append(" giao_1el giao_1el_analytic") + tmp.append("end") + orcainput["shieldings"] = tmp + + error_logical = False + if not orcainput["functional"]: + error_logical = True + elif not orcainput["basis"] and self.job["func"] not in composite_dfa: + error_logical = True + elif not orcainput["geom"]: + error_logical = True + if error_logical: + print("unusable input!") + + tmp = [] + for key, value in orcainput.items(): + if value: + tmp.extend(value) + if returndict: + return tmp, orcainput + else: + return tmp + + def _sp(self, silent=False): + """ + ORCA input generation and single-point calculation + """ + if not self.job["onlyread"]: + with open( + os.path.join(self.job["workdir"], "inp"), "w", newline=None + ) as inp: + for line in self._prep_input(): + inp.write(line + "\n") + + # Done writing input! + time.sleep(0.02) + if not silent: + print(f"Running single-point in {last_folders(self.job['workdir'], 2)}") + # start SP calculation + with open( + os.path.join(self.job["workdir"], "sp.out"), "w", newline=None + ) as outputfile: + call = [os.path.join(external_paths["orcapath"], "orca"), "inp"] + subprocess.call( + call, + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + stdout=outputfile, + ) + time.sleep(0.05) + # check if scf is converged: + if os.path.isfile(os.path.join(self.job["workdir"], "sp.out")): + with open( + os.path.join(self.job["workdir"], "sp.out"), + "r", + encoding=CODING, + newline=None, + ) as inp: + stor = inp.readlines() + for line in stor: + if "FINAL SINGLE POINT ENERGY" in line: + self.job["energy"] = float(line.split()[4]) + if "ORCA TERMINATED NORMALLY" in line: + self.job["success"] = True + if not self.job["success"]: + self.job["energy"] = 0.0 + self.job["success"] = False + print( + f"ERROR: scf in {last_folders(self.job['workdir'], 2)} " + "not converged!" + ) + else: + self.job["energy"] = 0.0 + self.job["success"] = False + print( + f"WARNING: {os.path.join(self.job['workdir'], 'sp.out')} " + "doesn't exist!" + ) + return + + def _smd_gsolv(self): + """ + Calculate SMD_gsolv, needs ORCA + if optimization is not performed with ORCA, only the density + functional for optimization is employed, + from my understanding smd is parametrized at 298 K, therefore it should only + be used at this temperature. + energy --> gas phase + energy2 --> smd_gsolv gsolv contribution + """ + energy_gas = None + energy_solv = None + print( + f"Running SMD_gsolv calculation in " + f"{last_folders(self.job['workdir'], 2)}." + ) + # calculate gas phase + keepsolv = self.job["solvent"] + keepsm = self.job["sm"] + self.job["solvent"] = "gas" + self.job["sm"] = "gas-phase" + self._sp(silent=True) + + if self.job["success"] == False: + self.job["success"] = False + self.job["energy"] = 0.0 + self.job["energy2"] = 0.0 + print( + f"ERROR: in gas phase single-point " + f"of {last_folders(self.job['workdir'], 2):18}" + ) + return + else: + energy_gas = self.job["energy"] + self.job["energy"] = 0.0 + # mv inp inp_solv sp.out sp_solv.out + try: + shutil.move( + os.path.join(self.job["workdir"], "inp"), + os.path.join(self.job["workdir"], "inp_gas"), + ) + shutil.move( + os.path.join(self.job["workdir"], "sp.out"), + os.path.join(self.job["workdir"], "sp_gas.out"), + ) + except FileNotFoundError: + pass + # calculate in solution + self.job["solvent"] = keepsolv + self.job["sm"] = keepsm + self._sp(silent=True) + if self.job["success"] == False: + self.job["success"] = False + self.job["energy"] = 0.0 + self.job["energy2"] = 0.0 + print( + f"ERROR: in gas solution phase single-point " + f"of {last_folders(self.job['workdir'], 2):18}" + ) + return + else: + energy_solv = self.job["energy"] + self.job["energy"] = 0.0 + # mv inp inp_solv sp.out sp_solv.out + try: + shutil.move( + os.path.join(self.job["workdir"], "inp"), + os.path.join(self.job["workdir"], "inp_solv"), + ) + shutil.move( + os.path.join(self.job["workdir"], "sp.out"), + os.path.join(self.job["workdir"], "sp_solv.out"), + ) + except FileNotFoundError: + pass + if self.job["success"]: + if energy_solv is None or energy_gas is None: + self.job["energy"] = 0.0 + self.job["energy"] = 0.0 + self.job["success"] = False + print( + f"ERROR: in SMD_Gsolv calculation " + f"{last_folders(self.job['workdir'], 2):18}" + ) + else: + self.job["energy"] = energy_gas + self.job["energy2"] = energy_solv - energy_gas + self.job["success"] = True + return + + def _xtbopt(self): + """ + ORCA input generation and geometry optimization using ANCOPT + implemented within xtb, generates inp.xyz, inp (orca-input) + and adds information to coord (xtb can then tell which file + orca has to use). + + uses: + fullopt --> outputname decision + workdir --> folder of calculation + + return: + cycles --> number of optimization cycles + ecyc --> energy at cycle + energy --> energy at last step + success --> calulation without crash + converged --> geometry optimization converged + """ + error_logical = False + if self.job["fullopt"]: + output = "opt-part2.out" + else: + output = "opt-part1.out" + if not self.job["onlyread"]: + print(f"Running optimization in {last_folders(self.job['workdir'], 2):18}") + files = [ + "xtbrestart", + "xtbtopo.mol", + "xcontrol-inp", + "wbo", + "charges", + "gfnff_topo", + ] + for file in files: + if os.path.isfile(os.path.join(self.job["workdir"], file)): + os.remove(os.path.join(self.job["workdir"], file)) + if not self.job["onlyread"]: + # convert coord to xyz, write inp.xyz + t2x(self.job["workdir"], writexyz=True, outfile="inp.xyz") + # add inputfile information to coord (xtb as a driver) + with open( + os.path.join(self.job["workdir"], "coord"), "r", newline=None + ) as coord: + tmp = coord.readlines() + with open( + os.path.join(self.job["workdir"], "coord"), "w", newline=None + ) as newcoord: + for line in tmp[:-1]: + newcoord.write(line) + newcoord.write("$external\n") + newcoord.write(" orca input file= inp\n") + newcoord.write( + f" orca bin= {os.path.join(self.job['progpath'], 'orca')}" + ) + newcoord.write("$end") + + with open( + os.path.join(self.job["workdir"], "inp"), "w", newline=None + ) as inp: + for line in self._prep_input(xyzfile="inp.xyz"): + inp.write(line + "\n") + # Done writing input! + callargs = [ + self.job["xtb_driver_path"], + "coord", + "--opt", + self.job["optlevel"], + "--orca", + ] + with open( + os.path.join(self.job["workdir"], "opt.inp"), "w", newline=None + ) as out: + out.write("$opt \n") + if ( + self.job["optcycles"] is not None + and float(self.job["optcycles"]) > 0 + ): + out.write(f"maxcycle={str(self.job['optcycles'])} \n") + out.write(f"microcycle={str(self.job['optcycles'])} \n") + out.write("average conv=true \n") + out.write(f"hlow={self.job.get('hlow', 0.01)} \n") + out.write("s6=30.00 \n") + # remove unnecessary sp/gradient call in xTB + out.write("engine=lbfgs\n") + out.write("$end \n") + callargs.append("-I") + callargs.append("opt.inp") + time.sleep(0.02) + with open( + os.path.join(self.job["workdir"], output), "w", newline=None + ) as outputfile: + returncode = subprocess.call( + callargs, + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + stdout=outputfile, + env=ENVIRON, + ) + if returncode != 0: + error_logical = True + print( + "ERROR: optimization in {:18} not converged".format( + last_folders(self.job["workdir"], 2) + ) + ) + time.sleep(0.02) + # check if optimization finished correctly: + if os.path.isfile(os.path.join(self.job["workdir"], output)): + with open( + os.path.join(self.job["workdir"], output), + "r", + encoding=CODING, + newline=None, + ) as inp: + stor = inp.readlines() + for line in stor: + if ( + "external code error" in line + or "|grad| > 500, something is totally wrong!" in line + or "abnormal termination of xtb" in line + ): + print( + "ERROR: optimization in {:18} not converged".format( + last_folders(self.job["workdir"], 2) + ) + ) + error_logical = True + break + elif " FAILED TO CONVERGE GEOMETRY " in line: + self.job["cycles"] += int(line.split()[7]) + self.job["converged"] = False + elif "*** GEOMETRY OPTIMIZATION CONVERGED AFTER " in line: + self.job["cycles"] += int(line.split()[5]) + self.job["converged"] = True + with open( + os.path.join(self.job["workdir"], output), + "r", + encoding=CODING, + newline=None, + ) as inp: + for line in inp: + if "av. E: " in line: + # self.job["ecyc"].append(float(line.split("Eh")[0].split()[-1])) + self.job["ecyc"].append(float(line.split("->")[-1])) + if " :: gradient norm " in line: + self.job["grad_norm"] = float(line.split()[3]) + else: + print( + "WARNING: {} doesn't exist!".format( + os.path.join(self.job["workdir"], output) + ) + ) + error_logical = True + if not error_logical: + try: + self.job["energy"] = self.job["ecyc"][-1] + self.job["success"] = True + except: + error_logical = True + if error_logical: + self.job["energy"] = 0.0 + self.job["success"] = False + self.job["converged"] = False + self.job["ecyc"] = [] + self.job["grad_norm"] = 10.0 + + # convert optimized xyz to coord file + x2t(self.job["workdir"], infile="inp.xyz") + return + + def _nmrS(self): + """ + ORCA NMR shielding constant calculation + """ + with open(os.path.join(self.job["workdir"], "inpS"), "w", newline=None) as inp: + for line in self._prep_input(): + inp.write(line + "\n") + # Done input! + # shielding calculation + print( + "Running shielding calculation in {:18}".format( + last_folders(self.job["workdir"], 2) + ) + ) + with open( + os.path.join(self.job["workdir"], "orcaS.out"), "w", newline=None + ) as outputfile: + call = [os.path.join(self.job["progpath"], "orca"), "inpS"] + subprocess.call( + call, + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + stdout=outputfile, + ) + time.sleep(0.1) + # check if calculation was successfull: + with open( + os.path.join(self.job["workdir"], "orcaS.out"), + "r", + encoding=CODING, + newline=None, + ) as inp: + store = inp.readlines() + self.job["success"] = False + for line in store: + if "ORCA TERMINATED NORMALLY" in line: + self.job["success"] = True + if not self.job["success"]: + print( + "ERROR: shielding calculation in {:18} failed!".format( + last_folders(self.job["workdir"], 1) + ) + ) + return + + def _nmrJ(self): + """ + ORCA NMR coupling constant calculation + + uses: + prepinfo nmrJ + workdir + progpath + success + """ + # generate input # double hybrids not implemented + with open(os.path.join(self.job["workdir"], "inpJ"), "w", newline=None) as inp: + for line in self._prep_input(): + inp.write(line + "\n") + # Done input! + # start coupling calculation + print( + "Running coupling calculation in {}".format( + last_folders(self.job["workdir"], 2) + ) + ) + with open( + os.path.join(self.job["workdir"], "orcaJ.out"), "w", newline=None + ) as outputfile: + call = [os.path.join(self.job["progpath"], "orca"), "inpJ"] + subprocess.call( + call, + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + stdout=outputfile, + ) + time.sleep(0.1) + # check if calculation was successfull: + with open( + os.path.join(self.job["workdir"], "orcaJ.out"), + "r", + encoding=CODING, + newline=None, + ) as inp: + store = inp.readlines() + self.job["success"] = False + for line in store: + if "ORCA TERMINATED NORMALLY" in line: + self.job["success"] = True + if not self.job["success"]: + print( + "ERROR: coupling calculation in {:18} failed!".format( + last_folders(self.job["workdir"], 1) + ) + ) + return + + def _genericoutput(self): + """ + ORCA read shielding and coupling constants and write them to plain output + """ + fnameshield = "orcaS.out" + atom = [] + sigma = [] + try: + with open( + os.path.join(self.job["workdir"], fnameshield), + "r", + encoding=CODING, + newline=None, + ) as inp: + data = inp.readlines() + for line in data: + if "CHEMICAL SHIELDING SUMMARY (ppm)" in line: + start = data.index(line) + for line in data[(start + 6) :]: + splitted = line.split() + if len(splitted) == 4: + atom.append(int(splitted[0]) + 1) + sigma.append(float(splitted[2])) + else: + break + except FileNotFoundError: + print( + "Missing file: {} in {}".format( + fnameshield, last_folders(self.job["workdir"], 2) + ) + ) + self.job["success"] = False + self.job["success"] = True + fnamecoupl = "orcaJ.out" + atom1 = [] + atom2 = [] + jab = [] + try: + with open( + os.path.join(self.job["workdir"], fnamecoupl), + "r", + encoding=CODING, + newline=None, + ) as inp: + data = inp.readlines() + for line in data: + if "NMR SPIN-SPIN COUPLING CONSTANTS" in line: + start = int(data.index(line)) + 6 + if " ****ORCA TERMINATED NORMALLY****" in line: + end = int(data.index(line)) + + for line in data[start:end]: + if "NUCLEUS" in line: + tmpsplitted = line.split() + atom1.append(int(tmpsplitted[4]) + 1) + atom2.append(int(tmpsplitted[9]) + 1) + elif "Total" in line and "iso= " in line: + splitted = line.split() + jab.append(float(splitted[5])) + else: + pass + except FileNotFoundError: + print( + "Missing file: {} in {}".format( + fnamecoupl, last_folders(self.job["workdir"], 2) + ) + ) + self.job["success"] = False + self.job["success"] = True + with open( + os.path.join(self.job["workdir"], "nmrprop.dat"), "w", newline=None + ) as out: + s = sorted(zip(atom, sigma)) + atom, sigma = map(list, zip(*s)) + for i in range(len(atom)): + out.write("{:{digits}} {}\n".format(atom[i], sigma[i], digits=4)) + for i in range(self.job["nat"] - len(atom)): + out.write("\n") + for i in range(len(atom1)): + out.write( + "{:{digits}} {:{digits}} {}\n".format( + atom1[i], atom2[i], jab[i], digits=4 + ) + ) + time.sleep(0.02) + return + + def execute(self): + """ + Choose what to execute for the jobtype + use: + prep --> ignore + sp --> _sp + cosmors --> not with orca + opt --> pure opt with ORCA + xtbopt --> opt with xtb as driver + rrhoxtb --> _rrho() + """ + if self.job["jobtype"] == "prep": + self.job["success"] = True + pass + elif self.job["jobtype"] == "xtb_sp": + self._xtb_sp() + elif self.job["jobtype"] in ("sp", "sp_implicit"): + self._sp() + elif self.job["jobtype"] == "opt": + print("RUNNING xtbopt!!!") + # self._opt() + self._xtbopt() + elif self.job["jobtype"] == "xtbopt": + self._xtbopt() + elif self.job["jobtype"] == "rrhoxtb": + self._xtbrrho() + # elif self.job['jobtype'] == "rrhoorca": + # self._rrho() + elif self.job["jobtype"] == "smd_gsolv": + self._smd_gsolv() + elif self.job["jobtype"] == "nmrJ": + self._nmrJ() + elif self.job["jobtype"] == "nmrS": + self._nmrS() + elif self.job["jobtype"] == "genericout": + self._genericoutput() + elif self.job["jobtype"] in ("gbsa_gsolv", "alpb_gsolv"): + if self.job["prepinfo"]: + tmp_solvent = self.job["solvent"] + self.job["solvent"] = "gas" + self._sp() + if not self.job["success"]: + return + self.job["solvent"] = tmp_solvent + self._xtb_gsolv() + else: + print(f"JOBTYPE {self.job['jobtype']} UNKNOWN!") diff --git a/censo_qm/parallel.py b/censo_qm/parallel.py new file mode 100644 index 0000000..6a74bbe --- /dev/null +++ b/censo_qm/parallel.py @@ -0,0 +1,131 @@ +""" +Performs the parallel execution of the QM calls. +""" +import time +import os +import traceback +from multiprocessing import Process +from .qm_job import QmJob +from .tm_job import TmJob +from .orca_job import OrcaJob +from .utilities import print + + +def execute_data(q, resultq): + """ + code that the worker has to execute + """ + while True: + if q.empty(): + break + task = q.get() + try: + task.execute() + except Exception as e: + print(e) + task.hugeERROR = e + task.tb = traceback.format_exc() + resultq.put(task) + q.task_done() + if q.empty(): + break + else: + task = q.get() + resultq.put(task) + q.task_done() + resultq.put(task) + time.sleep(0.02) + q.task_done() + time.sleep(0.02) + + +def run_in_parallel( + config, q, resultq, job, maxthreads, loopover, instructdict, foldername="" +): + """Run jobs in parallel + q = queue to put assemble tasks + resultq = queue to retrieve results + job = information which kind of job is to be performed tm_job , orca_job + loopover is list of qm_class objects + instrucdict example : {'jobtype': 'prep', 'chrg': args.chrg} + foldername is for existing objects to change the workdir + results = list of qm_class objects with results from calculations + """ + if instructdict.get("jobtype", None) is None: + raise KeyError("jobtype is missing in instructdict!") + if all(isinstance(x, QmJob) for x in loopover): + for item in loopover: + if isinstance(item, TmJob) and job == OrcaJob: + item.__class__ = job + elif isinstance(item, OrcaJob) and job == TmJob: + item.__class__ = job + elif isinstance(item, QmJob) and job != QmJob: + item.__class__ = job + item.job["workdir"] = os.path.normpath( + os.path.join(config.cwd, "CONF" + str(item.id), foldername) + ) + # update instructions + item.job.update(instructdict) + # put item on queue + q.put(item) + time.sleep(0.02) + time.sleep(0.02) + njobs = q.qsize() + if instructdict.get("onlyread", False): + print(f"\nReading data from {njobs} conformers calculated in " "previous run.") + else: + response = { + "prep": f"\nPreparing {q.qsize()} calculations.", + "sp": f"\nStarting {q.qsize()} single-point calculations.", + "xtb_sp": f"\nStarting {q.qsize()} xTB - single-point calculations.", + "lax_sp": f"\nStarting {q.qsize()} lax-single-point calculations.", + "cosmors": f"\nStarting {q.qsize()} COSMO-RS-Gsolv calculations.", + "gbsa_gsolv": f"\nStarting {q.qsize()} GBSA-Gsolv calculations", + "alpb_gsolv": f"\nStarting {q.qsize()} ALPB-Gsolv calculations", + "smd_gsolv": f"\nStarting {q.qsize()} SMD-Gsolv calculations", + "rrhoxtb": f"\nStarting {q.qsize()} G_RRHO calculations.", + "rrhoorca": f"\nStarting {q.qsize()} G_RRHO calculations.", + "rrhotm": f"\nStarting {q.qsize()} G_RRHO calculations.", + "opt": f"\nStarting {q.qsize()} optimizations.", + "xtbopt": f"\nStarting {q.qsize()} optimizations.", + "couplings": f"\nStarting {q.qsize()} coupling constants calculations", + "couplings_sp": f"\nStarting {q.qsize()} coupling constants calculations", + "shieldings": f"\nStarting {q.qsize()} shielding constants calculations", + "shieldings_sp": f"\nStarting {q.qsize()} shielding constants calculations", + "genericoutput": f"\nWriting {q.qsize()} generic outputs.", + "opt-rot": f"\nStarting {q.qsize()} optical-rotation calculations.", + "opt-rot_sp": f"\nStarting {q.qsize()} optical-rotation calculations.", + } + if instructdict["jobtype"] in response: + print(response[instructdict["jobtype"]]) + + # start working in parallel + for _ in range(int(maxthreads)): + worker = Process(target=execute_data, args=(q, resultq)) + worker.daemon = True + worker.start() + # NOBODY IS ALLOWED TO TOUCH THIS SLEEP STATEMENT!!!! + time.sleep(0.05) # sleep is important don't remove it!!! + # seriously don't remove it! + q.join() + + if not instructdict.get("onlyread", False): + print("Tasks completed!\n") + else: + print("Reading data from previous run completed!\n") + + # Get results + results = [] + while not resultq.empty(): + results.append(resultq.get()) + if getattr(results[-1], "hugeERROR", False): + print(getattr(results[-1], "tb")) + raise getattr(results[-1], "hugeERROR") + time.sleep(0.01) # sleep is important don't remove it!!! + # seriously don't remove it! + + time.sleep(0.02) + results.sort(key=lambda x: int(x.id)) + if njobs != len(results): + print(f"ERROR some conformers were lost!") + return results diff --git a/censo_qm/prescreening.py b/censo_qm/prescreening.py new file mode 100755 index 0000000..dfaa84a --- /dev/null +++ b/censo_qm/prescreening.py @@ -0,0 +1,1076 @@ +""" +prescreening == part1, calculate free energy on GFNn-xTB input geometry +idea is to improve on E and (Gsolv) +""" +import os +import sys +import math +from multiprocessing import JoinableQueue as Queue +from .cfg import PLENGTH, DIGILEN, AU2KCAL, CODING, censo_solvent_db +from .parallel import run_in_parallel +from .orca_job import OrcaJob +from .tm_job import TmJob +from .utilities import ( + check_for_folder, + print_block, + new_folders, + last_folders, + ensemble2coord, + printout, + move_recursively, + write_trj, + check_tasks, + calc_std_dev, + spearman, + print, + calc_boltzmannweights, +) + + +def part1(config, conformers, store_confs, ensembledata): + """ + Prescreening of the ensemble, with single-points on combined ensemble + geometries. + Calculate low level free energies with COSMO-RS single-point and gsolv + contribution and GFNFF-bhess thermostatistical contribution. + Input: + - config [conifg_setup object] contains all settings + - conformers [list of molecule_data objects] each conformer is represented + - ensembledata -> instance for saving ensemble (not conf) related data + Return: + -> config + -> conformers + -> store_confs + """ + save_errors = [] + print("\n" + "".ljust(PLENGTH, "-")) + print("CRE PRESCREENING - PART1".center(PLENGTH, " ")) + print("".ljust(PLENGTH, "-") + "\n") + # print flags for part1 + info = [] + info.append(["prog", "program"]) + info.append(["func", "functional for part1 and 2"]) + info.append(["basis", "basis set for part1 and 2"]) + if config.solvent != "gas": + info.append(["solvent", "Solvent"]) + info.append(["smgsolv1", "solvent model for Gsolv contribution"]) + info.append(["part1_threshold", "threshold"]) + info.append( + ["printoption", "starting number of considered conformers", len(conformers)] + ) + info.append(["evaluate_rrho", "calculate mRRHO contribution"]) + if config.evaluate_rrho: + info.append(["prog_rrho", "program for mRRHO contribution"]) + if config.prog_rrho == "xtb": + info.append(["part1_gfnv", "GFN version for mRRHO and/or GBSA_Gsolv"]) + if config.bhess: + info.append( + [ + "bhess", + "Apply constraint to input geometry during mRRHO calculation", + ] + ) + info.append(["temperature", "temperature"]) + optionsexchange = {True: "on", False: "off"} + for item in info: + if item[0] == "justprint": + print(item[1:][0]) + else: + if item[0] == "printoption": + option = item[2] + else: + option = getattr(config, item[0]) + if option is True or option is False: + option = optionsexchange[option] + elif isinstance(option, list): + option = ", ".join(option) + print( + "{}: {:{digits}} {}".format( + item[1], "", option, digits=DIGILEN - len(item[1]) + ) + ) + print("") + # end print + + calculate = [] # has to be calculated in this run + prev_calculated = [] # was already calculated in a previous run + try: + store_confs + except NameError: + store_confs = [] # stores all confs which are sorted out! + + if config.solvent == "gas": + print("Calculating single-point energies:") + else: + print("Calculating single-point energies and solvation contribution (G_solv):") + + # setup queues + q = Queue() + resultq = Queue() + + if config.prog == "tm": + job = TmJob + elif config.prog == "orca": + job = OrcaJob + + for conf in list(conformers): + if conf.removed: + store_confs.append(conformers.pop(conformers.index(conf))) + print(f"CONF{conf.id} is removed as requested by the user.") + continue + if conf.id > config.nconf: + store_confs.append(conformers.pop(conformers.index(conf))) + continue + if conf.prescreening_sp_info["info"] == "not_calculated": + conf = conformers.pop(conformers.index(conf)) + calculate.append(conf) + elif conf.prescreening_sp_info["info"] == "failed": + conf = conformers.pop(conformers.index(conf)) + store_confs.append(conf) + print(f"Calculation of CONF{conf.id} failed in the previous run.") + elif conf.prescreening_sp_info["info"] == "calculated": + conf = conformers.pop(conformers.index(conf)) + if config.solvent != "gas": + # check if solvation calculation calculated as well! + if conf.prescreening_gsolv_info["info"] == "failed": + store_confs.append(conf) + print( + f"Calculation of the solvation contribution for CONF" + f"{conf.id} failed in the previous run." + ) + elif ( + conf.prescreening_gsolv_info["info"] == "not_calculated" + and config.smgsolv1 in config.smgsolv_1 + ): + # additive solvation + calculate.append(conf) + else: + # implicit solvation + conf.job["success"] = True + prev_calculated.append(conf) + elif config.solvent == "gas": + conf.job["success"] = True + prev_calculated.append(conf) + if not calculate and not prev_calculated: + print("ERROR: No conformers left!") + if prev_calculated: + check_for_folder(config.cwd, [i.id for i in prev_calculated], config.func) + print("The prescreening_single-point was calculated before for:") + print_block(["CONF" + str(i.id) for i in prev_calculated]) + pl = config.lenconfx + 4 + len(str("/" + config.func)) + + instruction = { + "prepinfo": ["low+"], # TM: m4 scfconv 6 + "func": config.func, + "basis": getattr( + config, "basis", config.func_basis_default.get(config.func, "def2-mTZVPP") + ), + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": config.solvent, + "sm": config.smgsolv1, + "omp": config.omp, + "temperature": config.temperature, + "gfn_version": config.part1_gfnv, + "energy": 0.0, + "energy2": 0.0, + "success": False, + } + + if config.solvent == "gas": + instruction["jobtype"] = "sp" + instruction["method"], _ = config.get_method_name( + "sp", func=instruction["func"], basis=instruction["basis"] + ) + name = "prescreening_single-point" + folder = instruction["func"] + if config.prog == "orca": + instruction["progpath"] = config.external_paths["orcapath"] + else: + if config.smgsolv1 in config.smgsolv_1: + # additive Gsolv + # COSMORS + if config.smgsolv1 != "dcosmors" and "cosmors" in config.smgsolv1: + job = TmJob + exc_fine = {"cosmors": "normal", "cosmors-fine": "fine"} + tmp = { + "jobtype": "cosmors", + "cosmorssetup": config.external_paths["cosmorssetup"], + "cosmorsparam": exc_fine.get(config.smgsolv1, "normal"), + "cosmothermversion": config.external_paths["cosmothermversion"], + } + instruction.update(tmp) + instruction["method"], instruction["method2"] = config.get_method_name( + "cosmors", + func=instruction["func"], + basis=instruction["basis"], + sm=instruction["sm"], + ) + name = "prescreening COSMO-RS" + folder = str(instruction["func"]) + "/COSMO" + # GBSA-Gsolv / ALPB-Gsolv + elif instruction["sm"] in ("gbsa_gsolv", "alpb_gsolv"): + # do DFT gas phase sp and additive Gsolv + instruction["jobtype"] = instruction["sm"] + if config.prog == "orca": + instruction["progpath"] = config.external_paths["orcapath"] + instruction["xtb_driver_path"] = config.external_paths["xtbpath"] + instruction["method"], instruction["method2"] = config.get_method_name( + instruction["jobtype"], + func=instruction["func"], + basis=instruction["basis"], + sm=instruction["sm"], + gfn_version=instruction["gfn_version"], + ) + if ( + conf.prescreening_sp_info["info"] == "calculated" + and conf.prescreening_sp_info["method"] == instruction["method"] + ): + # do not calculate gas phase sp again! + instruction["energy"] = conf.prescreening_sp_info["energy"] + instruction["prepinfo"] = [] + name = "prescreening " + str(instruction["sm"]).upper() + folder = str(instruction["func"]) + "/Gsolv" + # SMD-Gsolv + elif instruction["sm"] == "smd_gsolv": + job = OrcaJob + instruction["jobtype"] = instruction["sm"] + instruction["progpath"] = config.external_paths["orcapath"] + instruction["method"], instruction["method2"] = config.get_method_name( + "smd_gsolv", + func=instruction["func"], + basis=instruction["basis"], + sm=instruction["sm"], + ) + name = "prescreening" + str(instruction["sm"]).upper() + folder = str(instruction["func"]) + "/Gsolv" + else: + # with implicit solvation + instruction["jobtype"] = "sp_implicit" + # instruction["prepinfo"] = ["low+"] + if config.prog == "orca": + instruction["progpath"] = config.external_paths["orcapath"] + instruction["method"], instruction["method2"] = config.get_method_name( + "sp_implicit", + func=instruction["func"], + basis=instruction["basis"], + sm=instruction["sm"], + ) + name = "prescreening_single-point" + folder = instruction["func"] + + check = {True: "was successful", False: "FAILED"} + if calculate: + print(f"The {name} is calculated for:") + print_block(["CONF" + str(i.id) for i in calculate]) + # create folders: + save_errors, store_confs, calculate = new_folders( + config.cwd, calculate, config.func, save_errors, store_confs + ) + # write coord to folder + calculate, store_confs, save_errors = ensemble2coord( + config, config.func, calculate, store_confs, save_errors + ) + if config.solvent != "gas": + if folder != str(config.func): + # create the COSMO folder + save_errors, store_confs, calculate = new_folders( + config.cwd, calculate, folder, save_errors, store_confs + ) + # write the coord file to the COSMO folder + calculate, store_confs, save_errors = ensemble2coord( + config, folder, calculate, store_confs, save_errors + ) + + # parallel calculation: + calculate = run_in_parallel( + config, q, resultq, job, config.maxthreads, calculate, instruction, folder + ) + + for conf in list(calculate): + if instruction["jobtype"] == "sp": + line = ( + f"{name} calculation {check[conf.job['success']]}" + f" for {last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.job['energy']:>.8f}" + ) + print(line) + if not conf.job["success"]: + save_errors.append(line) + conf.prescreening_sp_info["info"] = "failed" + conf.prescreening_sp_info["method"] = conf.job["method"] + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.prescreening_sp_info["energy"] = conf.job["energy"] + conf.prescreening_sp_info["info"] = "calculated" + conf.prescreening_sp_info["method"] = conf.job["method"] + elif instruction["jobtype"] == "sp_implicit": + line = ( + f"{name} calculation {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.job['energy']:>.8f}" + ) + print(line) + if not conf.job["success"]: + save_errors.append(line) + conf.prescreening_sp_info["info"] = "failed" + conf.prescreening_sp_info["method"] = conf.job["method"] + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.prescreening_sp_info["energy"] = conf.job["energy"] + conf.prescreening_sp_info["info"] = "calculated" + conf.prescreening_sp_info["method"] = conf.job["method"] + elif instruction["jobtype"] in ( + "cosmors", + "smd_gsolv", + "gbsa_gsolv", + "alpb_gsolv", + ): + line = ( + f"{name} calculation {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 3):>{pl}}: " + f"{conf.job['energy2']:>.8f}" + ) + print(line) + if not conf.job["success"]: + save_errors.append(line) + conf.prescreening_sp_info["info"] = "failed" + conf.prescreening_sp_info["method"] = conf.job["method"] + conf.prescreening_gsolv_info["info"] = "failed" + conf.prescreening_gsolv_info["method"] = conf.job["method2"] + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.prescreening_sp_info["energy"] = conf.job["energy"] + conf.prescreening_sp_info["info"] = "calculated" + conf.prescreening_sp_info["method"] = conf.job["method"] + conf.prescreening_gsolv_info["gas-energy"] = conf.job["energy"] + conf.prescreening_gsolv_info["energy"] = conf.job["energy2"] + conf.prescreening_gsolv_info["info"] = "calculated" + conf.prescreening_gsolv_info["method"] = conf.job["method2"] + else: + print( + f'UNEXPECTED BEHAVIOUR: {conf.job["success"]} {conf.job["jobtype"]}' + ) + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + check_tasks(calculate, config.check) + else: + print("No conformers are considered additionally.") + if prev_calculated: + # adding conformers calculated before: + for conf in list(prev_calculated): + conf.job["workdir"] = os.path.normpath( + os.path.join(config.cwd, "CONF" + str(conf.id), config.func) + ) + if instruction["jobtype"] in ("sp", "sp_implicit"): + print( + f"{name} calculation {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.prescreening_sp_info['energy']:>.8f}" + ) + elif instruction["jobtype"] in ( + "cosmors", + "smd_gsolv", + "gbsa_gsolv", + "alpb_gsolv", + ): + print( + f"{name} calculation {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 3):>{pl}}: " + f"{conf.prescreening_gsolv_info['energy']:>.8f}" + ) + calculate.append(prev_calculated.pop(prev_calculated.index(conf))) + for conf in calculate: + conf.reset_job_info() + if not calculate: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + # *************************************************************************** + # first sorting by E or Gsolv + # (remove high lying conformers above part1_threshold + 1.5 kcal/mol) + print("\n" + "".ljust(int(PLENGTH / 2), "-")) + print("Removing high lying conformers".center(int(PLENGTH / 2), " ")) + print("".ljust(int(PLENGTH / 2), "-") + "\n") + + for conf in calculate: + rrho = None + if config.solvent == "gas": + solv = None + else: + solv = "prescreening_gsolv_info" + e = "prescreening_sp_info" + conf.calc_free_energy(e=e, solv=solv, rrho=rrho) + try: + minfree = min([i.free_energy for i in calculate if i is not None]) + except ValueError: + raise + for conf in calculate: + conf.rel_free_energy = (conf.free_energy - minfree) * AU2KCAL + try: + maxreldft = max([i.rel_free_energy for i in calculate if i is not None]) + except ValueError: + print("ERROR: No conformer left or Error in maxreldft!") + # print sorting + columncall = [ + lambda conf: "CONF" + str(getattr(conf, "id")), + lambda conf: getattr(conf, "xtb_energy"), + lambda conf: getattr(conf, "rel_xtb_energy"), + lambda conf: getattr(conf, "prescreening_sp_info")["energy"], + lambda conf: getattr(conf, "prescreening_gsolv_info")["energy"], + lambda conf: getattr(conf, "free_energy"), + lambda conf: getattr(conf, "rel_free_energy"), + ] + columnheader = [ + "CONF#", + "E(GFNn-xTB)", + "ΔE(GFNn-xTB)", + "E [Eh]", + "Gsolv [Eh]", + "Gtot", + "ΔGtot", + ] + columndescription = ["", "[a.u.]", "[kcal/mol]", "", "", "[Eh]", "[kcal/mol]"] + columndescription2 = ["", "", "", "", "", "", "", ""] + columnformat = ["", (12, 7), (5, 2), (12, 7), (12, 7), (12, 7), (5, 2)] + + if config.solvent == "gas": + columnheader[5] = "Etot" + columnheader[6] = "ΔEtot" + columndescription[3] = instruction["method"] + # ignore gsolv in printout + columncall.pop(4) + columnheader.pop(4) + columndescription.pop(4) + columnformat.pop(4) + elif config.solvent != "gas": + # energy + columndescription[3] = instruction["method"] + # gsolv + columndescription[4] = instruction["method2"] + + calculate.sort(key=lambda x: int(x.id)) + printout( + os.path.join(config.cwd, "part1preG.dat"), + columncall, + columnheader, + columndescription, + columnformat, + calculate, + minfree, + columndescription2=columndescription2, + ) + + if maxreldft > (config.part1_threshold): + print("\n" + "".ljust(int(PLENGTH / 2), "-")) + print("Conformers considered further".center(int(PLENGTH / 2), " ")) + print("".ljust(int(PLENGTH / 2), "-") + "\n") + for conf in list(calculate): + if conf.rel_free_energy > (config.part1_threshold): + store_confs.append(calculate.pop(calculate.index(conf))) + if calculate: + print(f"Below the threshold of {config.part1_threshold} kcal/mol.\n") + print_block(["CONF" + str(i.id) for i in calculate]) + else: + print("Error: There are no more conformers left!") + else: + print( + "\nAll relative (free) energies are below the threshold " + f"of ({config.part1_threshold} kcal/mol.\nAll conformers are " + "considered further." + ) + ensembledata.nconfs_per_part["part1_firstsort"] = len(calculate) + # reset + for conf in calculate: + conf.free_energy = 0.0 + conf.rel_free_energy = None + print("".ljust(int(PLENGTH / 2), "-")) + # *************************************************************************** + if config.evaluate_rrho: + # check if prescreening rrho has been calculated + if config.solvent == "gas": + print("\nCalculating prescreening G_mRRHO!") + else: + print("\nCalculating prescreening G_mRRHO with implicit solvation!") + + for conf in list(calculate): + if conf.prescreening_grrho_info["info"] == "not_calculated": + pass + elif conf.prescreening_grrho_info["info"] == "failed": + conf = calculate.pop(calculate.index(conf)) + conf.__class__ = job + store_confs.append(conf) + print(f"Calculation of CONF{conf.id} failed in the previous run!") + elif conf.prescreening_grrho_info["info"] == "calculated": + conf = calculate.pop(calculate.index(conf)) + conf.__class__ = job + conf.job["success"] = True + prev_calculated.append(conf) + + if not calculate and not prev_calculated: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + folderrho = "rrho_part1" + if prev_calculated: + check_for_folder(config.cwd, [i.id for i in prev_calculated], folderrho) + print("The prescreening G_mRRHO calculation was performed before for:") + print_block(["CONF" + str(i.id) for i in prev_calculated]) + pl = config.lenconfx + 4 + len(str("/" + folderrho)) + instruction_prerrho = { + "jobtype": "rrhoxtb", + "func": getattr(config, "part1_gfnv"), + "gfn_version": getattr(config, "part1_gfnv"), + "temperature": config.temperature, + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": config.solvent, + "omp": config.omp, + "progpath": config.external_paths["xtbpath"], + "bhess": config.bhess, + "consider_sym": config.consider_sym, + "sm_rrho": config.sm_rrho, + "rmsdbias": config.rmsdbias, + "cwd": config.cwd, + "copymos": "", + "sym": "c1", + "multiTemp": False, + "energy": 0.0, + "energy2": 0.0, + "success": False, + } + + instruction_prerrho["method"], _ = config.get_method_name( + "rrhoxtb", + bhess=config.bhess, + gfn_version=instruction_prerrho["gfn_version"], + sm=instruction_prerrho["sm_rrho"], + solvent=instruction_prerrho["solvent"], + ) + if calculate: + print("The prescreening G_mRRHO calculation is now performed for:") + print_block(["CONF" + str(i.id) for i in calculate]) + # create folders: + save_errors, store_confs, calculate = new_folders( + config.cwd, calculate, folderrho, save_errors, store_confs + ) + # write coord to folder + calculate, store_confs, save_errors = ensemble2coord( + config, folderrho, calculate, store_confs, save_errors + ) + calculate = run_in_parallel( + config, + q, + resultq, + job, + config.maxthreads, + calculate, + instruction_prerrho, + folderrho, + ) + check = {True: "was successful", False: "FAILED"} + # check if too many calculations failed + + ### + for conf in list(calculate): + print( + f"The prescreening G_mRRHO calculation @ {conf.job['symmetry']} " + f"{check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.job['energy']:>.8f}" + ) + if not conf.job["success"]: + conf.prescreening_grrho_info["info"] = "failed" + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.sym = conf.job["symmetry"] + conf.prescreening_grrho_info["rmsd"] = conf.job["rmsd"] + conf.prescreening_grrho_info["energy"] = conf.job["energy"] + conf.prescreening_grrho_info["info"] = "calculated" + conf.prescreening_grrho_info["method"] = instruction_prerrho[ + "method" + ] + + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + check_tasks(calculate, config.check) + else: + print("No conformers are considered additionally.") + # adding conformers calculated before: + if prev_calculated: + for conf in list(prev_calculated): + conf.job["workdir"] = os.path.normpath( + os.path.join(config.cwd, "CONF" + str(conf.id), folderrho) + ) + print( + f"The prescreening G_mRRHO calculation @ {conf.sym} " + f"{check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.prescreening_grrho_info['energy']:>.8f}" + ) + calculate.append(prev_calculated.pop(prev_calculated.index(conf))) + if not calculate: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + + # # printout for part1 ------------------------------------------------------- + print("\n" + "".ljust(int(PLENGTH / 2), "-")) + print("* Gibbs free energies of part1 *".center(int(PLENGTH / 2), " ")) + print("".ljust(int(PLENGTH / 2), "-") + "\n") + columncall = [ + lambda conf: "CONF" + str(getattr(conf, "id")), + lambda conf: getattr(conf, "xtb_free_energy"), + lambda conf: getattr(conf, "rel_xtb_free_energy"), + lambda conf: getattr(conf, "prescreening_sp_info")["energy"], + lambda conf: getattr(conf, "prescreening_gsolv_info")["energy"], + lambda conf: getattr(conf, "prescreening_grrho_info")["energy"], + lambda conf: getattr(conf, "free_energy"), + lambda conf: getattr(conf, "rel_free_energy"), + ] + columnheader = [ + "CONF#", + "G(GFNn-xTB)", + "ΔG(GFNn-xTB)", + "E [Eh]", + "Gsolv [Eh]", + "GmRRHO [Eh]", + "Gtot", + "ΔGtot", + ] + columndescription = [ + "", # CONFX + "[a.u.]", # xtb energy + "[kcal/mol]", # rel xtb_energy + str(config.func), # E + "", # GSolv + "", + "[Eh]", # Gtot + "[kcal/mol]", # rel Gtot + ] + columndescription2 = ["", "", "", "", "", "", "", ""] + columnformat = ["", (12, 7), (5, 2), (12, 7), (12, 7), (12, 7), (12, 7), (5, 2)] + if config.solvent == "gas": + # Energy + columndescription[3] = instruction["method"] + elif config.solvent != "gas": + # Energy + columndescription[3] = instruction["method"] + # Gsolv + columndescription[4] = instruction["method2"] + if config.evaluate_rrho: + columndescription[5] = instruction_prerrho["method"] # Grrho + + if not config.evaluate_rrho or config.solvent == "gas": + if not config.evaluate_rrho: + # ignore rrho in printout + columncall.pop(5) + columnheader.pop(5) + columndescription.pop(5) + columnformat.pop(5) + if config.solvent == "gas": + columncall.pop(4) + columnheader.pop(4) + columndescription.pop(4) + columnformat.pop(4) + + for conf in calculate: + if not config.evaluate_rrho: + rrho = None + else: + rrho = "prescreening_grrho_info" + if config.solvent == "gas": + solv = None + else: + solv = "prescreening_gsolv_info" + conf.calc_free_energy(e="prescreening_sp_info", solv=solv, rrho=rrho) + conf.xtb_free_energy = conf.calc_free_energy( + e="xtb_energy", solv=None, rrho=rrho, out=True + ) + + try: + minfree = min([i.free_energy for i in calculate if i is not None]) + minfree_xtb = min([i.xtb_free_energy for i in calculate if i is not None]) + except ValueError: + raise ValueError + for conf in calculate: + conf.rel_free_energy = (conf.free_energy - minfree) * AU2KCAL + conf.rel_xtb_free_energy = (conf.xtb_free_energy - minfree_xtb) * AU2KCAL + try: + maxreldft = max([i.rel_free_energy for i in calculate if i is not None]) + except ValueError: + print("ERROR: No conformer left or Error in maxreldft!") + # print sorting + calculate.sort(key=lambda x: int(x.id)) + printout( + os.path.join(config.cwd, "part1.dat"), + columncall, + columnheader, + columndescription, + columnformat, + calculate, + minfree, + columndescription2=columndescription2, + ) + # -------------------------------------------------------------------------- + for conf in calculate: + if conf.free_energy == minfree: + ensembledata.bestconf["part1"] = conf.id + + # write to enso.json + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + + # *************************************************************************** + # Fuzzy or smart sorting + # increase the individual threshold for conformers with GRRHO differing from + # the mean GmRRHO + if len(calculate) == 1: + std_dev = 0.0 + else: + std_dev = calc_std_dev( + [ + conf.prescreening_grrho_info["energy"] * AU2KCAL + for conf in calculate + if conf.prescreening_grrho_info["energy"] is not None + ] + ) + max_fuzzy = 1 + fuzzythr = max_fuzzy * (1 - math.exp(-1 * 5 * (std_dev ** 2))) + print( + "\nAdditional global 'fuzzy-threshold' based on the standard deviation of (G_mRRHO):" + ) + print(f"Std_dev(G_mRRHO) = {std_dev:.3f} kcal/mol") + print(f"Fuzzythreshold = {fuzzythr:.3f} kcal/mol") + print( + f"Final sorting threshold = {config.part1_threshold:.3f} + " + f"{fuzzythr:.3f} = {config.part1_threshold + fuzzythr:.3f} kcal/mol" + ) + for conf in calculate: + conf.prescreening_grrho_info["fuzzythr"] = fuzzythr + + # spearman between DFT and DFT + RRHO + if config.evaluate_rrho and len(calculate) > 1: + for conf in calculate: + rrho = None + if config.solvent == "gas": + solv = None + else: + solv = "prescreening_gsolv_info" + e = "prescreening_sp_info" + conf.calc_free_energy(e=e, solv=solv, rrho=rrho) + try: + minfree = min([i.free_energy for i in calculate if i is not None]) + except ValueError: + raise ValueError + without_RRHO = [] + calculate.sort(key=lambda x: int(x.id)) + for conf in calculate: + without_RRHO.append((conf.free_energy - minfree) * AU2KCAL) + for conf in calculate: + conf.free_energy = 0.0 + for conf in calculate: + rrho = "prescreening_grrho_info" + if config.solvent == "gas": + solv = None + else: + solv = "prescreening_gsolv_info" + e = "prescreening_sp_info" + conf.calc_free_energy(e=e, solv=solv, rrho=rrho) + try: + minfree = min([i.free_energy for i in calculate if i is not None]) + except ValueError: + raise ValueError + with_RRHO = [] + calculate.sort(key=lambda x: int(x.id)) + for conf in calculate: + with_RRHO.append((conf.free_energy - minfree) * AU2KCAL) + for conf in calculate: + conf.free_energy = 0.0 + if config.solvent != "gas": + print( + f"Spearman correlation coefficient between (E + Solv) " + f"and (E + Solv + mRRHO) = {spearman(without_RRHO, with_RRHO):.3f}" + ) + else: + print( + f"Spearman correlation coefficient between (E) " + f"and (E + mRRHO) = {spearman(without_RRHO, with_RRHO):.3f}" + ) + + # sorting + if maxreldft > config.part1_threshold: + print("\n" + "".ljust(int(PLENGTH / 2), "-")) + print("Conformers considered further".center(int(PLENGTH / 2), " ")) + print("".ljust(int(PLENGTH / 2), "-") + "\n") + for conf in list(calculate): + if conf.rel_free_energy <= config.part1_threshold: + conf.part_info["part1"] = "passed" + elif conf.rel_free_energy <= ( + config.part1_threshold + conf.prescreening_grrho_info["fuzzythr"] + ): + print(f"Considered CONF{conf.id} because of increased fuzzythr.") + conf.part_info["part1"] = "passed" + continue + else: + conf.part_info["part1"] = "refused" + store_confs.append(calculate.pop(calculate.index(conf))) + if calculate: + print( + f"These conformers are below the {config.part1_threshold+fuzzythr:.3f} " + f"kcal/mol threshold.\n" + ) + print_block(["CONF" + str(i.id) for i in calculate]) + else: + print("Error: There are no more conformers left!") + else: + for conf in list(calculate): + conf.part_info["part1"] = "passed" + print( + "\nAll relative (free) energies are below the initial threshold " + f"of {config.part1_threshold} kcal/mol.\nAll conformers are " + "considered further." + ) + ensembledata.nconfs_per_part["part1"] = len(calculate) + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + + # free energy: + for conf in calculate: + if not config.evaluate_rrho: + rrho = None + else: + rrho = "prescreening_grrho_info" + if config.solvent == "gas": + solv = None + else: + solv = "prescreening_gsolv_info" + conf.calc_free_energy(e="prescreening_sp_info", solv=solv, rrho=rrho) + + # write coord.enso_best + for conf in calculate: + if conf.id == ensembledata.bestconf["part1"]: + # copy the lowest optimized conformer to file coord.enso_best + with open( + os.path.join("CONF" + str(conf.id), config.func, "coord"), + "r", + encoding=CODING, + newline=None, + ) as f: + coord = f.readlines() + with open( + os.path.join(config.cwd, "coord.enso_best"), "w", newline=None + ) as best: + best.write( + "$coord # {} {} !CONF{} \n".format( + conf.free_energy, + conf.prescreening_grrho_info["energy"], + conf.id, + ) + ) + for line in coord[1:]: + if "$" in line: # stop at $end ... + break + best.write(line) + best.write("$end \n") + + ################################################################################ + # calculate average G correction + print( + "\nCalculating Boltzmann averaged free energy of ensemble on " + f"input geometries (not DFT optimized)!\n" + ) + # calculate Boltzmannweights + if not config.evaluate_rrho or config.solvent == "gas": + if not config.evaluate_rrho and config.solvent == "gas": + line = ( + f"{'temperature /K:':<15} {'avE(T) /a.u.':>14} " + f"{'avG(T) /a.u.':>14} " + ) + elif not config.evaluate_rrho: + line = ( + f"{'temperature /K:':<15} {'avE(T) /a.u.':>14} " + f"{'avGsolv(T) /a.u.':>16} {'avG(T) /a.u.':>14} " + ) + elif config.solvent == "gas": + line = ( + f"{'temperature /K:':<15} {'avE(T) /a.u.':>14} " + f"{'avGmRRHO(T) /a.u.':>16} {'avG(T) /a.u.':>14} " + ) + else: + line = ( + f"{'temperature /K:':<15} {'avE(T) /a.u.':>14} " + f"{'avGmRRHO(T) /a.u.':>16} {'avGsolv(T) /a.u.':>16} " + f"{'avG(T) /a.u.':>14}" + ) + print(line) + print("".ljust(int(PLENGTH), "-")) + # get free energy at (T) + for conf in calculate: + if not config.evaluate_rrho: + rrho = None + else: + rrho = "prescreening_grrho_info" + if config.solvent == "gas": + solv = None + else: + solv = "prescreening_gsolv_info" + e = "prescreening_sp_info" + conf.calc_free_energy(e=e, solv=solv, rrho=rrho) + + calculate = calc_boltzmannweights(calculate, "free_energy", config.temperature) + avG = 0.0 + avE = 0.0 + avGRRHO = 0.0 + avGsolv = 0.0 + for conf in calculate: + avG += conf.bm_weight * conf.free_energy + avE += conf.bm_weight * conf.prescreening_sp_info["energy"] + avGRRHO += conf.bm_weight * conf.prescreening_grrho_info["energy"] + avGsolv += conf.bm_weight * conf.prescreening_gsolv_info["energy"] + + # printout: + if not config.evaluate_rrho or config.solvent == "gas": + if not config.evaluate_rrho and config.solvent == "gas": + line = f"{config.temperature:^15} {avE:>14.7f} {avG:>14.7f} " + elif not config.evaluate_rrho: + line = ( + f"{config.temperature:^15} {avE:>14.7f} {avGsolv:>16.7f} " + f"{avG:>14.7f} " + ) + elif config.solvent == "gas": + line = ( + f"{config.temperature:^15} {avE:>14.7f} {avGRRHO:>16.7f} " + f"{avG:>14.7f} " + ) + else: + line = ( + f"{config.temperature:^15} {avE:>14.7f} {avGRRHO:>16.7f} " + f"{avGsolv:>16.7f} {avG:>14.7f} " + ) + print(line, " <<==part1==") + print("".ljust(int(PLENGTH), "-")) + print("") + + ################################################################################ + + print("\nCalculating unbiased GFNn-xTB energy") + instruction_gfn = { + "jobtype": "xtb_sp", + "func": getattr(config, "part1_gfnv"), + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": config.solvent, + "progpath": config.external_paths["xtbpath"], + "sm": config.sm_rrho, + "rmsdbias": config.rmsdbias, + "omp": config.omp, + "temperature": config.temperature, + "gfn_version": config.part1_gfnv, + "energy": 0.0, + "energy2": 0.0, + "success": False, + } + if calculate: + folder_gfn = "GFN_unbiased" + save_errors, store_confs, calculate = new_folders( + config.cwd, calculate, folder_gfn, save_errors, store_confs + ) + # write coord to folder + calculate, store_confs, save_errors = ensemble2coord( + config, folder_gfn, calculate, store_confs, save_errors + ) + calculate = run_in_parallel( + config, + q, + resultq, + job, + config.maxthreads, + calculate, + instruction_gfn, + folder_gfn, + ) + for conf in list(calculate): + if not conf.job["success"]: + conf.xtb_energy_unbiased = conf.xtb_energy + else: + conf.xtb_energy_unbiased = conf.job["energy"] + # write ensemble + move_recursively(config.cwd, "enso_ensemble_part1.xyz") + if config.evaluate_rrho: + kwargs = {"energy": "xtb_energy_unbiased", "rrho": "prescreening_grrho_info"} + else: + kwargs = {"energy": "xtb_energy_unbiased"} + write_trj( + sorted(calculate, key=lambda x: float(x.free_energy)), + config.cwd, + "enso_ensemble_part1.xyz", + config.func, + config.nat, + "free_energy", + **kwargs, + ) + + # reset + for conf in calculate: + conf.free_energy = 0.0 + conf.rel_free_energy = None + conf.bm_weight = 0.0 + conf.reset_job_info() + if save_errors: + print( + "***---------------------------------------------------------***", + file=sys.stderr, + ) + print( + "Printing most relevant errors again, just for user convenience:", + file=sys.stderr, + ) + for _ in list(save_errors): + print(save_errors.pop(), file=sys.stderr) + print( + "***---------------------------------------------------------***", + file=sys.stderr, + ) + + tmp = int((PLENGTH - len("END of Part1")) / 2) + print("\n" + "".ljust(tmp, ">") + "END of Part1" + "".rjust(tmp, "<")) + return config, calculate, store_confs, ensembledata diff --git a/censo_qm/qm_job.py b/censo_qm/qm_job.py new file mode 100644 index 0000000..16cc6a3 --- /dev/null +++ b/censo_qm/qm_job.py @@ -0,0 +1,611 @@ +""" +Contains QmJob base class for calculating QM related properties of conformers. +Additionally contains functions which should be present irrespective of the QM +code. (xTB always available) +""" +import os +import math + +try: + from math import isclose +except ImportError: + from .utilities import isclose +import time +import subprocess +import json +from .cfg import ENVIRON, CODING, censo_solvent_db +from .utilities import last_folders, print +from .datastructure import MoleculeData + + +class QmJob(MoleculeData): + """ + QmJob base class for calculating QM related properties of conformers. + """ + + def __init__(self, rank, *args, **kwargs): + MoleculeData.__init__(self, rank, *args, **kwargs) + self.reset_job_info() + + def reset_job_info(self): + """ + Clear information/instructions from the previous job + """ + self.job = { + "jobtype": "", + "prepinfo": [], # additional info for cefine + "method": "", # description of the method + "method2": "", # description of the method + "workdir": "", + "copymos": "", + "omp": 1, + "charge": 0, + "unpaired": 0, + "gfn_version": None, + "bhess": None, + "consider_sym": False, + "symmetry": "C1", + "rmsdbias": False, + "sm_rrho": None, + "func": None, + "func2": None, # functional used in subsequent property calculation + "basis": None, + "solvent": "gas", + "sm": "gas-phase", + "rmsd": 0.0, # rmsd in case of bhess + "nat": None, # number of atoms + "onlyread": False, # don't calculate, just perform readout + "progpath": "", + "xtb_driver_path": "", # program path to xtb if xtb as driver + # optimization related: + "optcycles": None, # number of cycles that are allowed in the + "hlow": None, # setting for ancopt + "optlevel": None, + # geometry optimization + "cycles": 0, # number of cycles it needed for optimization convergence + "ecyc": [], + "decyc": [], + "grad_norm": 10.0, + "converged": False, + # temperature related: + "trange": [], # list with temperatures to evaluate G,H,S + "temperature": 298.15, + # nmrprop related: + "h_active": False, + "c_active": False, + "f_active": False, + "p_active": False, + "si_active": False, + # optical rotation related: + "freq_or": [], + # return values which can be updated: + "success": False, + "energy": 0.0, + "energy2": 0.0, + "erange1": {}, + "erange2": {}, + "erange3": {}, + "errormessage": [], + "internal_error": [], + # + "cosmorsparam": "", # normal/fine + } + + def _sp(self, silent=False): + """ + single-point calculation + """ + pass + + def _opt(self): + """ + geometry optimization + """ + pass + + def _genericoutput(self): + """ + Read shielding and coupling constants and write them to plain output + The first natom lines contain the shielding constants, and from + line natom +1 the coupling constants are written. + """ + pass + + def _xtb_sp(self): + """ + Get single-point energy from xTB + """ + files = [ + "xtbrestart", + "xtbtopo.mol", + "xcontrol-inp", + "wbo", + "charges", + "gfnff_topo", + "sp.out", + ] + for file in files: + if os.path.isfile(os.path.join(self.job["workdir"], file)): + os.remove(os.path.join(self.job["workdir"], file)) + # run single-point: + + call = [ + self.job["progpath"], + "coord", + "--" + self.job["gfn_version"], + "--sp", + "--chrg", + str(self.job["charge"]), + "--norestart", + ] + if self.job["solvent"] != "gas": + call.extend( + [ + "--" + str(self.job["sm"]), + censo_solvent_db[self.job["solvent"]]["xtb"][1], + ] + ) + + with open( + os.path.join(self.job["workdir"], "sp.out"), "w", newline=None + ) as outputfile: + returncode = subprocess.call( + call, + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + stdout=outputfile, + env=ENVIRON, + ) + if returncode != 0: + self.job["energy"] = 0.0 + self.job["success"] = False + print( + f"ERROR: {self.job['gfn_version']}-xTB error in " + f"{last_folders(self.job['workdir'], 2)}" + ) + return + # read gas phase energy: + if os.path.isfile(os.path.join(self.job["workdir"], "sp.out")): + with open( + os.path.join(self.job["workdir"], "sp.out"), + "r", + encoding=CODING, + newline=None, + ) as inp: + store = inp.readlines() + for line in store: + if "| TOTAL ENERGY" in line: + try: + self.job["energy"] = float(line.split()[3]) + self.job["success"] = True + except: + print( + "Error while converting " + "single-point in: {}".format( + last_folders(self.job["workdir"], 2) + ) + ) + self.job["energy"] = 0.0 + self.job["success"] = False + return + else: + self.job["energy"] = 0.0 + self.job["success"] = False + print( + f"ERROR: {self.job['gfn_version']}-xTB error in " + f"{last_folders(self.job['workdir'], 2)}" + ) + return + print( + f"{self.job['gfn_version']}-xTB energy for {last_folders(self.job['workdir'], 2)}" + f" = {self.job['energy']: >.7f}" + ) + + def _xtb_gsolv(self): + """ + Calculate additive GBSA or ALPB solvation contribution by + Gsolv = Esolv - Egas, + using xTB and the GFNn or GFN-FF hamiltonian. + --> return gsolv at energy2 + """ + tmp_gas = 0 + tmp_solv = 0 + if self.job["jobtype"] == "gbsa_gsolv": + xtbsm = "gbsa" + elif self.job["jobtype"] == "alpb_gsolv": + xtbsm = "alpb" + print( + f"Running {self.job['jobtype'].upper()} calculation in " + f"{last_folders(self.job['workdir'], 3)}" + ) + files = [ + "xtbrestart", + "xtbtopo.mol", + "xcontrol-inp", + "wbo", + "charges", + "gfnff_topo", + "gas.out", + "solv.out", + ] + for file in files: + if os.path.isfile(os.path.join(self.job["workdir"], file)): + os.remove(os.path.join(self.job["workdir"], file)) + # run gas phase single-point: + with open( + os.path.join(self.job["workdir"], "gas.out"), "w", newline=None + ) as outputfile: + returncode = subprocess.call( + [ + self.job["xtb_driver_path"], + "coord", + "--" + self.job["gfn_version"], + "--sp", + "--chrg", + str(self.job["charge"]), + "--norestart", + ], + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + stdout=outputfile, + env=ENVIRON, + ) + if returncode != 0: + self.job["energy2"] = 0.0 + self.job["success"] = False + print( + f"ERROR: Gas phase {self.job['gfn_version']}-xTB error in " + f"{last_folders(self.job['workdir'], 3)}" + ) + return + # read gas phase energy: + if os.path.isfile(os.path.join(self.job["workdir"], "gas.out")): + with open( + os.path.join(self.job["workdir"], "gas.out"), + "r", + encoding=CODING, + newline=None, + ) as inp: + store = inp.readlines() + for line in store: + if "| TOTAL ENERGY" in line: + try: + tmp_gas = float(line.split()[3]) + self.job["success"] = True + except: + print( + "Error while converting gas phase " + "single-point in: {}".format( + last_folders(self.job["workdir"], 3) + ) + ) + tmp_gas = None + self.job["energy2"] = 0.0 + self.job["success"] = False + return + else: + self.job["energy2"] = 0.0 + self.job["success"] = False + print( + f"ERROR: Gas phase {self.job['gfn_version']}-xTB error in " + f"{last_folders(self.job['workdir'], 3)}" + ) + return + # run single-point in solution: + # ``reference'' corresponds to 1\;bar of ideal gas and 1\;mol/L of liquid + # solution at infinite dilution, + with open( + os.path.join(self.job["workdir"], "solv.out"), "w", newline=None + ) as outputfile: + returncode = subprocess.call( + [ + self.job["xtb_driver_path"], + "coord", + "--" + self.job["gfn_version"], + "--sp", + "--" + xtbsm, + censo_solvent_db[self.job["solvent"]]["xtb"][1], + "reference", + "--chrg", + str(self.job["charge"]), + "--norestart", + ], + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + stdout=outputfile, + env=ENVIRON, + ) + if returncode != 0: + self.job["energy2"] = 0.0 + self.job["success"] = False + print( + f"ERROR: Solution phase {self.job['gfn_version']}-xTB error in " + f"{last_folders(self.job['workdir'], 3)}" + ) + return + time.sleep(0.05) + # #read solv.out + if os.path.isfile(os.path.join(self.job["workdir"], "solv.out")): + with open( + os.path.join(self.job["workdir"], "solv.out"), + "r", + encoding=CODING, + newline=None, + ) as inp: + store = inp.readlines() + for line in store: + if "| TOTAL ENERGY" in line: + try: + tmp_solv = float(line.split()[3]) + self.job["success"] = True + except: + print( + "Error while converting solution phase " + "single-point in: {}".format( + last_folders(self.job["workdir"], 3) + ) + ) + tmp_solv = None + self.job["energy2"] = 0.0 + self.job["success"] = False + return + else: + self.job["energy2"] = 0.0 + self.job["success"] = False + print( + f"ERROR: Solution phase {self.job['gfn_version']}-xTB error in " + f"{last_folders(self.job['workdir'], 3)}" + ) + return + if self.job["success"]: + if tmp_solv is None or tmp_gas is None: + self.job["energy2"] = 0.0 + self.job["success"] = False + else: + self.job["energy2"] = tmp_solv - tmp_gas + self.job["success"] = True + self.job["energy_xtb_gas"] = tmp_gas + self.job["energy_xtb_solv"] = tmp_solv + + def _xtbrrho(self): + """ + mRRHO contribution with GFNn/GFN-FF-XTB + """ + if not self.job["onlyread"]: + print( + f"Running {str(self.job['gfn_version']).upper()}-xTB mRRHO in " + f"{last_folders(self.job['workdir'], 2)}" + ) + files = [ + "xtbrestart", + "xtbtopo.mol", + "xcontrol-inp", + "wbo", + "charges", + "gfnff_topo", + ] + for file in files: + if os.path.isfile(os.path.join(self.job["workdir"], file)): + os.remove(os.path.join(self.job["workdir"], file)) + if self.job["trange"]: + for t in list(self.job["trange"]): + if isclose(self.job["temperature"], t, abs_tol=0.6): + self.job["trange"].pop(self.job["trange"].index(t)) + self.job["trange"].append(self.job["temperature"]) + with open( + os.path.join(self.job["workdir"], "xcontrol-inp"), "w", newline=None + ) as xcout: + xcout.write("$thermo\n") + if self.job["trange"]: + xcout.write( + f" temp=" + f"{','.join([str(i) for i in self.job['trange']])}\n" + ) + else: + xcout.write(" temp={}\n".format(self.job["temperature"])) + xcout.write(" sthr=50.0\n") + if self.job["bhess"]: + xcout.write(" imagthr={}\n".format("-100")) + else: + xcout.write(" imagthr={}\n".format("-50")) + xcout.write("$symmetry\n") + if self.job["consider_sym"]: + # xcout.write(" desy=0.1\n") # taken from xtb defaults + xcout.write(" maxat=1000\n") + else: + xcout.write(" desy=0.0\n") + xcout.write("$end\n") + if self.job["bhess"]: + # set ohess or bhess + dohess = "--bhess" + olevel = "vtight" + else: + dohess = "--ohess" + olevel = "vtight" + time.sleep(0.05) + with open( + os.path.join(self.job["workdir"], "ohess.out"), "w", newline=None + ) as outputfile: + if self.job["solvent"] != "gas": + callargs = [ + self.job["progpath"], + "coord", + "--" + str(self.job["gfn_version"]), + dohess, + olevel, + "--" + str(self.job["sm_rrho"]), + censo_solvent_db[self.job["solvent"]]["xtb"][1], + "--chrg", + str(self.job["charge"]), + "--enso", + "--norestart", + "-I", + "xcontrol-inp", + ] + else: + callargs = [ + self.job["progpath"], + "coord", + "--" + str(self.job["gfn_version"]), + dohess, + olevel, + "--chrg", + str(self.job["charge"]), + "--enso", + "--norestart", + "-I", + "xcontrol-inp", + ] + if self.job["rmsdbias"]: + callargs.extend( + [ + "--bias-input", + str(os.path.join(self.job["cwd"], "rmsdpot.xyz")), + ] + ) + returncode = subprocess.call( + callargs, + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + stdout=outputfile, + env=ENVIRON, + ) + time.sleep(0.05) + # check if converged: + if returncode != 0: + self.job["energy"] = 0.0 + self.job["success"] = False + self.job["errormessage"].append( + f"ERROR: {str(self.job['gfn_version']).upper()}-xTB ohess error in " + f"{last_folders(self.job['workdir'], 2):18}" + ) + print(self.job["errormessage"][-1]) + return + # start reading output! + if self.job["trange"]: + if not os.path.isfile(os.path.join(self.job["workdir"], "ohess.out")): + self.job["energy"] = 0.0 + self.job["success"] = False + self.job["errormessage"].append( + f"ERROR: file {self.job['workdir']}/ohess.out could not be found!" + ) + print(self.job["errormessage"][-1]) + return + gt = {} + ht = {} + rotS = {} + with open( + os.path.join(self.job["workdir"], "ohess.out"), + "r", + encoding=CODING, + newline=None, + ) as inp: + store = inp.readlines() + # rotational entropy: + for line in store: + if "VIB" in line: + try: + realline = store.index(line) + 1 + T = float(line.split()[0]) + rotS[T] = float(store[realline].split()[4]) + except (KeyError, ValueError): + pass + for line in store: + if "T/K" in line: + start = store.index(line) + for line in store[start + 2 :]: + if "----------------------------------" in line: + break + else: + try: + T = float(line.split()[0]) + gt[T] = float(line.split()[4]) + ht[T] = float(line.split()[2]) + except (ValueError, KeyError): + print("ERROR: can not convert G(T)") + if len(self.job["trange"]) == len(gt): + self.job["success"] = True + self.job["erange1"] = gt + self.job["erange2"] = ht + self.job["erange3"] = rotS + else: + self.job["success"] = False + return + # end self.trange + if self.job["bhess"]: + # read rmsd_info + with open( + os.path.join(self.job["workdir"], "ohess.out"), + "r", + encoding=CODING, + newline=None, + ) as inp: + store = inp.readlines() + for line in store: + if "final rmsd / " in line: + try: + self.job["rmsd"] = float(line.split()[3]) + except (ValueError): + self.job["rmsd"] = 0.0 + if os.path.isfile(os.path.join(self.job["workdir"], "xtb_enso.json")): + with open( + os.path.join(self.job["workdir"], "xtb_enso.json"), + "r", + encoding=CODING, + newline=None, + ) as f: + data = json.load(f) + if "number of imags" in data: + if data["number of imags"] > 0: + print( + f"WARNING: found {data['number of imags']} significant" + f" imaginary frequencies in " + f"{last_folders(self.job['workdir'], 2)}" + ) + if "G(T)" in data: + if float(self.job["temperature"]) == 0: + self.job["energy"] = data.get("ZPVE", 0.0) + self.job["success"] = True + self.job["erange1"][self.job["temperature"]] = data.get("ZPVE", 0.0) + self.job["erange2"][self.job["temperature"]] = data.get("ZPVE", 0.0) + else: + self.job["energy"] = data.get("G(T)", 0.0) + self.job["erange1"][self.job["temperature"]] = data.get("G(T)", 0.0) + # self.job['erange2'][self.job['temperature']] = data.get("H(T)", 0.0) + self.job["success"] = True + if "point group" in data: + self.job["symmetry"] = data["point group"] + else: + print( + "Error while converting mRRHO in: {}".format( + last_folders(self.job["workdir"], 2) + ) + ) + self.job["energy"] = 0.0 + self.job["success"] = False + else: + print( + "WARNING: File {} doesn't exist!".format( + os.path.join(self.job["workdir"], "xtb_enso.json") + ) + ) + self.job["energy"] = 0.0 + self.job["success"] = False + + def execute(self): + """ + Chooses which function to execute based on jobtype. + """ + pass diff --git a/censo_qm/refinement.py b/censo_qm/refinement.py new file mode 100755 index 0000000..8cac7cf --- /dev/null +++ b/censo_qm/refinement.py @@ -0,0 +1,903 @@ +""" +REFINEMENT == Part3 +designed to yield high level free energies on dft optimized conformers. +""" +from multiprocessing import JoinableQueue as Queue +import shutil +import os +import sys +from .cfg import PLENGTH, CODING, AU2KCAL, DIGILEN +from .utilities import ( + check_for_folder, + print_block, + new_folders, + last_folders, + frange, + calc_boltzmannweights, + printout, + move_recursively, + write_trj, + check_tasks, + print, +) +from .parallel import run_in_parallel +from .orca_job import OrcaJob +from .tm_job import TmJob +from .datastructure import MoleculeData + + +def part3(config, conformers, store_confs, ensembledata): + """ + Refinement of the ensemble, at high level DFT (possibly with implicit solvation) + Calculate low level free energies with COSMO-RS single-point and gsolv + Input: + - config [conifg_setup object] contains all settings + - conformers [list of molecule_data objects] each conformer is represented + Return: + -> config + -> conformers + """ + save_errors = [] + print("\n" + "".ljust(PLENGTH, "-")) + print("CRE REFINEMENT - PART3".center(PLENGTH, " ")) + print("".ljust(PLENGTH, "-") + "\n") + # print flags for part3 + info = [] + info.append(["prog3", "program for part3"]) + info.append(["part3_threshold", "Boltzmann sum threshold employed"]) + info.append(["func3", "functional for part3"]) + info.append(["basis3", "basis set for part3"]) + if config.solvent != "gas": + info.append(["solvent", "solvent"]) + info.append(["smgsolv3", "solvent model"]) + info.append(["temperature", "temperature"]) + if config.multitemp: + info.append(["multitemp", "evalulate at different temperatures"]) + info.append( + [ + "printoption", + "temperature range", + [ + i + for i in frange( + config.trange[0], config.trange[1], config.trange[2] + ) + ], + ] + ) + info.append(["prog_rrho", "program for mRRHO contribution"]) + if config.prog_rrho == "xtb": + info.append(["part3_gfnv", "GFN version for mRRHO and/or GBSA_Gsolv"]) + if config.bhess: + info.append( + ["bhess", "Apply constraint to input geometry during mRRHO calculation"] + ) + optionsexchange = {True: "on", False: "off"} + for item in info: + if item[0] == "justprint": + print(item[1:][0]) + else: + if item[0] == "printoption": + option = item[2] + else: + option = getattr(config, item[0]) + if option is True or option is False: + option = optionsexchange[option] + elif isinstance(option, list): + option = [str(i) for i in option] + if len(str(option)) > 40: + length = 0 + reduced = [] + for i in option: + length += len(i) + 2 + if length < 40: + reduced.append(i) + reduced.append("...") + option = reduced + length = 0 + option = ", ".join(option) + print( + "{}: {:{digits}} {}".format( + item[1], "", option, digits=DIGILEN - len(item[1]) + ) + ) + print("") + # end print + + calculate = [] # has to be calculated in this run + prev_calculated = [] # was already calculated in a previous run + try: + store_confs + except NameError: + store_confs = [] # stores all confs which are sorted out! + + if config.solvent == "gas": + print("\nCalculating single-point energies!") + else: + print( + "\nCalculating single-point energies and solvation contribution (G_solv)!" + ) + + # setup queues + q = Queue() + resultq = Queue() + + if config.prog == "tm": + job = TmJob + elif config.prog == "orca": + job = OrcaJob + + for conf in list(conformers): + if conf.removed: + store_confs.append(conformers.pop(conformers.index(conf))) + print(f"CONF{conf.id} is removed as requested by the user!") + continue + if ( + conf.part_info["part2"] == "passed" + and conf.optimization_info["info"] == "calculated" + ): + if conf.highlevel_sp_info["info"] == "failed": + conf = conformers.pop(conformers.index(conf)) + store_confs.append(conf) + print(f"Calculation of CONF{conf.id} failed in the previous run!") + elif conf.highlevel_sp_info["info"] == "not_calculated": + # has to be calculated now + conf = conformers.pop(conformers.index(conf)) + calculate.append(conf) + elif conf.highlevel_sp_info["info"] == "prep-failed": + # has to be retried now + conf = conformers.pop(conformers.index(conf)) + calculate.append(conf) + elif conf.highlevel_sp_info["info"] == "calculated": + conf = conformers.pop(conformers.index(conf)) + if config.solvent != "gas": + # check if solvation calculation is calculated as well + if conf.highlevel_gsolv_info["info"] == "failed": + store_confs.append(conf) + print( + f"Calculation of the solvation contribution for CONF" + f"{conf.id} failed in the previous run!" + ) + elif conf.highlevel_gsolv_info["info"] == "not_calculated": + calculate.append(conf) + elif conf.highlevel_gsolv_info["info"] == "prep-failed": + # retry + calculate.append(conf) + elif conf.highlevel_gsolv_info["info"] == "calculated": + conf.job["success"] = True + prev_calculated.append(conf) + else: + print("UNEXPECTED BEHAVIOUR") + elif config.solvent == "gas": + conf.job["success"] = True + prev_calculated.append(conf) + else: + print( + f"WARNING: CONF{conf.id} has not been optimized (part2)! " + f"Removing CONF{conf.id}" + ) + conf = conformers.pop(conformers.index(conf)) + store_confs.append(conf) + if not calculate and not prev_calculated: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + + instruction = { + "func": config.func3, + "basis": getattr(config, "basis3", "def2-TZVPD"), + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": config.solvent, + "sm": config.smgsolv3, + "omp": config.omp, + "temperature": config.temperature, + "gfn_version": config.part3_gfnv, + "copymos": "", + "energy": 0.0, + "energy2": 0.0, + "success": False, + } + if config.multitemp: + instruction["trange"] = [ + i for i in frange(config.trange[0], config.trange[1], config.trange[2]) + ] + else: + instruction["trange"] = [] + if config.solvent == "gas": + instruction["jobtype"] = "sp" + instruction["prepinfo"] = ["high"] + instruction["method"], _ = config.get_method_name( + "sp", func=instruction["func"], basis=instruction["basis"] + ) + if config.prog3 == "orca": + instruction["progpath"] = config.external_paths["orcapath"] + folder = "gsolv" + name = "highlevel single-point" + else: + if config.smgsolv3 in config.smgsolv_3: + # additive GSolv + # COSMORS + if "cosmors" in config.smgsolv3 and config.smgsolv3 != "dcosmors": + job = TmJob + exc_fine = {"cosmors": "normal", "cosmors-fine": "fine"} + tmp = { + "jobtype": "cosmors", + "prepinfo": ["high"], + "cosmorssetup": config.external_paths["cosmorssetup"], + "cosmorsparam": exc_fine.get(config.smgsolv3, "normal"), + "cosmothermversion": config.external_paths["cosmothermversion"], + } + instruction.update(tmp) + instruction["method"], instruction["method2"] = config.get_method_name( + "cosmors", + func=instruction["func"], + basis=instruction["basis"], + sm=instruction["sm"], + ) + folder = "gsolv/COSMO" + name = "highlevel COSMO-RS" + # GBSA-Gsolv / ALPB-Gsolv + elif instruction["sm"] in ("gbsa_gsolv", "alpb_gsolv"): + # do DFT gas phase sp and additive Gsolv + instruction["jobtype"] = instruction["sm"] + if config.prog3 == "orca": + instruction["progpath"] = config.external_paths["orcapath"] + instruction["xtb_driver_path"] = config.external_paths["xtbpath"] + instruction["method"], instruction["method2"] = config.get_method_name( + instruction["jobtype"], + func=instruction["func"], + basis=instruction["basis"], + sm=instruction["sm"], + gfn_version=instruction["gfn_version"], + ) + if ( + conf.highlevel_sp_info["info"] == "calculated" + and conf.highlevel_sp_info["method"] == instruction["method"] + ): + # do not calculate gas phase sp again! + instruction["energy"] = conf.highlevel_sp_info["energy"] + instruction["prepinfo"] = [] + else: + instruction["prepinfo"] = ["high"] + name = "highlevel " + str(instruction["sm"]).upper() + folder = "gsolv" + # SMD-Gsolv + elif instruction["sm"] == "smd_gsolv": + job = OrcaJob + instruction["prepinfo"] = ["high"] + instruction["jobtype"] = instruction["sm"] + instruction["progpath"] = config.external_paths["orcapath"] + instruction["method"], instruction["method2"] = config.get_method_name( + "smd_gsolv", + func=instruction["func"], + basis=instruction["basis"], + sm=instruction["sm"], + ) + name = "highlevel" + str(instruction["sm"]).upper() + folder = "gsolv" + else: + # with implicit solvation + instruction["jobtype"] = "sp_implicit" + instruction["prepinfo"] = ["high"] + if config.prog3 == "orca": + instruction["progpath"] = config.external_paths["orcapath"] + instruction["method"], instruction["method2"] = config.get_method_name( + "sp_implicit", + func=instruction["func"], + basis=instruction["basis"], + sm=instruction["sm"], + ) + name = "high level single-point" + folder = "gsolv" + + if prev_calculated: + check_for_folder(config.cwd, [i.id for i in prev_calculated], folder) + print("The calculation was performed before for:") + print_block(["CONF" + str(i.id) for i in prev_calculated]) + pl = config.lenconfx + 4 + len(str("/" + folder)) + + check = {True: "was successful", False: "FAILED"} + if calculate: + # make new folder: + save_errors, store_confs, calculate = new_folders( + config.cwd, calculate, folder, save_errors, store_confs + ) + # need to copy optimized coord to folder + for conf in calculate: + tmp1 = os.path.join(config.cwd, "CONF" + str(conf.id), config.func, "coord") + tmp2 = os.path.join("CONF" + str(conf.id), folder, "coord") + try: + shutil.copy(tmp1, tmp2) + except FileNotFoundError: + print("ERROR can't copy optimized geometry!") + if config.solvent == "gas": + print("The high level single-point is now calculated for:") + else: + print("The high level gsolv calculation is now calculated for:") + print_block(["CONF" + str(i.id) for i in calculate]) + # parallel calculation: + calculate = run_in_parallel( + config, q, resultq, job, config.maxthreads, calculate, instruction, folder + ) + # check if too many calculations failed + + for conf in list(calculate): + if instruction["jobtype"] in ("sp", "sp_implicit"): + line = ( + f"{name} calculation {check[conf.job['success']]}" + f" for {last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.job['energy']:>.8f}" + ) + print(line) + if not conf.job["success"]: + save_errors.append(line) + conf.highlevel_sp_info["info"] = "failed" + conf.highlevel_sp_info["method"] = instruction["method"] + conf.part_info["part3"] = "refused" + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.highlevel_sp_info["energy"] = conf.job["energy"] + conf.highlevel_sp_info["info"] = "calculated" + conf.highlevel_sp_info["method"] = instruction["method"] + elif instruction["jobtype"] in ( + "cosmors", + "smd_gsolv", + "gbsa_gsolv", + "alpb_gsolv", + ): + line = ( + f"{name} calculation {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 3):>{pl}}: " + f"{conf.job['energy2']:>.8f}" + ) + print(line) + if not conf.job["success"]: + save_errors.append(line) + conf.part_info["part3"] = "refused" + conf.highlevel_sp_info["info"] = "failed" + conf.highlevel_sp_info["method"] = instruction["method"] + conf.highlevel_gsolv_info["info"] = "failed" + conf.highlevel_gsolv_info["method"] = instruction["method2"] + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.highlevel_sp_info["energy"] = conf.job["energy"] + conf.highlevel_sp_info["info"] = "calculated" + conf.highlevel_sp_info["method"] = instruction["method"] + conf.highlevel_gsolv_info["energy"] = conf.job["energy2"] + conf.highlevel_gsolv_info["gas-energy"] = conf.job["energy"] + conf.highlevel_gsolv_info["info"] = "calculated" + conf.highlevel_gsolv_info["method"] = instruction["method2"] + conf.highlevel_gsolv_info["range"] = conf.job["erange1"] + else: + print( + f'UNEXPECTED BEHAVIOUR: {conf.job["success"]} {conf.job["jobtype"]}' + ) + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + check_tasks(calculate, config.check) + else: + print("No conformers are considered additionally.") + # adding conformers calculated before: + if prev_calculated: + # adding conformers calculated before: + for conf in list(prev_calculated): + conf.job["workdir"] = os.path.normpath( + os.path.join(config.cwd, "CONF" + str(conf.id), folder) + ) + if instruction["jobtype"] in ("sp", "sp_implicit"): + print( + f"Single-point calculation {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.highlevel_sp_info['energy']:>.8f}" + ) + elif instruction["jobtype"] in ( + "cosmors", + "smd_gsolv", + "gbsa_gsolv", + "alpb_gsolv", + ): + print( + f"COSMO-RS calculation {check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 3):>{pl}}: " + f"{conf.highlevel_gsolv_info['energy']:>.8f}" + ) + calculate.append(prev_calculated.pop(prev_calculated.index(conf))) + for conf in calculate: + conf.reset_job_info() + if not calculate: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + # *************************************************************************** + if config.evaluate_rrho: + instruction_rrho = { + "jobtype": "rrhoxtb", + "func": getattr(config, "part3_gfnv"), + "gfn_version": getattr(config, "part3_gfnv"), + "temperature": config.temperature, + "charge": config.charge, + "unpaired": config.unpaired, + "solvent": config.solvent, + "omp": config.omp, + "bhess": config.bhess, + "sm_rrho": config.sm_rrho, + "rmsdbias": config.rmsdbias, + "cwd": config.cwd, + "consider_sym": config.consider_sym, + "energy": 0.0, + "energy2": 0.0, + "success": False, + "progpath": config.external_paths["xtbpath"], + } + folder_rrho = "rrho_part3" + instruction_rrho["method"], _ = config.get_method_name( + "rrhoxtb", + bhess=config.bhess, + gfn_version=instruction_rrho["gfn_version"], + sm=instruction_rrho["sm_rrho"], + solvent=instruction_rrho["solvent"], + ) + if config.multitemp: + instruction_rrho["trange"] = [ + i for i in frange(config.trange[0], config.trange[1], config.trange[2]) + ] + else: + instruction_rrho["trange"] = [] + + # check if calculated + for conf in list(calculate): + if conf.removed: + store_confs.append(calculate.pop(calculate.index(conf))) + print(f"CONF{conf.id} is removed as requested by the user!") + continue + if ( + conf.part_info["part2"] == "passed" + and conf.optimization_info["info"] == "calculated" + ): + if conf.highlevel_grrho_info["info"] == "calculated": + conf = calculate.pop(calculate.index(conf)) + conf.job["success"] = True + prev_calculated.append(conf) + elif conf.highlevel_grrho_info["info"] == "failed": + conf = calculate.pop(calculate.index(conf)) + conf.part_info["part3"] = "refused" + store_confs.append(conf) + print(f"Calculation of CONF{conf.id} failed in the previous run!") + elif conf.highlevel_grrho_info["info"] in ( + "not_calculated", + "prep-failed", + ): + # stay in calculate (e.g not_calculated or prep-failed) + # check if method has been calculated in part2 + if instruction_rrho["method"] == conf.lowlevel_grrho_info["method"]: + # has been calculated before, just copy + conf.job["success"] = True + attributes = vars(MoleculeData(0)).get("highlevel_grrho_info") + tmp = {} + for key in attributes.keys(): + if key != "prev_methods": + tmp[key] = getattr(conf, "lowlevel_grrho_info").get(key) + getattr(conf, "highlevel_grrho_info").update(tmp) + prev_calculated.append(calculate.pop(calculate.index(conf))) + elif ( + instruction_rrho["method"] + in conf.lowlevel_grrho_info["prev_methods"].keys() + ): + # has been calculated before, just copy + conf.job["success"] = True + conf.load_prev( + "lowlevel_grrho_info", + instruction_rrho["method"], + saveto="highlevel_grrho_info", + ) + prev_calculated.append(calculate.pop(calculate.index(conf))) + else: + print("UNEXPECTED BEHAVIOUR") + else: + conf = calculate.pop(calculate.index(conf)) + store_confs.append(conf) + if not calculate and not prev_calculated: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + # do the rrho stuff: + if config.solvent == "gas": + print("\nCalculating highlevel G_mRRHO on DFT geometry!") + else: + print( + "\nCalculating highlevel G_mRRHO with implicit solvation " + "on DFT geometry!" + ) + if prev_calculated: + check_for_folder(config.cwd, [i.id for i in prev_calculated], folder_rrho) + print("The G_mRRHO calculation was performed before for:") + print_block(["CONF" + str(i.id) for i in prev_calculated]) + pl = config.lenconfx + 4 + len(str("/" + folder_rrho)) + + if calculate: + print("The G_mRRHO calculation is now performed for:") + print_block(["CONF" + str(i.id) for i in calculate]) + # create folders: + save_errors, store_confs, calculate = new_folders( + config.cwd, calculate, folder_rrho, save_errors, store_confs + ) + # copy optimized geoms to folder + for conf in list(calculate): + try: + tmp_from = os.path.join( + config.cwd, "CONF" + str(conf.id), config.func + ) + tmp_to = os.path.join( + config.cwd, "CONF" + str(conf.id), folder_rrho + ) + shutil.copy( + os.path.join(tmp_from, "coord"), os.path.join(tmp_to, "coord") + ) + except shutil.SameFileError: + pass + except FileNotFoundError: + if not os.path.isfile(os.path.join(tmp_from, "coord")): + print( + "ERROR: while copying the coord file from {}! " + "The corresponding file does not exist.".format(tmp_from) + ) + elif not os.path.isdir(tmp_to): + print("ERROR: Could not create folder {}!".format(tmp_to)) + print("ERROR: Removing conformer {}!".format(conf.name)) + conf.highlevel_grrho_info["info"] = "prep-failed" + store_confs.append(calculate.pop(calculate.index(conf))) + save_errors.append(f"CONF{conf.id} was removed, because IO failed!") + # parallel execution: + calculate = run_in_parallel( + config, + q, + resultq, + job, + config.maxthreads, + calculate, + instruction_rrho, + folder_rrho, + ) + check = {True: "was successful", False: "FAILED"} + # check if too many calculations failed + + ### + for conf in list(calculate): + print( + f"The G_mRRHO calculation @ {conf.job['symmetry']} " + f"{check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.job['energy']:>.8f}" + ) + if not conf.job["success"]: + conf.part_info["part3"] = "refused" + conf.highlevel_grrho_info["info"] = "failed" + conf.highlevel_grrho_info["method"] = instruction_rrho["method"] + store_confs.append(calculate.pop(calculate.index(conf))) + else: + conf.sym = conf.job["symmetry"] + conf.highlevel_grrho_info["rmsd"] = conf.job["rmsd"] + conf.highlevel_grrho_info["energy"] = conf.job["energy"] + conf.highlevel_grrho_info["info"] = "calculated" + conf.highlevel_grrho_info["method"] = instruction_rrho["method"] + conf.highlevel_grrho_info["range"] = conf.job["erange1"] + conf.highlevel_hrrho_info["range"] = conf.job["erange2"] + conf.highlevel_hrrho_info["info"] = "calculated" + conf.highlevel_hrrho_info["method"] = instruction_rrho["method"] + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + check_tasks(calculate, config.check) + else: + print("No conformers are considered additionally.") + # adding conformers calculated before: + if prev_calculated: + for conf in list(prev_calculated): + conf.job["workdir"] = os.path.normpath( + os.path.join(config.cwd, "CONF" + str(conf.id), folder_rrho) + ) + print( + f"The G_mRRHO calculation @ {conf.sym} " + f"{check[conf.job['success']]} for " + f"{last_folders(conf.job['workdir'], 2):>{pl}}: " + f"{conf.highlevel_grrho_info['energy']:>.8f}" + ) + calculate.append(prev_calculated.pop(prev_calculated.index(conf))) + if not calculate: + print("ERROR: No conformers left!") + print("Going to exit!") + sys.exit(1) + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + # printout for part3 ------------------------------------------------------- + print("\n" + "".ljust(int(PLENGTH / 2), "-")) + print("* Gibbs free energies of part3 *".center(int(PLENGTH / 2), " ")) + print("".ljust(int(PLENGTH / 2), "-") + "\n") + columncall = [ + lambda conf: "CONF" + str(getattr(conf, "id")), + lambda conf: getattr(conf, "xtb_energy"), + lambda conf: getattr(conf, "rel_xtb_energy"), + lambda conf: getattr(conf, "highlevel_sp_info")["energy"], + lambda conf: getattr(conf, "highlevel_gsolv_info")["energy"], + lambda conf: getattr(conf, "highlevel_grrho_info")["energy"], + lambda conf: getattr(conf, "free_energy"), + lambda conf: getattr(conf, "rel_free_energy"), + ] + columnheader = [ + "CONF#", + "E(GFNn-xTB)", + "ΔE(GFNn-xTB)", + "E [Eh]", + "Gsolv [Eh]", + "GmRRHO [Eh]", + "Gtot", + "ΔGtot", + ] + columndescription = ["", "[a.u.]", "[kcal/mol]", "", "", "", "[Eh]", "[kcal/mol]"] + columndescription2 = ["", "", "", "", "", "", "", ""] + columnformat = ["", (12, 7), (5, 2), (12, 7), (12, 7), (12, 7), (12, 7), (5, 3)] + if config.solvent == "gas": + columndescription[3] = instruction["method"] + elif config.solvent != "gas": + columndescription[3] = instruction["method"] + columndescription[4] = instruction["method2"] + if config.evaluate_rrho: + columndescription[5] = str(instruction_rrho["method"]).upper() # Grrho + if not config.evaluate_rrho or config.solvent == "gas": + if not config.evaluate_rrho: + # ignore rrho in printout + columncall.pop(5) + columnheader.pop(5) + columndescription.pop(5) + columndescription2.pop(5) + columnformat.pop(5) + if config.solvent == "gas": + columncall.pop(4) + columnheader.pop(4) + columndescription.pop(4) + columndescription2.pop(4) + columnformat.pop(4) + + for conf in calculate: + if not config.evaluate_rrho: + rrho = None + else: + rrho = "highlevel_grrho_info" + if config.solvent == "gas": + solv = None + else: + solv = "highlevel_gsolv_info" + e = "highlevel_sp_info" + conf.calc_free_energy(e=e, solv=solv, rrho=rrho) + + calculate = calc_boltzmannweights(calculate, "free_energy", config.temperature) + try: + minfree = min([i.free_energy for i in calculate if i is not None]) + except ValueError: + raise + for conf in calculate: + conf.rel_free_energy = (conf.free_energy - minfree) * AU2KCAL + calculate.sort(key=lambda x: int(x.id)) + printout( + os.path.join(config.cwd, "part3.dat"), + columncall, + columnheader, + columndescription, + columnformat, + calculate, + minfree, + columndescription2=columndescription2, + ) + # end printout for part3 + + for conf in calculate: + if conf.free_energy == minfree: + ensembledata.bestconf["part3"] = conf.id + # -----------------------------Trange Ouput---------------------------------- + if config.multitemp: + trange = [ + t for t in frange(config.trange[0], config.trange[1], config.trange[2]) + ] + else: + trange = [config.temperature] + for conf in calculate: + if conf.free_energy == minfree: + # writeout of temperaturereante --> trange.dat + try: + l1 = max([len(str(i)) for i in trange]) + l2 = max([len(str(i - 273.15)) for i in trange]) + l3 = 12 + l4 = 12 + l5 = 14 + l6 = 16 + l7 = 12 + with open( + os.path.join(config.cwd, "trange.dat"), "w", newline=None + ) as out: + print( + f"\nTemperature range for lowest lying conformer: CONF{conf.id}" + ) + if getattr(ensembledata, "comment")[0] != conf.id: + # ensemble correction has been calculated for confx + print( + f"The avGcorrection was calculated in part2 for " + f"CONF{getattr(ensembledata, 'comment')[0]}" + ) + line = f"\n{'T/K':>{l1}} {'T/°C':>{l2}} " + if config.solvent != "gas": + line = line + f"{'δGsolv/au':>{l3}} " + if config.evaluate_rrho: + line = line + f"{'GmRRHO/au':>{l4}} " + line = line + ( + f"{'E/au':>{l5}} " + f"{'avGcorrection/au':>{l6}} {'Gtot/au':>{l7}}" + ) + print(line) + out.write(line + "\n") + line = "".ljust(int(PLENGTH), "-") + print(line) + out.write(line + "\n") + for t in trange: + tmp = 0.0 + line = f"{t:{l1}.2f} {t-273.15:>{l2}.1f} " + if config.solvent != "gas": + tmp += conf.highlevel_gsolv_info["range"].get(t, 0.0) + line = ( + line + + f"{conf.highlevel_gsolv_info['range'].get(t, 0.0):>{l3}.7f} " + ) + if config.evaluate_rrho: + tmp += conf.highlevel_grrho_info["range"].get(t, 0.0) + line = ( + line + + f"{conf.highlevel_grrho_info['range'].get(t, 0.0):>{l4}.7f} " + ) + line = line + ( + f"{conf.highlevel_sp_info['energy']:>{l5}.7f} " + f"{ensembledata.avGcorrection['avGcorrection'].get(t, 0.0):>{l6}.7f} " + ) + tmp += conf.highlevel_sp_info["energy"] + tmp += ensembledata.avGcorrection["avGcorrection"].get(t, 0.0) + line = line + f" {tmp:>{l7}.7f}" + print(line) + out.write(line + "\n") + print("".ljust(int(PLENGTH), "-")) + print("") + except (ValueError, KeyError) as e: + print(f"ERROR: {e}") + # -----------------------------Trange Ouput END------------------------------ + # reset boltzmannweights to correct temperature + # get free energy at (T) + for conf in calculate: + if not config.evaluate_rrho: + rrho = None + else: + rrho = "highlevel_grrho_info" + if config.solvent == "gas": + solv = None + else: + solv = "highlevel_gsolv_info" + e = "highlevel_sp_info" + conf.calc_free_energy(e=e, solv=solv, rrho=rrho) + calculate = calc_boltzmannweights(calculate, "free_energy", config.temperature) + # SORTING for the next part: + print("\n" + "".ljust(int(PLENGTH / 2), "-")) + print("Conformers considered further".center(int(PLENGTH / 2), " ")) + print("".ljust(int(PLENGTH / 2), "-") + "\n") + # evaluate conformer consideration based on Boltzmann-population + calculate.sort(reverse=True, key=lambda x: float(x.bm_weight)) + sumup = 0.0 + for conf in list(calculate): + sumup += conf.bm_weight + if sumup >= (config.part3_threshold / 100): + if conf.bm_weight < (1 - (config.part3_threshold / 100)): + mol = calculate.pop(calculate.index(conf)) + mol.part_info["part3"] = "refused" + store_confs.append(mol) + else: + conf.part_info["part3"] = "passed" + else: + conf.part_info["part3"] = "passed" + + ensembledata.nconfs_per_part["part3"] = len(calculate) + + if calculate: + print( + f"\nConformers that are below the Boltzmann-thr of {config.part3_threshold}%:" + ) + print_block(["CONF" + str(i.id) for i in calculate]) + + # save current data to jsonfile + config.write_json( + config.cwd, + [i.provide_runinfo() for i in calculate] + + [i.provide_runinfo() for i in prev_calculated] + + [i.provide_runinfo() for i in store_confs] + + [ensembledata], + config.provide_runinfo(), + ) + + # write ensemble + move_recursively(config.cwd, "enso_ensemble_part3.xyz") + kwargs = {"energy": "xtb_energy", "rrho": "highlevel_grrho_info"} + write_trj( + sorted(calculate, key=lambda x: float(x.free_energy)), + config.cwd, + "enso_ensemble_part3.xyz", + config.func, + config.nat, + "free_energy", + **kwargs, + ) + + # write coord.enso_best + for conf in calculate: + if conf.id == ensembledata.bestconf["part3"]: + # copy the lowest optimized conformer to file coord.enso_best + with open( + os.path.join("CONF" + str(conf.id), config.func, "coord"), + "r", + encoding=CODING, + newline=None, + ) as f: + coord = f.readlines() + with open( + os.path.join(config.cwd, "coord.enso_best"), "w", newline=None + ) as best: + best.write( + "$coord # {} {} !CONF{} \n".format( + conf.free_energy, conf.highlevel_grrho_info["energy"], conf.id + ) + ) + for line in coord[1:]: + if "$" in line: # stop at $end ... + break + best.write(line) + best.write("$end \n") + + # reset + for conf in calculate: + conf.free_energy = 0.0 + conf.rel_free_energy = 0.0 + conf.bm_weight = 0.0 + conf.reset_job_info() + + if save_errors: + print( + "***---------------------------------------------------------***", + file=sys.stderr, + ) + print( + "Printing most relevant errors again, just for user convenience:", + file=sys.stderr, + ) + for _ in list(save_errors): + print(save_errors.pop(), file=sys.stderr) + print( + "***---------------------------------------------------------***", + file=sys.stderr, + ) + tmp = int((PLENGTH - len("END of Part3")) / 2) + print("\n" + "".ljust(tmp, ">") + "END of Part3" + "".rjust(tmp, "<")) + return config, calculate, store_confs, ensembledata diff --git a/censo_qm/setupcenso.py b/censo_qm/setupcenso.py new file mode 100755 index 0000000..d17d86b --- /dev/null +++ b/censo_qm/setupcenso.py @@ -0,0 +1,637 @@ +""" +Contains enso_startup for the initialization of all parameters set for the +suseqent calculation. +""" +import os +import sys +import json +from collections import OrderedDict +from .cfg import CODING, PLENGTH, DESCR, censo_solvent_db, __version__ +from .inputhandling import config_setup, internal_settings +from .datastructure import MoleculeData +from .qm_job import QmJob +from .utilities import ( + mkdir_p, + do_md5, + t2x, + move_recursively, + get_energy_from_ensemble, + frange, + print, +) +from .ensembledata import EnsembleData + + +def enso_startup(cwd, args): + """ + 1) read cml input, + 2) print header + 3) read or create enso control file '.censorc' + 4) read or write flags.dat control file + 5) check for crest_conformers.xyz + 6) check program settings + 7) read or write enso.json + """ + + print(DESCR) + config = config_setup(path=os.path.abspath(cwd)) + + if args.cleanup: + print("Cleaning up the directory from unneeded files!") + config.cleanup_run() + print("Removed files and going to exit!") + sys.exit(0) + elif args.cleanup_all: + print("Cleaning up the directory from ALL unneeded files!") + config.cleanup_run(True) + print("Removed files and going to exit!") + sys.exit(0) + + if args.writeconfig: + newconfigfname = "censorc_new" + print( + "A new ensorc was written into the current directory file: " + f"{newconfigfname}!\nYou have to adjust the settings to your needs" + " and it is mandatory to correctly set the program paths!\n" + "Additionally move the file to the correct filename: '.censorc'\n" + "and place it either in your /home/$USER/ or current directory.\n" + "All done!" + ) + config.write_rcfile(os.path.join(config.cwd, newconfigfname)) + sys.exit(0) + configfname = ".censorc" + if os.path.isfile(os.path.join(config.cwd, configfname)): + # local configuration file before remote configuration file + config.configpath = os.path.join(config.cwd, configfname) + elif os.path.isfile(os.path.join(os.path.expanduser("~"), configfname)): + # remote configuration file + config.configpath = os.path.join(os.path.expanduser("~"), configfname) + else: + print( + f"ERROR: Could not find the config file: {configfname}.\n" + f"{'':{7}}The file has to be either in /home/$USER/ or the current " + "working directory!\nGoing to exit!" + ) + sys.exit(1) + + ### solvent database adjustable by user + censo_assets_path = os.path.expanduser("~/.censo_assets") + if not os.path.isdir(censo_assets_path): + mkdir_p(censo_assets_path) + solvent_user_path = os.path.expanduser( + os.path.join("~/.censo_assets/", "censo_solvents.json") + ) + if os.path.isfile(solvent_user_path): + config.save_infos.append( + "Reading file: {}\n".format(os.path.basename(solvent_user_path)) + ) + try: + with open(solvent_user_path, "r", encoding=CODING, newline=None) as inp: + censo_solvent_db.update(json.load(inp, object_pairs_hook=OrderedDict)) + except (ValueError, TypeError, FileNotFoundError): + print( + f"Your censo_solvents.json file in {solvent_user_path} is corrupted!\n" + ) + raise + + else: + with open(solvent_user_path, "w") as out: + json.dump(censo_solvent_db, out, indent=4, sort_keys=True) + + if args.restart and os.path.isfile(os.path.join(config.cwd, "enso.json")): + tmp = config.read_json(os.path.join(config.cwd, "enso.json"), silent=True) + previous_settings = tmp.get("settings") + # import json + # print(json.dumps(vars(args), sort_keys=False, indent=4)) + for key, value in previous_settings.items(): + if vars(args).get(key, "unKn_own") == "unKn_own": + # print(key, 'not_known') + continue + if getattr(args, key, "unKn_own") is None: + setattr(args, key, value) + # print(json.dumps(vars(args), sort_keys=False, indent=4)) + + if config.configpath: + # combine args und comandline + # check if startread in file: + startread = "$CRE SORTING SETTINGS:" + with open(config.configpath, "r") as myfile: + try: + tmp_version = "$VERSION" + data = myfile.readlines() + censorc_version = "0.0.0" + for line in data: + if tmp_version in line: + censorc_version = line.split(":")[1] + if int(censorc_version.split(".")[0]) < int(__version__.split(".")[0]): + print( + f"ERROR: There has been an API break and you have to " + "create a new .censorc.\n E.g. 'censo -newconfig'" + ) + sys.exit(1) + myfile.seek(0) # reset reader + except (ValueError, KeyError, AttributeError) as e: + print(e) + print(f"ERROR: Please create a new .censorc --> 'censo -newconfig'") + sys.exit(1) + if not startread in myfile.read(): + print(f"ERROR: You are using a corrupted .censorc. Create a new one!") + sys.exit(1) + config.read_config(config.configpath, startread, args) + + # read inputfile: + if os.path.isfile(os.path.join(config.cwd, "enso.json")): + tmp = config.read_json(os.path.join(config.cwd, "enso.json"), silent=True) + if "ensemble_info" in tmp and args.inp is None: + inpfile = os.path.basename(tmp["ensemble_info"].get("filename")) + if os.path.isfile(inpfile): + args.inp = inpfile + if args.debug: + print(f"Using Input file from: {inpfile}") + if args.inp is None: + args.inp = "crest_conformers.xyz" + if os.path.isfile(args.inp): + config.ensemblepath = args.inp + # identify coord or xyz trajectory + config.md5 = do_md5(config.ensemblepath) + with open(config.ensemblepath, "r", encoding=CODING, newline=None) as inp: + foundcoord = False + for line in inp: + if "$coord" in line: + foundcoord = True + break + if foundcoord: + _, config.nat = t2x( + config.ensemblepath, writexyz=True, outfile="converted.xyz" + ) + config.ensemblepath = os.path.join(config.cwd, "converted.xyz") + config.maxconf = 1 + config.nconf = 1 + else: + with open( + config.ensemblepath, "r", encoding=CODING, newline=None + ) as infile: + try: + config.nat = int(infile.readline().strip().split()[0]) + filelen = 1 + except (ValueError, TypeError): + raise + for line in infile: + filelen += 1 + try: + config.maxconf = int(filelen / (config.nat + 2)) + if filelen % (config.nat + 2) != 0: + raise ValueError + except ValueError: + print( + "ERROR: Could not get the number of atoms or the " + "number of conformers from the inputfile " + f"{os.path.basename(args.inp)}" + ) + sys.exit(1) + else: + print("ERROR: The input file can not be found!") + sys.exit(1) + + # determine number of conformers: + if args.nconf: + if args.nconf > config.maxconf: + config.nconf = config.maxconf + else: + config.nconf = args.nconf + else: + config.nconf = config.maxconf + + # check settings-combination and show error: + error_logical = config.check_logic() + + # printing parameters + config.print_parameters() + config.read_program_paths(config.configpath) + requirements = config.needed_external_programs(config) + error_logical = config.processQMpaths(requirements, error_logical) + + if error_logical and not args.debug and args.checkinput: + print( + "\nERROR: ENSO can not continue due to input errors!\n" + " Fix errors and run enso -checkinput again!" + "\nGoing to exit!" + ) + sys.exit(1) + elif error_logical and not args.debug: + print("\nERROR: ENSO can not continue due to input errors!" "\nGoing to exit!") + sys.exit(1) + + if not error_logical or args.debug: + print("\n" + "".ljust(PLENGTH, "-")) + print(" Processing data from previous run (enso.json)".center(PLENGTH, " ")) + print("".ljust(PLENGTH, "-") + "\n") + # read enso.json + if os.path.isfile(os.path.join(config.cwd, "enso.json")): + config.jsonpath = os.path.join(config.cwd, "enso.json") + save_data = config.read_json(config.jsonpath) + # Check if settings and "ensemble_info" are present else end! + if "settings" not in save_data or "ensemble_info" not in save_data: + print( + f"ERROR: important information for restarting missing from " + f"{config.jsonpath}!" + ) + print("Going to exit!") + sys.exit(1) + previousrun = config_setup(internal_settings) + for item in save_data["settings"].keys(): + setattr(previousrun, item, save_data["settings"].get(item)) + if config.md5 != previousrun.md5: + print( + "WARNING: The inputfile containing all conformers was " + "changed, compared to the previous run!" + ) + for flag in config.restart_unchangeable: + if getattr(config, flag, "None") != getattr(previousrun, flag, "None2"): + print( + f"ERROR: setting {flag} was changed from " + f"{getattr(config, flag, 'None')} to {getattr(previousrun, flag, 'None')}!" + ) + error_logical = True + if ( + getattr(config, "evaluate_rrho", "None") + != getattr(previousrun, "evaluate_rrho", "None2") + ) and getattr(config, "part2", "None"): + print( + f"ERROR: setting {'evaluate_rrho'} can not be changed " + f"in geometry optimization!\n" + ) + error_logical = True + if error_logical and not args.debug: + print( + "ERROR: All flags which are concerned with geometry " + f"optimization \n{'':{7}}(func, prog, ancopt, sm, solv, chrg, " + "unpaired) are not allowed to be changed!\n" + f"{'':{7}}If you want to change these settings, " + "start from scratch in a new folder!" + ) + print("Going to exit!") + sys.exit(1) + if not args.checkinput: + move_recursively(config.cwd, os.path.basename(config.jsonpath)) + + # Check if flags have been changed between two runs and adjust data + # e.g. reset or load previously calculated + for flag in config.restart_changeable.keys(): + if getattr(config, flag, "None") != getattr(previousrun, flag, "None2"): + print( + f"WARNING: setting {flag} was changed from " + f"{getattr(previousrun, flag, 'None')} to " + f"{getattr(config, flag, 'None')}!" + ) + config.restart_changeable[flag] = True + if ( + flag == "multitemp" + and getattr(config, flag, "None") + and not getattr(previousrun, flag, "None") + ): + # multitemp only reset if not calculated before! + # --> off --> on + print( + f"WARNING: {flag} is requested and the different " + "temperatures have not been evaluated in the\n" + f"{'':9}previous run! Resetting calculations concerning trange!" + ) + elif ( + flag == "multitemp" + and not getattr(config, flag, "None") + and getattr(previousrun, flag, "None") + ): + # multitemp only reset if not calculated before! + # --> off --> on + config.restart_changeable[flag] = False + if flag == "trange": + # if temp in trange has not been calculated reset! + prev_t = getattr(previousrun, flag) + prev_trange = [ + i for i in frange(prev_t[0], prev_t[1], prev_t[2]) + ] + cur_t = getattr(config, flag) + current_trange = [ + i for i in frange(cur_t[0], cur_t[1], cur_t[2]) + ] + for temp in current_trange: + if temp not in prev_trange: + config.restart_changeable[flag] = True + break + else: + config.restart_changeable[flag] = False + + conformers = [] + for conf in save_data.keys(): + if conf == "ensemble_info": + ensembledata = EnsembleData( + id=save_data[conf].get("id"), + filename=save_data[conf].get("filename"), + part_info=save_data[conf].get("part_info"), + avGcorrection=save_data[conf].get("avGcorrection"), + comment=save_data[conf].get("comment"), + bestconf=save_data[conf].get("bestconf"), + nconfs_per_part=save_data[conf].get("nconfs_per_part"), + ) + ensembledata.nconfs_per_part["starting"] = config.nconf + elif conf not in ("settings", "ensemble_info"): + for info in vars(MoleculeData(0)).keys(): + if save_data[conf].get(info, "xXx") == "xXx": + print( + f"WARNING: Missing data {info} from enso.json! " + "Default is added." + ) + molecule = QmJob( + save_data[conf].get("id"), + chrg=save_data[conf].get("chrg"), + uhf=save_data[conf].get("uhf"), + xtb_energy=save_data[conf].get("xtb_energy"), + xtb_energy_unbiased=save_data[conf].get("xtb_energy_unbiased"), + xtb_free_energy=save_data[conf].get("xtb_free_energy"), + rel_xtb_energy=save_data[conf].get("rel_xtb_energy"), + rel_xtb_free_energy=save_data[conf].get("rel_xtb_free_energy"), + sym=save_data[conf].get("sym"), + gi=save_data[conf].get("gi"), + removed=save_data[conf].get( + "removed", getattr(MoleculeData(0), "removed") + ), + temperature_info=save_data[conf].get( + "temperature_info", + getattr(MoleculeData(0), "temperature_info"), + ), + cheap_prescreening_sp_info=save_data[conf].get( + "cheap_prescreening_sp_info", + getattr(MoleculeData(0), "cheap_prescreening_sp_info"), + ), + cheap_prescreening_gsolv_info=save_data[conf].get( + "cheap_prescreening_gsolv_info", + getattr(MoleculeData(0), "cheap_prescreening_gsolv_info"), + ), + prescreening_sp_info=save_data[conf].get( + "prescreening_sp_info", + getattr(MoleculeData(0), "prescreening_sp_info"), + ), + lowlevel_sp_info=save_data[conf].get( + "lowlevel_sp_info", + getattr(MoleculeData(0), "lowlevel_sp_info"), + ), + highlevel_sp_info=save_data[conf].get( + "highlevel_sp_info", + getattr(MoleculeData(0), "highlevel_sp_info"), + ), + prescreening_grrho_info=save_data[conf].get( + "prescreening_grrho_info", + getattr(MoleculeData(0), "prescreening_grrho_info"), + ), + lowlevel_grrho_info=save_data[conf].get( + "lowlevel_grrho_info", + getattr(MoleculeData(0), "lowlevel_grrho_info"), + ), + lowlevel_hrrho_info=save_data[conf].get( + "lowlevel_hrrho_info", + getattr(MoleculeData(0), "lowlevel_hrrho_info"), + ), + highlevel_grrho_info=save_data[conf].get( + "highlevel_grrho_info", + getattr(MoleculeData(0), "highlevel_grrho_info"), + ), + highlevel_hrrho_info=save_data[conf].get( + "highlevel_hrrho_info", + getattr(MoleculeData(0), "highlevel_hrrho_info"), + ), + prescreening_gsolv_info=save_data[conf].get( + "prescreening_gsolv_info", + getattr(MoleculeData(0), "prescreening_gsolv_info"), + ), + lowlevel_gsolv_info=save_data[conf].get( + "lowlevel_gsolv_info", + getattr(MoleculeData(0), "lowlevel_gsolv_info"), + ), + lowlevel_gsolv_compare_info=save_data[conf].get( + "lowlevel_gsolv_compare_info", + getattr(MoleculeData(0), "lowlevel_gsolv_compare_info"), + ), + highlevel_gsolv_info=save_data[conf].get( + "highlevel_gsolv_info", + getattr(MoleculeData(0), "highlevel_gsolv_info"), + ), + optimization_info=save_data[conf].get( + "optimization_info", + getattr(MoleculeData(0), "optimization_info"), + ), + nmr_coupling_info=save_data[conf].get( + "nmr_coupling_info", + getattr(MoleculeData(0), "nmr_coupling_info"), + ), + nmr_shielding_info=save_data[conf].get( + "nmr_shielding_info", + getattr(MoleculeData(0), "nmr_shielding_info"), + ), + part_info=save_data[conf].get( + "part_info", getattr(MoleculeData(0), "part_info") + ), + comment=save_data[conf].get( + "comment", getattr(MoleculeData(0), "comment") + ), + optical_rotation_info=save_data[conf].get( + "optical_rotation_info", + getattr(MoleculeData(0), "optical_rotation_info"), + ), + ) + + # adjust to restart changeable data: + for key, value in config.restart_changeable.items(): + if value and key == "multitemp": + molecule.reset_range_info( + trange=[ + i + for i in frange( + config.trange[0], + config.trange[1], + config.trange[2], + ) + ] + ) + elif value and key == "trange": + molecule.reset_range_info( + trange=[ + i + for i in frange( + config.trange[0], + config.trange[1], + config.trange[2], + ) + ] + ) + elif value and key in ( + "part1_gfnv", + "part2_gfnv", + "part3_gfnv", + ): + exc = { + "part1_gfnv": "prescreening_grrho_info", + "part2_gfnv": "lowlevel_grrho_info", + "part3_gfnv": "highlevel_grrho_info", + } + if getattr(config, key) != getattr(previousrun, key): + # save calculated to + molecule.save_prev( + exc[key], getattr(molecule, exc[key]).get("method") + ) + # load new if available + method, _ = config.get_method_name( + "rrhoxtb", + gfn_version=getattr(config, key), + bhess=config.bhess, + ) + molecule.load_prev(exc[key], method) + elif value and key in ("smgsolv1", "smgsolv2", "smgsolv3"): + exc = { + "smgsolv1": "prescreening_gsolv_info", + "smgsolv2": "lowlevel_gsolv_info", + "smgsolv3": "highlevel_gsolv_info", + } + exc_implicit = { + "smgsolv1": "prescreening_sp_info", + "smgsolv2": "lowlevel_sp_info", + "smgsolv3": "highlevel_sp_info", + } + exc2 = { + "smgsolv1": "part1_gfnv", + "smgsolv2": "part2_gfnv", + "smgsolv3": "part3_gfnv", + } + if getattr(config, key) != getattr(previousrun, key): + # save additive gsolv calculated to + molecule.save_prev( + exc[key], getattr(molecule, exc[key]).get("method") + ) + # Gsolv for implicit solvation included in E + # save energy calculated to + molecule.save_prev( + exc_implicit[key], + getattr(molecule, exc_implicit[key]).get("method"), + ) + # load new if available + # method naming --> + if key in ("smgsolv1", "smgsolv2"): + func = config.func + basis = config.basis + elif key in ("smgsolv3",): + func = config.func3 + basis = config.basis3 + if getattr(config, key) == "smd_gsolv": + e_method, gsolv_method = config.get_method_name( + "smd_gsolv", + func=func, + basis=basis, + sm=getattr(config, key), + ) + elif getattr(config, key) in ( + "cosmors", + "cosmors-fine", + ): + e_method, gsolv_method = config.get_method_name( + "cosmors", + sm=getattr(config, key), + func=func, + basis=basis, + ) + elif getattr(config, key) in ( + "alpb_gsolv", + "gbsa_gsolv", + ): + e_method, gsolv_method = config.get_method_name( + getattr(config, key), + sm=getattr(config, key), + func=func, + basis=basis, + gfn_version=getattr(config, exc2[key]), + ) + elif getattr(config, key) in ( + "cpcm", + "cosmo", + "smd", + "dcosmors", + ): + # Gsolv for implicit solvation included in E + # need to reset gsolv (--> gsolv_method2 has to be nonsense) + e_method, gsolv_method = config.get_method_name( + "sp_implicit", + func=func, + basis=basis, + sm=getattr(config, key), + ) + else: + print("UNEXPECTED") + e_method = "" + gsolv_method = "" + molecule.load_prev(exc_implicit[key], e_method) + molecule.load_prev(exc[key], gsolv_method) + elif value and key in ( + "func_or", + "basis_or", + "freq_or", + "func_or_scf", + ): + # save calculated to + molecule.save_prev( + "optical_rotation_info", + getattr(molecule, "optical_rotation_info").get( + "method" + ), + ) + # load new if available + method, _ = config.get_method_name( + "opt-rot", + prog=config.prog, + basis=config.basis_or, + func=config.func_or, + func2=config.func_or_scf, + ) + molecule.load_prev("optical_rotation_info", method) + # finally add molecule to list + conformers.append(molecule) + # if nconf is increased add new conformers! + newconfs = [] + for i in range(1, config.nconf + 1): + considered = False + for conf in list(conformers): + if conf.id == i: + considered = True + break + if not considered: + print(f"Adding CONF{i} as new conformer!") + newconfs.append(QmJob(i)) + if newconfs: + conformers.extend(newconfs) + get_energy_from_ensemble(config.ensemblepath, config, conformers) + elif not args.checkinput: + # don't create enso.json on checkinput + # enso.json does not exist, create new conformers + print("No restart information exists and is created during this run!\n") + conformers = [] + for i in range(1, config.nconf + 1): + conformers.append(QmJob(i)) + # read energy from input_file and calculate rel_energy + get_energy_from_ensemble(config.ensemblepath, config, conformers) + ensembledata = EnsembleData() + ensembledata.filename = args.inp + ensembledata.nconfs_per_part["starting"] = config.nconf + config.write_json( + config.cwd, + [i.provide_runinfo() for i in conformers] + [ensembledata], + config.provide_runinfo(), + ) + + if (args.checkinput and not error_logical) or (args.debug and args.checkinput): + print("\nInput check is finished. The ENSO program can be executed.\n") + sys.exit(0) + if not conformers: + print("Error: No conformers are considered!\nGoing to exit!") + sys.exit(1) + # formatting information: + config.lenconfx = max([len(str(i.id)) for i in conformers]) + conformers.sort(key=lambda x: int(x.id)) + return args, config, conformers, ensembledata diff --git a/censo_qm/tm_job.py b/censo_qm/tm_job.py new file mode 100644 index 0000000..d5759c4 --- /dev/null +++ b/censo_qm/tm_job.py @@ -0,0 +1,1458 @@ +""" +Contains TmJob class for calculating TM related properties of conformers. +""" +import os +import math + +try: + from math import isclose +except ImportError: + from .utilities import isclose +import time +import subprocess +import shutil +from .cfg import CODING, ENVIRON, AU2KCAL, censo_solvent_db, external_paths +from .utilities import last_folders, print +from .qm_job import QmJob + + +class TmJob(QmJob): + """ + Perform calculations with TM + - create input with cefine + - single-point calculation + - COSMO-RS calculation + - optimization with xTB as driver + - shielding constant calculations + - coupling constant calculations + - writing of generic output for shielding and coupling constants + """ + + def __init__(self, rank, *args, **kwargs): + QmJob.__init__(self, rank, *args, **kwargs) + + def _prep_cefine(self): + """ + Run define for Turbomole calculation using comandline program cefine. + """ + if self.job["basis"] == "def2-QZVP(-gf)": + self.job["basis"] = "def2-QZVP" + removegf = True + else: + removegf = False + + # build cefine call: + minimal_call = [ + external_paths["cefinepath"], + "-func", + str(self.job["func"]), + "-bas", + str(self.job["basis"]), + "-sym", + "c1", + "-noopt", + ] + extension = { + "clear": [], + "low": ["-grid", "m3", "-scfconv", "6"], + "low+": ["-grid", "m4", "-scfconv", "6"], + "high": ["-grid", "m4", "-scfconv", "7"], + "high+": ["-grid", "m5", "-scfconv", "7"], + } + # additional = ["-fpol", "-novdw"] + + dogcp = False # uses gcp with basis and is added to controlappend + call = minimal_call + if self.job["prepinfo"]: + if isinstance(self.job["prepinfo"], list): + if self.job["prepinfo"][0] in extension.keys(): + call.extend(extension[self.job["prepinfo"][0]]) + if "DOGCP" in self.job["prepinfo"]: + _ = self.job["prepinfo"].pop(self.job["prepinfo"].index("DOGCP")) + dogcp = True + if len(self.job["prepinfo"]) > 1: + call.extend(self.job["prepinfo"][1:]) + else: + call.extend(extension["low"]) + else: + call.extend(extension["low"]) + + # kt2: + if self.job["func"] in ("kt2", "kt1"): + # used only for shielding or coupling calcs! + call.extend(["-novdw"]) + + # r2scan-3c hack + if self.job["func"] == "r2scan-3c": + if "m3" in call: + call[call.index("m3")] = "m4" + # settings which request no dispersion: + if "-novdw" in call: + requestnovdw = True + # print("FOUND NOVDW") + else: + requestnovdw = False + + # update unpaired electrons + if int(self.job["unpaired"]) > 0: + call = call + ["-uhf", str(self.job["unpaired"])] + # update charge: + if int(self.job["charge"]) != 0: + call = call + ["-chrg", str(self.job["charge"])] + # remove -fg functions from def2-QZVP basis set + if removegf: + call = call + ["-gf"] + # call cefine: + for _ in range(2): + # print(call) + tmp = subprocess.check_output( + call, + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + ) + time.sleep(0.08) + output = tmp.decode("utf-8").splitlines() + for line in output: + if "define ended abnormally" in line: + self.job["success"] = False + return + elif "define_huge" in line: + print("ERROR: define_huge: not found!") + self.job["success"] = False + return + # check if wrong functional was written by cefine + with open( + os.path.join(self.job["workdir"], "control"), + "r", + encoding=CODING, + newline=None, + ) as control: + checkup = control.readlines() + for line in checkup: + if "functional" in line: + testfunc = [self.job["func"]] + if self.job["func"] in ("b973c", "b97-3c"): + testfunc.extend(["b973c", "b97-3c"]) + if not any(func in line for func in testfunc): + print( + "Wrong functional in control file" + " in {}".format(last_folders(self.job["workdir"], 2)) + ) + self.job["success"] = False + self.job["internal_error"].append("prep-failed") + else: + self.job["success"] = True + break + if not self.job["success"]: + return + + if self.job["func"] in ("kt2", "kt1"): + # update functional to kt2 + with open( + os.path.join(self.job["workdir"], "control"), "r", newline=None + ) as inp: + tmp = inp.readlines() + with open( + os.path.join(self.job["workdir"], "control"), "w", newline=None + ) as out: + for line in tmp: + if "functional" in line: + out.write(" functional xcfun set-gga \n") + if self.job["func"] == "kt2": + out.write(" functional xcfun kt2 1.0 \n") + elif self.job["func"] == "kt1": + out.write(" functional xcfun kt1 1.0 \n") + else: + out.write(line + "\n") + time.sleep(0.02) + # modify the control file + # solvent_dcosmors = { + # "acetone": [" epsilon= 20.7", "$dcosmo_rs file=propanone_25.pot"], + # "chcl3": [" epsilon= 4.8", "$dcosmo_rs file=chcl3_25.pot"], + # "acetonitrile": [" epsilon= 36.6", "$dcosmo_rs file=acetonitrile_25.pot"], + # "ch2cl2": [" epsilon= 9.1", "$dcosmo_rs file=chcl3_25.pot"], + # "dmso": [" epsilon= 47.2", "$dcosmo_rs file=dimethylsulfoxide_25.pot"], + # "h2o": [" epsilon= 80.1", "$dcosmo_rs file=h2o_25.pot"], + # "methanol": [" epsilon= 32.7", "$dcosmo_rs file=methanol_25.pot"], + # "thf": [" epsilon= 7.6", "$dcosmo_rs file=thf_25.pot"], + # "toluene": [" epsilon= 2.4", "$dcosmo_rs file=toluene_25.pot"], + # "octanol": [" epsilon= 9.86", "$dcosmo_rs file=octanol_25.pot"], + # "woctanol": [" epsilon= 8.1", "$dcosmo_rs file=wet-octanol_25.pot"], + # "hexadecane": [" epsilon= 2.08", "$dcosmo_rs file=hexadecane_25.pot"], + # "dmf": [" epsilon= 38.3", "$dcosmo_rs file="], # not in standard TM parameter folder! + # } + if self.job["solvent"] not in ("gas", "gas-phase", None): + solvent_dcosmors = { + self.job["solvent"]: [ + f" epsilon= {censo_solvent_db[self.job['solvent']]['DC']}", + f"$dcosmo_rs file={censo_solvent_db[self.job['solvent']]['dcosmors'][1]}_25.pot", + ] + } + # handle solvents: + controlappend = [] + if self.job["sm"] == "dcosmors" and self.job["solvent"] != "gas": + try: + filename = solvent_dcosmors.get(self.job["solvent"])[1].split("=")[1] + except IndexError: + filename = "" + if not os.path.isfile( + os.path.join( + os.path.dirname( + os.path.dirname(os.path.dirname(shutil.which("ridft"))) + ), + "parameter/" + filename, + ) + ): + if os.path.isfile( + os.path.expanduser(os.path.join("~/.censo_assets/", filename)) + ): + tmp = solvent_dcosmors.get(self.job["solvent"], "") + tmp[4] = "$dcosmo_rs file=" + str( + os.path.expanduser(os.path.join("~/.censo_assets/", filename)) + ) + solvent_dcosmors[self.job["solvent"]] = tmp + else: + line = ( + "WARNING: DCOSMO-RS potential file not found!" + " Trying file without verification!" + ) + # print(line) + self.job["internal_error"].append(line) + if self.job["solvent"] != "gas" and self.job["sm"] in ("cosmo", "dcosmors"): + if solvent_dcosmors.get(self.job["solvent"], "not found!") == "not found!": + print(f"ERROR: Solvent {self.job['solvent']} is not known for cefine!") + self.job["success"] = False + self.job["internal_error"].append("prep-failed") + return + else: + controlappend.append("$cosmo") + # write epsilon (dielectric constant) + controlappend.append(solvent_dcosmors.get(self.job["solvent"], "")[0]) + if self.job["jobtype"] not in ("opt-rot", "opt-rot_sp"): + controlappend.append(" cavity closed") + controlappend.append(" use_contcav") + controlappend.append(" nspa=272") + controlappend.append(" nsph=162") + controlappend.append("$cosmo_isorad") + # write parameterfile for dcosmors + if self.job["sm"] == "dcosmors": + controlappend.append( + solvent_dcosmors.get(self.job["solvent"], "")[1] + ) + if self.job["jobtype"] in ("opt-rot", "opt-rot_sp"): + controlappend.append("$scfinstab dynpol nm") + for i in self.job["freq_or"]: + controlappend.append(f" {i}") # e.g. 589 + controlappend.append("$velocity gauge") + controlappend.append("$rpaconv 4") + + if dogcp: + if self.job["basis"] == "def2-SV(P)": + controlappend.append("$gcp dft/sv(p)") + else: + controlappend.append( + f"$gcp dft/{self.job['basis'].lower().replace('-', '')}" + ) + + # write to control file: + with open( + os.path.join(self.job["workdir"], "control"), + "r", + encoding=CODING, + newline=None, + ) as control: + tmp = control.readlines() + # check if dispersion is found + nodisp = True + replacewatm = "" + needatm = ("b97-3c", "b973c") + for line in tmp: + if "$disp" in line: + nodisp = False + if self.job["func"] in needatm: + if "$disp3 -bj -abc" not in line: + replacewatm = "$disp3 -bj -abc" + if nodisp and (self.job["func"] not in needatm) and not requestnovdw: + controlappend.append("$disp3 -bj") + elif nodisp and (self.job["func"] in needatm) and not requestnovdw: + controlappend.append("$disp3 -bj -abc") + if nodisp and requestnovdw: + replacewatm = " " + if controlappend: + with open( + os.path.join(self.job["workdir"], "control"), "w", newline=None + ) as newcontrol: + for line in tmp[:-1]: + if "$end" in line: + pass + if "$disp" in line and replacewatm: + newcontrol.write(replacewatm + "\n") + else: + newcontrol.write(line) + for line in controlappend: + newcontrol.write(line + "\n") + newcontrol.write("$end\n") + if self.job["copymos"]: + if self.job["unpaired"] > 0: + molist = ["alpha", "beta"] + else: + molist = ["mos"] + try: + for item in molist: + tmp_from = os.path.join( + "CONF" + str(self.id), self.job["copymos"], item + ) + tmp_to = os.path.join(self.job["workdir"], item) + shutil.copy(tmp_from, tmp_to) + except FileNotFoundError: + pass + for item in molist: + if ( + not os.path.isfile(os.path.join(self.job["workdir"], item)) + or os.stat(os.path.join(self.job["workdir"], item)).st_size == 0 + ): + print(f"Error: {item} is missing!") + # NMR part + if self.job["jobtype"] in ( + "couplings", + "couplings_sp", + "shieldings", + "shieldings_sp", + ): + with open( + os.path.join(self.job["workdir"], "control"), + "r", + encoding=CODING, + newline=None, + ) as control: + tmp = control.readlines() + with open( + os.path.join(self.job["workdir"], "control"), "w", newline=None + ) as newcontrol: + for line in tmp: + if "rpacor" in line: + rpacor = 10000 + try: + tmpval = float(line.strip().split()[-1]) + if tmpval > rpacor: + rpacor = tmpval + except (ValueError, IndexError): + pass + tmp[tmp.index(line)] = f"$rpacor {str(rpacor)} \n" + elif "$cosmo_isorad" in line: + tmp.pop(tmp.index(line)) + for line in tmp[:-1]: + if "$end" in line: + pass + else: + newcontrol.write(line) + newcontrol.write("$ncoupling\n") + newcontrol.write(" simple\n") + # fc sd pso dso nofcsdcross + newcontrol.write(" thr=0.0\n") + nucsel1 = "$nucsel " + nucsel2 = "$nucsel2 " + if self.job["h_active"]: + nucsel1 = nucsel1 + '"h" ' + nucsel2 = nucsel2 + '"h" ' + if self.job["c_active"]: + nucsel1 = nucsel1 + '"c" ' + nucsel2 = nucsel2 + '"c" ' + if self.job["f_active"]: + nucsel1 = nucsel1 + '"f" ' + nucsel2 = nucsel2 + '"f" ' + if self.job["si_active"]: + nucsel1 = nucsel1 + '"si" ' + nucsel2 = nucsel2 + '"si" ' + if self.job["p_active"]: + nucsel1 = nucsel1 + '"p" ' + nucsel2 = nucsel2 + '"p" ' + if any( + [ + self.job["h_active"], + self.job["c_active"], + self.job["c_active"], + self.job["si_active"], + self.job["p_active"], + ] + ): + newcontrol.write(nucsel1 + "\n") + newcontrol.write(nucsel2 + "\n") + else: + # don't write nucsel, every shielding, coupling will be calculated + pass + newcontrol.write("$rpaconv 8\n") + newcontrol.write("$end") + time.sleep(0.15) + + # ****************************end cefine************************************ + + def _sp(self, silent=False): + """ + Turbomole single-point calculation, needs previous cefine run + """ + if not self.job["onlyread"]: + if not silent: + print( + f"Running single-point in {last_folders(self.job['workdir'], 2):18}" + ) + with open( + os.path.join(self.job["workdir"], "ridft.out"), "w", newline=None + ) as outputfile: + subprocess.call( + ["ridft"], + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + stdout=outputfile, + env=ENVIRON, + ) + time.sleep(0.02) + # check if scf is converged: + if os.path.isfile(os.path.join(self.job["workdir"], "ridft.out")): + with open( + os.path.join(self.job["workdir"], "ridft.out"), + "r", + encoding=CODING, + newline=None, + ) as inp: + stor = inp.readlines() + if " ENERGY CONVERGED !\n" not in stor: + print( + "ERROR: scf in {:18} not converged!".format( + last_folders(self.job["workdir"], 2) + ) + ) + self.job["success"] = False + self.job["energy"] = 0.0 + self.job["energy2"] = 0.0 + return + else: + print( + "WARNING: {} doesn't exist!".format( + os.path.join(self.job["workdir"], "ridft.out") + ) + ) + self.job["success"] = False + self.job["energy"] = 0.0 + self.job["energy2"] = 0.0 + return + if os.path.isfile(os.path.join(self.job["workdir"], "energy")): + with open( + os.path.join(self.job["workdir"], "energy"), + "r", + encoding=CODING, + newline=None, + ) as energy: + storage = energy.readlines() + try: + self.job["energy"] = float(storage[-2].split()[1]) + self.job["success"] = True + except ValueError: + print( + "ERROR while converting energy in: {:18}".format( + last_folders(self.job["workdir"], 2) + ) + ) + if self.job["jobtype"] == "sp_implicit": + self.job["energy2"] = 0.0 + else: + self.job["energy"] = 0.0 + self.job["success"] = False + + # ****************************end _sp*************************************** + + def _cosmors(self): + """ + Run COSMO-RS from within censo. + calculates directly in the workdir. folder COSMO has to be created + beforehand. + energy --> gas phase scf energy + energy2 --> gsolv contribution + """ + if not self.job["onlyread"]: + print( + f"Running COSMO-RS calculation in " + f"{last_folders(self.job['workdir'], 3):18}" + ) + # parametrisation + if self.job["cosmorsparam"] == "fine": + pass + elif self.job["cosmorsparam"] == "normal": + if "FINE" in self.job["cosmorssetup"]: + ## normal cosmors + tmp = self.job["cosmorssetup"] + tmp = tmp.replace("_FINE", "") + self.job["cosmorssetup"] = tmp.replace("BP_TZVPD", "BP_TZVP") + # run two single-points: + if self.job["copymos"]: + if self.job["unpaired"] > 0: + molist = ["alpha", "beta"] + else: + molist = ["mos"] + try: + for item in molist: + tmp_from = os.path.join( + "CONF" + str(self.id), self.job["copymos"], item + ) + tmp_to = os.path.join(self.job["workdir"], item) + shutil.copy(tmp_from, tmp_to) + except FileNotFoundError: + pass + for item in molist: + if ( + not os.path.isfile(os.path.join(self.job["workdir"], item)) + or os.stat(os.path.join(self.job["workdir"], item)).st_size == 0 + ): + print(f"Error: {item} is missing!") + # first single-point in gas phase! + tmp_solvent = self.job["solvent"] + tmp_sm = self.job["solvent"] + self.job["solvent"] = "gas" + self.job["sm"] = "gas-phase" + self._prep_cefine() + if not self.job["success"]: + return + # running single-point in gas phase + self._sp(silent=True) + self.job["solvent"] = tmp_solvent + self.job["sm"] = tmp_sm + if not self.job["success"]: + print( + "ERROR: gas-phase single-point calculation failed in: " + f"{last_folders(self.job['workdir'], 3):18}" + ) + return + with open( + os.path.join(self.job["workdir"], "out.energy"), "w", newline=None + ) as out: + out.write(str(self.job["energy"]) + "\n") + gas_phase_energy = self.job["energy"] + self.job["energy"] = 0.0 + # running single-point in ideal conductor! + with open( + os.path.join(self.job["workdir"], "control"), + "r", + encoding=CODING, + newline=None, + ) as inp: + tmp = inp.readlines() + with open( + os.path.join(self.job["workdir"], "control"), "w", newline=None + ) as out: + for line in tmp[:-1]: + out.write(line + "\n") + if self.job["cosmorsparam"] == "normal": + # normal + out.write("$cosmo \n") + out.write(" epsilon=infinity \n") + out.write(" use_contcav \n") + out.write(" cavity closed \n") + out.write(" nspa=272 \n") + out.write(" nsph=162 \n") + out.write("$cosmo_out file=out.cosmo \n") + out.write("$end \n") + else: + # fine + out.write("$cosmo \n") + out.write(" epsilon=infinity \n") + out.write(" use_contcav \n") + out.write(" cavity closed \n") + out.write(" nspa=272 \n") + out.write(" nsph=162 \n") + out.write("$cosmo_out file=out.cosmo \n") + # out.write("$cosmo_isorad \n") + # out.write("$cosmo_isodens \n") + out.write("$end \n") + self.job["success"] = False + self._sp(silent=True) + if not self.job["success"]: + print( + "ERROR: single-point in ideal conductor calculation failed in: " + f"{last_folders(self.job['workdir'], 3):18}" + ) + return + # info from .ensorc # replacement for cosmothermrc + # fdir=/software/cluster/COSMOthermX16/COSMOtherm/DATABASE-COSMO/BP-TZVP-COSMO autoc + # cosmors_solv = { + # "acetone": ["f = propanone.cosmo "], + # "h2o": ["f = h2o.cosmo "], + # "chcl3": ["f = chcl3.cosmo "], + # "ch2cl2": ["f = ch2cl2.cosmo "], + # "acetonitrile": ["f = acetonitrile_c.cosmo "], + # "dmso": ["f = dimethylsulfoxide.cosmo "], + # "methanol": ["f = methanol.cosmo "], + # "thf": ["f = thf.cosmo "], + # "toluene": ["f = toluene_c0.cosmo "], + # "octanol": ["f = 1-octanol "], + # "hexadecane": ["f = n-hexadecane "], + # "woctanol": ["f = h2o.cosmo ", "f = 1-octanol "], + # } + + if self.job["solvent"] not in ("gas", "gas-phase", None): + if self.job["solvent"] == "woctanol": + cosmors_solv = { + "woctanol": ["f = h2o.cosmo ", "f = 1-octanol.cosmo "] + } + else: + tmp_1 = os.path.splitext( + censo_solvent_db[self.job["solvent"]]["cosmors"][1] + )[0] + filename = f"{tmp_1}.cosmo" + cosmors_solv = {f"{self.job['solvent']}": [f"f = {filename} "]} + + mixture = {"woctanol": ["0.27 0.73"]} + if self.job["cosmorsparam"] == "fine": + solv_data = os.path.join( + os.path.split(self.job["cosmorssetup"].split()[5].strip('"'))[0], + "DATABASE-COSMO/BP-TZVPD-FINE", + ) + else: + solv_data = os.path.join( + os.path.split(self.job["cosmorssetup"].split()[5].strip('"'))[0], + "DATABASE-COSMO/BP-TZVP-COSMO", + ) + # test = ['ctd = BP_TZVP_C30_1601.ctd cdir = "/software/cluster/COSMOthermX16/COSMOtherm/CTDATA-FILES"'] + with open( + os.path.join(self.job["workdir"], "cosmotherm.inp"), "w", newline=None + ) as out: + out.write(self.job["cosmorssetup"] + "\n") + # write from ensorc + out.write("EFILE VPFILE \n") + out.write("!!\n") # for jobname in cosmors + if len(cosmors_solv[self.job["solvent"]]) > 1: + mix = mixture[self.job["solvent"]][0] + for line in cosmors_solv[self.job["solvent"]]: + out.write(line + "fdir=" + solv_data + " autoc \n") + elif len(cosmors_solv[self.job["solvent"]]) == 1: + mix = "1.0 0.0" + out.write( + cosmors_solv[self.job["solvent"]][0] + + " fdir=" + + solv_data + + " autoc \n" + ) + out.write("f = out.cosmo \n") + + if self.job["trange"]: + tmp1 = self.job["trange"] + tinside = False + for temp in tmp1: + if isclose(self.job["temperature"], temp, abs_tol=0.6): + tinside = True + if not tinside: + tmp1.append(self.job["temperature"]) + else: + tmp1 = [self.job["temperature"]] + tlist = [str("{:.2f}".format(i - 273.15)) for i in tmp1] + # henry = "henry xh={ "+mix+" } tc=25.0 Gsolv" + for i in tlist: + henry = "henry xh={ " + mix + " } tc=" + i + " Gsolv" + out.write(henry + "\n") + time.sleep(0.01) + # running COSMOtherm + with open( + os.path.join(self.job["workdir"], "cosmotherm.out"), "w", newline=None + ) as outputfile: + subprocess.call( + ["cosmotherm", "cosmotherm.inp"], + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + stdout=outputfile, + env=ENVIRON, + ) + time.sleep(0.1) + # get T and Gsolv for version > cosmothermX16 + ## volumework: + R = 1.987203585e-03 # kcal/(mol*K) + videal = ( + 24.789561955 / 298.15 + ) # molar volume for ideal gas at 298.15 K 100.0 kPa + gsolvt = {} + try: + with open( + os.path.join(self.job["workdir"], "cosmotherm.tab"), + "r", + encoding=CODING, + newline=None, + ) as inp: + stor = inp.readlines() + for line in stor: + vwork = 0 + if "T=" in line: + T = float(line.split()[5]) + vwork = R * T * math.log(videal * T) + elif " out " in line: + gsolvt[T] = float(line.split()[-1]) / AU2KCAL + vwork / AU2KCAL + self.job["erange1"] = gsolvt + except (FileNotFoundError, ValueError): + print( + "ERROR: cosmotherm.tab was not written, this error can be " + "due to a missing licensefile information, or wrong path " + "to the COSMO-RS Database." + ) + self.job["energy"] = 0.0 + self.job["energy2"] = 0.0 + self.job["erange1"] = {} + self.job["success"] = False + return + except IndexError: + print("ERROR: IndexERROR in cosmotherm.tab!") + self.job["energy"] = 0.0 + self.job["energy2"] = 0.0 + self.job["erange1"] = {} + self.job["success"] = False + return + # cosmothermrd + if ( + os.stat(os.path.join(self.job["workdir"], "cosmotherm.tab")).st_size + == 0 + ): + print( + "ERROR: cosmotherm.tab was not written, this error can be " + "due to a missing licensefile information, or wrong path " + "to the COSMO-RS Database." + ) + self.job["energy"] = 0.0 + self.job["energy2"] = 0.0 + self.job["erange1"] = {} + self.job["success"] = False + gsolv_out = 0.0 + for temp in gsolvt.keys(): + if isclose(self.job["temperature"], temp, abs_tol=0.6): + gsolv_out = gsolvt[temp] + temp = float(self.job["temperature"]) + ## volumework: + R = 1.987203585e-03 # kcal/(mol*K) + videal = ( + 24.789561955 / 298.15 + ) # molar volume for ideal gas at 298.15 K 100.0 kPa + volwork = R * temp * math.log(videal * temp) + + with open( + os.path.join(os.path.dirname(self.job["workdir"]), "cosmors.out"), + "w", + newline=None, + ) as out: + out.write( + "This is cosmothermrd (python version in ENSO) (SG,FB,SAW, 06/18)\n" + ) + out.write("final thermochemical solvation properties in kcal/mol\n") + out.write( + "----------------------------------------------------------\n" + ) + out.write( + " Gsolv({} K)= {:10.3f}\n".format( + temp, gsolv_out * AU2KCAL - volwork + ) + ) + out.write(" VWork({} K)= {:10.3f}\n".format(temp, volwork)) + out.write( + " Gsolv+VWork({} K)= {:10.3f}\n".format( + temp, (gsolv_out * AU2KCAL) # volwork already included! + ) + ) + time.sleep(0.01) + self.job["energy"] = gas_phase_energy + self.job["energy2"] = gsolv_out # VOLWORK INCLUDED + self.job["erange1"][self.job["temperature"]] = gsolv_out # VOLWORK INCLUDED + self.job["success"] = True + + # ********************************end _cosmors*********************************** + def _xtbopt(self): + """ + Turbomole optimization using the ANCOPT optimizer implemented in xTB + """ + error_logical = False + if self.job["fullopt"]: + output = "opt-part2.out" + else: + output = "opt-part1.out" + if not self.job["onlyread"]: + print(f"Running optimization in {last_folders(self.job['workdir'], 2):18}") + files = [ + "xtbrestart", + "xtbtopo.mol", + "xcontrol-inp", + "wbo", + "charges", + "gfnff_topo", + ] + for file in files: + if os.path.isfile(os.path.join(self.job["workdir"], file)): + os.remove(os.path.join(self.job["workdir"], file)) + + callargs = [ + self.job["xtb_driver_path"], + "coord", + "--opt", + self.job["optlevel"], + "--tm", + ] + with open( + os.path.join(self.job["workdir"], "opt.inp"), "w", newline=None + ) as out: + out.write("$opt \n") + if ( + self.job["optcycles"] is not None + and float(self.job["optcycles"]) > 0 + ): + out.write(f"maxcycle={str(self.job['optcycles'])} \n") + out.write(f"microcycle={str(self.job['optcycles'])} \n") + out.write("average conv=true \n") + out.write(f"hlow={self.job.get('hlow', 0.01)} \n") + out.write("s6=30.00 \n") + # remove unnecessary sp/gradient call in xTB + out.write("engine=lbfgs\n") + out.write("$end \n") + callargs.append("-I") + callargs.append("opt.inp") + time.sleep(0.02) + with open( + os.path.join(self.job["workdir"], output), "w", newline=None + ) as outputfile: + returncode = subprocess.call( + callargs, + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + stdout=outputfile, + env=ENVIRON, + ) + if returncode != 0: + error_logical = True + print( + "ERROR: optimization in {:18} not converged".format( + last_folders(self.job["workdir"], 2) + ) + ) + time.sleep(0.02) + # check if converged: + if os.path.isfile(os.path.join(self.job["workdir"], output)): + with open( + os.path.join(self.job["workdir"], output), + "r", + encoding=CODING, + newline=None, + ) as inp: + stor = inp.readlines() + for line in stor: + if ( + "external code error" in line + or "|grad| > 500, something is totally wrong!" in line + or "abnormal termination of xtb" in line + ): + print( + "ERROR: optimization in {:18} not converged".format( + last_folders(self.job["workdir"], 2) + ) + ) + error_logical = True + break + elif " FAILED TO CONVERGE GEOMETRY " in line: + self.job["cycles"] += int(line.split()[7]) + self.job["converged"] = False + elif "*** GEOMETRY OPTIMIZATION CONVERGED AFTER " in line: + self.job["cycles"] += int(line.split()[5]) + self.job["converged"] = True + with open( + os.path.join(self.job["workdir"], output), + "r", + encoding=CODING, + newline=None, + ) as inp: + for line in inp: + if "av. E: " in line: + # self.job["ecyc"].append(float(line.split("Eh")[0].split()[-1])) + self.job["ecyc"].append(float(line.split("->")[-1])) + if " :: gradient norm " in line: + self.job["grad_norm"] = float(line.split()[3]) + else: + print( + "WARNING: {} doesn't exist!".format( + os.path.join(self.job["workdir"], output) + ) + ) + error_logical = True + if not error_logical: + try: + self.job["energy"] = self.job["ecyc"][-1] + self.job["success"] = True + except: + error_logical = True + if error_logical: + self.job["energy"] = 0.0 + self.job["success"] = False + self.job["converged"] = False + self.job["ecyc"] = [] + self.job["grad_norm"] = 10.0 + + ##### VERSION BEFORE AVERAGING KEEP FOR NOW + # def _xtbopt(self): + # """ + # Turbomole optimization using the ANCOPT optimizer implemented in xTB + # """ + # error_logical = False + # if self.job["fullopt"]: + # output = "opt-part2.out" + # else: + # output = "opt-part1.out" + # if not self.job["onlyread"]: + # print(f"Running optimization in {last_folders(self.job['workdir'], 2):18}") + # files = [ + # "xtbrestart", + # "xtbtopo.mol", + # "xcontrol-inp", + # "wbo", + # "charges", + # "gfnff_topo", + # ] + # for file in files: + # if os.path.isfile(os.path.join(self.job["workdir"], file)): + # os.remove(os.path.join(self.job["workdir"], file)) + + # callargs = [ + # self.job["xtb_driver_path"], + # "coord", + # "--opt", + # self.job["optlevel"], + # "--tm", + # ] + # with open( + # os.path.join(self.job["workdir"], "opt.inp"), "w", newline=None + # ) as out: + # out.write("$opt \n") + # if self.job["optcycles"] is not None and float(self.job["optcycles"]) > 0: + # out.write(f"maxcycle={str(self.job['optcycles'])} \n") + # out.write(f"microcycle={str(self.job['optcycles'])} \n") + # out.write("average conv=true \n") + # out.write(f"hlow={self.job.get('hlow', 0.01)} \n") + # out.write("s6=30.00 \n") + # # remove unnecessary sp/gradient call in xTB + # out.write("engine=lbfgs\n") + # out.write("$end \n") + # callargs.append("-I") + # callargs.append("opt.inp") + # time.sleep(0.02) + # with open( + # os.path.join(self.job["workdir"], output), "w", newline=None + # ) as outputfile: + # returncode = subprocess.call( + # callargs, + # shell=False, + # stdin=None, + # stderr=subprocess.STDOUT, + # universal_newlines=False, + # cwd=self.job["workdir"], + # stdout=outputfile, + # env=ENVIRON, + # ) + # if returncode != 0: + # error_logical = True + # print( + # "ERROR: optimization in {:18} not converged".format( + # last_folders(self.job["workdir"], 2) + # ) + # ) + # time.sleep(0.02) + # # check if converged: + # if os.path.isfile(os.path.join(self.job["workdir"], output)): + # with open( + # os.path.join(self.job["workdir"], output), + # "r", + # encoding=CODING, + # newline=None, + # ) as inp: + # stor = inp.readlines() + # for line in stor: + # if ( + # "external code error" in line + # or "|grad| > 500, something is totally wrong!" in line + # or "abnormal termination of xtb" in line + # ): + # print( + # "ERROR: optimization in {:18} not converged".format( + # last_folders(self.job["workdir"], 2) + # ) + # ) + # error_logical = True + # break + # elif " FAILED TO CONVERGE GEOMETRY " in line: + # self.job["cycles"] += int(line.split()[7]) + # # self.job['cycles'] = int(line.split()[7]) + self.job['cycles'] + # self.job["converged"] = False + # elif "*** GEOMETRY OPTIMIZATION CONVERGED AFTER " in line: + # self.job["cycles"] += int(line.split()[5]) + # # self.job['cycles'] = int(line.split()[5]) + self.job['cycles'] + # self.job["converged"] = True + # with open( + # os.path.join(self.job["workdir"], output), + # "r", + # encoding=CODING, + # newline=None, + # ) as inp: + # for line in inp: + # if "total energy :" in line and not "gain" in line: + # self.job["ecyc"].append(float(line.split("Eh")[0].split()[-1])) + # else: + # print( + # "WARNING: {} doesn't exist!".format( + # os.path.join(self.job["workdir"], output) + # ) + # ) + # error_logical = True + # if os.path.isfile(os.path.join(self.job["workdir"], "energy")): + # with open( + # os.path.join(self.job["workdir"], "energy"), + # "r", + # encoding=CODING, + # newline=None, + # ) as energy: + # storage = energy.readlines() + # try: + # self.job["energy"] = float(storage[-2].split()[1]) + # self.job["success"] = True + # except ValueError: + # print( + # "ERROR while converting energy in {:18}".format( + # last_folders(self.job["workdir"], 2) + # ) + # ) + # else: + # error_logical = True + # if error_logical: + # self.job["energy"] = 0.0 + # self.job["success"] = False + # self.job["converged"] = False + # self.job["ecyc"] = [] + + # ********************************end _xtbopt*********************************** + + def _opt(self): + """ + Turbomole optimization using JOBEX, with adapted thresholds! + """ + pass + + def _nmr_coupling(self): + """ + Turbomole coupling constant calculation. + """ + print( + f"Running couplings calculation in {last_folders(self.job['workdir'], 2)}" + ) + # escf doesnot allow for mgrids! + with open( + os.path.join(self.job["workdir"], "control"), "r", newline=None + ) as inp: + tmp = inp.readlines() + with open( + os.path.join(self.job["workdir"], "control"), "w", newline=None + ) as out: + for line in tmp: + if "gridsize" in line: + out.write(f" gridsize {5} \n") + else: + out.write(line + "\n") + + with open( + os.path.join(self.job["workdir"], "escf.out"), "w", newline=None + ) as outputfile: + subprocess.call( + [self.job["progpath"], "-smpcpus", str(self.job["omp"])], + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + stdout=outputfile, + env=ENVIRON, + ) + time.sleep(0.02) + # check for convergence + with open( + os.path.join(self.job["workdir"], "escf.out"), + "r", + encoding=CODING, + newline=None, + ) as inp: + stor = inp.readlines() + if " **** escf : all done ****\n" in stor: + self.job["success"] = True + else: + print( + "ERROR: coupling calculation failed in {:18}".format( + last_folders(self.job["workdir"], 1) + ) + ) + self.job["success"] = False + + def _nmr_shielding(self): + """ + Turbomole shielding constant calculation. + """ + print( + "Running shielding calculation in {}".format( + last_folders(self.job["workdir"], 2) + ) + ) + # update grid to m5! + with open( + os.path.join(self.job["workdir"], "control"), "r", newline=None + ) as inp: + tmp = inp.readlines() + with open( + os.path.join(self.job["workdir"], "control"), "w", newline=None + ) as out: + for line in tmp: + if "gridsize" in line: + out.write(f" gridsize {'m5'} \n") + if "$disp" in line and self.job["func"] in ("kt2", "kt1"): + pass + else: + out.write(line + "\n") + time.sleep(0.02) + + with open( + os.path.join(self.job["workdir"], "mpshift.out"), "w", newline=None + ) as outputfile: + subprocess.call( + [self.job["progpath"], "-smpcpus", str(self.job["omp"])], + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + stdout=outputfile, + env=ENVIRON, + ) + time.sleep(0.02) + # check if shift calculation is converged: + with open( + os.path.join(self.job["workdir"], "mpshift.out"), + "r", + encoding=CODING, + newline=None, + ) as inp: + stor = inp.readlines() + found = False + for line in stor: + if " **** mpshift : all done ****" in line: + self.job["success"] = True + found = True + if not found: + print( + "ERROR: shielding calculation failed in {:18}".format( + last_folders(self.job["workdir"], 2) + ) + ) + self.job["success"] = False + + def _genericoutput(self): + """ + Read shielding and coupling constants and write them to plain output. + """ + fnameshield = "mpshift.out" + atom = [] + sigma = [] + try: + with open( + os.path.join(self.job["workdir"], fnameshield), + "r", + encoding=CODING, + newline=None, + ) as inp: + data = inp.readlines() + for line in data: + if ">>>>> DFT MAGNETIC SHIELDINGS <<<<<" in line: + start = data.index(line) + for line in data[start:]: + if "ATOM" in line: + splitted = line.split() + atom.append(int(splitted[2])) + sigma.append(float(splitted[4])) + except FileNotFoundError: + print( + "Missing file: {} in {}, Shielding constants are not written.".format( + fnameshield, last_folders(self.job["workdir"], 2) + ) + ) + self.job["success"] = False + except ValueError: + print("ERROR: ValueError in generic_output, nmrprop.dat can be flawed !") + self.job["success"] = False + self.job["success"] = True + fnamecoupl = "escf.out" + atom1 = [] + atom2 = [] + jab = [] + try: + with open( + os.path.join(self.job["workdir"], fnamecoupl), + "r", + encoding=CODING, + newline=None, + ) as inp: + data = inp.readlines() + for line in data: + if "Nuclear coupling constants" in line: + start = int(data.index(line)) + 3 + if "-----------------------------------" in line: + end = int(data.index(line)) + for line in data[start:end]: + if len(line.split()) in (6, 7): + splitted = line.split() + atom1.append(int(splitted[1])) + atom2.append(int(splitted[4].split(":")[0])) + jab.append(float(splitted[5])) + except FileNotFoundError: + print( + "Missing file: {} in {}, Coupling constants are not written.".format( + fnamecoupl, last_folders(self.job["workdir"], 2) + ) + ) + self.job["success"] = False + except ValueError: + print("ERROR: ValueError in generic_output, nmrprop.dat can be flawed") + self.job["success"] = False + self.job["success"] = True + with open( + os.path.join(self.job["workdir"], "nmrprop.dat"), "w", newline=None + ) as out: + s = sorted(zip(atom, sigma)) + atom, sigma = map(list, zip(*s)) + self.shieldings = dict(zip(atom, sigma)) + for i in range(len(atom)): + out.write("{:{digits}} {}\n".format(atom[i], sigma[i], digits=4)) + for i in range(self.job["nat"] - len(atom)): + out.write("\n") + for i in range(len(atom1)): + out.write( + "{:{digits}} {:{digits}} {}\n".format( + atom1[i], atom2[i], jab[i], digits=4 + ) + ) + time.sleep(0.02) + + def _optrot(self, silent=False): + """ + calculate optical rotation + """ + if not self.job["onlyread"]: + with open( + os.path.join(self.job["workdir"], "control"), "r", newline=None + ) as inp: + tmp = inp.readlines() + with open( + os.path.join(self.job["workdir"], "control"), "w", newline=None + ) as out: + for line in tmp: + if "functional" in line: + out.write(f" functional {self.job['func2']} \n") + elif "$disp" in line: + pass + else: + out.write(line + "\n") + + if not silent: + print( + f"Running optical-rotation calculation in {last_folders(self.job['workdir'], 2):18}" + ) + files = ["dipl_a", "dipole_a", "rhs_a"] + for file in files: + if os.path.isfile(os.path.join(self.job["workdir"], file)): + os.remove(os.path.join(self.job["workdir"], file)) + with open( + os.path.join(self.job["workdir"], "escf.out"), "w", newline=None + ) as outputfile: + subprocess.call( + [self.job["progpath"]], + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=self.job["workdir"], + stdout=outputfile, + env=ENVIRON, + ) + time.sleep(0.02) + # check if scf is converged: + if os.path.isfile(os.path.join(self.job["workdir"], "escf.out")): + with open( + os.path.join(self.job["workdir"], "escf.out"), + "r", + encoding=CODING, + newline=None, + ) as inp: + stor = inp.readlines() + # -------------testnew + escf_ok = False + length_rep = False # (length representation) + velocity_rep = False # (velocity representation) + do_read_length = False + do_read_velocity = True + hybriddfa = ( + "pbe0", + "pw6b95", + "wb97x-d3", + "cam-b3lyp", + "b3-lyp", + "pbeh-3c", + "m06x", + "bh-lyp", + "tpssh", + ) + if self.job["func2"] in hybriddfa: + do_read_length = True + dum = 0 + frequencies = self.job["freq_or"] + frequencies.sort(reverse=True) + for line in stor: + if "escf ended normally" in line: + escf_ok = True + if " Frequency / nm: " in line: + freq = float(line.strip().split()[-1]) + if isclose(frequencies[dum], freq, abs_tol=0.6): + freq = float(frequencies[dum]) + else: + print("Can't find freq in nm!") + dum += 1 + if " specific rotation [alpha] in deg*[dm(g/cc)]^(-1)" in line: + if not length_rep: + length_rep = True + velocity_rep = False + elif length_rep: + velocity_rep = True + length_rep = False + if velocity_rep and do_read_velocity: + self.job["energy"] = 0.0 + self.job["success"] = True + self.job["energy2"] = 0.0 + self.job["erange1"][freq] = float(line.split("(-1)")[-1]) + elif length_rep and do_read_length: + print("Using length representation.") + self.job["energy"] = 0.0 + self.job["success"] = True + self.job["energy2"] = 0.0 + self.job["erange1"][freq] = float(line.split("(-1)")[-1]) + for freq in self.job["freq_or"]: + if freq not in self.job["erange1"].keys(): + escf_ok = False + if not escf_ok: + print( + "ERROR: in escf.out {:18} not converged!".format( + last_folders(self.job["workdir"], 2) + ) + ) + self.job["success"] = False + self.job["energy"] = 0.0 + self.job["energy2"] = 0.0 + self.job["erange1"] = {} + else: + print( + "WARNING: {} doesn't exist!".format( + os.path.join(self.job["workdir"], "escf.out") + ) + ) + self.job["success"] = False + self.job["energy"] = 0.0 + self.job["energy2"] = 0.0 + self.job["erange1"] = {} + + def execute(self): + """ + Choose what to execute for the jobtype + """ + if self.job["jobtype"] == "rrhoxtb": + self._xtbrrho() + elif self.job["jobtype"] == "xtb_sp": + self._xtb_sp() + elif self.job["jobtype"] == "prep": + self._prep_cefine() + elif self.job["jobtype"] in ("sp", "sp_implicit"): + if self.job["prepinfo"]: + # do cefine first + self._prep_cefine() + if not self.job["success"]: + return + self._sp() + elif self.job["jobtype"] == "cosmors": + self._cosmors() + elif self.job["jobtype"] == "xtbopt": + self._xtbopt() + elif self.job["jobtype"] == "genericout": + self._genericoutput() + elif self.job["jobtype"] in ("couplings", "couplings_sp"): + if self.job["prepinfo"]: + self._prep_cefine() + if not self.job["success"]: + return + if self.job["jobtype"] == "couplings_sp": + self._sp(silent=False) + if not self.job["success"]: + return + else: + try: + tmp_from = os.path.join(self.job["workdir"], "mos") + tmp_to = os.path.join(self.job["workdir"], "mos_J") + shutil.copy(tmp_from, tmp_to) + except FileNotFoundError: + pass + self._nmr_coupling() + elif self.job["jobtype"] in ("shieldings", "shieldings_sp"): + if self.job["prepinfo"]: + self._prep_cefine() + # print('performed cefine') + if not self.job["success"]: + return + if self.job["copymos"]: + # use mos as starting mos if basisJ == basisS but + # funcJ != funcS + try: + tmp_from = os.path.join(self.job["workdir"], "mos_J") + tmp_to = os.path.join(self.job["workdir"], "mos") + shutil.copy(tmp_from, tmp_to) + except FileNotFoundError: + pass + # print("copied mos") + if self.job["jobtype"] == "shieldings_sp": + self._sp(silent=False) + # print("ran sp") + if not self.job["success"]: + return + # print("running shieldings") + self._nmr_shielding() + elif self.job["jobtype"] in ("gbsa_gsolv", "alpb_gsolv"): + if self.job["prepinfo"]: + tmp_solvent = self.job["solvent"] + self.job["solvent"] = "gas" + self._prep_cefine() + if not self.job["success"]: + return + self._sp() + if not self.job["success"]: + return + self.job["solvent"] = tmp_solvent + self._xtb_gsolv() + elif self.job["jobtype"] in ("opt-rot", "opt-rot_sp"): + if self.job["prepinfo"]: + self._prep_cefine() + if not self.job["success"]: + return + if self.job["jobtype"] == "opt-rot_sp": + self._sp() + if not self.job["success"]: + return + self._optrot() + else: + print(f"JOBTYPE {self.job['jobtype']} UNKNOWN!") diff --git a/censo_qm/utilities.py b/censo_qm/utilities.py new file mode 100755 index 0000000..b013799 --- /dev/null +++ b/censo_qm/utilities.py @@ -0,0 +1,3095 @@ +""" +Utility functions which are used in the CENSO modules. From creating folders to +printout routines. +""" +import os +import sys +import shutil +import math +import hashlib +import time +import subprocess +from copy import deepcopy +from builtins import print as print_orig +from .cfg import ENVIRON, CODING, AU2J, AU2KCAL, BOHR2ANG, KB + + +def print(*args, **kwargs): + """ + patch print to always flush + """ + sep = " " + end = "\n" + file = None + flush = True + for key, value in kwargs.items(): + if key == "sep": + sep = value + elif key == "end": + end = value + elif key == "file": + file = value + elif key == "flush": + key = value + print_orig(*args, sep=sep, end=end, file=file, flush=flush) + + +def frange(start, end, step=1): + """ + range with floats + """ + try: + start = float(start) + end = float(end) + step = float(step) + except (ValueError, TypeError): + raise + if start > end: + tmp = start + start = end + end = tmp + count = 0 + while True: + temp = float(start + count * step) + if temp >= end: + break + yield temp + count += 1 + + +def mkdir_p(path): + """ + create mkdir -p like behaviour + """ + try: + os.makedirs(path) + except OSError as e: + if not os.path.isdir(path): + raise e + + +def print_block(strlist, width=80): + """Print all elements of strlist in block mode + e.g. within 80 characters then newline + - width [int] width of block + """ + length = 0 + try: + maxlen = max([len(str(x)) for x in strlist]) + except (ValueError, TypeError): + maxlen = 12 + for item in strlist: + length += maxlen + 2 + if length <= width: + if not item == strlist[-1]: # works only if item only once in list! + print("{:>{digits}}, ".format(str(item), digits=maxlen), end="") + else: + print("{:>{digits}}".format(str(item), digits=maxlen), end="") + else: + print("{:>{digits}}".format(str(item), digits=maxlen)) + length = 0 + if length != 0: + print("\n") + + +def t2x(path, writexyz=False, outfile="original.xyz"): + """convert TURBOMOLE coord file to xyz data and/or write *.xyz ouput + + - path [abs. path] does not need to include the filename coord + - writexyz [bool] default=False, directly write to outfile + - outfile [filename] default = 'original.xyz' filename of xyz file which + is written into the same directory as + returns: + - coordxyz --> list of strings including atom x y z information + - number of atoms + """ + if not os.path.basename(path) == "coord": + path = os.path.join(path, "coord") + with open(path, "r", encoding=CODING, newline=None) as f: + coord = f.readlines() + x = [] + y = [] + z = [] + atom = [] + for line in coord[1:]: + if "$" in line: # stop at $end ... + break + x.append(float(line.split()[0]) * BOHR2ANG) + y.append(float(line.split()[1]) * BOHR2ANG) + z.append(float(line.split()[2]) * BOHR2ANG) + atom.append(str(line.split()[3].lower())) + # natoms = int(len(coord[1:-1])) # unused + coordxyz = [] + for i in range(len(x)): + coordxyz.append( + "{:3} {: .10f} {: .10f} {: .10f}".format( + atom[i][0].upper() + atom[i][1:], x[i], y[i], z[i] + ) + ) + if writexyz: + with open( + os.path.join(os.path.split(path)[0], outfile), + "w", + encoding=CODING, + newline=None, + ) as out: + out.write(str(len(coordxyz)) + "\n\n") + for line in coordxyz: + out.write(line + "\n") + return coordxyz, int(len(coordxyz)) + + +def x2t(path, infile="inp.xyz"): + """convert file inp.xyz to TURBOMOLE coord file""" + if ".xyz" not in os.path.basename(path): + path = os.path.join(path, infile) + with open(path, "r", encoding=CODING, newline=None) as f: + xyz = f.readlines() + atom = [] + x = [] + y = [] + z = [] + for line in xyz[2:]: + atom.append(str(line.split()[0].lower())) + x.append(float(line.split()[1]) / BOHR2ANG) + y.append(float(line.split()[2]) / BOHR2ANG) + z.append(float(line.split()[3]) / BOHR2ANG) + coordxyz = [] + for i in range(len(x)): + coordxyz.append(f"{x[i]: .14f} {y[i]: .14f} {z[i]: .14f} {atom[i]}") + with open( + os.path.join(os.path.split(path)[0], "coord"), "w", newline=None + ) as coord: + coord.write("$coord\n") + for line in coordxyz: + coord.write(line + "\n") + coord.write("$end\n") + + +def write_trj( + results, cwd, outpath, optfolder, nat, attribute, overwrite=False, *args, **kwargs +): + """ + Write trajectory (multiple xyz geometries) to file. + """ + if overwrite and os.path.isfile(outpath): + os.remove(outpath) + for key, value in kwargs.items(): + if key == "rrho": + rrho = value + elif key == "energy": + energy = value + try: + rrho + except NameError: + rrho = None + try: + energy + except NameError: + energy = None + try: + with open(outpath, "a", encoding=CODING, newline=None) as out: + for conf in results: + conf_xyz, nat = t2x(os.path.join(cwd, "CONF" + str(conf.id), optfolder)) + ### coordinates in xyz + out.write(" {}\n".format(nat)) + xtbfree = conf.calc_free_energy( + e=energy, solv=None, rrho=rrho, out=True + ) + if xtbfree is not None: + xtbfree = f"{xtbfree:20.8f}" + out.write( + f"G(CENSO)= {getattr(conf, attribute):20.8f}" + f" G(xTB)= {xtbfree}" + f" !CONF{str(conf.id)}\n" + ) + for line in conf_xyz: + out.write(line + "\n") + except (FileExistsError, ValueError): + print("Could not write trajectory: {}.".format(last_folders(outpath, 1))) + + +def check_for_float(line): + """ Go through line and check for float, return first float""" + elements = line.strip().split() + value = None + for element in elements: + try: + value = float(element) + found = True + except ValueError: + found = False + value = None + if found: + break + return value + + +def last_folders(path, number=1): + """ + Return string of last folder or last two folders of path, depending on number + """ + if number not in (1, 2, 3): + number = 1 + if number == 1: + folder = os.path.basename(path) + if number == 2: + folder = os.path.join( + os.path.basename(os.path.dirname(path)), os.path.basename(path) + ) + if number == 3: + basename = os.path.basename(path) + dirname = os.path.basename(os.path.dirname(path)) + predirname = os.path.basename(os.path.split(os.path.split(path)[0])[0]) + folder = os.path.join(predirname, dirname, basename) + return folder + + +def get_energy_from_ensemble(path, config, conformers): + """ + Get energies from the ensemble inputfile and assign xtb_energy and + rel_xtb_energy + """ + with open(path, "r", encoding=CODING, newline=None) as inp: + data = inp.readlines() + if config.maxconf * (config.nat + 2) > len(data): + print( + f"ERROR: Either the number of conformers ({config.nconf}) " + f"or the number of atoms ({config.nat}) is wrong!" + ) + # calc energy and rel energy: + e = {} + conformers.sort(key=lambda x: int(x.id)) + for conf in conformers: + e[conf.id] = check_for_float(data[(conf.id - 1) * (config.nat + 2) + 1]) + try: + lowest = float(min([i for i in e.values() if i is not None])) + except (ValueError, TypeError): + print("WARNING: Can't calculate rel_xtb_energy!") + return + for conf in conformers: + try: + conf.xtb_energy = e[conf.id] + conf.rel_xtb_energy = (e[conf.id] - lowest) * AU2KCAL + # print(f"CONF{conf.id} {conf.xtb_energy} {conf.rel_xtb_energy}") + except (ValueError, TypeError) as e: + print(e) + return conformers + + +def ensemble2coord(config, foldername, conflist, store_confs, save_errors): + """ + read ensemble file: e.g. 'crest_conformers.xyz' and write coord files into + designated folders + + - path [abs path] to ensemble file + - nat [int] number of atoms in molecule + - nconf [int] number of considered conformers + - cwd [path] path to current working directory + - foldername [str] name of folder into which the coord file is to be written + - conflist [list with conf object] all conf objects + + returns list with conformer objects + """ + if not os.path.isfile(config.ensemblepath): + print(f"ERROR: File {os.path.basename(config.ensemblepath)} does not exist!") + with open(config.ensemblepath, "r", encoding=CODING, newline=None) as inp: + data = inp.readlines() + if config.maxconf * (config.nat + 2) > len(data): + print( + f"ERROR: Either the number of conformers ({config.nconf}) " + f"or the number of atoms ({config.nat}) is wrong!" + ) + for conf in conflist: + i = conf.id + atom = [] + x = [] + y = [] + z = [] + start = (i - 1) * (config.nat + 2) + 2 + end = i * (config.nat + 2) + for line in data[start:end]: + atom.append(str(line.split()[0].lower())) + x.append(float(line.split()[1]) / BOHR2ANG) + y.append(float(line.split()[2]) / BOHR2ANG) + z.append(float(line.split()[3]) / BOHR2ANG) + coordxyz = [] + for j in range(len(x)): + coordxyz.append(f"{x[j]: .14f} {y[j]: .14f} {z[j]: .14f} {atom[j]}") + outpath = os.path.join(config.cwd, "CONF" + str(conf.id), foldername, "coord") + if not os.path.isfile(outpath): + # print(f"Write new coord file in {last_folders(outpath)}") + with open(outpath, "w", newline=None) as coord: + coord.write("$coord\n") + for line in coordxyz: + coord.write(line + "\n") + coord.write("$end") + return conflist, store_confs, save_errors + + +def splitting(item): + """ + Used in move_recursively. + + """ + try: + return int(item.rsplit(".", 1)[1]) + except ValueError: + return 0 + + +def move_recursively(path, filename): + """ + Check if file or file.x exists and move them to file.x+1 ignores e.g. + file.save + """ + files = [ + f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) + ] # list of all files in directory + newfiles = [] # list of all files in directory that contain filename and '.' + for item in files: + if filename + "." in item: + newfiles.append(item) + newfiles.sort(key=splitting, reverse=True) + for item in newfiles: + try: + data = item.rsplit(".", 1) # splits only at last '.' + int(data[1]) + except ValueError: + continue + tmp_from = os.path.join(path, item) + newfilename = str(data[0]) + "." + str(int(data[1]) + 1) + tmp_to = os.path.join(path, newfilename) + # print("Backing up {} to {}.".format(item, newfilename)) + shutil.move(tmp_from, tmp_to) + + if filename in files: + print("Backing up {} to {}.".format(filename, filename + ".1")) + shutil.move(os.path.join(path, filename), os.path.join(path, filename + ".1")) + + +def calc_boltzmannweights(confs, property, T): + """ + Calculate Boltzmannweights: + - confs [list] list with conformer objects + - property [str] e.g. free_energy of conformer + - T [float] temperature at which the Boltzmann weight has to be evaluated + + returns confs + """ + if len(confs) == 1: + confs[0].bm_weight = 1.0 + return confs + try: + T = float(T) + except ValueError: + T = 298.15 # K + print(f"Temperature can not be converted and is therfore set to T = {T} K.") + if T == 0: + T += 0.00001 # avoid division by zero + try: + minfree = min( + [ + getattr(conf, property, None) + for conf in confs + if getattr(conf, property, None) is not None + ] + ) + except ValueError: + print("ERROR: Boltzmann weight can not be calculated!") + bsum = 0.0 + for item in confs: + bsum += getattr(item, "gi", 1.0) * math.exp( + -((item.free_energy - minfree) * AU2J) / (KB * T) + ) + for item in confs: + item.bm_weight = ( + getattr(item, "gi", 1.0) + * math.exp(-((item.free_energy - minfree) * AU2J) / (KB * T)) + / bsum + ) + return confs + + +def new_folders(cwd, conflist, foldername, save_errors, store_confs): + """ + create folders for all conformers in conflist + """ + + for conf in conflist: + tmp_dir = os.path.join(cwd, "CONF" + str(conf.id), foldername) + try: + mkdir_p(tmp_dir) + except Exception as e: + print(e) + if not os.path.isdir(tmp_dir): + print(f"ERROR: Could not create folder for CONF{conf.id}!") + print(f"CONF{conf.id} is removed, because IO failed!") + save_errors.append(f"CONF{conf.id} was removed, " "because IO failed!") + store_confs.append(conflist.pop(conflist.index(conf))) + print("Constructed folders!") + return save_errors, store_confs, conflist + + +def check_for_folder(path, conflist, foldername, debug=False): + """ + Check if folders exist (of conformers calculated in previous run) + """ + error_logical = False + for i in conflist: + tmp_dir = os.path.join(path, "CONF" + str(i), foldername) + if not os.path.exists(tmp_dir): + print( + f"ERROR: directory of {last_folders(tmp_dir, 2)} does not exist, although " + "it was calculated before!" + ) + error_logical = True + if error_logical and not debug: + print("One or multiple directories are missing.\n") + return error_logical + + +def do_md5(path): + """ + Calculate md5 of file to identifly if restart happend on the same file! + Input is buffered into smaller sizes to ease on memory consumption. + """ + BUF_SIZE = 65536 + md5 = hashlib.md5() + if os.path.isfile(path): + with open(path, "rb") as f: + while True: + data = f.read(BUF_SIZE) + if not data: + break + md5.update(data) + return md5.hexdigest() + else: + raise FileNotFoundError + + +def rank_simple(vector): + """ + needed to rank vectors + """ + return sorted(range(len(vector)), key=vector.__getitem__) + + +def rankdata(a): + """ + rank vectors like in numpy + """ + n = len(a) + ivec = rank_simple(a) + svec = [a[rank] for rank in ivec] + sumranks = 0 + dupcount = 0 + newarray = [0] * n + for i in range(n): + sumranks += i + dupcount += 1 + if i == n - 1 or svec[i] != svec[i + 1]: + averank = sumranks / float(dupcount) + 1 + for j in range(i - dupcount + 1, i + 1): + newarray[ivec[j]] = averank + sumranks = 0 + dupcount = 0 + return newarray + + +def pearson(A, B): + """ + Calculate pearson correlation coefficient + """ + if len(A) != len(B): + print("ERROR in PEARSON lists are not of equal length!") + n = float(len(A)) + muA = sum(A) / n + muB = sum(B) / n + diffA = map(lambda x: x - muA, A) + diffB = map(lambda x: x - muB, B) + stdA = math.sqrt((1 / (n - 1)) * sum([d * d for d in diffA])) + stdB = math.sqrt((1 / (n - 1)) * sum([d * d for d in diffB])) + try: + return (sum([A[i] * B[i] for i in range(int(n))]) - n * muA * muB) / ( + (n - 1) * stdA * stdB + ) + except ZeroDivisionError as e: + print("WARNING: ", e) + return 0.0 + + +def spearman(A, B): + """ + Calculate spearman correlation coefficient + """ + return pearson(rankdata(A), rankdata(B)) + + +def printout( + outputpath, + columncall, + columnheader, + columndescription, + columnformat, + calculate, + minfree, + columndescription2=[], +): + """ + Create printout which is printed to stdout and file. + """ + calculate.sort(key=lambda x: int(x.id)) + if not any( + [ + len(i) == len(columncall) + for i in (columnheader, columndescription, columnformat) + ] + ): + print("Lists of uneqal length!") + collength = [] + columnheaderprint = [] + columndescriptionprint = [] + columndescriptionprint2 = [] + if not columndescription2: + columndescription2 = ["" for _ in range(len(columncall))] + # split on "[" eg. COSMORS[B97-3c/def2-TZVP] + + for i in range(len(columndescription)): + if "[" in columndescription[i] and columndescription[i] not in ( + "[Eh]", + "[kcal/mol]", + "[a.u.]", + ): + columndescription2[i] = "[" + str(columndescription[i]).split("[")[1] + columndescription[i] = str(columndescription[i]).split("[")[0] + try: + for j in range(len(columncall)): + if columnformat[j]: + collength.append( + max( + [ + len(str(f"{i:{columnformat[j][0]}.{columnformat[j][1]}f}")) + for i in map(columncall[j], calculate) + ] + ) + ) + else: + collength.append(max([len(i) for i in map(columncall[j], calculate)])) + if ( + max( + len(i) + for i in [ + columndescription[j], + columnheader[j], + columndescription2[j], + ] + ) + > collength[j] + ): + collength[j] = max( + len(i) + for i in [ + columndescription[j], + columnheader[j], + columndescription2[j], + ] + ) + except (ValueError, TypeError) as e: + print(f"\n\nERRROR {e}") + for j in range(len(columncall)): + collength.append(12) + + for i in range(len(columncall)): + columnheaderprint.append(f"{columnheader[i]:>{collength[i]}}") + columndescriptionprint.append(f"{columndescription[i]:>{collength[i]}}") + if columndescription2: + columndescriptionprint2.append(f"{columndescription2[i]:>{collength[i]}}") + with open(outputpath, "w", newline=None) as out: + line = " ".join(columnheaderprint) + print(line) + out.write(line + "\n") + line = " ".join(columndescriptionprint) + print(line) + out.write(line + "\n") + if columndescription2: + line = " ".join(columndescriptionprint2) + print(line) + out.write(line + "\n") + for conf in calculate: + columncallprint = [] + for i in range(len(columncall)): + if columnformat[i]: + columncallprint.append( + f"{columncall[i](conf):{collength[i]}.{columnformat[i][1]}f}" + ) + else: + columncallprint.append(f"{columncall[i](conf):{collength[i]}}") + if conf.free_energy != minfree: + line = " ".join(columncallprint) + print(line) + out.write(line + "\n") + else: + line = " ".join(columncallprint + [f" <------"]) + print(line) + out.write(line + "\n") + + +def crest_routine(config, conformers, func, store_confs, prev_calculated=[]): + """ + check if two conformers are rotamers of each other, + this check is always performed, but removing conformers depends on crestcheck + returns conformers + returns store_confs + returns prev_calculated + """ + dirn = "conformer_rotamer_check" ### directory name + fn = "conformers.xyz" ### file name + + print("\nChecking for identical structures in ensemble with CREGEN!\n") + # create folder for comparison + if not os.path.isdir(os.path.join(config.cwd, dirn)): + mkdir_p(os.path.join(config.cwd, dirn)) + # delete conformers.xyz file if it already exists + if os.path.isfile(os.path.join(config.cwd, dirn, fn)): + os.remove(os.path.join(config.cwd, dirn, fn)) + # delete coord file if exists + if os.path.isfile(os.path.join(config.cwd, dirn, "coord")): + os.remove(os.path.join(config.cwd, dirn, "coord")) + + allconfs = deepcopy(conformers) + allconfs.extend(deepcopy(prev_calculated)) + + ### sort conformers according to energy of optimization + allconfs.sort(key=lambda conf: float(getattr(conf, "optimization_info")["energy"])) + # write coord: + try: + shutil.copy( + os.path.join(config.cwd, "CONF" + str(allconfs[0].id), func, "coord"), + os.path.join(config.cwd, dirn, "coord"), + ) + except Exception as e: + print(f"ERROR: {e}") + + # write conformers.xyz file + with open( + os.path.join(config.cwd, dirn, fn), "w", encoding=CODING, newline=None + ) as out: + for conf in allconfs: + conf_xyz, nat = t2x(os.path.join(config.cwd, "CONF" + str(conf.id), func)) + out.write(" {}\n".format(nat)) ### number of atoms + out.write( + "{:20.8f} !{}\n".format( + getattr(conf, "optimization_info")["energy"], "CONF" + str(conf.id) + ) + ) + for line in conf_xyz: + out.write(line + "\n") + for conf in allconfs: + conf_xyz, nat = t2x(os.path.join(config.cwd, "CONF" + str(conf.id), func)) + out.write(" {}\n".format(nat)) ### number of atoms + out.write( + "{:20.8f} !{}\n".format( + getattr(conf, "optimization_info")["energy"], "CONF" + str(conf.id) + ) + ) + for line in conf_xyz: + out.write(line + "\n") + time.sleep(0.01) + + crestcall = [ + config.external_paths["crestpath"], + "coord", + "-cregen", + fn, + "-ethr", + "0.15", + "-rthr", + "0.175", + "-bthr", + "0.03", + "-enso", + ] + + with open( + os.path.join(config.cwd, dirn, "crest.out"), "w", newline=None, encoding=CODING + ) as outputfile: + subprocess.call( + crestcall, + shell=False, + stdin=None, + stderr=subprocess.STDOUT, + universal_newlines=False, + cwd=os.path.join(config.cwd, dirn), + stdout=outputfile, + env=ENVIRON, + ) + time.sleep(0.05) + try: + with open( + os.path.join(config.cwd, dirn, "enso.tags"), + "r", + encoding=CODING, + newline=None, + ) as inp: + store = inp.readlines() + except (Exception) as e: + print(f"ERROR: {e}") + print("ERROR: output file (enso.tags) of CREST routine does not exist!") + keep = [] + if config.crestcheck: + try: + for line in store: + keep.append(line.split()[1][1:]) + for conf in list(conformers): + if "CONF" + str(conf.id) not in keep: + conf.optimization_info["info"] = "calculated" + conf.optimization_info["cregen_sort"] = "removed" + print( + f"!!!! Removing CONF{conf.id} because it is sorted " + "out by CREGEN." + ) + store_confs.append(conformers.pop(conformers.index(conf))) + for conf in list(prev_calculated): + if "CONF" + str(conf.id) not in keep: + conf.optimization_info["info"] = "calculated" + conf.optimization_info["cregen_sort"] = "removed" + print( + f"!!!! Removing CONF{conf.id} because it is sorted " + "out by CREGEN." + ) + store_confs.append(prev_calculated.pop(prev_calculated.index(conf))) + except (NameError, Exception) as e: + print(f"ERROR: {e}") + return conformers, prev_calculated, store_confs + + +def format_line(key, value, options, optionlength=70, dist_to_options=30): + """ + used in print_parameters + """ + # limit printout of possibilities + if len(str(options)) > optionlength: + length = 0 + reduced = [] + for item in options: + length += len(item) + 2 + if length < optionlength: + reduced.append(item) + reduced.append("...") + options = reduced + length = 0 + line = "{}: {:{digits}} # {} \n".format( + key, str(value), options, digits=dist_to_options - len(key) + ) + return line + + +def check_tasks(results, check=False, thresh=0.25): + """ + Check if too many tasks failed and exit if so! + """ + # Check if preparation failed too often: + counter = 0 + for item in results: + if not item.job["success"]: + counter += 1 + try: + fail_rate = float(counter) / float(len(results)) + except ZeroDivisionError: + print(f"ERROR: Too many calculations failed!" "\nGoing to exit!") + sys.exit(1) + if fail_rate >= thresh and check: + print( + f"ERROR: {fail_rate*100} % of the calculations failed!" "\nGoing to exit!" + ) + sys.exit(1) + elif fail_rate >= thresh: + print(f"WARNING: {fail_rate*100} % of the calculations failed!") + + +def isclose(value_a, value_b, rel_tol=1e-9, abs_tol=0.0): + """ + Replace function if not available from math module (exists since python 3.5) + """ + return abs(value_a - value_b) <= max( + rel_tol * max(abs(value_a), abs(value_b)), abs_tol + ) + + +def calc_std_dev(data): + """ + Calculate standard deviation + """ + n = len(data) + mean = sum(data) / n + variance = sum([(x - mean) ** 2 for x in data]) / (n - 1) + std_dev = math.sqrt(variance) + return std_dev + + +def calc_weighted_std_dev(data, weights=[]): + """ + Calculate standard deviation + """ + n = len(data) + if n == 0: + return 0.0 + if not weights or len(weights) < n: + weights = [1.0 for _ in range(n)] + w_mean = sum([data[i] * weights[i] for i in range(n)]) / sum(weights) + m = 0 + for i in weights: + if i != 0.0: + m += 1 + variance = sum([weights[i] * (data[i] - w_mean) ** 2 for i in range(n)]) / ( + (m - 1) * sum(weights) / m + ) + std_dev = math.sqrt(variance) + return std_dev + + +def write_anmrrc(config): + """ Write file .anmrrc with information processed by ANMR """ + h_tm_shieldings = { + "TMS": { + "pbeh-3c": { + "tpss": { + "gas": 32.0512048, + "acetone": 32.03971003333333, + "chcl3": 32.041133316666674, + "acetonitrile": 32.03617056666667, + "ch2cl2": 32.04777176666666, + "dmso": 32.039681316666666, + "h2o": 32.036860174999994, + "methanol": 32.04573335, + "thf": 32.04154705833333, + "toluene": 32.02829061666666, + }, + "pbe0": { + "gas": 31.820450258333327, + "acetone": 31.801199816666667, + "chcl3": 31.807363400000003, + "acetonitrile": 31.797744033333334, + "ch2cl2": 31.815502166666665, + "dmso": 31.797286500000002, + "h2o": 31.801018416666665, + "methanol": 31.809920125, + "thf": 31.802681225, + "toluene": 31.790892416666665, + }, + "pbeh-3c": { + "gas": 32.32369869999999, + "acetone": 32.30552229166667, + "chcl3": 32.30850654166667, + "acetonitrile": 32.3015773, + "ch2cl2": 32.31627083333333, + "dmso": 32.303862816666665, + "h2o": 32.30345545833333, + "methanol": 32.3130819, + "thf": 32.306951225, + "toluene": 32.29417180833333, + }, + }, + "b97-3c": { + "tpss": { + "gas": 32.099305599999994, + "acetone": 32.07685382499999, + "chcl3": 32.078372550000005, + "acetonitrile": 32.067920741666676, + "ch2cl2": 32.0876576, + "dmso": 32.07713496666667, + "h2o": 32.07222951666666, + "methanol": 32.085467083333334, + "thf": 32.07950451666667, + "toluene": 32.06162065, + }, + "pbe0": { + "gas": 31.869211950000004, + "acetone": 31.83879448333333, + "chcl3": 31.845031441666663, + "acetonitrile": 31.829924375, + "ch2cl2": 31.855811533333338, + "dmso": 31.835178675000005, + "h2o": 31.83680665833334, + "methanol": 31.850090208333338, + "thf": 31.841073758333337, + "toluene": 31.824697675, + }, + "pbeh-3c": { + "gas": 32.37107341666667, + "acetone": 32.341934458333334, + "chcl3": 32.34503841666666, + "acetonitrile": 32.332714675, + "ch2cl2": 32.35537393333334, + "dmso": 32.34058045833333, + "h2o": 32.338073200000004, + "methanol": 32.35207416666667, + "thf": 32.34418670833334, + "toluene": 32.32693729166667, + }, + }, + "tpss": { + "tpss": { + "gas": 31.86774000000001, + "acetone": 31.848927016666664, + "chcl3": 31.851003891666664, + "acetonitrile": 31.843538541666664, + "ch2cl2": 31.860415141666664, + "dmso": 31.849057266666673, + "h2o": 31.844762508333332, + "methanol": 31.857667625, + "thf": 31.851878716666665, + "toluene": 31.833541825, + }, + "pbe0": { + "gas": 31.636587116666664, + "acetone": 31.60924136666667, + "chcl3": 31.616506625, + "acetonitrile": 31.604173191666664, + "ch2cl2": 31.62743169166667, + "dmso": 31.604975658333334, + "h2o": 31.607992624999994, + "methanol": 31.620864658333335, + "thf": 31.611675816666665, + "toluene": 31.59546233333333, + }, + "pbeh-3c": { + "gas": 32.14311896666666, + "acetone": 32.11710325, + "chcl3": 32.12106585833333, + "acetonitrile": 32.11156126666667, + "ch2cl2": 32.1315459, + "dmso": 32.114840533333336, + "h2o": 32.11376850833333, + "methanol": 32.127508733333336, + "thf": 32.11950190833333, + "toluene": 32.1023676, + }, + }, + } + } + h_orca_shieldings = { + "TMS": { + "pbeh-3c": { + "tpss": { + "gas": 32.17000000000001, + "acetone": 32.09433333333334, + "chcl3": 32.10649999999999, + "acetonitrile": 32.09366666666667, + "ch2cl2": 32.099, + "dmso": 32.09466666666666, + "h2o": 32.10341666666666, + "methanol": 32.09250000000001, + "thf": 32.10183333333333, + "toluene": 32.122833333333325, + }, + "pbe0": { + "gas": 31.819000000000003, + "acetone": 31.732666666666663, + "chcl3": 31.747000000000003, + "acetonitrile": 31.73166666666667, + "ch2cl2": 31.738416666666666, + "dmso": 31.732666666666663, + "h2o": 31.741500000000002, + "methanol": 31.73066666666666, + "thf": 31.74116666666667, + "toluene": 31.765999999999995, + }, + "dsd-blyp": { + "gas": 31.91416666666667, + "acetone": 31.83541666666667, + "chcl3": 31.84766666666667, + "acetonitrile": 31.834666666666667, + "ch2cl2": 31.839916666666667, + "dmso": 31.835583333333332, + "h2o": 31.844166666666666, + "methanol": 31.833166666666667, + "thf": 31.842583333333334, + "toluene": 31.86475, + }, + "wb97x": { + "gas": 31.952, + "acetone": 31.867499999999996, + "chcl3": 31.880999999999997, + "acetonitrile": 31.866666666666664, + "ch2cl2": 31.872666666666664, + "dmso": 31.86758333333333, + "h2o": 31.876083333333337, + "methanol": 31.86533333333333, + "thf": 31.8755, + "toluene": 31.89966666666666, + }, + "pbeh-3c": { + "gas": 32.324999999999996, + "acetone": 32.23866666666667, + "chcl3": 32.25299999999999, + "acetonitrile": 32.23783333333333, + "ch2cl2": 32.24466666666667, + "dmso": 32.23866666666667, + "h2o": 32.24733333333333, + "methanol": 32.23666666666667, + "thf": 32.24733333333333, + "toluene": 32.272, + }, + "kt2": { + "gas": 31.817999999999998, + "acetone": 31.73233333333333, + "chcl3": 31.746333333333336, + "acetonitrile": 31.73133333333333, + "ch2cl2": 31.737666666666666, + "dmso": 31.73233333333333, + "h2o": 31.740666666666666, + "methanol": 31.73, + "thf": 31.740499999999994, + "toluene": 31.765666666666664, + }, + }, + "b97-3c": { + "tpss": { + "gas": 32.21800000000001, + "acetone": 32.140166666666666, + "chcl3": 32.152166666666666, + "acetonitrile": 32.140499999999996, + "ch2cl2": 32.145, + "dmso": 32.14183333333333, + "h2o": 32.175000000000004, + "methanol": 32.13766666666667, + "thf": 32.148, + "toluene": 32.168833333333325, + }, + "pbe0": { + "gas": 31.868, + "acetone": 31.778999999999996, + "chcl3": 31.792583333333337, + "acetonitrile": 31.778666666666663, + "ch2cl2": 31.784333333333336, + "dmso": 31.78016666666667, + "h2o": 31.815166666666666, + "methanol": 31.77633333333333, + "thf": 31.787500000000005, + "toluene": 31.812, + }, + "dsd-blyp": { + "gas": 31.962999999999997, + "acetone": 31.881250000000005, + "chcl3": 31.89325, + "acetonitrile": 31.881583333333335, + "ch2cl2": 31.886000000000006, + "dmso": 31.882583333333333, + "h2o": 31.916833333333333, + "methanol": 31.878500000000003, + "thf": 31.889, + "toluene": 31.910750000000004, + }, + "wb97x": { + "gas": 32.00091666666666, + "acetone": 31.913416666666663, + "chcl3": 31.9265, + "acetonitrile": 31.9135, + "ch2cl2": 31.918499999999995, + "dmso": 31.914666666666665, + "h2o": 31.94883333333333, + "methanol": 31.910666666666668, + "thf": 31.921500000000005, + "toluene": 31.94516666666667, + }, + "pbeh-3c": { + "gas": 32.373, + "acetone": 32.28366666666667, + "chcl3": 32.29716666666666, + "acetonitrile": 32.28333333333333, + "ch2cl2": 32.288666666666664, + "dmso": 32.284499999999994, + "h2o": 32.317166666666665, + "methanol": 32.28066666666667, + "thf": 32.29183333333334, + "toluene": 32.31616666666667, + }, + "kt2": { + "gas": 31.868, + "acetone": 31.778666666666663, + "chcl3": 31.792500000000004, + "acetonitrile": 31.778666666666663, + "ch2cl2": 31.784333333333336, + "dmso": 31.78033333333333, + "h2o": 31.794583333333332, + "methanol": 31.77633333333333, + "thf": 31.787500000000005, + "toluene": 31.812, + }, + }, + "tpss": { + "tpss": { + "gas": 31.97300000000001, + "acetone": 31.898, + "chcl3": 31.909500000000005, + "acetonitrile": 31.897833333333338, + "ch2cl2": 31.902666666666665, + "dmso": 31.898999999999997, + "h2o": 31.910666666666668, + "methanol": 31.89566666666667, + "thf": 31.90516666666667, + "toluene": 31.925, + }, + "pbe0": { + "gas": 31.625, + "acetone": 31.537166666666668, + "chcl3": 31.550499999999996, + "acetonitrile": 31.536666666666665, + "ch2cl2": 31.542500000000004, + "dmso": 31.537666666666667, + "h2o": 31.549500000000005, + "methanol": 31.53458333333334, + "thf": 31.545499999999993, + "toluene": 31.569, + }, + "dsd-blyp": { + "gas": 31.718000000000004, + "acetone": 31.639666666666667, + "chcl3": 31.651416666666663, + "acetonitrile": 31.639499999999998, + "ch2cl2": 31.644083333333338, + "dmso": 31.640416666666667, + "h2o": 31.65216666666667, + "methanol": 31.636916666666664, + "thf": 31.64683333333333, + "toluene": 31.667833333333334, + }, + "wb97x": { + "gas": 31.757, + "acetone": 31.672250000000002, + "chcl3": 31.68516666666667, + "acetonitrile": 31.67166666666667, + "ch2cl2": 31.6775, + "dmso": 31.67266666666666, + "h2o": 31.68466666666666, + "methanol": 31.66966666666667, + "thf": 31.680166666666665, + "toluene": 31.703, + }, + "pbeh-3c": { + "gas": 32.13400000000001, + "acetone": 32.047333333333334, + "chcl3": 32.06066666666667, + "acetonitrile": 32.04666666666666, + "ch2cl2": 32.05266666666666, + "dmso": 32.047666666666665, + "h2o": 32.059, + "methanol": 32.044666666666664, + "thf": 32.05566666666666, + "toluene": 32.079, + }, + "kt2": { + "gas": 31.622999999999994, + "acetone": 31.536666666666665, + "chcl3": 31.55, + "acetonitrile": 31.5365, + "ch2cl2": 31.54183333333333, + "dmso": 31.537666666666667, + "h2o": 31.548666666666666, + "methanol": 31.533833333333334, + "thf": 31.544833333333333, + "toluene": 31.56866666666667, + }, + }, + } + } + c_tm_shieldings = { + "TMS": { + "pbeh-3c": { + "tpss": { + "gas": 186.6465687, + "acetone": 187.27903107499998, + "chcl3": 187.238498325, + "acetonitrile": 187.372512775, + "ch2cl2": 187.0771589, + "dmso": 187.243299225, + "h2o": 187.37157565, + "methanol": 187.10988087500002, + "thf": 187.19458635, + "toluene": 187.48276635, + }, + "pbe0": { + "gas": 188.859355325, + "acetone": 189.6196798, + "chcl3": 189.4971041, + "acetonitrile": 189.698041075, + "ch2cl2": 189.318608125, + "dmso": 189.68253637499998, + "h2o": 189.65553119999998, + "methanol": 189.409198575, + "thf": 189.55889105, + "toluene": 189.776394325, + }, + "pbeh-3c": { + "gas": 198.41611147499998, + "acetone": 199.13367970000002, + "chcl3": 199.054179875, + "acetonitrile": 199.250248325, + "ch2cl2": 198.845265825, + "dmso": 199.185056825, + "h2o": 199.2289907, + "methanol": 198.917945675, + "thf": 199.076003325, + "toluene": 199.3931504, + }, + }, + "b97-3c": { + "tpss": { + "gas": 186.97419324999998, + "acetone": 187.496073025, + "chcl3": 187.45393565, + "acetonitrile": 187.554538075, + "ch2cl2": 187.31238564999998, + "dmso": 187.469466275, + "h2o": 187.57139320000002, + "methanol": 187.344972675, + "thf": 187.42200885, + "toluene": 187.671731225, + }, + "pbe0": { + "gas": 189.169130675, + "acetone": 189.816064175, + "chcl3": 189.69082477499998, + "acetonitrile": 189.860330875, + "ch2cl2": 189.532330975, + "dmso": 189.88587445000002, + "h2o": 189.8368566, + "methanol": 189.62332455, + "thf": 189.76569125, + "toluene": 189.94371412499999, + }, + "pbeh-3c": { + "gas": 198.7168509, + "acetone": 199.3308802, + "chcl3": 199.25125382500002, + "acetonitrile": 199.41320919999998, + "ch2cl2": 199.06108425, + "dmso": 199.390014125, + "h2o": 199.41478467500002, + "methanol": 199.13192775, + "thf": 199.28161922500001, + "toluene": 199.562540575, + }, + }, + "tpss": { + "tpss": { + "gas": 185.410099625, + "acetone": 185.99193982499997, + "chcl3": 185.949648475, + "acetonitrile": 186.0799505, + "ch2cl2": 185.80363820000002, + "dmso": 185.97415155, + "h2o": 186.07484635, + "methanol": 185.839592875, + "thf": 185.9190184, + "toluene": 186.17204557500003, + }, + "pbe0": { + "gas": 187.626469575, + "acetone": 188.34549135, + "chcl3": 188.212218325, + "acetonitrile": 188.413268225, + "ch2cl2": 188.04820440000003, + "dmso": 188.42875420000001, + "h2o": 188.3724699, + "methanol": 188.14698049999998, + "thf": 188.2963985, + "toluene": 188.46803717499998, + }, + "pbeh-3c": { + "gas": 197.27823677499998, + "acetone": 197.953274625, + "chcl3": 197.871683925, + "acetonitrile": 198.0615831, + "ch2cl2": 197.6764831, + "dmso": 198.014841225, + "h2o": 198.048432475, + "methanol": 197.75143105, + "thf": 197.905333025, + "toluene": 198.186480775, + }, + }, + } + } + c_orca_shieldings = { + "TMS": { + "pbeh-3c": { + "tpss": { + "gas": 188.604, + "acetone": 189.7395, + "chcl3": 189.5435, + "acetonitrile": 189.77, + "ch2cl2": 189.6625, + "dmso": 189.8015, + "h2o": 189.8495, + "methanol": 189.77, + "thf": 189.647, + "toluene": 189.30400000000003, + }, + "pbe0": { + "gas": 188.867, + "acetone": 190.265, + "chcl3": 190.02224999999999, + "acetonitrile": 190.298, + "ch2cl2": 190.16649999999998, + "dmso": 190.33175, + "h2o": 190.38799999999998, + "methanol": 190.29875, + "thf": 190.1445, + "toluene": 189.73375, + }, + "dsd-blyp": { + "gas": 191.37099999999998, + "acetone": 192.606, + "chcl3": 192.385, + "acetonitrile": 192.63599999999997, + "ch2cl2": 192.51575000000003, + "dmso": 192.66625000000002, + "h2o": 192.7205, + "methanol": 192.63524999999998, + "thf": 192.4955, + "toluene": 192.12275, + }, + "wb97x": { + "gas": 190.36075, + "acetone": 191.689, + "chcl3": 191.453, + "acetonitrile": 191.72175000000001, + "ch2cl2": 191.5935, + "dmso": 191.753, + "h2o": 191.8085, + "methanol": 191.72150000000002, + "thf": 191.57150000000001, + "toluene": 191.17225, + }, + "pbeh-3c": { + "gas": 198.458, + "acetone": 199.905, + "chcl3": 199.649, + "acetonitrile": 199.94, + "ch2cl2": 199.8025, + "dmso": 199.9715, + "h2o": 200.0265, + "methanol": 199.93900, + "thf": 199.7775, + "toluene": 199.3395, + }, + "kt2": { + "gas": 190.719, + "acetone": 191.988, + "chcl3": 191.7645, + "acetonitrile": 192.019, + "ch2cl2": 191.8965, + "dmso": 192.05150000000003, + "h2o": 192.1055, + "methanol": 192.02, + "thf": 191.8775, + "toluene": 191.4905, + }, + }, + "b97-3c": { + "tpss": { + "gas": 188.908, + "acetone": 190.0265, + "chcl3": 189.83749999999998, + "acetonitrile": 190.062, + "ch2cl2": 189.954, + "dmso": 190.103, + "h2o": 190.07774999999998, + "methanol": 190.0595, + "thf": 189.9445, + "toluene": 189.614, + }, + "pbe0": { + "gas": 189.18025, + "acetone": 190.57025000000002, + "chcl3": 190.33075, + "acetonitrile": 190.60525, + "ch2cl2": 190.47, + "dmso": 190.65175, + "h2o": 190.59925000000004, + "methanol": 190.60775, + "thf": 190.456, + "toluene": 190.058, + }, + "dsd-blyp": { + "gas": 191.66199999999998, + "acetone": 192.88025, + "chcl3": 192.66174999999998, + "acetonitrile": 192.915, + "ch2cl2": 192.79025, + "dmso": 192.95425, + "h2o": 192.91275000000002, + "methanol": 192.91250000000002, + "thf": 192.77625, + "toluene": 192.4135, + }, + "wb97x": { + "gas": 190.65525, + "acetone": 191.97199999999998, + "chcl3": 191.73825, + "acetonitrile": 192.00725, + "ch2cl2": 191.875, + "dmso": 192.04950000000002, + "h2o": 191.99675000000002, + "methanol": 192.007, + "thf": 191.86025, + "toluene": 191.47125, + }, + "pbeh-3c": { + "gas": 198.752, + "acetone": 200.196, + "chcl3": 199.9445, + "acetonitrile": 200.23250000000002, + "ch2cl2": 200.0925, + "dmso": 200.277, + "h2o": 200.15925, + "methanol": 200.23350000000002, + "thf": 200.075, + "toluene": 199.65050000000002, + }, + "kt2": { + "gas": 191.037, + "acetone": 192.29649999999998, + "chcl3": 192.0765, + "acetonitrile": 192.3275, + "ch2cl2": 192.20350000000002, + "dmso": 192.3755, + "h2o": 192.188, + "methanol": 192.33275, + "thf": 192.1925, + "toluene": 191.8175, + }, + }, + "tpss": { + "tpss": { + "gas": 187.22, + "acetone": 188.442, + "chcl3": 188.214, + "acetonitrile": 188.4745, + "ch2cl2": 188.351, + "dmso": 188.5115, + "h2o": 188.58350000000002, + "methanol": 188.473, + "thf": 188.33950000000002, + "toluene": 187.965, + }, + "pbe0": { + "gas": 187.5725, + "acetone": 188.99225, + "chcl3": 188.73424999999997, + "acetonitrile": 189.0295, + "ch2cl2": 188.8875, + "dmso": 189.06875, + "h2o": 189.14175, + "methanol": 189.0275, + "thf": 188.8665, + "toluene": 188.4305, + }, + "dsd-blyp": { + "gas": 190.06825, + "acetone": 191.39, + "chcl3": 191.15425, + "acetonitrile": 191.42600000000002, + "ch2cl2": 191.29475000000002, + "dmso": 191.461, + "h2o": 191.53225, + "methanol": 191.4225, + "thf": 191.27499999999998, + "toluene": 190.87675000000002, + }, + "wb97x": { + "gas": 189.04575, + "acetone": 190.45225000000002, + "chcl3": 190.20074999999997, + "acetonitrile": 190.4885, + "ch2cl2": 190.35025000000002, + "dmso": 190.52525, + "h2o": 190.5975, + "methanol": 190.4855, + "thf": 190.32899999999998, + "toluene": 189.904, + }, + "pbeh-3c": { + "gas": 197.184, + "acetone": 198.7195, + "chcl3": 198.449, + "acetonitrile": 198.75799999999998, + "ch2cl2": 198.611, + "dmso": 198.7955, + "h2o": 198.8655, + "methanol": 198.755, + "thf": 198.587, + "toluene": 198.1245, + }, + "kt2": { + "gas": 189.386, + "acetone": 190.7245, + "chcl3": 190.488, + "acetonitrile": 190.7585, + "ch2cl2": 190.6275, + "dmso": 190.7975, + "h2o": 190.87900000000002, + "methanol": 190.75799999999998, + "thf": 190.6095, + "toluene": 190.2095, + }, + }, + } + } + f_tm_shieldings = { + "CFCl3": { + "pbeh-3c": { + "tpss": { + "gas": 163.5665883, + "acetone": 165.9168679, + "chcl3": 165.043061, + "acetonitrile": 166.377831, + "ch2cl2": 164.776383, + "dmso": 166.1839641, + "h2o": 166.880495, + "methanol": 165.4364879, + "thf": 165.7384153, + "toluene": 165.7812123, + }, + "pbe0": { + "gas": 179.4820255, + "acetone": 181.9743764, + "chcl3": 181.1338758, + "acetonitrile": 182.4438224, + "ch2cl2": 180.8751895, + "dmso": 182.2224636, + "h2o": 182.9958356, + "methanol": 181.5031528, + "thf": 181.7669891, + "toluene": 181.7963177, + }, + "pbeh-3c": { + "gas": 225.045234, + "acetone": 226.6335916, + "chcl3": 226.0133192, + "acetonitrile": 226.9371636, + "ch2cl2": 225.8300352, + "dmso": 226.8061873, + "h2o": 227.4000142, + "methanol": 226.3012569, + "thf": 226.5247654, + "toluene": 226.555523, + }, + }, + "b97-3c": { + "tpss": { + "gas": 150.4514566, + "acetone": 151.5612999, + "chcl3": 150.5819485, + "acetonitrile": 151.9884593, + "ch2cl2": 150.2953968, + "dmso": 151.8818575, + "h2o": 151.6179136, + "methanol": 151.0439011, + "thf": 151.4207377, + "toluene": 151.4686522, + }, + "pbe0": { + "gas": 167.7783433, + "acetone": 169.09491, + "chcl3": 168.1354478, + "acetonitrile": 169.5416871, + "ch2cl2": 167.8558489, + "dmso": 169.3950732, + "h2o": 169.2178304, + "methanol": 168.5860848, + "thf": 168.9136991, + "toluene": 168.9347931, + }, + "pbeh-3c": { + "gas": 213.6651892, + "acetone": 214.1284506, + "chcl3": 213.4293417, + "acetonitrile": 214.4297108, + "ch2cl2": 213.2298905, + "dmso": 214.366451, + "h2o": 214.1162368, + "methanol": 213.76845, + "thf": 214.0512078, + "toluene": 214.0924969, + }, + }, + "tpss": { + "tpss": { + "gas": 146.4091676, + "acetone": 148.7113398, + "chcl3": 147.7715256, + "acetonitrile": 149.1854535, + "ch2cl2": 147.4708159, + "dmso": 148.9781692, + "h2o": 148.8407317, + "methanol": 148.1815132, + "thf": 148.5140784, + "toluene": 148.6001306, + }, + "pbe0": { + "gas": 163.4654205, + "acetone": 165.9356023, + "chcl3": 165.0269644, + "acetonitrile": 166.4188044, + "ch2cl2": 164.7336009, + "dmso": 166.1830401, + "h2o": 166.0858984, + "methanol": 165.4145633, + "thf": 165.7038144, + "toluene": 165.7726604, + }, + "pbeh-3c": { + "gas": 209.8752809, + "acetone": 211.4025693, + "chcl3": 210.7286529, + "acetonitrile": 211.7120494, + "ch2cl2": 210.5166504, + "dmso": 211.5990015, + "h2o": 211.4250312, + "methanol": 211.0321396, + "thf": 211.2798891, + "toluene": 211.3499046, + }, + }, + } + } + f_orca_shieldings = { + "CFCl3": { + "pbeh-3c": { + "tpss": { + "gas": 166.028, + "acetone": 167.858, + "chcl3": 167.569, + "acetonitrile": 167.92, + "ch2cl2": 167.732, + "dmso": 167.992, + "h2o": 168.239, + "methanol": 167.889, + "thf": 167.737, + "toluene": 167.278, + }, + "pbe0": { + "gas": 178.99, + "acetone": 181.086, + "chcl3": 180.741, + "acetonitrile": 181.154, + "ch2cl2": 180.939, + "dmso": 181.224, + "h2o": 181.464, + "methanol": 181.123, + "thf": 180.934, + "toluene": 180.377, + }, + "dsd-blyp": { + "gas": 225.542, + "acetone": 227.877, + "chcl3": 227.478, + "acetonitrile": 227.949, + "ch2cl2": 227.712, + "dmso": 228.007, + "h2o": 228.213, + "methanol": 227.919, + "thf": 227.691, + "toluene": 227.033, + }, + "wb97x": { + "gas": 193.433, + "acetone": 195.381, + "chcl3": 195.059, + "acetonitrile": 195.445, + "ch2cl2": 195.245, + "dmso": 195.508, + "h2o": 195.733, + "methanol": 195.415, + "thf": 195.239, + "toluene": 194.719, + }, + "pbeh-3c": { + "gas": 224.834, + "acetone": 226.308, + "chcl3": 226.076, + "acetonitrile": 226.36, + "ch2cl2": 226.207, + "dmso": 226.424, + "h2o": 226.639, + "methanol": 226.333, + "thf": 226.215, + "toluene": 225.843, + }, + "kt2": { + "gas": 144.178, + "acetone": 146.15, + "chcl3": 145.821, + "acetonitrile": 146.219, + "ch2cl2": 146.007, + "dmso": 146.298, + "h2o": 146.569, + "methanol": 146.185, + "thf": 146.012, + "toluene": 145.488, + }, + }, + "b97-3c": { + "tpss": { + "gas": 153.325, + "acetone": 153.259, + "chcl3": 152.987, + "acetonitrile": 153.326, + "ch2cl2": 153.137, + "dmso": 153.425, + "h2o": 153.729, + "methanol": 153.292, + "thf": 153.16, + "toluene": 152.731, + }, + "pbe0": { + "gas": 167.245, + "acetone": 167.447, + "chcl3": 167.121, + "acetonitrile": 167.52, + "ch2cl2": 167.31, + "dmso": 167.626, + "h2o": 167.92, + "methanol": 167.486, + "thf": 167.322, + "toluene": 166.785, + }, + "dsd-blyp": { + "gas": 216.287, + "acetone": 217.144, + "chcl3": 216.726, + "acetonitrile": 217.223, + "ch2cl2": 216.969, + "dmso": 217.304, + "h2o": 217.555, + "methanol": 217.19, + "thf": 216.957, + "toluene": 216.272, + }, + "wb97x": { + "gas": 182.767, + "acetone": 182.921, + "chcl3": 182.602, + "acetonitrile": 182.99, + "ch2cl2": 182.783, + "dmso": 183.077, + "h2o": 183.351, + "methanol": 182.957, + "thf": 182.792, + "toluene": 182.279, + }, + "pbeh-3c": { + "gas": 213.421, + "acetone": 213.215, + "chcl3": 212.997, + "acetonitrile": 213.271, + "ch2cl2": 213.116, + "dmso": 213.36, + "h2o": 213.627, + "methanol": 213.241, + "thf": 213.14, + "toluene": 212.796, + }, + "kt2": { + "gas": 130.539, + "acetone": 130.291, + "chcl3": 130.081, + "acetonitrile": 130.364, + "ch2cl2": 130.242, + "dmso": 130.472, + "h2o": 130.803, + "methanol": 130.326, + "thf": 130.267, + "toluene": 129.808, + }, + }, + "tpss": { + "tpss": { + "gas": 148.387, + "acetone": 149.573, + "chcl3": 149.247, + "acetonitrile": 149.647, + "ch2cl2": 149.43, + "dmso": 149.748, + "h2o": 150.066, + "methanol": 149.609, + "thf": 149.446, + "toluene": 148.927, + }, + "pbe0": { + "gas": 162.075, + "acetone": 163.638, + "chcl3": 163.239, + "acetonitrile": 163.71, + "ch2cl2": 163.472, + "dmso": 163.807, + "h2o": 164.125, + "methanol": 163.671, + "thf": 163.476, + "toluene": 162.835, + }, + "dsd-blyp": { + "gas": 211.635, + "acetone": 213.66, + "chcl3": 213.199, + "acetonitrile": 213.746, + "ch2cl2": 213.469, + "dmso": 213.828, + "h2o": 214.092, + "methanol": 213.71, + "thf": 213.451, + "toluene": 212.692, + }, + "wb97x": { + "gas": 177.986, + "acetone": 179.452, + "chcl3": 179.093, + "acetonitrile": 179.528, + "ch2cl2": 179.299, + "dmso": 179.616, + "h2o": 179.902, + "methanol": 179.491, + "thf": 179.302, + "toluene": 178.721, + }, + "pbeh-3c": { + "gas": 208.73, + "acetone": 209.687, + "chcl3": 209.429, + "acetonitrile": 209.749, + "ch2cl2": 209.573, + "dmso": 209.825, + "h2o": 210.102, + "methanol": 209.716, + "thf": 209.592, + "toluene": 209.176, + }, + "kt2": { + "gas": 124.897, + "acetone": 126.154, + "chcl3": 125.806, + "acetonitrile": 126.235, + "ch2cl2": 126.001, + "dmso": 126.345, + "h2o": 126.689, + "methanol": 126.193, + "thf": 126.019, + "toluene": 125.465, + }, + }, + } + } + p_tm_shieldings = { + "PH3": { + "pbeh-3c": { + "tpss": { + "gas": 560.9783608, + "acetone": 559.5567974, + "chcl3": 555.7297268, + "acetonitrile": 558.7420853, + "ch2cl2": 555.9207578, + "dmso": 559.0317956, + "h2o": 551.9868157, + "methanol": 557.7229598, + "thf": 559.4070044, + "toluene": 558.9538264, + }, + "pbe0": { + "gas": 573.7889709, + "acetone": 572.6807308, + "chcl3": 568.6200619, + "acetonitrile": 572.0156003, + "ch2cl2": 568.6775273, + "dmso": 572.2984368, + "h2o": 564.8512663, + "methanol": 570.6948985, + "thf": 572.4491708, + "toluene": 572.2945282, + }, + "pbeh-3c": { + "gas": 622.6149401, + "acetone": 624.221383, + "chcl3": 622.2460822, + "acetonitrile": 624.0839458, + "ch2cl2": 622.3660073, + "dmso": 623.8685076, + "h2o": 622.54767, + "methanol": 623.1569748, + "thf": 623.7253948, + "toluene": 623.2733775, + }, + }, + "b97-3c": { + "tpss": { + "gas": 559.5296772, + "acetone": 557.5438599, + "chcl3": 553.7653249, + "acetonitrile": 556.735552, + "ch2cl2": 554.1613395, + "dmso": 557.010476, + "h2o": 550.1185847, + "methanol": 555.82703, + "thf": 557.2207586, + "toluene": 556.8427805, + }, + "pbe0": { + "gas": 572.4232552, + "acetone": 570.7398164, + "chcl3": 566.7271447, + "acetonitrile": 570.0779914, + "ch2cl2": 566.9826221, + "dmso": 570.3456887, + "h2o": 563.05667, + "methanol": 568.8622417, + "thf": 570.3305746, + "toluene": 570.2507738, + }, + "pbeh-3c": { + "gas": 621.2286124, + "acetone": 622.356702, + "chcl3": 620.3365742, + "acetonitrile": 622.2263079, + "ch2cl2": 620.6570087, + "dmso": 621.9912341, + "h2o": 620.7021951, + "methanol": 621.3567408, + "thf": 621.7091401, + "toluene": 621.3088355, + }, + }, + "tpss": { + "tpss": { + "gas": 558.1589032, + "acetone": 556.5475548, + "chcl3": 553.3273579, + "acetonitrile": 555.6559443, + "ch2cl2": 553.600544, + "dmso": 556.0983125, + "h2o": 548.970911, + "methanol": 555.4535832, + "thf": 556.3191064, + "toluene": 555.9299261, + }, + "pbe0": { + "gas": 571.012794, + "acetone": 569.7250563, + "chcl3": 566.2936179, + "acetonitrile": 568.9923465, + "ch2cl2": 566.4237381, + "dmso": 569.4236946, + "h2o": 561.898531, + "methanol": 568.4989088, + "thf": 569.4140377, + "toluene": 569.3191735, + }, + "pbeh-3c": { + "gas": 620.0674752, + "acetone": 621.5116584, + "chcl3": 619.9397925, + "acetonitrile": 621.2898165, + "ch2cl2": 620.15928, + "dmso": 621.2154327, + "h2o": 619.7280828, + "methanol": 621.0126668, + "thf": 620.9449236, + "toluene": 620.5363442, + }, + }, + }, + "TMP": { + "pbeh-3c": { + "tpss": { + "gas": 281.6302978, + "acetone": 265.4354914, + "chcl3": 257.5409613, + "acetonitrile": 263.2430698, + "ch2cl2": 257.0543221, + "dmso": 262.8752182, + "h2o": 242.4838211, + "methanol": 245.6431135, + "thf": 266.7188352, + "toluene": 269.0597797, + }, + "pbe0": { + "gas": 277.8252556, + "acetone": 261.5502528, + "chcl3": 254.1109855, + "acetonitrile": 259.5059377, + "ch2cl2": 253.6358478, + "dmso": 258.7821425, + "h2o": 239.5329333, + "methanol": 242.1687948, + "thf": 262.8378646, + "toluene": 265.4050199, + }, + "pbeh-3c": { + "gas": 390.6073841, + "acetone": 378.6668397, + "chcl3": 373.2000393, + "acetonitrile": 377.1343123, + "ch2cl2": 372.9163524, + "dmso": 376.6203422, + "h2o": 362.7163813, + "methanol": 364.8220379, + "thf": 379.5051748, + "toluene": 381.2789752, + }, + }, + "b97-3c": { + "tpss": { + "gas": 276.8654211, + "acetone": 259.8829696, + "chcl3": 251.5648819, + "acetonitrile": 257.7225804, + "ch2cl2": 251.0880934, + "dmso": 256.90761, + "h2o": 234.4800595, + "methanol": 237.4630709, + "thf": 261.291204, + "toluene": 263.9827571, + }, + "pbe0": { + "gas": 273.0911933, + "acetone": 256.1507446, + "chcl3": 248.2072561, + "acetonitrile": 254.0571117, + "ch2cl2": 247.7513367, + "dmso": 253.0100842, + "h2o": 231.7425518, + "methanol": 234.1695454, + "thf": 257.4644157, + "toluene": 260.3717755, + }, + "pbeh-3c": { + "gas": 386.2437698, + "acetone": 373.8145109, + "chcl3": 368.1719462, + "acetonitrile": 372.350904, + "ch2cl2": 367.8934403, + "dmso": 371.4995766, + "h2o": 355.9965281, + "methanol": 358.0517851, + "thf": 374.7716841, + "toluene": 376.8283779, + }, + }, + "tpss": { + "tpss": { + "gas": 278.0447826, + "acetone": 261.4382678, + "chcl3": 253.5317417, + "acetonitrile": 259.5831076, + "ch2cl2": 253.0735218, + "dmso": 258.8205488, + "h2o": 236.9938311, + "methanol": 240.0596152, + "thf": 262.646474, + "toluene": 265.5482099, + }, + "pbe0": { + "gas": 274.1582231, + "acetone": 257.5976215, + "chcl3": 250.0455696, + "acetonitrile": 255.8739799, + "ch2cl2": 249.6032437, + "dmso": 254.7109046, + "h2o": 234.1066151, + "methanol": 236.6658834, + "thf": 258.6914971, + "toluene": 261.8410368, + }, + "pbeh-3c": { + "gas": 387.4697022, + "acetone": 375.2569197, + "chcl3": 369.9533245, + "acetonitrile": 374.0256406, + "ch2cl2": 369.6688695, + "dmso": 373.1520781, + "h2o": 358.1827766, + "methanol": 360.3168296, + "thf": 376.0015788, + "toluene": 378.3153047, + }, + }, + }, + } + p_orca_shieldings = { + "PH3": { + "pbeh-3c": { + "tpss": { + "gas": 578.49, + "acetone": 577.53, + "chcl3": 577.773, + "acetonitrile": 577.631, + "ch2cl2": 577.63, + "dmso": 577.688, + "h2o": 577.764, + "methanol": 577.506, + "thf": 577.671, + "toluene": 577.946, + }, + "pbe0": { + "gas": 573.639, + "acetone": 573.637, + "chcl3": 573.71, + "acetonitrile": 573.764, + "ch2cl2": 573.67, + "dmso": 573.829, + "h2o": 573.914, + "methanol": 573.632, + "thf": 573.688, + "toluene": 573.665, + }, + "dsd-blyp": { + "gas": 569.431, + "acetone": 567.575, + "chcl3": 567.994, + "acetonitrile": 567.65, + "ch2cl2": 567.746, + "dmso": 567.695, + "h2o": 567.745, + "methanol": 567.531, + "thf": 567.809, + "toluene": 568.372, + }, + "wb97x": { + "gas": 568.27, + "acetone": 568.185, + "chcl3": 568.261, + "acetonitrile": 568.31, + "ch2cl2": 568.218, + "dmso": 568.375, + "h2o": 568.459, + "methanol": 568.18, + "thf": 568.236, + "toluene": 568.231, + }, + "pbeh-3c": { + "gas": 622.505, + "acetone": 626.377, + "chcl3": 625.536, + "acetonitrile": 626.609, + "ch2cl2": 626.042, + "dmso": 626.709, + "h2o": 626.85, + "methanol": 626.48, + "thf": 625.933, + "toluene": 624.513, + }, + "kt2": { + "gas": 587.254, + "acetone": 587.821, + "chcl3": 587.78, + "acetonitrile": 587.962, + "ch2cl2": 587.81, + "dmso": 588.032, + "h2o": 588.129, + "methanol": 587.829, + "thf": 587.812, + "toluene": 587.606, + }, + }, + "b97-3c": { + "tpss": { + "gas": 574.673, + "acetone": 575.587, + "chcl3": 575.672, + "acetonitrile": 575.6, + "ch2cl2": 575.619, + "dmso": 575.662, + "h2o": 575.948, + "methanol": 575.57, + "thf": 575.668, + "toluene": 575.8, + }, + "pbe0": { + "gas": 569.721, + "acetone": 571.667, + "chcl3": 571.577, + "acetonitrile": 571.703, + "ch2cl2": 571.631, + "dmso": 571.774, + "h2o": 572.075, + "methanol": 571.67, + "thf": 571.656, + "toluene": 571.48, + }, + "dsd-blyp": { + "gas": 565.936, + "acetone": 565.88, + "chcl3": 566.179, + "acetonitrile": 565.866, + "ch2cl2": 566.012, + "dmso": 565.915, + "h2o": 566.166, + "methanol": 565.843, + "thf": 566.084, + "toluene": 566.506, + }, + "wb97x": { + "gas": 564.429, + "acetone": 566.244, + "chcl3": 566.161, + "acetonitrile": 566.279, + "ch2cl2": 566.206, + "dmso": 566.349, + "h2o": 566.646, + "methanol": 566.247, + "thf": 566.233, + "toluene": 566.086, + }, + "pbeh-3c": { + "gas": 618.99, + "acetone": 624.483, + "chcl3": 623.499, + "acetonitrile": 624.639, + "ch2cl2": 624.087, + "dmso": 624.744, + "h2o": 625.072, + "methanol": 624.593, + "thf": 623.983, + "toluene": 622.448, + }, + "kt2": { + "gas": 583.324, + "acetone": 585.797, + "chcl3": 585.592, + "acetonitrile": 585.848, + "ch2cl2": 585.715, + "dmso": 585.925, + "h2o": 586.235, + "methanol": 585.813, + "thf": 585.725, + "toluene": 585.371, + }, + }, + "tpss": { + "tpss": { + "gas": 574.839, + "acetone": 574.09, + "chcl3": 574.267, + "acetonitrile": 574.11, + "ch2cl2": 574.167, + "dmso": 574.166, + "h2o": 574.435, + "methanol": 574.084, + "thf": 574.22, + "toluene": 574.478, + }, + "pbe0": { + "gas": 569.911, + "acetone": 570.088, + "chcl3": 570.127, + "acetonitrile": 570.133, + "ch2cl2": 570.135, + "dmso": 570.198, + "h2o": 570.482, + "methanol": 570.103, + "thf": 570.164, + "toluene": 570.119, + }, + "dsd-blyp": { + "gas": 566.08, + "acetone": 564.411, + "chcl3": 564.793, + "acetonitrile": 564.406, + "ch2cl2": 564.583, + "dmso": 564.448, + "h2o": 564.684, + "methanol": 564.385, + "thf": 564.658, + "toluene": 565.213, + }, + "wb97x": { + "gas": 564.63, + "acetone": 564.706, + "chcl3": 564.726, + "acetonitrile": 564.75, + "ch2cl2": 564.72, + "dmso": 564.813, + "h2o": 565.093, + "methanol": 564.721, + "thf": 564.752, + "toluene": 564.742, + }, + "pbeh-3c": { + "gas": 619.182, + "acetone": 623.189, + "chcl3": 622.29, + "acetonitrile": 623.352, + "ch2cl2": 622.833, + "dmso": 623.451, + "h2o": 623.764, + "methanol": 623.308, + "thf": 622.734, + "toluene": 621.304, + }, + "kt2": { + "gas": 583.522, + "acetone": 584.278, + "chcl3": 584.168, + "acetonitrile": 584.337, + "ch2cl2": 584.241, + "dmso": 584.407, + "h2o": 584.701, + "methanol": 584.305, + "thf": 584.256, + "toluene": 584.034, + }, + }, + }, + "TMP": { + "pbeh-3c": { + "tpss": { + "gas": 291.33, + "acetone": 276.264, + "chcl3": 277.254, + "acetonitrile": 275.207, + "ch2cl2": 276.171, + "dmso": 276.988, + "h2o": 262.671, + "methanol": 263.366, + "thf": 278.685, + "toluene": 283.761, + }, + "pbe0": { + "gas": 277.761, + "acetone": 262.673, + "chcl3": 263.634, + "acetonitrile": 261.631, + "ch2cl2": 262.58, + "dmso": 263.406, + "h2o": 249.27, + "methanol": 249.931, + "thf": 265.061, + "toluene": 270.123, + }, + "dsd-blyp": { + "gas": 299.195, + "acetone": 286.35, + "chcl3": 287.213, + "acetonitrile": 285.469, + "ch2cl2": 286.302, + "dmso": 286.997, + "h2o": 274.843, + "methanol": 275.42, + "thf": 288.362, + "toluene": 292.724, + }, + "wb97x": { + "gas": 277.52, + "acetone": 262.317, + "chcl3": 263.295, + "acetonitrile": 261.26, + "ch2cl2": 262.227, + "dmso": 263.036, + "h2o": 248.805, + "methanol": 249.485, + "thf": 264.716, + "toluene": 269.816, + }, + "pbeh-3c": { + "gas": 390.602, + "acetone": 379.7, + "chcl3": 380.279, + "acetonitrile": 378.978, + "ch2cl2": 379.593, + "dmso": 380.317, + "h2o": 368.831, + "methanol": 369.216, + "thf": 381.391, + "toluene": 384.986, + }, + "kt2": { + "gas": 297.198, + "acetone": 281.884, + "chcl3": 282.896, + "acetonitrile": 280.816, + "ch2cl2": 281.794, + "dmso": 282.606, + "h2o": 268.382, + "methanol": 269.076, + "thf": 284.334, + "toluene": 289.473, + }, + }, + "b97-3c": { + "tpss": { + "gas": 286.404, + "acetone": 270.748, + "chcl3": 271.725, + "acetonitrile": 269.462, + "ch2cl2": 270.524, + "dmso": 271.355, + "h2o": 256.342, + "methanol": 257.122, + "thf": 273.469, + "toluene": 278.676, + }, + "pbe0": { + "gas": 272.706, + "acetone": 257.164, + "chcl3": 258.119, + "acetonitrile": 255.895, + "ch2cl2": 256.94, + "dmso": 257.797, + "h2o": 242.92, + "methanol": 243.667, + "thf": 259.855, + "toluene": 264.973, + }, + "dsd-blyp": { + "gas": 294.405, + "acetone": 281.158, + "chcl3": 282.018, + "acetonitrile": 280.073, + "ch2cl2": 280.993, + "dmso": 281.703, + "h2o": 269.086, + "methanol": 269.737, + "thf": 283.464, + "toluene": 287.882, + }, + "wb97x": { + "gas": 272.595, + "acetone": 256.861, + "chcl3": 257.836, + "acetonitrile": 255.578, + "ch2cl2": 256.643, + "dmso": 257.483, + "h2o": 242.627, + "methanol": 243.389, + "thf": 259.577, + "toluene": 264.773, + }, + "pbeh-3c": { + "gas": 385.991, + "acetone": 374.828, + "chcl3": 375.394, + "acetonitrile": 373.92, + "ch2cl2": 374.61, + "dmso": 375.349, + "h2o": 363.431, + "methanol": 363.874, + "thf": 376.762, + "toluene": 380.401, + }, + "kt2": { + "gas": 292.227, + "acetone": 276.414, + "chcl3": 277.413, + "acetonitrile": 275.12, + "ch2cl2": 276.191, + "dmso": 277.05, + "h2o": 262.135, + "methanol": 262.912, + "thf": 279.163, + "toluene": 284.4, + }, + }, + "tpss": { + "tpss": { + "gas": 286.331, + "acetone": 271.022, + "chcl3": 271.947, + "acetonitrile": 269.751, + "ch2cl2": 270.768, + "dmso": 271.616, + "h2o": 256.882, + "methanol": 257.6, + "thf": 273.659, + "toluene": 278.687, + }, + "pbe0": { + "gas": 272.619, + "acetone": 257.298, + "chcl3": 258.198, + "acetonitrile": 256.053, + "ch2cl2": 257.051, + "dmso": 257.926, + "h2o": 243.408, + "methanol": 244.095, + "thf": 259.935, + "toluene": 264.977, + }, + "dsd-blyp": { + "gas": 294.334, + "acetone": 281.319, + "chcl3": 282.131, + "acetonitrile": 280.265, + "ch2cl2": 281.144, + "dmso": 281.852, + "h2o": 269.472, + "methanol": 270.068, + "thf": 283.556, + "toluene": 287.875, + }, + "wb97x": { + "gas": 272.586, + "acetone": 257.148, + "chcl3": 258.069, + "acetonitrile": 255.901, + "ch2cl2": 256.919, + "dmso": 257.755, + "h2o": 243.195, + "methanol": 243.894, + "thf": 259.785, + "toluene": 264.863, + }, + "pbeh-3c": { + "gas": 385.897, + "acetone": 374.881, + "chcl3": 375.407, + "acetonitrile": 373.999, + "ch2cl2": 374.652, + "dmso": 375.391, + "h2o": 363.697, + "methanol": 364.097, + "thf": 376.757, + "toluene": 380.319, + }, + "kt2": { + "gas": 292.105, + "acetone": 276.574, + "chcl3": 277.519, + "acetonitrile": 275.313, + "ch2cl2": 276.339, + "dmso": 277.197, + "h2o": 262.553, + "methanol": 263.276, + "thf": 279.247, + "toluene": 284.37, + }, + }, + }, + } + si_tm_shieldings = { + "TMS": { + "pbeh-3c": { + "tpss": { + "gas": 334.2579542, + "acetone": 334.1639413, + "chcl3": 334.1459912, + "acetonitrile": 334.1644763, + "ch2cl2": 334.143167, + "dmso": 334.2355086, + "h2o": 334.1700712, + "methanol": 334.1638302, + "thf": 334.1765686, + "toluene": 334.1672644, + }, + "pbe0": { + "gas": 332.1432161, + "acetone": 332.0806043, + "chcl3": 332.027555, + "acetonitrile": 332.070525, + "ch2cl2": 332.0181509, + "dmso": 332.1389588, + "h2o": 332.0768365, + "methanol": 332.082777, + "thf": 332.0989747, + "toluene": 332.0655251, + }, + "pbeh-3c": { + "gas": 425.4500968, + "acetone": 425.4194168, + "chcl3": 425.3783658, + "acetonitrile": 425.4187809, + "ch2cl2": 425.3492293, + "dmso": 425.4302912, + "h2o": 425.4004059, + "methanol": 425.3865089, + "thf": 425.4157351, + "toluene": 425.4555181, + }, + }, + "b97-3c": { + "tpss": { + "gas": 334.5698984, + "acetone": 334.0803779, + "chcl3": 334.1093328, + "acetonitrile": 334.0665281, + "ch2cl2": 334.1280337, + "dmso": 334.1272572, + "h2o": 334.0495564, + "methanol": 334.1137413, + "thf": 334.1251606, + "toluene": 334.1235476, + }, + "pbe0": { + "gas": 332.3546979, + "acetone": 331.9058869, + "chcl3": 331.8955148, + "acetonitrile": 331.8800833, + "ch2cl2": 331.9140658, + "dmso": 331.948424, + "h2o": 331.8617288, + "methanol": 331.9375391, + "thf": 331.9562723, + "toluene": 331.9253075, + }, + "pbeh-3c": { + "gas": 426.0062656, + "acetone": 425.7811084, + "chcl3": 425.7602588, + "acetonitrile": 425.745999, + "ch2cl2": 425.7473718, + "dmso": 425.779427, + "h2o": 425.7365851, + "methanol": 425.7713265, + "thf": 425.7964293, + "toluene": 425.8200844, + }, + }, + "tpss": { + "tpss": { + "gas": 333.7779314, + "acetone": 333.3511708, + "chcl3": 333.3794838, + "acetonitrile": 333.3298692, + "ch2cl2": 333.3946486, + "dmso": 333.3881767, + "h2o": 333.3406562, + "methanol": 333.3784136, + "thf": 333.3860666, + "toluene": 333.3885135, + }, + "pbe0": { + "gas": 331.5820841, + "acetone": 331.1904714, + "chcl3": 331.1839521, + "acetonitrile": 331.1565218, + "ch2cl2": 331.1982524, + "dmso": 331.2347884, + "h2o": 331.1670301, + "methanol": 331.2231923, + "thf": 331.2383692, + "toluene": 331.2108329, + }, + "pbeh-3c": { + "gas": 425.0726297, + "acetone": 424.9009564, + "chcl3": 424.8706079, + "acetonitrile": 424.8831877, + "ch2cl2": 424.8554965, + "dmso": 424.9143792, + "h2o": 424.8579037, + "methanol": 424.8851226, + "thf": 424.9146175, + "toluene": 424.9330242, + }, + }, + } + } + si_orca_shieldings = { + "TMS": { + "pbeh-3c": { + "tpss": { + "gas": 344.281, + "acetone": 344.239, + "chcl3": 344.311, + "acetonitrile": 344.198, + "ch2cl2": 344.231, + "dmso": 344.292, + "h2o": 344.228, + "methanol": 344.291, + "thf": 344.283, + "toluene": 344.452, + }, + "pbe0": { + "gas": 332.181, + "acetone": 332.067, + "chcl3": 332.162, + "acetonitrile": 332.033, + "ch2cl2": 332.082, + "dmso": 332.122, + "h2o": 332.048, + "methanol": 332.122, + "thf": 332.134, + "toluene": 332.298, + }, + "dsd-blyp": { + "gas": 357.874, + "acetone": 357.762, + "chcl3": 357.864, + "acetonitrile": 357.726, + "ch2cl2": 357.783, + "dmso": 357.798, + "h2o": 357.715, + "methanol": 357.809, + "thf": 357.826, + "toluene": 358.001, + }, + "wb97x": { + "gas": 335.739, + "acetone": 335.641, + "chcl3": 335.74, + "acetonitrile": 335.606, + "ch2cl2": 335.659, + "dmso": 335.687, + "h2o": 335.608, + "methanol": 335.692, + "thf": 335.707, + "toluene": 335.879, + }, + "pbeh-3c": { + "gas": 425.385, + "acetone": 425.52, + "chcl3": 425.527, + "acetonitrile": 425.511, + "ch2cl2": 425.508, + "dmso": 425.578, + "h2o": 425.566, + "methanol": 425.557, + "thf": 425.54, + "toluene": 425.556, + }, + "kt2": { + "gas": 341.186, + "acetone": 341.197, + "chcl3": 341.284, + "acetonitrile": 341.166, + "ch2cl2": 341.208, + "dmso": 341.263, + "h2o": 341.201, + "methanol": 341.253, + "thf": 341.263, + "toluene": 341.446, + }, + }, + "b97-3c": { + "tpss": { + "gas": 344.503, + "acetone": 344.558, + "chcl3": 344.676, + "acetonitrile": 344.487, + "ch2cl2": 344.537, + "dmso": 344.67, + "h2o": 344.542, + "methanol": 344.662, + "thf": 344.637, + "toluene": 344.919, + }, + "pbe0": { + "gas": 332.338, + "acetone": 332.293, + "chcl3": 332.442, + "acetonitrile": 332.236, + "ch2cl2": 332.31, + "dmso": 332.4, + "h2o": 332.288, + "methanol": 332.392, + "thf": 332.403, + "toluene": 332.676, + }, + "dsd-blyp": { + "gas": 357.729, + "acetone": 357.628, + "chcl3": 357.774, + "acetonitrile": 357.578, + "ch2cl2": 357.655, + "dmso": 357.692, + "h2o": 357.632, + "methanol": 357.703, + "thf": 357.725, + "toluene": 357.985, + }, + "wb97x": { + "gas": 335.744, + "acetone": 335.688, + "chcl3": 335.837, + "acetonitrile": 335.633, + "ch2cl2": 335.71, + "dmso": 335.774, + "h2o": 335.704, + "methanol": 335.776, + "thf": 335.792, + "toluene": 336.064, + }, + "pbeh-3c": { + "gas": 425.911, + "acetone": 426.14, + "chcl3": 426.185, + "acetonitrile": 426.113, + "ch2cl2": 426.124, + "dmso": 426.254, + "h2o": 426.162, + "methanol": 426.22, + "thf": 426.196, + "toluene": 426.294, + }, + "kt2": { + "gas": 341.631, + "acetone": 341.666, + "chcl3": 341.811, + "acetonitrile": 341.61, + "ch2cl2": 341.676, + "dmso": 341.798, + "h2o": 341.602, + "methanol": 341.777, + "thf": 341.781, + "toluene": 342.086, + }, + }, + "tpss": { + "tpss": { + "gas": 343.24, + "acetone": 343.388, + "chcl3": 343.506, + "acetonitrile": 343.343, + "ch2cl2": 343.385, + "dmso": 343.48, + "h2o": 343.378, + "methanol": 343.47, + "thf": 343.449, + "toluene": 343.647, + }, + "pbe0": { + "gas": 331.055, + "acetone": 331.217, + "chcl3": 331.313, + "acetonitrile": 331.175, + "ch2cl2": 331.224, + "dmso": 331.303, + "h2o": 331.205, + "methanol": 331.296, + "thf": 331.293, + "toluene": 331.461, + }, + "dsd-blyp": { + "gas": 357.099, + "acetone": 357.125, + "chcl3": 357.231, + "acetonitrile": 357.081, + "ch2cl2": 357.141, + "dmso": 357.179, + "h2o": 357.075, + "methanol": 357.188, + "thf": 357.195, + "toluene": 357.379, + }, + "wb97x": { + "gas": 334.802, + "acetone": 334.886, + "chcl3": 334.987, + "acetonitrile": 334.842, + "ch2cl2": 334.897, + "dmso": 334.957, + "h2o": 334.855, + "methanol": 334.958, + "thf": 334.959, + "toluene": 335.134, + }, + "pbeh-3c": { + "gas": 424.346, + "acetone": 424.653, + "chcl3": 424.66, + "acetonitrile": 424.64, + "ch2cl2": 424.633, + "dmso": 424.74, + "h2o": 424.718, + "methanol": 424.709, + "thf": 424.681, + "toluene": 424.701, + }, + "kt2": { + "gas": 340.026, + "acetone": 340.228, + "chcl3": 340.311, + "acetonitrile": 340.189, + "ch2cl2": 340.226, + "dmso": 340.332, + "h2o": 340.207, + "methanol": 340.311, + "thf": 340.302, + "toluene": 340.453, + }, + }, + } + } + + if config.solvent != "gas": + # optimization in solvent: + if config.prog == "tm" and config.sm2 == "cosmo": + print( + "WARNING: The geometry optimization of the reference molecule " + "was calculated with DCOSMO-RS instead of COSMO as solvent " + "model (sm2)!" + ) + elif config.prog == "orca" and config.sm2 == "cpcm": + print( + "WARNING: The geometry optimization of the reference molecule " + "was calculated with SMD instead of CPCM as solvent model (sm2)!" + ) + if config.prog4_s == "tm": + h_qm_shieldings = h_tm_shieldings + c_qm_shieldings = c_tm_shieldings + f_qm_shieldings = f_tm_shieldings + p_qm_shieldings = p_tm_shieldings + si_qm_shieldings = si_tm_shieldings + lsm = "DCOSMO-RS" + lsm4 = "DCOSMO-RS" + lbasisS = "def2-TZVP" + if config.sm4_s == "cosmo": + print( + "WARNING: The reference shielding constant was calculated with DCOSMORS " + "instead of COSMO as solvent model (sm4_s)!" + ) + elif config.prog4_s == "orca": + lsm = "SMD" + lsm4 = "SMD" + lbasisS = "def2-TZVP" + h_qm_shieldings = h_orca_shieldings + c_qm_shieldings = c_orca_shieldings + f_qm_shieldings = f_orca_shieldings + p_qm_shieldings = p_orca_shieldings + si_qm_shieldings = si_orca_shieldings + if config.sm4_s == "cpcm": + print( + "WARNING: The reference shielding was calculated with SMD " + "instead of CPCM as solvent model (sm4_2)!" + ) + if config.func_s == "pbeh-3c": + lbasisS = "def2-mSVP" + + if config.basis_s != "def2-TZVP" and config.func_s != "pbeh-3c": + print( + "WARNING: The reference shielding constant was calculated with the " + "basis def2-TZVP (basisS)!" + ) + if config.func == "r2scan-3c": + print( + "WARNING: The reference shielding constants is not available for r2scan-3c and b97-3c is used instead!" + ) + opt_func = "b97-3c" + else: + opt_func = config.func + + # get absolute shielding constant of reference + prnterr = False + try: + hshielding = "{:4.3f}".format( + h_qm_shieldings[config.h_ref][opt_func][config.func_s][config.solvent] + ) + except KeyError: + hshielding = 0 + prnterr = True + try: + cshielding = "{:4.3f}".format( + c_qm_shieldings[config.c_ref][opt_func][config.func_s][config.solvent] + ) + except KeyError: + cshielding = 0 + prnterr = True + try: + fshielding = "{:4.3f}".format( + f_qm_shieldings[config.f_ref][opt_func][config.func_s][config.solvent] + ) + except KeyError: + fshielding = 0 + prnterr = True + try: + pshielding = "{:4.3f}".format( + p_qm_shieldings[config.p_ref][opt_func][config.func_s][config.solvent] + ) + except KeyError: + pshielding = 0 + prnterr = True + try: + sishielding = "{:4.3f}".format( + si_qm_shieldings[config.si_ref][opt_func][config.func_s][config.solvent] + ) + except KeyError: + sishielding = 0 + prnterr = True + if prnterr: + prnterr = ( + "ERROR! The reference absolute shielding constant " + "could not be found!\n You have to edit the file" + " .anmrrc by hand!" + ) + print(prnterr) + element_ref_shield = { + "h": float(hshielding), + "c": float(cshielding), + "f": float(fshielding), + "p": float(pshielding), + "si": float(sishielding), + } + + # for elementactive + exch = {True: 1, False: 0} + exchonoff = {True: "on", False: "off"} + # write .anmrrc + with open(os.path.join(config.cwd, ".anmrrc"), "w", newline=None) as arc: + arc.write("7 8 XH acid atoms\n") + if config.resonance_frequency is not None: + arc.write( + "ENSO qm= {} mf= {} lw= 1.0 J= {} S= {} T= {:6.2f} \n".format( + str(config.prog4_s).upper(), + str(config.resonance_frequency), + exchonoff[config.couplings], + exchonoff[config.shieldings], + float(config.temperature), + ) + ) + else: + arc.write("ENSO qm= {} lw= 1.2\n".format(str(config.prog4_s).upper())) + try: + length = max( + [ + len(i) + for i in [ + hshielding, + cshielding, + fshielding, + pshielding, + sishielding, + ] + ] + ) + except: + length = 6 + # lsm4 --> localsm4 ... + arc.write( + "{}[{}] {}[{}]/{}//{}[{}]/{}\n".format( + config.h_ref, + config.solvent, + config.func_s, + lsm4, + lbasisS, + opt_func, + lsm, + config.basis, + ) + ) + arc.write( + "1 {:{digits}} 0.0 {}\n".format( + hshielding, exch[config.h_active], digits=length + ) + ) # hydrogen + arc.write( + "6 {:{digits}} 0.0 {}\n".format( + cshielding, exch[config.c_active], digits=length + ) + ) # carbon + arc.write( + "9 {:{digits}} 0.0 {}\n".format( + fshielding, exch[config.f_active], digits=length + ) + ) # fluorine + arc.write( + "14 {:{digits}} 0.0 {}\n".format( + sishielding, exch[config.si_active], digits=length + ) + ) # silicon + arc.write( + "15 {:{digits}} 0.0 {}\n".format( + pshielding, exch[config.p_active], digits=length + ) + ) # phosphorus + return element_ref_shield diff --git a/docs/documentation.rst b/docs/documentation.rst new file mode 100644 index 0000000..93ac12f --- /dev/null +++ b/docs/documentation.rst @@ -0,0 +1,5 @@ +CENSO - Commandline ENergetic SOrting of Conformer Rotamer Ensembles +==================================================================== + + +assets folder (to store dcosmors potential files) diff --git a/docs/example.rst b/docs/example.rst new file mode 100644 index 0000000..e69de29 diff --git a/docs/src/solvents.png b/docs/src/solvents.png new file mode 100644 index 0000000000000000000000000000000000000000..4fed87299f204092810246f60a29dd0e64ec38f2 GIT binary patch literal 312571 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 z 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ʭIFupGTh<`pWVZUsry3P6{Tzd2W-fG1#FbhSj6yieWs&-DLY;BUoRDTdNfJ$ z1)YFpRJeK;O=BM$7~ECa;s$JO%^SjduzA}q{Dx+l^h*~mn7q4u@mO?T^pgTa#Il^? zHVa@6kf3dO#2U;pm%_rr3f#BaT3W6OLH6ce^bz`VjH_#FKdw7`M+;W)#Lnj%S%%od z1GjIu@sIXr_@&cnNemQ2>S6fE!|2mwm&B4R#jyDEra`b(2%%$@19|YrPEyZivlLoEB{m053;khy$B5~Bgmd|*`T*(S+WdSwugbC zVVaS7&uxI}#L4%d4fr!(J;a#GsptXd-{lw}G%N@>zx~Eiv0c@wx@Eyw!GH$g-n_uO7b1fgL^Q+$jbO{Bzq!X*DC_}? z!CNwa1oWn@gs-Yvnk&MabIzkZNZ?Fjgj|Kh@FP)W~fd-JNfr6s(A4cZV@U2zUB zy@11xg_Y>Ng7EmzNqtEYOQCXX4*@0QAQr^q9F-Im%LqVDu)_UQZ7-bBpP>DwB?7qf zO}NkVkd-?Fs}7nu$^O0Bh4;DI(N-0{H?#`{u92=hfbHR-y;%l6!@0b4d!TEfCjv?F z2xQm=fDbJ}(fFd;@TwMQi0i?41hX$fbsZrj%ff)FeAC$Z`0YVj7#kd;zP2iTvk%Dr#YQwsRWWV zYmQB5kLB9XFqpwufs?2ND!f#dlG0cO8e|rUbGC0NA6Ce{qAdc&+hVQwb-ST3SVpG( zg^~`Q?28i6AW8+vb5a6tI19G$zdxNPUvU6M5+#r&Q(D-B-)>oI=#V4?iXWjBY+!2Q zl$elkLHC5|mH(NO3b39(|Jex+;^oEuqNzG4Z#fwh7k>S^Z~5?H*hfoEH8pHxlsO4p zd5jY-P%Zf4=u*DLI0x5g@I3B;%!}XqIQ+iV$ZNkQ1t=#Z@vcQfaB)_r*c`{4r-D2d%*7YK z0+4(9A^^oWU{xY;0A47aIbGMGHx#T(e*?Hs0GadMY+I0rH z7;I92P2&UtjhZaGXfVqle^vCPNP@joZ4AF|G_u20?-f}G?p8{jk1f4U0$ zCv9@l(51c;-X!Xa(Wab`X-5*N1MSM~b5qmx3(+p1$leKU9=Tt;!=PX92zbT{NIzdz z(XU}F{-hmlzR6<*Uws@cEDzbr!fN7 zH+M$Ff#K_7Sah8R0m#l8c8^&1VwVm$pIC_*wM3qUwgeB-RiFgw3CiX@vky>M60rG* zWuT`R1Cqyr>Tizj!1a{PZCh}o5b0Cx66aOJGBU4-@_-+o{?0x9kiGNz>Wr+KT6a2d znd6}?`w`%vwRFKz=SOlTCMG$6EiFQpwE(BG2+;XjP9vb(AA!Lz;2UGBw6sXUm3pn)&3!x@WC;BSODHxmIK8Z+kn{V z{#shn%W1Xa>j6g5`VGh$>z)wj36=p%mkMjc^B3Hp+D{zl zNX^N))lgd6EebPJLXZbUF}xs?6aW>Sx<{X1HBYZ!I|rTkE$CtJ%(V>k_eTO9nk}s3 z%1{oRGot_&aN&6aw3QA(U_9h)E;ckde89L&0w&ZlWGiNP`B3&#yC(-iM2rFQ9XR`QCPs>QUrJn*`6NI!^1(KF`|dT3fdqC zz`0S=6#@%`0QbR+&+wRyj=;NnmLR1wVF5NYnDsfjh!{eC1I0=w?2pc(B9F?`$uB^O z>8sxY2y!d9&(BRx+AhMiN9!$+W3*&QTXW-U1B%m{M%G*>9G?X4kyng+= zS(ontv>T4izOD%f6sSAZLSAWCzxWl6(3G^7$5S`UG*OKqrKj4DOif)V8wY+0)0i^; zUp4^VXZ#Zr6Y*%U4D7GT{Rpwew)6mdc zizuOVtCf7F##d@jU;r8D2@}De$zeXAed{`y&xebCJ%mEe658Oh5ZZ3Zz0b$CxYt@ykN9hIKK+O?K)Fl$TeG-ya8qv3fqtqLjnW_tYO-*=r9#osXbpcDt(~EbKC@!$DBTDzsNVcE{ij zLk%ME&sVf}{S9`pjALXqv?pcmL&SJ7f}%lvZy1Wo%cCVup{ZMC!y{1PbRimm5bT9+ zoR2u?H`JgQS9s)73-(EFSy@?I8cyH9^X=DXP>86iN`jpil&N_N`a%Fyg#oA~@o~$z z5|EZw_ZFzCjKt@qHsQbK>ckFx_%L}0d>sw2Q>gInScRJB>Gr4-yg6P1#-WL#dvI+3 z<;AmYSR zh+y<#vDXs*K(RZwxR{upp=HC0Bo-)OtMj~&BiHt~!dJCZ?Ezk>bEjtaYkYbeJhbH^?zzJGI|BqTHI^_XW}v zn?@2elaE$<>Ht0BA^`E2@O=jxu#=E17~&#OD%2O|p?LZOU^HkTFxC;2NlQ#g*&qbt z5m?BgcE+ID`vCZlvGMUV8(@(!2PT(pC6LyIh$+Fb8h1`j4<%6pe2nuRD1u^%A zqA(%QymCvc9D%v@&+sDXB1hVUOifR}5jf&QPJkw44v;9}lpY$QSsnQD?OWdHEP(j! z<_%DU$6VTb0Lq5lFv!&nHZ@?-wBiX#tTRyrAtj@`E*Q-YKd%MZc`aVjT`&l~z3BkU z#c1kwt{&b#Cuq%I3hH^+*A5do)=(g9fi!wcH!tjikkYC#FbtRr>fPAbXq(yR89#HK z67t8;@bERXnb3x$puzbG76lUGe*{}6lRybU1Y1%Y2Zc*_;?BuILjh>(ZHn;jF3xQh zZk-MA_df>}9Dnb0ERayLV|dKAS0+{=bthlzhJ-?+96GSwBO`T^5sotKCrup|PkS9f z#;a)?GPSVqEw|(e*xM+eo+y$Bn>uGc34`a$s+O{BQ7{+eMP;R>TqR{?+>3pMV|a3M zav3F*bZ?Io!6H0J8{NOh8jjW=!(l^Uyx~6W$&+cN={}e>cQA!W>flj3$H-WJRa7*gFD=O*^P%kD ziJ2_qn=mQbD6Jf0f}|!3?T;fEI=brAfBL+btEGM${x`9Sxp5l$NJB5k-^D{khY|`i z%I0N=ceX$csiL)`!-*hP?Jm4F9e?P~bzJ%nGbeT_fMz$BI}kI}VV%!FqNhDNxB)nm zIG~gL#YTbdMZJA`ZWb}sf4Jbf99Ac9&Z9D-VkmR!Nl=tFc)yxJ?uB>*6Gs*$Q&*@l zyjc3Ld5l@pxx~r&8$p7&M{g0k9D+v^-LK!j)$zR1{tAi+nJx-b-ALkw)MFP#E!3jk zW@mqY)K_O?Z(qAMc61m+UGE|LpSV0`ZTJd5KX*1X<84qAgj(tlcK`i7NOsf~!OtrO zM8aLcLTy9Vye|N=wgBNM4@we?cLv3fJ-ENWkFwEFru%(h*j7R6Hhiz*vao55$%HD< zAN?_D*GgA7|HklX7iqHO|H(ESKToayw`pwJKmU=sj=v56^Z%=l{IFUi{hMa5S%E(q zUi!^I>besMkh_#=#a!(*z@6|Tur*xq8ow}kxufq$se}33*JyDJO~DNUZ&W!8XwHQ? zMTR#!P+N)DadFYy(8ew7Enxe*Ox!;IaT57Te0^~{VJgHA2W&f8oS;kn07}orfh}7* zyAEr=1(@k`1?G%J*1~-_@OaR)`Weq0u!RVq`2FQR-^;xRmDx3DoV5V{ND~C+jBU`l zywy_=>@|CX`Q+qeuMeZ6^=r_<;vEJqfAVn&#m)Fw!gIg!0K5%s`3K~nKMPT*%*@P_pSGZlbBSN*IrO%8p|fZYjB_P;EDT^r zXlDq2*^>c-@{xM9P=+&=5`{$786=J{Jp_N1>({j1>?{q!+-g zJOBeM@e7bp$-|+F=bf7R@a)(D*zV1gK#jHqWn@zEvN4PVCGeYT#6WA+y{}tx4=wj5 zAukyN0^+TsBY=mDT!A&U3SAAQ063*BELy6LZ$!Q|{XfsWw2b8{|-o>pnzhM|uFkEialvv2N%^ z2qw}SR1_DtfI?`sp|P>%eNb43zUo{wR0fwKn*AX*X~0~?&iFGLz3tCHRWOwUn;FA^8_i71GcCWe8w?=d7Ah$G_r~)tsds2 zsNj_ZE;(FiG5`jzP@}lR&%q@I?|r4a|aUWdtk%D>;cf~zBHf?oGrHA%0P)|8yk~|9;pYsALZA(nyN$Zo#5q$@%lH& z9|UfP9>DyW;bs}&qIzHoH;r1M-|CtIesx`A{U%HtwiAM6KcJ0Ijf|q2Et7>E<_Jt! zV4jX0Izb#Y2Vn93R8{A$1{ZZ7_-DFM)}Rcr378@39ZU#0j?bkO|D7X0ZU7rLACguu zbG7hW$b*QT`0kh(5145soLoLFE%^^hK8G;WSwQLuiKsr~J%?eqz+@wRpd`MQm6LN) z(P)S5t+px#gO7yEzh46HU=Cz*d(QzgIG2Eox(TWh(}bfs2=r`w=$uPMRFsXa?X8FU zVC3s?S3@=tX1Zx0)*UzcKcEu4sv10ee18gH(?6*w)NdHoi~o1$^-BG>^Wx5me9#8H z@nW_w16u%B$DIzPCVf`SBK)R=pr2SLeErO%VoIIMo?E*w_y=zY!PxE;P}TPboJ^zLP?kn`v(0$t09!> zVEG#06pmxV9pU7uxcGPpXqK1Ycmp=2ZQHN~;^yqGM-pyZ3`{p)AK~nm#B-ZIB~_4@ z|0PewN}N3%Y0L4F@#e%EmzOEd0{Am(T+F^4uB_IEc%2_{U%vF6RQ~kwMv=GL+aI^( z+58_XjJ35dhZu9bPW%?l9=~uN)ao?#+84yHtuHKGD_QeM+7X8{F<+}J=R`IPBODw- z0a}#`hj|Zf3$)+Ql|$8Ljx!Eibxnv>9vWI&aYKzI)DBuPgYWKhIi`2sH!B&qfdeh! zOSqC{l5P-|{A(dG)ytjIa~v?rf;lHmJV^-3#iCL9uzmI`9ss@R$#^nkv{>I$ehV__ zpN(&ZMg4%?+T7dGf;T*H5c;&!AqP;#MMI+@Ap&m_pyWFmgaZr|0ZN`7E(=ffH&Kjf zrk$A&fL;=8rIdtJ-!hAMPb8&7x9<)Enaa zrT{{YOrI8jzLQt>`sOJBv{bC8t=Nju;8_-a&3D^N5awrqhGx329=L5RS$sdN9t zLsV&p&@ABz3YAY_Uw2TDk>#5F68_5Q`YSaV%v}e^%*xKgNmZ7F+K`^T_QV~Ka?N=F z_O%*TKhrzOho6S3*mYx90EiJO^>dTf*X_a0WYNH-4nlzT{QP}%CDbHnA9J7=V@7w8 z0~ek#G0Eeb!*JlVK7$fcE&g;_DqLO^e8Zy|qAmwGSbt)Gx+@2D=*;N)eQ|nR7y-hj z;}FV_C>Ni<3dfr`z)R+22mswe!1I0-nh3ikJM3XKE&7XCtAFW$l)wXE?eEjRY$q}D z7Q1?NZGj9V)ulXwE74J4BVda^n%;ohFC?EP_Cmju!$3Ij0^N1L($>NH@u(eq^$wwom|Bq|^ylf#vSAnfA>OpKwqslRvL z@b8_c1-1a@Q4Q1z$_n}LH-fM4eAgkXJNmLC21kB45AW6R;C=r4x zzB~3qAGd977v^UregP}i#f#AoT^73bn+Fno=o6Juya~BnZ0*yc`!A!G|KZ?>xzLxd zkz18IcjhS!ppTpf2I?6Ez?+{ixZ;wfQ3cNBc`)DxZDh|w;X{c2tf-f=Li^DuR9EC7 zqHDxmO&GWvenZ0Lo9OStUWko~iuw(x`Q*$7HMiF33uBb9IWN-%{UZ?1eFA~e3$FTw zt*Y6i+afTo%Zli@{?7OU9KG71#3qq72LDw9?(9^rEp(9{#c=p&~KcqGO-Bz%#P;ubfH6xa|NTL#l zuG5GEM~@NEjFo1=;TzW*V1^3(6d_-2Q*giP)6VZ@M=rmIahApgW8W z;!STr^W3ugq9C;E^4HejMnnoA8E@|LL=*PSwdoY(4V=X1)^0z;Lz4=Zwc;K#MQhC^ zC?NtPBpQ^m?0`?)wwUc?1tGTo+v@;b%Hv?M=|jZ0U01#buGg$-;~$ts{@!RAsL5|LA>?QQ4#J6eNS=oIB#=>lDCG|4sOKKC^M3jKISHsxTs;?O7uWlr3XGgm7PT=3 zM4b---o?2%Xj#yd9pR(f`}EV;2sEHzFMLG{k{u}U{+?iyFezYzw#OWSpmzygejMbF zYuy37$_G|3`!`g2o>FwBF#s|*&s*W_9sp-&fk(Z=8g$zB9|A$aZu%-91%bNn4Bodv z#uS6g`ehFW0_yYTjkh5-h;ZrEZYO~}-B+kqt=!;siTSwPcLFOSv=$E=5t!?RI1s%nn zwP6Y1F$hA=;z)p$47g9g3*-h?)OjC{l3}b8i^aW3^NSRH6IHsPu^8;E5Kc%`%@_o2 zM~|58ZdGNmDRUPHgrd;v5Cl56^LOn5$oX~Fflhb8pngOh3QVSsxI3BQ#l^)~fW)sG z?LfO}trhl1kI+~2?A!y=^oX?H(X7PL&N3ruk-Mb-Huq3r%h#;--wT{HFBOh zFyou+8TPJnDdad>P2I!BOlAUN9qCK4%N9IyV%luIRyWM9}o8PEVgjLc!oN zZB9l6bbu+|DtaGpJUW_x!@BPsKsQZc(>P`8Q<9Jrfyh4%WIU%Ge*c((J;+AM!Oq@& z{r&-vU9GC10NFw*@pHhJ*u22S_0+Ot;Dh3MRTUs6wLAX$KG6uVt4s(E>qd!W;#lxB znx(q1618~b*9fI!6wf&+oG~PDPLdVtgr()mL$6rJL z**5xfMumKsEajQqP6MJO5|fM=*z$DX-5qY+AoaIFggJZlaYAWQsxc~f2LGi1{g=Ov zUYVVPL|r*>|q-;jAsEcXa!VI8yJxvyAt>km8F;-g8`M<)V6|u>n_&rrOy8 z|NBUlJDzub`;0wSNl+3$$&S8K&u0avNhov!$C^vhfhCQw@o&tc(tR4s6pHh;$di=%{{`o1@T%X8K0>ot!e&S=uU$_-T2m%U7wc|;`=%TO}MdfB;j?QBo@?rzZ`-M@JU*9sVe}4!Wq_5QGX9Xd{kr8=E z!c{D8$B4t%`Tx6F6E3Mu47Pb<5+`=Q*6Nz~X@tsiQ1hy{wZuZJ=8|l2-+A3T*S({& zAJh;Nd?%HB%pE-Yf@)GnV13=w<00X9I>{3{B0O^+@R_M3ab?^#1D;?k1iO{@BA(qy za4QL!VN0AjP^YX?+}W$?!!g0JVOs6mnhTOBRMwAWS&AaT^o2_Jr%ZHcd%X0kq&_&% zK1pgL_I$^sq5i6ryYh>bgTW0NF_}I2wW*>u%$5)BHo!sCZW_|b#|bGcPnxgGWS28C zg)@wsf|oOAhWC!B@glM;B{Ej@<0{hGKBJ2~5!rI!@E$_!H6z7HucS^GA~WK>*G@Eg zw7WX0)OwOO5+T$(mC11{|7=y)gcUQTTqL%qZG436;y4pF&b0S65Uw~;ep$vJXF?81 zslJElA}e^_PKiewk6FDZ+Gkj?VBIK-S4k#mrADB!hLeZ4a?#sEZ&O=42B(st@iG?4 zSitqxXBO}3;~zE_K_U(t$j7cU0s?zPzO%KcVDs3EQy`164EJ`cX z%l7o8?pI6rEwnK&8MEQoOZPaaQWy|5IjvrCZ^*()>ssZ(t`A5TDT~Mq9h^(kjqE8} z^1j~TcY59qS@=`j=7p5n@Z84q$)v^?yCUhxE1VUMO2R%$DV@H;j~-@BlI~W?oV^W1 zE2J_82jWP@hmGn1qSc{wp3*`;O%~r;$)4qir`L77V$MsJYI2{)b~s$}*2|u|ZzH*{ zR6w|*UnwEzK!rDMiTL6Ckx?ItMivNG2%4-+RmeA8IGdb=PV_9}X)_NF$E=8&T3@O> zT;d6+)QiW9#qFe?9^)ozVh5qE``Ui4XkG??s)&14Ua%px_^Tn#$`sQ0G{LSH)`?^q z88Jul$q1Ew_3#KG;^|Ag8SO0ODTie?b)z(*xPh-Fcu8;XY~x+ty-jv})=&fy%APX` zl`F!PBrNqa%eu=y=g#X*gHuc1r&37IT#v_1i5E+;oGvMWXWbAlj5OL|-g8Rg3x~7d zUKOn3q{NQ@If2|3i#$t8g$?6IR(L6A+K?oF;y*6*+&cs^%>5g*uEqax0b&{_`H+wg zd-Bb?7>nzzw}q~G$FK}$a5y5olJ1sHy*QO0s@zYqW^b@Gsh(JR>eyTIeP_&NMbvQ1 z9NSpPJ8O2;J8S$vC;!>6VVjB$;|V(2Ws=U<>&(Mm6&l#`0*bj&3Y&V5RZ{NF!$L*^IT>G1e5gO4*yeapv3-Y1+!`Nlg(zSbzO`D~2>)!OlOdiLqruDy51> zzf|QfP$O0sd-eTIJ}&Y3RnE3D6uKoi^oHY8E!6+G&5)1T$}JqOEetRZXr9GvMV-MU z1~273`;~Xs>5av+?Sqw@#wJck>9g%6-t}S+VQ-P~x~a7D&!z0`o%e#;eSf{%aD2)p z6R@o2yu5!zH!0XNvTSFJ4OSTt#RgY9jSu?dY+jY&5Zyt6#L0gY=+6CeMYA9{sv-^U z&|ITt{WZVHwCqi*xJTu#e2I$aN?MnyHv>}eoVeEYB;^(1cK92H?fKYhhAsbHoN@nk zWufqfJFC^a=^Ce)amnUNPc8}TXX3?@jQ!wxX{#1mtLO-+E^qWa#ik@?E%)MH`O!DV>2#I3Kzaka5z*dvKxME@ z*!g;fyO9Dbxj={QtWAPwV)H9s44E9QAT+dh*3Anr#|-K=>O>RvNiGNr&PA^f$ir(+inZ7=QhJTN>C|?V9HySLJaf|nm78ZHWi7h>?DT$AXa_LC+ z+Z)faWv}_+i0!N~z8lpmefBah(f2crE`oa=1U;%b?nGAxg@#~5Gt6pwg#diLYkiwT!P1vK)xWI<9eXZ%M0XJU4 zhNhnOm~?3wTYHDi85zl}XKCL^u?7v%L=(av)^KH91A{PZt=duv#zx-RCs=7+ZangW zD{EQ1#4?z9hSlQZh_xcow6_TpME{V4y^8_RI-}9St2b&tsH}OuEBWSWSc5iElgsuw z!)Z%CnfN%QZ+wWoGIJ^Ptz2>I8ndwbrex{C1meL zd22&{ZVHOY?4C0Un6SYrl^nEh6$V^qYT0|6<=9I)`GUYGpu+}RUVds)1YUe)qnhhv zomYNy-i*o$Tkx(7(OJ|fmwKaT6EpdkLPsR5AV7X6b(13J-aVYj(XES3U2HQLi*?V+ z%qK!W^8$8FNjBsiGsrPG%Usy`alOi?)?7U{dfcPdLjGRf|9|P1+-OrksKuqQs-90}7k5?py1& zL^7ee8Wc8lmj2s1&F!us&*nWGE_;vhZDuhx^F%N&(T_WLBsJd=3`pRw@D0zt=*8*x&q;ZK$C{lRr8S{Z8GLn^Vq7@L zc8<9Q|H{2X?x12kX57XWyX$9IzZU*94;4Gj%KT4=C0BqvecG7>l|Bb5845S<_z5~4 z+diB~d@A-Rxf4SyzT$KCjH?(QrYhCnqB4zZ?>>vuNaDXSx{ZjYswgo$uDJ0^X(ZKV z!Ad1+PfKBb5c_QVPRstxQay*Hj+JfbeP4Or^(xt_hW8t9iJg8+mHIB!F#CqNZF$3KK_)W5$9OE_nN7Cmp$BWI{CtgnG~RbPzEYTrrYs>%jOZ zI=rh9lDS(IKUqeZI`ST8oBFXAGPxHKW+pFCwS?NeuN4VM)D$>UCBeXg3H371V zcRl}N-`TV-t)0lv4%ylPq1ARfc38?nw zB;l-zTyi@#xt|Mw92d?hrIA@Ou!@!zf$+BMmNdt&jH9Ce=b8DgcSkCN0Z~rYh2Jxo!!y=m z$_?*5EdSi83`WZZoE}S`c~$z3caYayA0) ziox}Fq$rUK{Aerc8TEF&mB)qUFpL}?xn?#+S}D%`ha`unAE)zuo>K0lx$rseFzdwJ zsdF+MalK7#85p+TQ4J+p>+XMi=ev0I(YsPR3R+xDEimg!9Wh-O$&olS#hrK#ec=E! z;C~O2%oJBxJ!00EZ<`!V>1LiNo=-_UDVq=BxLjl|5|4rJwz^lszGB-w^=LO2iMtCJ zAH96J?WpeeVO#K{2kG~FrQUF_%3eAM_%VbrWq+)hX1Q2#HfL~wx~=h@;eu!8Dfcc_lwBRyB)oEQAdw977W0^U+@l?F9+;I4>Wg;1P`H$B?d0wd z@7niK+-DBQS%0t_*F>3?1UtSe^{JY`(BAbCRj!uu*4<5xo@xz>tt-}xkMEz@nx(*) zVk5WsPq|YnnG50ek>u^waK;e!A?p=%M&BTSm(mO-$`>zMli%BVwnY)Mz9K4=>sngL z+m~9{UG?33ZG^)-dQDNH(sA2^u)~gQA7{1u#hv-XCqDBY{Ucpu-m$ZiE)9R8AL*7; zCVBbwj78Oqd6+c;h0Q<+hPW%Y!vCI;WmB!}kI4TaYScHotCu)C5c7g8kXiO@bws{a z$Pcc6ew_OH66fiLt&Ww{f@HJQMk%>gWcuaS)WoCv3SvL1bDzA8f(pizJ-bhM_qd-C^R$=>YUPd!e&E;t!)OxnS$$W!%=7I! zZIbgXw{UV5<(Z8c&JmeRGhK!g(VTLD8j{n2o716YBQXLqi*~t%TPDsFTtuffA~?h* z4u6Mdtca5ox!Qj3iObOOAPo!9@1-%cce2QrRwqri#3VA#o#yVl5Z%i1BQ@LMnYo)f zQC!)%=U){lv3s6ZMTrVaCd7?J?;bdv9}E!QzIE{Uc~)2-{rDkW9_I#oZhb?LVwiM^w^2L5N@`>hnm<+olyAo0?m%Nn=JmgrxwCIkW>>TQAI+Z5?q@2WCl~ z`@69G)OXkh{G@Pn@DMFd6E158DW}s(a>IfIEyJx}CWL*Gqfg~HIIL8bcjG+vTI|yo zz8Kw_lK4StQ6FP^hVl7Vr@YbRt0{$t`&vplz1pVHYl--MDSASpL}|AZyskR=TsDdq z&KQ&p+_l@8s?j9M@lX^xbE2EUEn-4A`KBVL2sY0;Ib7b9Luc7zj`^vg2fFiLC1|J; z4tV)EW~mDHR9E}SwvzO?>JG(JTkP23FH%$yUjP@%Dkl6IowpP z6n}={I=<Z&32WL4=$A|Zj-P4t`0@6j!f{JVW;()vu6aqx4ja}@f?@kME$mc~ zHRVF@V5yRyEB8@=Kh>ICocg_6utU0;u1CAsZL!Jk`g`hTlvHobDf&3h4hQKuX(r=t z3tY(*rYQ>-_IN=)+EKR~dw5-r-{a)Rp;-za^OnFH_?e0X^QX?&Z<8DTIZ|)mxQp$a ztC0GpzfuyvSQh5?`J7<^1-{&*v*ln5CQ;r#Pu&WH_ zDhxrKAJtQahK#~oNFRzGl7Ldf5{zn8ZBmN;`OW|&fTFB!Rr2_L zZmYPj6uVC0Z?*1PMS`hWGm|6luj*qOtAohwSRq0!Wk2am#+uV*=yF@(&6{t{3>2Mb z)qiH@2|8k{sHUlQ=4uzbeCJIpoEX&R%2Sj0258=o)+hPP2c{?Nmk>3HVu)ChUhbS8 zO%~1}*$`UY{G(v8DM2#HsM*J1jn#p{oJcO6$`z2Q_#&aJqjSyn6&DF4*4Qq)8iT~JF-6nfP-N-8$Q|vEg;zWN1>Cu(_++ z5_x5DJg)ra>x(~$hdVeXLN?FjF?=t+f#thm_0i$p6XX}}I6pQxmuS49b8ODMJ(;c1 z4X+>H;5@BCmMaiy?)?CAZA)`iU%qx0|5@I@r%67(cBmLBYhwZ9`)!dXe^=$Siw*Pb z&-3Z{r96;4-`MH9PyA>VMJOB7v(2pL>`%(PALsHGK~CVUq<`K^WRd0fBDz9i`EHM* zAknmC4A&@hL^UoW@Fq!D9Y#o)m4_%_mUW4eIp`#bgAFh)$nUJJz;5g9j6lEz2b z)s%hHoMdTcAX%{4+zP$8MBE27$C#Lzmaj$WsH$~h=@NdEzo*vDvqB&Ff4aVRrxW@u zp6jhh#`%^#A@IplxV&05n@5={mRZ;`GF`NR;4(RDN9o{GXC{{1Z+<#V4EL?1UMUrj zC`UvR296@(5S(tw zaaj4~8RE=P@Yci=|MHo^Q*shMeGgWTrA><{G?G2go1-=X)`^~old;jzm2AiQ8s>rL@;gNfyb zSvH;m|HGR-(;bid*EH)!P!TeyMzT&jtn~MMKmd{az5eJ9*OTib=E6Mxz8TqtYBn3?W)p0A>-=E z5R#BLb2vX$u`G)nUMo(Vvgm5j$-@F5h{OqHY_eq3zNNj{1&KmQQi4%c!=AV za^ywjj^ika?;Lp&_g>)hbc)6u^Wx4ytZIJWKVLoT%nn?-p`@)OOijDHwS(V1D_$ME zM@CIE?pn}u%Ie+-!_6M{@Rmj=GOR*I)U63&6)W>?S)#tZF(D@cbNSb#=W$rSR9;p8 zwzYE3U#w8e>SA9|VKIyEqV&Wrd*InL#qMa~uS+58IrWrB^e5h_irEHh6(%UB0Oce0 zP$Ks4vKJZbez~dLv%Bn*?N6C!O|DZax1!iSrz}wEIjTggQ=>AgoWF>@(^yugu%hgy zs?#newNup)od_FYI{df|-U>@n9g zjU39lAG}el{NNJQmHks61?sPpUMYI}6w&cp7`^xD@!Q_`76&}+yv)vs{$%O`HC9dN zmeyEZVQ^0G(C)jow(RN#ZkdhFWec_W8gun)f5GB3ge;S}#?`-+leugq=qmFWg0SUE zPSu~+g1uuem!>{@&o8M)8dlO!9=WJU)CjJf&D}n`mpuA^GGT-wm%Zg*q}rd9EHFGE z#jcN?zAzMdR&q>I{SB2F*_Al$L$}e}J4bJOAMv+jcNLykPi6E|n;&xT|E$^4xxjWe z>fRgNMpxb;OU-=BKdOnoQp7q9ZdP`D`;-NCH=DXA`sb8R>*H>+sW^Di2hIKV@80QY zE6b%j4m4CM1_Y|1BRN`{A=av9I9IAw-m=I=9HnS6EoamUi%CB!Q7Q8bp&a_fe1349 z-{i+g>t6bVFzK2m)ZJ#cMn(r+FSxPg5Y3cj4}GHLb@+wD{GjMCe$IoW@yRFG>)u0k z3#TwbWy_nXI?Qi}x+UEEGczq(lDj#%a0do#NDCqkH=oV!_s^xcCdt0;$=M(p7v_AY zMXaM5a{X?F0)J1v3YWf5j=3d$ZB~X5Nne%@PkDicCfMbH zo3rN}Ghso)`kb0D|LQ3nziGDds%xSrS+nXF1TXxszuq(Lbkva@x9*$WYcH%jb0i`k z4*WS8wAsy?MtJ&;)Fxie#+C0B=gNpy&gRU{7BoNhaM(DjV|Ocr$T~m1_yGBqy+zkk z7{V+Y+$xL2{z`SH^}dND;jM&+d~rUxB=@yKZbR+jT#A9>qGtZsMCPzQ4wptl_X*Sr{!xkgLoWW6ecY9;cMWGR_bw@amyS>youIL#+lR`0$E@t_Vatj_OZD`wxL z!(xJ-S$3p0>t$G7u9g==0fUxV4BmY_n|#4P5yEpylvWnE2XIpTDntA$Idhku4y8V5 z5p+T7HS#KCH};BuT(e$G*uRthY8M7AJ)AM#6%&K+&wkyJ%BH#&-xjrAl_2ghII=AC z_kaq*R}J+zrNdFYfFDLZj)O=e!=&Tt*ztRPX4>XilG(h*NZ#E%dL+-E_>lbh*C-L< zMt6ki<2v^HXJ$%P`3yd$#yyPB^l2;!DX-txD~G4N8$?m0!{=u@ov4<2i%4vl_p@3JrPH${KqJZ0NnW1$?Pp7az3_eexE*L6`5Ir5Pwh#+Pr zVLkIj7%_cmYo3xA;(W@R{N;u`k}Qqi*>!qvfsAT<|EzktqrS|_&-8L!=bIaYWQe%` z^`m@d+5Al0N~THCuQ-*R*=G9WMulb`;!PgEg)2Qn$c>WBJg&;@EfU#qmp_lav-=23 zaRT}Ud!1{P57d^wEkkyotSdryAoN6(iA^;>s^ngEkNh{ z@zN~He6GtCUf+s8(Ydq1C@OJw+)8d@@WlYxr1%e}#`|i^l6v@k%fAaTTR8xQr?X@C z+RTy63oSy-o>_B~V>yo`h&w-9dSCZi#aOK1Ax;-t7@k*__jGSfG)V84j}oj_E&2X( z->2>2(Be;DJu1J4+~f!fW~0M&hKMl>E+3`H z(Qp_uuIoSI3tbUMQg3)GZ>$bpHknO3#7jFRY57PP8~p4Pol&2ped*V?hJP?@En-0z zB}m7UEk`4mh2whD%~;h>H4&>@aX##Abii#kC_&N`H&kl{`gdlVJ&_|#{V3Z<+&V)> z##p8ElJ=4Z_bdW;H?B3QvbMb79x2n$cwn9Q_5N>fJ1^e!9K3RxoDfpYVyV)OF1=uF z`msj-o?cTmFWgUwxY+I+$tf4>?(#KZy_C}raj5KhH|~8-eu{bjlj>z3qxB&UD|u8g z-Y%ba0P|p?Xt4DBkoyWzohH^i&-^G2Ulh=ThY3XX!zH@ zY6OSgVv2JC$D4 zoSGN?gFT>2rCpu}ivVB*2 zl=_%xg*L})tBghJm6pUp#;^i1#q4|g)||qvsPoeJrS8pV8^~EN=S0y-N8^mUswF#r zXAqG|`6IcJBJ1o{ zj5~R#H4eNS7U7V68!@vTiUOYp`l_G43P_ip(O<43z_2xpWlz}bKjhKr;@~tbgP>TS zbt7o2n;p5$Qt#z;|AbA$W(FH8!{iHMWuKLriMn-mhDq@}p4s2xci#Li`ocJL?#!7e z5(LJc^Fg~(&M8dIr7ns)k6gneMfG4!vvhtuSAoviT&^2u`3fm=r| ztbvG%kIRQPkv84A@C0k($GR2IQ7y?gr^1Im@mePbY(vxbLO|@c+UuzbjNzNLM5LHS z+&J8@A&Vs`^E<0lX^&OnP?@*679I&J|F?Ba`kKdY|93YM(jQ@F#i@FdWrU{oMJc7< z{IRAnSPsl7k0t1QE=FKRbR!{`3wfPQ_kJy(?rnTbPwCFj}OdU^tQRSQ+iP=Nmn>|$((ywwfm9>WMmP_hHJi!X2DskIV zPuh)y%)J#F7DB?7q={BNvb*fhJtOWt!kgq|uyVz@oHi%wh1;1_%js8HD4a0FE!DPO zpM|&0+vLGXI=P-L60;@kp44o8B;!jlSs`_JLT*BQk%C!#S@Px%-b%vUPthiiGD;ne zt#8kWcVkSS+99k2fz!k1Wj8#1)?+E9zb&RD*_ZM--WWDNfq7foZ81&TwB^8CD=~9- zdRUXBX)AwN^GeRWjszc*S6q3d!Yf4rJ@3V~ zhEL(7JW82Q{fCdQ8=WbvgMk_g&3pGws@}g(>jX1vFpT+Uy9_wqOTsmOG2EE8NV$%$3Uo;HVL?&+Nm%>W+$>Zrh z({N&DMQJ8SXU(m3>c7M?iw~XU8;;;=OZ*~mBcZEWr-Y=v;T{(8kRIM^?g05+Dqe-~ zif(-z;H4y?N@1g~qoR5qcrK2C{8SzojXic74F3BzB^tCe!^}W^1~)Mw1$O+yr$3Z@ zGbScpWKC6hg&mXL#7eu@jP44_VA{Im7e8lT(UE($VuA0(+$~36pfLO3-6bFAYS!2c zkt4H8a%7E^|*HD35JL$Dl)TOtc1nxe8wY zqTD>BDF3E*80wtNXxpIUo5S^&I;Q(pUjQ$Mn=P%tGg5r`!TLBWC&fK++z{!!DHZ;v zFrFSl!!hU*RRVA?EGyX?3#c2I8EXw`PQt2 zeob){=K10{H8MbB(=gvwBbWb<9#D>33%6eW&8O18{3X`#yMVI5tEd?Gw>q@BsB1K; zKnVJLb1R6ic9>wR|9*Jt^M4-(UfdiHZ^hGrwa^+{_-5@Lz51VV=HF${Z{n=0o6c1|d!UStzfT8Fi7F&i=bVf7e_k0+l=`0=B7x5W;6Q zy1xy>-lGnho1h54Z*}9whY47bdI``KWh2+Tef+r0zMb$XK+s!M@ld#ROBhT|hU#H+ zkAYLmR_m`{Ch;}JKw7NQcVGRq|2&WBcro}rt0gs$PmMTeU^_P54(Gf6eIs;nv`z%P zZLCjEJpPXh@C_Z3_#oN$?09A3&i^I9rJc`(ExY4WfcN*Gv-tOk{k<2p+kfxo_-9m9 zJtzMqi~jv-S<}C4sN)~2a9{a){ILJOd{vF|sX#o>cL!#4XYKyGDX7AjEc)N2egd)c zBOB279y{~v7~fJk_}|k76}t0Pps^PSBLX!r?fU60av8|(IBVv>RV3DoUKVy-c+>qBUfR7WzGV0kcw2L1rRKOcL%Aye7Zo(IR zf?i-jI`ESrrTy`i%=Y|um1xNQ-rr62!6s)zf3nK5lRZ4J7rwB@jO| zXL#Jv)}|O81KOwUy^a#c-`W1}I|OjwbPNm@s`XC<;^BMp{2*(jC0Pgl5oMN?Fmo`m zW(xv^QWjgsp(==yL;Rn=cdJBXC=h?iFNY!^b_|A_hiAiDqZQ*P*|rwVRUTfx{<6lY zqyBrva#dXSM-xD^=6lPo8XIe0WMyr6sj6zn1EU(65guRD1TF6nk2N&heD?mXigO!( zR$#Tg>33j?1ks=R7C`aoS^px75x3U;jxlTLYBsLC!8gUD{z=!VEC*_Z211jW^acw? zs@6eMM)t@2Kl&9wlZfR8j_gU-C7n0i7jv2hON<>A%yN`*mSFNH#_q{w+Llu%Pqvh{ zEDe-!Tm-c?b{?LB;So?#s!`E{6Y5>nB{_736e<6{T-|Rzef}H`Rw;~6;k+o{*8*M8 z9T3p+146RIPm4YDI`92#qqh=uD^Fi@(0;n`;@gW;62gLJ-8RZY@m*aX2f_PFbYc8G zUt$||j(Z0qgNN`|w6=z^Qe3IN`B3+JKjX8cq~XZ;uRvaLc;88^cE*l^uT9UCz2*9| zIdspo#&IZ^`~JJMJC9EGCE*brYHMoIL8GUI6zm>6;G!I~mdmu>r8q6X2w#_W{tKQ$ zUY+|o-6gXj96!J93}(7&A;_S_&UBf|n3n`8Lh81mX5U*4g4oF`c zV{ay-`T2Ovq0dcd>NCTt+Ly0*L|n}sVuDu}`in;4BUdX&PKG{RU6s2AVszajTj8;> z&Ux9{7b<|Cyn`6PUI1P_<@105&G~HW>*uOrq;D)4y!mqa<}KhO9&Rm>oZl*rV-k=3 zXy7pzxbp-?8dt;F$tmo`=F;Z-iz03)C;Y-gK8;i#(7JA~oY`GlBhD`l*R$%VU#`FT z8sK#K`2&z4i!VzS-Jz0x_>hb;Aad<+w{M06<_|eG=v$cGJR3ZdZyx>WvcBN^(qWsLT(kShwp)BPaOmZ)jm?E2OYLx*qXRe5p1ydP-zbC8 z*WRu*SD?rP55BLx>!Jv#_t3ytRXUCV^aFT<)_QLk^v);3c9{+)5Zpr96Wb>)vvX++u>3!)efK|?{~rErL=h^XC8c2{(olq^28oDl zlE}{9l(v;Jl9{si$X*SRk+L_L*|N9qb$8DBe*b~*51-FDk8`Z|eZOAMaXqi+bm2M0k4Xw@_Y_PUo<~gvzb<%eHavQUoc8%D_PN*By@O(BGRIyJ@22 z|Hk%$rb=A%6@pl-sab#C**U&^l3!IZC?maos(S7wbyH+bL!wQ9<(MNOsdKT3!$ob2 z{YaA^{#qWcX!+LYu)@C+5f?SH#iB2HsjWN1wcYc;0d`T7kHUu5pMrgUeG3llV0V?# z%L>{8sfVa3HyVCLQ^O1uhQ$6#yEBX_qMyH1vssU?rt=>@TrDg6Gog8`U1^w}z6YbU zEs9COFNO7gCe+A;7>EVlgGYxu!t~?)SN0M(O}}|qJk5#M)jXzTO8vjJMj88+8WyO= zWnF^O!AJ8)w(eOSxyJ@avp9ZUx$&$p`{ap1Kc%G8Gj;M3e8R>#T4zmlWPThVy4ExB zpFHidN?!v0c|a$&NhH+lTaD#CPn`<*vy1V3cbaZvAKY6$pEtgs4x%>ycF!%uwyCK` z%iW^0N8TU3++j4;FD|>TnJGanr>^$Wh=@U*lljYn(?|YleXENV^&8)rsCXWCcKMoy zgv%EOj$qsVz-uo!gIZ6|wQ-~xHhR&zK4TY6V)$OQ67|_>)jXvA_ID9%T%FThou2iU z5a%8$Id=y8{MCuxz`R@d zR^s#A$;oHEjhf>w(_sCV*AC3>2M1jX7u?jiBR|I!9@JJX8TT{|{48*R%}Z(-p^{dj z()aIy-m9U_r6kYqg-_)T`3py^z`IN1y4IrE5-FuCD~6O5I%ND_*tAem2Pw8_wE9<0kiCD1Nu@|MaX@%D-_E*~Mx4P(daao7 zr++pa`ybOD+mqwDxW!g_UrSu2aa$iJx{Id5rZX}|*t01!F)TmWp@Ki&7nFZ)!{)SO zYq`U#*EN_VY*PZFtH<3@kBu#3->l;n6xLrT2)~XaQ$+c1>7MtsgCDRV*V{OgO@913 z+y5rdUP)X)(fYG`%4gSBTl;i%Op>I`THl139=oe&^QtlZn^%4Cp{Wu6tX+Q7_M*fe zY)`z;G`csIbl8rH)}+oJQcO~5y}FQ$$;cb6uT)lkcRyaq+ow@L&8|;LWzc}rW;|)* z8#b%axP?>u3p995>xY=^#72JZ&HA*O_Tht@N@=R{q1hMDo>@%V>nr+8?Y65BDEj*S zebWKAcgGB2x2>`yAz`VnD|U{KPGv`8gT0GA+?P79y$#{@*83 zpnn`2>-XTc)4hAXr}+33G_n#j!xCQa7b7kol=sQY?|Su%`{tvkN;LjooY&e{sADDH zs}J|pC?+QO&7`!R|5&bnPt2l!V$;Bd5Nr1XQ}e;6It~1ehKQ*KCbu`_T4m;~G){e5o)!B&s6xi-N7p*rM!B}4YiR;;}32-4=>ry((&*AFd`Gati*o zZ*I{McT2cOUZe;bPUHFHw0du4ReW@AvzzeNt=S>rb)&NfT~fBpu)tx^nW-%U&O=jW@`m#`u6g{UUYCt&AoAq&k@hq`9zfA+2ZTdm9SH$}WR ze)UN@)w(7e~`vbJ_#+Pm;QvF%@&)+z#=hF-W=ttQ6~ZPV3V{2D2>#yD_! zsryHWOZj%;v1hH5Csg0$w?W@wDaA2o&doPq>2ySgl6^^;wY;Jtn`ZUBXZ0%M16zr6 z=g&`(%Jkzqy8mrOZdH48_{qPkof*7PM}e!Pv|9ABFoN@gV&3chSgT<^o7sgo6{wxvL77XrpY|>cdvnijMc}OqGYV1X6-wDdigYIIUxy!7Apw$+P}#f z{ZG{R{oe&xl~?<(aMAkmS&CGt@uSePC6gRrFSO( z-Fb<_085%0N8R(R+yAVmpAw0U<`)qu%Cwq(%+EU{t^AbH!HGxJ^7{e3f`GUXY(?4F zK`!nux6cy->wIY1gbR#KK#f zPM-!f1A75HFyzbt;wSX{x|KWQ(l^>Vf2sAlhxwhK7 z7!xL7WZa_A@WpJ^v3TfR3D#Ay#nddq?i~xmpK3;5ykrV4M+7DnRxg;X$S{}xpp1I+Nz<>UoQ{yoNu@>5l8^KDOtTSG9Aa4SQ)RLQ6dI^_S#woY$tk-Z#7y9EY{M zpnNqIl^s82SpGfcTmNm5b2i(L+#2r<(1>G2Ja2omDM#ER$)AL;yLaL%_dF9!3`BnY>KN@^g)wquTE46=m zS}-v!QN#9koY!3YoYCh}j;!S2`%qd9oBny8iGkG5uP4_jt=j+L2*<^O=Y7fRQ|9*Q zx-@Bh$Tm-^rJoYZ#Tu>7D5H9G-Iti*RfOtW{q!DR`chL zB_*lsSi-&kyPU64)nP{D;oYk8YnQygT{LbsxBHW-qeT2X;JEzv5pG%-uW@H#jLLzb z@DfOmoWp{0*LwO2@73Q=Lw`vucby2=+{|t)cEV=NX43vs*Qf1~75>KbV)rAMh+}J$ z6|G;O?_>EJAWuN_hu){b5*(~KPD=-TL~R&<2KXrx;Z;Se^=n^8ns+WQoFeu(EYBym zRE7l6J^i9pS#4V1=Or4-ds%8JDdv(lGo#z5&Gys!j&;dJ8oDyqudj3cP~y(j2c525jKR5cuJMlz+nHfFAkUBffe6?-bHG%xSP;XVU8TQ>N(09OA)> ziLNqSeeI4@Pjz{C4h&@-o*r{rMafgKMKNLiDQfIL73FU)8n+zdG^jn8c>YR*VbVfP zCyE5Y-T|e4fkrmA(hFlvN1EjAR2AyB6P-MoqT)7VPua(diXI=qe)aw*y$iNRk)O?U!bI9cm8Q;CVr7n|8TPDTshfiMKao=x81g9 zsD-A9LJQseUvrdG5IJb`@@O2Mxnb=6pLTm^ZOOkzDM!w%>bOH1lyj`U8x@8-Ul$4z z==IoLFg=}dueIGc>~AquQCR4S(F3ZE%SUW~_aDTy%>F9xNQv2| zqU+dJHMKgW2xToHWG`N>XQK^?t&BFh3^eEa^zby)E~tA+k+qShd#A+#G79}FG5k+! zXSa)oeS{N-_U>tyj*Y~{_T0W!K#9K$j%VD;JXG%+8cMwXrSbOFD~s98R>r17tW~b* ztQFrpH^>E@uoCPaP~HFZyq>owKX?969Pz4>uF_GN-6zno9=8#SBlVwL{!Z*WE=@0)cjyx0f ziZXuHmhDqh@Aso!bAO)7x^bIXU7QH56*zypXBP&-G;1I-@DM;zu%(g-40f2vJzpwU zY*1^Lprw4jP)12&Rma8t6RUhWk3HTaDrZbCS~o#1=_zprtLd2uke*czA10sb1qQdl z+hja=_H0NWmMyImmkBeHV^#xvA>Zg6`6bt=|Az)z9fJy@2V~<-Xo;XI*4eUs-fef& zo9BB4$@g;6^}jcTQ^FQ*A`;J_RzU3vz;znd5kb+a=t{zeVcP|*kN-t>P6>UOiu{HA zXg_8c*0e;EQKqaE!@<&jNYJ+c_CqqxOUoJ$=qMX1Hy z>fF$!;5zj0sgb|3mksY>q4YL^HIN>{o;Kz1r=euxE>8m&)%+gpng07T@vlkx|K`(UoO3o&pUUaU*YI8(Kz5Wy`#avp@bQhv6$cbKnlMm3a5u%6Arqsf6% zK@4D?HsWX7YUR;tqn1J^he{X-IM5Ov5Fx05>@vGgMIp3xV@c(ZkHk;7q3F1?@-Ie+ z4@)b^qi}SN{%6rhOpyBLQrE8W!{DO77(a0ukAPnV?vpiV*bO1@<$@J{$J0ccb*r(V zNlu?X^J*Nuj|6IVu`Jk`M(^k13bP&k*w{c6L(M)4Ivm%fq?YU;-lB>@17Ay~aUK-` z5sgDY+ef!GsW2Zu{oe;aqzt8}y)ZSQC-8&N{bJg5;(<36h1!h+Airc6!UEkx zQdcMbB7QJidb81t9=jMj-?3vEXVk^vl}Jj@2$A;1l8cZ+6$+|~QibSVG_)Blp$#Di z*-sX<*{7i0*Ley$=SJ6C5ic?!=9P0Hy7p#SGK@Q|eD+@n3kM)^ z+;Mw<9<2zmo}Qjhu4pqe^OxDH3`02gIe#J6&7Q=VQbB4(xC<1XoWZ9uOqwZ7lRtXu zRL-_d4;)A#4`<`*P%qsRL3X3EZqP}kb3&CJ9Y-}nM>;?;1j8pRacp*P*1hy*wNYUn zPR@tMZ_A$lC)h>U-_2^}b0a+<*j(en-Jywr(b~b6pA_gQMM7i@8k0h#L$lNAEdZB~uy$p|39RIZR& zAdOq{N@@$CULXeRq{tp_oB)lM^oG>RyGN7R4P>$WzeDBLttQG%>{*bx*2u+)dWyFw zvQo~MyiDgxO(5(q){+7>wk=8!3ZE3dLQ6nA#i;gz%zw+;mgwpziv_f#tq8d%hB$o_<>2UFQq^t&R~udI6Dnf zC1If6%Wxs=m;>Tb7|}R9`Y)k`LZMq)cKp0aHRK;;LpnHt>nD%q?Df9bdj(Bh6O>36 zzmsyVE)KwnTA3lFA}=q$7FICD=-zFxwrUfgZhcSck}UNF&KNnP)V|c)51+sNTDm1~ zi{aO%Q2ohto7;|_FAnSwY+JNJz+)J6z9hlyQlk ztz)Mo9uQBcCe~@u?qNQ2+I1iCV@GElw>q(r`X{3+-y7wDjYKNpMe!`|ej>v@>Ip{I zLPCYgjK*T6>`?oh&yJDS-O@V?n2M>S^Ta48_*~71o9fODTZ^@b_TmhmoA<;_DNa&! z?I&Ir_Iqg2ckL52?%Ghix%epIO|eW9#gG;!XQE1QQ2kjSes<6%kVqmpiLsSlS;T9BHG()<4|yj7}eq zIFPL-H!Whn2164ILz{E4vr-tRsFWKG!&U;$B@vo-zcTL0Z=rk6_E0gNF9j7%MfhkzkI4t5kQCAXaxVSjHmm6SzEq@<)LYpSKLx)|R0 zp7w!Ie7HHXh@1b*P7kw{gWK-gvboX|A~#b*&l74iDU2TTBeea&$EbeN78?jg%zxnZ zy~iN=MqD*leJ5cO>5@p{L2FSs_il1@h`#HXD+dVb7S20}3Cexl)zlNTt}|NH<~kWP zDQ~74DW${m2rYis-I2Pv6t?uz4r}X*i(bv_U;oh7cr)GO9q>IN$LUV0~sI_pKHL0TB>7v<*)uF5yh zLZVYBzB2Xxkz>ubjg8gEZ(HNVmS?9N5f&DRDKDpwyup)juPD*8P*c?^mgnPfYlD_( zIMQ8Q>_HbPic)ire^$1#1`w0Dje9p;ueW3Et`oH}{v(4lOdtF*or z59AE%z7--!RW@JA8oCkGIQ+ID@?c$qRd`HHSwd7_onnyU)3`QG48Y%U&_hI#h9MCw zicq+KNYFrQNso2=_U5`&SJy+tN3?Zj`C6vD-FaVM`|`4&=0^m9c8pEjn=(b!K4mHSm_Gt%_i1~P1+80{Atd~AVX$sdSq zrxz3yn38rDSjqD76gvNp3t)K{zF#p{ULS0P6#jjWM5%M<9*!=~ z40qiwN`p5dAEe}!C z8Yd^G9&w06rPCamna0T5=p0nZj}zEYF;jx6Z4a^!Ve zW5=S&jT^6AV3*BIJiEXd2@~6P$TNn})6rc$c2n>C3LITB+X^IL<^q!q-S;S>f`g75 zT05bXxFqeTN<~d{jgD4n+R5>pg;&Q13k3y*aa3_R!Jj(Mr&Cawg;WtkKF#63@>}BBN^>rY1p^t+tqRqcpP)B*rPH@hJ2PP zJ_OU_bHAeynQp`$%hIjzKLQPLvEPttAKPw0R?I+CZuU3tB!0%oz3&BIWzFbM7TgV{ zZpJIl$QY(2cY1FR7z10MQVf#0&&wwSsb%c3~1t9mQP4(4a5nSQ7GSbyG;XCg9 zxTLdl?K`jx;-7c+a_6bWR`Z6wC*PE>#S9k0u*j5s%a$6jX_rR*u0~!P{K%Jx2J9CO z*=jo+uX?BQR1#TXmNvAk-A{$}Vd=O2BtC`N4Db<*sKOf)lW zMg8D0qOW6Ql;8w=mNi&zWXi=VDB?^F_x4VwxU8*m0aJ2xFLHwG!kwBASHcu6J=7#2 z1_?K~cUPV`yL)&TVFUFS7d(5`4TyxB+1M1pn(<97d|+drWJ38^qZngzRf;qCo3DGb zi=McTQzS+@-g*`=LWfq|@K?meO7xjBI}DjvAD`O`x5=B;s7WjJ2WQdnc=7 zY8uu(KEBon#|{AnOAUk17y$$=FHcn$go-;2Rmh0IFqsqL@?Y`4uiZ^pf!gAX&N~ob zC6hP*`0?X9yuI>?E;u=v5L;}OsUsK4r|+Fly|BL9Dyrk|-VNQFl62dCZ`JyG)7nUk zTIW>ox^I)q>Vm94)mW*T^dm;Xi0S?hVH78+yAE&rl)>)0M!Csi(uh_t`bBARDXYiL zY`%fK(}I*U?1X`!|H7Av>eTd^<9B|EQB|hy=%#c%DfrNVgD{w2V+s)zGQ zg`FVldmZ-wleaNz zH;E%ehE_}R@$tFi#5oTmsZC!E!B+`Rmx-oNILW0AY3aRW>YsiopE z9n8SrSb@w8B)VYXt;FlcitaD#r~)Lat5#gr#@C$SiFF7UTSt&DX5wC5%&#hF2AZC@ zdyPM!A`)D$K+b>)PZ~2ss_N)#L7BSvolFcX9ccXO6JC;oc1?6d5?YUsJ~$KZ?&WoO zA1APjc}MV)HY+KkQj}h-=W3~f?uz?Ayu=Fm2k0;H0pA8_0 zMsMF)OZ1d?lMEy@%1V-tLQ!Jpfi$(Yj)3TDJXFM(gfmv)tiv9K2TrH;?GL1tCF1pk z#&q`z3YuwaXlU#sY@vxahyIIWg^r${ePf@MwY5wOB9*;PgqeXsoF!6dtEyWsQX{xg-!muH@kVtHWtWX48Q%7xCJky$tSdZeQM`Ej5~$oQ#W$uch0-$hgW4AB|1lW<=HD z-Q)$+?tZ0JCS)d!lGy|Db1yI#X7q4(r>A~X{27viEvm0Yo<4s3u?@YD9-(Du7-SFR zLiae2_Z|(FGWCsT>%>Q@#`y$Ui`S_3i8#}It1rKvzkF1??X3e*PB$VsI%^@fz;caO(Byf?Itxds;_RgjeSJr*lF0t#st{Da<aLEEtT3+$mCXUfHFOe^15<3VCf*@2lIMr6z5D)`vF z8ik}{6TH3t(NU)om}e)Q+4IxCKfO6c`-=xISndwqr&5-dmM)5xl&gQUDqo>M%0r!@ zrKrXB{K8dS*;e#IjR&?o*AVpZ_IA-D;{{)f=Ea!$At4h=$hjMou*et#5+3_PHv$Kk zSSY1GZ!3(bHz*-DgL^?ob9*=pkgX-;l*L_czJuv24PU}U-IAYqFmwHek46DMv4 zps<;7AOi>@9L%SsV4mT~z2*6I_no_UqpBf&Z|Eok=aihAu)&C}LGbv|m0%zXLy2L}hTH@JUVl;)#K;-9A+&TqCt9awmM+-A~84-qx!6Ws{q zgYd89lf(*PTVTlO|9x9c&d3nPn*P!8g8D^3(RXw(H zq`#XRJz~iPPC3%**9rb=*z*Mn*2a{-Eb!aF2zqMcz<;feHXtIYn?nIyzCE_oxdsCI_5%c5dm4hu6H-k+nRq zweqd7js7X2*aYJ4eK-d7c>ZrurivG<(V~FA#Bs{}YpucpVj~r{kKYDp(1xBA5jkkl zs(^Dyn$>pQ$vF3JlM_@wkKTHq&b`$DE|+7rcvjYymeS|$Ie=en`vb#0qmMg3tp@`@ zHw=c8E4^><8EtXC=O!ky5BpuUkV@YG8t7^s41&#>n_!(N;S3t7N76YoY&G5-_&hxp zS2rc|^&Wn*XXuHbAmyR3bHpbt$4BV`F0)!~!M;IuG=hOs%2blURmWZmuzEBtxJ2GN zy1Y+FiFU&_Ie*o0Imy|TO&(2!$;bGK9vkPg>z01{`+v_m!%NZAvqZJ5v$vDgBeJmN z%&DVxg9o${Mi=hfpDmR+ld(?cw^PTN)n8hVto`Y=F#Bd@JfL-O{pn)qZG?yJ!d60a z2X_`9F;?-{S#Y&-VHeHO2ZAxjjA`}|9@*6wsqfx+a3I~X^CEY(wjk>yl~nBytwjyp zhZ8ba9rh>O6NsK^Jp`pzbl7}wf_NPX-fI5XJq&4p-aDU4_>9)4Vvk(SON$EwkgckRH9 zf~jgpDoP8@k)a_K$q{%LomW!a?~Ag6jbA0h|D(Mk_tuDN@g<{^d0}wu=zxf3(O-VK zoLj1@dSu^Z;k{d|rdddQWxDnk)9%xMs0DA+7C&=^Y-iKwT$U^3Z^Pf75gfehI`QoJ z^Ff*}&Z1@b1kRCOXo@Y?El{2{Ya|_Di>grtKP=Y|^v(m*S`2QyLK3u9aPHa|0V%%Nfv_4 z#Z-V84ui5%3~IIu>3uAQ#w}Rv^ywtj+L>y5@)j>7Dym+NtjX`{>Pl3r7{X{ZV+G$> zYWPvbMLY5>XPpor=V!HLZ2~nFogcReazw4a|SD22l!ltN} zLh{&dVUxD|&Y^fhF0!(+UF!)%Y93(F&1JLn#4VKl187pc*6qQ2(`ZC>xmNB)FOGz< zOY7Txn11nOu2lvJC(nTt{)F-jfR`nRS)WG4oPU;Qj1Fc6F`;RLU^`sR(p{h*zNpuU z!bTMZ-AX$7j&Oupa;*&4aOFk?tw(O^qC#;~meMEtY-r>fcjwYC4n~LNpD=0?JdcM~ z)@NRXvLT~i<+ufxpY-z)M9r%R$hsGs0qD@nH%0`od{i+899LU(?%X-&JL)QtQBhaP zr|2^>^a238)pIW|@$WX{s3L0uL$t_~2cHk-mLiumu90yv)-y6NguMHuzaLW!5{ESs zhBr*pi=i}_SFFIl;3APO0@q5T9im=1%k3s;l@<+C0|Jz^;Rm=?zVsvnVy}mtw(Djh z$W((Gi$_O2Mxc)E%oP%TJa z*Y)~RQ!@!gXQxa*b`Fdn{82xK>FVhTQ@D~NDbLCA$XhT@%Y*DV14S`1zXcxd&g{`b zpy_KI&hy%7_<`bflV8_B-oGZD$Q?@u%sn$W)g30mAq)%*VwGMlWC9j&8rPY^sO`{J?64B(A3^T;5C}7tWuK^^NN}MG~;@-oBKB1ILwf&H#XiJBldlRR4JZ zHV1`-YQEI1@^bFkbH*73OCOtnMsOj%Z+y6h$;@gKN_9kFlYaVZ00FWaP?=ml;RO6+ zcG(z_J8ACII1F%LmN?Cw)hPmxA27Ajf6czm!&;i`4xYdp#zHk76U+=%Lp1tF@19wVXl@WRcT&0 ztPF68oE{c+!~c>tMuN_9j0j#H%^XPxj?BQUJW}gCzD`O5+14a^EHI=eJp& zt#rpRz@qPyIfJ{+g6*Z*JweX1BY~GpOb%xyp9P`~TZ|;{h2y9L+n1kv0#7L+%_r;w zP8F>##(zJNNQX_k5wgC;F2kQUFp9_w%kI(S^wEdoGgHMKp;H7(z>|QjG{>!P0uawm zvi^EI_19mRbIs^nimz`_J#*)wZT(&!-1aadgO?}4KuJ*B)rOZ2B~`rB(87s7ib%2rJFFtM%P-!?5*-g9GJ(NwkH zuK@Ljd)9wG29U0JhJ$qZ@l9`_*`{6D+`K?hyxtNssb6_j&pbUp4$Sw|UkemEP;+ZL zK5u=eFr@i4WP0Ue73>4(akTmqYyyF{d;%&V6m$Q_NanN*SxR69w*}&c;UJx5oQCC8v^E1$P|NHyeNNEp87e zl(h6X7wZ58cS^3pGrQT?qHTPp8&plVJ1QE1b`T1iBBA1GHzeMjR@)J$Et+M+NFDsJ zH$zfUZzNoti%@u@NHbDLs?PcQ5#V6|4o^us!NRwm>-3CjqdYrqFJWLL>7);&v+h?J}F9au~&T*D&?-8Rfd zMYHke(u3-KN=&T1SF{x(?Jh^Q`ep%twoeto-%>O7xYs;q8ul7rnk+$9VxT$eg zeUByRjc?aP3Ba0rOFKGNHxx^Yz{B;&AFD1;;g0!uR1wQ_EF0k*?1P3RRrl++Z{|uz zdhmlfM#M_Uq=*+t!_XmOGnU|e4^Rm6`=0E$Ho(W%W-Sni4{ukwf57K*2Ez6)S{_et zZ{fjn?t7l)_W1?3A9&_M!K2k?mm;A1WVhKo&zquc+DiAHZ?ug4{ES6e~x3X0-&XLJZPDW*c*n-`fW+Z@Nu zYS@djCx3rt%PDj5Un5eF~*Dw4%^umog<2`9p%Y*rF?jyD=!=u$JVR$p~ zr#?R5?0rV!w3yg9fG<4)V)k{a-5S|uXC>r}3=R3l(fRGQF(q6{8x?yLBDBQIkdV<> zS?&vMS`UN*4_!K}=K-E$QAZ&{;Pt{jOZWyHsoNAN?H}U*O@9wMc%CPd&q{8GpeKS6 zY%9eXkSGEVjH0bv9N@uQ62&cg(pE`9V6M5T`o5gbqy|hT19gXNq@*hJ68_l3eK?Es zEx1s)Ee&%{4r#x?rswu=*=}ruygUGdpB!*tW(hNR0VFDZLC%UgE@h;r zTb%Lm5u_7_PCsUMPfyQ|Y|@>)@=aM)6$>7nQx1sHLMpJ{^8XYX zfaYIVkne)wS8Tsdf7=iLSIKHUO#jo6*YiJoxIm5u;1#WZxy^|kUYwGp-pR@1Jrx0g zN>4CO@f#FySzU-OOw<)p&P@$Z#!tSz5=Rfba|ZeE4yGLf!l%}gRUnO%t*EGIdSP*e zw2qzH_Gw)=xW9tnbq!zdNU5u}%Lu(TiHX>vt zHa#YzcJ4f|$&k!y9whFyj-Jd_s>@HN@EI+1l>xQhNZRW+93)ap~ z9)-ac^BcaYzfhoEb$j>DPk6a2DXML7V#=!f!_M%8vLB{P<^C1k4IRa2LPfP4Q_`2R zsLggsyVlHTDehNTXQE!uE&5A)J^LHy&xa2j%gsomn+S-l<>diXWkBA+?MuyVZTUZa zW%J4GuQj$;_wto1_q8Q3;|$7A22)xrWJV_eFffXf%+a=-^i_=-^e`1h6AD02uy+)m z1DB}z?{6+@T46-1WFa0v&-wi`gJSeA{qi}5Xk7s_+I}d$Zti-VxlqYPUgMvnb(qSiJM$ruvnP^ z%(Gp6kdM{f3i*8aRZG)A(RP#y1yYyCZc(__)hRfIz5aejaw!-)rB^w9{ygp@`@Nma z&7OPxu6oX%(t!uvSKO;0vF568UhGS5@1DDYtU_;+@&oGfVoofpXT6i7%W#mZ71O}0um#=BX8@N#Ww)c+JGXB2od>=gDh1~^27>I~ z@O~CMy(Ly{i!_Uzqv1?xS*!5#C)@KIv2`3-fS=yQ=6miqUX$gH8SeK!^VlO&zh5A! zu9kpTRq%aryNr~SVXEW8WWT8O$mMPD!A=G_tVlD&d@T{SmMa#z+S>05i_m6Y%~?Rf zUL>eUWApJ6X1sN`3R~{L?{`hxEfRipY==}b{)DII15(nA##yF8^`CFd&7l-tH)wem zrjWPgnGJ8kH|mlm6Ndb=KSak53CrmfT>f70q>kD|bep;G%>WDr3$`{!wtQ`GR~JY# zJhmeie|1V@CwdDfRT6x-c;-woolc+GMWezQYxQsi-p9zy`D$S)dY);mFCn*YQuBxB zx?lE0xPa>@p<+=eOe;uNyk3xVSyE7OJgVz3J?m#P*IV-IKef>tj6U|l%Z?FOb7@aV zZi>!1vg zC~Va9(|)|8;4ib6`~SEAS5WMnJfd6f`=UE9pB0I5#eyX(86M?+cB_ZDxnt_+s2bAp z50ik?DF|DfInfST%`dRVl%%S%Sc89HS>xX+ZE@?cu;zq0WFwl!q zklvMRKzOg~oYa~O<@~NbH}|xB#ZTen$RW4n&CCF5$!gkl>(n;@tlG@R*7%x%2Ci=d zWM@oU^7X4F34DElHQsDml5@N~C@@#az6Y>N1m=@l%n+eW5<;jx%_y7}ASe7T$ z4c$e@7;x?b95_XME`PYgrG~Wu53ew|HVt!a`g;3F`}_3~Z(o+lY?`6f+?9BFUj12I zo^foPiz(|W)oKNA?__J`#=aG|oey13vlz>__aw)t@w>JY&csiSVoP79{>9yI&Lp_W ziR<%*ISQ_Ph*OBFA|E~*(O8tGl6WGMvwS~hO}u__pKltIh|Nmb9ijK1eSt*%7VdPk zxbaSQITH~->wQE7C5h-0X8@Oj#W$4>mYGu(lgDC&;L)SC^i)?PUls8tZ$#G9ZD}Ye ziBxUa1qTylgiE|`pRU_yuKa?aGUCL`$6wW=xT6bPuDV5hVLG=iCE-~ z!6}S!ALW;=xL@EL;|(-WuLjX)=AjWN#&S~K`0_y|`t{V5#sEDGdy{%}9}VBQ0?@=P z`6Gr`odXa>=q5SBePJ}!Ca`Jaalh7Cm6x0QL zo!L5gW_o&F+x|}c=u!FZ-@oNzzi&8+L{L`CX=fUj_4`a4%0mo{! z>&O0h*#@VL8$WovZt!2^>{>GQ3!gkJud&~;)6$>2)8t8)KyzQfm+ML=UNWwIDoQJC zlOmRHEY2~y3dkuM*-)e~oniBp5N_+Kr);|U-DwN;*-Zju-nG|`fwQVg>P;gfqiKOM z6dB7W`E*eXXZGf@m|0sVO&riT`1&3=s`lc-!gJMN1Tx^m$=IA3PcCQ=K4p=l5Z!V7 zG2h1CzP`ZIq{0I2Z2IN>H@4FiD=}k!?M@<&5Lw_E0=;F|3ed6>#02?oh{G6>hE`8M z<`gMCCo@S=W=2Mxgyso@y33t&Fpk5Sk=k*>=vK61CW}u{5Fa)Mw|$Bdwftiy0=P-z zmo>8Sq7LxT1dFi7Z(Uvk<+)6;*TCp1WDk{a(MTK)%*%5)xRiszU`$!)t5*>&8d)hR zWr?FlR)mTfJos7ND?Aub?2i@nJPtEFZv;gX{+;pXzzM23$kMnQ}mjbymkw@~;Du^t+**A1pRcCeAFn?a&#$EdRQ+B?}lCG%*t1)ek9 zRZ&wj(!0Dz4`o2`>h8wyvu;ajb6fCg7(lo=VF6I|C`ewX&m(U+*la?w028sO+0shA zt&RqWVtwO2F|ifTM~|pgb?iuJPr}4xCB7WM(A#w5tvhyzbja=wl#aS@VNolQ&h1xj zZ}^_=Eb30VBYzNwJ)627AV1AvYCF7c56C7Kn0Vf;MGg{PSk^U|j5%@AY0>fvqKnPd z*SVO@jKwEdP46_#_o;Mtp8g{zm85UQw_)v~-xC&^9!AS*lTBADMppJ<1$%;KiW9Y}%e%AG|UGgsM?CROtaoZtr^r zutc?Jvkm=*5MMAT8*!TKQvk%hUmR2zvR@{Zy4^sU6?F|PQ%k*FV(Ql{gA#4mu3fL1 zkIn7YeU^LL3wcallL>8KyezkLVP}K2dT>a{ObZpQkH;U#FZ8X z{B^wHN#=EwWp+4+IUeRCYt@VVe-mtz5mmtB=o40dea*GTjZ_R=F`FOtW(fEeT}b*3ofD9|UdHcSC;@NDo|?;8PRFc6ctJja#^Xo*=;c z4p}kM(^HDoafy;QmFrBt3U`A2v3M_yC@=5mxD#8xxeL6pibH+(B(L0&~(AECwo9=>P0H$j!~2 z8Zmn72_w6G(8q7+(=5SAQ)be!1OD8)al6p|Y?AEUe_Y@?HZi7+7Xh8l`Bn7MD3y{V z?X|h0wUF=XEb{W=(8S3D>amU>CzIJ25n4Z)bOQLfID6m5MnLBS=Ths8vVJ0{i2|O^+UcjR1_wC@t0braUZ3Ej!!VPIG7-v}HYXOKV!4 zoIDi5hWJ;)i%0WuKgpImofNFHIp64juvy@=l)1W+Y}lBTl~cro!GqlXEi6=L!1sDj z4sMr)P(RnW7c?B#|JgM@lpPt%3ueTRk$IIAt+UCw9UN!#xi4c6qotJj%mCfwazH?# z!JcYO?ww5*g5`_%g`co(bWgpx_ieyxu~w3cAZ?F8aAX7#^0DNiua$8v6N_p)6KyfW z6YI7!yAQZp1ZVKO`mp#8yQLYMG4v^M_l75gcT#$A*y&7MWI2CUf1a7){YE3bXD4FR zTsTplY6z01-~zxnh?oBU4URxydz5_u!Ic>S#;d|iHVWO$=6~XE5hx7;H}v2UhG|s* z1DoA}0aXS``l0mrvjmUW`AV3)Pl}mVUybW9B>ojonxhqt zY3>nVn-m22?*`Z(w1ALVZ*`^~zCjt=A{A%dXsWIO3$;Ppegwno^ec^L>a|Hg;d^D} zG|jdO85fr|)dbZ&IxcEHZJ5cW*iH4bHBV#C_XM%PiBLL}Z}$6P1&E?g;f^Xzjs=5& zpAHmp)X{}GPJXvjoT#p@w%Ly9|C;q!+h9eKv>b>RIJ6n;n6*ua)EESII*vckwZK`cyl?J;XhK&a+Bk4Ozghy z9l7%fjG-O;%4z#=#n-=}IGBC++P7mBpc^cqXkqYp@nVN9rknw9{gsqS1|#m^>;lq; zD&UxVl6S@VnTG;UO;Xe>H$HBA-nnr2LkWM+N`^7m{U0I7WVdhN1MPPcACi zC?(wbdl9W=Be^uEm{A@P$L)7j8#`Z@!F8f0&|{*ATh|};?FaeS-LHW4>(z`4oOT*2 zp;ed=_u~UbvWR{oS4BW;S?mYHq6lJm_k++mdG-Xz?Ia%vGMckqZGeF0nDD=kDz=VD zQp@SywrkfiV6@gK^qa?mu*nzG(z5p9%Ij{lH5LTA^kN=vZvB3eCqdCK?o37(7{k)t zRoaLwy)1_CeaB=w5RY5rqMd!y7n$% z{zoK_>v=857jsmy?OJj!Zktz?`I2P_amtBwpJn;tTldm;#rHeMUln&-;bQ5U7v;(c+QR+dj%5mj-)ZvkS{8ROoP1P?Li>-~gHLLt6Z{Th!XffvoMLDHAFn#L$npS6j_*=ndzn8Qo{x2?9arKn|k>R7Cz^=L2;b49EZW0NDKVj zn<&Y?kf=LyrxM?m=L*YOuT2YP8e&l`Pw9M8curDK_!WzJxw*|2v6~)*`@~-uF{-f@ zP_@lbA)$Po+qY>TXtz<&w-n0`_t2=jY;Vf6#mccLiFn0g2&#ZQm;t2jy373Ur&}_s zQ%e~M3|6YKB4kh(`s5j=X2~r-2Q5ECgYDkAi>>(H9v8F>izlV9-74X|>r{;}Dm@=? zLo^BjUKC!Y)>G##r*Qje$PzEijne-N*|wz>mQSZb(6 zz5gD|Bt3bF7ciq2615t#3?99XnKuQDqen_ObnvS6!5NciP;4J|ll0ti;4~o3dQ{wQ zfYcujsjCOUt?59=H zLA651<|6=ACI0%6SRqM&kIMWDu)YPO1+x>QX!=jW%)K2K`E3MtP$s9Q)PWGP=Y#9y ziS`)?sZ@we^mzJ>*y%ukK@lgs-LOv6TZeF<3fAX|b$c#fzPx@t#zvdmOD>9p{E&YC z%Qy9~DRwfUGjJV2D(}?$~$l7I#9Szzd30ZwE8U9=jGI@==4Ud_(umr~elHCMeA4iZ=<4 zT_M6Lw*8$F+;-$m-~NqDcd$ao&FAVxmBQp9Tm4bQjCSJGHo1=X({n%H?jp8j38)pW z+b1h(QJ798$W(mphs_{iU2oQTQ=7kh@qo+DVmYEs*KLd7K=;XVn&1_hCtT-tyq|8i<_ucSv3}22>)=SJe7xm*|#by4E0}~r_$Oto_AC0 zM;O!iQok1Uie@o&4f}d6e;<1i^Oz|kFA&JLb8|0BZWlbxRY~+W*L>Cv(kA+U?U4BR z_)k}6kT};)Ve~85z`2R6g?;(PPKk<2+yei84Rd_esCPo-YMjf;Ors&pe#Y(>vQAK2 zhhRWg0-y7Srmr*f=-&A++0TwFFbms;f>+n|d_yCsscA_k{7Yy_0#0>Uk^jy!H$kM! z=?()tgf)r3^Jp#@wks-ZAcrn~Bpvw#+M!6g8w0<8|DD68{Aa1h1?DHQL!gO+!>ZHW zfeDhy_285DnT1b0Kp70ufuh=j0&MdCCI8Ohfq}U;*;6x~nAh-vH~RcV8X zCZ^$9nJI|C%kKlvI*RR79ECC8aW=$R5cEp~#-4K`KeePBtM~kyVN8l#vlq z2-$o8uW#q{JkRg{d!6(A>mxzQ6b9^B&jxx~_MlkWMl`5ZJc9o*v`NfdU}N!~@Nc z#>mpI__10YXeT)fQ21Wdb))bEo_3YS-v5+pJ0R|zV0If-dnR|lB;(9GfCM6$LG2~d zZb95HT|lKJ6(kY5t4OpL&obj}c{$geAs0g6Vczas5#P-;hf;ELjhIpgo8_;(6CJ2R1%98M9Nr z-Lsi`Ectj4m}v{`)O(y=zVEc?-gxNP{<%$58s^VmOc?MabJ-TH<5}V+9<4c9Dzb?^ zA)Z;0i-MJUj}p~=Wyzf-D}lr6RGg07Gp_{+se`F)zPp)9ObvKGw`>V|Xz(J-_N~Sn z@tEJGw8Wy%IMW(elc1pY^D1_VRM$mA)!XpSl>1OU9dz=vwufYf)FB9EB4jBk9C@ULaj4cGzuthijdfphU z5!#b}#pR=Zt%De4z6MDsRJXt}1JJrX6+*EvkSKdQJH@#!F9t&}JpxQFx51z`7FHC$ zRuM0Aa(c^;){|vM3T9zvXb64%PSs0uu>W-tl$P#%o~3tESH^X!QQdUo#*Jx^*Ja5o zDNXb8@MK}aat0B3vZK&Ne>+gbV0k`TzkcfZ!&>GvZ4UKCp=Sd!dtxq+VL`#!)P` zxDJ_Z63K@Hn)J#Dn5H#QIp-%&o_J#buLO@{CXyCBd+&v**Ug(7SFPWc<%nNQwQhg? zJwbWjZ}~H4O37>qrPF7mvTw7?OAFG6vn)|BrwLlM&+aF(5F5hP1lQ&AWrJ2#Eqek< zM>J2zy=FA%&NUmG16&F?el3A(}bMo6FV2d^=S=ung zIeG!l2aNM`kYB`OU)$Ylt~cgW(u)SJ&kXAp)t|ggy=qm|&uIq-hneTdoma$3MG`C8`QzaQ09BfKMfSs-UyR%qY0(X19`-AV$rnR@d zkR&m^jH6?dm`%UOxq-a)sC=i-gMcyuDLVG$%TKL&d+OBa z`F)%0f7_LO_;8{FS$q(0Q0VfDCPb!UkYjdIl|WEsHmXHI;6E(oy?m)m$x_@T4pt+W z(r+e^YZJh@?O&PfKkoc&dsAkt3HNJT??ncOy#8a<@ zsYW+@js@;xGH|V9xZ7W`PM+wb+P8(Sa&?|T>rCQ}&O4M}D^I6WXBw8U1eCBcR4K4H zyg$xZS{x3`Y|{v(EgIL9Zn6*fqF%N#Ol46=_w&=3alj|o4_%HwqE-Ke#-XCwZr?{Y zs8%)kEQD=2MmD#Qo_f$8y6_I)ncjs?yWM&R$vzA0>g>GaHc<#Wc1pW1rrfhFYF8D! z16k|Xn(Z+$Xmsw9m{Ur$&FXG7ul5g&vO^Pe_pf#^l-N-f50+>Mta@g6V~4?_c$bPr9 zo@FL8a?Q#6BXsX6#eo`=e--3}!=h!~LLQD^59Q=WY85wZ^Lz2tvmaLw=myLAk@wVN zXO59sYi5=L_899<-Y=v$`8t(1$G|l$b6DD@3nEI zuuGaOlpfnWZ*6M|1d)=%s6vU6Pg+ObUQVu@BSYiGD=51 ze|WIB_kxc9!oA=?Qg022Wl4HEm<)01CLG`ao&*524;Go=0#oFB-LagX44*H zfQePE?{2L*Pw@-`p97$c9aipm>;~G8*FPk4zz0fJtyxJ@>!(&fzpyKcd<+~x%Z#>3(K+J$%{rSuB0jl}iQLGAoxd6AtBGRhgJVT_bFUg`F%V%U| zb&Wx9R5b$Ww=^C|Xxh!a-p#bp%BvB3ZJ5>pv^3_KZ~!!7irTHY1Ens<@ncaa-0$Gv z{y8X&*|d4SyZ-$nyY0LvqsYqmfB%vdA{n@U-_*bVkh}&={QvuNAm;zioB#Lcyv=36 z&iOyDM}16{31iHEKjhyR{%1n{|Gi}6Es_lIfB)!2PN3)i-_OYkZD@3$`OnXJs|NtM zqQQD2@4U{_ai4EO=X1AFJz{mY@g88O=3qiI&2@^4iB%$U7Zpnl$l?t)@c!@XHtq&V zR2{m0+hWobVLqARSiyZ@Hv#~p%2)KjrfQIL!yo9~diOtXG4T=x?;L;&`qvO{^OL7n z)6pqF1N34L*v=vaJDgN1tEy67Cig(aD^d)?>nbwQ^E*LD=U|cDUCR9dA{8B!yiLpm zr&73M62_}@$joN;fSNdw*fYUT&$P?)00_VXLLfTTMBbV6Ou)%L3B!PbhR8<)c?5!D zx%N)<*xhLFJ?1Gqfo_(e7D#LCUEac@@ku^OH54+cZWW-}-9Qu%0(<+qoQYLQ)l$TyU8bvphWJ27d~NXdrh})){8RKM zD5$njdSZ3Rm&<~W=Tn97ovXBwq=SFHDmw)&tNz$1BP0{RP?lZ~2-iOL;Q|-8Bn=y7R~~Mw^{YZU=o1>?Vlq+rw-wRlj{3-R{JRPq7^F=nc!?xO77h z?eYCzhE`U3m%+I{r$4o8S@I=i8mcf-x8dn=%fUxhhDnrf!<|xf1_p-E)lM`Q%b#L4 zD80O$-@%Pz=d7tAHG%r< z4AZXkL+B@ZYV`r{UvA%kO!vPpvl<7HSzV4MLtAU0OYqI zm@mIjW=vWcq#_zOen$;h1l&0XBU948q6Li?j9r%m^)Prma6Z`Edvz3w`s*L7tFI)2 z1`lon>3R_^h*s$rfIMk|EJae(hQ>jh@;~?cgq)l>=K9x>=1wyKxw&X;JoN%#(->Z= z;2oPECS;@!!ZMHwb#d68tUlWJ^XHRkl>95AB=;gPX@pey!`VCQTb{xI=`Yc}Dk{vLfk%s;#|dcTk_)QZdH{Ab@zZ8FK1#}_x|&x>)Sv9RROSM+O~a)ZCJ=EvEugNdOZ2^zydTZx0b8lp!>%xH7<2~H z--Bd-Pn#6i!4T(S+bgL5{9r4vaed$pjP0(Y@_tB$i*p(pm-qOey0Zv zRQ$-!_^#~bo|`wBRl*ZJ?KbJ8YGj|2lW%N&esFj!JIBiR!if`FADB%eHftx*h;$K~ zUOeaDWbAdHh0-W&aru*r$6*eN4b(#mPHa(q58;kD)7vgF-Vrc^=Nh;k^U6K&sQ5mZ z<$_$^fySwcAeNVxXVBm1#-_HTiI?)+!net!E;6F}$CzyXfQ0*^V>zZ+(`m`cuV4sM z#>1Ra+tE?f1&jJl(A;jmB1A~J#`!-c(Zt3)&A^k#67Vm+gdw^K>pr>DZ zdmI7e*ozUCTZ}Rz^mMQG;Qz1Q)ldl?1P``jT(}h-y-W0*9nfJ>M7|B6d3CpguQ#fwseO1#cFh3pIzkt~Q|tis zXS*m$s7sL2p84D!6&Pp?T$h#l58gk8HfJ18yg!703^T=F==TAwZwp1ZvUl+~=v>0m(@$TN;ZLK>*Lugy+{P=oTrf|z)t=CSNeP1}tJ1BAh#jKO?Qt{LzLiG zmz*RzNMDkTZZk2W-gOUTqW9sOJde^~)XvT>+t)c{eB5RRrJ}WXVm#j_X^%Il~A0?#mB`x_*Acdm*j3ZxI?I1qSm?dYQM-JFRvw`E%45Gg5y5kgdN5&9Y{lrXDd_#?eSMpCny zB@ETBbn8704Go$?s5Jm!r;jhK<#NM>9^%Ug*bKRjPEKpC>oxs7&HVFxdTNrh;fGo)U#l9MFhg1{Y%ap3>YSwY2S=zVeirg1oi~>lQ zRh2BMHR88`N^t8aSflOWKGas#)EHZ&T*8y2BKKuKXEQM656MQRXWdcRHxW^UIn3?T zr%wyF4iLmFqM}Lis;~PuRSM909eY@L;Xq&(-pN^#PORT4q;}y#erb8RNAlKF93W-B z;nU9Hxr`f>Dd^Q`z7J&+20n)Eb`e~Hf`Y#W8dEO$OepW$)adJyz+Q>1b#|CRTleAr zw)a~I5T4GS5$wTL72)kN(T`vP-frF4rVw{-Gxn~$xDZJK4txN3+q&{$l4AjB=qoBp z$}>Keh|8PF$ID&t{7%X1XQTr z!Iv&F&Ao*Ta?Jk;afIER7UmFH?^hx0`T?&GIqfa%y(NTYs9iRg2SVr15 zaP*(QQPpDtU~$al`9p}N7psl;{Ew{uJThDZXRGz!n3OvWo{6~@%m)aZ1ZH#4L-Or&0^aY7t;t;BlOSH5rJI^j7Ff4hi zd==cLxRI5$fcx;_u`tvlPOw>6%_W5}Vtf@S-pRV=r)c=TaZtWIdGh2UQp9NBIYuEl zMa2h&Nl9k?7{2EM4zaJr6fYQgggf%9zPftzceu(9Hnk32u8YUUxjyItC(t$Vcfw?i z!SGFC;gY1`0xAu|Mn9kX_a`fHb!A>Sz_I8IP({P%#b6pN9vag`9J~vMCk01a^@dMS z&aXmk+*+GYwY%adM&R# zoE)co!+7$auM=)1g3lFz?Iq-F@ua-rS%hb3R_eP3K({ssaEtobJJyX;;)Hu}kG+Tw zOxG5?SxW73DRu-V2ZKjDc9D@O+P)X$3omcgb*%IWq`Qtd0L0O!9}Fdtr5=;gFw%@E ztWoZ7daqf{%XhdzGbN>5_Qqe4O83z)r3n~OqL=w9Oc zckiAX;AQ<~E2#lbgd(dBfmLD6Md?VUNtm2`kk34bOeyJY45XMF7>)v`4J(E^(k%r} zwwn$cLK~3*GmT{1TcSh>cpKBHnwgySm|2#7zq$zo6({gEza77SbMJjt(eaO{KCR)^ z-n?rz-$W9FQfS?GOPkRRCu{*IQx+ z$h0H4Ebnwnry3g?z6#qHm66j32NGRzC2uz_i+2COKKu3{)uv}^@kMn64FP`lB zd2-Ob(9qbZNku^Cvl$Q2nqe%30px!8HdJ8KVmHO}MHCTk9X~HRa z2ZgHZ>Sj2bu+_ow@%!)l1P57!%vCcmqugvL2}a?u(CyL6mz8(`p>MVrhEoos{jX0q z!k=WIOEGQT3QFHwR1{x?au)y`!lWf&ztjEu_h(_lV0P6HHL+WOV+N!w2>)h$G~00& ztXD}zjnIv{HY}lXerQpZx(M&=BV^a`NbF7~q9JvRS#Q|fA z9HNeD3~B#JbZR?Ft1mB7Q+HBGoMpI<3YfzBRm3f51ijSt^{r_}7q_lox9&}x<{hYJ zz?rg*&>(F>KdJ4+4XlZhUU}5(z~uYExwot9Xi>rbg9j}i(f6aOF&gba$MZO+La`V& zOA3a9ZxC92{QMj)FCN$+vwEqyt26<{3gSZcp3R##SLf%8aT_jqF-U!EQiHZlc=hTH zWJkbBsYwthUA}}&v{Y6m$HI1KGXW&^sRZY)T{q-nRSpRXz6cSv46S8-j~{z;4e|Xg zU)oy`GS2xFd?5%PX5Z?<<=L@IY=q#`_DMt_al6L!+LDr^(H1ht1OzfG@7-G~@&{!o zZ>=Qnf!Etav?#2EC(5tj&%;PhuQyf!Hetx_5#T}{#x;ch872r&=+g2lDG0bKNq@?X zsxZ1YFc4WDg*Q+|<+%`QjN#yg{*;(PSVHkO=6tKHychoD9KfH@PPV0DP+&z2^GI4r zKIeBD0vAE975W7of&k}hYPz|}1{^Scu43phiqcG502m1F+C@PSky38S0x!X~-jvVz zT1ZG}ZS$12%1cFj`=-BFA zlbyHrwBS3y(>Ip`htzlt-{&WUXENrCLN!TT7&SiDh(DLK7KzDO*O(;MNCOX>|AxtZ z@d9d@j~lw-2EhKsAHJB(18eV45cgRQKYw(}7>0=Jao=hhW*i2a9?-F%gefsaMb3m( zgW9g+CpZI6D{V9fTp+NtCZJ-|hANx9#kffZIF0SdfRr=}FbZ(XC^RH+O z`hIErX<9`w0D`B&CBhTBkpK*VCw!I=2G7c$KD`y4bAK{QO;jz%vGOv20(fiz%eHOK znw`JeL=<_L1Db2TU%fS7IqoQ7Q+zHdmTZ1Q!<>!5rs{*gWK(WhTJ6as)z1(=h)shD z>I!?FuHj4nkiM#S=b``lbzuRr8;JCB~t>6-#)<}oQ(J9OZD zG)u|J$xmz6ziMk~IZbj@~-aMJpdd zPbcDt_9j3{GU7%gTbOz2F}Y;jhU^y za+LA~$TbRwfu#fwAH4g%UVOYT2{3{c>yNF8J=*Ce4tj@U> zzqj^whZXY2@AQGXXTlh*7fPq#PKHi#2R9#!Ui7%)0#u3%JIPZl6h&)MIkh_`shh8w zJBN0vX}B$SCyn=wpa6;Yx2*a6I>73~lLUY6_#Hg!E58K%*zMUg-N;fc#n0V%uU654Rv z&@aQySPu5_wBg{uz@(l724$id3en0>ba-=5oVQj$jrJW5e7QTonLEhI*_$~(@61g= zQ{I5OuR_Gt&5c`o+|_yw>x~z8J{bBlE#k}!45b5qrmJtrJz68RAO*Q?~GTR zrIfeGTYQo$cX5Gp)x`0A7H&VJOZO+%$_#m@q;QKKjy@CjT*`e>!}^oL7L4g^C}nTU zyv5wWVBkO)tQ>Q})E+^Qa)wuN3u7Cm|0zDkQc>0kBdD1d(rNtNZ=8uc0QmKolJfy( z-}RvAJs|%^jNPuVjfI6p1LR--2dJ^bzuHd?sHMm2m|DEd%Hl|W^Zfa{3;Hbpsx%p^ z%F4y?S)eDjzCDYmS>;ak0_7j{*G|l!Uu4>`&j>eqtC6l z^xqy2Zr`S9(XXniLXtw1dsU1IQ{Da8C7ey#p25Mvuqn~Rv(KThvup_$)4CZGvuDl* zmv1{_!1jJ_KRl6suVeOqPdZYJ7z!qe3=BU`=-CeRH>Nb7-_5>`c=YsXqQEKAvZ(|H zNA6CjOZd>NC`IzIvv=(x5(xsGu(~9Xz8`Bim@=*a9j^X<_2aW&=;_)y6+^RnJ`~%v zJ`@oU2q2xiIhrgiENZqDS2ooHhP+#9V`i4*qNWyDn!)|X79`jD2nQ)wJ&y9iFsgM$FI>MbeZ8ZE-r)=#Rn?!<$(EP*<1we9CEcx0TL$b{ zQ=2>|{}X0^K+Ef=Q3SerR1QDj)jJ^gOHMY%usy;G%lm$5&VUIeyzQi=rTIsDhllm5 zdTeZcrbrjZtQ@h}bNhuvMV(e>3`3cnT!iV~@Y${tFQJy)T3%KrW|5LmR1~RT5fe%I zLvg#byKAJsfBkvSKG9EL4~gq&=b=AuBEdSwK_HZ7HIk;Ni$AI?B6JR;i_y~Oht8jW zFGE<3Ri?{iJotwJGYOC#oxgVNnwLrghG*Y^uXXshr*or_5QJNnDE`*$lurnHhljU` z(Nn+_d(@8iJj1HR~U^D6r6dDZde=Go@a3gd&_Ko}S=|LXZ6v*h9-O%{pf4 zj@Q>>8s$1j(A|saGvN@ z^u^fI4bb}J!)Y)Quk#nIwlXy}T}ac&c&9Jvt*WC_9f}6nRJPV$Tf2=19om5hV%)@SHUt|kEJ z9Y1jt<(5tKgO?gFOwr&-%fA4r>!B8Yk(lC#B$?9|+q3VzcEBuV4#NEt_O^ARsP^&g zmNgP}a+lyk-|dTOUhv7aqz3H`k}h{f2X0Suz8v_Sxl5qEU7%+<#7=q<&d_= z6)`CJJp1M{)l;>mcGU;>*ogLEqs~Af?qEpjw})`>=!DqD7*I`BP8E*;tl!y*)3V1@ ztvjWnv+M9+`|9X);R0$^NgvUZ^*!1-8> zF7OF99cd=%@R}k&d<9HOB^JRORMjIku^U*cG@$+Bpnw1Y<+yVyXkNFt0?vSIPt|TR zzNe+xKL(^oVAiV3k0THhY6mILo9&4if_%A7KEX zJxEY9WE~_p7=ns5gQT)Y%S0QHZvy`PlP;iG?8auDW4gK#G*NX9H@EEPR8UluCwtA5 z6ILbyuxlBxK~*&a=NyK>GbczmB@0*v4R-!Kfq&$CuCtM^;lv?UGU2j?nK`F8Z|*Ba z^jfj{<;9#~rVMC1VD53`s=-*8C=EzHfYn46n_R7phz?f^Yd)QVX1YZMvZ0m!Z+ zM$yxBkON3599NeIJDQmxZRG$4mxSa=MM1!WyK^UG)C|nNdlOSrvoAzlpV5VTzCMYb za|262lXD2UL4Diy?dn*5F}4g)-SzRlT)?C2fS&x;`=_F&gH*5>RtfJ;p_u~@rz|-! z@#@}l{fAiS>H#D$2qs7(g3ur^&ACcp=mEiD{x*K!4C|Bl~x7Ize;q26j61#FjqEOo<;T)r7E$LR1>a(m+3mBmmF;daqk1dGCj)%L?1JXl#?G*UriQ`5Jdz{x0L9=sNY3xJf zyGL2b_QBxL01@fe1kuGXAlR<~b$#OReF^cq@ArevZ3(r4b9`!~$Ze&B6!_VD*k8TM zF4x|K5ymEF=4l>o?g@6v-*@iZxe0Q*_4?JT_qlzu#lM<>JYvuH;A8;BBc`T&n0-tu zGrWZ4x{utAoZBoJg*dL`_U+Rr<+PM5E-&FIqahr&bV~ydxff{N4j)21xT*#Tpk`U< zZOZPGO}S8jbNMGE^cfrK;t5ex-tdx%b~Gzt#rtE%Jk-p>LOxy^=GyJ#JPZrvhGjj! zef;QM3>8c&K>r&0jqpYvGCjm%v<>*p$DWC~TKvTO_j@!YoAZRxcsl2kKPs!mnLRxX zmy&4?%HK#dJ^lSohao1?ux)F5iZCDzP9@)4-w_d9YVy;6B(WkrErNInqz#fYOOFVb zndAnp#S;iAMOZQXZ>fWgZ`>`w({s|ab8Gpqd#(5=`%5ZfZCFj(B&^4a$Wx6d)eR2v zJ}Sbq{72yBl)FHmz)POQQjuzMqO~4+DLWWrbK-q{Fv(0orMnDpM!Yl6)-+(Q#X#UA=UOWeP+4+a#Gu7bDDVn05$XP``xsccBh< zwFWhZA|P!mQV^ncZjv!-JK>3Isi3_TPAR)oYGD#(;hBSHXa~M&$7>i8`jbT!2Ay-h zbcnM1tWCzUl{ueYcYIVXS#j1KJJt!#G&eT~ z?CG`3&Wr=op%feEl(BOC+YJ;Qx6tcAolI-lXOs6B_yC9xmV+VT;lIyoYPMi*8~W+8 zAYf=SO_Hw-9pKN$XtkQ^2m%z|y?(PN?!dLd%a$MwQJ>fx37>*}$fbL}zHh*DV!{Q^ zc=gH|W5O=5h+k@x)r7LPwgo+5U}>2;HOGQFMFoAH$JbZJz9VRk3vp8l6*vXr>o4;| ziCGjAb7>lpIUQ<0p;=bUu{cWmV)+<{u%G7GlOJP_T3pVEft9fIi~&`5KmK0EF^WJB z6@tD6)tVL=W-;&71|j-Q{Lq`ExUo4`8>_XKF8!c&W7%~}c_G&J9K%1SUQZ4CShCN= znP;CKV3ZlEf)hXvsQR=7N#DAVTk5?$eFY7iFXSz{1VO5s_2GFvSs8Hm`3kuigG6gb zc?bgQtZw3yUQzCp#wH0fa3`n;k|yS20yeux_6czkr`YzlZ%1h-v>Pn6#~)89C4AY7 zIbCks>(jq>>|tE}3w?1U-vu>*A!p6L(2@uk|9^kmuj~lSkh&1bZX5{bRVE0|fc?~-4hKHMZ>btAyumeu0jMXlKy<>b2Zf_^RJ- zBLXaUH?RUWAnh-s$dKhe^UYxcz~Bc;Pwu0@St{{4`v(G4_I-<)z)Cz_F3Il z+^?KF4B$>g&SfUdtJLrQeaTPSdod)FK;d%A)LOv&NKYCx1{4`Av$Xm(RZEQA@MJ}$p z>6%ZYS|CaAz-?HTI~00nuv3SL@Vt9>RVR8|(%0q?wzc6sLX{DM zAoSv3tsN}UGg0J(P?CJ$CTIl0IyVj-9XgKR8l%ITfF%bFBw>M?l41#dSN6opN?}8j zr7W@?|9^l&ZYUadiAzYhZY4^fSD!wpU$p3qf_nilcQG{qG-9#nAZ0zyw@lVLGVB>A z0ovG16VT8wO)N2_;o$sad~lW13>`CiTvvR zqij8QcPm;-hd&S&^dHR-e#cW1SZ2Uu#nZRx{XhbBkk*jB$%mth? z0jXEA&%b-L>-a;Azn4GbH|k)XH*t_4Zp&y&qw0E>bEFwS-zq3nz&wEm_0V{F@v?L5 z-qstfchM8g+2n;|OG5-BDy^{4P;(LXmQy6>q%ypC;{zrRXY>^(CxT>m98RRT)E)S7g~Hc8XW7RwIa zNfYqaY+5k$Kc;Vxp>eQjOUKW=Pi@EBX4nHNN{c+f9bjaw_!sfzhPLGA<&$V)#5{xL z3IG^3nTrqGkt`BJus*m5uns+E$wYL}jb0IT-$3&*3W9uDcT2@+Dd*RS7_!^<5q0{s zART+RJ=aaN#3R^Hw==J%r6o^o{#Tg97W2*{tV%mdsygM-qu5zCgU%vzaQ)w>Q+Ny{ zNj}BI(+K6Dzpg)7P{a#3tOn#03UVA$Y1Kd9xPgzZfokAD;97zJ9NEf|I15)5{-*!~ zG}eu-3>h6_VQHUdQ1OU1wzOP2=Mk&Cuaaapi%29dMXk&BD4ltB`}PShpC=GPU9~aB z)cUT$Uph?(dyW4K1N`HB6b5_NFv z7^$C0?+pX*#axXu_8dtn{mF5rRI6 zQ-Aq@*(W02!^c&!|yVFvw2=^mI-ssUmGq7xtq_^?>|&Er7L2Pc#CPJ4_7? zKeKx$9vpcIr1N)MZo2O&4nv@+qA5b3dl;W8n0QTW-?~+3|5xlqehYu$bahA!)(4(Z zL(Eje7@|K7+~>rxm?&T5odS@>fu8+DU6kkLb^HTH@2|iMz8=3GggQ#>q~rf@%*3$h z1@5mSqLn6OrC0=LF$QhaaR1td59Q@;z8|_dR|@+E1`do)PQIwmUO^N08+~kyg{Edm zHr^`=s(dgsdT42BgMNu|BCK;jp@9kyqU^G&DtSz1_d}9l^TxK1{wxo3CQ9xoQgRkT zb_lIc?9nDyB8usveRab8jT41J<&UvSJeeoPmoDvTbl|~<#17Ka4Y}D(@@dVY!OHt$ zVgBa`$oH!f+IryYB`dQ(`~;xr3~O%!ae^M8j6-@-(szs^;?N)3!Hv=lv#W^xXTT`C zlAMx)87MYio_(#8CdkA(DSY z0!k!C3z|a=%I;QR2R~u4B6k~sVQ@x;bW8#TO^6_Zt5Rpi#*!!r_=ek&^1YPQRAU6P zCXnMhVBMc`*<3qpg6F;OnrFBmdh@mQ(}g&6zlQGoO^{k4+1c3*ZEfOjzkYp6SB9wj z1a&tT^m@$6!4G_WCzLP(qau#R)EfbhT#ACrmpnxXmQQM@>g0`!|0%9mAeg)cjxra# zTViZ%ZC726Tp>p$RviVRlmt7rl9a=OYS_#o0X4}}e3)3g)ZBaD4NnOIJfy3bJUoWy zjL8VeDiHJ=#J402&eX!}tPxXNL1E$fSyy*^&^xvja6A>Y<-A6J1i(ZLEI_P3Z$4zf z3p$G!*27U_fXRQg9f`Ud8mwCoa!Hy0o+XJ(-~+kx22|W{%w2&A^((CKD<^Fc9vA(9 zgX3=)?nz}MKF0JGq1!nC+w%z0f#irKA1|Ct1VJ}ELP@Y}-I|K$JLHJ`vS#h=n{;~d zv9TrKSRO###w-MBw@r;0AgLqT92+$S?IUi1FA)WQxmj&nY@dpS;P6)r5Jx&JKcpR4 zsEv|lz;Io@777IV`T6+^=4>wT1s_3|LqkP4Iy>uyLy>Ig=s3HjREOdgetADF1DmT2 zOb511WskuWJOTh}Jm^o_Q$y%QyigVUVnOFwixjW--#-=>7BaEW-FfoM6rhb-M@I)1 zlF=K_*UlyD&FH{hXgCXLC}g8gse+peyhAhbjg-ov73z1n05tMq9_TA)QHJE34U5CK z;1z!OH0arq(L+7e0Fuk0B?mbP)j=7A)TOu((wy>WaGpb+u?>vs##?vp42Pl|_v-rv z7;^pvCjW66gB&J_X8&_jc%*s5%|ozTJS zTfxw8aT`%mQPIyFRwMNaRrDjUN*$(b(C?jtYfjWR%-#39S-M7;wqRFSflTzXF~~XIK@bDXrUQWoo~5>p36p)meW>#u$nHM;Pk+#k5BH;? z3baRRQ)A;fDuM<0)rYdOG;BA&2gRx47iOd8BSHUAPdzE9+&QOFSAK&I{&&)#29gMH z-u$bU6piYKK|Y%rpuoz4Uwh|Is#x5^?WAX5&_9_4s-zj{*Q*GU_`VB`>=Hr1C2aUf zP>?-_-D0cuf~5WP7qG=_+E#Fo!Hsy|hbci&&eAO)hC1J%HlnB*0tQp%!|sBH<(!Yb z8P;p+OqK8Sc=35r$g@|{>V6LU8}g=MF4T+`aHL6p4CKKR`WX*wYc>DpY#+#*zRe;MNs zF~_gCL$}pt9>Ur41OYAckiMFlT~lS{8QM_^8aa|H{(KRh2ud4b&XJ;P#Lp2*0w(>u zkASRENH7?|oj?**Z(Uvb+mB~uKgpFC!6AAMhQcA_=zg@=I~ivV@bX$^<*+ZHux>#Dh)|;bnx|uB4_&$w7+%S! zbu!_CRUYLiis8T5fnB@42bpwDSt&!)8@fUSPdL0V2vk_(WXXArG_Wdgpm z$Zs7-;wd5s=%hpzrUrwTyW82&brumvNd*9snip=JyVlYXWSNG_%1T-{ALaAsFF{M* z9IOHDy0x-B7)(n*K2bqTq5)Y1J=;1gEKDBZ^t&tCox@X8Q$gr8HgDZ3x|f~ZASP`B z*h`1+g34idz}q4`HzL2if;_u*Rq47YtVQyBT-Bp4s4ph?U2}R@kuUiTqZVnW1A~qh z8GPmR-`+bhLKC8dY;Yp!&}_nBticGV6&0p#wp$4U;9$pn3}R3t|Dm?|{D2H)E%A&X zy5PMeRca4h>>c8Qv7o&kV4wlm7%ayg-aR*s|2hGcD;E_3u-G{+At6je=x&ZM0IkEI z5B}l{s9?b&)ze?9&v&wsj$k8Lu=}||*cgpgCl=&%*Z|JQ_Otuw3g@q&4A5J84cX3m zR79B+1X|aFEg->|bknRMO2{>O;6EqoQFHH-l3I3iJ7$klyG_JOry8R{t3=D%r$??m zr`Vu@wKu=%2|k(@p|&_Bu*7gjgBS;q;`Wm)3N^m-kkcTJGS3>S2?1`@Z=mwVO^E{= zaNk!r(riSn0=KGByo_Muj4w4Ne=Z0WE>Bwf-1s0290dZqL`!($u7fs@@zi^tCwmbk z7jQV*pb{ScFg%WBZak*Y1VQRw73UQz}_W@pS``#6eaSYaeMH1j`xJFe(_sY}~$maIyQ%S#A=jBV~Nt z!Bu;0es=o7`%y33ywL$465OHc<;97!eF)=yDLZqo{W#glDN%K%Taz3REszH>dWAPt zdg)(uD0>HIK+@u`7(YXwDbx<@fqOT6{(SB|>YrJrHuzVqT&~dR#b~EIi>8hY*4$Tw z!=AD~8pjvlR8;E(7lBxks7lAcAU$)y%&=n6=$bk-^8`dj zc0_4F!?~y}xle8uTOpaed$3nA4GQ(XE-vJZO%Dih=o=Om^tplnR_>V6^^3<7ThBTE zT!7_%KGutUXQ<&vIGWsSj*@4BY5x^CD$( zl;=h=eab=}Kkj~3q|Y0Co}~K5SHVZ7a(_1Y2cjWp#GIyX!yt0J-Y7FEPDf)Posjnp z+?q+uo&xw3*`(dupLOCW`7c?Ocv6@)QvJRK-m`uz=D7rM<7k&7M%fYlw_fX5<(%}~ zfmUMv*Pd6MBy|{GLsT{V`B&h|%jN$N4A08Gny}`s6CaU6zWHqEjgOBv9-y$l>F0NL zvtRih{cU>r*OT5ZvEzMxz*TiWzYZgeqXq^BK6!bfqC3kltsWBctNHkmeWV{G_8(c2 zRXDyhb22!Nv0+7~TQT-}^PkQ>`#91}5ylAt>qaH^endlFfZAt3E|wRN=Ia>I`B^($ z2J;>30eq_=Iuyviz;sf0^{GO6K=5`C?}4;)$Z02LkiP@Z>=mt3J8^<$0s_aMv5}xi ze-e}VR$iWP@2x=t6@iGZg+WAWjtw9)Y<7>aB&(ieA7ccVCcA=otag~dTV(K=0`ke! z`gsb>^6Q*IBu!0U-3`eYh|=io;_9IbUI6#62HjFQU?T@UApf&;fdZu zlc@^MxV;!+%zj8tMkOM~#o2rHh*2^3{{2opE~U^ag4?#Z_r_6n1HqoxuQWtfDkxzv?S3rec{=?W3> z1Ny_2Cv=-JGjv8K16@;<@Wg35jsWo8*VtxK5M{y(&?VH>B_VOyv%-wP@Z1d)ROwjm%LfC`_&hs^fG?J=lXq_L<0zm3{(Y_O?$K;nY6tX-cy1g$ z8aV@QE$#g9esJy1*RQ6&>5%5PZQHh{jZ9(5`ugD3I{9H9CP7wwIX@_{Yhle?-aXmp zy&ZdA)iK_QynO!g$75sYwFulFJhPCP8fu* zBMADt=W{S7&V0c7nOEvk+_{!^msZ}^&c}(W$xRP&aaxBur=S^ij+)&}4|;W)R`!7l z`g6VR9ayGWXLJ7K^q+J1-;s0%>a=Ny3isGPo0xO$!zRaccuii8c=|H?GIfNO^nvz= z8)9rIt|wTM@08pyl(K&coR*Z=ajo$+KrO(;ebpMVmDB!|dOp1+*%?*0wrYLS z5M^Rwif~E^lb4LUo1H!Ne%J9kI#(*MA=OU;*E%Me!xv6*M@K?N8>R zRm@Ua-Fgm?kJ&Y_WDl3l10Z;a{@5qf*%h`?E5k^Kr!V#Vx;~#-R#`xgMdm?2 zoR}R2+*W32%U_VyJb@fhU`n!+@{v=@exzWsGN4h!o>gAo59W1VRAE$E*%gF#mV$u{QHqq`36RV!I>3fr>= zo(ZHNFrowNHV+dE^MEc=skfmD66YMr_AfakXWH}=U@=$PmMg+}B=>ZvhiwUfM}YSv zAE+}kFfnz^%(!$}aGTRXg!`Hm$SC=>Gauw5#=Yvy4kpv4@m8mtOA!>Nx#LRg=~`s_V2<^4f+`4M%idevbP3d znT60(l&Dk^J9`S?OGQ@tyI1aN@K|NqX9?x$D5*qkYG`c@ z{cQn+{--2KWI}CtbYfP7oNY@2!+k?V0KIL&;|>$d@A&lTQ33PR78mQ#vdqP(AqR5Y zLH`%f&pwX%?Qf9MJh!AI12otR_pWlL1$f&@#1|B_E0=#in1+g$D&q4B$>y>1_-OOeA#S@i(Ubo4jEhwnN=IT?*}OTy5MErr zUcki2sJv~{CRGEhbf+PmSQW8tRa&Qc3=p(Yt!0S9N$W~c^lh!@+ol1-pG2L(m4URi z{5q+5Oc-HhU{`QeHmw9Ml*@)HVPRpvhMBxKZ<=k6uy)f>?-di%8cAOdK=DU?DARQq zy8N3%@$^Rc^a_roCY57m_h`Q{nGy9nfBhm+S5%8FzgkzsD4~f(0A(StEsf>mi4#+w z)!j+Gq;|{>YZ-jpAcKS#cr(JW0|$1L)t}Xb0k{8zGni6(v(`{+Tjrlh^nWW3 z1(awtxMEA*Qy`!}-t!gm$@kQTqI>pSax^iCPs$O)>?LYr6=neRiV`DVQOVh>bYjiQ zdbo&>`hd4ZB~f=)!fE`ci(+x;9pIzZ;%1*?Lr@LQ&SQ;mgnmL=6QFV@+-)X7sv$Vq z^oK@7DAJPfu(cW6AU3Vv)&M}eV=EKWk2KAbXb&MqXMBx#Yz^a4@%*?p27+Z%mWg5K>IpLGsO}Xk^2DG3B*I)=+w} z0+!nk5}FgLB30x-lemUkGW*)_Gmt87AOlb2l629my=`e7P2j%F z<9tI?4G9xNVP*RU+=}A&(0o%6M*Le{fH6d^p}tAWw+4qz{MPN;H4nVKpGHJRT6Nf{ zkFEf+JKgaGv_t>U&IiQ$$R3r{dU`t&gT1rS%@zesrq+ThdDn+^my>q<)5lrIfi%Uw zTZkHTu8>0)DMf80phsSSV3(xh!Gw@S8V-eb!Q&8rrg^up&`r~%cS4j3t82RioW_l) zzyPd3>zoea#^%eSscVUSWb$=^673CQ=WU=-bENne6Y-GUSa`T_6N4dzsxieJ*z?u_ zC~zVOeCpxFdbynk&+QCpYeN0PBOqY=5!3a2Qdj~={|Wkiu|EvCxvwo(>^}eu+ph4F z+|0Or zyZEso=U5P5&rb&ST|KghU^K$jgqe>8KI+2RGiNGU2v2N$>_lsQefV=+)j`k(2aq~{ zpeR&A*!kvtS`B_duHAca(%RU)h}4?6+#@J0^P#fp7xw*-oSvBY z5{n5Yp->Yf2*A)gnVExPoql8AM1cR(r?UX{KC6KSihb?E0+1L0B%ipv-b!7t(09l9fbW3KfP^OjJ-=8_ZuwJL_r&hyVO zaDlnU%?tnpdGLwIEi#?+!keigC9m0{+>jzWcN*n1IU#Qriv5S2@etzG>f0HhTpJ@_11!7@ z0wQ)A2&>ylgAxb3mj!i8OYF6LOcEvf5CS~3p}kB3`U`6wJ@Rj@0e=PH0%zq{ftj1Y zv^&U{f9o5t8Qyw3+hHx-vB)Lx5Ijt3!_8oS1?1ov=$yx$ot>ZM{D6sYS~?iOp9gca zw}X1uiXu;zq<`ihVB$l0f$cHn4t&$P<`t?2VF1j@Gz$UAw+GBri`|!MLED}t-5|gy zpAnilzwe1DNkG0&lGF<3G5#b6J-^^h;|6{eD?xzM^ex+M7&FGh*e}qjEmlH5I)1Bo z?d^9jGu-83>N!H&00+Cno27c59Pbg4eX_;y1{?+LL*xj?Ov}TtA78$5BVNP+JVNXP zZK*@bT8ZCZH;&X1qb*R9=5r>lD5J;+?44^*c}W%*sV@JCFtG;%tEW)wk$Toz(5vM$ z6%dMxNw}Mi_P8u41h{<`jI-CTT}vX3)p>VMQQmlKAn`?G%V8Eef5?xgNaGorRjWpj zXx+^o%iv3%9v>GY`TRq0{s|}^76Ns?i-$CE_%ma>GGG**>hsp%qqhr5@99SLz49_Q zw^$C}@lj^{7!>utH*;UOH8_h=D~TaV`#gN8*9q(+GjwL)8VJ7jK$ZNWs5rv|y1Qd9VO4wpJVTc*tQ>E#1as_}JD*-V1Lnpx zUL@(n6?LfFb+M(mB@oS{FBGdb+W~Y+(h^N=7AL_$;I0~j-@p667;*+0`RU7-2}7_; z&$vY?o`-|Kjm$-)@6hI?l*9P#N;+?_;3xpvq$o#;4#n;{b|^l3$weQTIm< zoi#h_$c1KfVstcd0Ck2}R~RRXTWl=+$-H@Uh?cf?Gc?Y^a&$7`(k6}tFY_b;yy%Ss%Ut?_1IdQD-%S;@wYf%%&b zrf9tx6S`kxe6svd^_8Mt;W7rh{bf%xLJVJQzmq2rY;I|1;hUz_ctOo9Zi}U}e1PEG z(%TA=-KY7S-w)2gO3J2oF1)TUAmHrHnYr+p@*r>3qN1Yi{8}-%$i0C7+gB4)Q*Fi# zqDPKAK?NvK%=pcBo{xw6#^* z+CoLzQ}aX`Ix1;t|6X^G=ljR^x_;O7`*mHO>k%F2d_MQ*zTfZH`!$SJu94wWc<9@! z7f&cFcN=8dbV|Srz!{^|lWfE~i&gl-jE&=djKfdK zP4t8Z|8dQ8gi~5T(DvILpp&iK?FwL-g@twT^5Da&h$?0WfLvzak`FFU8&1rGy~l#v zJ44k3v@9jF53M`1DEb7Lkafg@UeP80_|_5-(R*3TXzK%5SOhu${e+e0{=0sftks3& z)&G9guK9Zj{{8*PEk2xX|9u^l+5B1-uKW)s9sl$7|M#OWx}fPNUvE8nV2MzV zCe^iX8mylZcv8igKaicrB7O@q!z{R_KJSu6fqZ1{=Ju{(Ie4llHuE3yvj_W#*-orl z?8C19B?nbuT*Fq3vDa8-8)*;{$xz$bc6cdE$Yz`a*60_MjpW(~9ScEPu=S5q#`W0n zzdrrn--;(Au($x3DeKtTS-He}8pya@6J}FAI7fn#s8lk!8yg!FHmz66TZoEeO{hwY zHYUx}hM;78!caAp3}QH`RFZYmg`)GNghn$ahC9(|#!#tfB5s$?AkaA1=kep}&mTU# z`~!Sr_81i&8X`7qU zyh4)Uq@+pkJQn?hfA6L^QT06kWEe@Mf;-T%eR5x^Y^Lsm?Ft*O0j~j}ep440$PH!8 zwpfv7Y)AIU_xW(5ZyGo+?5O>~_!97<@#A-DsN&3gkVutg04AXYtq**hPat@011hxv zG;ubre(r;+=ZkLX3U!N2!X!Y9-!tHKyh)J(SSQ|)<}IO(fc)|4*5UJ467_RM!LRUX zcu2b)i3Z&(t@y)}!E*vWpEFDI;K->;rD7apbrZ|=VA)&#|-o} zEF~xXn4i90_8;L&IlJ3_vh>1@tO-9~-yU*lb0a5|UhF%B(kFis-b2So!h77I1IxX< zjtmd`p6lUSqu8i{ZGHe1qT3-1rDgh`rk^W*mP*Ck zEsO@rPK=hdAsUFDWywBVQgazFWWXgpCgb|25*zA!2wnGM7G;B1Fu_KC9(Y5WD~vZy zd)(yTW9mSq8k(C=ly(cR`e((1dy$qfBrm*s>(&SJe%P9&<m> zbU1%Q0QDunVC#CnpZxs%_A5IaPP1M+KQYUMLD(m8iR8)XbBNKCfdySE^^` zD=cpwJ@`ol6E1H`c2y1yxnsY(gM948xX^aH@yO6HYH z@)xTnYpIGC=d-X3%LBZbdtcxC`ycSRY(X}UW3NBgh7IbtaMDkf=kGLIl3d&N71dx+V)wf z0I!lkeC0*ZR__G_=$tusF62#uy)+Cs=ZC{#G4FYBur)kMg@_r6KCMOww2CiA#>U!U zH-DNK8wd`c=8pda1HD%@)zz=CKAXWWeF>Qnn}uxiKiajZ6BTtC#0D0_D=LS+!|+v$xbH#Ku%<)e{V>8&<*?K zb9P_fEt({>eQj-RjZqkjB_`xYc|tSu1tV^qxo;xB*-fuT7JGRkq8iS!vm=4_Y3;C{ zp~^qNd`x8B@i$wB7qLWdU)VTTadG)!@|AXaM-G&pu|fMy&CIkmuU$5P_%Eqb4f|mM z(=G`0DU?IsgRn1m`4=yof2bdfwv{xb(A~=_XI;8jxhq zX)fIYHVzw_>`7iD19setYroP?0D4M0-jD<}8+w>uo3Ys3{4c^CLmg&R+uUJWIZ2pD zQM4eH7{d{;+F04y1InHwgtnqB%iiKHv@I)qj&EJ5kO>RW&oDoAHOFyz%wpLLj!e60 z)ZuMa(!unPwY9@%fJo0EKe50`Px&-RZyd54)?DzSjX6o{$AFVmo}_2s)%YAJvH4q*2E~dAq56``@(mm6&TqF+#I2{`xnCk>` zky+}7tpI7zO4VaJf7%Ncw2>^Ldvt(yCCL2Cy^P{Q7W_|FWu}7`*;y{a-yv$n24~at``db(|5eNEMG%?8S20hq# zmCVW;(42!weeY(1)ECQiVk>zT+?AOo;7YP6CZ$w$evi8jLYtEHDlg^e2J{O*P~wbl zNNEMW@EMbPW_nibAYxY^oKjXEIP`KhB{~k(+g)_;cH1dv74E}da7x{v5&B3)5FvjI zzqGW(`E^OJ6CRfw;IJI{a5@`1mKj($Eg{^d1_0vT60?{!?5LOZgfe(}ME>If2nwb_ zJj|2T+kSapGHe)L{j+qbQ%I&eWH6!VHpb4$_gMhB+=kjWC2b67Owr8j%)qR%Im52H z_a<=3n2o77r?C&zp~U44vWz_}FYhifoZADi^d2a9Ga-3bF-e!BzyTyNbZqnLd*6sm ze8?k}uLs;_xm?7Svavrx`c(`j`RDWVlEFKic)Vdbra10ajme#CdYvR(u^)IFL$Vfv zgqnOX(*n{K^x+@ucLx|gMyDefZPo$Ot&~wO%3=`Z!GdbE>GNwzGc$N{kndxS{S#u> z7*9+SAQ}{_b4Y;vrVu_#nU_FFZ}gu8=T3o8egxUs@4k;&2Efn`t@_uAIj@rf1j)as zv{!(lnW!JV37yoshlX-2~S`!CPzd_PJi@ z;BpvmX+e$|@L?hF>-2YJWqm5p5Cp-9VpDob%BhPb$hLEOiw~Q_TIT@D@FQ|AfBR-r zR#7qFh{7oxGWXga#$;PYqWVNokS#Ybrb&@C=k|hR_j_bShGXs8;p~izF)dZq8j!qE zOk0Ai_yosxQi3y`4j!rwLJsyp75oi6o}(b$zo>VGGymfhY}l_cgmi}t{tFg7oabrf zpFi(9DmFIsWM(vR{3I7Cq7pLWE!f{aM=|aNPsFH|SBbr)fnF!^N>uWncNHaREf{js z;Cs1HDQBcT3RT$I?-gF~#&nidycQLxv1ZcQp9RJ#ouKmhz_&*kpvcQrkM7ftRIqzdld>&fYr-# z&Aa-{!{c)rFzj6NwjrH(>}LZ;s2;8{*SQd0;a9+;;y|TLFHO(S&o3X$z$EsP|GY~e zs(lB>+>d}p!rr#=3i1pjP6~OZRg{|yCPt@0z=RWkpVtEs-v{H7d|Xy*k>XC9%uiSb zv*ENLxEh{N?C>tU5MzA{3T~O|ge|~l3^Bg1@ny+|zWX`|yo2zL-GLvkDg6SDY9x@K zO~SA{k<|j5z!R|a0neVnGC`yvsgjEd&}HJ&Q|#{ZP?)&tbpr?-5afSbej!PX@@U6q zM0nyPri}qMh~tb^JavkhNj}e=gq`R*HvL6$Ob_8LH%p*P5UlFZ%0z)OrD1XBPZccZ z?Mz`Lszd#NF+pVrpv6= zTEEpUo8bX??>oE)Q=tr!P@0RA+jBpzgS{b{OaV2KKXT;uWt?}pV8d7RFg+kO`i;fv zR{0`c3a3g2Ad|bRWmblWbrJ(;K?NNwUy_lljOZz;HS9>SPdUIY)9`8#yQ2|O4}YS5 z9E6B+w=Z_Xs_vM?okmD&c7qjXFz8P_?2k}7k@@4srcM|-==8xEXn}Dy+I4r_wb3_6 zAEkgE%5;KS-6QY| zkHd~+kNoTd@lN`%vM@uow6n$xJWI1L;vnUexc|ZrxWYA5$1`{7J0vAXtul3aRD*NI z1l3VMj)NN@ciwFyTtKQ(Dma|VenF*t-6Z3zCZlrzJ}_f2oDHhkVRIEW9M;5QZ2=_= zphnKNN5^NXeV*t>s3Z{_3ak)@`HY5{{s6kAz1Vj${gn-+$34SGa)Z&<vdoV)x8`W-c{#DK7YkK!~0UUU+=#2J`S%aSruXXpbknM1m|o1QqIkbvoyAQ`4c zp7}lun5He;ZQ9wL5{7TF5r6k>v;i(143P?d!!&|LBqb%i#m+68b5fck1 z*RKz&Y$|fYxw(qvFlAPoaHpUd@i|>F;n{oH(Y} zO}v!uI{iL~1Eg-*>ox3EKncUgUGsc<1{H?E+j08AWWyje2-kqWFW@@4$OffPR^dat zAf>OkDX*CHjyTL87^Y8S#tXb1rsI!}Z+(FZn5T3U&m>pGv`$ZVx90cm?x?&1tWx!x z9Tkv3JcTtleaD_Xr+;>JU47P=XWRYY_+$<&aUD$RDTo)$jN~}CJ*?PW?sBH?&Z>@$ zKBSeBtmR~3Fkn^4U2#N6c~X=i5(MRe*`6hn1|bH z_Qo$?KH^wgCvX63{2$w^MrhC@_w$6x2{OMD==;DmZZtXKlt zc?`;&jnBwn#lB8TIi78xA}Nt;O1++n=8zDk2io zH8kE^$YX13YcbupTldW`LF2EdQ6l9SetIn5WQ4A&<1GA7k5COEZHl$HqD~3Jt&U|J zgY+>jFX=c{9NR&lvM$7wF`SA>9;J2Bwr<~^U!LRE;kEaheI2N?Dt+zkmjfD=pz(K6 z)JewYee__k!B6W~eN&T^1!|{V5ZK#AYQu_K%^(iPNpFsZu8?Nu_wO%fSWhS_*15!k zkb9|DySBcl=%_KCGNtQ802J4hD<6icc!l95DnuNEpu2jT`RsQdjs|8q=qCz|Yfe;L z0!AiLdlI1`I&!CJ`e#ao-2*x3(ncl`1yzyb4NhxJq8X%nYf`<}uxn{+m*A4uy34_2 zKmTC|8%z5UffoVm}Sgta@(1U!A(bk)Z=yKhj{bV~31`pw| zK{Nig6vG3@{lw8#YZU6$&Yq3Vi^PuQoCsB5ZO)H#m*;hGk~C^l;8P-JZpFtrUG~Qx|$mUx$=wWXJTgD#s=9!L5P?Nx! z-#dn-8ahUU@o$`aB36GPpswLj(6P^U|ACjeinkm_s}@r+li&LL6UH30Y^Pdrszz)= z=%pa5&EFGX6>{Z_OD)jpiPEkgq=cc6)!Q0oqM}}V`}B_8M*8r7l}Y=k@8p!3B|QIn z$d}VHIK2LJI!zROye#ZE_Id7@3#G_eMRTZCz&n(C>4nz;Fb|H=E2}CUnh;%o@$IiZWo5ihyRg z3%`%l`%K)RM}MO{h%tSYc|5M zaAXow6plMZyjX;LE=~Y^F-C@qeb&5^ub0K$XM?!h!5=!R5KFmpeK1PNM3pfBl1nY? z-_erbEr`#I^|20^3I&x9vBlIKx(Y<}l@l7XxD3FN9-lR51b}xx?>$)~M!xBNYWy?Ip7ra3!{z5BY$gYg;&W@()q+W+L2Kz8@OJ0q+ zxLhKLl7uX2jFir4lB*eY=^wKP5g5-XLC$L8WmN(vRj%a{*HBa~##k?XGX;V>0fX1) zuD1yvgYo4A4H%%^EFgrehDa~aEf>>-u^^Sb!hu)2?L58&ZyB?A z7n)DqYm(+swyp11o!uMVXK85411vxtF1N*geb|fa8)oL}#H{)_i_xp{h!_z+;5PMV zkMV&$dwxAkke>4F?d_Gy_uk$u(c=OJ(@1ae=RbO@?@>8`>h@~&qt~-$>x1o0vTQ?K z)y1_0pGCRMU1|x?e(Ec1t6~$O&y8;AZmVQOU|8flax>NV10*ONZx#N9=by_5T4+a9 z`(4dC(R7DX9$oAJL^Z*5I_OVYHa5uz8I)+Ui#0=BSQNDd=16VlF3M{mhoTZAf7`HP zk6scOU6m#S+krNYdsL|!XHzit5>;b;vwn|`%7**tALW%F|7BnZ7@-lWFg{8c9;Q$z zWzlNKRja3?2IPvY!?FlEwj_HYH&#qnm;aqOtb2#3*uZ0&{7)pDd0Me$+Vfl$@|je-TnhSCD`Kx~ zz7Jbn$8i3>cn0>++x~~hQ@E0ajaOFMnJgOrWcMu4r<$k17n8Y1KfC+64HB)gG39u6 zr@Tt{a$P~eW(x?DmvChVXCYT7YJM*O5LpZc+j`6bwFO}I6jj6kS^O66bnk5sP~;?@ z1iPYij6$Z$?=&Xt0f035K6_SoyXVTxFw|xJ^%)O^AR>>J8^b>uwYT99B&aUtQwUV( zdKhYtGbzMuv)&4_v<@yuI#`7MzSmF&Fus`aqeO8&B)9VJQ0;D2YyK^Ug>6ZJe(`b_cLQH+-@L0VJ>oRlAq+25eN( z?#lygr;xxZoA&xA*d77X#0+ef@879&U`>gXUGtUKap_%}9m5 z+DvtKMD^IcOERm-WpZZYhd+j z+|}M-^_n#SX~JcFVCt_dwZK~V{p;65Q=Wz#4M-|2M=gk^;yN7iB&pQ-*ZHi`CT=&k z1(!N-f<`a=X6(X?EVzP>!P=b<(U185uTKox#z$6+f5%>(#zj(=t^$$U=v$;nH zLBzT$?1(ip)*X<*0>1{7ISuyD%whn%S39HNEx^GsY}TJw3ude#`r&~xBn`M52SbR- zXzBzGdDSmyw`l;TV0lRP4$A69c0&=$nJ1B;7wRJ}`{eyFR6GwZ{I>O=hTS4COx zv+8chd!1&Tnmq2^OWG)M;ll^^Ek_kL0aW?Z9TOJ{N2+=dpXE_A)gEd?T_-Cx1CX~a z-w1(B?$e;$)ooadn<7afuWRvMTx-|wOvYT^0@x!jSQZ*{#DyzxuRRghHx6Hm&nc;? zImf7I>b_v^5+Kh$s;UUVQLZxRYzFa|VlUVlVIy+;1B>gbnqEuPC8l z5b8l{1#AaplJR580E5tR5|_T)*l@4l79Sy#KnNC>m7+u4M)?Ln9b^7r^Dl8#j(A2b zaW_oFt~2n`M~R=HtL@mS8W4_UV1;?qq8|nUFEDM*K+$!;I2fQc@i^PxH-rSHLcF*p zDuXO`c5ZM}NHnSrFuX*JdLvVyNH<<#_655lndBs(x8?2Lu#U#IxvuaLVOYfmPenk?wiUE zG&r(UDrUI;ur7ad>vJcUZf$Zx(F1N5CQyis!V6cRdMpHet@$Y&YXb*rqADc7zB-1J zeT^>Cbr2xndKv|d6RGd90%C&QXPhq3ngD!th!t3tx&Bi8ig^<^E`@^l7!2maQM>Ln z&RIp;rM3Xz?~D3|?n1WYfLGiL-#saIb~4j_hud>ua`?xSb_72Im@Cc)8a9b?Y63=< zrzl|vQi>|YxG4CJD=&fZ^q>g1(+KDKU2$epG{neV@mIdH0;GIGT9(~L$#S2~8-t2- ztPXMPPEBaNqO!95?IF#b#5tpnMvD6$9k`q5SKRkq8-LSQu`9wRsR;d3KgfxhWVMsZ zR~opQ<-vn*SpnvqP*Qr(j=K{CTc>FhIA)-O)Zn~T-Hv4Tl+ej{U}s(*UEf-N)@mM0 zs^Nb~03Qg$7tjhlA_MoesB_g+RKAha0B<59P|!l)(+9w2>sQd>58NUwAYkKl|GxiP z4vvx1D`YT*=VU69*@9?9QCZ~S(BOZ^_b(cY!CQY*Ckl+;AYwF=G{<))7G{1z26-+1 zO97c+&Xl?(Y#oOohLEFhj%oL_lntK!Mdpr%-~mK1|Dfq}4}ef#9x#{%ZalVsjYO1| zBh~5}e5=0Wru3`G59_G{y0<9nLu}V_Z@|e?AzesADksMHXAKFqTR@1jO6%SqyN{$mw;$H%kl+Utmk+ zwCOSo%*)BIJ0-BkZ81PC$4}ckLFOc%p`tK3xplP*-YCX$Kc0X(bxczWZfPzk0^=LX z{*edgakBQ$DFp@BxJX;K23gnWQij~_k^8H&6P@VW$KNCT$UB#3YB$2sSSZf4=Q!rRyYC-iOYZxKoAp~&@%jgHe&NC8IHg>D$29r%>yOhAo7IoNIwUl22q$`X zG{zM&K-N369zYa0h`gM+%$L>Xjyi%TA?-}T6hP{3Dn>#1WsSjEtN&S;367l9AKb$` zNL9K&^2`?8>MwZM{Yh2E3|LAKN2s?w+wi?*Y;4}iMa+>QElfv~aB^Eoz$M;A>==q| z@=i1)yBvRlWb_qiMLwBhiFy=Pk56}8-lvXw;y@NCRn`^(F8V$w)sn1_@F{BLc@oCMYw9AAI_)NOQZb(1CXqM6yL zZn*;zoV<0$fHB~?|E?1Zxf9{7XTj}Xn~1sF?c-Zd@^R-gsZ@9k{{A%ZzNkolyS#;G z`WFTpTBQ0N&U${`M#GTA$9_E5ttje7HDAIU^rNE!^s8Oj$_;xZ(h zs}?36dYzb44tyvoY6_CFkA05;^=?H41%U%d;E=)GK^*08;B{duf`!6C#~WmqV;C)8 zGB(zz!o)Wlip)1wK&axGFR-WS!-L6!N`-jXMsKGXoenRPc%0dbs8px~SJMtSXA2JbYKb1K)S)Z;T_b zG@5zii3f;YBMg9-vIQT}n51B~=Yqxj1$cD8vV}KPzzKfGCi{o)2qO)il^t+x)&Vr7 zqodioya&|drgdxA`mScjOK~{iCW#h{%Q2cnhsPl-OqX)~^O}iGgNiwdE20Jk*Dmt_ zU*DyU?KFD19Px9iGJBchId(|((m$oWgK!in$*b|o>8 zCX$GdoviFF#J{3~Od_en`>w&D@@rj%PSj@q;BX;K2A%s7`tGYyYMPE%o*3`@lI;G?)d}@daR_YDF{~ zER?E7vSG(*c-(#!EXm*! zctdjeBr(VGQ=~%4ejklNH;VpE;iT&X1zfv%Ro{G;9MgZfh}Rb1183(a|h z#L*eE+s^ zq=|b1-^=pdJHTd)<=SE(`*!H~^ft?9|8W6uYYdHyL_7@U2*sbvTHzeob>p`39E*Y_ zs0OF$5)?ca`Pi#qdO!OCC4NTX4%fK>+WmK9y?#QR+`1DjKOe+6^Q6Q5nGi1 zH~mCFBBP5eV@VV*{g;3Z%=^65)t|m4oi@9mI$WKPvcivL5-g+#a&IS9SG(7_EBn;lGzye?KS#)X>wv#K!=%_IC9mPU-VO{wG&Z zw#1COK`d+aU2EoWz`+5Fk*frXSXT(Y1Sd(Iy&K`GS)^v0THZupV`mciP+ z*1{W=)zqFg6GjLxg#z~E6nd&w|6-7Cw&RU8oTG%Ho*VcLa`aiU1z@OXhXW-UM}w7l z=?u0e27Kn>RDJZB|Ku^x^@I1IuR~~CUOL7IJ%o0RnUowq#Tmuh1FsEY{P7}_i-dzA zXm^EWtM*7w)s{=>ynG0Gs0l%y2OiT+fsIT8ada^s{k6> z8rp(kMuHUXlTag1UYq*|k9XMSgy^|pqwP}{;5cP)z*RQCq_}uHaSCO*Fm=C~P zEsD4_a4Q-XNo-`?PH$9#I)voOh&5wpI4Oo=rCzN9Tp~Dyi-17}y~Q3rK0bq?Kd3yu zg`VEgn^vTWw8aP6*^{abA+jTlO-;{9xf#@z2N+Aa%N|n$9vBOsFpup9=kz&B+EJ>v zTsWEhR|(mIjMD(eqWIowIZ7Cs$QDTvku!3eF|z6g0RIKYNya@o_0(D`L-LKSMNb8 zFeo)}$FkJ}oFQ;-*VfgwJWT!G*C&YS_pWN`g#Ajry~t=R2ejZL+Kj>s1_mo{HYjm| zX?NoI@sAkulw9TH@f?dhh*r_gll6X$w44Cu`I6@W`?Mw>A`F)M6JTg3p5~ z*N*j*0v2_#&qhbiNL+O2HAFA2%CYQ-*IotiYHUGpBeAo4ACwt0UERUldqDIgXBuRo zzn^VG-DF!jO1zIT_?5eu4gv1#i@54nBlm{T(7ZTFJ+kU$NY8Z4~53P7h{{-K||cY zkc}9X#Se0s+{r6_S~9EnDjmYN-U-vF%VsMv$l{<D+&I6*roW{M+=@w6&P1FL!}}=r0$c$`C1uZOx6C{;B_CX@BI0}QUyayCvQ#z z(jPn=`ubWU1O}JXRa7Xhk8c#cpzpw-*BVrcMnc0h?>!DKxN?2ud(ysH^x)agB)UOo zLH(RRu2#KbmBF^6ey=pl9-t|2x?lM1DwT>!(ASQRp~}-vkB&d(Tf1zC_1R_${#f)Z z8+#H;dkIspbT^vUR5YP4aRPwdV4({CxRT+Y?#5FXTGskDMauTpXkbtH1{aw+BM8KG zkht+tYVY2LCr;n^FoU_=m~5m4%beNb(|Uwqh27frPRSlxD`JleJtjh4;Y2}eLmA7cvDQ~_u@U7 zo0;uwkT$loY+#c9XILV zO`ftwVoXp~X>cdR=HN{M#!8|4XnrvF1GU!D=(8M~6 zHUp}Xj?4|ZmJU+*F=h$VfEVDt9*Fcb3LM2VHur8J3!vmxMR(}ZWsmV6Le9~=e`^?D%DkG*@{MwP>e7IJOfU~f+|S4g`R5X!<*#`>QO1z~C&7;&nU(XeYI zx?X;F+I+oiFym*#F_(IN@Kjxcu9?Csj0c&|QS6!6!7VCk@|!?yItj`&9|gpz<(~kN zNf^73DI3W?325wZNMr<~uThcj7T?;%1+xlo%&43t)U(qH7Vg~Owc({@PdWhFfyYHR zK9E_$V?G#UYV7f_0AK6sL3C|#MV7?;6(ZRf;S?FbPjXxnN9mhO|CGTU3BN!0q>UbT z9gvC4vu~co9TJmer_l)9`8#FwQqWwL8Tq0^GeI|r%pE-TM?y21%Y9 z#JW=RhUWn!)u@xq6RT0Vs2qQGv(BHgZ#EJRC+WJfAm14Tna)9+-b_Z5)o&4 z*(&i#7weL)30Pv8Bq$R)8(#M)cr498>stsx%e?g{P2BF`V=zk*_ICx8DsJ{OxFq3= zvp2vC2!JGW0DW>EkxHPo`iJDXB^8-lV}g4V)H2X-PVXoMc_^VEmBL!q+1pU=-kGaKQ!>~A=Qq#hNV zGM;GA^D!U{fc|_~JIC>g!o@T&(iyCf4{w?~z-sXk>jc=`<*U}`zaj~OmC)$zl{A`c z@||L$B<3h(Y%CoRzqD=v%YQ?!o~fy+`@|GM%a#E{?gUs;Fz+Y)CMQi4q~hKj%L-Ks z;xH5aN*0fx?wi*HHg&6ewtO6}y$>)T3A{%~&YQ{`G_2>b<38{=0-F9FHgk-PV#W&D zcoAM;#GUS#uANahcyEru+YO^i2rKG_@EZBTTFG0rH4OmT?SgIc`#F5DMHr|&;Jf+! z%0W{{adDM4X31ic%a%oCOjJSz7=EvV>7z^mcp6O15E?F>PJl@%DA2FsgtQg>9`zaT z!Nlwm!LichiYG{7Ek4IC<7TL$zB@5r#RK}>G&@|Q>>{vsSva_-uX+o3RT32bTcnzUz?VwdT0bx@|<5F6z4D(8fk8DRS?qy9&CcvP1^LWc@@y9PH*% zb#8KwZM$R6#4abDshcokWpnv*^p!^ycSPZ4LwpkZU!lxZu3tyD@YHR`2kv|L$z;OhO^8Gy;t&F{D4S*ePVnnhD*rYB{I^lfNtvjrjQz#K)I~Y+z!f^PWs4?9xPV+Pi^|haX4{KQVQ<~qf{N9g2#<)3{G~1jv)iQEj9EAIb z5?K*%W?)`vI)wcbU_*8XOFP+oA_`p4O{|qrPglgL#V($9FGd$sA-Z=3$Y&%Y?3DS( ziH)k*`%Xa4aoji?Z$^_ea?v{{_rTLe6_mA;PykJggb;1fomV7VS&!(S=sew3K{&X1 zS+(5tJb7fKTEQ0Zqep{OWQM=4=N~65e3SeP_Nll9mC*ciBD#TV>cl_tpdzJ&`&%r> ze$|0j#7TD@nE&;jLB(=Z`I6>R0`ia~T-C#>+D3tGhy^c{rH#5YfI{yd3N0ojbI77k zWo7D1&X6+{{iuAPTqeq-+ki6L;7m}GNu`3cVg&=paTI$}w!0BpAUlDY-@R5LsvTK^ zd&m)l!Z1Qf=e(umZURgQ!`P#@a36X0c(}P~U(sw9P^s98tfKU0QRzsDIQe0%b>jQ( zpj5>>ACe~J0n7|Hjo{KR2c}A6x+ta+`sQL-CLr|sRabdZd1zZ3naa&5<|*0 z7Pf)IXp+R^V%!O~3A^(P@u8Q?N-gC{VFaMGo8VwW;NrBXLV$G}mu zE>^OG91>*-Wn*(!o7_|J`gJ`SxR8srbohYS??w(MUkdlCRhM6;cX&Q}{=5tGqj?db zqL_YuN-Qf|iXsEmUr#W0sz4=bDo6|t!Fwg3XpC`E3??>DnJ`)?7)o7$P3h3T7*WV= z?6-h_tPyVzFDJq%Bo>&iTez>;hs89QVivW?lKs}#xvWR>yq)DH_&*xkq4`;z3t64Kq zzs@CxJPnwkhMVtS?pxS$R^&r#D=j@E!y*26b_GT-!{9r&J^f^8jQe^Wn7O;e2hRzd zX`?2WC?p3D6PloC3vNPHavS}rbgo{NZPxVkx0seK0nZdDcYt}q5hdF{h3j+>OI+~d%+u|nQ zEesL|L>|kjX4R0;%1xWTW&(z+qZK1Bw^79hK~pDj7P&p{xgr&U5ntX=j(t&av5sD@ zv&5}NlBgU^cxDwk*7ED++@{XsIH6Faux%Z@N4>Gsq=F211EB|}`Nz@v(iUkX>bK51 zF{P0@@g943T=^g|t)^8BeUO)b*M!Dakay<%9P(Uw0~r=VWQfVjUWK#&?w#AW8)@U6 zKF7=t$Y-Ye9#OkT2l3G`s|5A=h?|~M9a0TgUT=ViDZGGw-vf~HN}xRc9)_Nn1#%>m84i62F;F4l0Be54+0hw1$5>& zQ{`?Xc9E55cC3XS^Kj^|eoX@!hi0%d6i+u!?-2jujWo#bwGrhY6D$GEJm&@R2?z^2 zyq=xC$_4dr)yNMr-zN@pm&l@OT^?xu9+1r&xE4R^$^%id0H^u^LI^~t={hobECz(5 zQ=>ZXzgAZw4G{i{$g@?F3* z-hO(Zz5a7FMuW3TzI(n#K9Dbl9bn}cWRP5ks1QcqcDQ)qLPFiI%vit|Y8U&NS;x^bA0n%1vQo zBIyZ7MK%h9fp|Qu451x6{v5OAzR`r2{bc4@5x(sQ8mTk@#4XVO_n@&{LiBg=w}2)1 zOaVv$K88Eqv?2mAw&;z%w#D-G7&Gz{O-_?ea2DR<_dtNO*XcMncY^exod|&)f?GDh z_2AcH_vB+1R@Y~VCVD9XpY5|m4pGroJRMK=26WJM?LWfi$0vn{HL>|ky?>bjLrbDD zxCZb_*bU0d?cjR6w6`65LXXbM(v~qRuB}ZiYAOBo1)knvq@tno2rt_JhxzQx2~kXx zmSfs}n@XK)yo02FoUPg41VuYBvKv^#?m>^n_BjZ&t<P3(_Ai#>~j ziDBGdz1$2Z+c8Y6po+o-0ee#LbU>Uime_uH;i@T7pT2m}vRziTEjGcP5c+^H+!blY zc11_%Ou2-ek}XLG(gQOABTh6`iShcmS;uV0ma?$SEcuToS%gF}8)DN)gsw(dbUtTy zYe=wzXnFx4_N=>$0}n&(*{u826LG6*L9f<-PKBCYU$c@Bna)!zSf#FwCDBAJ$l13#@-LojnjjyK;SXtvHs0O+FK6+byT$=b&UwZN~uf81*|=c?Q`zt$LlUAeUue5HfkKi7=xa zP!sHY(sxM0n6Oc4Hf6CF90MaYWioBTaltZvl*}UMb#crxuR)!$~JzFk@BbH>KkNoI@Vb^ zNEa{|43ecW;*;eMN#bx`mi{ClJvI;=_uiw!T<#qi*3>Uab#7WNobyX~q#&P=_D(|t z*7L*PpX+8prKxN_sAn7?MKo=-xiT4n;$US=A6FeliDI?JDdQfxt|5JKiFsLRp}lrK z=%1`l&t~m}C{I7TwV(OJpM~Y$j}ZRo#S2TaZ~Vk5e-VXi)2oSbSwwi1W3#%h6t#;p z^Nq|qT;`3o)4OtaKtcDSsl1|sZBUI{@eHQvo#r|>3)%&%4*>-`7t}P9ES&R&t1ji64NckzqUd z!yhL6?2xKIV4OSe*3WVL4M=Uf@Y&bxq5ef+K&)ff)hPOz=cBE-*qrKH)>UIp+=93h z#Nh>egLX6S^x7k~ph-5@xq)q_q-QlX(vI~HM7yWK+(0kTO#+Z2?f99s2JDY2D=UYw zj;5nFTi8VbzIDTsqQkF}(Qtp31ZI7up`U8-QB#7hST6Fvym!vDat5K+bc~k z#k4Byt5THeE=o0?O9px@hmcUFolk7Vy7r;vEM5yX6Gctsagu%|`U(vGOVD=AFydEc zoZT(uQMfv06r?f@$K2ky1U#wvv3+P(_%Ai53novj_b3dTB%uv0G7hlh_=VHGE(^1W zPgc+N0HlyZ!LOFE7S1_iLdx~w2*3K4hd+H{zabk68Qbv0RPQ2*VyZpf$nSVx8K%y07INn{iF+p9Qhxz+6WqQ5D?$3CfdH)erp@WV2$e^%yC`^_jS6%~r%3$W@g$JKG|rhLPdAN{z+J4Mjo z-G5wwO(SZ#pswsoM*Y1T^d8+aYg>UM@yno(50)AHu;%Wtb8a$x5u!0lay*L?>*~w|^*!Ni(ziqe+Y7`^moQC$7W8#h1Y1Wtzk2<8k0U6I88{^hV~SA~ zdP@_7S@UcL@YR86D^L1yJGC{FrGPChcf6Vh;AtfwXQ!J=Nk~T;bNjBRM$OuIOp!xxgp>!2G3dA z7t~rFS*0~tw(l@D4SrKSj(Vw>%$Xyb`qymQq)V#xoA!Pw{MftR<%49|{P|MGWL4MZenGBikw1VpeCO^L#^yA!lAisz+cO z7m<({iM@z_(x%Os8E<5Nc3#3bX52H%dR_1*JeB$UAr0W683v#SUM2jF|Ag9p8p;~& zxM^^H*V)G_fB(k093{&Ohln9`9!gEQ*`vHs1?P77gK36EJXdWrP1NsN05!&M! z|H-yOCWFaFzy-7Kp*q~E; z@oKi7fyB86^R!VEQHfw08{apHoiZTHP8w~Hcir8j*xG9{=u5sl-KqNgjcqyhn4DNu zG#)|&_it|89Is*F;q5*96^VRqD9Q3Z){Ll)A&19JZ*s@L9z-I=FTPoDQAbG6D{RNJ z{6vI@Xv=7wQkM!$n0j!a|Ia9lNu5X%QZ0T?eY$aeTLM0de^_krl|ZG29$xmQ7goBr{KAak+7 z-be%doL+>4)|Hy(sz;6PT_0VZ*~QLS_RQ|dEI2~ZT&>egi*0N z^R)X_ra6_w@B`YgftOXLsRn&+*uY%6a5(f%Z-|-E%IwDvac{UfN5i6E7Vv2-=gj&F5l0az-3*qf4=Q8rKmjKGwZ}yD52MeEb>YOU6l2 z%mSzsQ_P4Hd#JfP1QEe5$vh#lB;+m|kpCeH1-V@kvcIw*x-w8Q`>n{FIHltox&a_o=bTwq0>jLpgo3v1P3(Rv){$IrSV zFsv%0cic~;AIAapdyTU6mEj4sB`mCW=QaZH-nIgD-;KLQLkwu>tOJB1!A@B`Z(DI; zD$rtf#bYs87cyo>_i7|9__a*>&stpmOYd*a(ePSK&;D!6M>Z9YfBv-q!~b7qpDTFq ze~7lD{Kv8TuhO3T_8aZEH zvIhR4jaNhfNSTBp^D< zmEG*uLmGhoM;O5D#r8D0(W|H$FBbg3+gsS2o{2ZMIu%mi9`ID&g14HC1!IJbeneNk zbP+Cmemigd8B z)-_~A;*_^j#9~2^sHOUJ3yK|KyZ#oZW;AB`AO)#w(FR&80gOC+jrrFSjIRAPWyk?c zZ?sgxwqR1fl1qF?4Yw^YIT;c*$Oe+%)_sBHC4G{Z$L>TKww<8xSFzr=w~D}8fW@DG zbZ$8$5S#Yjy~UTZe(+o9B|1n2%Hp`(+}PNdgz!l=FYW`QUMRz7LL>R|5!P@7h9j>{ z^3e&1NG$qEp&*~X>>0EYWyQA0z9F7*SdviP$} z7fMn4-Ovpr6SWzzM_lU7(-wQowV_y2?&CO=XL8Ye1mC`O>jeklVRYHDf*;3n-n zZ0hZ!@4V9jZ8byhhb&1|u85uIf+jQ%=STV8x-kQTHu25=P1D4tmxSZB_v z51keRu;T1S%`UvPu?c|qckb)1_`)fuJ*-h@<%F@k`z9!H;+hDJ#6+|E07ws-X(VvhU7a!3W+>X& zkFBI+YzCaQ!G!X;XWnb>nSY}s&gE5&qiwmmw;r=z01d4k31JYjxx#!*2Ke!2EuiqM zsf|~LH+vzVoxH3j8ku8-rN?W;p<(hmBjFX7W$z`hXHR8>@Je*~c{J|YtJsd*CAhps z{fq2jsuj{CgdO5*i7vsB&Z5|E;zbiZ<6i{cWJhWfs2lePH7Z+_Fpd2s|2{S`yK-lYg@{MPRHnw9k7nn zrU&=#?br611udF&gGA?aHH?y0dVqtve#3 zqM%Y^72L3OKoA5$6W2mqSVRdbGKe4%Q1(Tp&e(_oW3^gY6&EC7Q85AuOI)ZcA_7K} z2q+eWkgypL5t#4yLg$?Jbo$4f8P7q^A<26$@BM!Fe)qfge)q>8Cr9p$zs+l!IC0__ zuskMK!4=j$o=4X@+xI+#(ym;{L>!LP#AEg7m)Oe!LH8y)8VYkFQ&wO2CId1SWzf@E zkbkODp(b+Gm$a6V1)^_6EfDcoaAO{3o@f>ulND})n#qdhgU*~Jk0obYCXLW0o`tP` z6>x7x1mf!blXdJ-npBI*FbAQc)(NvHE_Ck7fd7`tCLFi|*zZy}7==`JzWG}>oWr|F zgGLtDLmDUNCR#d%^HNcb^%qB_LELJEnzum>uR$tQ+je6r(fDc5tjAguZ@$c|)G)dqeNsj)j6j2y0r0FPb)0+!lYzxnT5Kb$tZqceI*@ng^;3-b zS_RB77JQyjV8^bTfYn=DbF!iSL3{dM146tz(LW|7BiOB(j2qjsqmbM%kcSPTuyreQ zlSyCyI=GFrsP31gT81{C{%+MouB$3teUM69`7c7od$W$i%iS0b>6W4xWo06k=aGB4kwFhC;302Zbz znW~(srvqznnx}1@4Uq*WPOaPY=C2L+#E7uqgZVsjH&rC>LY#nvj1#jU1#p z*{X>f8#{lq%$aXyVQ$_N8}R}PHzog&q7t%%({~FcI_EmRq>N?a704vkBRsH2m_I)_Ar~Zp}gsS>XHdsE$iHjfV!B4hd^aiMq};z}~q!Iw=l&kh$^} ziZE5{-`2!55P0}A^wm*6NZyX4@M|6fd!7~qG}I!C;=L#^z^P{o%UW=I-_jicgY6&> z23LU^V=@55bO9*tUqd7KEvI|QHc^&4LHpbxb+DrFR?u~I{@J-e!dja^OXb2&?NQ;V zM575>*H|D=YF}D+wI3quu#6mPDp2tvVWrXcgF% zl1nW=5n0+Y6dR;D_JNpLAw2MiENIgiE^dV+bg!1c>(5Sh*z-e%!n*}M-Q}du^sz7= zOu=Ma%=Q8KfeK}&Un`1I21(5IPpQP)G#&)~I27J*21UFPm3O?BrQe{cBI(RR*Xx&S z7pEtJyiCZ|m$RTko{Lt(`b?qw3V8p!pp}nr>4O@MuAbPs0 zYX-58|4J%O(!q~AEk=dF@j*1Tc_H`7K$>VGn1?ssz#xT)lb@n6YHCJR^KAOWNs~^I zc_aDT1wKyB5(#N{YP&@R#W9y35p!7Fa4%tpIb?rYg>GM?W4O&dym?L^Gr#r8MXypI^r3&-4H@HeX#>f|SNa!Tx+%KjUuh_%oJ#LyX^AZ?EKm zZFcIG`sOYQg?xe$L1IQWkWf@+Z;}Yx31j%oQmJ(3rzGn2hU*<&8I5 z?g7zF1R+L#p25*qp)SK=!`ijRS#AADy(!CK|N4k-X$eWxxNX~}&ohp7fLDPkdKYR> zqr5?DZwF*)xH&Q`9t6Gu@TP97v$GdE!yBFu99!K)1~lnCf1g^t`qj)emvB>2MA@DJ ziHRZ>;}m$fXm3~>gitYG zN?G9;*;V$pRfy#L3Z&7h0xrqp(rP;DsgWcr{oTxqP`^#P68}G;75=ZB1$FQk$$Az` z8`C;1SrX!)#Szrg68i%2?H2>)cwwLRr$T?hkVP#oaUTT(#J)3DL@!5zi|!2xfdug5 z9xEYZkelI4@nUQW_S6fIC@r=?l`5CJ7J0o44C&~9P-R9=)NVp~miBGfK1>u$sU{+o zxsVP+abhNnt)HTqVpKx4DK8Kz09Q%mc{!!kHHg8_pII}OI+2s+L~4~#k<(8y)Op25 z#CFl@hb}>l4bxc@4AW9j=201TeI{O`)gGoEk9#f%kuoP?B3uP6VKUi0V!r0w|6Ng`w{$pMd_)0vEqQ!ptm)P4Douraq?0+;K*slrM5UhL)Fb z*SfpcmM2(uzxY7ROfb@OT?hThdlNNufqcSauLHe^w5A{!Sg3P?EMzi*w*yJv(Z74^ z*B%}ou^5LIpP?W}sJH%5wH3eR6CgJeIiM&V@b$pi7SZWY`#1~#@S+O4)`KM>b<n zsdXgQ+J)|z5JcL5$4^inQaf5(JGcEHn;bAvvvtH;vk;T=r9@a@$}mdwN@c6LQ6vma z;wurYq;>x8PJv^w^Rb#g|KXe8-6`alHp!quI-d2MECq3d2A`4>{sDcLy9a*KsSodu z&G7ln0>P@9vXK6xU4)~fW9Dxjb-fxv6lb#x4)`ge-{r1UFI1Zyg|TzVw3&5&N#%{Z zQ?tlZj|rJY+;@x%2BHWNqzAe5f%cWD9)y!i8iF&Si}0k9kREM7UC;?S-79aIACkHw zm$M1gLu)(VMd4)IRbefe8FRgm2t@8a?&mC0|ALe@c~6lB2-gcx_LqmaJC6_m7-w+M z0W(Ri{kj1)jA*VJDXIZXd|cXtpti0`Kn~FEFtqiMa(L2pkYFBKk%6p?{3ieY;O>z> z|3~ukzp+xLToho)`(Er$hy(MtG(tFqoWk3fx-#zx$Q-Qw7CP^p4;SPXk#f1()EsI? zmHsel1L@xdZ$5}#QkOS?v#2{lt2r!qxyH7{h$tg=&aMbdT_!>dcV4A;GMx?)lF;{H zNdA_ZLl7oSev~o;T3#gKhB+sn>=_qf(flDR^~##9OWsnTt2v(|igS8+N_eWDBd$}| z>Tgb2CQ~%N7UcQ#fwWv??MfZCz+mVz=+>k<{y9&>+q9N_)FM zUm9$gl1Q1>=fw8VRDa4e>z{Jw2Zal%;^JR#)JB-;#lR(4y{OT=by7_Z6Js^>^&xm_ zg@}czY^y6>A^+`*w*(NHj*$)k&I`kbzLAk3OS1|)@Laxlr~dd;%RP+!8XJvbyHwN_ zKzqp$o-YbA5Z%4d1rKIb6Yob;K_)*ri(33LbY#kWENzg0I*$BDHfENN2g?RQ$3ra` zn3+sw$BPvdt4%97p<$0Wx>Kfj6+bH^;w;m3b@Ns!tx_D2p-WV3nMMv@kcOzpNOSlb zDAN!5B>ri1ii8tqXF2mxU8+LUa!_LuW8MRjZ`QXkUx7@=70x3@ketJex$z)<*V*2LAzA=W~nnPXrcBpQ@ zwM=<3QLtm{)_1=7GA*CM)7v@MLN70&PH%{5nw1!?Y(=dd`4dil+C~}jc^`RA*yq`j zQabgv#l`N292uHK3`cMJ^yEf=i+!m%)bi0^8x2LF_)(hGXN&r~`K`fL5tzS4%Cn{F|Q z`4ZCUR=zb2)FQDxjI;npg)Rj;9f53yHJh@l)o1R!_LuIv=@)_;b?~l9T6f z?pmHqsW*w)t6C4niQ5=H1?wWl;Ncp2=Z8z(VLH>kqOXDW;3{x(+B&0qw*D2Lp=}(? z+~feUnM;Ige$J1hSS;IBy%}8jB6=UwGmAUjiG|XEa#Y=Of-R<}fbT!|JtiCA&~*V8 z(|Zx4iBP&3s92J!u`zSjF@`ZcQ@GUy%;Nyd7zTeK&Y54#~< rlm{5NcMT0Ex4`(wu%1K<%s&klI_Mc)P>x$mzS=sb10!eU&hP#OxLY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + { "acetone":{ "cosmors": ["propanone_c0", "propanone_c0"], "dcosmors": ["propanone", "propanone"], "xtb": ["acetone", "acetone"], "cpcm": ["acetone", "acetone"], "smd": ["ACETONE", "ACETONE"], "DC": 20.7 }, "benzaldehyde":{ "cosmors": ["benzaldehyde_c0", "benzaldehyde_c0"], "dcosmors": [null, "propanone"], "xtb": ["benzaldehyde", "benzaldehyde"], "cpcm": [null, "Pyridine"], "smd": ["BENZALDEHYDE", "BENZALDEHYDE"], "DC": 18.2 }, "benzene":{ "cosmors": ["benzene_c0", "benzene_c0"], "dcosmors": [null, "toluene"], "xtb": ["benzene", "benzene"], "cpcm": ["Benzene", "Benzene"], "smd": ["BENZENE", "BENZENE"], "DC": 2.3 }} + + solvent name in censo + dielectric constant (ɛ​) + + names of the parameter files + without file extension + + + + + + + + + + solvent names in each + solvent model + + + + + + + + not all solvents are availableand "replacement" solvents can be choosen + + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..75282c5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,28 @@ +[metadata] +name = censo-QM +version = 0.0.15 +description = CENSO - Comandline ENergetic SOrting for conformer rotamer ensembles +long_description = file: README.rst +long_description_content_type = text/x-rst +author = Fabian Bohle +author_email = 'tbd' +url = 'tbd' +license = LGPL3 +classifiers = + Intended Audience :: Science/Research + Operating System :: POSIX :: Linux + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Topic :: Scientific/Engineering :: Chemistry + +[options] +packages = find: +tests_require = + pytest +python_requires = >=3.6 + +[options.entry_points] +console_scripts = + censo = censo_qm.censo:main diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..fc1f76c --- /dev/null +++ b/setup.py @@ -0,0 +1,3 @@ +from setuptools import setup + +setup() \ No newline at end of file