├── .circleci └── config.yml ├── .codecov.yml ├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── CI.yaml ├── .gitignore ├── .readthedocs.yaml ├── LICENSE ├── MANIFEST.in ├── QMzyme ├── CalculateModel.py ├── GenerateModel.py ├── MDAnalysisWrapper.py ├── QMzymeAtom.py ├── QMzymeModel.py ├── QMzymeRegion.py ├── RegionBuilder.py ├── SelectionSchemes.py ├── TruncationSchemes.py ├── Writers.py ├── __init__.py ├── _version.py ├── aqme │ ├── __init__.py │ ├── argument_parser.py │ ├── crest.py │ ├── filter.py │ ├── qprep.py │ └── utils.py ├── configuration │ └── __init__.py ├── converters.py ├── data │ ├── 1oh0.pdb │ ├── 1oh0_equ.prmtop │ ├── 1oh0_equ.prod_1.stripped.dcd │ ├── 1oh0_equ.prod_1.stripped.pqr │ ├── 1oh0_equ.rst7 │ └── __init__.py ├── truncation_utils.py └── utils.py ├── README.md ├── docs ├── API │ ├── API_reference.rst │ ├── QMzyme.CalculateModel.rst │ ├── QMzyme.GenerateModel.rst │ ├── QMzyme.MDAnalysisWrapper.rst │ ├── QMzyme.QMzymeAtom.rst │ ├── QMzyme.QMzymeModel.rst │ ├── QMzyme.QMzymeRegion.rst │ ├── QMzyme.QMzymeResidue.rst │ ├── QMzyme.RegionBuilder.rst │ ├── QMzyme.SelectionSchemes.rst │ ├── QMzyme.TruncationSchemes.rst │ ├── QMzyme.Writers.rst │ ├── QMzyme.aqme.qprep.rst │ ├── QMzyme.configuration.rst │ ├── QMzyme.truncation_utils.rst │ ├── QMzyme.utils.rst │ └── index.rst ├── Contributing │ ├── CODE_OF_CONDUCT.rst │ ├── calculation_methods.rst │ ├── general.rst │ ├── index.rst │ ├── selection_schemes.rst │ ├── suggestions.rst │ ├── truncation_schemes.rst │ └── writers.rst ├── Examples │ ├── Getting_Started.ipynb │ ├── Ligand Parameterization.ipynb │ ├── QM-only Calculation.ipynb │ ├── QMQM2 Calculation.ipynb │ ├── QMxTB Calculation.ipynb │ ├── Serialization.ipynb │ ├── Working with Trajectories.ipynb │ └── index.rst ├── Images │ ├── QMzyme_logo.png │ ├── QMzyme_schema.png │ ├── all_alpha_carbon.png │ └── terminal_alpha_carbon.png ├── Makefile ├── Quickstart │ └── installation.rst ├── README.rst ├── conf.py ├── index.rst ├── make.bat ├── references.rst └── requirements.txt ├── logo.png ├── pyproject.toml ├── requirements.txt ├── setup.cfg └── tests ├── .DS_Store ├── test_CA_terminal.py ├── test_CalculateModel.py ├── test_E2E.py ├── test_GenerateModel.py ├── test_QMzymeAtom.py ├── test_QMzymeModel.py ├── test_QMzymeRegion.py ├── test_SelectionSchemes.py └── test_TruncationSchemes.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # This config was automatically generated from your source code 2 | # Stacks detected: cicd:github-actions:.github/workflows,deps:python:. 3 | version: 2.1 4 | orbs: 5 | python: circleci/python@2.1.1 6 | codecov: codecov/codecov@3.2.4 7 | 8 | jobs: 9 | build-and-test: 10 | # Install dependencies and run tests 11 | docker: 12 | #- image: cimg/python:3.11-node 13 | - image: continuumio/miniconda3 14 | #filters: 15 | #branches: 16 | #only: 17 | #- class_structure_redesign 18 | steps: 19 | - checkout 20 | - python/install-packages: 21 | pkg-manager: pip 22 | - run: | 23 | conda update -y conda 24 | conda create -n qmzyme python=3.11 25 | source activate base 26 | conda activate qmzyme 27 | pip install -e .[test] 28 | pytest -v --color yes --cov-config=pyproject.toml --cov=QMzyme --cov-report=xml 29 | rm -r /tmp/* 30 | cp -r * /tmp 31 | - store_artifacts: 32 | path: docs/_build/html/ 33 | destination: html 34 | - persist_to_workspace: 35 | root: /tmp 36 | paths: 37 | - coverage.xml 38 | - QMzyme 39 | 40 | codecov-coverage: 41 | docker: 42 | - image: cimg/python:3.11 43 | steps: 44 | - attach_workspace: 45 | at: /tmp 46 | # this checkout step is necessary to avoid an issue in which codecov thinks the report is empty 47 | - checkout 48 | - codecov/upload: 49 | file: /tmp/coverage.xml 50 | token: CODECOV_TOKEN 51 | 52 | # build_docs: 53 | # docker: 54 | # - image: continuumio/miniconda3 55 | # steps: 56 | # - checkout 57 | # - run: | 58 | # conda update -y conda 59 | # conda create -n qmzyme python=3.11 60 | # source activate base 61 | # conda activate qmzyme 62 | # conda install pandoc 63 | # pip install -e . 64 | # pip install -r docs/requirements.txt 65 | # cd docs;make html 66 | # - store_artifacts: 67 | # path: docs/_build/html/ 68 | # destination: html 69 | 70 | workflows: 71 | build-and-test: 72 | jobs: 73 | # - build_docs 74 | - build-and-test 75 | - codecov-coverage: 76 | requires: 77 | - build-and-test 78 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: 80% 6 | patch: 7 | default: 8 | target: 80% 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | QMzyme/_version.py export-subst 2 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | We welcome contributions from external contributors, and this document 4 | describes how to merge code changes into this QMzyme. 5 | 6 | ## Getting Started 7 | 8 | * Make sure you have a [GitHub account](https://github.com/signup/free). 9 | * [Fork](https://help.github.com/articles/fork-a-repo/) this repository on GitHub. 10 | * On your local machine, 11 | [clone](https://help.github.com/articles/cloning-a-repository/) your fork of 12 | the repository. 13 | 14 | ## Making Changes 15 | 16 | * Add some really awesome code to your local fork. It's usually a [good 17 | idea](http://blog.jasonmeridth.com/posts/do-not-issue-pull-requests-from-your-master-branch/) 18 | to make changes on a 19 | [branch](https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/) 20 | with the branch name relating to the feature you are going to add. 21 | * When you are ready for others to examine and comment on your new feature, 22 | navigate to your fork of QMzyme on GitHub and open a [pull 23 | request](https://help.github.com/articles/using-pull-requests/) (PR). Note that 24 | after you launch a PR from one of your fork's branches, all 25 | subsequent commits to that branch will be added to the open pull request 26 | automatically. Each commit added to the PR will be validated for 27 | mergability, compilation and test suite compliance; the results of these tests 28 | will be visible on the PR page. 29 | * If you're providing a new feature, you must add test cases and documentation. 30 | * When the code is ready to go, make sure you run the test suite using pytest. 31 | * When you're ready to be considered for merging, check the "Ready to go" 32 | box on the PR page to let the QMzyme devs know that the changes are complete. 33 | The code will not be merged until this box is checked, the continuous 34 | integration returns checkmarks, 35 | and multiple core developers give "Approved" reviews. 36 | 37 | # Additional Resources 38 | 39 | * [General GitHub documentation](https://help.github.com/) 40 | * [PR best practices](http://codeinthehole.com/writing/pull-requests-and-other-good-practices-for-teams-using-github/) 41 | * [A guide to contributing to software packages](http://www.contribution-guide.org) 42 | * [Thinkful PR example](http://www.thinkful.com/learn/github-pull-request-tutorial/#Time-to-Submit-Your-First-PR) 43 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | Provide a brief description of the PR's purpose here. 3 | 4 | ## Todos 5 | Notable points that this PR has either accomplished or will accomplish. 6 | - [ ] TODO 1 7 | 8 | ## Questions 9 | - [ ] Question1 10 | 11 | ## Status 12 | - [ ] Ready to go -------------------------------------------------------------------------------- /.github/workflows/CI.yaml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | .pytest_cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # profraw files from LLVM? Unclear exactly what triggers this 105 | # There are reports this comes from LLVM profiling, but also Xcode 9. 106 | *profraw 107 | 108 | # In-tree generated files 109 | */_version.py 110 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # readthedocs.yml 2 | 3 | version: 2 4 | 5 | 6 | # Build documentation in the docs/ directory with Sphinx 7 | sphinx: 8 | builder: html 9 | configuration: docs/conf.py 10 | fail_on_warning: false 11 | 12 | build: 13 | os: ubuntu-20.04 14 | tools: 15 | python: "3.11" 16 | 17 | python: 18 | install: 19 | - requirements: docs/requirements.txt 20 | - method: pip 21 | path: . 22 | 23 | # conda: 24 | # environment: docs/requirements.yaml 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2024 Heidi Klem 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | global-exclude *.py[cod] __pycache__ *.so 2 | -------------------------------------------------------------------------------- /QMzyme/CalculateModel.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Code written by Heidi Klem. 3 | # e: heidiklem@yahoo.com or heidi.klem@nist.gov 4 | ############################################################################### 5 | 6 | """ 7 | Module in charge of creating input files for QM-only or QM/MM calculations. This 8 | module integrates the `AQME QPREP `_ 9 | workflow. 10 | """ 11 | 12 | from QMzyme.data import protein_residues 13 | 14 | class CalculateModel: 15 | """ 16 | Class responsible for storing calculation type and associated region. 17 | This information is used to determine what type of calculation writer to call 18 | in the Writers module. 19 | 20 | This class also determines if there are any model issues, such as guessing region charge if partial 21 | overlap between occurs between regions (you do not want to double-count charge from duplicate atoms). 22 | 23 | This class will also alert you if you if the methods you are assigning to regions do not add up to a 24 | multi-scale calculation method that exists as a subclass of :class:`~QMzyme.Writers.Writer`, because 25 | QMzyme would not know how to write that input file without the corresponding writer class. 26 | """ 27 | calculation = {} 28 | calc_type = None 29 | 30 | def add(self, type, region): 31 | if type == 'QM' and 'QM' in CalculateModel.calculation: 32 | # If region is same, we assume the user is trying to overwrite the old method 33 | # Otherwise, we will rename the type to allow for multiple QM regions 34 | if region != CalculateModel.calculation['QM']: 35 | count = 2 36 | while type in CalculateModel.calculation: 37 | type = f'QM{count}' 38 | count += 1 39 | CalculateModel.calculation[type] = region 40 | CalculateModel.calc_type = type 41 | 42 | def combine_regions_and_methods(): 43 | """ 44 | This method is triggered in :class:`~QMzyme.GenerateModel.GenerateModel` when 45 | :func:`~QMzyme.GenerateModel.GenerateModel.write_input` is called. 46 | 47 | It ensures the overall multiscale method contains all atoms of the QMzymeModel regions 48 | that have been assigned a method. 49 | """ 50 | 51 | if len(CalculateModel.calculation) == 1: 52 | return 53 | # Start with QM region(s) 54 | # collect QM_regions: 55 | calc_type = '' 56 | qm_regions_sorted = sorted([calc for calc in CalculateModel.calculation if 'QM' in calc]) 57 | base_region = CalculateModel.calculation[qm_regions_sorted[0]] 58 | calc_type += qm_regions_sorted[0] 59 | if len(qm_regions_sorted) > 1: 60 | for i in range(1,len(qm_regions_sorted)): 61 | combined = base_region.combine(CalculateModel.calculation[qm_regions_sorted[i]]) 62 | base_region = combined 63 | calc_type += qm_regions_sorted[i] 64 | for calc in ['XTB', 'ChargeField']: 65 | if calc in CalculateModel.calculation: 66 | combined = base_region.combine(CalculateModel.calculation[calc]) 67 | calc_type += calc 68 | CalculateModel.calc_type = calc_type 69 | CalculationFactory._make_calculation(calc_type)().assign_to_region(region=combined) 70 | CalculateModel.calculation[calc_type] = combined 71 | 72 | def _reset(): 73 | CalculateModel.calculation = {} 74 | CalculateModel.calc_type = None 75 | 76 | class CalculationBase: 77 | """ 78 | Base class for all single-method classes. Contains the inherited method assign_to_region(). 79 | """ 80 | def assign_to_region(self, region, charge=None, mult=1): 81 | """ 82 | Connects a calculation method instance to a QMzymeRegion. This method also searches for any 83 | atoms in the region with attribute ``is_fixed=True`` to store what atoms will be constrained 84 | in the calculation file. 85 | 86 | :param region: Region to assign method istance to. 87 | :type region: :class:`~QMzyme.QMzymeRegion.QMzymeRegion`, required 88 | 89 | :param charge: Charge of the region. If not specified and the QMzymeRegion instance does not have 90 | a `charge` attribute, the QMzymeRegion :func:`~QMzyme.QMzymeRegion.QMzymeRegion.guess_charge` method 91 | will be called. 92 | :type charge: int, default=None 93 | """ 94 | self._set_constraints(region) 95 | self.mult = mult 96 | region.set_method(self.__dict__) 97 | self._set_charge(region, charge) 98 | region.set_atom_segid(region.method["type"]) 99 | CalculateModel().add(type=self.type, region=region) 100 | 101 | def _set_charge(self, region, charge): 102 | if charge is None: 103 | if not hasattr(region, "charge"): 104 | region.guess_charge(verbose=False) 105 | self.charge = region.charge 106 | else: 107 | self.charge = charge 108 | 109 | def _set_constraints(self, region): 110 | self.freeze_atoms = region.get_indices('is_fixed', True) # these indices are 0 indexed and in order of increasing atom id 111 | 112 | class QM_Method(CalculationBase): 113 | """ 114 | Class to set a quantum mechanics method for a QMzymeRegion. 115 | 116 | :param region: QMzymeRegion to apply method to 117 | :type region: :class:`~QMzyme.QMzymeRegion.QMzymeRegion`, required 118 | 119 | :param basis_set: Defines the basis set to use for calculation. 120 | :type param: str, required 121 | 122 | :param functional: Defines the functional to use for calculation. 123 | :type functional: str, required 124 | 125 | :param charge: Charge of the region. If not provided in parameters, charge will be guessed. 126 | :type charge: int 127 | 128 | :param mult: Multiplicity of the region. 129 | :type mult: int, default=1 130 | 131 | :param qm_input: Keywords to include in the input file route line to declare any details 132 | beyond the basis set and functional. E.g. "EmpiricalDispersion=GD3BJ opt freq". Not including 133 | anything here means the calculation will be a single-point energy calculation with no frequency analysis. 134 | :type qm_input: str, default='' 135 | 136 | :param qm_end: Final line(s) in the input file. 137 | :type qm_end: str, default='' 138 | 139 | """ 140 | def __init__(self, basis_set, functional, qm_input="", qm_end="", program='orca'): 141 | self.type = 'QM' 142 | self.qm_input = qm_input 143 | self.basis_set = basis_set 144 | self.functional = functional 145 | self.qm_input = qm_input 146 | self.qm_end = qm_end 147 | self.program = program 148 | self._set_qm_input() 149 | 150 | def _set_qm_input(self): 151 | for info in [self.functional, self.basis_set]: 152 | if info not in self.qm_input: 153 | self.qm_input = f"{info} {self.qm_input}" 154 | self.qm_input = self.qm_input.strip() 155 | 156 | class XTB_Method(CalculationBase): 157 | """ 158 | Class to set an xTB method for a QMzymeRegion. 159 | """ 160 | def __init__(self): 161 | self.type = 'XTB' 162 | 163 | class ChargeField_Method(CalculationBase): 164 | """ 165 | Under development. Class to prepare a QMzymeRegion for ChargeField treatment (aka charge embedding). 166 | """ 167 | def __init__(self): 168 | self.type = 'ChargeField' 169 | 170 | class MultiscaleCalculationBase(CalculationBase): 171 | """ 172 | Base class for all multi-scale methods. 173 | """ 174 | def assign_to_region(self, region, charge=None, mult=1): 175 | self.region = region 176 | self._set_constraints(region) 177 | self.mult = mult 178 | region.set_method(self.__dict__) 179 | region.method["program"] = 'orca' 180 | #self._set_charge(CalculateModel.calculation[self.type], charge) 181 | self._set_charge(self.region, charge) 182 | self._set_qm_input() 183 | # fix segids 184 | for atom in self.region.atoms: 185 | if atom.id in CalculateModel.calculation['QM'].ids: 186 | setattr(atom, "segid", "QM") 187 | else: 188 | #setattr(atom, "segid", CalculateModel.calc_type[2:]) 189 | setattr(atom, "segid", CalculateModel.calc_type[2:]) 190 | 191 | def _set_qm_input(self): 192 | # QM/QM2 or QM/XTB. QMChargeField duck types this method. 193 | qm_input = f"QM/{CalculateModel.calc_type.replace('QM', '', 1)}" 194 | if qm_input not in CalculateModel.calculation['QM'].method['qm_input']: 195 | qm_input += f" {CalculateModel.calculation['QM'].method['qm_input']}" 196 | qmmm_section = f"%QMMM {self.qm_input}\n" 197 | qmmm_section += f" QMATOMS {self._qm_atoms()} END\n" 198 | qmmm_section += f" Charge_Total {self.region.charge} END" 199 | self.qm_input = f"{qm_input}\n{qmmm_section}" 200 | self.region.method['qm_input'] = self.qm_input 201 | self.region.method['type'] = self.type 202 | 203 | def _qm_atoms(self): 204 | import numpy as np 205 | qm_atoms = self.region.get_ix_array_from_ids(ids=CalculateModel.calculation['QM'].ids) 206 | # qm_atoms = [] 207 | # for i, atom in enumerate(self.region.atoms): 208 | # if atom.segid == 'QM': 209 | # qm_atoms.append(i) 210 | range = '' 211 | for i in np.arange(1, np.max(qm_atoms)+1): 212 | if i in qm_atoms: 213 | if i-1 not in qm_atoms: 214 | range += "{"+str(i) 215 | if i+1 not in qm_atoms: 216 | range += "} " 217 | elif i+1 not in qm_atoms: 218 | range += f":{i}"+"} " 219 | return range.strip() 220 | 221 | class _QMQM2_Method(MultiscaleCalculationBase): 222 | def __init__(self): 223 | self.type = 'QMQM2' 224 | qm2_region = CalculateModel.calculation['QM2'] 225 | qm2_input = qm2_region.method['qm_input'] 226 | self.qm_input = qm2_input.lower().replace('opt','') 227 | self.qm_input = qm2_input.lower().replace('freq','') 228 | self.qm_input = f"QM2CUSTOMMETHOD '{qm2_input}'" 229 | 230 | class _QMXTB_Method(MultiscaleCalculationBase): 231 | def __init__(self): 232 | self.type = 'QMXTB' 233 | self.qm_input = '' 234 | 235 | class _QMChargeField_Method(MultiscaleCalculationBase): 236 | def __init__(self): 237 | self.type = 'QMChargeField' 238 | self.qm_input = '' 239 | 240 | class CalculationFactory: 241 | """ 242 | Factory Class to Register Concrete Calculation Method Classes. 243 | """ 244 | calc_methods = {} 245 | def _register_method(calculation_type, class_name): 246 | CalculationFactory.calc_methods[calculation_type] = class_name 247 | 248 | def _make_calculation(calculation_type): 249 | calculation = CalculationFactory.calc_methods.get(calculation_type) 250 | if not calculation: 251 | raise UserWarning(f"Calculation method {calculation} not found.") 252 | return calculation 253 | 254 | possible_methods = { 255 | 'QM': QM_Method, 256 | 'XTB': XTB_Method, 257 | 'ChargeField': ChargeField_Method, 258 | 'QMQM2':_QMQM2_Method, 259 | 'QMXTB': _QMXTB_Method, 260 | 'QMChargeField': _QMChargeField_Method 261 | } 262 | for key, val in possible_methods.items(): 263 | CalculationFactory._register_method(key, val) 264 | 265 | -------------------------------------------------------------------------------- /QMzyme/GenerateModel.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Code written by Heidi Klem. 3 | # e: heidiklem@yahoo.com or heidi.klem@nist.gov 4 | ############################################################################### 5 | 6 | """ 7 | **GenerateModel** is the primary user-facing module in QMzyme. :class:`~GenerateModel` 8 | is used to load a starting structure, define QMzyme regions, and write calculation input. 9 | To avoid unintended behavior, the initial stucture must be pre-processed. I.e., ensure 10 | hydrogens have been added, and the structure is representative of the system you hope 11 | to study. If atomic charge information is not present in the input file(s), QMzyme 12 | will guess atomic charges by refering to the residue names. Any residue name corresponding 13 | to standard protein residue names, defined `here `_, 14 | are able to be parsed for their total charge. This library can also be found in :py:mod:`~QMzyme.configuration` under 15 | the dictionary protein_residues. If you have a non-protein residue QMzyme will assume its charge is 0. This 16 | is important if you have a ligand with a non-zero charge that you will include in your calculation. After importing 17 | QMzyme you can update the charge dictionary to add this information by adding to the residue_charges dictionary found 18 | in :py:mod:`~QMzyme.configuration`. 19 | 20 | The starting structure is loaded in using MDAnalysis and converted to a Universe object. 21 | There are a variety of ways to define QMzyme region(s), and once a region has been set it 22 | can be further modified, i.e., through truncation schemes. Lastly, this module interfaces with 23 | :class:`~QMzyme.CalculateModel.CalculateModel`, :class:`~QMzyme.Writers.Writer` and 24 | :class:`~QMzyme.aqme.qprep.qprep` to automate the creation of single- or multi-scale 25 | calculation input files. 26 | """ 27 | 28 | from QMzyme.QMzymeModel import QMzymeModel 29 | from QMzyme.utils import make_selection 30 | from QMzyme.TruncationSchemes import TerminalAlphaCarbon 31 | from QMzyme.CalculateModel import CalculateModel, CalculationFactory 32 | from QMzyme.Writers import WriterFactory 33 | 34 | 35 | class GenerateModel(QMzymeModel): 36 | """ 37 | GenerateModel can be instantiated with an MDAnalysis Universe directly, 38 | or any combination of parameters that MDAnalysis.core.universe.Universe 39 | accepts to create a Universe. 40 | See https://userguide.mdanalysis.org/stable/universe.html for details. 41 | 42 | :param name: Name to give to the QMzymeModel. This is used for default file naming 43 | purposes throughout the QMzyme package. If not provided, it will default to 44 | the base name of the universe filename attribute. 45 | :type name: str, optional 46 | :param universe: MDAnalysis Universe object. If not specified, user will need to provide file(s) that 47 | MDAnalysis can use to create a Universe object. 48 | :type universe: `MDAnalysis.Universe `_, default=None 49 | :param select_atoms: `MDAnalysis selection command `_ 50 | to specify which atoms are stored in the universe. 51 | :type select_atoms: str, default='all' 52 | :param frame: If trajectory was provided, specify a frame to extract coordinates from. 53 | :type frame: int, default=0 54 | :param pickle_file: Provide name/path+file of previously pickled QMzymeModel object to inialize 55 | :type pickle_file: str, default=None 56 | 57 | :Usage: 58 | 59 | To instantiate a model from a PDB file called "filename.pdb": 60 | 61 | .. code-block:: python 62 | 63 | model = QMzyme.GenerateModel("filename.pdb") 64 | 65 | If "filename.pdb" contains any components you know you do not want included in your model, you can initialize the 66 | GenerateModel instance from a subselection of atoms by using the select_atoms argument: 67 | 68 | .. code-block:: python 69 | 70 | model = QMzyme.GenerateModel("filename.pdb", select_atoms="not resname WAT") 71 | 72 | You can also initialize the model from a topology and trajectory file(s) and specify what frame to take coordinates from: 73 | 74 | .. code-block:: python 75 | 76 | model = QMzyme.GenerateModel("filename.pdb", "filename.dcd", frame=100) 77 | 78 | """ 79 | def __init__(self, *args, name=None, universe=None, select_atoms='all', frame=0, pickle_file=None, **kwargs): 80 | CalculateModel._reset() 81 | super().__init__(*args, name=name, universe=universe, frame=frame, select_atoms=select_atoms, pickle_file=pickle_file, **kwargs) 82 | 83 | def __repr__(self): 84 | return f"" 85 | 86 | def set_catalytic_center(self, selection): 87 | """ 88 | This method calls the set_region method and forces the region name 89 | to be 'catalytic_center'. See :py:meth:`~QMzyme.GenerateModel.GenerateModel.set_region`. 90 | """ 91 | self.set_region(selection=selection, name='catalytic_center') 92 | 93 | 94 | def set_region(self, selection, name=None, **kwargs): 95 | """ 96 | Method to create a QMzymeRegion and add to the QMzymeModel regions list. 97 | 98 | :param selection: Determines what atoms are included in the region. A 99 | variety of input options are accepted: 100 | 101 | * str that can be interpreted by `MDAnalysis selection commands `_ 102 | 103 | * an `MDAnalysis AtomGroup` 104 | 105 | * a :class:`~QMzyme.QMzymeRegion.QMzymeRegion` 106 | 107 | * any concrete class of :class:`~QMzyme.SelectionSchemes.SelectionScheme`, i.e., :class:`~QMzyme.SelectionSchemes.DistanceCutoff`. Options can be found in :py:mod:`~QMzyme.SelectionSchemes`. 108 | 109 | :type selection: See options below, required 110 | :param name: Name of the resulting region. 111 | :type name: str, optional 112 | :param kwargs: Keyword arguments that might be needed if a :class:`~QMzyme.SelectionSchemes.SelectionScheme` 113 | is used. For example, the parameter `cutoff` is required to use the :class:`~QMzyme.SelectionSchemes.DistanceCutoff` 114 | scheme. 115 | 116 | :Usage: 117 | 118 | .. code-block:: python 119 | 120 | model.set_region(selection="resid 10 or resid 15", name="two_residues") 121 | 122 | .. code-block:: python 123 | 124 | from QMzyme.SelectionSchemes import DistanceCutoff 125 | model.set_region(selection=DistanceCutoff, cutoff=5) 126 | 127 | .. note:: 128 | When using a :class:`~QMzyme.SelectionSchemes.SelectionScheme` the scheme class must be imported. 129 | 130 | """ 131 | region = make_selection(selection, model=self, name=name, **kwargs) 132 | self.add_region(region) 133 | 134 | 135 | def truncate(self, scheme=TerminalAlphaCarbon, name=None): 136 | """ 137 | Method to truncate QMzymeModel. All QMzymeModel regions with assigned methods will be 138 | combined and truncated according to the specified scheme. The resulting region will 139 | be saved as the QMzymeModel `truncated` attribute. 140 | 141 | :param scheme: Specifies the truncation scheme to use. Options can be found 142 | in :py:mod:`~QMzyme.TruncationSchemes`. 143 | :type scheme: :py:class:`~QMzyme.TruncationSchemes.TruncationScheme` concrete class, 144 | default=:class:`~QMzyme.TruncationSchemes.TerminalAlphaCarbon` 145 | :param name: Name to give the truncated model. If None, the original 146 | region name will be the combination of calculation methods and the suffix '_combined_region_truncated'. 147 | :type name: str, optional 148 | """ 149 | #combine regions 150 | if hasattr(self, "truncated"): 151 | raise UserWarning("Your model has already been truncated.") 152 | if CalculateModel.calculation == {}: 153 | raise UserWarning("You must first assign calculation method(s) to the model region(s).") 154 | if len(CalculateModel.calculation) > 1: 155 | CalculateModel.combine_regions_and_methods() 156 | calc_type = CalculateModel.calc_type 157 | s = scheme(region=CalculateModel.calculation[calc_type], name=name) 158 | region = s.return_region() 159 | if calc_type != 'QM': 160 | CalculationFactory._make_calculation(calc_type)().assign_to_region(region=region) 161 | CalculateModel.calculation[calc_type] = region 162 | setattr(self, "truncated", region) 163 | print(f"\nTruncated model has been created and saved to attribute 'truncated' "+ 164 | "and stored in QMzyme.CalculateModel.calculation under key "+ 165 | f"{calc_type}. This model will be used to write the calculation input.") 166 | 167 | def write_input(self, filename=None, memory='24GB', nprocs=12, reset_calculation=False): 168 | """ 169 | Method to write calculation file input. The code will automatically 170 | detect what type of calculation file to prepare based on the 171 | calculation methods that have been assigned to the model region(s). Once this is called 172 | the QMzymeModel object will automatically be serialized using the pickle library and saved 173 | under the filename {self.name+'.pkl'} in the current working directory. 174 | 175 | :param filename: Name to use for resulting file. If not specified, the 176 | file will be named according to the region(s) name. The file format ending 177 | does not need to be included. 178 | :type filename: str, optional 179 | :param memory: Amount of memory to specify in the input file. 180 | :type memory: str, optional 181 | :param nprocs: Number of processors to specify in the input file. 182 | :type nprocs: int, optional 183 | 184 | .. note:: 185 | 186 | A :class:`~QMzyme.CalculateModel.QM_Method` must first be assigned 187 | to a region. 188 | """ 189 | if not hasattr(self, "truncated"): 190 | print("\nWARNING: model has not been truncated. Resulting model may "+ 191 | "not be a chemically complete structure (i.e., incomplete atomic "+ 192 | "valencies due to removed atoms).\n") 193 | CalculateModel.combine_regions_and_methods() 194 | 195 | writer_type = CalculateModel.calc_type 196 | writer = WriterFactory.make_writer(writer_type, filename, memory, nprocs) 197 | if reset_calculation == True: 198 | CalculateModel._reset() 199 | self.store_pickle() 200 | #writer.write() 201 | -------------------------------------------------------------------------------- /QMzyme/MDAnalysisWrapper.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Code written by Heidi Klem. 3 | # e: heidiklem@yahoo.com or heidi.klem@nist.gov 4 | ############################################################################### 5 | 6 | """ 7 | Code to integrate MDAnalysis utilities in QMzyme. 8 | """ 9 | import numpy as np 10 | import pickle 11 | import warnings 12 | import MDAnalysis as mda 13 | from MDAnalysis.lib.pkdtree import * 14 | 15 | 16 | def init_universe(*args, frame=0, **kwargs): 17 | """ 18 | Accepts all argument and key word arguments that :class:`~MDAnalysis.Universe` 19 | can accept to create a Universe instance. Note, you may need to pass the 20 | format key word in some cases. 21 | """ 22 | warnings.filterwarnings( 23 | action='ignore', 24 | category=DeprecationWarning 25 | ) 26 | u = mda.Universe(*args, **kwargs) 27 | if frame != 0: 28 | u.trajectory[frame] 29 | if not hasattr(u.atoms, "elements"): 30 | from MDAnalysis.topology.guessers import guess_types 31 | guessed_elements = guess_types(u.atoms.names) 32 | u.add_TopologyAttr("elements", guessed_elements) 33 | warnings.warn("Element information was missing from input. MDAnalysis.topology.guessers.guess_types was used to infer element types.", UserWarning, stacklevel=2) 34 | if not hasattr(u.atoms, 'chainID') or u.atoms[0].chainID == '': 35 | u.add_TopologyAttr("chainID") 36 | u.atoms.chainIDs = 'X' 37 | return u 38 | 39 | def select_atoms(universe, selection): 40 | """ 41 | :param universe: MDAnalysis Universe object. 42 | :param selection: Selection of atoms to be made- based on `MDAnalysis selection command language `_. 43 | :type selection: str, required 44 | """ 45 | atom_group = universe.select_atoms(selection) 46 | return atom_group 47 | 48 | def universe_selection(universe, selection): 49 | sel = universe.select_atoms(selection) 50 | return mda.Merge(sel.atoms) 51 | 52 | def get_neighbors(ag1, ag2, radius, remove_duplicates=True): 53 | """ 54 | Returns list of atoms in distance based atom group. 55 | """ 56 | # Using the fast C based code 57 | tree = PeriodicKDTree() 58 | atoms = [] 59 | full_system = ag1 60 | sub_system = ag2 61 | 62 | # To ensure bigger atom group selection is used to set_coords 63 | if len(ag2) > len(ag1): 64 | full_system = ag2 65 | sub_system = ag1 66 | tree.set_coords(full_system.positions) 67 | pairs = tree.search_tree(sub_system.positions, radius) 68 | 69 | for pair in pairs: 70 | atom = full_system[pair[1]] 71 | if remove_duplicates is True: 72 | if atom not in atoms: 73 | atoms.append(atom) 74 | else: 75 | atoms.append(atom) 76 | return sum(atoms) 77 | -------------------------------------------------------------------------------- /QMzyme/QMzymeAtom.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Code written by Heidi Klem. 3 | # e: heidiklem@yahoo.com or heidi.klem@nist.gov 4 | ############################################################################### 5 | 6 | import inspect 7 | import numpy as np 8 | 9 | class QMzymeAtom: 10 | """ 11 | Required Parameters 12 | -------------------- 13 | 14 | :param name: Atom name: ex., 'C1' 15 | :type name: str 16 | 17 | :param element: Element one-letter name: ex., 'C' 18 | :type element: str 19 | 20 | :param position: Array of cartesian coordinates. 21 | :type position: List[float] or np.array 22 | 23 | :param resid: Integer residue number. 24 | :type resid: int 25 | 26 | :param resname: Three letter residue name: ex., 'VAL' 27 | :type resname: str 28 | 29 | Pararmeters with defaults 30 | --------------------------- 31 | 32 | :param id: Atom id. If the atom is generated from an MDAnalysis AtomGroup (probably the case) 33 | the id will be the universe atom ID. 34 | :type id: int, default=1 35 | 36 | :param region: Region the atom exists within (if any). 37 | :type region: :class:`~QMzyme.QMzymeRegion.QMzymeRegion`, default=None 38 | 39 | .. note:: User may add any number of additional attributes as keyword arguments (kwargs). 40 | """ 41 | def __init__(self, name, element, position, resid, resname, id=1, region=None, **kwargs): 42 | self.name = name 43 | self.element = element 44 | self.position = position 45 | self.resid = resid 46 | self.resname = resname 47 | self.id = id 48 | self.__region = region 49 | self.is_fixed = False 50 | self.is_point_charge = False 51 | 52 | for attr, val in kwargs. items(): 53 | setattr(self, attr, val) 54 | 55 | def __repr__(self): 56 | return f"" 57 | 58 | def __eq__(self, other): 59 | if other == None: 60 | return 61 | self_attrs = [self.name, self.element, self.resid, self.resname] 62 | other_attrs = [other.name, other.element, other.resid, other.resname] 63 | self_position, other_position = self.position, other.position 64 | eq_attrs = np.array_equal(self_attrs, other_attrs) 65 | #eq_position = np.array_equal(self_position, other_position) 66 | #return (eq_attrs, eq_position) == (True, True) 67 | return (eq_attrs) == (True) 68 | 69 | @property 70 | def region(self): 71 | """ 72 | :returns: Region this atom belongs to. If it does not belong to a region, returns None. 73 | :rtype: :class`~QMzyme.QMzymeRegion.QMzymeRegion` 74 | """ 75 | return self.__region 76 | 77 | @region.setter 78 | def region(self, value): 79 | fname = inspect.currentframe().f_code.co_name 80 | raise AttributeError(f"This attribute is protected. If you truly wish to change its value use self.set_{fname}({value}).") 81 | 82 | def _set_region(self, value): 83 | self.__region = value 84 | 85 | def set_neighbor(self, value: bool=True): 86 | """ 87 | Sets ``is_neighbor=True`` for QMzymeAtom instance, unless 'value=False' is passed. 88 | 89 | .. note:: The :class:`~QMzyme.SelectionSchemes.DistanceCutoff` class calls this method. This might 90 | be useful if you want to further evaluate why what residues were included in a QMzymeRegion that 91 | was selected based on the distance cutoff scheme. 92 | """ 93 | self.is_neighbor = value 94 | 95 | def set_fixed(self, value: bool=True): 96 | """ 97 | Sets ``is_fixed=True`` for QMzymeAtom instance, unless 'value=False' is passed. 98 | 99 | .. note:: The :module:`~QMzyme.CalculateModel` module will read what atoms have ``set_fixed=True`` and 100 | use that information to communicate to :module:`~QMzyme.Writers` what atoms to constrain in the 101 | calculation input file. 102 | """ 103 | self.is_fixed = value 104 | 105 | def set_point_charge(self, value: bool=True): 106 | """ 107 | Sets ``is_point_charge=True`` for QMzymeAtom instance, unless 'value=False' is passed. This will eventually 108 | be used for calculations with charge embedding. 109 | 110 | :raises: UserWarning if the atom does not have the attribute 'charge'. 111 | """ 112 | if not hasattr(self, "charge"): 113 | raise UserWarning(f"Cannot set atom {self} as point_charge because no charge information found.") 114 | self.is_point_charge = value 115 | 116 | def get_chain(self): 117 | """ 118 | Searches possible attribute names chain can live under 119 | (chainID, chain_ID, chain,id) and returns value if found. 120 | """ 121 | chain = None 122 | for name in ['chain', 'chainID', 'chain_ID', 'chain_id', 'chainid']: 123 | if hasattr(self, name): 124 | chain = getattr(self, name) 125 | return chain 126 | 127 | def is_within(self, region): 128 | """ 129 | :param region: Region to search for atom in. 130 | :type region: :class:`~QMzyme.QMzymeRegion.QMzymeRegion`, required 131 | :returns: True if the same atom is found in region. Used to avoid duplication. 132 | :rtype: bool 133 | """ 134 | atom = region.get_atom(id=self.id) 135 | if atom is None: 136 | return False 137 | for k in ['name', 'resid', 'resname', 'element']: 138 | if self.__dict__[k] != atom.__dict__[k]: 139 | return False 140 | return True 141 | -------------------------------------------------------------------------------- /QMzyme/QMzymeModel.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Code written by Heidi Klem. 3 | # e: heidiklem@yahoo.com or heidi.klem@nist.gov 4 | ############################################################################### 5 | 6 | import os 7 | import pickle 8 | from QMzyme.CalculateModel import CalculateModel 9 | import QMzyme.MDAnalysisWrapper as MDAwrapper 10 | from QMzyme.data import protein_residues, residue_charges 11 | 12 | class QMzymeModel: 13 | """ 14 | Base class for :class:`QMzyme.GenerateModel`. Contains methods to create and 15 | modify a ``QMzymeModel`` instance. A QMzymeModel an be instantiated with an 16 | MDAnalysis Universe directly, or any combination of parameters that 17 | MDAnalysis.core.universe.Universe accepts to create a Universe i.e., 18 | (example.prmtop, example.dcd, dt=5). 19 | See https://userguide.mdanalysis.org/stable/universe.html for details. 20 | 21 | :param name: Name to give to the QMzymeModel. This is used for default file naming 22 | purposes throughout the QMzyme package. If not provided, it will default to 23 | the base name of the universe filename attribute. 24 | :type name: str, optional 25 | :param universe: MDAnalysis Universe object. 26 | :type universe: `MDAnalysis.Universe `_, default=None 27 | 28 | 29 | :param name: Name of QMzymeModel 30 | :type name: str, default=None 31 | :param universe: MDAnalysis Universe object 32 | :type universe: `MDAnalysis.Universe `_, default=None 33 | :param frame: If trajectory was provided, specify a frame to base coordinates on 34 | :type frame: int, default=0 35 | :param pickle_file: Provide name/path+file of previously pickled QMzymeModel object to inialize 36 | :type pickle_file: str, default=None 37 | """ 38 | def __init__(self, *args, name, universe, select_atoms='all', frame=0, pickle_file=None, **kwargs): 39 | if pickle_file is not None: 40 | with open(pickle_file, 'rb') as f: 41 | self.__dict__ = pickle.load(f).__dict__ 42 | return 43 | elif universe is None: 44 | universe = MDAwrapper.init_universe(*args, frame=frame, **kwargs) 45 | self.universe = MDAwrapper.universe_selection(universe, select_atoms) 46 | self.select_atoms = select_atoms 47 | if name is None: 48 | name = os.path.basename(universe.filename).split('.')[0] 49 | self.name = name 50 | self.frame = frame 51 | self.filename = universe.filename 52 | self.regions = [] 53 | 54 | if not hasattr(self.universe.atoms, "charges"): 55 | print("\nCharge information not present. QMzyme will try to guess "+ 56 | "region charges based on residue names consistent with AMBER naming "+ 57 | "conventions (i.e., aspartate: ASP --> Charge: -1, aspartic acid: ASH --> Charge: 0.). "+ 58 | "See QMzyme.data.residue_charges for the full set.") 59 | unk_res = [] 60 | for res in self.universe.residues: 61 | if res.resname not in residue_charges: 62 | if unk_res == []: 63 | print("\n\tNonconventional Residues Found") 64 | print("\t------------------------------") 65 | if res.resname not in unk_res: 66 | unk_res.append(res.resname) 67 | print(f"\t{res.resname} --> Charge: UNK, defaulting to 0") 68 | if unk_res != []: 69 | print("\nYou can update charge information for nonconventional residues by running "+ 70 | "\n\t>>>QMzyme.data.residue_charges.update("+"{"+"'3LETTER_RESNAME':INTEGER_CHARGE}). "+ 71 | "\nNote your changes will not be stored after you exit your session. "+ 72 | "It is recommended to only alter the residue_charges dictionary. "+ 73 | "If you alter the protein_residues dictionary instead that could cause "+ 74 | "unintended bugs in other modules (TruncationSchemes).\n") 75 | 76 | def __repr__(self): 77 | return f"" 78 | 79 | @property 80 | def n_regions(self): 81 | return len(self.regions) 82 | 83 | def add_region(self, region): 84 | if region.n_atoms == 0: 85 | raise UserWarning(f"Region contains no atoms and will not be created.") 86 | if hasattr(self, region.name): 87 | raise UserWarning(f"Region with name {region.name} already exists in QMzymeModel {self.name}."+ 88 | "Please use a different region name or remove the existing region via remove_region({region.name}).") 89 | setattr(self, region.name, region) 90 | self.regions.append(region) 91 | 92 | def get_region_names(self): 93 | return [r.name for r in self.regions] 94 | 95 | def get_region(self, region_name=None): 96 | try: 97 | return getattr(self,region_name) 98 | except: 99 | raise UserWarning(f"Region with name {region_name} does not exist. "+ 100 | f"Existing regions are: {self.get_region_names()}") 101 | 102 | def has_region(self, region_name): 103 | # return region_name in self.get_region_names() 104 | return hasattr(self, region_name) 105 | 106 | def remove_region(self, region_name): 107 | """ 108 | Removes a QMzymeRegion from the QMzymeModel. 109 | 110 | :param region_name: Name of the region to be removed. 111 | :type region_name: str, required 112 | """ 113 | #del self.regions[region_idx] 114 | delattr(self, region_name) 115 | n_regions = len(self.regions) 116 | for i in range(n_regions): 117 | if self.regions[i].name == region_name: 118 | del self.regions[i] 119 | break 120 | 121 | def pymol_visualize(self, filename:str=None): 122 | """ 123 | Creates a QMzymeModel_visualize.py script that you can load into PyMol. 124 | 125 | :param filename: Name of PyMol .py file. If not specified, the name 126 | attribute of the QMzymeModel will be used. 127 | :type filename: str, optional 128 | 129 | """ 130 | lines = '' 131 | lines += f"cmd.bg_color('white')\n" 132 | starting_structure = self.name 133 | self.universe.atoms.write(f"{self.name}_universe.pdb") 134 | file = os.path.abspath(f'{self.name}_universe.pdb') 135 | lines += f"cmd.load('{file}', '{self.name}')\n" 136 | #lines += f"cmd.color('gray70', self.name)\n" 137 | lines += f"cmd.set('surface_color', 'gray')\n" 138 | lines += f"cmd.set('transparency', 0.75)\n" 139 | lines += f"cmd.zoom('visible')\n" 140 | lines += f"cmd.orient('visible')\n" 141 | lines += f"cmd.scene('Starting Structure', 'store')\n" 142 | lines += f"cmd.hide('everything', '{self.name}')\n" 143 | 144 | for region in self.regions: 145 | region.write(f'{region.name}.pdb') 146 | file = os.path.abspath(f'{region.name}.pdb') 147 | lines += f"cmd.load('{file}', '{region.name}')\n" 148 | lines += f"cmd.hide('cartoon', '{region.name}')\n" 149 | lines += f"cmd.show_as('sticks', '{region.name}')\n" 150 | lines += f"cmd.zoom('visible')\n" 151 | lines += f"cmd.orient('visible')\n" 152 | lines += f"cmd.scene('{region.name}', 'store')\n" 153 | lines += f"cmd.hide('everything', '{region.name}')\n" 154 | 155 | if CalculateModel.calc_type != None: 156 | region = CalculateModel.calculation[CalculateModel.calc_type] 157 | region.write(f'{region.name}.pdb') 158 | file = os.path.abspath(f'{region.name}.pdb') 159 | lines += f"cmd.load('{file}', '{region.name}')\n" 160 | lines += f"cmd.hide('cartoon', '{region.name}')\n" 161 | lines += f"cmd.color('gray85', '{region.name} and elem c')\n" 162 | lines += f"cmd.color('oxygen','{region.name} and elem o')\n" 163 | lines += f"cmd.color('slate', '{region.name} and elem n')\n" 164 | lines += f"cmd.color('gray98', '{region.name} and elem h')\n" 165 | lines += f"cmd.color('sulfur', '{region.name} and elem s')\n" 166 | lines += f"cmd.show_as('sticks', '{region.name} and segid QM')\n" 167 | lines += f"cmd.show_as('lines', '{region.name} and (not segid QM)')\n" 168 | fixed = [str(i+1) for i, atom in enumerate(region.atoms) if atom.is_fixed] 169 | fixed_sel = f"id {'+'.join(fixed)}" 170 | if len(fixed) > 0: 171 | lines += f"cmd.create('fixed_atoms', '{region.name} and {fixed_sel}')\n" 172 | lines += f"cmd.hide('cartoon', 'fixed_atoms')\n" 173 | lines += f"cmd.set('sphere_scale', 0.15, 'fixed_atoms')\n" 174 | lines += f"cmd.set('sphere_color', 'black', 'fixed_atoms')\n" 175 | #lines += f"cmd.set('sphere_transparency', 0.7, 'fixed_atoms')\n" 176 | lines += f"cmd.show_as('spheres', 'fixed_atoms')\n" 177 | #lines += f"cmd.select('residue_labels', '{region.name}')\n" 178 | lines += f"cmd.create('residue_labels', '{region.name}')\n" 179 | lines += f"cmd.hide('everything', 'residue_labels')\n" 180 | lines += f"cmd.set('label_size', 14)\n" 181 | lines += f"cmd.label('n. ha and residue_labels', 'resn+resi')\n" 182 | lines += f"cmd.zoom('visible')\n" 183 | lines += f"cmd.create('model_surface', '{region.name}')\n" 184 | lines += f"cmd.show_as('surface', 'model_surface')\n" 185 | lines += f"cmd.orient('visible')\n" 186 | lines += f"cmd.scene('{region.name}', 'store')\n" 187 | lines += f"cmd.set('cartoon_transparency', 0.6)\n" 188 | #lines += f"cmd.show('surface', '{region.name}')\n" 189 | lines += f"cmd.show('cartoon', '{self.name}')\n" 190 | lines += f"cmd.zoom('visible')\n" 191 | lines += f"cmd.orient('visible')\n" 192 | 193 | if filename == None: 194 | filename = f'QMzymeModel_{self.name}_visualize.py' 195 | elif not filename.endswith('.py'): 196 | filename = filename+'.py' 197 | with open (filename, 'w+') as f: 198 | f.write(lines) 199 | 200 | def store_pickle(self, filename=None): 201 | """ 202 | The pickle file will by default be named after the QMzymeModel.name 203 | attribute, which by default is the base name of the file originally 204 | used to initialize the model. You can also specify a filename by 205 | passing the argument 'filename'. 206 | 207 | :param filename: Name of the pickle file generated. If no extension 208 | is provided the '.pkl' extension will be added to the str 209 | :type filename: str 210 | """ 211 | if filename == None: 212 | filename = self.name+'.pkl' 213 | if len(filename.split('.')) < 2: 214 | filename += '.pkl' 215 | with open(filename, 'wb') as f: 216 | pickle.dump(self, f) 217 | -------------------------------------------------------------------------------- /QMzyme/RegionBuilder.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Code written by Heidi Klem. 3 | # e: heidiklem@yahoo.com or heidi.klem@nist.gov 4 | ############################################################################### 5 | 6 | """ 7 | Class to build a QMzymeRegion object. 8 | """ 9 | 10 | import warnings 11 | import copy 12 | from QMzyme.QMzymeRegion import QMzymeRegion 13 | from QMzyme.QMzymeAtom import QMzymeAtom 14 | from QMzyme.converters import mda_atom_to_qmz_atom 15 | from MDAnalysis.core.groups import Atom 16 | from MDAnalysis.core.groups import AtomGroup 17 | 18 | from typing import TYPE_CHECKING, Any, Dict, Generic, List, Optional, TypeVar, Union 19 | 20 | _QMzymeAtom = TypeVar("_QMzymeAtom", bound="QMzymeAtom") 21 | _QMzymeRegion = TypeVar("_QMzymeRegion", bound="QMzymeRegion") 22 | _MDAtom = TypeVar("_MDAtom", bound="Atom") 23 | _AtomGroup = TypeVar("_AtomGroup", bound="AtomGroup") 24 | _Atom = TypeVar("_Atom", bound=Union["QMzymeAtom", "Atom"]) 25 | 26 | #remove_mda_atom_props = ['chainID', 'level', 'universe', 'bfactor', 'altLoc', 'ix_array', 'segment', 'segindex'] 27 | remove_mda_atom_props = ['level', 'universe', 'bfactor', 'altLoc', 'ix_array', 'segment', 'segindex'] 28 | 29 | class RegionBuilder: 30 | 31 | def __init__(self, name, atom_group = None, universe = None): 32 | self.name = name 33 | self.atoms = [] 34 | self.region = None 35 | self.atom_group = atom_group 36 | self.universe = universe 37 | if atom_group is not None: 38 | self.init_atom_group(atom_group) 39 | 40 | def __repr__(self): 41 | return f"" 43 | 44 | @property 45 | def n_residues(self): 46 | return len(list(set(atom.resid for atom in self.atoms))) 47 | 48 | @property 49 | def n_atoms(self): 50 | return len(self.atoms) 51 | 52 | def init_atom_group(self, atom_group): 53 | """ 54 | It is assumed that the atoms in the selection are already unique. 55 | """ 56 | for atom in atom_group.atoms: 57 | self.init_atom(atom, uniquify=True) 58 | self.atom_group = atom_group 59 | self.universe = atom_group.universe 60 | region = self.get_region() 61 | #return region 62 | 63 | def init_atom(self, atom, uniquify=True): 64 | warnings.filterwarnings('ignore') 65 | if isinstance(atom, Atom): 66 | atom = mda_atom_to_qmz_atom(atom) 67 | else: 68 | atom_props = self.get_atom_properties(atom) 69 | if uniquify is True: 70 | self.uniquify_atom(atom_props) 71 | atom = QMzymeAtom(**atom_props) 72 | # if self.atoms != []: 73 | # print(atom, self.atoms[-1]) 74 | self.atoms.append(atom) 75 | 76 | def uniquify_atom(self, atom_props): 77 | temp_region = QMzymeRegion(self.name, self.atoms) 78 | resid = atom_props['resid'] 79 | element = atom_props['element'] 80 | if resid in temp_region.resids: 81 | residue_atoms = temp_region.get_residue(resid).atoms 82 | names = [a.name for a in residue_atoms] 83 | i = 0 84 | while atom_props['name'] in names: 85 | i += 1 86 | atom_props['name'] = f"{element}{i}" 87 | while atom_props['id'] in temp_region.ids: 88 | atom_props['id'] += 1 89 | 90 | def get_atom_properties(self, atom: _Atom): 91 | atom_attr_dict = {} 92 | for attr in dir(atom): 93 | if attr.startswith('_') or attr.startswith('get'): 94 | continue 95 | try: 96 | atom_attr_dict[attr] = getattr(atom, attr) 97 | except: 98 | pass 99 | if isinstance(atom, Atom): 100 | try: 101 | atom_attr_dict['chain'] = atom_attr_dict['chainID'] 102 | except: 103 | atom_attr_dict['chain'] = 'X' 104 | d = copy.copy(atom_attr_dict) 105 | for attr in d: 106 | if attr in remove_mda_atom_props: 107 | del atom_attr_dict[attr] 108 | try: 109 | del atom_attr_dict['region'] 110 | except: 111 | pass 112 | return atom_attr_dict 113 | 114 | def get_region(self): 115 | self.region = QMzymeRegion(self.name, self.atoms, self.atom_group, self.universe) 116 | return self.region 117 | -------------------------------------------------------------------------------- /QMzyme/SelectionSchemes.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Code written by Heidi Klem. 3 | # e: heidiklem@yahoo.com or heidi.klem@nist.gov 4 | ############################################################################### 5 | 6 | """ 7 | Module containing functions to define a QMzymeRegion based on some logic/workflow. 8 | """ 9 | 10 | import QMzyme.MDAnalysisWrapper as MDAwrapper 11 | from QMzyme.RegionBuilder import RegionBuilder 12 | from QMzyme.QMzymeRegion import QMzymeRegion 13 | from QMzyme.QMzymeModel import QMzymeModel 14 | from typing import Type 15 | 16 | 17 | import abc 18 | 19 | class SelectionScheme(abc.ABC): 20 | """ 21 | SelectionScheme is the abstract base class used to prescribe concrete selection scheme 22 | sub classes. Fork QMzyme to build your own concrete selection scheme class and submit 23 | a Pull Request (PR) on github to have your scheme added to QMzyme! You will need to create 24 | comprehensive tests before the PR is accepted. See the 25 | `documentation on contributing `_ 26 | for more information. 27 | 28 | Below is a template that you can use to implement your own selection scheme class: 29 | 30 | class InformativeName(SelectionScheme): 31 | This is an example docstring. 32 | 33 | Include a detailed description of how the scheme works in the class level doc string. Also 34 | include any parameters the `__init__()` method of your class accepts. 35 | 36 | :Parameters: 37 | :model: (:class:`~QMzyme.QMzymeModel.QMzymeModel`) QMzymeModel to provide starting structure that selection will be performed on. When 38 | using the main :class:`~QMzyme.GenerateModel.GenerateModel` class, the QMzyme model is automatically 39 | passed as an argument to the selection scheme. It is recommended you use the Universe (universe attribute) 40 | representing the starting structure to perform the selection on. 41 | :param name: (str, required) Name of the region generated. 42 | 43 | The return should always be a QMzyme region. 44 | 45 | :Returns: 46 | :class:`~QMzyme.QMzymeRegion.QMzymeRegion` 47 | 48 | .. note:: 49 | 50 | Include any notes you want users to be aware of. 51 | 52 | """ 53 | def __init__(self, model, name): 54 | """ 55 | Assign any key word arguments as attributes to self. Then in your 56 | `select_atoms()` method you can pull any necessary args from 57 | self attributes, instead of relying on passing them. 58 | 59 | Every concrete scheme `__init__()` method should include this line 60 | at the very end: 61 | 62 | .. code:: python 63 | 64 | super().__init__(model, name) 65 | 66 | This will automatically run your `select_atoms()` method and return the resulting region. 67 | """ 68 | self.name = name 69 | self.model: QMzymeModel = model 70 | self.region: QMzymeRegion 71 | self.select_atoms() 72 | self.reference() 73 | if self.reference is not None: 74 | print(f"Use of this selection scheme requires citing the following reference(s): \n \t{self.reference}") 75 | self.return_region() 76 | 77 | @abc.abstractmethod 78 | def select_atoms(self): 79 | """ 80 | Write your code to perform the selection. 81 | 82 | At the end of your code you should set `self.region = {region}`. 83 | 84 | The product of your selection scheme needs to be a QMzymeRegion 85 | in order for it to work with `GenerateModel().set_region()`. 86 | 87 | This method is automatically called in the ``super().__init__(model, name)`` 88 | line of your `__init__()` method. 89 | """ 90 | ... 91 | 92 | def method_name(self): 93 | """ 94 | You can add whatever other methods you want in your class, but 95 | you should call those methods as necessary in `__init__()` otherwise 96 | your scheme will be automated in `GenerateModel.set_region()` 97 | """ 98 | pass 99 | 100 | def return_region(self): 101 | """ 102 | This method belongs to the base class and is automatically called in 103 | the ``super().__init__(model, name)`` line of your `__init__()` method. All 104 | you have to do is make sure you have created a class attribute called `region`. 105 | """ 106 | self.region.rename(self.name) 107 | return self.region 108 | 109 | @abc.abstractmethod 110 | def reference(self): 111 | """ 112 | This method needs to be included in your class. All it should do is create an 113 | attribute called `reference` that provides a citable reference of the scheme, to 114 | give credit where credit is due. The reference will be automatically printed when 115 | the class is instantiated. This is taken care of in the the ``super().__init__(model, name)`` 116 | line of your `__init__()` method. 117 | 118 | Example: 119 | 120 | .. code:: python 121 | 122 | self.reference = "1. Alegre‐Requena, J. V., Sowndarya S. V., S., Pérez‐Soto, R., Alturaifi, T. M. & Paton, R. S. AQME: Automated quantum mechanical environments for researchers and educators. WIREs Comput Mol Sci 13, e1663 (2023)." 123 | 124 | In some cases, there might not be a direct reference (see DistanceCutoff class), but 125 | there might be relevant work a user might be interested in. Please only refer to the 126 | work of interest in the class doc string, not in the reference method. 127 | 128 | If there are no references, please only include the line: 129 | 130 | .. code:: python 131 | 132 | self.reference = None 133 | 134 | """ 135 | ... 136 | 137 | 138 | class DistanceCutoff(SelectionScheme): 139 | """ 140 | The DistanceCutoff class performs a selection simply based on the distance of 141 | atoms from a pre-defined catalytic_center region. Users must first call 142 | ``GenerateModel().set_catalytic_center(kwargs)`` in order to then run DistanceCutoff via 143 | ``GenerateModel().set_region(selection=DistanceCutoff, name={str}, cutoff={int})``. 144 | 145 | This scheme is known to require rather large QM regions to achieve agreement 146 | with experiment (Ex., Kulik HJ, Zhang J, Klinman JP, Martínez TJ. How Large 147 | Should the QM Region Be in QM/MM Calculations? The Case of Catechol 148 | O-Methyltransferase. J Phys Chem B. 2016 Nov 10;120(44):11381-11394. 149 | doi: 10.1021/acs.jpcb.6b07814.). 150 | 151 | 152 | :param model: QMzymeModel to provide starting structure that selection 153 | will be performed on. 154 | :type model: :class:`~QMzyme.QMzymeModel.QMzymeModel`, required. 155 | 156 | :param name: Name of the region generated. 157 | :type name: str, required. 158 | 159 | :param cutoff: Numerical value to define cutoff. 160 | :type cutoff: float, required. 161 | 162 | :param include_whole_residues: Informs code whether or not to only include 163 | atoms within the cutoff, or include whole residues if they have at least one 164 | atom within the cutoff. 165 | :type include_whole_residues: bool, default=True. 166 | 167 | 168 | :returns: :class:`~QMzyme.QMzymeRegion.QMzymeRegion` 169 | 170 | .. note:: 171 | 172 | Users are encouraged to evaluate the resulting region. There may be situations where 173 | a charged residue is within the cutoff distance, however, its charge partner is not. 174 | Such situations can drastically alter the chemistry of the model! Maybe someone could 175 | write up a less generic distance based selection scheme that would take such situations 176 | into consideration. Or modify the current class to include an argument 177 | `include_charge_partners=True`. 178 | 179 | """ 180 | def __init__(self, model, name, cutoff, include_whole_residues=True): 181 | """ 182 | """ 183 | if name is None: 184 | name = f'cutoff_{cutoff}' 185 | self.cutoff = cutoff 186 | self.include_whole_residues = include_whole_residues 187 | super().__init__(model, name) 188 | 189 | def select_atoms(self): 190 | """ 191 | """ 192 | if not self.model.has_region('catalytic_center'): 193 | raise UserWarning("You must first define a catalytic_center. See method `set_catalytic_center()`.") 194 | 195 | neighbors = MDAwrapper.get_neighbors( 196 | self.model.universe.select_atoms('all'), 197 | self.model.get_region('catalytic_center')._atom_group, self.cutoff) 198 | 199 | neighbors_byres = neighbors.residues.sorted_unique.atoms 200 | 201 | # Convert MDAnalysis AtomGroup to QMzymeRegion 202 | qmz_neighbors = RegionBuilder(name=f"cutoff_{self.cutoff}", atom_group=neighbors).get_region() 203 | qmz_neighbors_byres = RegionBuilder(name=f"cutoff_{self.cutoff}", atom_group=neighbors_byres).get_region() 204 | 205 | for atom in qmz_neighbors_byres.atoms: 206 | if atom.id in qmz_neighbors.ids: 207 | atom.set_neighbor(True) 208 | else: 209 | atom.set_neighbor(False) 210 | 211 | region = qmz_neighbors_byres 212 | if self.include_whole_residues is False: 213 | region = qmz_neighbors 214 | 215 | setattr(region, 'catalytic_center', self.model.get_region('catalytic_center')) 216 | self.region = region 217 | 218 | def reference(self): 219 | """ 220 | """ 221 | self.reference = None 222 | -------------------------------------------------------------------------------- /QMzyme/TruncationSchemes.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Code written by Heidi Klem. 3 | # e: heidiklem@yahoo.com or heidi.klem@nist.gov 4 | ############################################################################### 5 | 6 | """ 7 | Module containing functions to truncate a QMzymeRegion based on some logic/scheme. 8 | """ 9 | 10 | from QMzyme.data import protein_residues, backbone_atoms 11 | from QMzyme.QMzymeRegion import QMzymeRegion 12 | from QMzyme.truncation_utils import * 13 | import abc 14 | 15 | 16 | class TruncationScheme(abc.ABC): 17 | def __init__(self, region, name): 18 | self.region = region 19 | self.truncated_region = None 20 | if name == None: 21 | name = f'{self.region.name}_truncated' 22 | self.name = name 23 | self.truncate() 24 | if hasattr(self.region.atoms[0], "charge"): 25 | balance_charge(self.region, self.truncated_region) 26 | elif hasattr(self.region, "charge"): 27 | self.truncated_region.set_charge(self.region.charge) 28 | if getattr(self.region, "method") != None: 29 | self.truncated_region.set_method(self.region.method) 30 | self.return_region() 31 | 32 | @abc.abstractmethod 33 | def truncate(self): 34 | ... 35 | 36 | def return_region(self): 37 | self.truncated_region.rename(self.name) 38 | return self.truncated_region 39 | 40 | class TerminalAlphaCarbon(TruncationScheme): 41 | """ 42 | The TerminalAlphaCarbon scheme will 1) remove N-terminal backbone atoms 43 | (N and H) if the preceding sequence residue (resid-1) is not included 44 | in the region and add a hydrogen atom along the CA–N backbone 45 | bond vector; and 2) remove C-terminal backbone atoms (C and O) if the 46 | following sequence residue (resid+1) is not included in the region and 47 | add a hydrogen atom along the CA–C backbone bond vector. In 48 | the case of Proline, if the preceding sequence residue is not present 49 | the Proline N atom is kept and a hydrogen is added along the N–(resid-1)C 50 | backbone bond vector. 51 | 52 | .. image:: ../../docs/Images/terminal_alpha_carbon.png 53 | :width: 250 54 | 55 | Image modified from Klem, H., McCullagh, M. & Paton, R. S. Top Catal. 56 | 65, 165–186 (2022). 57 | """ 58 | def __init__(self, region, name): 59 | super().__init__(region, name) 60 | 61 | def truncate(self): 62 | remove_atoms = [] 63 | r = QMzymeRegion(name = self.name, atoms = self.region.atoms, universe = self.region._universe) 64 | for res in self.region.residues: 65 | resname = res.resname 66 | if resname not in protein_residues: 67 | continue 68 | # Define necessary backbone atoms 69 | Natom = res.get_atom(backbone_atoms['N']) 70 | CAatom = res.get_atom(backbone_atoms['CA']) 71 | Catom = res.get_atom(backbone_atoms['C']) 72 | Oatom = res.get_atom(backbone_atoms['O']) 73 | preceding_Catom = get_preceding_Catom(self.region, res.resid) 74 | following_Natom = get_following_Natom(self.region, res.resid) 75 | if resname != 'PRO': 76 | Hatom = res.get_atom(backbone_atoms['H']) 77 | if preceding_Catom is not None and preceding_Catom.id not in self.region.ids: 78 | if resname != 'PRO': 79 | cap_atom = cap_H(Natom, CAatom) 80 | r.remove_atom(r.get_atom(id=Natom.id)) 81 | r.remove_atom(r.get_atom(id=Hatom.id)) 82 | r.add_atom(cap_atom) 83 | if resname == 'PRO': 84 | cap_atom = cap_H(preceding_Catom, Natom) 85 | setattr(cap_atom, "id", cap_atom.id-1) 86 | r.add_atom(cap_atom) 87 | if following_Natom is not None and following_Natom.id not in self.region.ids: 88 | cap_atom = cap_H(Catom, CAatom) 89 | r.remove_atom(r.get_atom(id=Catom.id)) 90 | r.remove_atom(r.get_atom(id=Oatom.id)) 91 | r.add_atom(cap_atom) 92 | # if hasattr(self.region, "charge"): 93 | # r.set_charge(self.region.charge) 94 | # if getattr(self.region, "method") != None: 95 | # r.set_method(self.region.method) 96 | self.truncated_region = r 97 | 98 | class AlphaCarbon(TruncationScheme): 99 | """ 100 | Function to truncate a QMzymeRegion accoring to the AlphaCarbon scheme. 101 | This method is still under development. 102 | 103 | .. image:: ../../docs/Images/all_alpha_carbon.png 104 | :width: 250 105 | 106 | Image modified from Klem, H., McCullagh, M. & Paton, R. S. Top Catal. 107 | 65, 165–186 (2022). 108 | """ 109 | def __init__(self, region, name): 110 | super().__init__(region, name) 111 | 112 | def truncate(self): 113 | remove_atoms = [] 114 | r = QMzymeRegion(name=self.name, atoms=self.region.atoms, universe=self.region._universe) 115 | for res in self.region.residues: 116 | resname = res.resname 117 | if resname not in protein_residues: 118 | continue 119 | # Define necessary backbone atoms 120 | Natom = res.get_atom(backbone_atoms['N']) 121 | CAatom = res.get_atom(backbone_atoms['CA']) 122 | Catom = res.get_atom(backbone_atoms['C']) 123 | Oatom = res.get_atom(backbone_atoms['O']) 124 | preceding_Catom = get_preceding_Catom(self.region, res.resid) 125 | following_Natom = get_following_Natom(self.region, res.resid) 126 | if preceding_Catom is not None: # fixes issues if this is the very first res in sequence 127 | if resname != 'PRO': 128 | Hatom = res.get_atom(backbone_atoms['H']) 129 | cap_atom = cap_H(Natom, CAatom) 130 | r.remove_atom(r.get_atom(id=Natom.id)) 131 | r.remove_atom(r.get_atom(id=Hatom.id)) 132 | r.add_atom(cap_atom) 133 | if resname == 'PRO': 134 | cap_atom = cap_H(preceding_Catom, Natom) 135 | setattr(cap_atom, "id", cap_atom.id-1) 136 | r.add_atom(cap_atom) 137 | if following_Natom is not None: # fixes issues if this is the very last res in sequence 138 | cap_atom = cap_H(Catom, CAatom) 139 | r.remove_atom(r.get_atom(id=Catom.id)) 140 | r.remove_atom(r.get_atom(id=Oatom.id)) 141 | r.add_atom(cap_atom) 142 | self.truncated_region = r 143 | -------------------------------------------------------------------------------- /QMzyme/__init__.py: -------------------------------------------------------------------------------- 1 | """QM-based enzyme model generation and validation.""" 2 | 3 | # Add imports here 4 | #from QMzyme import * 5 | from .GenerateModel import GenerateModel 6 | from .CalculateModel import CalculateModel, QM_Method, XTB_Method, ChargeField_Method 7 | from ._version import __version__ 8 | -------------------------------------------------------------------------------- /QMzyme/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.7.dev0" 2 | -------------------------------------------------------------------------------- /QMzyme/aqme/__init__.py: -------------------------------------------------------------------------------- 1 | from .qprep import qprep 2 | from .utils import * 3 | from .argument_parser import * 4 | from .crest import * 5 | 6 | -------------------------------------------------------------------------------- /QMzyme/aqme/argument_parser.py: -------------------------------------------------------------------------------- 1 | #####################################################. 2 | # This file contains the argument parser # 3 | #####################################################. 4 | 5 | import os 6 | 7 | var_dict = { 8 | "varfile": None, 9 | "verbose": True, 10 | "input": "", 11 | "output_name": "output", 12 | "command_line": False, 13 | "name": None, 14 | "path": "", 15 | "output": ".sdf", 16 | "csearch": False, 17 | "cmin": False, 18 | "qprep": False, 19 | "qcorr": False, 20 | "smi": None, 21 | "metal_atoms": [], 22 | "auto_metal_atoms": True, 23 | "charge": None, 24 | "mult": None, 25 | "complex_coord": [], 26 | "complex_type": "", 27 | "metal_idx": [], 28 | "metal_sym": [], 29 | "constraints_atoms": [], 30 | "constraints_dist": [], 31 | "constraints_angle": [], 32 | "constraints_dihedral": [], 33 | "ewin_cmin": 5.0, 34 | "ewin_csearch": 5.0, 35 | "opt_fmax": 0.05, 36 | "opt_steps": 1000, 37 | "opt_steps_rdkit": 1000, 38 | "heavyonly": True, 39 | "degree": 120.0, 40 | "max_torsions": 0, 41 | "sample": "auto", 42 | "auto_sample": 20, 43 | "ff": "MMFF", 44 | "seed": 62609, 45 | "rms_threshold": 0.25, 46 | "max_matches_rmsd": 1000, 47 | "energy_threshold": 0.25, 48 | "initial_energy_threshold": 0.0001, 49 | "max_mol_wt": 0, 50 | "ani_method": "ANI2x", 51 | "stacksize": "1G", 52 | "xtb_keywords": None, 53 | "max_workers": 4, 54 | "ewin_sample_fullmonte": 2.0, 55 | "ewin_fullmonte": 5.0, 56 | "nsteps_fullmonte": 100, 57 | "nrot_fullmonte": 3, 58 | "ang_fullmonte": 30, 59 | "cregen": False, 60 | "cregen_keywords": None, 61 | "program": None, 62 | "nprocs": 8, 63 | "mem": "16GB", 64 | "mol": None, 65 | "destination": None, 66 | "qm_input": "", 67 | "ts_input": "opt=(calcfc,noeigen,ts,maxstep=5)", 68 | "qm_end": "", 69 | "chk_path": "", 70 | "gen_atoms": [], 71 | "bs_nogen": "", 72 | "bs_gen": "", 73 | "freeze_atoms": [], 74 | "lowest_only": False, 75 | "lowest_n": None, 76 | "e_threshold_qprep": None, 77 | "chk": False, 78 | "w_dir_main": os.getcwd(), 79 | "files": [], 80 | "atom_types": [], 81 | "cartesians": [], 82 | "dup": True, 83 | "dup_threshold": 0.0001, 84 | "ro_threshold": 0.1, 85 | "amplitude_ifreq": 0.2, 86 | "ifreq_cutoff": 0.0, 87 | "freq_conv": None, 88 | "im_freq_input": 'opt=(calcfc,maxstep=5)', 89 | "s2_threshold": 10.0, 90 | "isom_type": None, 91 | "isom_inputs": os.getcwd(), 92 | "vdwfrac": 0.5, 93 | "covfrac": 1.1, 94 | "fullcheck": True, 95 | "suffix": "", 96 | "geom": [], 97 | "bond_thres": 0.2, 98 | "angle_thres": 30, 99 | "dihedral_thres": 30, 100 | "crest_keywords": None, 101 | "crest_force": 0.5, 102 | "prefix": "", 103 | "qdescp": False, 104 | "qdescp_temp": 300, 105 | "qdescp_acc": 0.2, 106 | "qdescp_solvent": None, 107 | "boltz": True, 108 | "nmr_atoms": [6, 1], # [C,H] 109 | "nmr_slope": [-1.0537, -1.0784], # [C,H] 110 | "nmr_intercept": [181.7815,31.8723], # [C,H] 111 | "nmr_experim": None, 112 | "nodup_check": False, 113 | "qdescp_atoms": [], 114 | "xtb_opt": True, 115 | "dbstep_r": 3.5, 116 | "robert": True, 117 | "csv_name": None, 118 | "crest_nrun": 1, 119 | "crest_nclust": 0.4 120 | } 121 | 122 | 123 | # part for using the options in a script or jupyter notebook 124 | class options_add: 125 | pass 126 | 127 | 128 | def set_options(kwargs): 129 | # set default options and options provided 130 | options = options_add() 131 | # dictionary containing default values for options 132 | 133 | for key in var_dict: 134 | vars(options)[key] = var_dict[key] 135 | for key in kwargs: 136 | if key in var_dict: 137 | vars(options)[key] = kwargs[key] 138 | elif key.lower() in var_dict: 139 | vars(options)[key.lower()] = kwargs[key.lower()] 140 | else: 141 | print("Warning! Option: [", key,":",kwargs[key],"] provided but no option exists, try the online documentation to see available options for each module.",) 142 | 143 | return options 144 | -------------------------------------------------------------------------------- /QMzyme/configuration/__init__.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Code written by Heidi Klem. 3 | # e: heidiklem@yahoo.com or heidi.klem@nist.gov 4 | ############################################################################### 5 | 6 | """ 7 | Module containing the following information used by QMzyme that users can 8 | modify to their specific needs. 9 | - Protein residue three letter names and associated formal charges 10 | - Any residue three letter names and associated formal charges 11 | - Single letter atom names of amino-acid backbone atoms 12 | """ 13 | 14 | import copy 15 | 16 | 17 | protein_residues = { 18 | 'ALA': 0, 19 | 'ARG': 1, 20 | 'ASH': 0, 21 | 'ASN': 0, 22 | 'ASP': -1, 23 | 'CYM': -1, 24 | 'CYS': 0, 25 | 'CYX': 0, 26 | 'GLH': 0, 27 | 'GLN': 0, 28 | 'GLU': -1, 29 | 'GLY': 0, 30 | 'HIS': 0, 31 | 'HID': 0, 32 | 'HIE': 0, 33 | 'HIP': 1, 34 | 'HYP': 0, 35 | 'ILE': 0, 36 | 'LEU': 0, 37 | 'LYN': 0, 38 | 'LYS': 1, 39 | 'MET': 0, 40 | 'PHE': 0, 41 | 'PRO': 0, 42 | 'SER': 0, 43 | 'THR': 0, 44 | 'TRP': 0, 45 | 'TYR': 0, 46 | 'VAL': 0 47 | } 48 | """ 49 | The entries in protein_residues are used in TruncationSchemes.py to ignore non 50 | amino acid residues, since those will not have backbone atoms and would raise 51 | errors during truncation. If you want to add a residue and charge but the residue 52 | is not an amino-acid, or should not be considered in truncation procedures, please 53 | add that residue to the residue_charges dictionary, rather than the protein_residues 54 | dictionary. All residues in the protein_residues dictionary are automatically copied 55 | into the residue_charges dictionary, but not vice-versa. 56 | """ 57 | 58 | 59 | residue_charges = copy.copy(protein_residues) 60 | """ 61 | If there is a non-native amino acid you will encounter often you can add its 62 | name and charge to the protein_residues dictionary so you do not have 63 | to manually add that information each time you run QMzyme. 64 | """ 65 | residue_charges['WAT'] = 0 66 | residue_charges['HOH'] = 0 67 | residue_charges['Na+'] = 1 68 | residue_charges['Cl-'] = -1 69 | 70 | 71 | backbone_atoms = { 72 | 'N':'N', 73 | 'H':'H', 74 | 'CA':'CA', 75 | 'HA':'HA', 76 | 'C':'C', 77 | 'O':'O' 78 | } 79 | """ 80 | The entries in backbone_atoms are used in TruncationSchemes.py to decide what atoms to 81 | remove and replace with hydrogen. The keys should not be changed in this dictionary, only 82 | the values assigned to each key. The default backbone_atoms dictionary is designed to match 83 | the conventions used by the `chemical component dictionary `_. 84 | 85 | 86 | If your amino acid backbone atom names do not match the chemical component dictionary 87 | convention ('N', 'H', 'CA', 'HA', 'C', 'O') you can edit that here globally, or you can 88 | redefine them at the start of your QMzyme run by re-defining the QMzyme.configuration.backbone_atoms 89 | dictionary accordingly. This might be necessary if you are using a structure generated from a 90 | force-field topology that, for example, names the H atom bound to the backbone N atom 'HN' instead 91 | of 'H'. In this case, you could simply set QMzyme.configuration.backbone_atoms['H'] = 'HN'. 92 | """ 93 | -------------------------------------------------------------------------------- /QMzyme/converters.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | import numpy as np 3 | from QMzyme.QMzymeAtom import QMzymeAtom 4 | import MDAnalysis 5 | from MDAnalysis.core.topology import Topology 6 | from MDAnalysis.topology.base import change_squash 7 | from MDAnalysis.core.topologyattrs import ( 8 | Atomids, 9 | Atomnames, 10 | Atomtypes, 11 | Elements, 12 | Masses, 13 | Charges, 14 | Bonds, 15 | Resids, 16 | Resnums, 17 | Resnames, 18 | Segids, 19 | ChainIDs, 20 | ICodes, 21 | Occupancies, 22 | Tempfactors, 23 | ) 24 | def region_to_atom_group(region): 25 | 26 | n_atoms = region.n_atoms 27 | u = MDAnalysis.Universe.empty( 28 | n_atoms=n_atoms, 29 | n_residues=n_atoms, # Although this will make u.n_residues return a misleading number, 30 | #this won't matter after the group has been saved to a PDB and reloaded into a universe. 31 | atom_resindex=np.arange(n_atoms), # Doing it this way makes the attribute setting simpler 32 | n_segments=n_atoms, 33 | trajectory=True) # Needs to be True so positions can be set 34 | 35 | # Store atom attributes 36 | atom_attributes = {} 37 | for atom in region.atoms: 38 | for attr in atom.__dict__: 39 | if attr.startswith('_'): 40 | continue 41 | elif attr not in atom_attributes.keys(): 42 | try: 43 | atom_attributes[attr] = [getattr(atom, attr)] 44 | except: 45 | pass 46 | else: 47 | atom_attributes[attr].append(getattr(atom, attr)) 48 | if 'chain' in atom_attributes: 49 | atom_attributes['chainID'] = atom_attributes['chain'] 50 | del atom_attributes['chain'] 51 | 52 | # Now load the attributes to the new Universe 53 | for attr, val in atom_attributes.items(): 54 | # if attr in exclude_attributes: 55 | # continue 56 | # u.add_TopologyAttr(attr, val) 57 | try: 58 | u.add_TopologyAttr(attr, val) 59 | except: 60 | pass 61 | u.atoms.positions = atom_attributes['position'] 62 | 63 | # add segid info if provided 64 | if hasattr(region.atoms[0], "segid"): 65 | segments = list(set(region.segids)) 66 | for segid in segments: 67 | segment = u.add_Segment(segid=segid) 68 | ag = [] 69 | for atom in region.atoms: 70 | if atom.segid == segid: 71 | ag.append(u.select_atoms(f'id {atom.id}')[0]) 72 | sum(ag).residues.segments=segment 73 | 74 | # Create AtomGroup and sort by resids 75 | atom_group = sum(list(u.atoms.sort(key='ids'))) 76 | 77 | return atom_group 78 | 79 | 80 | def mda_atom_to_qmz_atom(mda_atom): 81 | attrs = {} 82 | for attr in MDAATOMATTRIBUTES: 83 | if hasattr(mda_atom, attr): 84 | attrs[attr] = getattr(mda_atom, attr) 85 | if not hasattr(mda_atom, 'chainID'): 86 | attrs['chainID'] = 'X' 87 | atom = QMzymeAtom(**attrs) 88 | return atom 89 | 90 | MDAATOMATTRIBUTES = [ 91 | 'chainID', 92 | 'charge', 93 | 'element', 94 | 'force', 95 | 'fragindex', 96 | 'fragment', 97 | 'icode', 98 | 'id', 99 | 'index', 100 | 'ix', 101 | 'ix_array', 102 | 'mass', 103 | 'name', 104 | 'occupancy', 105 | 'position', 106 | 'radius', 107 | 'record_type', 108 | 'resid', 109 | 'residue', 110 | 'resindex', 111 | 'resname', 112 | 'resnum', 113 | 'segid', 114 | 'segindex', 115 | 'segment', 116 | 'tempfactor', 117 | 'type', 118 | 'universe', 119 | 'velocity', 120 | ] 121 | -------------------------------------------------------------------------------- /QMzyme/data/1oh0_equ.prod_1.stripped.dcd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hklem/QMzyme/29cb26f2114fe7df71fc9b0985c734b69374a8b7/QMzyme/data/1oh0_equ.prod_1.stripped.dcd -------------------------------------------------------------------------------- /QMzyme/data/__init__.py: -------------------------------------------------------------------------------- 1 | from importlib import resources 2 | import copy 3 | from QMzyme.configuration import protein_residues, residue_charges, backbone_atoms 4 | 5 | _data_ref = resources.files('QMzyme.data') 6 | 7 | PDB = (_data_ref / '1oh0.pdb').as_posix() 8 | TOP = (_data_ref / '1oh0_equ.prmtop').as_posix() 9 | RST = (_data_ref / '1oh0_equ.rst7').as_posix() 10 | DCD = (_data_ref / '1oh0_equ.prod_1.stripped.dcd').as_posix() 11 | PQR = (_data_ref / '1oh0_equ.prod_1.stripped.pqr').as_posix() 12 | 13 | # protein_residues = QMzyme.configuration.protein_residues 14 | # residue_charges = QMzyme.configuration.residue_charges 15 | # backbone_atoms = QMzyme.configuration.backbone_atoms 16 | -------------------------------------------------------------------------------- /QMzyme/truncation_utils.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Code written by Heidi Klem. 3 | # e: heidiklem@yahoo.com or heidi.klem@nist.gov 4 | ############################################################################### 5 | 6 | """ 7 | Module containing functions utilized by truncation_schemes.py. 8 | """ 9 | 10 | import numpy as np 11 | from QMzyme.QMzymeAtom import QMzymeAtom 12 | from QMzyme.RegionBuilder import RegionBuilder 13 | from QMzyme.converters import * 14 | from QMzyme.data import backbone_atoms 15 | 16 | 17 | def cap_H(replace_atom, fixed_atom, bond_length=1.09, base_atom=None): 18 | """ 19 | :param replace_atom: The Atom to be converted to hydrogen. 20 | :param fixed_atom: The Atom bound to replace_atom that serves as reference point for bond vector calculation. 21 | """ 22 | new_position = set_bond_length(replace_atom.position, fixed_atom.position, bond_length) 23 | new_atom_dict = { 24 | 'element': 'H', 25 | 'type': 'H', 26 | 'name': f'H{replace_atom.element}', 27 | 'position': new_position, 28 | 'mass': 1.00794, 29 | } 30 | if base_atom is None: 31 | base_atom = replace_atom 32 | if fixed_atom.resname == 'PRO' and fixed_atom.name == backbone_atoms['N']: 33 | new_atom_dict['charge'] = 0.0 34 | new_atom_dict['name'] = 'HN' 35 | base_atom = fixed_atom 36 | new_atom = create_new_atom(base_atom, new_atom_dict) # used fixed atom because sometimes replaced atom comes from a different residue 37 | return new_atom 38 | 39 | def balance_charge(region, truncated_region): 40 | """ 41 | To be used if original region has atom charge information, 42 | so the QMzymeRegion guess_charge() and read_charges() methods do not get messed up. 43 | Within a residue, any atoms added should distribute the charges of any atoms removed. 44 | """ 45 | for res in region.residues: 46 | truncated_res = truncated_region.get_residue(res.resid) 47 | removed_atoms = [] 48 | added_atoms = [] 49 | chrg = 0 50 | for atom in res.atoms: 51 | if atom.name not in [atom.name for atom in truncated_res.atoms]: 52 | removed_atoms.append(atom) 53 | for atom in truncated_res.atoms: 54 | if atom.name not in [atom.name for atom in res.atoms]: 55 | added_atoms.append(atom) 56 | if len(removed_atoms) == 0: 57 | continue 58 | for atom in removed_atoms: 59 | chrg += atom.charge 60 | fractional_charge = chrg/len(added_atoms) 61 | for atom in added_atoms: 62 | atom.charge = fractional_charge 63 | 64 | 65 | def set_bond_length(mobile_coords, fixed_coords, new_length): 66 | M = new_length/np.linalg.norm(fixed_coords-mobile_coords) 67 | new_coords = fixed_coords-(M*(fixed_coords-mobile_coords)) 68 | return new_coords 69 | 70 | 71 | def create_new_atom(base_atom, new_atom_dict): 72 | for key, val in base_atom.__dict__.items(): 73 | if key.startswith('_QMzymeAtom__'): 74 | key = key.split('_QMzymeAtom__')[-1] 75 | if key not in new_atom_dict: 76 | new_atom_dict[key] = val 77 | new_atom = QMzymeAtom(**new_atom_dict) 78 | return new_atom 79 | 80 | 81 | def get_preceding_Catom(region, resid): 82 | if resid == 1: 83 | return None 84 | if region._universe != None: 85 | try: 86 | mda_atom = region._universe.select_atoms(f'resid {resid-1} and name {backbone_atoms["C"]}').atoms[0] 87 | atom = mda_atom_to_qmz_atom(mda_atom) 88 | except: #Possibly a structure that had been previous truncated.. or weird backbone atom names! 89 | return None 90 | return atom 91 | 92 | 93 | def get_following_Natom(region, resid): 94 | if region._universe != None: 95 | try: 96 | mda_atom = region._universe.select_atoms(f'resid {resid+1} and name {backbone_atoms["N"]}').atoms[0] 97 | atom = mda_atom_to_qmz_atom(mda_atom) 98 | except: #Possibly a structure that had been previous truncated.. or weird backbone atom names! 99 | atom = None # covers if res is last protein res in universe 100 | return atom 101 | 102 | 103 | def has_Nterm_neighbor(atom): 104 | return atom.resid-1 in atom.region.resids 105 | 106 | 107 | def has_Cterm_neighbor(atom): 108 | return atom.resid+1 in atom.region.resids 109 | -------------------------------------------------------------------------------- /QMzyme/utils.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Code written by Heidi Klem. 3 | # e: heidiklem@yahoo.com or heidi.klem@nist.gov 4 | ############################################################################### 5 | 6 | """ 7 | Module containing broad functionality utilized throughout the package. 8 | """ 9 | 10 | from functools import singledispatch 11 | from QMzyme.QMzymeModel import QMzymeModel 12 | from QMzyme.RegionBuilder import RegionBuilder 13 | import QMzyme.MDAnalysisWrapper as MDAwrapper 14 | from MDAnalysis.core.groups import AtomGroup 15 | from QMzyme.SelectionSchemes import SelectionScheme 16 | from QMzyme.SelectionSchemes import DistanceCutoff 17 | from abc import ABCMeta 18 | from QMzyme.QMzymeRegion import QMzymeRegion 19 | import numpy as np 20 | 21 | def check_filename(filename, format): 22 | if filename.endswith(format): 23 | return filename 24 | if not format.startswith('.'): 25 | format = '.'+format 26 | return filename.split('.')[0]+format 27 | 28 | @singledispatch 29 | def make_selection(selection, model: QMzymeModel, name=None, **kwargs): 30 | """ 31 | Method to enable variable input comability: will return an MDA AtomGroup if 32 | input was an MDA selection command str, or return the input if it was either 33 | an MDA AtomGroup or QMzymeRegion. 34 | """ 35 | raise UserWarning(f"Invalid selection {selection}.") 36 | #print('make selection from: ', selection) 37 | #return selection 38 | 39 | @make_selection.register 40 | def MDA_str_selection(selection: str, model: QMzymeModel, name, **kwargs): 41 | region_builder = RegionBuilder(name) 42 | selection = MDAwrapper.select_atoms(model.universe, selection) 43 | region_builder.init_atom_group(selection) 44 | region = region_builder.get_region() 45 | return region 46 | 47 | @make_selection.register 48 | def MDA_AtomGroup_selection(selection: AtomGroup, model: QMzymeModel, name, **kwargs): 49 | region_builder = RegionBuilder(name) 50 | region_builder.init_atom_group(selection) 51 | region = region_builder.get_region() 52 | return region 53 | 54 | @make_selection.register 55 | def MDA_AtomGroup_selection(selection: QMzymeRegion, model: QMzymeModel, name, **kwargs): 56 | if name is not None: 57 | selection.name = name 58 | return selection 59 | 60 | @make_selection.register 61 | def scheme_selection(selection: ABCMeta, model: QMzymeModel, name, **kwargs): 62 | s = selection(model=model, name=name, **kwargs) 63 | region = s.return_region() 64 | return region 65 | 66 | def rmsd(xyz1, xyz2, align=False): 67 | if align == True: 68 | t, r = compute_translation_and_rotation(xyz1, xyz2) 69 | xyz1 = kabsch_transform(xyz1, t, r) 70 | delta = xyz1 - xyz2 71 | rmsd = (delta ** 2.0).sum(1).mean() ** 0.5 72 | return rmsd 73 | 74 | def compute_translation_and_rotation(mobile, target): 75 | #meta data 76 | n_atoms = mobile.shape[0] 77 | n_dim = mobile.shape[1] 78 | mu1 = np.zeros(n_dim) 79 | mu2 = np.zeros(n_dim) 80 | for i in range(n_atoms): 81 | for j in range(n_dim): 82 | mu1[j] += mobile[i,j] 83 | mu2[j] += target[i,j] 84 | mu1 /= n_atoms 85 | mu2 /= n_atoms 86 | mobile = mobile - mu1 87 | target = target - mu2 88 | 89 | correlation_matrix = np.dot(np.transpose(mobile), target) 90 | V, S, W_tr = np.linalg.svd(correlation_matrix) 91 | if np.linalg.det(V) * np.linalg.det(W_tr) < 0.0: 92 | V[:, -1] = -V[:, -1] 93 | rotation = np.dot(V, W_tr) 94 | translation = mu2 - np.dot(mu1,rotation) 95 | return translation, rotation 96 | 97 | def kabsch_transform(mobile, translation, rotation): 98 | mobile_prime = np.dot(mobile,rotation) + translation 99 | return mobile_prime 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![](logo.png) 3 | 4 | ![GitHub Release](https://img.shields.io/github/v/release/hklem/QMzyme?label=Release) 5 | ![Read the Docs](https://img.shields.io/readthedocs/qmzyme?label=Documentation) 6 | [![CircleCI](https://dl.circleci.com/status-badge/img/circleci/LRUEotncYnASivTi54FASD/DM24Fo4B8Af3VgC59vNCCx/tree/main.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/circleci/LRUEotncYnASivTi54FASD/DM24Fo4B8Af3VgC59vNCCx/tree/main) 7 | ![Codecov](https://img.shields.io/codecov/c/github/hklem/QMzyme?label=Coverage) 8 | [![Powered by MDAnalysis](https://img.shields.io/badge/Powered%20by-MDAnalysis-orange.svg?logoWidth=16&logo=data:image/x-icon;base64,AAABAAEAEBAAAAEAIAAoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJD+XwCY/fEAkf3uAJf97wGT/a+HfHaoiIWE7n9/f+6Hh4fvgICAjwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACT/yYAlP//AJ///wCg//8JjvOchXly1oaGhv+Ghob/j4+P/39/f3IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJH8aQCY/8wAkv2kfY+elJ6al/yVlZX7iIiI8H9/f7h/f38UAAAAAAAAAAAAAAAAAAAAAAAAAAB/f38egYF/noqAebF8gYaagnx3oFpUUtZpaWr/WFhY8zo6OmT///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAn46Ojv+Hh4b/jouJ/4iGhfcAAADnAAAA/wAAAP8AAADIAAAAAwCj/zIAnf2VAJD/PAAAAAAAAAAAAAAAAICAgNGHh4f/gICA/4SEhP+Xl5f/AwMD/wAAAP8AAAD/AAAA/wAAAB8Aov9/ALr//wCS/Z0AAAAAAAAAAAAAAACBgYGOjo6O/4mJif+Pj4//iYmJ/wAAAOAAAAD+AAAA/wAAAP8AAABhAP7+FgCi/38Axf4fAAAAAAAAAAAAAAAAiIiID4GBgYKCgoKogoB+fYSEgZhgYGDZXl5e/m9vb/9ISEjpEBAQxw8AAFQAAAAAAAAANQAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjo6Mb5iYmP+cnJz/jY2N95CQkO4pKSn/AAAA7gAAAP0AAAD7AAAAhgAAAAEAAAAAAAAAAACL/gsAkv2uAJX/QQAAAAB9fX3egoKC/4CAgP+NjY3/c3Nz+wAAAP8AAAD/AAAA/wAAAPUAAAAcAAAAAAAAAAAAnP4NAJL9rgCR/0YAAAAAfX19w4ODg/98fHz/i4uL/4qKivwAAAD/AAAA/wAAAP8AAAD1AAAAGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALGxsVyqqqr/mpqa/6mpqf9KSUn/AAAA5QAAAPkAAAD5AAAAhQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkUFBSuZ2dn/3V1df8uLi7bAAAATgBGfyQAAAA2AAAAMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0AAADoAAAA/wAAAP8AAAD/AAAAWgC3/2AAnv3eAJ/+dgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9AAAA/wAAAP8AAAD/AAAA/wAKDzEAnP3WAKn//wCS/OgAf/8MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAANwAAADtAAAA7QAAAMAAABUMAJn9gwCe/e0Aj/2LAP//AQAAAAAAAAAA)](https://www.mdanalysis.org) 9 | 10 | [comment]: <[![PyPI version](https://badge.fury.io/py/QMzyme.svg)](https://badge.fury.io/py/QMzyme)> 11 | 12 | 13 | *QMzyme is currently under-development. Please note the user interface may change! The first stable API version will be released as QMzyme==1.0.0 on PyPi.* 14 | 15 | QMzyme is a Python toolkit to facilitate (quantum mechanical) QM-based enzyme calculations. The GenerateModel module guides the process of generating calculation ready truncated or partitioned molecule regions. Any input file(s) accepted by [MDAnalysis to create a Universe](https://userguide.mdanalysis.org/stable/universe.html) object can be used to start working in QMzyme. From there, the code relies on more flexible QMzyme objects: QMzymeAtom, QMzymeResidue, QMzymeRegion and QMzymeModel. 16 | 17 | Full documentation with installation instructions, technical details and examples can be found in [Read the Docs](https://qmzyme.readthedocs.io/). 18 | 19 | ## Contributing to QMzyme 20 | For suggestions and improvements of the code (greatly appreciated!), please reach out through the issues and pull requests options of Github. See documentation about [contributing guidelines](https://qmzyme.readthedocs.io/en/latest/Contributing/index.html). 21 | 22 | ## Current Contributors 23 | * Heidi Klem, NIST (main developer) 24 | * Demian Riccardi, NIST 25 | 26 | ## References 27 | QMzyme has not been formally published. Please refer back here for the proper citation once it has. 28 | 29 | QMzyme's main dependency is MDAnalysis. When using QMzyme please [cite MDAnalysis accordingly](https://www.mdanalysis.org/pages/citations/): 30 | 31 | * R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler, D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein. MDAnalysis: A Python package for the rapid analysis of molecular dynamics simulations. In S. Benthall and S. Rostrup, editors, Proceedings of the 15th Python in Science Conference, pages 98-105, Austin, TX, 2016. SciPy, doi:10.25080/majora-629e541a-00e. 32 | 33 | * N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. J. Comput. Chem. 32 (2011), 2319-2327, doi:10.1002/jcc.21787. PMCID:PMC3144279 34 | 35 | If you use QMzyme to write QM software calculation input files, please include this citation for [AQME](https://aqme.readthedocs.io/en/latest/): 36 | 37 | * Alegre-Requena, J. V.; Sowndarya, S.; Pérez-Soto, R.; Alturaifi, T.; Paton, R. AQME: Automated Quantum Mechanical Environments for Researchers and Educators. Wiley Interdiscip. Rev. Comput. Mol. Sci. 2023, 13, e1663. (DOI: 10.1002/wcms.1663). 38 | 39 | ### Acknowledgements 40 | MolSSI for providing the [CMS Cookiecutter](https://github.com/molssi/cookiecutter-cms) that informed initial project architecture. 41 | 42 | We really THANK all the testers for their feedback, including: 43 | 44 | * **Helena Giramé** (2024, Feixas and Garcia-Borràs groups at University of Girona, IQCC) 45 | * **Cristina Berga** (2024, Feixas and Garcia-Borràs groups at University of Girona, IQCC) 46 | * **Aqza Elza John** (2024, Feixas and Garcia-Borràs groups at University of Girona, IQCC) 47 | * **Hande Abeş** (2024, Feixas and Garcia-Borràs groups at University of Girona, IQCC) 48 | * **Raul Perez-Soto** (2024, Kim group at Colorado State University) 49 | * **Alex Platt** (2024, Paton group at Colorado State University) 50 | 51 | We greatly acknowledge HPC resources obtained through ACCESS: 52 | * Timothy J. Boerner, Stephen Deems, Thomas R. Furlani, Shelley L. Knuth, and John Towns. 2023. ACCESS: Advancing Innovation: NSF’s Advanced Cyberinfrastructure Coordination Ecosystem: Services & Support. “In Practice and Experience in Advanced Research Computing (PEARC ’23)”, July 23–27, 2023, Portland, OR, USA. ACM, New York, NY, USA, 4 pages. https://doi.org/10.1145/3569951.3597559. 53 | * This work used Expanse at SDSC through allocation BIO230144 from the Advanced Cyberinfrastructure Coordination Ecosystem: Services & Support (ACCESS) program, which is supported by National Science Foundation grants #2138259, #2138286, #2138307, #2137603, and #2138296. 54 | 55 | #### Copyright 56 | Copyright (c) 2024, Heidi Klem 57 | 58 | -------------------------------------------------------------------------------- /docs/API/API_reference.rst: -------------------------------------------------------------------------------- 1 | 2 | .. |schema| image:: ../Images/QMzyme_schema.png 3 | :width: 600 4 | 5 | Main Modules 6 | ------------- 7 | 8 | |schema| 9 | 10 | The GenerateModel, CalculateModel, and Writers modules communicate to 11 | create a calculation ready QMzymeModel. 12 | 13 | .. toctree:: 14 | :maxdepth: 1 15 | 16 | QMzyme.GenerateModel 17 | QMzyme.SelectionSchemes 18 | QMzyme.TruncationSchemes 19 | QMzyme.CalculateModel 20 | QMzyme.Writers 21 | 22 | 23 | Data Structures 24 | --------------------------------------- 25 | 26 | Modules for constructing QMzyme objects. 27 | 28 | .. toctree:: 29 | :maxdepth: 1 30 | 31 | QMzyme.QMzymeAtom 32 | QMzyme.QMzymeResidue 33 | QMzyme.QMzymeModel 34 | QMzyme.QMzymeRegion 35 | QMzyme.RegionBuilder 36 | 37 | 38 | Other Modules 39 | -------------- 40 | 41 | .. toctree:: 42 | :maxdepth: 1 43 | 44 | QMzyme.configuration 45 | QMzyme.utils 46 | QMzyme.truncation_utils 47 | QMzyme.aqme.qprep 48 | QMzyme.MDAnalysisWrapper 49 | -------------------------------------------------------------------------------- /docs/API/QMzyme.CalculateModel.rst: -------------------------------------------------------------------------------- 1 | CalculateModel 2 | ================ 3 | 4 | .. automodule:: QMzyme.CalculateModel 5 | 6 | 7 | 8 | QM Treatment 9 | --------------------- 10 | 11 | .. autoclass:: QMzyme.CalculateModel.QM_Method 12 | :show-inheritance: 13 | :members: 14 | :undoc-members: 15 | 16 | xTB Treatment 17 | --------------------- 18 | 19 | .. autoclass:: QMzyme.CalculateModel.XTB_Method 20 | :show-inheritance: 21 | :members: 22 | :undoc-members: 23 | 24 | MM Treatment 25 | ------------------- 26 | 27 | Under development. 28 | 29 | Developer Classes 30 | ------------------ 31 | 32 | If you are interested in contributing to QMzyme by creating new calculation methods, you will need to interact with the classes. 33 | 34 | .. autoclass:: QMzyme.CalculateModel.CalculationBase 35 | :members: 36 | 37 | .. autoclass:: QMzyme.CalculateModel.MultiscaleCalculationBase 38 | :show-inheritance: 39 | :members: 40 | 41 | .. autoclass:: QMzyme.CalculateModel.CalculateModel 42 | :members: 43 | 44 | .. autoclass:: QMzyme.CalculateModel.CalculationFactory 45 | :members: 46 | -------------------------------------------------------------------------------- /docs/API/QMzyme.GenerateModel.rst: -------------------------------------------------------------------------------- 1 | .. _GenerateModel: 2 | 3 | GenerateModel 4 | ================ 5 | 6 | .. automodule:: QMzyme.GenerateModel 7 | :show-inheritance: 8 | :members: 9 | :undoc-members: 10 | 11 | -------------------------------------------------------------------------------- /docs/API/QMzyme.MDAnalysisWrapper.rst: -------------------------------------------------------------------------------- 1 | MDAnalysisWrapper 2 | =================== 3 | 4 | .. automodule:: QMzyme.MDAnalysisWrapper 5 | :members: 6 | :undoc-members: 7 | 8 | -------------------------------------------------------------------------------- /docs/API/QMzyme.QMzymeAtom.rst: -------------------------------------------------------------------------------- 1 | QMzymeAtom 2 | =================== 3 | 4 | .. autoclass:: QMzyme.QMzymeAtom.QMzymeAtom 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/API/QMzyme.QMzymeModel.rst: -------------------------------------------------------------------------------- 1 | QMzymeModel 2 | =================== 3 | 4 | .. autoclass:: QMzyme.QMzymeModel.QMzymeModel 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/API/QMzyme.QMzymeRegion.rst: -------------------------------------------------------------------------------- 1 | QMzymeRegion 2 | =================== 3 | 4 | .. autoclass:: QMzyme.QMzymeRegion.QMzymeRegion 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/API/QMzyme.QMzymeResidue.rst: -------------------------------------------------------------------------------- 1 | QMzymeResidue 2 | =================== 3 | 4 | .. autoclass:: QMzyme.QMzymeRegion.QMzymeResidue 5 | :show-inheritance: 6 | :members: 7 | :undoc-members: -------------------------------------------------------------------------------- /docs/API/QMzyme.RegionBuilder.rst: -------------------------------------------------------------------------------- 1 | RegionBuilder 2 | =================== 3 | 4 | .. autoclass:: QMzyme.RegionBuilder.RegionBuilder 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/API/QMzyme.SelectionSchemes.rst: -------------------------------------------------------------------------------- 1 | SelectionSchemes 2 | ================= 3 | 4 | .. automodule:: QMzyme.SelectionSchemes 5 | :show-inheritance: 6 | :special-members: __init__ 7 | :members: 8 | :undoc-members: 9 | 10 | -------------------------------------------------------------------------------- /docs/API/QMzyme.TruncationSchemes.rst: -------------------------------------------------------------------------------- 1 | TruncationSchemes 2 | ================= 3 | 4 | .. automodule:: QMzyme.TruncationSchemes 5 | :show-inheritance: 6 | :members: 7 | :undoc-members: 8 | 9 | -------------------------------------------------------------------------------- /docs/API/QMzyme.Writers.rst: -------------------------------------------------------------------------------- 1 | Writers 2 | ================ 3 | 4 | QM-only input 5 | --------------- 6 | 7 | .. autoclass:: QMzyme.Writers.QMWriter 8 | :members: 9 | :undoc-members: 10 | 11 | QM/QM2 input 12 | ------------------ 13 | 14 | .. autoclass:: QMzyme.Writers.QMQM2Writer 15 | :members: 16 | :undoc-members: 17 | 18 | QM/xTB input 19 | ------------------ 20 | 21 | .. autoclass:: QMzyme.Writers.QMXTBWriter 22 | :members: 23 | :undoc-members: 24 | 25 | QM/MM input 26 | ------------------ 27 | 28 | .. autoclass:: QMzyme.Writers.QMMMWriter 29 | :members: 30 | :undoc-members: 31 | 32 | 33 | Developer Classes 34 | -------------------- 35 | 36 | If you are interesed in contributing to the QMzyme project by adding a new concrete Writer class, you will need 37 | to be familiar with these developer classes. Read more about it in the 38 | `QMzyme Documentation `_. 39 | 40 | We hope to support multi-scale methods beyond 2 layers in the bear future. 41 | 42 | .. autoclass:: QMzyme.Writers.Writer 43 | :members: 44 | :undoc-members: 45 | 46 | .. autoclass:: QMzyme.Writers.WriterFactory 47 | :members: 48 | :undoc-members: 49 | 50 | -------------------------------------------------------------------------------- /docs/API/QMzyme.aqme.qprep.rst: -------------------------------------------------------------------------------- 1 | AQME.qprep 2 | ================ 3 | 4 | .. automodule:: QMzyme.aqme.qprep 5 | 6 | -------------------------------------------------------------------------------- /docs/API/QMzyme.configuration.rst: -------------------------------------------------------------------------------- 1 | QMzyme.configuration 2 | ===================== 3 | 4 | .. automodule:: QMzyme.configuration.__init__ 5 | :members: 6 | :special-members: 7 | -------------------------------------------------------------------------------- /docs/API/QMzyme.truncation_utils.rst: -------------------------------------------------------------------------------- 1 | Truncation utils 2 | ================= 3 | 4 | .. automodule:: QMzyme.truncation_utils 5 | :show-inheritance: 6 | :members: 7 | :undoc-members: 8 | 9 | -------------------------------------------------------------------------------- /docs/API/QMzyme.utils.rst: -------------------------------------------------------------------------------- 1 | utils 2 | ================ 3 | 4 | .. automodule:: QMzyme.utils 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/API/index.rst: -------------------------------------------------------------------------------- 1 | Code Documentation 2 | -------------------- 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | API_reference 8 | -------------------------------------------------------------------------------- /docs/Contributing/CODE_OF_CONDUCT.rst: -------------------------------------------------------------------------------- 1 | Contributor Covenant Code of Conduct 2 | ======================================= 3 | 4 | Pledge 5 | ------------- 6 | 7 | We as members, contributors, and leaders pledge to make participation in our 8 | community a harassment-free experience for everyone, regardless of age, body 9 | size, visible or invisible disability, ethnicity, sex characteristics, gender 10 | identity and expression, level of experience, education, socio-economic status, 11 | nationality, personal appearance, race, caste, color, religion, or sexual 12 | identity and orientation. 13 | 14 | We pledge to act and interact in ways that contribute to an open, welcoming, 15 | diverse, inclusive, and healthy community. 16 | 17 | Our Standards 18 | -------------- 19 | 20 | Examples of behavior that contributes to a positive environment for our 21 | community include: 22 | 23 | * Demonstrating empathy and kindness toward other people 24 | * Being respectful of differing opinions, viewpoints, and experiences 25 | * Giving and gracefully accepting constructive feedback 26 | * Accepting responsibility and apologizing to those affected by our mistakes, 27 | and learning from the experience 28 | * Focusing on what is best not just for us as individuals, but for the overall 29 | community 30 | 31 | Examples of unacceptable behavior include: 32 | 33 | * The use of sexualized language or imagery, and sexual attention or advances of 34 | any kind 35 | * Trolling, insulting or derogatory comments, and personal or political attacks 36 | * Public or private harassment 37 | * Publishing others' private information, such as a physical or email address, 38 | without their explicit permission 39 | * Other conduct which could reasonably be considered inappropriate in a 40 | professional setting 41 | 42 | Enforcement Responsibilities 43 | --------------------------------------- 44 | 45 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 48 | 49 | Scope 50 | ------------- 51 | 52 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. 53 | Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 54 | 55 | Enforcement 56 | ------------- 57 | 58 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 59 | reported to main developer Heidi Klem at heidi.klem@nist.gov. 60 | All complaints will be reviewed and investigated promptly and fairly. 61 | 62 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 63 | 64 | Enforcement Guidelines 65 | --------------------------------------- 66 | 67 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 68 | 69 | 1. Correction 70 | 71 | **Community Impact**: Use of inappropriate language or other behavior deemed 72 | unprofessional or unwelcome in the community. 73 | 74 | **Consequence**: A private, written warning from community leaders, providing 75 | clarity around the nature of the violation and an explanation of why the 76 | behavior was inappropriate. A public apology may be requested. 77 | 78 | 2. Warning 79 | 80 | **Community Impact**: A violation through a single incident or series of 81 | actions. 82 | 83 | **Consequence**: A warning with consequences for continued behavior. No 84 | interaction with the people involved, including unsolicited interaction with 85 | those enforcing the Code of Conduct, for a specified period of time. This 86 | includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 87 | 88 | Attribution 89 | ------------- 90 | 91 | This Code of Conduct is adapted from the Contributor Covenant, 92 | version 2.1, available at 93 | https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. 94 | 95 | Community Impact Guidelines were inspired by 96 | Mozilla's code of conduct enforcement ladder. 97 | 98 | For answers to common questions about this code of conduct, see the FAQ at 99 | https://www.contributor-covenant.org/faq. Translations are available at 100 | https://www.contributor-covenant.org/translations. -------------------------------------------------------------------------------- /docs/Contributing/calculation_methods.rst: -------------------------------------------------------------------------------- 1 | .. _calculation_methods: 2 | 3 | Adding a Calculation Type 4 | ============================ 5 | 6 | In QMzyme, :class:`~QMzyme.CalculateModel` contains all methods used to 7 | define a calculation type, its details, and assign it to a region. When a calculation 8 | class (Ex., :class:`~QMzyme.CalculateModel.QM_Method`) is assigned to a region using 9 | its parent class (:class:`~QMzyme.CalculateModel.CalculationBase`) method, `.assign_to_region()` 10 | and the calculation type is then recorded in :class:`~QMzyme.CalculateModel.CalculateModel`. 11 | This is required to enable :class:`~QMzyme.GenerateModel.GenerateModel` to automate writing of 12 | calculation input files, because :class:`~QMzyme.Writers.Writer` will be able to read 13 | the CalculateModel data and decide what writer class to call (assuming there is 14 | a writer class that corresponds to your new calculation type!). See :ref:`writers` to learn more. 15 | 16 | See :ref:`general` for general guidance on contributing to QMzyme to get started. 17 | -------------------------------------------------------------------------------- /docs/Contributing/general.rst: -------------------------------------------------------------------------------- 1 | .. _general: 2 | 3 | General Guidance 4 | ==================== 5 | 6 | Fork repository 7 | ------------------ 8 | 9 | Visit https://github.com/hklem/QMzyme. On the top right of the page, click Fork 10 | > create a new fork (fill in details) > Create fork. 11 | 12 | Make changes 13 | -------------- 14 | 15 | Make sure to include docstrings and comments. The easier the code is to read, 16 | the more likely the changes are to be accepted. 17 | 18 | Some modules, such as SelectionSchemes.py and TruncationSchemes.py are 19 | accompanied by abstract classes under the same name that prescribe how 20 | additional schemes should be constructed. To better understand abstract 21 | classes and methods, see https://docs.python.org/3/library/abc.html. 22 | 23 | Viewing Documentation Locally 24 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 25 | 26 | You can also add onto the read the docs webpage in the QMzyme/docs directory. 27 | To build the documentaion pages locally go to the QMzyme/docs dirctory and run: 28 | 29 | .. code-block:: bash 30 | 31 | pip install -r requirements.txt # to install sphinx and other docs related packages 32 | brew install pandoc # pandoc is required to configure the jupyter notebook files used as tutorials. If you are not on MacOS see pandoc webpage for other installation methods: https://pandoc.org/installing.html 33 | make html 34 | open _build/html/index.html # a web browswer should open 35 | 36 | There may be some WARNING messages. For the most part, they are probably okay to ignore (i.e., 'WARNING: document isn't included in any toctree') 37 | 38 | Test Changes 39 | ------------------ 40 | 41 | If you are adding or changing code functionality, you will also be asked 42 | to implement tests before the pull request is accepted and merged. The tests 43 | are located in the /tests/ directory. If you modified an existing module, 44 | find the corresponding test_MODULENAME.py file and add one or more tests there. 45 | 46 | QMzyme uses the pytest package for its testing suite. If you are unfamiliar with pytest 47 | you can learn more on their [documentation webpage](https://docs.pytest.org/en/8.2.x/). 48 | 49 | Running Tests Locally 50 | ~~~~~~~~~~~~~~~~~~~~~~~ 51 | In the main QMzyme directory run: 52 | 53 | .. code-block:: bash 54 | 55 | pip install -e .[test] # installs pytest and pytest-cov 56 | # To run only a single test file: 57 | pytest tests/test_FILENAME.py -vv --color yes 58 | # To run all tests 59 | pytest -vv --color yes 60 | 61 | If any tests failed review the verbose output to understand why and fix the code accordingly. 62 | 63 | 64 | Create pull request 65 | ------------------ 66 | 67 | See https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork. 68 | Pull requests will only be accepted if all tests pass. 69 | -------------------------------------------------------------------------------- /docs/Contributing/index.rst: -------------------------------------------------------------------------------- 1 | Contributions are welcomed and encouraged! Is there a selection scheme 2 | or truncation scheme you would like QMzyme to support? Add your own! 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | CODE_OF_CONDUCT 8 | suggestions 9 | general 10 | selection_schemes 11 | truncation_schemes 12 | calculation_methods 13 | writers 14 | -------------------------------------------------------------------------------- /docs/Contributing/selection_schemes.rst: -------------------------------------------------------------------------------- 1 | Adding a Selection Scheme 2 | =========================== 3 | 4 | :class:`~QMzyme.SelectionSchemes.SelectionScheme` is an `abstract base class (ABC) 5 | `_ used to prescribe concrete selection 6 | scheme classes. Please note, if you submit a GitHub Pull Request to include a new 7 | selection scheme you will need to have also added appropriate tests in the QMzyme/tests 8 | directory. The docstring of the `SelectionScheme` base class describes how you can create 9 | your own subclass: 10 | 11 | .. autoclass:: QMzyme.SelectionSchemes.SelectionScheme 12 | :members: 13 | -------------------------------------------------------------------------------- /docs/Contributing/suggestions.rst: -------------------------------------------------------------------------------- 1 | .. _suggestions: 2 | 3 | Suggestions/Ideas 4 | ===================== 5 | 6 | If you have ideas or suggestions on how to improve QMzyme please do 7 | not hesitate to engage on the 8 | `QMzyme Ideas GitHub Project space `_, 9 | or contribute directly by `forking the repository and submitting a pull request `_. Questions can be directed via email to 10 | heidi.klem{AT}nist{dot}gov. 11 | -------------------------------------------------------------------------------- /docs/Contributing/truncation_schemes.rst: -------------------------------------------------------------------------------- 1 | Adding a Truncation Scheme 2 | =========================== 3 | 4 | :class:`~QMzyme.TruncationSchemes.TruncationScheme` is an `abstract base class (ABC) 5 | `_ used 6 | to prescribe concrete truncation scheme classes. See the TruncationScheme class documentaion 7 | for details on how to include your own scheme. Please note, if you submit a 8 | GitHub Pull Request to include a new scheme you will need to have also added 9 | appropriate tests in the QMzyme/tests directory. 10 | -------------------------------------------------------------------------------- /docs/Contributing/writers.rst: -------------------------------------------------------------------------------- 1 | .. _writers: 2 | 3 | Adding a Writer 4 | ================ 5 | 6 | :class:`~QMzyme.Writers.Writer` is called directly by 7 | :class:`~QMzyme.GenerateModel.GenerateModel` to automate writing of 8 | calculation input files. If you would like to contribute by expanding the 9 | suite of supported input, you can write your own concrete writer class! 10 | 11 | See :ref:`general` for general guidance on contributing to QMzyme to get started. 12 | 13 | If you just wish for your writer to be used directly (i.e., someone would 14 | import your class from QMzyme.Writers and call it, providing all arguments), 15 | then all you have to do is write the class and add appropriate tests. 16 | 17 | However, in order for your writer class to be automatically detectable by GenerateModel 18 | you will need to do some steps in addition to writing your class and creating tests: 19 | - register your class to :class:`~QMzyme.Writers.WritersRegistry`. 20 | - add code to :class:`~QMzyme.GenerateModel.GenerateModel`, see :ref:`calculation_methods`. 21 | 22 | -------------------------------------------------------------------------------- /docs/Examples/Getting_Started.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "42aabce4-9a6d-4f92-9182-9a91f551e070", 6 | "metadata": { 7 | "tags": [] 8 | }, 9 | "source": [ 10 | "# Getting Started\n", 11 | "\n", 12 | "In this example, you will see how you can initialize the QMzyme GenerateModel module in various ways, using the pre-packaged QMzyme data." 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 5, 18 | "id": "eb54298b-c61b-4161-8847-6bff92712113", 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import QMzyme\n", 23 | "import os" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 2, 29 | "id": "9cf01442-2ef5-4804-8f1d-5bac5ceb838d", 30 | "metadata": { 31 | "scrolled": true 32 | }, 33 | "outputs": [ 34 | { 35 | "name": "stdout", 36 | "output_type": "stream", 37 | "text": [ 38 | "File: 1oh0.pdb\n", 39 | "\n", 40 | "Charge information not present. QMzyme will try to guess region charges based on residue names consistent with AMBER naming conventions (i.e., aspartate: ASP --> Charge: -1, aspartic acid: ASH --> Charge: 0.). See QMzyme.data.residue_charges for the full set.\n", 41 | "\n", 42 | "\tNonconventional Residues Found\n", 43 | "\t------------------------------\n", 44 | "\tEQU --> Charge: UNK, defaulting to 0\n", 45 | "\n", 46 | "You can update charge information for nonconventional residues by running \n", 47 | "\t>>>QMzyme.data.residue_charges.update({'3LETTER_RESNAME':INTEGER_CHARGE}). \n", 48 | "Note your changes will not be stored after you exit your session. It is recommended to only alter the residue_charges dictionary. If you alter the protein_residues dictionary instead that could cause unintended bugs in other modules (TruncationSchemes).\n", 49 | "\n", 50 | "GenerateModel instance: contains 0 region(s)>\n" 51 | ] 52 | } 53 | ], 54 | "source": [ 55 | "# Initialization of the QMzymeModel\n", 56 | "# The GenerateModel class can be initialized in any way that an MDAnalysis Universe can be initialized.\n", 57 | "\n", 58 | "# With a PDB file:\n", 59 | "\n", 60 | "from QMzyme.data import PDB\n", 61 | "print(\"File: \", os.path.basename(PDB))\n", 62 | "pdb_model = QMzyme.GenerateModel(PDB)\n", 63 | "print(\"GenerateModel instance: \", pdb_model)" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 3, 69 | "id": "88a45a1d-6137-4e6f-ac9d-73d1dea23a38", 70 | "metadata": {}, 71 | "outputs": [ 72 | { 73 | "name": "stdout", 74 | "output_type": "stream", 75 | "text": [ 76 | "Files: 1oh0_equ.prmtop 1oh0_equ.rst7\n", 77 | "GenerateModel instance: contains 0 region(s)>\n" 78 | ] 79 | } 80 | ], 81 | "source": [ 82 | "# With a topology file (.prmtop) and a restart file (.rst7) used in an AMBER MM simulation setup. The contained structure inclues the water box, so there are many atoms!\n", 83 | "\n", 84 | "from QMzyme.data import TOP, RST\n", 85 | "print(\"Files: \", os.path.basename(TOP), os.path.basename(RST))\n", 86 | "top_rst_model = QMzyme.GenerateModel(TOP, RST, format='RESTRT')\n", 87 | "print(\"GenerateModel instance: \", top_rst_model)" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 4, 93 | "id": "297ba1ef-987a-4460-9c75-d45d8cad8854", 94 | "metadata": {}, 95 | "outputs": [ 96 | { 97 | "name": "stdout", 98 | "output_type": "stream", 99 | "text": [ 100 | "Files: 1oh0_equ.prod_1.stripped.pqr 1oh0_equ.prod_1.stripped.dcd\n", 101 | "GenerateModel instance: contains 0 region(s)>\n" 102 | ] 103 | }, 104 | { 105 | "name": "stderr", 106 | "output_type": "stream", 107 | "text": [ 108 | "/Users/hrk/anaconda3/envs/qmzyme/lib/python3.11/site-packages/MDAnalysis/coordinates/DCD.py:165: DeprecationWarning: DCDReader currently makes independent timesteps by copying self.ts while other readers update self.ts inplace. This behavior will be changed in 3.0 to be the same as other readers. Read more at https://github.com/MDAnalysis/mdanalysis/issues/3889 to learn if this change in behavior might affect you.\n", 109 | " warnings.warn(\"DCDReader currently makes independent timesteps\"\n", 110 | "/Users/hrk/git/QMzyme/QMzyme/MDAnalysisWrapper.py:28: UserWarning: Element information was missing from input. MDAnalysis.topology.guessers.guess_types was used to infer element types.\n", 111 | " warnings.warn(\"Element information was missing from input. MDAnalysis.topology.guessers.guess_types was used to infer element types.\", UserWarning)\n" 112 | ] 113 | } 114 | ], 115 | "source": [ 116 | "# With a PQR file, containing charge information, and a trajectory DCD file:\n", 117 | "\n", 118 | "from QMzyme.data import PQR, DCD\n", 119 | "print(\"Files: \", os.path.basename(PQR), os.path.basename(DCD))\n", 120 | "pqr_dcd = QMzyme.GenerateModel(PQR, DCD)\n", 121 | "print(\"GenerateModel instance: \", pqr_dcd)" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "id": "9175ebc5-ea73-45a2-a01c-64cf050e88fb", 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [] 131 | } 132 | ], 133 | "metadata": { 134 | "kernelspec": { 135 | "display_name": "Python 3 (ipykernel)", 136 | "language": "python", 137 | "name": "python3" 138 | }, 139 | "language_info": { 140 | "codemirror_mode": { 141 | "name": "ipython", 142 | "version": 3 143 | }, 144 | "file_extension": ".py", 145 | "mimetype": "text/x-python", 146 | "name": "python", 147 | "nbconvert_exporter": "python", 148 | "pygments_lexer": "ipython3", 149 | "version": "3.11.7" 150 | } 151 | }, 152 | "nbformat": 4, 153 | "nbformat_minor": 5 154 | } 155 | -------------------------------------------------------------------------------- /docs/Examples/Ligand Parameterization.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "9e33c919-1309-41a1-807b-f0406574dbb5", 6 | "metadata": {}, 7 | "source": [ 8 | "# Ligand Parameterization\n", 9 | "\n", 10 | "Molecular mechanics forcefields need to be told how to treat each atom via a set of parameters. If there is a molecule (residue) in your system that your forcefield of choice does not already have parameters, you will need to build these yourself. In this example, we will see how QMzyme can automate the calculations required to parameterize a ligand in line with the RESP procedure (J. Phys. Chem. 1993, 97, 40, 10269–1028). " 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 8, 16 | "id": "2c90413f-1995-47eb-bd4a-f0b4f6efb2c6", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import QMzyme\n", 21 | "from QMzyme.data import PDB" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "id": "428ce54d-3d0c-43f9-91f6-a7565ca92c20", 27 | "metadata": {}, 28 | "source": [ 29 | "### Initialize Model" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 2, 35 | "id": "c3f2b5c3-a90d-4291-8393-c92a738d786f", 36 | "metadata": {}, 37 | "outputs": [ 38 | { 39 | "name": "stdout", 40 | "output_type": "stream", 41 | "text": [ 42 | "\n", 43 | "Charge information not present. QMzyme will try to guess region charges based on residue names consistent with AMBER naming conventions (i.e., aspartate: ASP --> Charge: -1, aspartic acid: ASH --> Charge: 0.). See QMzyme.data.residue_charges for the full set.\n", 44 | "\n", 45 | "\tNonconventional Residues Found\n", 46 | "\t------------------------------\n", 47 | "\tEQU --> Charge: UNK, defaulting to 0\n", 48 | "\n", 49 | "You can update charge information for nonconventional residues by running \n", 50 | "\t>>>QMzyme.data.residue_charges.update({'3LETTER_RESNAME':INTEGER_CHARGE}). \n", 51 | "Note your changes will not be stored after you exit your session. It is recommended to only alter the residue_charges dictionary. If you alter the protein_residues dictionary instead that could cause unintended bugs in other modules (TruncationSchemes).\n", 52 | "\n" 53 | ] 54 | } 55 | ], 56 | "source": [ 57 | "# The initial pdb file should be preparred prior (hydrogens must be present).\n", 58 | "\n", 59 | "pdb_file = PDB # here we are using package data. \n", 60 | "model = QMzyme.GenerateModel(PDB)" 61 | ] 62 | }, 63 | { 64 | "cell_type": "markdown", 65 | "id": "10ee63fd-d748-40b4-b7af-c37e0cb9d0f7", 66 | "metadata": {}, 67 | "source": [ 68 | "### Add Ligand Charge" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": 3, 74 | "id": "55b1bfd3-b8e0-4e88-9672-81ae8096ca7f", 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "QMzyme.data.residue_charges.update({'EQU': -1})" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "id": "62fe2630-8b6f-41fc-b01c-ab3721c097b2", 84 | "metadata": {}, 85 | "source": [ 86 | "### Designate Ligand Region " 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": 4, 92 | "id": "440699e9-b9c2-419a-be91-21e7f35f80cf", 93 | "metadata": {}, 94 | "outputs": [ 95 | { 96 | "name": "stdout", 97 | "output_type": "stream", 98 | "text": [ 99 | "Ligand region: \n" 100 | ] 101 | } 102 | ], 103 | "source": [ 104 | "model.set_region(name='EQU', selection='resid 263 and resname EQU')\n", 105 | "print(\"Ligand region: \", model.EQU)" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "id": "579483c9-7d49-4888-a755-375c5bde88bc", 111 | "metadata": {}, 112 | "source": [ 113 | "### Build the QM Method" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 5, 119 | "id": "392ab132-4158-4e76-9896-70b56201b76c", 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "# For the purpose of this example we will forgo geometry optimization and only perform a single point energy \n", 124 | "# calculation and population analysis at the level used in the original RESP procedure (J. Phys. Chem. 1993, 97, 40, 10269–1028).\n", 125 | "\n", 126 | "qm_method = QMzyme.QM_Method(\n", 127 | " basis_set='6-31G*', \n", 128 | " functional='HF', \n", 129 | " qm_input='SCF=Tight Pop=MK IOp(6/33=2,6/42=6,6/43=20)', \n", 130 | " program='gaussian'\n", 131 | ")" 132 | ] 133 | }, 134 | { 135 | "cell_type": "markdown", 136 | "id": "27de3960-f346-444a-9793-b5162baef942", 137 | "metadata": {}, 138 | "source": [ 139 | "### Assign QM Method to Region" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": 6, 145 | "id": "b605c89e-d0c4-4538-aae3-dd244945cba4", 146 | "metadata": {}, 147 | "outputs": [ 148 | { 149 | "name": "stdout", 150 | "output_type": "stream", 151 | "text": [ 152 | "QMzymeRegion EQU has an estimated charge of -1.\n" 153 | ] 154 | } 155 | ], 156 | "source": [ 157 | "# Now we assign this method to our QMzymeRegion EQU\n", 158 | "# We also need to specify the charge and multiplicity (mult) because QMzyme currently only guesses charges of standard amino acids.\n", 159 | "\n", 160 | "qm_method.assign_to_region(region=model.EQU)" 161 | ] 162 | }, 163 | { 164 | "cell_type": "markdown", 165 | "id": "d3b093f7-f604-4376-8ff2-ad31f22f3522", 166 | "metadata": {}, 167 | "source": [ 168 | "### Write Calculation Input File" 169 | ] 170 | }, 171 | { 172 | "cell_type": "code", 173 | "execution_count": 7, 174 | "id": "933b4fb0-333d-43c9-b280-e1a21d8935a3", 175 | "metadata": {}, 176 | "outputs": [ 177 | { 178 | "name": "stdout", 179 | "output_type": "stream", 180 | "text": [ 181 | "\n", 182 | "WARNING: model has not been truncated. Resulting model may not be a chemically complete structure (i.e., incomplete atomic valencies due to removed atoms).\n", 183 | "\n", 184 | "File /Users/hrk/git/QMzyme/docs/Examples/QCALC/EQU_resp.com created.\n" 185 | ] 186 | } 187 | ], 188 | "source": [ 189 | "# Back to our Model\n", 190 | "\n", 191 | "# QMzyme will know we only have one region with a calculation method set, \n", 192 | "# so it will logically create the input file for that scenario\n", 193 | "\n", 194 | "model.write_input('EQU_resp')" 195 | ] 196 | } 197 | ], 198 | "metadata": { 199 | "kernelspec": { 200 | "display_name": "Python 3 (ipykernel)", 201 | "language": "python", 202 | "name": "python3" 203 | }, 204 | "language_info": { 205 | "codemirror_mode": { 206 | "name": "ipython", 207 | "version": 3 208 | }, 209 | "file_extension": ".py", 210 | "mimetype": "text/x-python", 211 | "name": "python", 212 | "nbconvert_exporter": "python", 213 | "pygments_lexer": "ipython3", 214 | "version": "3.11.7" 215 | } 216 | }, 217 | "nbformat": 4, 218 | "nbformat_minor": 5 219 | } 220 | -------------------------------------------------------------------------------- /docs/Examples/QM-only Calculation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "3ff2e826-2061-4071-859c-010a1a5fe4e0", 6 | "metadata": {}, 7 | "source": [ 8 | "# QM-only Calculation " 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "dbaa7c4b-20c8-4e04-a7cc-2c2fad8e6999", 14 | "metadata": {}, 15 | "source": [ 16 | "In this example, we will use QMzyme to create a truncated active site composed of residues within 4 Angstroms of the bound ligand. All C-alpha atoms are set to be frozen during geometry optimization.\n", 17 | "\n", 18 | "Classes used in this example:\n", 19 | "\n", 20 | "- [GenerateModel](https://qmzyme.readthedocs.io/en/latest/API/QMzyme.GenerateModel.html)\n", 21 | "\n", 22 | "- [QM_Method](https://qmzyme.readthedocs.io/en/latest/API/QMzyme.CalculateModel.html#qm-treatment)\n", 23 | "\n", 24 | "- [CA_terminal TruncationScheme](https://qmzyme.readthedocs.io/en/latest/API/QMzyme.TruncationSchemes.html#QMzyme.TruncationSchemes.CA_terminal)\n", 25 | "\n", 26 | "- [DistanceCutoff SelectionScheme](https://qmzyme.readthedocs.io/en/latest/API/QMzyme.SelectionSchemes.html#QMzyme.SelectionSchemes.DistanceCutoff)\n" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 7, 32 | "id": "656289a3-2d1c-49a9-aaed-69fcca90e2f5", 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "import QMzyme \n", 37 | "from QMzyme.data import PDB" 38 | ] 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "id": "869ac5af-96c8-4e7a-b505-d7f2d8f663d4", 43 | "metadata": {}, 44 | "source": [ 45 | "#### Initialize Model and Define Regions" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": 2, 51 | "id": "790aeb66-da33-4940-9401-b2bea7758ba2", 52 | "metadata": { 53 | "tags": [] 54 | }, 55 | "outputs": [ 56 | { 57 | "name": "stdout", 58 | "output_type": "stream", 59 | "text": [ 60 | "\n", 61 | "Charge information not present. QMzyme will try to guess region charges based on residue names consistent with AMBER naming conventions (i.e., aspartate: ASP --> Charge: -1, aspartic acid: ASH --> Charge: 0.). See QMzyme.data.residue_charges for the full set.\n", 62 | "\n", 63 | "\tNonconventional Residues Found\n", 64 | "\t------------------------------\n", 65 | "\tEQU --> Charge: UNK, defaulting to 0\n", 66 | "\n", 67 | "You can update charge information for nonconventional residues by running \n", 68 | "\t>>>QMzyme.data.residue_charges.update({'3LETTER_RESNAME':INTEGER_CHARGE}). \n", 69 | "Note your changes will not be stored after you exit your session. It is recommended to only alter the residue_charges dictionary. If you alter the protein_residues dictionary instead that could cause unintended bugs in other modules (TruncationSchemes).\n", 70 | "\n", 71 | "[, ]\n" 72 | ] 73 | } 74 | ], 75 | "source": [ 76 | "model = QMzyme.GenerateModel(PDB)\n", 77 | "\n", 78 | "# add unknown residue charge\n", 79 | "QMzyme.data.residue_charges.update({'EQU': -1})\n", 80 | "\n", 81 | "# set catalytic center\n", 82 | "model.set_catalytic_center(selection='resid 263')\n", 83 | "\n", 84 | "# import selection scheme 'DistanceCutoff' and use it to create 5 Angstrom \n", 85 | "# region around catalytic center.\n", 86 | "from QMzyme.SelectionSchemes import DistanceCutoff\n", 87 | "model.set_region(selection=DistanceCutoff, cutoff=5)\n", 88 | "print(model.regions)" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "id": "8aea48de-fedd-4fdc-b0a0-bdf11f5651f5", 94 | "metadata": {}, 95 | "source": [ 96 | "#### Designate Coordinate Constraints" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": 3, 102 | "id": "8d511ea4-2fc5-44fb-88d7-3998b6385253", 103 | "metadata": {}, 104 | "outputs": [ 105 | { 106 | "name": "stdout", 107 | "output_type": "stream", 108 | "text": [ 109 | "Fixed atoms: [, , , , , , , , , , , , , , , , , , , , , ]\n" 110 | ] 111 | } 112 | ], 113 | "source": [ 114 | "c_alpha_atoms = model.cutoff_5.get_atoms(attribute='name', value='CA')\n", 115 | "model.cutoff_5.set_fixed_atoms(atoms=c_alpha_atoms)\n", 116 | "\n", 117 | "print(\"Fixed atoms: \",model.cutoff_5.get_atoms(attribute='is_fixed', value=True))" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "id": "0452bc54-1da1-4587-a835-03e368cd7c8a", 123 | "metadata": {}, 124 | "source": [ 125 | "#### Build the QM Method and Assign to Region" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": 4, 131 | "id": "7ece80d6-f4de-4c2a-b882-9e974f0ed8a6", 132 | "metadata": {}, 133 | "outputs": [ 134 | { 135 | "name": "stdout", 136 | "output_type": "stream", 137 | "text": [ 138 | "QMzymeRegion cutoff_5 has an estimated charge of -2.\n" 139 | ] 140 | } 141 | ], 142 | "source": [ 143 | "qm_method = QMzyme.QM_Method(\n", 144 | " basis_set='6-31G*', \n", 145 | " functional='wB97X-D3', \n", 146 | " qm_input='OPT FREQ', \n", 147 | " program='orca'\n", 148 | ")\n", 149 | "\n", 150 | "# since we are not specifying the charge in this method below, the method\n", 151 | "# will estimate the charge based on residue naming conventions\n", 152 | "qm_method.assign_to_region(region=model.cutoff_5)\n", 153 | "\n", 154 | "# you would alternatively uncomment and run:\n", 155 | "#qm_method.assign_to_region(region=model.cutoff_5, charge=-2, mult=1)" 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "id": "a97f3e6c-b3e5-4d18-b2dd-6b62964b8ccb", 161 | "metadata": {}, 162 | "source": [ 163 | "#### Truncate Model" 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": 5, 169 | "id": "1ec497de-267b-40ce-8d30-1063e532cd4b", 170 | "metadata": {}, 171 | "outputs": [ 172 | { 173 | "name": "stdout", 174 | "output_type": "stream", 175 | "text": [ 176 | "\n", 177 | "Truncated model has been created and saved to attribute 'truncated' and stored in QMzyme.CalculateModel.calculation under key QM. This model will be used to write the calculation input.\n", 178 | "Truncated model: \n", 179 | "Method details: {'type': 'QM', 'qm_input': '6-31G* wB97X-D3 OPT FREQ', 'basis_set': '6-31G*', 'functional': 'wB97X-D3', 'qm_end': '', 'program': 'orca', 'freeze_atoms': [2, 23, 42, 58, 78, 84, 105, 122, 129, 148, 164, 174, 191, 211, 227, 244, 263, 279, 292, 309, 319, 343], 'mult': 1, 'charge': -2}\n" 180 | ] 181 | } 182 | ], 183 | "source": [ 184 | "model.truncate()\n", 185 | "print(\"Truncated model: \", QMzyme.CalculateModel.calculation['QM'])\n", 186 | "print(\"Method details: \", QMzyme.CalculateModel.calculation['QM'].method)" 187 | ] 188 | }, 189 | { 190 | "cell_type": "markdown", 191 | "id": "4515b22d-ed66-40e0-957a-ddf79aad6460", 192 | "metadata": {}, 193 | "source": [ 194 | "#### Write QM Input File" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 6, 200 | "id": "2e602cab-691f-4e08-8172-2e1d365bef81", 201 | "metadata": {}, 202 | "outputs": [ 203 | { 204 | "name": "stdout", 205 | "output_type": "stream", 206 | "text": [ 207 | "File /Users/hrk/git/QMzyme/docs/Examples/QCALC/cutoff_5_truncated.inp created.\n" 208 | ] 209 | } 210 | ], 211 | "source": [ 212 | "model.write_input()" 213 | ] 214 | } 215 | ], 216 | "metadata": { 217 | "kernelspec": { 218 | "display_name": "Python 3 (ipykernel)", 219 | "language": "python", 220 | "name": "python3" 221 | }, 222 | "language_info": { 223 | "codemirror_mode": { 224 | "name": "ipython", 225 | "version": 3 226 | }, 227 | "file_extension": ".py", 228 | "mimetype": "text/x-python", 229 | "name": "python", 230 | "nbconvert_exporter": "python", 231 | "pygments_lexer": "ipython3", 232 | "version": "3.11.7" 233 | } 234 | }, 235 | "nbformat": 4, 236 | "nbformat_minor": 5 237 | } 238 | -------------------------------------------------------------------------------- /docs/Examples/QMQM2 Calculation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "5058c99d-37e0-4d1d-a60c-30e432c8a4dc", 6 | "metadata": {}, 7 | "source": [ 8 | "# QM/QM2 Calculation\n", 9 | "\n", 10 | "In this example you will define a catalytic center as the bound ligand, residue 263, and a second region containing atoms within 5 Angstroms of the catalytic center. You will then set a high-level QM method for the catalytic center region, and a lower-lever QM (QM2) method to treat the other atoms. All C-alpha atoms will be constrained for optimization.\n", 11 | "\n", 12 | "Classes used in this example:\n", 13 | "\n", 14 | "- [GenerateModel](https://qmzyme.readthedocs.io/en/latest/API/QMzyme.GenerateModel.html)\n", 15 | "\n", 16 | "- [QM_Method](https://qmzyme.readthedocs.io/en/latest/API/QMzyme.CalculateModel.html#qm-treatment)\n", 17 | "\n", 18 | "- [CA_terminal TruncationScheme](https://qmzyme.readthedocs.io/en/latest/API/QMzyme.TruncationSchemes.html#QMzyme.TruncationSchemes.CA_terminal)\n", 19 | "\n", 20 | "- [DistanceCutoff SelectionScheme](https://qmzyme.readthedocs.io/en/latest/API/QMzyme.SelectionSchemes.html#QMzyme.SelectionSchemes.DistanceCutoff)\n" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 5, 26 | "id": "16b959bc-6a16-4a6d-bbb0-afc559117b8e", 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "import QMzyme\n", 31 | "from QMzyme.SelectionSchemes import DistanceCutoff\n", 32 | "from QMzyme.data import PDB" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "id": "83e96410-4b36-40e8-a888-37108addeff5", 38 | "metadata": {}, 39 | "source": [ 40 | "#### Initialize Model and Define Regions" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": 2, 46 | "id": "28163f9d-a3a5-42c4-a9ad-4d52e831522f", 47 | "metadata": {}, 48 | "outputs": [ 49 | { 50 | "name": "stdout", 51 | "output_type": "stream", 52 | "text": [ 53 | "\n", 54 | "Charge information not present. QMzyme will try to guess region charges based on residue names consistent with AMBER naming conventions (i.e., aspartate: ASP --> Charge: -1, aspartic acid: ASH --> Charge: 0.). See QMzyme.data.residue_charges for the full set.\n" 55 | ] 56 | } 57 | ], 58 | "source": [ 59 | "QMzyme.data.residue_charges.update({'EQU': -1})\n", 60 | "model = QMzyme.GenerateModel(PDB)\n", 61 | "model.set_catalytic_center(selection='resid 263')\n", 62 | "model.catalytic_center.set_charge(-1)\n", 63 | "model.set_region(selection=DistanceCutoff, cutoff=5)\n", 64 | "c_alpha_atoms = model.cutoff_5.get_atoms(attribute='name', value='CA')\n", 65 | "model.cutoff_5.set_fixed_atoms(atoms=c_alpha_atoms)" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "id": "4fc7305e-8f8e-488d-ba8e-809272fc0023", 71 | "metadata": {}, 72 | "source": [ 73 | "#### Build the QM Methods and Assign to Regions" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 3, 79 | "id": "0bd5f0a3-16f1-48e8-a4fd-16cc25763c49", 80 | "metadata": {}, 81 | "outputs": [ 82 | { 83 | "name": "stdout", 84 | "output_type": "stream", 85 | "text": [ 86 | "QMzymeRegion cutoff_5 has an estimated charge of -2.\n", 87 | "QMzymeRegion catalytic_center_cutoff_5_combined has an estimated charge of -2.\n", 88 | "\n", 89 | "Truncated model has been created and saved to attribute 'truncated' and stored in QMzyme.CalculateModel.calculation under key QMQM2. This model will be used to write the calculation input.\n" 90 | ] 91 | } 92 | ], 93 | "source": [ 94 | "qm1_method = QMzyme.QM_Method(basis_set='6-31+G**', \n", 95 | " functional='wB97X-D3', \n", 96 | " qm_input='OPT FREQ', \n", 97 | " program='orca')\n", 98 | "\n", 99 | "qm2_method = QMzyme.QM_Method(basis_set='6-31G*', \n", 100 | " functional='wB97X-D3')\n", 101 | "\n", 102 | "qm1_method.assign_to_region(region=model.catalytic_center)\n", 103 | "qm2_method.assign_to_region(region=model.cutoff_5)\n", 104 | "\n", 105 | "model.truncate()" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "id": "6388fb32-fde4-4b71-b5fe-72ee23e2477a", 111 | "metadata": {}, 112 | "source": [ 113 | "#### Write QM Input" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 4, 119 | "id": "3a3082e2-bea2-4cb9-9dc7-ed032a704be7", 120 | "metadata": {}, 121 | "outputs": [ 122 | { 123 | "name": "stdout", 124 | "output_type": "stream", 125 | "text": [ 126 | "File /Users/hrk/git/QMzyme/docs/Examples/QCALC/catalytic_center_cutoff_5_combined_truncated.inp created.\n" 127 | ] 128 | } 129 | ], 130 | "source": [ 131 | "model.write_input()" 132 | ] 133 | } 134 | ], 135 | "metadata": { 136 | "kernelspec": { 137 | "display_name": "Python 3 (ipykernel)", 138 | "language": "python", 139 | "name": "python3" 140 | }, 141 | "language_info": { 142 | "codemirror_mode": { 143 | "name": "ipython", 144 | "version": 3 145 | }, 146 | "file_extension": ".py", 147 | "mimetype": "text/x-python", 148 | "name": "python", 149 | "nbconvert_exporter": "python", 150 | "pygments_lexer": "ipython3", 151 | "version": "3.11.7" 152 | } 153 | }, 154 | "nbformat": 4, 155 | "nbformat_minor": 5 156 | } 157 | -------------------------------------------------------------------------------- /docs/Examples/QMxTB Calculation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "5058c99d-37e0-4d1d-a60c-30e432c8a4dc", 6 | "metadata": {}, 7 | "source": [ 8 | "# QM/xTB Calculation\n", 9 | "\n", 10 | "In this example you will define a catalytic center as the bound ligand, residue 263 (EQU), and a second region containing atoms within 5 Angstroms of the catalytic center using the Selection Scheme DistanceCutoff. All alpha carbons will be constrained for optimization using set_fixed_atoms(), a method of the QMzymeRegion class. You will set a high-level QM method for the catalytic center region, and a lower-lever xTB (XTB) method to treat the larger system. After assigning the calculation methods you will truncated the model using the default Truncation Scheme, AlphaCarbonTerminal.\n", 11 | "\n", 12 | "Classes used in this example:\n", 13 | "\n", 14 | "- [GenerateModel](https://qmzyme.readthedocs.io/en/latest/API/QMzyme.GenerateModel.html)\n", 15 | "\n", 16 | "- [QM_Method](https://qmzyme.readthedocs.io/en/latest/API/QMzyme.CalculateModel.html#qm-treatment)\n", 17 | "\n", 18 | "- [XTB_Method](https://qmzyme.readthedocs.io/en/latest/API/QMzyme.CalculateModel.html#xtb-treatment)\n", 19 | "\n", 20 | "- [CA_terminal TruncationScheme](https://qmzyme.readthedocs.io/en/latest/API/QMzyme.TruncationSchemes.html#QMzyme.TruncationSchemes.CA_terminal)\n", 21 | "\n", 22 | "- [DistanceCutoff SelectionScheme](https://qmzyme.readthedocs.io/en/latest/API/QMzyme.SelectionSchemes.html#QMzyme.SelectionSchemes.DistanceCutoff)\n", 23 | "\n" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 7, 29 | "id": "16b959bc-6a16-4a6d-bbb0-afc559117b8e", 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "import QMzyme\n", 34 | "from QMzyme.SelectionSchemes import DistanceCutoff\n", 35 | "from QMzyme.data import PDB" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "id": "45354723-d13c-4147-a30d-8c42403a38a2", 41 | "metadata": {}, 42 | "source": [ 43 | "#### Initialize Model and Define Regions" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 2, 49 | "id": "28163f9d-a3a5-42c4-a9ad-4d52e831522f", 50 | "metadata": {}, 51 | "outputs": [ 52 | { 53 | "name": "stdout", 54 | "output_type": "stream", 55 | "text": [ 56 | "\n", 57 | "Charge information not present. QMzyme will try to guess region charges based on residue names consistent with AMBER naming conventions (i.e., aspartate: ASP --> Charge: -1, aspartic acid: ASH --> Charge: 0.). See QMzyme.data.residue_charges for the full set.\n" 58 | ] 59 | } 60 | ], 61 | "source": [ 62 | "QMzyme.data.residue_charges.update({'EQU': -1})\n", 63 | "model = QMzyme.GenerateModel(PDB)\n", 64 | "model.set_catalytic_center(selection='resid 263')\n", 65 | "model.catalytic_center.set_charge(-1)\n", 66 | "model.set_region(selection=DistanceCutoff, cutoff=5)\n", 67 | "c_alpha_atoms = model.cutoff_5.get_atoms(attribute='name', value='CA')\n", 68 | "model.cutoff_5.set_fixed_atoms(atoms=c_alpha_atoms)" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "id": "bae33a15-b64b-4ac0-b29f-6bc000d3230f", 74 | "metadata": {}, 75 | "source": [ 76 | "#### Build the QM Method and Assign to Region" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 3, 82 | "id": "0bd5f0a3-16f1-48e8-a4fd-16cc25763c49", 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "qm_method = QMzyme.QM_Method(basis_set='6-31G*', \n", 87 | " functional='wB97X-D3', \n", 88 | " qm_input='OPT FREQ', \n", 89 | " program='orca')\n", 90 | "\n", 91 | "qm_method.assign_to_region(region=model.catalytic_center)" 92 | ] 93 | }, 94 | { 95 | "cell_type": "markdown", 96 | "id": "d11f5045-cf28-492a-98ec-8f404ffcd968", 97 | "metadata": {}, 98 | "source": [ 99 | "#### Initialize the xTB method and Assign to Region" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": 4, 105 | "id": "1686de60-d870-4086-85e6-bd5e90fe3f78", 106 | "metadata": {}, 107 | "outputs": [ 108 | { 109 | "name": "stdout", 110 | "output_type": "stream", 111 | "text": [ 112 | "QMzymeRegion cutoff_5 has an estimated charge of -2.\n" 113 | ] 114 | } 115 | ], 116 | "source": [ 117 | "# no arguments are passed to initialize XTB_METHOD.\n", 118 | "QMzyme.XTB_Method().assign_to_region(region=model.cutoff_5)" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "id": "1bb56eea-48f6-4c36-8dfd-4cc8345ba565", 124 | "metadata": {}, 125 | "source": [ 126 | "#### Truncate the Model" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 5, 132 | "id": "a1819243-606f-4dd7-be14-9f21d4782fc2", 133 | "metadata": {}, 134 | "outputs": [ 135 | { 136 | "name": "stdout", 137 | "output_type": "stream", 138 | "text": [ 139 | "QMzymeRegion catalytic_center_cutoff_5_combined has an estimated charge of -2.\n", 140 | "\n", 141 | "Truncated model has been created and saved to attribute 'truncated' and stored in QMzyme.CalculateModel.calculation under key QMXTB. This model will be used to write the calculation input.\n" 142 | ] 143 | } 144 | ], 145 | "source": [ 146 | "model.truncate()" 147 | ] 148 | }, 149 | { 150 | "cell_type": "markdown", 151 | "id": "53125766-5cc5-480c-88f6-91978858fecc", 152 | "metadata": {}, 153 | "source": [ 154 | "#### Write ORCA File" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 6, 160 | "id": "3a3082e2-bea2-4cb9-9dc7-ed032a704be7", 161 | "metadata": {}, 162 | "outputs": [ 163 | { 164 | "name": "stdout", 165 | "output_type": "stream", 166 | "text": [ 167 | "File /Users/hrk/git/QMzyme/docs/Examples/QCALC/catalytic_center_cutoff_5_combined_truncated.inp created.\n" 168 | ] 169 | } 170 | ], 171 | "source": [ 172 | "model.write_input()" 173 | ] 174 | } 175 | ], 176 | "metadata": { 177 | "kernelspec": { 178 | "display_name": "Python 3 (ipykernel)", 179 | "language": "python", 180 | "name": "python3" 181 | }, 182 | "language_info": { 183 | "codemirror_mode": { 184 | "name": "ipython", 185 | "version": 3 186 | }, 187 | "file_extension": ".py", 188 | "mimetype": "text/x-python", 189 | "name": "python", 190 | "nbconvert_exporter": "python", 191 | "pygments_lexer": "ipython3", 192 | "version": "3.11.7" 193 | } 194 | }, 195 | "nbformat": 4, 196 | "nbformat_minor": 5 197 | } 198 | -------------------------------------------------------------------------------- /docs/Examples/Serialization.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "9e8d0127-5375-4d76-9475-c3dcfbbc7eaa", 6 | "metadata": {}, 7 | "source": [ 8 | "# QMzyme Serialization\n", 9 | "\n", 10 | "In this example we follow the same steps from the [QMQM2 Calculation](https://qmzyme.readthedocs.io/en/latest/Examples/QMQM2%20Calculation.html) example, up until it is time to assign the calculation method. Let's pretend we got to that point and needed to read some literature to decide what level of theory we should use. So we will serialize the QMzymeModel object using the Python library [pickle](https://docs.python.org/3/library/pickle.html) by calling the method store_pickle, take a (hypothetical) break to read the literature, then reload the pickle file to continue." 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "eb17ecaf-3b15-401d-b4e8-75a919f8dc9c", 16 | "metadata": {}, 17 | "source": [ 18 | "#### Define the QM and QM2 regions" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 1, 24 | "id": "501af7b9-3600-46c0-ab42-d3dfd04177a7", 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "name": "stderr", 29 | "output_type": "stream", 30 | "text": [ 31 | "/Users/hrk/anaconda3/envs/qmzyme/lib/python3.11/site-packages/MDAnalysis/topology/tpr/utils.py:51: DeprecationWarning: 'xdrlib' is deprecated and slated for removal in Python 3.13\n", 32 | " import xdrlib\n" 33 | ] 34 | }, 35 | { 36 | "name": "stdout", 37 | "output_type": "stream", 38 | "text": [ 39 | "\n", 40 | "Charge information not present. QMzyme will try to guess region charges based on residue names consistent with AMBER naming conventions (i.e., aspartate: ASP --> Charge: -1, aspartic acid: ASH --> Charge: 0.). See QMzyme.data.residue_charges for the full set.\n" 41 | ] 42 | } 43 | ], 44 | "source": [ 45 | "import QMzyme\n", 46 | "from QMzyme.SelectionSchemes import DistanceCutoff\n", 47 | "from QMzyme.data import PDB\n", 48 | "\n", 49 | "QMzyme.data.residue_charges.update({'EQU': -1})\n", 50 | "model = QMzyme.GenerateModel(PDB)\n", 51 | "model.set_catalytic_center(selection='resid 263')\n", 52 | "model.catalytic_center.set_charge(-1)\n", 53 | "model.set_region(selection=DistanceCutoff, cutoff=5)\n", 54 | "c_alpha_atoms = model.cutoff_5.get_atoms(attribute='name', value='CA')\n", 55 | "model.cutoff_5.set_fixed_atoms(atoms=c_alpha_atoms)" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "id": "647c7ec9-24be-401f-928b-fbfb198f2136", 61 | "metadata": {}, 62 | "source": [ 63 | "#### Serialize QMzymeModel object \n", 64 | "The pickle file will by default be named after the QMzymeModel.name attribute, which by default is the base name of the file originally used to initialize the model. You can also specify a filename by passing the argument 'filename'. " 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 2, 70 | "id": "01873b56-2610-4322-a080-e0b76485b3f6", 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "model.store_pickle()" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "id": "25a8b18e-0117-45bc-9e42-4f493fa1ce7f", 80 | "metadata": {}, 81 | "source": [ 82 | "#### Upload serialized QMzymeModel object" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 3, 88 | "id": "addf5acf-8e79-4095-866d-dfac8ad31a1b", 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "test = QMzyme.GenerateModel(pickle_file=f'{model.name}.pkl')" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "id": "e9e5ce71-7c8b-4f47-9f15-d34dabea04a4", 98 | "metadata": {}, 99 | "source": [ 100 | "#### Define and assign calculation methods" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": 4, 106 | "id": "1ef41e9c-05bc-46c9-b1a1-7299025d19e8", 107 | "metadata": {}, 108 | "outputs": [ 109 | { 110 | "name": "stdout", 111 | "output_type": "stream", 112 | "text": [ 113 | "QMzymeRegion cutoff_5 has an estimated charge of -2.\n", 114 | "QMzymeRegion catalytic_center_cutoff_5_combined has an estimated charge of -2.\n", 115 | "\n", 116 | "Truncated model has been created and saved to attribute 'truncated' and stored in QMzyme.CalculateModel.calculation under key QMQM2. This model will be used to write the calculation input.\n" 117 | ] 118 | } 119 | ], 120 | "source": [ 121 | "qm1_method = QMzyme.QM_Method(basis_set='6-31+G**', \n", 122 | " functional='wB97X-D3', \n", 123 | " qm_input='OPT FREQ', \n", 124 | " program='orca')\n", 125 | "\n", 126 | "qm2_method = QMzyme.QM_Method(basis_set='6-31G*', \n", 127 | " functional='wB97X-D3')\n", 128 | "\n", 129 | "qm1_method.assign_to_region(region=model.catalytic_center)\n", 130 | "qm2_method.assign_to_region(region=model.cutoff_5)\n", 131 | "\n", 132 | "model.truncate()" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "id": "7743f422-a7f4-4de1-83f6-4801eba1b91a", 138 | "metadata": {}, 139 | "source": [ 140 | "#### Write calculation input file\n", 141 | "\n", 142 | "The final step now is to use the write_input() method to create the calculation input file. When this method is called, the store_pickle() method is triggered and you will have the corresponding .pkl file for your QMzymeModel. This is especially useful because after your calculation is completed you can then load the calculation results back into the corresponding QMzymeRegion using the QMzymeRegion method store_calculation_results() passing it the calculation file. This method uses the cclib library to parse the output, so any calculation file that cclib can read can be stored to your QMzymeRegion." 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": 5, 148 | "id": "2eb9506a-7939-48f8-a6c0-e0d96c5f9610", 149 | "metadata": {}, 150 | "outputs": [ 151 | { 152 | "name": "stdout", 153 | "output_type": "stream", 154 | "text": [ 155 | "Use of this Writer class requires citing the following: \n", 156 | " \t1. Alegre‐Requena, J. V., Sowndarya S. V., S., Pérez‐Soto, R., Alturaifi, T. M. & Paton, R. S. AQME: Automated quantum mechanical environments for researchers and educators. WIREs Comput Mol Sci 13, e1663 (2023).\n", 157 | "File /Users/hrk/git/QMzyme/docs/Examples/QCALC/catalytic_center_cutoff_5_combined_truncated.inp created.\n" 158 | ] 159 | } 160 | ], 161 | "source": [ 162 | "model.write_input()" 163 | ] 164 | } 165 | ], 166 | "metadata": { 167 | "kernelspec": { 168 | "display_name": "Python 3 (ipykernel)", 169 | "language": "python", 170 | "name": "python3" 171 | }, 172 | "language_info": { 173 | "codemirror_mode": { 174 | "name": "ipython", 175 | "version": 3 176 | }, 177 | "file_extension": ".py", 178 | "mimetype": "text/x-python", 179 | "name": "python", 180 | "nbconvert_exporter": "python", 181 | "pygments_lexer": "ipython3", 182 | "version": "3.11.7" 183 | } 184 | }, 185 | "nbformat": 4, 186 | "nbformat_minor": 5 187 | } 188 | -------------------------------------------------------------------------------- /docs/Examples/index.rst: -------------------------------------------------------------------------------- 1 | 2 | Example Workflows 3 | ------------------- 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | Getting_Started 9 | QM-only Calculation 10 | QMQM2 Calculation 11 | QMxTB Calculation 12 | Ligand Parameterization 13 | Working with Trajectories 14 | Serialization 15 | 16 | 17 | Cookbook 18 | ------------------- 19 | 20 | *In progress. Please come back later to see what has been added.* 21 | 22 | A collection of "recipes" to perform specific tasks in QMzyme. 23 | 24 | .. toctree:: 25 | :maxdepth: 2 26 | 27 | -------------------------------------------------------------------------------- /docs/Images/QMzyme_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hklem/QMzyme/29cb26f2114fe7df71fc9b0985c734b69374a8b7/docs/Images/QMzyme_logo.png -------------------------------------------------------------------------------- /docs/Images/QMzyme_schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hklem/QMzyme/29cb26f2114fe7df71fc9b0985c734b69374a8b7/docs/Images/QMzyme_schema.png -------------------------------------------------------------------------------- /docs/Images/all_alpha_carbon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hklem/QMzyme/29cb26f2114fe7df71fc9b0985c734b69374a8b7/docs/Images/all_alpha_carbon.png -------------------------------------------------------------------------------- /docs/Images/terminal_alpha_carbon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hklem/QMzyme/29cb26f2114fe7df71fc9b0985c734b69374a8b7/docs/Images/terminal_alpha_carbon.png -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/Quickstart/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ================================================ 3 | 4 | Requirements 5 | --------------- 6 | *These libraries are installed during pip installation.* 7 | 8 | * Python >= 3.11 (older versions might work too, but testing is done with 3.11) 9 | * NumPy 10 | * `aqme `_ 11 | * `MDAnalysis `_ 12 | 13 | Source Code (recommended) 14 | -------------------------- 15 | The source code is available from https://github.com/hklem/QMzyme. 16 | 17 | .. code-block:: bash 18 | 19 | git clone https://github.com/hklem/QMzyme.git 20 | cd QMzyme 21 | 22 | # The next 2 commands are optional, but recommended 23 | conda create -n qmzyme "python==3.11" 24 | conda activate qmzyme 25 | 26 | pip install -e . # the -e signifies developer install 27 | 28 | # If you want to run tests you can install the required dependencies by executing: 29 | pip install 'QMzyme[test]' 30 | 31 | 32 | From PyPi 33 | ----------- 34 | The data files used in the Tutorials and Cookbook documentation 35 | section may not be included if you do the pip install. You can 36 | download the data files as you wish directly from the 37 | `GitHub repo `_. 38 | 39 | .. code-block:: bash 40 | 41 | pip install QMzyme #NOTE the PyPi distribution still under development. User interface may break 42 | -------------------------------------------------------------------------------- /docs/README.rst: -------------------------------------------------------------------------------- 1 | ================================================ 2 | QMzyme 3 | ================================================ 4 | 5 | .. contents:: 6 | :local: 7 | 8 | 9 | Introduction 10 | ================================================ 11 | 12 | .. introduction-start 13 | 14 | QMzyme is a Python toolkit to facilitate (quantum mechanical) QM-based enzyme 15 | calculations. The GenerateModel module guides the process of generating 16 | QM-calculation ready truncated or partitioned enzyme models. The code 17 | framework can accept any input files MDAnalysis would accept to formulate an 18 | MDAnalysis Universe object. The QMzyme framework works with MDAnalysis modules 19 | to create more dynamic QMzyme objects: QMzymeAtom, QMzymeResidue, QMzymeRegion and 20 | QMzymeModel. The QMzymeModel is an abstraction of a molecular system, such as a real enzyme. 21 | Its utility comes from the ability to perform calculations on it. The calculation 22 | results will (ideally) be validatable through comparison to experiment, and 23 | (hopefully) provide new chemical or methodological insights. 24 | 25 | .. introduction-end 26 | 27 | 28 | .. main_modules-start 29 | Main Modules 30 | =============== 31 | 32 | GenerateModel 33 | ---------------- 34 | 35 | Module where the user introduces the starting structure and makes decisions on 36 | how the QMzymeModel should be constructed by defining one or more regions. The 37 | GenerateModel class allows a user to define/select regions. Calculation details 38 | are assigned at the QMzymeRegion level. 39 | 40 | 41 | CalculateModel 42 | ---------------- 43 | 44 | Module where the user defines how a region will be treated in a calculation. 45 | The CalculateModel classes allow the user to declare calculation-level attributes 46 | for a QMzymeRegion instance. 47 | 48 | 49 | 50 | .. main_modules-end 51 | 52 | .. data_structures-start 53 | Data Structures 54 | =================== 55 | 56 | QMzymeModel 57 | ------------- 58 | 59 | Composed of QMzymeRegion objects. 60 | 61 | QMzymeRegion 62 | ------------- 63 | 64 | Composed of QMzymeAtom and QMzymeResidue objects. 65 | 66 | QMzymeResidue 67 | --------------- 68 | 69 | Composed of QMzymeAtom objects. 70 | 71 | QMzymeAtom 72 | ------------ 73 | 74 | The foundational unit that all other QMzyme objects are built from. 75 | 76 | .. data_structures-end 77 | 78 | .. installation-start 79 | Installation 80 | ================================================ 81 | 82 | .. code-block:: bash 83 | 84 | pip install QMzyme (distribution under development.) 85 | 86 | .. installation-end 87 | 88 | .. requirements-start 89 | Requirements 90 | ================================================ 91 | 92 | * Python >= 3.11 93 | * NumPy 94 | * `aqme `_ 95 | * MDAnalysis 96 | 97 | *These libraries are installed during the pip installation.* 98 | 99 | .. requirements-end 100 | 101 | 102 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Path setup -------------------------------------------------------------- 7 | 8 | # If extensions (or modules to document with autodoc) are in another directory, 9 | # add these directories to sys.path here. If the directory is relative to the 10 | # documentation root, use os.path.abspath to make it absolute, like shown here. 11 | # 12 | import os 13 | import sys 14 | import datetime 15 | # Ensure that modules can be imported without installing aqme 16 | sys.path.insert(0, os.path.abspath('..')) 17 | 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 22 | 23 | now = datetime.datetime.now() 24 | project = 'QMzyme-documentation' 25 | copyright = f'{now.year}, Heidi Klem' 26 | author = 'Heidi Klem' 27 | packageversion = __import__('QMzyme').__version__ 28 | release = packageversion 29 | 30 | # -- General configuration --------------------------------------------------- 31 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 32 | 33 | extensions = ['sphinx.ext.autodoc', 'nbsphinx'] 34 | 35 | templates_path = ['_templates'] 36 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 37 | 38 | # Disable smartquotes which might transform '--' into a different character 39 | smartquotes = False 40 | 41 | # -- Options for HTML output ------------------------------------------------- 42 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 43 | 44 | html_theme = 'sphinx_rtd_theme' 45 | html_static_path = ['_static'] 46 | autoclass_content = 'both' 47 | toc_object_entries = False 48 | autodoc_member_order = 'bysource' 49 | 50 | # Custom sidebar templates, maps document names to template names. 51 | # alabaster sidebars 52 | html_sidebars = { 53 | '**': [ 54 | 'about.html', 55 | 'navigation.html', 56 | 'relations.html', 57 | 'searchbox.html', 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. |logo| image:: Images/QMzyme_logo.png 2 | :width: 300 3 | 4 | |logo| 5 | 6 | :QMzyme version: |release| 7 | .. 8 | :Last updated: |today| 9 | 10 | .. note:: 11 | QMzyme is in a developmental stage. 12 | Please note the user interface may change! 13 | The first API version with guaranteed stability 14 | will be released as version 1.0.0 (QMzyme==1.0.0) on PyPi (pip). 15 | 16 | ============================================= 17 | Introduction 18 | ============================================= 19 | 20 | 21 | **QMzyme** is a Python toolkit to facilitate (quantum mechanical) QM-based enzyme 22 | calculations. The :class:`~QMzyme.GenerateModel.GenerateModel` class guides the process of generating 23 | QM-calculation ready truncated or partitioned structures. The code 24 | framework can accept all input files that `MDAnalysis `_ accepts to create an 25 | MDAnalysis `Universe `_ object. The QMzyme framework works with MDAnalysis modules 26 | to create more dynamic QMzyme data structures: :class:`~QMzyme.QMzymeAtom.QMzymeAtom`, 27 | :class:`~QMzyme.QMzymeRegion.QMzymeResidue`, :class:`~QMzyme.QMzymeRegion.QMzymeRegion`, and 28 | :class:`~QMzyme.QMzymeModel.QMzymeModel`. QMzymeModel is an abstraction of a molecular system, such as a real enzyme, that 29 | comprises at least one QMzymeRegion. Its utility comes from the ability to perform calculations on it. 30 | Calculation methods (think Hamiltonians) are assigned at the QMzymeRegion level. QMzyme, as its namesake suggests, 31 | takes a QM-centric perspective. Therefore, the CalculateModel and Writers modules are designed for compatibility with QM-focused 32 | software, rather than MD-focused software, even though many MD software support QM program interfacing. The calculation 33 | results will (ideally) be validatable through comparison to experiment, and (hopefully) provide new chemical or methodological insights. 34 | 35 | If you have ideas or suggestions on how to improve QMzyme please do not hesitate to engage on the `QMzyme Ideas GitHub Project space `_, or contribute directly by `forking the repository and submitting a pull request `_. Questions can be directed via email to 36 | heidi.klem{AT}nist{dot}gov. 37 | 38 | 39 | .. toctree:: 40 | :maxdepth: 2 41 | 42 | Quickstart/installation 43 | 44 | .. toctree:: 45 | :maxdepth: 2 46 | :caption: Examples/Tutorials 47 | 48 | Examples/index 49 | 50 | .. toctree:: 51 | :maxdepth: 2 52 | :caption: Code Documentation 53 | 54 | API/API_reference 55 | 56 | .. toctree:: 57 | :maxdepth: 2 58 | :caption: Contributing to QMzyme! 59 | 60 | Contributing/CODE_OF_CONDUCT 61 | Contributing/suggestions 62 | Contributing/general 63 | Contributing/selection_schemes 64 | Contributing/truncation_schemes 65 | Contributing/calculation_methods 66 | Contributing/writers 67 | 68 | 69 | .. Hide the contents from the front page because they are already in 70 | .. the side bar in the Alabaster sphinx style; requires Alabaster 71 | .. config sidebar_includehidden=True (default) 72 | 73 | .. Contents 74 | .. ======== 75 | .. toctree:: 76 | :maxdepth: 4 77 | :caption: Documentation 78 | :numbered: 79 | :hidden: 80 | 81 | 82 | Indices and tables 83 | ================== 84 | 85 | * :ref:`genindex` 86 | * :ref:`search` 87 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/references.rst: -------------------------------------------------------------------------------- 1 | .. The references are accessible globally; you can cite these papers anywhere 2 | .. in the docs. 3 | 4 | .. _references: 5 | 6 | ============ 7 | References 8 | ============ 9 | 10 | QMzyme has not been formally published. Please refer back here for the proper citation once it has. 11 | 12 | When using QMzyme in published work, please [cite MDAnalysis accordingly](https://www.mdanalysis.org/pages/citations/): 13 | 14 | * R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler, D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein. MDAnalysis: A Python package for the rapid analysis of molecular dynamics simulations. In S. Benthall and S. Rostrup, editors, Proceedings of the 15th Python in Science Conference, pages 98-105, Austin, TX, 2016. SciPy, doi:10.25080/majora-629e541a-00e. 15 | 16 | * N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. J. Comput. Chem. 32 (2011), 2319-2327, doi:10.1002/jcc.21787. PMCID:PMC3144279 17 | 18 | If you use QMzyme to write QM software calculation input files, please include this citation for [AQME](https://aqme.readthedocs.io/en/latest/): 19 | 20 | * Alegre-Requena, J. V.; Sowndarya, S.; Pérez-Soto, R.; Alturaifi, T.; Paton, R. AQME: Automated Quantum Mechanical Environments for Researchers and Educators. Wiley Interdiscip. Rev. Comput. Mol. Sci. 2023, 13, e1663. (DOI: 10.1002/wcms.1663). 21 | 22 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | nbsphinx 3 | sphinx-rtd-theme 4 | sphinx-autoapi 5 | sphinx-autodocgen 6 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hklem/QMzyme/29cb26f2114fe7df71fc9b0985c734b69374a8b7/logo.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0", "versioningit~=2.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | # Self-descriptive entries which should always be present 6 | # https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ 7 | [project] 8 | name = "QMzyme" 9 | description = "QM-based enzyme model generation and validation." 10 | dynamic = ["version"] 11 | readme = "README.md" 12 | authors = [ 13 | { name = "Heidi Klem", email = "heidi.klem@nist.gov" } 14 | ] 15 | license = { text = "MIT" } 16 | # See https://pypi.org/classifiers/ 17 | classifiers = [ 18 | "License :: OSI Approved :: MIT License", 19 | "Programming Language :: Python :: 3", 20 | ] 21 | requires-python = ">=3.11" 22 | # Declare any run-time dependencies that should be installed with the package. 23 | dependencies = [ 24 | "importlib_resources", 25 | "MDAnalysis", 26 | "jupyterlab", #not super necessary, but helpful to follow along with examples 27 | "PyYAML", #aqme dependency 28 | #"aqme", #bc lots of dependencies 29 | "rdkit-pypi", # dependency of aqme but doesn't get installed with pip install aqme 30 | "openbabel-wheel" #this works! And you don't have to add conda install -c conda-forge openbabel to config.yml, which makes testing faster (by like 30%) 31 | #"openbabel" #doesn't work to install this way... need to do conda install -c conda-forge openbabel in config.yml 32 | ] 33 | 34 | # Update the urls once the hosting is set up. 35 | [project.urls] 36 | "Source" = "https://github.com/hklem/QMzyme/" 37 | "Documentation" = "https://qmzyme.readthedocs.io/" 38 | 39 | [project.optional-dependencies] 40 | test = [ 41 | "pytest", 42 | "pytest-cov" 43 | ] 44 | 45 | 46 | [tool.setuptools] 47 | # This subkey is a beta stage development and keys may change in the future, see https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html for more details 48 | # 49 | # As of version 0.971, mypy does not support type checking of installed zipped 50 | # packages (because it does not actually import the Python packages). 51 | # We declare the package not-zip-safe so that our type hints are also available 52 | # when checking client code that uses our (installed) package. 53 | # Ref: 54 | # https://mypy.readthedocs.io/en/stable/installed_packages.html?highlight=zip#using-installed-packages-with-mypy-pep-561 55 | zip-safe = false 56 | # Let setuptools discover the package in the current directory, 57 | # but be explicit about non-Python files. 58 | # See also: 59 | # https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html#setuptools-specific-configuration 60 | # Note that behavior is currently evolving with respect to how to interpret the 61 | # "data" and "tests" subdirectories. As of setuptools 63, both are automatically 62 | # included if namespaces is true (default), even if the package is named explicitly 63 | # (instead of using 'find'). With 'find', the 'tests' subpackage is discovered 64 | # recursively because of its __init__.py file, but the data subdirectory is excluded 65 | # with include-package-data = false and namespaces = false. 66 | 67 | #include-package-data = false 68 | [tool.setuptools.packages.find] 69 | #namespaces = false 70 | where = [".","QMzyme/data"] # use "." and not "QMzyme" so pytest can find tests. 71 | 72 | # Ref https://setuptools.pypa.io/en/latest/userguide/datafiles.html#package-data 73 | [tool.setuptools.package-data] 74 | QMzyme = [ 75 | "py.typed", 76 | "QMzyme/data/*", 77 | ] 78 | 79 | [tool.versioningit] 80 | #default-version = "1+unknown" 81 | default-version = "0.0.7.dev0" 82 | 83 | [tool.versioningit.format] 84 | #distance = "{base_version}+{distance}.{vcs}{rev}" 85 | #dirty = "{base_version}+{distance}.{vcs}{rev}.dirty" 86 | #distance-dirty = "{base_version}+{distance}.{vcs}{rev}.dirty" 87 | distance = "{tag}.post{distance}" 88 | dirty = "{tag}.post{distance}" 89 | distance-dirty = "{tag}.post{distance}" 90 | 91 | [tool.versioningit.vcs] 92 | # The method key: 93 | method = "git" # <- The method name 94 | # Parameters to pass to the method: 95 | match = ["*"] 96 | default-tag = "0.0.7.dev0" 97 | 98 | [tool.versioningit.write] 99 | file = "QMzyme/_version.py" 100 | 101 | [tool.coverage.run] 102 | omit = ["QMzyme/aqme/*", "QMzyme/_version.py"] 103 | 104 | [tool.coverage.report] 105 | exclude_also = [ 106 | # Don't complain about abstract methods, they aren't run: 107 | "@(abc\\.)?abstractmethod", 108 | ] 109 | skip_empty = true 110 | 111 | 112 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # Helper file to handle all configs 2 | 3 | [coverage:run] 4 | # .coveragerc to control coverage.py and pytest-cov 5 | omit = 6 | # Omit the tests 7 | */tests/* 8 | # Omit generated versioneer 9 | QMzyme/_version.py 10 | 11 | [yapf] 12 | # YAPF, in .style.yapf files this shows up as "[style]" header 13 | COLUMN_LIMIT = 119 14 | INDENT_WIDTH = 4 15 | USE_TABS = False 16 | 17 | [flake8] 18 | # Flake8, PyFlakes, etc 19 | max-line-length = 119 20 | 21 | [aliases] 22 | test = pytest 23 | 24 | [options] 25 | #... 26 | install_requires = 27 | importlib-resources 28 | MDAnalysis 29 | cclib 30 | 31 | -------------------------------------------------------------------------------- /tests/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hklem/QMzyme/29cb26f2114fe7df71fc9b0985c734b69374a8b7/tests/.DS_Store -------------------------------------------------------------------------------- /tests/test_CA_terminal.py: -------------------------------------------------------------------------------- 1 | 2 | from QMzyme.GenerateModel import GenerateModel 3 | from QMzyme.TruncationSchemes import * 4 | from QMzyme.truncation_utils import * 5 | import pytest 6 | from QMzyme.data import PDB, protein_residues 7 | 8 | def test_CA_terminal(): 9 | model = GenerateModel(PDB) 10 | model.set_region(name='region', selection='resid 263 or resid 16 or resid 17 or resid 57') 11 | truncation = TerminalAlphaCarbon(region=model.region, name=None) 12 | truncate = truncation.return_region() 13 | truncate.write('truncated_test') 14 | 15 | 16 | model.set_region(name='region1', selection='resid 263 or resid 16') 17 | model.set_region(name='region2', selection='resid 17 or resid 57') 18 | combined = model.region1.combine(model.region2) 19 | truncation = TerminalAlphaCarbon(region=combined, name=None) 20 | truncated_combined = truncation.return_region() 21 | truncated_combined.write('truncated_combined_test') 22 | 23 | assert truncate.n_atoms == truncated_combined.n_atoms 24 | for atom in truncate.atoms: 25 | assert atom.name == truncated_combined.get_atom(atom.id).name 26 | 27 | -------------------------------------------------------------------------------- /tests/test_CalculateModel.py: -------------------------------------------------------------------------------- 1 | from QMzyme.CalculateModel import QM_Method 2 | from QMzyme.GenerateModel import GenerateModel 3 | #from importlib_resources import files 4 | from QMzyme.data import PDB 5 | import os 6 | import shutil 7 | import pytest 8 | 9 | original_contents = os.listdir() 10 | 11 | def restore_directory(): 12 | for name in os.listdir(): 13 | if name not in original_contents: 14 | try: 15 | os.remove(name) 16 | except: 17 | shutil.rmtree(name) 18 | 19 | @pytest.mark.parametrize( 20 | "Test, program",[ 21 | ('Gaussian Test', 'gaussian'), 22 | ('Orca Test', 'orca'), 23 | ] 24 | ) 25 | def test_QM_Method(Test, program): 26 | model = GenerateModel(PDB) 27 | region_name = 'cutoff_3' 28 | model.set_region(name=region_name, selection='byres around 3 resid 263') 29 | # check basis_set info is set for all atoms 30 | bs1 = '6-31g(d)' 31 | ids = model.cutoff_3.get_ids('name', 'CA') 32 | model.cutoff_3.set_fixed_atoms(ids) 33 | region, basis_set, functional = model.cutoff_3, '6-31G*', 'wB97X-D3' 34 | qm_method = QM_Method( 35 | basis_set=basis_set, 36 | functional=functional, 37 | qm_input='OPT FREQ', 38 | program=program 39 | ) 40 | region.set_charge(-100) 41 | qm_method.assign_to_region(region=region) 42 | 43 | # check region method: 44 | assert hasattr(region, "method") 45 | assert region.method["basis_set"] == basis_set 46 | assert region.method["functional"] == functional 47 | assert region.method["charge"] == -100 48 | assert region.method["mult"] == 1 49 | assert region.method["freeze_atoms"] == region.get_ix_array_from_ids(ids) 50 | 51 | model.write_input() 52 | restore_directory() 53 | -------------------------------------------------------------------------------- /tests/test_E2E.py: -------------------------------------------------------------------------------- 1 | """ 2 | End to end tests designed to match up with documentation examples. 3 | """ 4 | from QMzyme.GenerateModel import GenerateModel 5 | from QMzyme.CalculateModel import CalculateModel 6 | from QMzyme.SelectionSchemes import * 7 | from QMzyme.TruncationSchemes import * 8 | from QMzyme.data import PDB, PQR, protein_residues, residue_charges 9 | import os 10 | import shutil 11 | import pytest 12 | 13 | original_contents = os.listdir() 14 | 15 | def restore_directory(): 16 | for name in os.listdir(): 17 | if name not in original_contents: 18 | try: 19 | os.remove(name) 20 | except: 21 | shutil.rmtree(name) 22 | 23 | 24 | def test_QM_only_calculation(): 25 | from QMzyme.CalculateModel import QM_Method 26 | # Initialize model and define regions 27 | residue_charges.update({'EQU':-1}) 28 | model = GenerateModel(PDB) 29 | model.set_catalytic_center(selection='resid 263') 30 | model.set_region(selection=DistanceCutoff, cutoff=5) 31 | assert model.n_regions == 2 32 | assert len(model.regions) == 2 33 | assert 'catalytic_center' in model.get_region_names() 34 | assert 'cutoff_5' in model.get_region_names() 35 | 36 | # Designate coordinate constraints 37 | c_alpha_atoms = model.cutoff_5.get_atoms(attribute='name', value='CA') 38 | for atom in model.cutoff_5.atoms: 39 | assert atom.is_fixed == False 40 | model.cutoff_5.set_fixed_atoms(atoms=c_alpha_atoms) 41 | for atom in c_alpha_atoms: 42 | assert atom.is_fixed == True 43 | 44 | # Build the QM Method and Assign to Region 45 | qm_method = QM_Method( 46 | basis_set='6-31G*', 47 | functional='wB97X-D3', 48 | qm_input='OPT FREQ', 49 | program='orca' 50 | ) 51 | 52 | qm_method.assign_to_region(region=model.cutoff_5) 53 | assert model.cutoff_5.method["qm_input"] == '6-31G* wB97X-D3 OPT FREQ' 54 | assert model.cutoff_5.charge == -2 55 | for atom in c_alpha_atoms: 56 | assert CalculateModel.calculation['QM'].get_atom(id=atom.id).is_fixed == True 57 | 58 | # Truncate Model 59 | print(CalculateModel.calculation) 60 | print(CalculateModel.calc_type) 61 | model.truncate() 62 | 63 | # Write QM Input File 64 | model.write_input() 65 | assert 'QCALC' in os.listdir() 66 | assert 'cutoff_5_truncated.inp' in os.listdir('QCALC') 67 | with open(os.path.join('QCALC', 'cutoff_5_truncated.inp'), 'r') as f: 68 | file = f.read() 69 | qm_input = file[file.find('!')+1:].split('\n')[0] 70 | assert qm_input.endswith(model.cutoff_5.method["qm_input"]) 71 | assert f'%geom Constraints' in file 72 | assert f'%QMMM' not in file 73 | restore_directory() 74 | assert 'QCALC' not in os.listdir() 75 | 76 | @pytest.mark.parametrize( 77 | "qm1_dict, qm2_dict",[ 78 | ({'basis_set': '6-31+G**', 79 | "functional": 'wB97X-D3', 80 | "qm_input": 'OPT FREQ', 81 | "program": 'orca'}, 82 | {'basis_set': '6-31G*', 83 | "functional": 'wB97X-D3'}), 84 | ] 85 | ) 86 | def test_QMQM2_calculation(qm1_dict, qm2_dict): 87 | from QMzyme.CalculateModel import QM_Method 88 | residue_charges.update({'EQU':-1}) 89 | model = GenerateModel(PDB) 90 | model.set_catalytic_center(selection='resid 263') 91 | model.catalytic_center.set_charge(-1) 92 | assert model.catalytic_center.charge == -1 93 | model.set_region(selection=DistanceCutoff, cutoff=5) 94 | c_alpha_atoms = model.cutoff_5.get_atoms(attribute='name', value='CA') 95 | model.cutoff_5.set_fixed_atoms(atoms=c_alpha_atoms) 96 | 97 | qm1_method = QM_Method(**qm1_dict) 98 | qm2_method = QM_Method(**qm2_dict) 99 | print("Before any method assigning") 100 | qm1_method.assign_to_region(region=model.catalytic_center) 101 | print("After first method calc type: ",CalculateModel.calc_type) 102 | qm2_method.assign_to_region(region=model.cutoff_5) 103 | print("After second method calc type: ",CalculateModel.calc_type) 104 | for attr, val in qm1_dict.items(): 105 | if attr == 'qm_input': 106 | continue 107 | assert model.catalytic_center.method[attr] == val 108 | for attr, val in qm2_dict.items(): 109 | if attr == 'qm_input': 110 | continue 111 | assert model.cutoff_5.method[attr] == val 112 | 113 | # Truncate Model 114 | #model.truncate() 115 | model.write_input("qmqm2") 116 | assert 'qmqm2.inp' in os.listdir('QCALC') 117 | with open(os.path.join('QCALC', 'qmqm2.inp'), 'r') as f: 118 | file = f.readlines() 119 | assert 'QM/QM2' in file[3] 120 | for component in model.catalytic_center.method['qm_input'].split('%QMMM')[0].split(): 121 | assert component in file[3] 122 | 123 | assert '%QMMM' in file[4] 124 | assert file[4].strip() == "%QMMM QM2CUSTOMMETHOD '6-31G* wB97X-D3'" 125 | assert file[5].strip() == "QMATOMS {"+"360:396} END" 126 | assert file[6].strip() == "Charge_Total -2 END" 127 | assert file[7].strip() == f"%geom Constraints" 128 | 129 | restore_directory() 130 | 131 | 132 | def test_QMXTB_calculation(): 133 | from QMzyme.CalculateModel import CalculateModel, QM_Method, XTB_Method 134 | residue_charges.update({'EQU':-1}) 135 | model = GenerateModel(PDB) 136 | model.set_catalytic_center(selection='resid 263') 137 | model.set_region(selection=DistanceCutoff, cutoff=5) 138 | c_alpha_atoms = model.cutoff_5.get_atoms(attribute='name', value='CA') 139 | model.cutoff_5.set_fixed_atoms(atoms=c_alpha_atoms) 140 | 141 | # check that warning is raised if a non-QM method is set before a QM method is set. 142 | # with pytest.raises(UserWarning): 143 | # assert XTB_Method().assign_to_region(region=model.cutoff_5) 144 | 145 | qm_method = QM_Method(basis_set='6-31G*', 146 | functional='wB97X-D3', 147 | qm_input='OPT FREQ', 148 | program='orca') 149 | 150 | qm_method.assign_to_region(region=model.catalytic_center) 151 | XTB_Method().assign_to_region(region=model.cutoff_5) 152 | model.truncate() 153 | model.write_input("test_qmxtb") 154 | assert CalculateModel.calculation['QMXTB'].n_atoms != model.cutoff_5.n_atoms 155 | with open(os.path.join('QCALC', 'test_qmxtb.inp'), 'r') as f: 156 | file = f.readlines() 157 | 158 | assert file[3].strip() == "! QM/XTB 6-31G* wB97X-D3 OPT FREQ" 159 | assert file[4].strip() == f"%QMMM" 160 | assert file[5].strip() == "QMATOMS {"+"324:360} END" 161 | assert file[6].strip() == "Charge_Total -2 END" 162 | assert file[32].strip() == "* xyz -1 1" 163 | 164 | # qm_input = file[file.find('!')+1:].split('\n')[0] 165 | # assert qm_input.strip().startswith('QM/XTB') 166 | 167 | # write again to make sure the method gets reset 168 | model.write_input("test_qmxtb") 169 | with open(os.path.join('QCALC', 'test_qmxtb.inp'), 'r') as f: 170 | file = f.read() 171 | 172 | assert len(file.split('%QMMM')) == 2 173 | assert len(file.split('QM/XTB')) == 2 174 | 175 | restore_directory() 176 | 177 | def test2_QM_XTB_calculation(): 178 | from QMzyme.CalculateModel import QM_Method, XTB_Method 179 | qm_method = QM_Method( 180 | basis_set='6-31G*', 181 | functional='wB97X-D3', 182 | qm_input='OPT FREQ', 183 | program='orca' 184 | ) 185 | 186 | # Initialize model and define regions 187 | residue_charges.update({'EQU':-1}) 188 | model = GenerateModel(PQR) 189 | #model = GenerateModel(PDB) 190 | 191 | model.set_catalytic_center(selection='resid 263 or resid 16') 192 | qm_method.assign_to_region(region=model.catalytic_center) 193 | model.set_region(name='xtb_region', selection=DistanceCutoff, cutoff=3) 194 | model.set_region(name='ile17', selection='resid 17') 195 | xtb_region = model.xtb_region.combine(model.ile17) 196 | c_alpha_atoms = xtb_region.get_atoms(attribute='name', value='CA') 197 | xtb_region.set_fixed_atoms(atoms=c_alpha_atoms) 198 | xtb_method = XTB_Method().assign_to_region(region=xtb_region) 199 | with pytest.raises(UserWarning): 200 | # you cannot add a region with the same name 201 | model.set_region(name='xtb_region', selection=xtb_region) 202 | model.remove_region('xtb_region') # replacing this region 203 | # need to add to model so combined regions can be truncated 204 | model.set_region(name='xtb_region', selection=xtb_region) 205 | 206 | model.truncate() 207 | model.write_input('test2_qmxtb') 208 | 209 | with open(os.path.join('QCALC', 'test2_qmxtb.inp'), 'r') as f: 210 | file = f.readlines() 211 | 212 | assert file[3].strip() == "! QM/XTB 6-31G* wB97X-D3 OPT FREQ" 213 | assert file[4].strip() == f"%QMMM" 214 | assert file[5].strip() == "QMATOMS {"+"57:77} {413:449"+"} END" 215 | assert file[6].strip() == "Charge_Total -1 END" 216 | 217 | # check writing of pymol visualization script 218 | model.pymol_visualize() 219 | assert f'QMzymeModel_{model.name}_visualize.py' in os.listdir() 220 | -------------------------------------------------------------------------------- /tests/test_GenerateModel.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for the QMzyme GenerateModel.py code. 3 | """ 4 | 5 | # Import package, test suite, and other packages as needed 6 | # Name each function as test_* to be automatically included in test workflow 7 | 8 | from QMzyme.GenerateModel import GenerateModel 9 | import pytest 10 | import numpy as np 11 | import numpy.testing as npt 12 | from QMzyme.RegionBuilder import RegionBuilder 13 | from MDAnalysis.core.universe import Universe 14 | from QMzyme.data import PDB, PQR, DCD 15 | 16 | 17 | @pytest.mark.parametrize( 18 | "init_file, traj_file", 19 | [(PDB, None), 20 | (PQR, DCD),] 21 | ) 22 | def test_init(init_file, traj_file): 23 | if traj_file is not None: 24 | model = GenerateModel(init_file, traj_file) 25 | assert model.name == '1oh0_equ' 26 | model.set_region(name='test', selection='resid 263') 27 | assert hasattr(model.regions[-1].atoms[0], "charge") 28 | frame_0_positions = model.regions[-1].positions 29 | model = GenerateModel(init_file, traj_file, frame=2) 30 | model.set_region(name='test', selection='resid 263') 31 | #assert model.regions[-1].positions != frame_0_positions 32 | with pytest.raises(AssertionError): 33 | npt.assert_array_equal(model.regions[-1].positions, frame_0_positions) 34 | 35 | else: 36 | model = GenerateModel(init_file) 37 | assert model.name == '1oh0' 38 | model.set_region(name='test', selection='resid 263') 39 | assert not hasattr(model.regions[-1].atoms[0], "charge") 40 | assert model.__repr__() == " contains 1 region(s)>" 41 | assert model.universe.__class__ == Universe 42 | assert model.filename == init_file 43 | 44 | def test_set_catalytic_center(selection='resid 263'): 45 | model = GenerateModel(PDB) 46 | model.set_catalytic_center(selection) 47 | assert len(model.regions) == 1 48 | assert model.regions[0].name == 'catalytic_center' 49 | assert model.regions[0].n_atoms == 37 50 | 51 | model.remove_region("catalytic_center") 52 | assert len(model.regions) == 0 53 | 54 | selection_str = 'resid 16 or resid 17' 55 | @pytest.mark.parametrize( 56 | "Test, init_file, region_name, selection", 57 | [('Selection string as input', PDB, 'test', selection_str), 58 | ('MDA AtomGroup as input', PDB, 'test', selection_str), 59 | ('QMzymeRegion as input', PDB, 'test', selection_str),] 60 | ) 61 | def test_set_region(Test, init_file, region_name, selection): 62 | model = GenerateModel(init_file) 63 | if Test == 'Selection string as input': 64 | model.set_region(name=region_name, selection=selection) 65 | elif Test == 'MDA AtomGroup as input': 66 | mda_atomgroup = model.universe.select_atoms(selection) 67 | model.set_region(name=region_name, selection=mda_atomgroup) 68 | elif Test == 'QMzymeRegion as input': 69 | mda_atomgroup = model.universe.select_atoms(selection) 70 | region_builder = RegionBuilder(region_name) 71 | region_builder.init_atom_group(mda_atomgroup) 72 | qmz_region = region_builder.get_region() 73 | model.set_region(name=region_name, selection=qmz_region) 74 | 75 | # check that warning is raised if you try to add a region with the same name of a region that already exists 76 | with pytest.raises(UserWarning): 77 | model.set_region(name=region_name, selection=qmz_region) 78 | 79 | assert len(model.regions) == 1 80 | assert model.regions[0].name == region_name 81 | assert model.regions[0].n_atoms == 40 82 | assert model.regions[0].n_residues == 2 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /tests/test_QMzymeAtom.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for the QMzymeAtom.py code. 3 | """ 4 | 5 | # Import package, test suite, and other packages as needed 6 | # Name each function as test_* to be automatically included in test workflow 7 | 8 | import pytest 9 | from QMzyme.QMzymeAtom import QMzymeAtom 10 | 11 | 12 | atom_dict = { 13 | "name": "CA", 14 | "element": "C", 15 | "position": [1.000, 2.000, 3.000], 16 | "resid": "1", 17 | "resname": "VAL", 18 | "id": 100, 19 | "random_property": "placeholder", 20 | } 21 | 22 | 23 | @pytest.mark.parametrize( 24 | "test_type, atom_input", 25 | [("Testing required args", atom_dict), ("Testing optional args", atom_dict)] 26 | ) 27 | def test_init(test_type, atom_input): 28 | if test_type == "Testing required args": 29 | name = atom_input["name"] 30 | element = atom_input["element"] 31 | position = atom_input["position"] 32 | resid = atom_input["resid"] 33 | resname = atom_input["resname"] 34 | qmz_atom = QMzymeAtom(name, element, position, resid, resname) 35 | 36 | assert qmz_atom.name == name 37 | assert qmz_atom.element == element 38 | assert qmz_atom.position == position 39 | assert qmz_atom.resid == resid 40 | assert qmz_atom.resname == resname 41 | assert str(qmz_atom.__repr__()) == f'' 42 | 43 | if test_type == "Testing optional args": 44 | qmz_atom = QMzymeAtom(**atom_dict) 45 | 46 | assert qmz_atom.name == atom_input["name"] 47 | assert qmz_atom.element == atom_input["element"] 48 | assert qmz_atom.position == atom_input["position"] 49 | assert qmz_atom.resid == atom_input["resid"] 50 | assert qmz_atom.resname == atom_input["resname"] 51 | assert qmz_atom.id == atom_input["id"] 52 | assert qmz_atom.random_property == atom_input["random_property"] 53 | assert str(qmz_atom.__repr__()) == f'' 54 | 55 | def test_setters(): 56 | qmz_atom = QMzymeAtom(**atom_dict) 57 | 58 | # name 59 | # with pytest.raises(AttributeError): 60 | # qmz_atom.name = 'F' 61 | assert qmz_atom.name != 'F' 62 | #qmz_atom.set_name('F') 63 | qmz_atom.name = 'F' 64 | assert qmz_atom.name == 'F' 65 | 66 | # element 67 | # with pytest.raises(AttributeError): 68 | # qmz_atom.element = 'P' 69 | assert qmz_atom.element != 'P' 70 | #qmz_atom.set_element('P') 71 | qmz_atom.element = 'P' 72 | assert qmz_atom.element == 'P' 73 | 74 | # region 75 | assert qmz_atom.region is None 76 | with pytest.raises(AttributeError): 77 | qmz_atom.region = 'placeholder' 78 | assert qmz_atom.region != 'placeholder' 79 | qmz_atom._set_region('placeholder') 80 | assert qmz_atom.region == 'placeholder' 81 | 82 | # is_fixed 83 | assert not qmz_atom.is_fixed 84 | qmz_atom.set_fixed() 85 | assert qmz_atom.is_fixed 86 | 87 | # is_point_charge 88 | assert qmz_atom.is_point_charge == False 89 | with pytest.raises(UserWarning): 90 | qmz_atom.set_point_charge() 91 | #assert qmz_atom.is_point_charge 92 | 93 | # is_neighbor 94 | with pytest.raises(AttributeError): 95 | f = qmz_atom.is_neighbor 96 | qmz_atom.set_neighbor() 97 | assert qmz_atom.is_neighbor 98 | 99 | 100 | -------------------------------------------------------------------------------- /tests/test_QMzymeModel.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for the QMzymeModel.py code. 3 | """ 4 | 5 | # Import package, test suite, and other packages as needed 6 | # Name each function as test_* to be automatically included in test workflow 7 | 8 | import pytest 9 | import os 10 | import shutil 11 | from QMzyme.QMzymeModel import QMzymeModel 12 | from QMzyme.RegionBuilder import RegionBuilder 13 | from QMzyme.data import PDB 14 | from QMzyme.MDAnalysisWrapper import init_universe 15 | 16 | 17 | u = init_universe(PDB) 18 | original_contents = os.listdir() 19 | 20 | def restore_directory(): 21 | for name in os.listdir(): 22 | if name not in original_contents: 23 | try: 24 | os.remove(name) 25 | except: 26 | shutil.rmtree(name) 27 | 28 | def test_QMzymeModel(): 29 | 30 | # instantiate model and ensure base attributes are set 31 | model = QMzymeModel(name='1oh0', universe=u) 32 | assert model.name == '1oh0' 33 | assert model.__repr__() == " contains 0 region(s)>" 34 | assert model.filename == PDB 35 | assert model.n_regions == 0 36 | assert hasattr(model, 'universe') 37 | 38 | # build region and add to model 39 | rb1 = RegionBuilder(name='test_region') 40 | rb1.init_atom_group(atom_group=u.select_atoms('resid 263')) 41 | region = rb1.get_region() 42 | model.add_region(region) 43 | 44 | # check model has that region now 45 | assert model.has_region('test_region') 46 | assert model.n_regions == 1 47 | 48 | # check region related methods 49 | assert model.get_region_names() == ['test_region'] 50 | assert model.get_region(region_name='test_region') == region 51 | 52 | # negative test 53 | with pytest.raises(UserWarning): 54 | model.get_region('blah') 55 | 56 | # check writing of pymol visualization script 57 | model.pymol_visualize() 58 | assert f'QMzymeModel_{model.name}_visualize.py' in os.listdir() 59 | 60 | restore_directory() 61 | 62 | 63 | -------------------------------------------------------------------------------- /tests/test_QMzymeRegion.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for the QMzyme RegionBuilder.py ands QMzymeRegion.py code. 3 | """ 4 | 5 | # Import package, test suite, and other packages as needed 6 | # Name each function as test_* to be automatically included in test workflow 7 | 8 | 9 | import numpy as np 10 | import pytest 11 | import QMzyme 12 | from QMzyme.RegionBuilder import RegionBuilder 13 | import MDAnalysis as mda 14 | from QMzyme.data import PDB 15 | 16 | u = mda.Universe(PDB) 17 | atom_group = u.select_atoms('resid 2-5') 18 | id1 = atom_group.atoms[0].id 19 | 20 | def test_RegionBuilder(): 21 | rb1 = RegionBuilder(name='test') 22 | assert rb1.__repr__() == "" 24 | 25 | rb1.init_atom_group(atom_group=atom_group) 26 | assert rb1.__repr__() == "" 28 | 29 | def test_QMzymeRegion(): 30 | region_builder = RegionBuilder(name='test') 31 | assert region_builder.__repr__() == "" 33 | 34 | region_builder.init_atom_group(atom_group=atom_group) 35 | assert region_builder.__repr__() == "" 37 | 38 | # test region was populated as expected 39 | region = region_builder.get_region() 40 | assert region.__repr__() == "" 41 | assert region.has_atom(id=id1) 42 | assert region.has_residue(resid=3) 43 | #assert region.atom_group == atom_group 44 | assert any(region.ids) == any(atom_group.ids) 45 | assert any(region.resids) == any(atom_group.resids) 46 | 47 | # add atom through region builder init_atom() method 48 | mda_atom = u.select_atoms('resid 1 and name CA').atoms[0] # has id=5 49 | region_builder.init_atom(mda_atom) 50 | region = region_builder.get_region() 51 | qmz_atom = region.get_atom(id=5) 52 | assert region.n_atoms == 62 53 | assert 1 in region.resids 54 | assert 5 in region.ids 55 | 56 | region_builder.init_atom(mda_atom) 57 | with pytest.raises(UserWarning): 58 | region = region_builder.get_region() # Because this atom already exists in region. 59 | # remove that problem atom from region_builder atoms to continue with testing 60 | region_builder.atoms = region_builder.atoms[:-1] 61 | 62 | res = region.get_residue(resid=qmz_atom.resid) 63 | assert f"{qmz_atom.element}1" not in [atom.name for atom in res.atoms] # check it doesn't exist first 64 | # now add the atom again- it will be changed because it was not unique and not an mda_atom with immutable id. 65 | region_builder.init_atom(qmz_atom) 66 | assert qmz_atom != region_builder.atoms[-1] # atom has changed because it was already there 67 | assert qmz_atom == region_builder.atoms[-2] 68 | region = region_builder.get_region() 69 | assert region.n_atoms == 63 70 | res = region.get_residue(resid=qmz_atom.resid) 71 | assert f"{qmz_atom.element}1" in [atom.name for atom in res.atoms] 72 | assert region_builder.atoms[-1].id == max(region.get_residue(qmz_atom.resid).ids) 73 | 74 | # test getting atom ids for all CA atoms 75 | ids = region.get_ids(attribute='name', value='CA') 76 | assert sorted(ids) == [5, 22, 36, 63, 69] 77 | 78 | # test setting fixed atoms 79 | region.set_fixed_atoms(ids=ids) 80 | for id in ids: 81 | assert region.get_atom(id).is_fixed == True 82 | 83 | def test_add_regions(): 84 | model = QMzyme.GenerateModel(PDB) 85 | model.set_region(name='r1', selection='resid 263') 86 | model.set_region(name='r2', selection='resid 103') 87 | model.set_region(name='r1_r2', selection='resid 103 or resid 263') 88 | 89 | assert model.r1.n_atoms + model.r2.n_atoms == model.r1_r2.n_atoms 90 | assert model.r1 + model.r2 == model.r1_r2 91 | assert model.r1_r2 + model.r1 == model.r1_r2 92 | model.r1.set_fixed_atoms(ids=model.r1.ids) 93 | 94 | r3 = model.r1_r2 + model.r1 95 | for atom in r3.atoms: 96 | assert atom.is_fixed == False 97 | 98 | r3 = model.r1 + model.r1_r2 99 | for atom in r3.atoms: 100 | if atom.id in model.r1.ids: 101 | assert atom.is_fixed == True 102 | else: 103 | assert atom.is_fixed == False 104 | 105 | def test_subtract_regions(): 106 | model = QMzyme.GenerateModel(PDB) 107 | model.set_region(name='r1', selection='resid 263') 108 | model.set_region(name='r2', selection='resid 103') 109 | model.set_region(name='r1_r2', selection='resid 103 or resid 263') 110 | 111 | assert model.r1_r2 - model.r1 == model.r2 112 | assert (model.r1 - model.r1_r2).n_atoms == 0 113 | 114 | 115 | def test_equal_regions(): 116 | model = QMzyme.GenerateModel(PDB) 117 | model.set_region(name='r1', selection='resid 263') 118 | model.set_region(name='r2', selection='resid 263') 119 | assert model.r1 == model.r2 120 | 121 | 122 | setattr(model.r1.atoms[0], "name", "X") 123 | assert model.r1 != model.r2 124 | 125 | 126 | def test_QMzymeResidue(): 127 | region_builder = RegionBuilder(name='test') 128 | region_builder.init_atom_group(atom_group=atom_group) 129 | region = region_builder.get_region() 130 | residue = region.residues[0] 131 | assert residue.__repr__() == "" 132 | assert residue.get_atom('CA').__repr__() == "" 133 | assert residue.chain == 'A' 134 | assert not residue.has_atom(100000000) 135 | 136 | 137 | -------------------------------------------------------------------------------- /tests/test_SelectionSchemes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for the QMzyme GenerateModel.py code. 3 | """ 4 | 5 | # Import package, test suite, and other packages as needed 6 | # Name each function as test_* to be automatically included in test workflow 7 | 8 | from QMzyme.GenerateModel import GenerateModel 9 | import pytest 10 | from QMzyme.RegionBuilder import RegionBuilder 11 | from MDAnalysis.core.universe import Universe 12 | from QMzyme.SelectionSchemes import DistanceCutoff 13 | from QMzyme.data import PDB 14 | 15 | 16 | def test_DistanceCutoff(): 17 | from QMzyme.SelectionSchemes import DistanceCutoff 18 | model = GenerateModel(PDB) 19 | with pytest.raises(UserWarning): 20 | DistanceCutoff(model=model, name=None, cutoff=3, include_whole_residues=True) 21 | with pytest.raises(UserWarning): 22 | model.set_region(selection=DistanceCutoff, name=None, cutoff=3, include_whole_residues=True) 23 | model.set_catalytic_center('resid 263') 24 | model.set_region(selection=DistanceCutoff, name=None, cutoff=3, include_whole_residues=True) 25 | assert len(model.regions) == 2 26 | assert model.cutoff_3.n_atoms == 275 27 | assert model.cutoff_3.n_residues == 19 28 | 29 | model.set_region(selection=DistanceCutoff, name=None, cutoff=5, include_whole_residues=True) 30 | assert len(model.regions) == 3 31 | assert model.cutoff_5.n_atoms == 427 32 | assert model.cutoff_5.n_residues == 33 33 | -------------------------------------------------------------------------------- /tests/test_TruncationSchemes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for the QMzyme truncation_schemes.py and truncation_utils.py codes. 3 | """ 4 | 5 | # Import package, test suite, and other packages as needed 6 | # Name each function as test_* to be automatically included in test workflow 7 | 8 | from QMzyme.GenerateModel import GenerateModel 9 | from QMzyme.TruncationSchemes import * 10 | import pytest 11 | from QMzyme.data import PDB 12 | 13 | 14 | @pytest.mark.parametrize( 15 | "Test, init_file, region_selection",[ 16 | ('First and last residue in protein: MET1 GLN262', PDB, 'resid 1 or resid 262'), 17 | ('MET1 ASN2', PDB, 'resid 1 or resid 2'), 18 | ('MET1 LEU3', PDB, 'resid 1 or resid 3'), 19 | ('ASN2 THR5', PDB, 'resid 2 or resid 5'), 20 | ('ASN2 LEU3 THR5 ALA6', PDB, 'resid 2 or resid 3 or resid 5 or resid 6'), 21 | ('PRO4 THR5', PDB, 'resid 4 or resid 5'), 22 | ('LEU3 PRO4', PDB, 'resid 3 or resid 4'), 23 | ('With Non protein residue: WAT265', PDB, 'resid 3 or resid 265'), 24 | ] 25 | ) 26 | def test_AlphaCarbon(Test, init_file, region_selection, truncation_scheme=AlphaCarbon): 27 | model = GenerateModel(init_file) 28 | model.set_region(name='region', selection=region_selection) 29 | region_truncated = truncation_scheme(model.region, name=None).return_region() 30 | assert region_truncated != model.region 31 | 32 | for i, res in enumerate(model.region.residues): 33 | truncated_res = region_truncated.residues[i] 34 | res_atoms = [atom.name for atom in res.atoms] 35 | truncated_res_atoms = [atom.name for atom in truncated_res.atoms] 36 | if res.resid == 1: 37 | assert 'N' in res_atoms 38 | assert 'N' in truncated_res_atoms 39 | elif res.resname != 'PRO': 40 | assert 'N' in res_atoms 41 | assert 'H' in res_atoms 42 | assert 'N' not in truncated_res_atoms 43 | assert 'HN' in truncated_res_atoms 44 | elif res.resname == 'PRO': 45 | assert 'N' in res_atoms 46 | assert 'H' not in res_atoms 47 | assert 'N' in truncated_res_atoms 48 | assert 'HN' in truncated_res_atoms 49 | assert 'C' in res_atoms 50 | assert 'O' in res_atoms 51 | if res.resid != 262: 52 | assert 'C' not in truncated_res_atoms 53 | assert 'O' not in truncated_res_atoms 54 | assert 'HC' in truncated_res_atoms 55 | elif res.resid == 262: 56 | assert 'C' in truncated_res_atoms 57 | assert 'O' in truncated_res_atoms 58 | 59 | 60 | @pytest.mark.parametrize( 61 | "Test, init_file, region_selection",[ 62 | ('First and last residue in protein: MET1 GLN262', PDB, 'resid 1 or resid 262'), 63 | ('MET1 ASN2', PDB, 'resid 1 or resid 2'), 64 | ('MET1 LEU3', PDB, 'resid 1 or resid 3'), 65 | ('ASN2 THR5', PDB, 'resid 2 or resid 5'), 66 | ('ASN2 LEU3 THR5 ALA6', PDB, 'resid 2 or resid 3 or resid 5 or resid 6'), 67 | ('PRO4 THR5', PDB, 'resid 4 or resid 5'), 68 | ('LEU3 PRO4', PDB, 'resid 3 or resid 4'), 69 | ('With Non protein residue: WAT265', PDB, 'resid 3 or resid 265'), 70 | ] 71 | ) 72 | def test_TerminalAlphaCarbon(Test, init_file, region_selection, truncation_scheme=TerminalAlphaCarbon): 73 | model = GenerateModel(init_file) 74 | model.set_region(name='region', selection=region_selection) 75 | region_truncated = truncation_scheme(model.region, name=None).return_region() 76 | #model.truncate() 77 | #region_truncated = model.truncated 78 | assert region_truncated != model.region 79 | 80 | #First check that the original region didn't change: 81 | original_first_res = model.region.residues[0] 82 | truncated_first_res = region_truncated.residues[0] 83 | original_last_res = model.region.residues[-1] 84 | truncated_last_res = region_truncated.residues[-1] 85 | 86 | if original_first_res.resname != 'PRO': 87 | removed_atom_name = 'H' 88 | if original_first_res.resid == 1: 89 | assert 'H1' in [atom.name for atom in original_first_res.atoms] 90 | assert 'H2' in [atom.name for atom in original_first_res.atoms] 91 | assert 'H3' in [atom.name for atom in original_first_res.atoms] 92 | assert 'H2' in [atom.name for atom in truncated_first_res.atoms] 93 | assert 'H3' in [atom.name for atom in truncated_first_res.atoms] 94 | if original_first_res.resid != 1: 95 | assert 'H' in [atom.name for atom in original_first_res.atoms] 96 | assert 'N' in [atom.name for atom in original_first_res.atoms] 97 | assert 'H' not in [atom.name for atom in truncated_first_res.atoms] 98 | assert 'N' not in [atom.name for atom in truncated_first_res.atoms] 99 | assert 'HN' in [atom.name for atom in truncated_first_res.atoms] 100 | 101 | if original_first_res.resname == 'PRO': 102 | assert 'N' in [atom.name for atom in original_first_res.atoms] 103 | assert 'H' not in [atom.name for atom in original_first_res.atoms] 104 | assert 'N' in [atom.name for atom in truncated_first_res.atoms] 105 | assert 'HN' in [atom.name for atom in truncated_first_res.atoms] 106 | 107 | for i in range(model.region.n_residues-1): 108 | resid = model.region.resids[i] 109 | next_resid = model.region.resids[i+1] 110 | original_res = model.region.get_residue(resid) 111 | original_next_res = model.region.get_residue(next_resid) 112 | truncated_res = region_truncated.get_residue(resid) 113 | truncated_next_res = region_truncated.get_residue(next_resid) 114 | assert 'C' in [atom.name for atom in original_res.atoms] 115 | assert 'O' in [atom.name for atom in original_res.atoms] 116 | assert 'N' in [atom.name for atom in original_next_res.atoms] 117 | if original_next_res.resname != 'PRO': 118 | assert 'H' in [atom.name for atom in original_next_res.atoms] 119 | if resid+1 == next_resid: 120 | #C term is not removed 121 | assert 'C' in [atom.name for atom in truncated_res.atoms] 122 | assert 'O' in [atom.name for atom in truncated_res.atoms] 123 | assert 'N' in [atom.name for atom in truncated_next_res.atoms] 124 | if original_next_res.resname != 'PRO': 125 | assert 'H' in [atom.name for atom in truncated_next_res.atoms] 126 | if resid+1 != next_resid: 127 | #C term is removed 128 | assert 'C' not in [atom.name for atom in truncated_res.atoms] 129 | assert 'O' not in [atom.name for atom in truncated_res.atoms] 130 | assert 'H' not in [atom.name for atom in truncated_next_res.atoms] 131 | if original_next_res.resname == 'PRO': 132 | assert 'N' in [atom.name for atom in truncated_next_res.atoms] 133 | elif original_next_res.resname != 'PRO': 134 | assert 'N' not in [atom.name for atom in truncated_next_res.atoms] 135 | assert 'HN' in [atom.name for atom in truncated_next_res.atoms] 136 | assert 'HC' in [atom.name for atom in truncated_res.atoms] 137 | names = [atom.name for atom in truncated_res.atoms] 138 | if 'N' not in names and 'C' not in names: 139 | print(original_res.atoms) 140 | assert 'HN' in names 141 | assert 'HC' in names 142 | --------------------------------------------------------------------------------