├── .github └── workflows │ ├── python-publish.yml │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── data ├── alanine_dipeptide │ ├── 1.pdb │ ├── 2.pdb │ ├── 3.pdb │ ├── 4.pdb │ ├── 5.pdb │ ├── 6.pdb │ ├── 7.pdb │ ├── 8.pdb │ └── config.yaml ├── clashes │ └── tryptophan_helix.pdb ├── fileformats │ ├── 4HHB.mmtf │ ├── 7SG5_model.pdb │ ├── 7SG5_model_complex.pdb │ └── 7lka.cif └── modelled │ ├── config.yaml │ ├── output.pdb │ ├── sg_imgt.pdb │ ├── sg_imgt_protonated.pdb │ ├── test_output │ └── test_sg ├── pyproject.toml ├── requirements.txt ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── src └── topmodel │ ├── __init__.py │ ├── app.py │ ├── check │ ├── __init__.py │ ├── amide_bond.py │ ├── chirality.py │ ├── clashes.py │ └── py.typed │ ├── py.typed │ └── util │ ├── MMTFParser.py │ ├── __init__.py │ ├── errors.py │ ├── parser.py │ ├── py.typed │ └── utils.py ├── tests ├── test_app.py ├── test_check │ ├── test_amide_bond.py │ ├── test_chirality.py │ └── test_clashes.py └── test_util │ └── test_parser.py └── tox.ini /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | deploy: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | environment: 24 | name: pypi 25 | url: https://pypi.org/p/protein-topmodel 26 | permissions: 27 | id-token: write 28 | steps: 29 | - uses: actions/checkout@v4 30 | - name: Set up Python 31 | uses: actions/setup-python@v4 32 | with: 33 | python-version: '3.x' 34 | - name: Install dependencies 35 | run: | 36 | python -m pip install --upgrade pip 37 | pip install build 38 | - name: Build package 39 | run: python -m build 40 | - name: Publish package 41 | uses: pypa/gh-action-pypi-publish@release/v1 42 | with: 43 | user: __token__ 44 | password: ${{ secrets.PYPI_PUBLISH }} 45 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, windows-latest] 13 | python-version: ['3.8', '3.9', '3.10', '3.11'] 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install tox tox-gh-actions 25 | - name: Test with tox 26 | run: tox 27 | -------------------------------------------------------------------------------- /.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 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # own 132 | *.swp 133 | .ipynb_checkpoints 134 | *.ipynb 135 | paper/ 136 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Liedl Lab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TopModel 2 | 3 | A python script to quickly inspect and highlight issues in structure models. 4 | 5 | ![Tests](https://github.com/liedllab/TopModel/actions/workflows/tests.yml/badge.svg) 6 | 7 | ## Installation 8 | 9 | ```bash 10 | pip install protein-topmodel 11 | ``` 12 | 13 | Or alternatively from GitHUB: 14 | ```bash 15 | pip install git+https://github.com/liedllab/topmodel.git 16 | ``` 17 | 18 | ## Test Installation 19 | 20 | ```bash 21 | topmodel --help 22 | ``` 23 | 24 | # Command-Line Interface (CLI) 25 | ```bash 26 | topmodel path/to/file*.pdb 27 | ``` 28 | 29 | TopModel is a command line tool that checks the chiralities, the amide bonds and overall VDW clashes 30 | in a structure model. Optionally, the structure can be opened in PyMOL to visualize 31 | the issues found. For this PyMOL needs to be in PATH. 32 | 33 | ## License Info 34 | 35 | When using the code in a publication, please cite: 36 | 37 | 38 | Fernández-Quintero, M. L., Kokot, J., Waibl, F., Fischer, A. L. M., Quoika, P. K., Deane, C. M., 39 | & Liedl, K. R. (2023). Challenges in antibody structure prediction. mAbs, 15(1). 40 | [https://doi.org/10.1080/19420862.2023.2175319](https://doi.org/10.1080/19420862.2023.2175319) 41 | 42 | 43 | # Background 44 | ## Chirality 45 | 46 | TopModel can assign `L` and `D` to aminoacids. In the command-line interface (CLI) only the `D` 47 | aminoacids are shown. 48 | The chirality is computed by computing a normal vector $\vec{n}$ to the plane defined by the triangle 49 | of the three highest priority atoms $A$ around the chiral center where the suffix denotes the 50 | priority. 51 | 52 | $$\vec{n} = \overrightarrow{A_3A_1} \times \overrightarrow{A_3A_2}$$ 53 | 54 | The direction of the normal vector is determined by the orientation of the side chains. By 55 | calculating the dot product of the normal vector and a vector from the chiral center to the plane the 56 | relative orientation of the three atoms around the chiral center, and thus the chirality, can be 57 | determined from the sign of the result. 58 | 59 | $$ 60 | chirality = \vec{n} \cdot \overrightarrow{A_3A_{center}} = 61 | \begin{cases} 62 | \mathbf{D}\, \text{if } > 0 \\ 63 | \mathbf{L}\, \text{if } < 0 64 | \end{cases} 65 | $$ 66 | 67 | ## Amide bonds 68 | 69 | TopModel can assign `CIS` and `TRANS` to the amide bonds depending on the dihedral angle defined by 70 | $C_{\alpha}CNC_\alpha$. 71 | Amide bonds that could not be assigned to either `CIS` or `TRANS` are labeled as `NON_PLANAR`. 72 | 73 | Cis amide bonds to prolines are labelled separately as they occur more frequently. 74 | 75 | ## Clashes 76 | 77 | TopModel detects Van der Waals clashes by calculating the distance between all pair of atoms that 78 | are within 5 Å of each other. A clash is defined by: 79 | $$d_{AB} < r_A + r_B - 0.5Å$$ 80 | 81 | # As a package 82 | 83 | TopModel can be imported as a package. The package contains the small modules `chirality`, 84 | `amide_bond` and `clashes` that provide functions to calculate the respective property. 85 | 86 | ```python 87 | import topmodel # or 88 | from topmodel.check import chirality, amide_bond, clashes 89 | ``` 90 | 91 | # Dependencies 92 | 93 | - biopython 94 | - scipy 95 | - mendeleev 96 | - Click 97 | - colorama 98 | - PyMOL (to open the structure in PyMOL) 99 | -------------------------------------------------------------------------------- /data/alanine_dipeptide/1.pdb: -------------------------------------------------------------------------------- 1 | ATOM 1 N ALA 1 -0.677 -1.230 -0.491 1.00 0.00 N 2 | ATOM 2 CA ALA 1 -0.001 0.064 -0.491 1.00 0.00 C 3 | ATOM 3 C ALA 1 1.499 -0.110 -0.491 1.00 0.00 C 4 | ATOM 4 O ALA 1 2.030 -1.227 -0.502 1.00 0.00 O 5 | ATOM 5 CB ALA 1 -0.509 0.856 0.727 1.00 0.00 C 6 | ATOM 6 H ALA 1 -0.131 -2.162 -0.491 1.00 0.00 H 7 | ATOM 7 HA ALA 1 -0.269 0.603 -1.418 1.00 0.00 H 8 | ATOM 8 1HB ALA 1 -1.605 1.006 0.691 1.00 0.00 H 9 | ATOM 9 2HB ALA 1 -0.285 0.342 1.681 1.00 0.00 H 10 | ATOM 10 3HB ALA 1 -0.053 1.861 0.784 1.00 0.00 H 11 | ATOM 11 N ALA 2 2.250 0.939 -0.479 1.00 0.00 N 12 | ATOM 12 CA ALA 2 3.700 0.770 -0.479 1.00 0.00 C 13 | ATOM 13 C ALA 2 4.400 2.108 -0.464 1.00 0.00 C 14 | ATOM 14 O ALA 2 3.774 3.175 -0.443 1.00 0.00 O 15 | ATOM 15 CB ALA 2 4.071 -0.082 -1.704 1.00 0.00 C 16 | ATOM 16 H ALA 2 1.841 1.862 -0.470 1.00 0.00 H 17 | ATOM 17 HA ALA 2 3.994 0.236 0.444 1.00 0.00 H 18 | ATOM 18 1HB ALA 2 3.579 -1.073 -1.679 1.00 0.00 H 19 | ATOM 19 2HB ALA 2 3.773 0.402 -2.655 1.00 0.00 H 20 | ATOM 20 3HB ALA 2 5.159 -0.270 -1.762 1.00 0.00 H 21 | TER 22 | END 23 | -------------------------------------------------------------------------------- /data/alanine_dipeptide/2.pdb: -------------------------------------------------------------------------------- 1 | ATOM 1 N ALA 1 -0.677 -1.230 -0.491 1.00 0.00 N 2 | ATOM 2 CA ALA 1 -0.001 0.064 -0.491 1.00 0.00 C 3 | ATOM 3 C ALA 1 1.499 -0.110 -0.491 1.00 0.00 C 4 | ATOM 4 O ALA 1 2.030 -1.227 -0.502 1.00 0.00 O 5 | ATOM 5 CB ALA 1 -0.509 0.856 0.727 1.00 0.00 C 6 | ATOM 6 H ALA 1 -0.131 -2.162 -0.491 1.00 0.00 H 7 | ATOM 7 HA ALA 1 -0.269 0.603 -1.418 1.00 0.00 H 8 | ATOM 8 1HB ALA 1 -1.605 1.006 0.691 1.00 0.00 H 9 | ATOM 9 2HB ALA 1 -0.285 0.342 1.681 1.00 0.00 H 10 | ATOM 10 3HB ALA 1 -0.053 1.861 0.784 1.00 0.00 H 11 | ATOM 11 N ALA 2 2.250 0.939 -0.479 1.00 0.00 N 12 | ATOM 12 CA ALA 2 3.700 0.770 -0.479 1.00 0.00 C 13 | ATOM 13 C ALA 2 4.400 2.108 -0.464 1.00 0.00 C 14 | ATOM 14 O ALA 2 3.774 3.175 -0.443 1.00 0.00 O 15 | ATOM 15 CB ALA 2 4.207 -0.035 0.731 1.00 0.00 C 16 | ATOM 16 H ALA 2 1.841 1.862 -0.470 1.00 0.00 H 17 | ATOM 17 HA ALA 2 3.969 0.241 -1.412 1.00 0.00 H 18 | ATOM 18 1HB ALA 2 5.303 -0.184 0.694 1.00 0.00 H 19 | ATOM 19 2HB ALA 2 3.982 0.470 1.691 1.00 0.00 H 20 | ATOM 20 3HB ALA 2 3.750 -1.040 0.777 1.00 0.00 H 21 | TER 22 | END 23 | -------------------------------------------------------------------------------- /data/alanine_dipeptide/3.pdb: -------------------------------------------------------------------------------- 1 | ATOM 1 N ALA 1 -0.677 -1.230 -0.491 1.00 0.00 N 2 | ATOM 2 CA ALA 1 -0.001 0.064 -0.491 1.00 0.00 C 3 | ATOM 3 C ALA 1 1.499 -0.110 -0.491 1.00 0.00 C 4 | ATOM 4 O ALA 1 2.030 -1.227 -0.502 1.00 0.00 O 5 | ATOM 5 CB ALA 1 -0.371 0.929 -1.709 1.00 0.00 C 6 | ATOM 6 H ALA 1 -0.131 -2.162 -0.491 1.00 0.00 H 7 | ATOM 7 HA ALA 1 -0.296 0.589 0.436 1.00 0.00 H 8 | ATOM 8 1HB ALA 1 0.121 1.920 -1.673 1.00 0.00 H 9 | ATOM 9 2HB ALA 1 -0.071 0.455 -2.663 1.00 0.00 H 10 | ATOM 10 3HB ALA 1 -1.458 1.117 -1.766 1.00 0.00 H 11 | ATOM 11 N ALA 2 2.250 0.939 -0.479 1.00 0.00 N 12 | ATOM 12 CA ALA 2 3.700 0.770 -0.479 1.00 0.00 C 13 | ATOM 13 C ALA 2 4.400 2.108 -0.464 1.00 0.00 C 14 | ATOM 14 O ALA 2 3.774 3.175 -0.443 1.00 0.00 O 15 | ATOM 15 CB ALA 2 4.071 -0.082 -1.704 1.00 0.00 C 16 | ATOM 16 H ALA 2 1.841 1.862 -0.470 1.00 0.00 H 17 | ATOM 17 HA ALA 2 3.994 0.236 0.444 1.00 0.00 H 18 | ATOM 18 1HB ALA 2 3.579 -1.073 -1.679 1.00 0.00 H 19 | ATOM 19 2HB ALA 2 3.773 0.402 -2.655 1.00 0.00 H 20 | ATOM 20 3HB ALA 2 5.159 -0.270 -1.762 1.00 0.00 H 21 | TER 22 | END 23 | -------------------------------------------------------------------------------- /data/alanine_dipeptide/4.pdb: -------------------------------------------------------------------------------- 1 | ATOM 1 N ALA 1 -0.677 -1.230 -0.491 1.00 0.00 N 2 | ATOM 2 CA ALA 1 -0.001 0.064 -0.491 1.00 0.00 C 3 | ATOM 3 C ALA 1 1.499 -0.110 -0.491 1.00 0.00 C 4 | ATOM 4 O ALA 1 2.030 -1.227 -0.502 1.00 0.00 O 5 | ATOM 5 CB ALA 1 -0.371 0.929 -1.709 1.00 0.00 C 6 | ATOM 6 H ALA 1 -0.131 -2.162 -0.491 1.00 0.00 H 7 | ATOM 7 HA ALA 1 -0.296 0.589 0.436 1.00 0.00 H 8 | ATOM 8 1HB ALA 1 0.121 1.920 -1.673 1.00 0.00 H 9 | ATOM 9 2HB ALA 1 -0.071 0.455 -2.663 1.00 0.00 H 10 | ATOM 10 3HB ALA 1 -1.458 1.117 -1.766 1.00 0.00 H 11 | ATOM 11 N ALA 2 2.250 0.939 -0.479 1.00 0.00 N 12 | ATOM 12 CA ALA 2 3.700 0.770 -0.479 1.00 0.00 C 13 | ATOM 13 C ALA 2 4.400 2.108 -0.464 1.00 0.00 C 14 | ATOM 14 O ALA 2 3.774 3.175 -0.443 1.00 0.00 O 15 | ATOM 15 CB ALA 2 4.205 -0.035 0.730 1.00 0.00 C 16 | ATOM 16 H ALA 2 1.841 1.862 -0.470 1.00 0.00 H 17 | ATOM 17 HA ALA 2 3.968 0.241 -1.413 1.00 0.00 H 18 | ATOM 18 1HB ALA 2 5.301 -0.186 0.693 1.00 0.00 H 19 | ATOM 19 2HB ALA 2 3.981 0.469 1.690 1.00 0.00 H 20 | ATOM 20 3HB ALA 2 3.749 -1.041 0.776 1.00 0.00 H 21 | TER 22 | END 23 | -------------------------------------------------------------------------------- /data/alanine_dipeptide/5.pdb: -------------------------------------------------------------------------------- 1 | ATOM 1 N ALA 1 -0.677 -1.230 -0.491 1.00 0.00 N 2 | ATOM 2 CA ALA 1 -0.001 0.064 -0.491 1.00 0.00 C 3 | ATOM 3 C ALA 1 1.499 -0.110 -0.491 1.00 0.00 C 4 | ATOM 4 O ALA 1 2.030 -1.227 -0.502 1.00 0.00 O 5 | ATOM 5 CB ALA 1 -0.509 0.856 0.727 1.00 0.00 C 6 | ATOM 6 H ALA 1 -0.131 -2.162 -0.491 1.00 0.00 H 7 | ATOM 7 HA ALA 1 -0.269 0.603 -1.418 1.00 0.00 H 8 | ATOM 8 1HB ALA 1 -1.605 1.006 0.691 1.00 0.00 H 9 | ATOM 9 2HB ALA 1 -0.285 0.342 1.681 1.00 0.00 H 10 | ATOM 10 3HB ALA 1 -0.053 1.861 0.784 1.00 0.00 H 11 | ATOM 11 N ALA 2 2.250 0.939 -0.479 1.00 0.00 N 12 | ATOM 12 CA ALA 2 1.623 2.257 -0.467 1.00 0.00 C 13 | ATOM 13 C ALA 2 2.664 3.351 -0.454 1.00 0.00 C 14 | ATOM 14 O ALA 2 3.876 3.102 -0.465 1.00 0.00 O 15 | ATOM 15 CB ALA 2 0.683 2.316 0.748 1.00 0.00 C 16 | ATOM 16 H ALA 2 3.256 0.849 -0.478 1.00 0.00 H 17 | ATOM 17 HA ALA 2 1.033 2.377 -1.395 1.00 0.00 H 18 | ATOM 18 1HB ALA 2 -0.097 1.531 0.703 1.00 0.00 H 19 | ATOM 19 2HB ALA 2 1.226 2.176 1.704 1.00 0.00 H 20 | ATOM 20 3HB ALA 2 0.153 3.284 0.815 1.00 0.00 H 21 | TER 22 | END 23 | -------------------------------------------------------------------------------- /data/alanine_dipeptide/6.pdb: -------------------------------------------------------------------------------- 1 | ATOM 1 N ALA 1 -0.677 -1.230 -0.491 1.00 0.00 N 2 | ATOM 2 CA ALA 1 -0.001 0.064 -0.491 1.00 0.00 C 3 | ATOM 3 C ALA 1 1.499 -0.110 -0.491 1.00 0.00 C 4 | ATOM 4 O ALA 1 2.030 -1.227 -0.502 1.00 0.00 O 5 | ATOM 5 CB ALA 1 -0.509 0.856 0.727 1.00 0.00 C 6 | ATOM 6 H ALA 1 -0.131 -2.162 -0.491 1.00 0.00 H 7 | ATOM 7 HA ALA 1 -0.269 0.603 -1.418 1.00 0.00 H 8 | ATOM 8 1HB ALA 1 -1.605 1.006 0.691 1.00 0.00 H 9 | ATOM 9 2HB ALA 1 -0.285 0.342 1.681 1.00 0.00 H 10 | ATOM 10 3HB ALA 1 -0.053 1.861 0.784 1.00 0.00 H 11 | ATOM 11 N ALA 2 2.250 0.939 -0.479 1.00 0.00 N 12 | ATOM 12 CA ALA 2 1.623 2.257 -0.467 1.00 0.00 C 13 | ATOM 13 C ALA 2 2.664 3.351 -0.454 1.00 0.00 C 14 | ATOM 14 O ALA 2 3.876 3.102 -0.465 1.00 0.00 O 15 | ATOM 15 CB ALA 2 0.712 2.494 -1.683 1.00 0.00 C 16 | ATOM 16 H ALA 2 3.256 0.849 -0.478 1.00 0.00 H 17 | ATOM 17 HA ALA 2 1.025 2.327 0.461 1.00 0.00 H 18 | ATOM 18 1HB ALA 2 0.215 3.482 -1.637 1.00 0.00 H 19 | ATOM 19 2HB ALA 2 1.272 2.458 -2.638 1.00 0.00 H 20 | ATOM 20 3HB ALA 2 -0.093 1.738 -1.749 1.00 0.00 H 21 | TER 22 | END 23 | -------------------------------------------------------------------------------- /data/alanine_dipeptide/7.pdb: -------------------------------------------------------------------------------- 1 | ATOM 1 N ALA 1 -0.677 -1.230 -0.491 1.00 0.00 N 2 | ATOM 2 CA ALA 1 -0.001 0.064 -0.491 1.00 0.00 C 3 | ATOM 3 C ALA 1 1.499 -0.110 -0.491 1.00 0.00 C 4 | ATOM 4 O ALA 1 2.030 -1.227 -0.502 1.00 0.00 O 5 | ATOM 5 CB ALA 1 -0.371 0.929 -1.709 1.00 0.00 C 6 | ATOM 6 H ALA 1 -0.131 -2.162 -0.491 1.00 0.00 H 7 | ATOM 7 HA ALA 1 -0.296 0.589 0.436 1.00 0.00 H 8 | ATOM 8 1HB ALA 1 0.121 1.920 -1.673 1.00 0.00 H 9 | ATOM 9 2HB ALA 1 -0.071 0.455 -2.663 1.00 0.00 H 10 | ATOM 10 3HB ALA 1 -1.458 1.117 -1.766 1.00 0.00 H 11 | ATOM 11 N ALA 2 2.250 0.939 -0.479 1.00 0.00 N 12 | ATOM 12 CA ALA 2 1.623 2.257 -0.467 1.00 0.00 C 13 | ATOM 13 C ALA 2 2.664 3.351 -0.454 1.00 0.00 C 14 | ATOM 14 O ALA 2 3.876 3.102 -0.465 1.00 0.00 O 15 | ATOM 15 CB ALA 2 0.683 2.316 0.748 1.00 0.00 C 16 | ATOM 16 H ALA 2 3.256 0.849 -0.478 1.00 0.00 H 17 | ATOM 17 HA ALA 2 1.033 2.377 -1.395 1.00 0.00 H 18 | ATOM 18 1HB ALA 2 -0.097 1.531 0.703 1.00 0.00 H 19 | ATOM 19 2HB ALA 2 1.226 2.176 1.704 1.00 0.00 H 20 | ATOM 20 3HB ALA 2 0.153 3.284 0.815 1.00 0.00 H 21 | TER 22 | END 23 | -------------------------------------------------------------------------------- /data/alanine_dipeptide/8.pdb: -------------------------------------------------------------------------------- 1 | ATOM 1 N ALA 1 -0.677 -1.230 -0.491 1.00 0.00 N 2 | ATOM 2 CA ALA 1 -0.001 0.064 -0.491 1.00 0.00 C 3 | ATOM 3 C ALA 1 1.499 -0.110 -0.491 1.00 0.00 C 4 | ATOM 4 O ALA 1 2.030 -1.227 -0.502 1.00 0.00 O 5 | ATOM 5 CB ALA 1 -0.371 0.929 -1.709 1.00 0.00 C 6 | ATOM 6 H ALA 1 -0.131 -2.162 -0.491 1.00 0.00 H 7 | ATOM 7 HA ALA 1 -0.296 0.589 0.436 1.00 0.00 H 8 | ATOM 8 1HB ALA 1 0.121 1.920 -1.673 1.00 0.00 H 9 | ATOM 9 2HB ALA 1 -0.071 0.455 -2.663 1.00 0.00 H 10 | ATOM 10 3HB ALA 1 -1.458 1.117 -1.766 1.00 0.00 H 11 | ATOM 11 N ALA 2 2.250 0.939 -0.479 1.00 0.00 N 12 | ATOM 12 CA ALA 2 1.623 2.257 -0.467 1.00 0.00 C 13 | ATOM 13 C ALA 2 2.664 3.351 -0.454 1.00 0.00 C 14 | ATOM 14 O ALA 2 3.876 3.102 -0.465 1.00 0.00 O 15 | ATOM 15 CB ALA 2 0.712 2.494 -1.683 1.00 0.00 C 16 | ATOM 16 H ALA 2 3.256 0.849 -0.478 1.00 0.00 H 17 | ATOM 17 HA ALA 2 1.025 2.327 0.461 1.00 0.00 H 18 | ATOM 18 1HB ALA 2 0.215 3.482 -1.637 1.00 0.00 H 19 | ATOM 19 2HB ALA 2 1.272 2.458 -2.638 1.00 0.00 H 20 | ATOM 20 3HB ALA 2 -0.093 1.738 -1.749 1.00 0.00 H 21 | TER 22 | END 23 | -------------------------------------------------------------------------------- /data/alanine_dipeptide/config.yaml: -------------------------------------------------------------------------------- 1 | n_res: 2 2 | 3 | 1: 4 | amide: trans 5 | chirality: 6 | - L 7 | - L 8 | 2: 9 | amide: trans 10 | chirality: 11 | - L 12 | - D 13 | 3: 14 | amide: trans 15 | chirality: 16 | - D 17 | - L 18 | 4: 19 | amide: trans 20 | chirality: 21 | - D 22 | - D 23 | 5: 24 | amide: cis 25 | chirality: 26 | - L 27 | - L 28 | 6: 29 | amide: cis 30 | chirality: 31 | - L 32 | - D 33 | 7: 34 | amide: cis 35 | chirality: 36 | - D 37 | - L 38 | 8: 39 | amide: cis 40 | chirality: 41 | - D 42 | - D 43 | -------------------------------------------------------------------------------- /data/clashes/tryptophan_helix.pdb: -------------------------------------------------------------------------------- 1 | ATOM 1 N TRP 1 1.630 -2.708 -0.400 1.00 0.00 N 2 | ATOM 2 CA TRP 1 2.192 -1.361 -0.429 1.00 0.00 C 3 | ATOM 3 C TRP 1 3.702 -1.404 -0.428 1.00 0.00 C 4 | ATOM 4 O TRP 1 4.373 -0.728 -1.206 1.00 0.00 O 5 | ATOM 5 CB TRP 1 1.688 -0.573 0.812 1.00 0.00 C 6 | ATOM 6 CG TRP 1 0.182 -0.294 0.826 1.00 0.00 C 7 | ATOM 7 CD1 TRP 1 -0.790 -1.060 1.502 1.00 0.00 C 8 | ATOM 8 CD2 TRP 1 -0.515 0.672 0.130 1.00 0.00 C 9 | ATOM 9 CE2 TRP 1 -1.895 0.486 0.393 1.00 0.00 C 10 | ATOM 10 CE3 TRP 1 -0.079 1.700 -0.745 1.00 0.00 C 11 | ATOM 11 NE1 TRP 1 -2.092 -0.588 1.248 1.00 0.00 N 12 | ATOM 12 CZ2 TRP 1 -2.849 1.335 -0.209 1.00 0.00 C 13 | ATOM 13 CZ3 TRP 1 -1.040 2.530 -1.322 1.00 0.00 C 14 | ATOM 14 CH2 TRP 1 -2.405 2.353 -1.057 1.00 0.00 C 15 | ATOM 15 H TRP 1 2.209 -3.523 -0.382 1.00 0.00 H 16 | ATOM 16 HA TRP 1 1.873 -0.855 -1.360 1.00 0.00 H 17 | ATOM 17 2HB TRP 1 2.208 0.401 0.884 1.00 0.00 H 18 | ATOM 18 3HB TRP 1 1.972 -1.104 1.742 1.00 0.00 H 19 | ATOM 19 1HD TRP 1 -0.561 -1.932 2.101 1.00 0.00 H 20 | ATOM 20 1HE TRP 1 -2.984 -0.976 1.575 1.00 0.00 H 21 | ATOM 21 3HE TRP 1 0.971 1.839 -0.961 1.00 0.00 H 22 | ATOM 22 2HZ TRP 1 -3.902 1.197 -0.013 1.00 0.00 H 23 | ATOM 23 3HZ TRP 1 -0.724 3.323 -1.985 1.00 0.00 H 24 | ATOM 24 2HH TRP 1 -3.125 3.014 -1.515 1.00 0.00 H 25 | ATOM 25 N TRP 2 4.306 -2.173 0.414 1.00 0.00 N 26 | ATOM 26 CA TRP 2 5.765 -2.215 0.415 1.00 0.00 C 27 | ATOM 27 C TRP 2 4.346 -2.048 -0.075 1.00 0.00 C 28 | ATOM 28 O TRP 2 3.891 -0.951 -0.393 1.00 0.00 O 29 | ATOM 29 CB TRP 2 6.244 -3.243 1.477 1.00 0.00 C 30 | ATOM 30 CG TRP 2 5.931 -2.860 2.927 1.00 0.00 C 31 | ATOM 31 CD1 TRP 2 4.823 -3.308 3.675 1.00 0.00 C 32 | ATOM 32 CD2 TRP 2 6.593 -1.958 3.735 1.00 0.00 C 33 | ATOM 33 CE2 TRP 2 5.887 -1.873 4.960 1.00 0.00 C 34 | ATOM 34 CE3 TRP 2 7.750 -1.168 3.512 1.00 0.00 C 35 | ATOM 35 NE1 TRP 2 4.779 -2.707 4.948 1.00 0.00 N 36 | ATOM 36 CZ2 TRP 2 6.339 -1.004 5.976 1.00 0.00 C 37 | ATOM 37 CZ3 TRP 2 8.181 -0.322 4.534 1.00 0.00 C 38 | ATOM 38 CH2 TRP 2 7.488 -0.242 5.750 1.00 0.00 C 39 | ATOM 39 H TRP 2 3.777 -2.738 1.063 1.00 0.00 H 40 | ATOM 40 HA TRP 2 6.156 -1.207 0.653 1.00 0.00 H 41 | ATOM 41 2HB TRP 2 7.338 -3.391 1.400 1.00 0.00 H 42 | ATOM 42 3HB TRP 2 5.816 -4.242 1.261 1.00 0.00 H 43 | ATOM 43 1HD TRP 2 4.075 -3.991 3.294 1.00 0.00 H 44 | ATOM 44 1HE TRP 2 4.067 -2.818 5.678 1.00 0.00 H 45 | ATOM 45 3HE TRP 2 8.286 -1.218 2.574 1.00 0.00 H 46 | ATOM 46 2HZ TRP 2 5.804 -0.934 6.912 1.00 0.00 H 47 | ATOM 47 3HZ TRP 2 9.065 0.281 4.383 1.00 0.00 H 48 | ATOM 48 2HH TRP 2 7.849 0.419 6.523 1.00 0.00 H 49 | ATOM 49 N TRP 3 3.588 -3.089 -0.163 1.00 0.00 N 50 | ATOM 50 CA TRP 3 2.217 -2.928 -0.636 1.00 0.00 C 51 | ATOM 51 C TRP 3 1.460 -1.942 0.222 1.00 0.00 C 52 | ATOM 52 O TRP 3 0.779 -1.042 -0.267 1.00 0.00 O 53 | ATOM 53 CB TRP 3 1.503 -4.308 -0.604 1.00 0.00 C 54 | ATOM 54 CG TRP 3 2.064 -5.343 -1.585 1.00 0.00 C 55 | ATOM 55 CD1 TRP 3 3.003 -6.347 -1.273 1.00 0.00 C 56 | ATOM 56 CD2 TRP 3 1.843 -5.440 -2.944 1.00 0.00 C 57 | ATOM 57 CE2 TRP 3 2.644 -6.499 -3.439 1.00 0.00 C 58 | ATOM 58 CE3 TRP 3 1.029 -4.680 -3.822 1.00 0.00 C 59 | ATOM 59 NE1 TRP 3 3.376 -7.082 -2.415 1.00 0.00 N 60 | ATOM 60 CZ2 TRP 3 2.626 -6.814 -4.815 1.00 0.00 C 61 | ATOM 61 CZ3 TRP 3 1.023 -5.014 -5.176 1.00 0.00 C 62 | ATOM 62 CH2 TRP 3 1.808 -6.067 -5.666 1.00 0.00 C 63 | ATOM 63 H TRP 3 3.939 -4.001 0.093 1.00 0.00 H 64 | ATOM 64 HA TRP 3 2.234 -2.529 -1.668 1.00 0.00 H 65 | ATOM 65 2HB TRP 3 0.426 -4.187 -0.827 1.00 0.00 H 66 | ATOM 66 3HB TRP 3 1.525 -4.725 0.422 1.00 0.00 H 67 | ATOM 67 1HD TRP 3 3.420 -6.497 -0.286 1.00 0.00 H 68 | ATOM 68 1HE TRP 3 4.065 -7.839 -2.487 1.00 0.00 H 69 | ATOM 69 3HE TRP 3 0.426 -3.861 -3.453 1.00 0.00 H 70 | ATOM 70 2HZ TRP 3 3.235 -7.619 -5.198 1.00 0.00 H 71 | ATOM 71 3HZ TRP 3 0.402 -4.450 -5.857 1.00 0.00 H 72 | ATOM 72 2HH TRP 3 1.780 -6.305 -6.719 1.00 0.00 H 73 | ATOM 73 N TRP 4 1.530 -2.046 1.506 1.00 0.00 N 74 | ATOM 74 CA TRP 4 0.798 -1.093 2.335 1.00 0.00 C 75 | ATOM 75 C TRP 4 1.207 0.326 2.017 1.00 0.00 C 76 | ATOM 76 O TRP 4 0.378 1.218 1.841 1.00 0.00 O 77 | ATOM 77 CB TRP 4 1.082 -1.399 3.832 1.00 0.00 C 78 | ATOM 78 CG TRP 4 0.536 -2.742 4.326 1.00 0.00 C 79 | ATOM 79 CD1 TRP 4 1.271 -3.942 4.427 1.00 0.00 C 80 | ATOM 80 CD2 TRP 4 -0.760 -3.063 4.673 1.00 0.00 C 81 | ATOM 81 CE2 TRP 4 -0.798 -4.445 4.982 1.00 0.00 C 82 | ATOM 82 CE3 TRP 4 -1.941 -2.279 4.725 1.00 0.00 C 83 | ATOM 83 NE1 TRP 4 0.458 -5.016 4.837 1.00 0.00 N 84 | ATOM 84 CZ2 TRP 4 -2.016 -5.052 5.358 1.00 0.00 C 85 | ATOM 85 CZ3 TRP 4 -3.131 -2.900 5.106 1.00 0.00 C 86 | ATOM 86 CH2 TRP 4 -3.169 -4.265 5.420 1.00 0.00 C 87 | ATOM 87 H TRP 4 2.083 -2.777 1.931 1.00 0.00 H 88 | ATOM 88 HA TRP 4 -0.284 -1.185 2.123 1.00 0.00 H 89 | ATOM 89 2HB TRP 4 0.649 -0.607 4.473 1.00 0.00 H 90 | ATOM 90 3HB TRP 4 2.170 -1.349 4.032 1.00 0.00 H 91 | ATOM 91 1HD TRP 4 2.319 -4.030 4.171 1.00 0.00 H 92 | ATOM 92 1HE TRP 4 0.719 -6.001 4.954 1.00 0.00 H 93 | ATOM 93 3HE TRP 4 -1.923 -1.227 4.477 1.00 0.00 H 94 | ATOM 94 2HZ TRP 4 -2.050 -6.105 5.594 1.00 0.00 H 95 | ATOM 95 3HZ TRP 4 -4.039 -2.315 5.159 1.00 0.00 H 96 | ATOM 96 2HH TRP 4 -4.104 -4.716 5.716 1.00 0.00 H 97 | ATOM 97 N TRP 5 2.461 0.611 1.928 1.00 0.00 N 98 | ATOM 98 CA TRP 5 2.856 1.982 1.621 1.00 0.00 C 99 | ATOM 99 C TRP 5 2.238 2.446 0.323 1.00 0.00 C 100 | ATOM 100 O TRP 5 1.682 3.538 0.220 1.00 0.00 O 101 | ATOM 101 CB TRP 5 4.405 2.056 1.520 1.00 0.00 C 102 | ATOM 102 CG TRP 5 5.146 1.793 2.835 1.00 0.00 C 103 | ATOM 103 CD1 TRP 5 5.698 0.559 3.236 1.00 0.00 C 104 | ATOM 104 CD2 TRP 5 5.334 2.655 3.897 1.00 0.00 C 105 | ATOM 105 CE2 TRP 5 6.000 1.939 4.923 1.00 0.00 C 106 | ATOM 106 CE3 TRP 5 4.963 4.011 4.086 1.00 0.00 C 107 | ATOM 107 NE1 TRP 5 6.239 0.630 4.534 1.00 0.00 N 108 | ATOM 108 CZ2 TRP 5 6.308 2.579 6.143 1.00 0.00 C 109 | ATOM 109 CZ3 TRP 5 5.285 4.623 5.297 1.00 0.00 C 110 | ATOM 110 CH2 TRP 5 5.950 3.919 6.311 1.00 0.00 C 111 | ATOM 111 H TRP 5 3.161 -0.104 2.068 1.00 0.00 H 112 | ATOM 112 HA TRP 5 2.492 2.653 2.422 1.00 0.00 H 113 | ATOM 113 2HB TRP 5 4.718 3.052 1.154 1.00 0.00 H 114 | ATOM 114 3HB TRP 5 4.771 1.352 0.746 1.00 0.00 H 115 | ATOM 115 1HD TRP 5 5.663 -0.340 2.635 1.00 0.00 H 116 | ATOM 116 1HE TRP 5 6.665 -0.122 5.087 1.00 0.00 H 117 | ATOM 117 3HE TRP 5 4.443 4.558 3.312 1.00 0.00 H 118 | ATOM 118 2HZ TRP 5 6.814 2.039 6.930 1.00 0.00 H 119 | ATOM 119 3HZ TRP 5 5.015 5.657 5.454 1.00 0.00 H 120 | ATOM 120 2HH TRP 5 6.190 4.421 7.236 1.00 0.00 H 121 | ATOM 121 N TRP 6 2.294 1.674 -0.709 1.00 0.00 N 122 | ATOM 122 CA TRP 6 1.697 2.122 -1.963 1.00 0.00 C 123 | ATOM 123 C TRP 6 0.234 2.451 -1.779 1.00 0.00 C 124 | ATOM 124 O TRP 6 -0.260 3.489 -2.217 1.00 0.00 O 125 | ATOM 125 CB TRP 6 1.853 1.002 -3.029 1.00 0.00 C 126 | ATOM 126 CG TRP 6 3.298 0.711 -3.446 1.00 0.00 C 127 | ATOM 127 CD1 TRP 6 4.112 -0.317 -2.928 1.00 0.00 C 128 | ATOM 128 CD2 TRP 6 4.098 1.426 -4.313 1.00 0.00 C 129 | ATOM 129 CE2 TRP 6 5.382 0.825 -4.315 1.00 0.00 C 130 | ATOM 130 CE3 TRP 6 3.840 2.579 -5.097 1.00 0.00 C 131 | ATOM 131 NE1 TRP 6 5.415 -0.266 -3.459 1.00 0.00 N 132 | ATOM 132 CZ2 TRP 6 6.414 1.367 -5.111 1.00 0.00 C 133 | ATOM 133 CZ3 TRP 6 4.874 3.092 -5.881 1.00 0.00 C 134 | ATOM 134 CH2 TRP 6 6.142 2.495 -5.890 1.00 0.00 C 135 | ATOM 135 H TRP 6 2.746 0.772 -0.649 1.00 0.00 H 136 | ATOM 136 HA TRP 6 2.207 3.045 -2.298 1.00 0.00 H 137 | ATOM 137 2HB TRP 6 1.289 1.264 -3.944 1.00 0.00 H 138 | ATOM 138 3HB TRP 6 1.378 0.067 -2.673 1.00 0.00 H 139 | ATOM 139 1HD TRP 6 3.785 -1.025 -2.177 1.00 0.00 H 140 | ATOM 140 1HE TRP 6 6.219 -0.862 -3.231 1.00 0.00 H 141 | ATOM 141 3HE TRP 6 2.868 3.051 -5.090 1.00 0.00 H 142 | ATOM 142 2HZ TRP 6 7.394 0.914 -5.115 1.00 0.00 H 143 | ATOM 143 3HZ TRP 6 4.692 3.966 -6.490 1.00 0.00 H 144 | ATOM 144 2HH TRP 6 6.921 2.912 -6.510 1.00 0.00 H 145 | ATOM 145 N TRP 7 -0.519 1.620 -1.142 1.00 0.00 N 146 | ATOM 146 CA TRP 7 -1.933 1.938 -0.964 1.00 0.00 C 147 | ATOM 147 C TRP 7 -2.105 3.265 -0.263 1.00 0.00 C 148 | ATOM 148 O TRP 7 -2.890 4.119 -0.670 1.00 0.00 O 149 | ATOM 149 CB TRP 7 -2.607 0.813 -0.131 1.00 0.00 C 150 | ATOM 150 CG TRP 7 -2.668 -0.550 -0.827 1.00 0.00 C 151 | ATOM 151 CD1 TRP 7 -1.753 -1.608 -0.646 1.00 0.00 C 152 | ATOM 152 CD2 TRP 7 -3.544 -0.972 -1.806 1.00 0.00 C 153 | ATOM 153 CE2 TRP 7 -3.156 -2.275 -2.204 1.00 0.00 C 154 | ATOM 154 CE3 TRP 7 -4.647 -0.327 -2.423 1.00 0.00 C 155 | ATOM 155 NE1 TRP 7 -2.042 -2.695 -1.493 1.00 0.00 N 156 | ATOM 156 CZ2 TRP 7 -3.877 -2.949 -3.214 1.00 0.00 C 157 | ATOM 157 CZ3 TRP 7 -5.348 -1.016 -3.413 1.00 0.00 C 158 | ATOM 158 CH2 TRP 7 -4.971 -2.308 -3.802 1.00 0.00 C 159 | ATOM 159 H TRP 7 -0.137 0.761 -0.773 1.00 0.00 H 160 | ATOM 160 HA TRP 7 -2.414 2.021 -1.958 1.00 0.00 H 161 | ATOM 161 2HB TRP 7 -3.642 1.099 0.132 1.00 0.00 H 162 | ATOM 162 3HB TRP 7 -2.096 0.704 0.846 1.00 0.00 H 163 | ATOM 163 1HD TRP 7 -0.907 -1.565 0.027 1.00 0.00 H 164 | ATOM 164 1HE TRP 7 -1.523 -3.574 -1.600 1.00 0.00 H 165 | ATOM 165 3HE TRP 7 -4.940 0.673 -2.134 1.00 0.00 H 166 | ATOM 166 2HZ TRP 7 -3.586 -3.942 -3.521 1.00 0.00 H 167 | ATOM 167 3HZ TRP 7 -6.196 -0.542 -3.887 1.00 0.00 H 168 | ATOM 168 2HH TRP 7 -5.535 -2.818 -4.568 1.00 0.00 H 169 | ATOM 169 N TRP 8 -1.407 3.510 0.794 1.00 0.00 N 170 | ATOM 170 CA TRP 8 -1.573 4.792 1.471 1.00 0.00 C 171 | ATOM 171 C TRP 8 -1.304 5.942 0.528 1.00 0.00 C 172 | ATOM 172 O TRP 8 -2.060 6.908 0.448 1.00 0.00 O 173 | ATOM 173 CB TRP 8 -0.594 4.861 2.676 1.00 0.00 C 174 | ATOM 174 CG TRP 8 -0.889 3.862 3.799 1.00 0.00 C 175 | ATOM 175 CD1 TRP 8 -0.268 2.608 3.968 1.00 0.00 C 176 | ATOM 176 CD2 TRP 8 -1.853 3.942 4.784 1.00 0.00 C 177 | ATOM 177 CE2 TRP 8 -1.803 2.744 5.538 1.00 0.00 C 178 | ATOM 178 CE3 TRP 8 -2.805 4.949 5.088 1.00 0.00 C 179 | ATOM 179 NE1 TRP 8 -0.821 1.896 5.050 1.00 0.00 N 180 | ATOM 180 CZ2 TRP 8 -2.700 2.548 6.611 1.00 0.00 C 181 | ATOM 181 CZ3 TRP 8 -3.675 4.735 6.157 1.00 0.00 C 182 | ATOM 182 CH2 TRP 8 -3.623 3.554 6.910 1.00 0.00 C 183 | ATOM 183 H TRP 8 -0.755 2.824 1.147 1.00 0.00 H 184 | ATOM 184 HA TRP 8 -2.619 4.885 1.820 1.00 0.00 H 185 | ATOM 185 2HB TRP 8 -0.605 5.875 3.120 1.00 0.00 H 186 | ATOM 186 3HB TRP 8 0.449 4.726 2.329 1.00 0.00 H 187 | ATOM 187 1HD TRP 8 0.505 2.225 3.314 1.00 0.00 H 188 | ATOM 188 1HE TRP 8 -0.590 0.949 5.373 1.00 0.00 H 189 | ATOM 189 3HE TRP 8 -2.858 5.859 4.508 1.00 0.00 H 190 | ATOM 190 2HZ TRP 8 -2.667 1.636 7.189 1.00 0.00 H 191 | ATOM 191 3HZ TRP 8 -4.402 5.495 6.407 1.00 0.00 H 192 | ATOM 192 2HH TRP 8 -4.306 3.419 7.735 1.00 0.00 H 193 | ATOM 193 N TRP 9 -0.250 5.909 -0.214 1.00 0.00 N 194 | ATOM 194 CA TRP 9 0.010 7.019 -1.125 1.00 0.00 C 195 | ATOM 195 C TRP 9 -1.150 7.227 -2.071 1.00 0.00 C 196 | ATOM 196 O TRP 9 -1.623 8.341 -2.285 1.00 0.00 O 197 | ATOM 197 CB TRP 9 1.302 6.721 -1.935 1.00 0.00 C 198 | ATOM 198 CG TRP 9 2.586 6.680 -1.102 1.00 0.00 C 199 | ATOM 199 CD1 TRP 9 3.199 5.517 -0.592 1.00 0.00 C 200 | ATOM 200 CD2 TRP 9 3.326 7.740 -0.619 1.00 0.00 C 201 | ATOM 201 CE2 TRP 9 4.377 7.211 0.171 1.00 0.00 C 202 | ATOM 202 CE3 TRP 9 3.172 9.142 -0.771 1.00 0.00 C 203 | ATOM 203 NE1 TRP 9 4.320 5.826 0.202 1.00 0.00 N 204 | ATOM 204 CZ2 TRP 9 5.289 8.082 0.806 1.00 0.00 C 205 | ATOM 205 CZ3 TRP 9 4.089 9.981 -0.139 1.00 0.00 C 206 | ATOM 206 CH2 TRP 9 5.134 9.460 0.637 1.00 0.00 C 207 | ATOM 207 H TRP 9 0.387 5.126 -0.166 1.00 0.00 H 208 | ATOM 208 HA TRP 9 0.132 7.949 -0.537 1.00 0.00 H 209 | ATOM 209 2HB TRP 9 1.442 7.482 -2.726 1.00 0.00 H 210 | ATOM 210 3HB TRP 9 1.193 5.768 -2.489 1.00 0.00 H 211 | ATOM 211 1HD TRP 9 2.820 4.516 -0.753 1.00 0.00 H 212 | ATOM 212 1HE TRP 9 4.925 5.183 0.725 1.00 0.00 H 213 | ATOM 213 3HE TRP 9 2.364 9.552 -1.361 1.00 0.00 H 214 | ATOM 214 2HZ TRP 9 6.091 7.684 1.410 1.00 0.00 H 215 | ATOM 215 3HZ TRP 9 3.990 11.051 -0.250 1.00 0.00 H 216 | ATOM 216 2HH TRP 9 5.831 10.135 1.110 1.00 0.00 H 217 | ATOM 217 N TRP 10 -1.658 6.204 -2.671 1.00 0.00 N 218 | ATOM 218 CA TRP 10 -2.779 6.405 -3.585 1.00 0.00 C 219 | ATOM 219 C TRP 10 -3.933 7.085 -2.886 1.00 0.00 C 220 | ATOM 220 O TRP 10 -4.524 8.040 -3.384 1.00 0.00 O 221 | ATOM 221 CB TRP 10 -3.237 5.027 -4.137 1.00 0.00 C 222 | ATOM 222 CG TRP 10 -2.213 4.322 -5.032 1.00 0.00 C 223 | ATOM 223 CD1 TRP 10 -1.305 3.327 -4.617 1.00 0.00 C 224 | ATOM 224 CD2 TRP 10 -1.918 4.574 -6.356 1.00 0.00 C 225 | ATOM 225 CE2 TRP 10 -0.842 3.731 -6.729 1.00 0.00 C 226 | ATOM 226 CE3 TRP 10 -2.473 5.491 -7.286 1.00 0.00 C 227 | ATOM 227 NE1 TRP 10 -0.443 2.942 -5.661 1.00 0.00 N 228 | ATOM 228 CZ2 TRP 10 -0.321 3.790 -8.040 1.00 0.00 C 229 | ATOM 229 CZ3 TRP 10 -1.945 5.526 -8.576 1.00 0.00 C 230 | ATOM 230 CH2 TRP 10 -0.886 4.687 -8.950 1.00 0.00 C 231 | ATOM 231 H TRP 10 -1.286 5.279 -2.511 1.00 0.00 H 232 | ATOM 232 HA TRP 10 -2.457 7.063 -4.414 1.00 0.00 H 233 | ATOM 233 2HB TRP 10 -4.169 5.140 -4.723 1.00 0.00 H 234 | ATOM 234 3HB TRP 10 -3.519 4.356 -3.302 1.00 0.00 H 235 | ATOM 235 1HD TRP 10 -1.249 2.946 -3.605 1.00 0.00 H 236 | ATOM 236 1HE TRP 10 0.334 2.272 -5.630 1.00 0.00 H 237 | ATOM 237 3HE TRP 10 -3.285 6.147 -7.004 1.00 0.00 H 238 | ATOM 238 2HZ TRP 10 0.500 3.151 -8.329 1.00 0.00 H 239 | ATOM 239 3HZ TRP 10 -2.361 6.214 -9.299 1.00 0.00 H 240 | ATOM 240 2HH TRP 10 -0.501 4.734 -9.958 1.00 0.00 H 241 | ATOM 241 N TRP 11 -4.309 6.649 -1.731 1.00 0.00 N 242 | ATOM 242 CA TRP 11 -5.424 7.306 -1.056 1.00 0.00 C 243 | ATOM 243 C TRP 11 -5.151 8.780 -0.868 1.00 0.00 C 244 | ATOM 244 O TRP 11 -5.988 9.637 -1.144 1.00 0.00 O 245 | ATOM 245 CB TRP 11 -5.649 6.633 0.327 1.00 0.00 C 246 | ATOM 246 CG TRP 11 -6.123 5.178 0.262 1.00 0.00 C 247 | ATOM 247 CD1 TRP 11 -5.298 4.039 0.366 1.00 0.00 C 248 | ATOM 248 CD2 TRP 11 -7.392 4.703 0.004 1.00 0.00 C 249 | ATOM 249 CE2 TRP 11 -7.325 3.288 -0.041 1.00 0.00 C 250 | ATOM 250 CE3 TRP 11 -8.620 5.375 -0.226 1.00 0.00 C 251 | ATOM 251 NE1 TRP 11 -6.029 2.849 0.184 1.00 0.00 N 252 | ATOM 252 CZ2 TRP 11 -8.490 2.534 -0.305 1.00 0.00 C 253 | ATOM 253 CZ3 TRP 11 -9.757 4.609 -0.478 1.00 0.00 C 254 | ATOM 254 CH2 TRP 11 -9.695 3.209 -0.515 1.00 0.00 C 255 | ATOM 255 H TRP 11 -3.839 5.866 -1.301 1.00 0.00 H 256 | ATOM 256 HA TRP 11 -6.333 7.211 -1.680 1.00 0.00 H 257 | ATOM 257 2HB TRP 11 -6.396 7.201 0.912 1.00 0.00 H 258 | ATOM 258 3HB TRP 11 -4.724 6.691 0.934 1.00 0.00 H 259 | ATOM 259 1HD TRP 11 -4.227 4.082 0.518 1.00 0.00 H 260 | ATOM 260 1HE TRP 11 -5.680 1.885 0.168 1.00 0.00 H 261 | ATOM 261 3HE TRP 11 -8.675 6.455 -0.207 1.00 0.00 H 262 | ATOM 262 2HZ TRP 11 -8.444 1.456 -0.342 1.00 0.00 H 263 | ATOM 263 3HZ TRP 11 -10.702 5.105 -0.648 1.00 0.00 H 264 | ATOM 264 2HH TRP 11 -10.594 2.643 -0.709 1.00 0.00 H 265 | ATOM 265 N TRP 12 -4.006 9.150 -0.404 1.00 0.00 N 266 | ATOM 266 CA TRP 12 -3.743 10.574 -0.223 1.00 0.00 C 267 | ATOM 267 C TRP 12 -3.921 11.328 -1.520 1.00 0.00 C 268 | ATOM 268 O TRP 12 -4.248 10.762 -2.562 1.00 0.00 O 269 | ATOM 269 CB TRP 12 -2.290 10.761 0.297 1.00 0.00 C 270 | ATOM 270 CG TRP 12 -2.035 10.204 1.701 1.00 0.00 C 271 | ATOM 271 CD1 TRP 12 -1.480 8.942 1.997 1.00 0.00 C 272 | ATOM 272 CD2 TRP 12 -2.370 10.764 2.916 1.00 0.00 C 273 | ATOM 273 CE2 TRP 12 -2.013 9.842 3.932 1.00 0.00 C 274 | ATOM 274 CE3 TRP 12 -2.987 11.998 3.246 1.00 0.00 C 275 | ATOM 275 NE1 TRP 12 -1.454 8.698 3.383 1.00 0.00 N 276 | ATOM 276 CZ2 TRP 12 -2.260 10.154 5.287 1.00 0.00 C 277 | ATOM 277 CZ3 TRP 12 -3.215 12.286 4.591 1.00 0.00 C 278 | ATOM 278 CH2 TRP 12 -2.855 11.380 5.598 1.00 0.00 C 279 | ATOM 279 H TRP 12 -3.302 8.465 -0.170 1.00 0.00 H 280 | ATOM 280 HA TRP 12 -4.466 10.987 0.506 1.00 0.00 H 281 | ATOM 281 2HB TRP 12 -2.023 11.834 0.311 1.00 0.00 H 282 | ATOM 282 3HB TRP 12 -1.570 10.309 -0.414 1.00 0.00 H 283 | ATOM 283 1HD TRP 12 -1.161 8.232 1.245 1.00 0.00 H 284 | ATOM 284 1HE TRP 12 -1.143 7.852 3.875 1.00 0.00 H 285 | ATOM 285 3HE TRP 12 -3.275 12.699 2.475 1.00 0.00 H 286 | ATOM 286 2HZ TRP 12 -1.991 9.455 6.065 1.00 0.00 H 287 | ATOM 287 3HZ TRP 12 -3.677 13.226 4.860 1.00 0.00 H 288 | ATOM 288 2HH TRP 12 -3.040 11.633 6.631 1.00 0.00 H 289 | TER 290 | END 291 | -------------------------------------------------------------------------------- /data/fileformats/4HHB.mmtf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liedllab/TopModel/4b4998e58b5f9fb60d93a1d0aced8eabe6df6781/data/fileformats/4HHB.mmtf -------------------------------------------------------------------------------- /data/modelled/config.yaml: -------------------------------------------------------------------------------- 1 | output: 2 | pdb: output.pdb 3 | min_res: 1 4 | max_res: 236 5 | d_amino: [] 6 | # sg: 7 | # pdb: sg_imgt.pdb 8 | # min_res: 2 9 | # max_res: 233 10 | # d_amino: 11 | # - 100 12 | # - 104 13 | # - 107 14 | -------------------------------------------------------------------------------- /data/modelled/test_output: -------------------------------------------------------------------------------- 1 | #Res CHIRAL_00001[L] CHIRAL_00001[D] 2 | 1.000 1 0 3 | 2.000 1 0 4 | 3.000 1 0 5 | 4.000 1 0 6 | 5.000 1 0 7 | 6.000 1 0 8 | 7.000 1 0 9 | 9.000 1 0 10 | 10.000 1 0 11 | 11.000 1 0 12 | 12.000 1 0 13 | 13.000 1 0 14 | 14.000 1 0 15 | 16.000 1 0 16 | 17.000 1 0 17 | 18.000 1 0 18 | 19.000 1 0 19 | 20.000 1 0 20 | 21.000 1 0 21 | 22.000 1 0 22 | 23.000 1 0 23 | 24.000 1 0 24 | 25.000 1 0 25 | 27.000 1 0 26 | 28.000 1 0 27 | 29.000 1 0 28 | 30.000 1 0 29 | 31.000 1 0 30 | 32.000 1 0 31 | 33.000 1 0 32 | 34.000 1 0 33 | 35.000 1 0 34 | 36.000 1 0 35 | 37.000 1 0 36 | 38.000 1 0 37 | 39.000 1 0 38 | 40.000 1 0 39 | 41.000 1 0 40 | 43.000 1 0 41 | 44.000 1 0 42 | 45.000 1 0 43 | 46.000 1 0 44 | 47.000 1 0 45 | 48.000 1 0 46 | 50.000 1 0 47 | 51.000 1 0 48 | 52.000 1 0 49 | 53.000 1 0 50 | 55.000 1 0 51 | 57.000 1 0 52 | 58.000 1 0 53 | 59.000 1 0 54 | 60.000 1 0 55 | 61.000 1 0 56 | 62.000 1 0 57 | 63.000 1 0 58 | 64.000 1 0 59 | 65.000 1 0 60 | 66.000 1 0 61 | 67.000 1 0 62 | 68.000 1 0 63 | 69.000 1 0 64 | 70.000 1 0 65 | 71.000 1 0 66 | 72.000 1 0 67 | 73.000 1 0 68 | 74.000 1 0 69 | 75.000 1 0 70 | 76.000 1 0 71 | 77.000 1 0 72 | 78.000 1 0 73 | 79.000 1 0 74 | 80.000 1 0 75 | 81.000 1 0 76 | 82.000 1 0 77 | 83.000 1 0 78 | 84.000 1 0 79 | 85.000 1 0 80 | 86.000 1 0 81 | 87.000 1 0 82 | 88.000 1 0 83 | 89.000 1 0 84 | 90.000 1 0 85 | 91.000 1 0 86 | 92.000 1 0 87 | 93.000 1 0 88 | 94.000 1 0 89 | 95.000 1 0 90 | 96.000 1 0 91 | 97.000 1 0 92 | 98.000 1 0 93 | 99.000 1 0 94 | 100.000 1 0 95 | 101.000 1 0 96 | 102.000 1 0 97 | 103.000 1 0 98 | 104.000 1 0 99 | 105.000 1 0 100 | 106.000 1 0 101 | 107.000 1 0 102 | 108.000 1 0 103 | 109.000 1 0 104 | 110.000 1 0 105 | 111.000 1 0 106 | 113.000 1 0 107 | 115.000 1 0 108 | 116.000 1 0 109 | 117.000 1 0 110 | 118.000 1 0 111 | 119.000 1 0 112 | 120.000 1 0 113 | 121.000 1 0 114 | 122.000 1 0 115 | 123.000 1 0 116 | 124.000 1 0 117 | 125.000 1 0 118 | 126.000 1 0 119 | 127.000 1 0 120 | 128.000 1 0 121 | 129.000 1 0 122 | 130.000 1 0 123 | 131.000 1 0 124 | 132.000 1 0 125 | 133.000 1 0 126 | 134.000 1 0 127 | 135.000 1 0 128 | 136.000 1 0 129 | 137.000 1 0 130 | 139.000 1 0 131 | 140.000 1 0 132 | 141.000 1 0 133 | 142.000 1 0 134 | 143.000 1 0 135 | 144.000 1 0 136 | 145.000 1 0 137 | 146.000 1 0 138 | 147.000 1 0 139 | 148.000 1 0 140 | 149.000 1 0 141 | 150.000 1 0 142 | 151.000 1 0 143 | 152.000 1 0 144 | 153.000 1 0 145 | 154.000 1 0 146 | 155.000 1 0 147 | 156.000 1 0 148 | 157.000 1 0 149 | 158.000 1 0 150 | 159.000 1 0 151 | 160.000 1 0 152 | 161.000 1 0 153 | 162.000 1 0 154 | 163.000 1 0 155 | 164.000 1 0 156 | 165.000 1 0 157 | 166.000 1 0 158 | 167.000 1 0 159 | 168.000 1 0 160 | 170.000 1 0 161 | 171.000 1 0 162 | 172.000 1 0 163 | 173.000 1 0 164 | 174.000 1 0 165 | 175.000 1 0 166 | 176.000 1 0 167 | 177.000 1 0 168 | 178.000 1 0 169 | 179.000 1 0 170 | 180.000 1 0 171 | 181.000 1 0 172 | 182.000 1 0 173 | 183.000 1 0 174 | 184.000 1 0 175 | 186.000 1 0 176 | 187.000 1 0 177 | 188.000 1 0 178 | 189.000 1 0 179 | 190.000 1 0 180 | 191.000 1 0 181 | 193.000 1 0 182 | 195.000 1 0 183 | 197.000 1 0 184 | 198.000 1 0 185 | 199.000 1 0 186 | 200.000 1 0 187 | 201.000 1 0 188 | 202.000 1 0 189 | 203.000 1 0 190 | 204.000 1 0 191 | 205.000 1 0 192 | 206.000 1 0 193 | 207.000 1 0 194 | 208.000 1 0 195 | 209.000 1 0 196 | 210.000 1 0 197 | 211.000 1 0 198 | 212.000 1 0 199 | 213.000 1 0 200 | 214.000 1 0 201 | 215.000 1 0 202 | 216.000 1 0 203 | 217.000 1 0 204 | 218.000 1 0 205 | 219.000 1 0 206 | 220.000 1 0 207 | 221.000 1 0 208 | 222.000 1 0 209 | 223.000 1 0 210 | 224.000 1 0 211 | 225.000 1 0 212 | 226.000 1 0 213 | 230.000 1 0 214 | 231.000 1 0 215 | 232.000 1 0 216 | 233.000 1 0 217 | 234.000 1 0 218 | 235.000 1 0 219 | 236.000 1 0 220 | 237.000 1 0 221 | -------------------------------------------------------------------------------- /data/modelled/test_sg: -------------------------------------------------------------------------------- 1 | #Res CHIRAL_00001[L] CHIRAL_00001[D] 2 | 1.000 1 0 3 | 2.000 1 0 4 | 3.000 1 0 5 | 4.000 1 0 6 | 5.000 1 0 7 | 6.000 1 0 8 | 8.000 1 0 9 | 9.000 1 0 10 | 10.000 1 0 11 | 11.000 1 0 12 | 12.000 1 0 13 | 13.000 1 0 14 | 15.000 1 0 15 | 16.000 1 0 16 | 17.000 1 0 17 | 18.000 1 0 18 | 19.000 1 0 19 | 20.000 1 0 20 | 21.000 1 0 21 | 22.000 1 0 22 | 23.000 1 0 23 | 24.000 1 0 24 | 26.000 1 0 25 | 27.000 1 0 26 | 28.000 1 0 27 | 29.000 1 0 28 | 30.000 1 0 29 | 31.000 1 0 30 | 32.000 1 0 31 | 33.000 1 0 32 | 34.000 1 0 33 | 35.000 1 0 34 | 36.000 1 0 35 | 37.000 1 0 36 | 38.000 1 0 37 | 39.000 1 0 38 | 40.000 1 0 39 | 42.000 1 0 40 | 43.000 1 0 41 | 44.000 1 0 42 | 45.000 1 0 43 | 46.000 1 0 44 | 47.000 1 0 45 | 49.000 1 0 46 | 50.000 1 0 47 | 51.000 1 0 48 | 52.000 1 0 49 | 54.000 1 0 50 | 56.000 1 0 51 | 57.000 1 0 52 | 58.000 1 0 53 | 59.000 1 0 54 | 60.000 1 0 55 | 61.000 1 0 56 | 62.000 1 0 57 | 63.000 1 0 58 | 64.000 1 0 59 | 65.000 1 0 60 | 66.000 1 0 61 | 67.000 1 0 62 | 68.000 1 0 63 | 69.000 1 0 64 | 70.000 1 0 65 | 71.000 1 0 66 | 72.000 1 0 67 | 73.000 1 0 68 | 74.000 1 0 69 | 75.000 1 0 70 | 76.000 1 0 71 | 77.000 1 0 72 | 78.000 1 0 73 | 79.000 1 0 74 | 80.000 1 0 75 | 81.000 1 0 76 | 82.000 1 0 77 | 83.000 1 0 78 | 84.000 1 0 79 | 85.000 1 0 80 | 86.000 1 0 81 | 87.000 1 0 82 | 88.000 1 0 83 | 89.000 1 0 84 | 90.000 1 0 85 | 91.000 1 0 86 | 92.000 1 0 87 | 93.000 1 0 88 | 94.000 1 0 89 | 95.000 1 0 90 | 96.000 1 0 91 | 97.000 1 0 92 | 98.000 1 0 93 | 99.000 1 0 94 | 100.000 0 1 95 | 101.000 1 0 96 | 102.000 1 0 97 | 103.000 1 0 98 | 104.000 0 1 99 | 105.000 1 0 100 | 106.000 1 0 101 | 107.000 0 1 102 | 108.000 1 0 103 | 109.000 1 0 104 | 110.000 1 0 105 | 112.000 1 0 106 | 114.000 1 0 107 | 115.000 1 0 108 | 116.000 1 0 109 | 117.000 1 0 110 | 118.000 1 0 111 | 119.000 1 0 112 | 120.000 1 0 113 | 121.000 1 0 114 | 122.000 1 0 115 | 123.000 1 0 116 | 124.000 1 0 117 | 125.000 1 0 118 | 126.000 1 0 119 | 127.000 1 0 120 | 128.000 1 0 121 | 129.000 1 0 122 | 130.000 1 0 123 | 131.000 1 0 124 | 132.000 1 0 125 | 133.000 1 0 126 | 134.000 1 0 127 | 135.000 1 0 128 | 137.000 1 0 129 | 138.000 1 0 130 | 139.000 1 0 131 | 140.000 1 0 132 | 141.000 1 0 133 | 142.000 1 0 134 | 143.000 1 0 135 | 144.000 1 0 136 | 145.000 1 0 137 | 146.000 1 0 138 | 147.000 1 0 139 | 148.000 1 0 140 | 149.000 1 0 141 | 150.000 1 0 142 | 151.000 1 0 143 | 152.000 1 0 144 | 153.000 1 0 145 | 154.000 1 0 146 | 155.000 1 0 147 | 156.000 1 0 148 | 157.000 1 0 149 | 158.000 1 0 150 | 159.000 1 0 151 | 160.000 1 0 152 | 161.000 1 0 153 | 162.000 1 0 154 | 163.000 1 0 155 | 164.000 1 0 156 | 165.000 1 0 157 | 166.000 1 0 158 | 168.000 1 0 159 | 169.000 1 0 160 | 170.000 1 0 161 | 171.000 1 0 162 | 172.000 1 0 163 | 173.000 1 0 164 | 174.000 1 0 165 | 175.000 1 0 166 | 176.000 1 0 167 | 177.000 1 0 168 | 178.000 1 0 169 | 179.000 1 0 170 | 180.000 1 0 171 | 181.000 1 0 172 | 182.000 1 0 173 | 184.000 1 0 174 | 185.000 1 0 175 | 186.000 1 0 176 | 187.000 1 0 177 | 188.000 1 0 178 | 189.000 1 0 179 | 191.000 1 0 180 | 193.000 1 0 181 | 195.000 1 0 182 | 196.000 1 0 183 | 197.000 1 0 184 | 198.000 1 0 185 | 199.000 1 0 186 | 200.000 1 0 187 | 201.000 1 0 188 | 202.000 1 0 189 | 203.000 1 0 190 | 204.000 1 0 191 | 205.000 1 0 192 | 206.000 1 0 193 | 207.000 1 0 194 | 208.000 1 0 195 | 209.000 1 0 196 | 210.000 1 0 197 | 211.000 1 0 198 | 212.000 1 0 199 | 213.000 1 0 200 | 214.000 1 0 201 | 215.000 1 0 202 | 216.000 1 0 203 | 217.000 1 0 204 | 218.000 1 0 205 | 219.000 1 0 206 | 220.000 1 0 207 | 221.000 1 0 208 | 222.000 1 0 209 | 223.000 1 0 210 | 224.000 1 0 211 | 228.000 1 0 212 | 229.000 1 0 213 | 230.000 1 0 214 | 231.000 1 0 215 | 232.000 1 0 216 | 233.000 1 0 217 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "setuptools.build_meta" 3 | requires = ["setuptools", "wheel"] 4 | 5 | 6 | [tool.pytest.ini_options] 7 | addopts = "--cov=topmodel" 8 | testpaths = [ 9 | "tests", 10 | ] 11 | 12 | [tool.mypy] 13 | mypy_path = "src" 14 | check_untyped_defs = true 15 | disallow_any_generics = true 16 | ignore_missing_imports = true 17 | no_implicit_optional = true 18 | show_error_codes = true 19 | strict_equality = true 20 | warn_redundant_casts = true 21 | warn_return_any = true 22 | warn_unreachable = true 23 | warn_unused_configs = true 24 | no_implicit_reexport = true 25 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==21.4.0 2 | biopython==1.79 3 | click==8.1.3 4 | coverage==6.4.2 5 | exceptiongroup==1.0.0rc8 6 | hypothesis==6.53.0 7 | iniconfig==1.1.1 8 | numpy==1.23.1 9 | packaging==21.3 10 | -e git+https://github.com/janikkokot/PDBear@fdebbe3184b2965ca283e445e8294c79747bdead#egg=pdbear 11 | pluggy==1.0.0 12 | py==1.11.0 13 | pyparsing==3.0.9 14 | pytest==7.1.2 15 | pytest-cov==3.0.0 16 | pytest-cover==3.0.0 17 | pytest-coverage==0.0 18 | PyYAML==6.0 19 | sortedcontainers==2.4.0 20 | tomli==2.0.1 21 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | coverage==6.4.2 2 | flake8==6.0.0 3 | hypothesis==6.67.1 4 | mypy==1.0.0 5 | mypy-extensions==1.0.0 6 | pytest==7.2.1 7 | pytest-cov==3.0.0 8 | pytest-cover==3.0.0 9 | pytest-coverage==0.0 10 | tox==3.26.0 11 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = protein-topmodel 3 | description = validate molecular structure models 4 | author = Janik Kokot 5 | license = MIT 6 | license_file = LICENSE 7 | platforms = unix, linux, osx, cygwin, win32 8 | version = 1.0.1 9 | 10 | [options] 11 | packages = topmodel, topmodel.util, topmodel.check 12 | install_requires = 13 | Click 14 | colorama 15 | biopython 16 | mmtf-python 17 | scipy 18 | mendeleev 19 | python_requires = >=3.8 20 | package_dir = =src 21 | 22 | [options.entry_points] 23 | console_scripts = 24 | topmodel = topmodel.app:main 25 | 26 | [options.extras_require] 27 | testing = 28 | pytest>=6.0 29 | pytest-cov>=2.0 30 | mypy>=0.910 31 | flake8>=3.9 32 | tox>=3.24 33 | 34 | [option.spackage_data] 35 | topmodel = py.typed 36 | topmodel.util = py.typed 37 | topmodel.check = py.typed 38 | 39 | [flake8] 40 | max-line-length = 100 41 | per-file-ignores = __init__.py:F401 42 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | if __name__ == '__main__': 4 | setup( 5 | name='protein-topmodel', 6 | long_description=open('README.md').read(), 7 | long_description_content_type='text/markdown', 8 | ) 9 | -------------------------------------------------------------------------------- /src/topmodel/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liedllab/TopModel/4b4998e58b5f9fb60d93a1d0aced8eabe6df6781/src/topmodel/__init__.py -------------------------------------------------------------------------------- /src/topmodel/app.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | """Entry point for CLI""" 3 | from __future__ import annotations 4 | from enum import Enum 5 | from pathlib import Path 6 | import os 7 | import subprocess 8 | import tempfile 9 | import textwrap 10 | from typing import Callable 11 | 12 | from Bio.PDB.Structure import Structure 13 | import click 14 | 15 | from topmodel import check 16 | from topmodel.util.utils import ChiralCenters, AmideBonds 17 | from topmodel.util.utils import Clashes, StructuralIrregularity 18 | from topmodel.util.errors import MissingInformationError, PDBCodeError 19 | from topmodel.util.parser import get_structure 20 | 21 | 22 | class Color(Enum): 23 | """Usable colors by colorama/click.""" 24 | RED = 'red' 25 | YELLOW = 'yellow' 26 | MAGENTA = 'magenta' 27 | CYAN = 'cyan' 28 | GREEN = 'green' 29 | 30 | 31 | class App: 32 | """App container""" 33 | def __init__(self, amides: bool, chiralities: bool, clashes: bool): 34 | self.funcs: list[Callable[[Structure], dict[Enum, list[StructuralIrregularity]]]] = [] 35 | self.display: dict[Enum, Color] = {} 36 | if chiralities: 37 | self.funcs.append(check.get_chirality) # type: ignore[attr-defined] 38 | self.display.update( 39 | {ChiralCenters.D: Color.MAGENTA}, 40 | ) 41 | if amides: 42 | self.funcs.append(check.get_amide_stereo) # type: ignore[attr-defined] 43 | self.display.update({ 44 | AmideBonds.CIS: Color.RED, 45 | AmideBonds.CIS_PROLINE: Color.GREEN, 46 | AmideBonds.NON_PLANAR: Color.CYAN, 47 | }) 48 | if clashes: 49 | self.funcs.append(check.get_clashes) # type: ignore[attr-defined] 50 | self.display.update( 51 | {Clashes.VDW: Color.YELLOW}, 52 | ) 53 | try: 54 | self.width = min(80, os.get_terminal_size().columns) 55 | except OSError: 56 | self.width = 80 57 | 58 | self._n_res: int 59 | self._data: dict[Enum, list[StructuralIrregularity]] 60 | self._score: int 61 | 62 | @property 63 | def n_res(self): 64 | try: 65 | return self._n_res 66 | except AttributeError as error: 67 | raise ValueError("Structure has not been processed yet.") from error 68 | 69 | @property 70 | def data(self): 71 | try: 72 | return self._data 73 | except AttributeError as error: 74 | raise ValueError("Structure has not been processed yet.") from error 75 | 76 | @property 77 | def score(self): 78 | try: 79 | return self._score 80 | except AttributeError as error: 81 | raise ValueError("Score has not been computed yet.") from error 82 | 83 | def process_structure(self, structure: Structure) -> None: 84 | """Calculate chosen measures from structure""" 85 | structure_data: dict[Enum, list[StructuralIrregularity]] = {} 86 | for func in self.funcs: 87 | structure_data.update(func(structure)) 88 | 89 | self._n_res = len(list(structure.get_residues())) 90 | self._data = structure_data 91 | return None 92 | 93 | def output_to_terminal(self) -> None: 94 | """Organise the output to terminal""" 95 | if not any(self.data[x] for x in (AmideBonds.CIS, AmideBonds.NON_PLANAR, 96 | AmideBonds.CIS_PROLINE, ChiralCenters.D, Clashes.VDW)): 97 | click.echo(click.style("No irregularities were found.", bold=True, fg='green')) 98 | 99 | for key, values in self.data.items(): 100 | if not values: 101 | continue 102 | key_string = f'\n{key.name} {key.__class__.__name__}' 103 | value_string = ', '.join((x.to_cli() for x in values)) 104 | wrapped = textwrap.wrap(value_string, width=self.width) 105 | try: 106 | click.echo((click.style(key_string, bold=True, fg=self.display[key].value) 107 | + " detected at:")) 108 | except KeyError: 109 | continue 110 | else: 111 | for line in wrapped: 112 | click.echo(line) 113 | 114 | def to_pml(self, structure_path: Path) -> str: 115 | """Transforms output of processed structure into a string which can be used by pymol to open 116 | the structure.""" 117 | commands = [] 118 | if structure_path.exists(): 119 | commands.append(f'load {structure_path}') 120 | else: 121 | commands.append(f'fetch {structure_path}') 122 | commands.append('set cartoon_transparency, 0.3') 123 | commands.append('hide lines') 124 | commands.append('show cartoon') 125 | commands.append('color white') 126 | 127 | for key, values in reversed(self.data.items()): 128 | sel_string = ' or '.join((entry.to_pymol() for entry in values)) 129 | if sel_string: 130 | commands.append(f'select {key.name}, {sel_string}') 131 | try: 132 | color = self.display[key].value 133 | except KeyError: 134 | continue 135 | else: 136 | commands.append(f'color {color}, {key.name}') 137 | commands.append(f'show sticks, {key.name}') 138 | 139 | commands.append('color atomic, not elem C') 140 | commands.append('deselect') 141 | 142 | pml = '\n'.join(commands) 143 | return pml 144 | 145 | def compute_score(self) -> None: 146 | """Assign a score based on the errors in a Structure.""" 147 | raw_score = len(self.data[Clashes.VDW]) 148 | score = 30*raw_score // self.n_res 149 | self._score = int(score) 150 | return None 151 | 152 | def __repr__(self): 153 | return f"{self.__class__.__name__}(width={self.width}px)" 154 | 155 | 156 | @click.command() 157 | @click.argument( 158 | "files", 159 | required=True, 160 | nargs=-1, 161 | ) 162 | @click.option("--amides/--no-amides", is_flag=True, default=True) 163 | @click.option("--chiralities/--no-chiralities", is_flag=True, default=True) 164 | @click.option("--clashes/--no-clashes", is_flag=True, default=True) 165 | @click.option("--score/--no-score", is_flag=True, default=False) 166 | @click.option("--quiet/--verbose", is_flag=True, default=False, help='Start PyMOL quietly') 167 | @click.option("--pymol", is_flag=True, default=False, show_default=True, 168 | help=('Open the structure in PyMOL with the irregularities annotated. ' 169 | 'Requires pymol to be in path.') 170 | ) 171 | def main(files: list[str], 172 | amides: bool, 173 | chiralities: bool, 174 | clashes: bool, 175 | score: bool, 176 | quiet: bool, 177 | pymol: bool, 178 | ) -> None: 179 | """Check structure models for errors""" 180 | 181 | paths = (Path(file) for file in files) 182 | for path in paths: 183 | app = App(amides, chiralities, clashes) 184 | click.echo('-'*app.width) 185 | click.echo(click.style(f'{path.name.upper()}', bold=True)) 186 | try: 187 | struc = get_structure(path) 188 | except PDBCodeError as error: 189 | click.echo(click.style(error, fg='white', bg='red', bold=True)) 190 | raise click.Abort() from error 191 | try: 192 | app.process_structure(struc) 193 | except MissingInformationError as error: 194 | click.echo(click.style(error, fg='white', bg='red', bold=True)) 195 | raise click.Abort() from error 196 | 197 | app.output_to_terminal() 198 | if score: 199 | app.compute_score() 200 | if app.score == 0: 201 | score_color = 'green' 202 | elif app.score < 10: 203 | score_color = 'yellow' 204 | else: 205 | score_color = 'red' 206 | click.echo(click.style('\nCLASH SCORE: ', bold=True) + 207 | click.style(f'{app.score:>3d}', bold=True, fg=score_color)) 208 | if pymol: 209 | pml = app.to_pml(path) 210 | with tempfile.NamedTemporaryFile(mode='w', suffix='.pml') as tmp: 211 | tmp.write(pml) 212 | tmp.flush() 213 | command = 'pymol -Q' if quiet else 'pymol' 214 | subprocess.call((*command.split(), tmp.name)) 215 | if not quiet: 216 | click.echo(f'{"SUMMARY":=^{app.width}}') 217 | app.output_to_terminal() 218 | if score: 219 | click.echo(click.style('\nCLASH SCORE: ', bold=True) + 220 | click.style(f'{app.score:>3d}', bold=True, fg=score_color)) 221 | 222 | click.echo('-'*app.width) 223 | 224 | 225 | if __name__ == '__main__': 226 | main() 227 | -------------------------------------------------------------------------------- /src/topmodel/check/__init__.py: -------------------------------------------------------------------------------- 1 | """Compute different measures on a structure.""" 2 | 3 | from . import clashes 4 | from .clashes import get_clashes 5 | 6 | from . import amide_bond 7 | from .amide_bond import get_amide_stereo 8 | 9 | from . import chirality 10 | from .chirality import get_chirality 11 | -------------------------------------------------------------------------------- /src/topmodel/check/amide_bond.py: -------------------------------------------------------------------------------- 1 | """Calculate and assign `cis` and `trans` label to amide bonds. Bonds that are neither cis nor trans 2 | are assigned the `strange` label.""" 3 | 4 | from __future__ import annotations 5 | from Bio.PDB import Structure, Residue, vectors 6 | import numpy as np 7 | from topmodel.util.errors import ProlineException, MissingInformationError 8 | from topmodel.util.utils import AmideBonds, CoupleIrregularity 9 | 10 | 11 | def get_amide_stereo( 12 | struc: Structure.Structure 13 | ) -> dict[AmideBonds, list[CoupleIrregularity]]: 14 | """Iterates over structure and yields result in a dictionary that maps the label to a list of 15 | Residues.""" 16 | 17 | stereo: dict[AmideBonds, list[CoupleIrregularity]] = {e: [] for e in AmideBonds} 18 | header = struc.get_residues() 19 | tailer = struc.get_residues() 20 | next(tailer) # advance second iterator so tailer is always one step ahead of header 21 | for head, tail in zip(header, tailer): 22 | try: 23 | label = assign_stereo(head, tail) 24 | except ProlineException: 25 | label = AmideBonds.CIS_PROLINE 26 | 27 | stereo[label].append(CoupleIrregularity(head, tail, label.value)) 28 | return stereo 29 | 30 | 31 | def assign_stereo(head: Residue.Residue, tail: Residue.Residue) -> AmideBonds: 32 | """Calculates dihedral angle of amide bond between Residue `head` and `tail`. The angle then is 33 | mapped to the labels `cis`, `trans` or `strange`.""" 34 | try: 35 | angle = vectors.calc_dihedral( 36 | head['CA'].get_vector(), head['C'].get_vector(), 37 | tail['N'].get_vector(), tail['CA'].get_vector(), 38 | ) 39 | except KeyError as error: 40 | head_atoms = {atom.get_name() for atom in head.get_atoms()} 41 | tail_atoms = {atom.get_name() for atom in tail.get_atoms()} 42 | diff = {'C', 'CA', 'N'}.difference(head_atoms.union(tail_atoms)) 43 | diff_str = ", ".join(diff) 44 | raise MissingInformationError( 45 | f'{diff_str} needed to determine the amide bond orientation' 46 | ) from error 47 | 48 | if head['C'] - tail['N'] > 2: 49 | return AmideBonds.TRANS 50 | angle = np.mod(angle, 2*np.pi) 51 | if 5*np.pi/6 <= angle <= 7*np.pi/6: 52 | return AmideBonds.TRANS 53 | if (0 <= angle <= np.pi/6) or (11*np.pi/6 <= angle <= 2*np.pi): 54 | if tail.resname == 'PRO': 55 | raise ProlineException 56 | return AmideBonds.CIS 57 | return AmideBonds.NON_PLANAR 58 | -------------------------------------------------------------------------------- /src/topmodel/check/chirality.py: -------------------------------------------------------------------------------- 1 | """Calculate chirality of an aminoacid.""" 2 | 3 | from __future__ import annotations 4 | 5 | import warnings 6 | from Bio.PDB import vectors, Residue, Structure 7 | import numpy as np 8 | import numpy.typing as npt 9 | from topmodel.util.errors import GlycineException, MissingInformationError 10 | from topmodel.util.utils import ChiralCenters, SingleIrregularity 11 | 12 | 13 | def assign_chirality_amino_acid(residue: Residue.Residue) -> ChiralCenters: 14 | """Assign L/D label to residue.""" 15 | try: 16 | c_alpha = residue['CA'].coord 17 | except KeyError as error: 18 | raise MissingInformationError from error 19 | try: 20 | c_beta = residue['CB'].coord 21 | except KeyError as error: 22 | if residue.resname == 'GLY': 23 | return ChiralCenters.L 24 | else: 25 | raise MissingInformationError from error 26 | n = residue['N'].coord 27 | c = residue['C'].coord 28 | 29 | assignment = np.dot(c_alpha - c_beta, np.cross(n-c_beta, c-c_beta)) 30 | if assignment > 0: 31 | return ChiralCenters.D 32 | elif assignment < 0: 33 | return ChiralCenters.L 34 | raise ValueError 35 | 36 | 37 | def get_chirality( 38 | struc: Structure.Structure 39 | ) -> dict[ChiralCenters, list[SingleIrregularity]]: 40 | """Iterate over structure to yield a defaultdict with `L` and `D` as keys and lists of Residues 41 | as corresponding values.""" 42 | 43 | chirality: dict[ChiralCenters, list[SingleIrregularity]] = {e: [] for e in ChiralCenters} 44 | for residue in struc.get_residues(): 45 | label = assign_chirality_amino_acid(residue) 46 | chirality[label].append( 47 | SingleIrregularity(residue, label.value) 48 | ) 49 | return chirality 50 | 51 | 52 | def _assign_chirality_amino_acid(residue: Residue.Residue) -> ChiralCenters: 53 | """deprecated version. 54 | Assign L/D label to residue.""" 55 | warnings.warn("Deprecated.", DeprecationWarning) 56 | try: 57 | c_alpha = residue['CA'].get_vector().copy() 58 | except KeyError as error: 59 | raise MissingInformationError from error 60 | atoms = {atom.name: (atom.get_vector() - c_alpha) for atom in residue} 61 | 62 | try: 63 | transformer = vectors.rotmat(atoms['HA'], vectors.Vector(0, 0, -1)) 64 | except KeyError as error: 65 | if residue.resname == 'GLY': 66 | raise GlycineException from error 67 | raise MissingInformationError( 68 | "No H-alphas are in the structure which is needed to determine the chiralities" 69 | ) from error 70 | 71 | theta_zero = _get_theta(atoms['N'], transformer) 72 | rotations = {name: np.mod(_get_theta(vec, transformer) - theta_zero, 2*np.pi) 73 | for name, vec in atoms.items()} 74 | 75 | chirality = ChiralCenters.L if rotations['C'] < rotations['CB'] else ChiralCenters.D 76 | return chirality 77 | 78 | 79 | def _get_theta(vec: vectors.Vector, rotation_matrix: npt.NDArray[np.float64]) -> float: 80 | """Deprecated. No longer needed. 81 | Rotate vector so that HA lies at x=0, y=0 and then calculate angle of newly obtained vector.""" 82 | warnings.warn("Deprecated.", DeprecationWarning) 83 | x, y, _ = vec.left_multiply(rotation_matrix) 84 | theta: float = np.arctan2(y, x) 85 | return theta 86 | -------------------------------------------------------------------------------- /src/topmodel/check/clashes.py: -------------------------------------------------------------------------------- 1 | """Compute VDW clashes of a Structure""" 2 | from __future__ import annotations 3 | from collections import defaultdict 4 | from scipy.spatial import KDTree 5 | from mendeleev import fetch 6 | from Bio.PDB import Structure, Entity 7 | 8 | from topmodel.util.utils import Clashes, CoupleIrregularity 9 | 10 | 11 | # perfoms this on import 12 | _df = fetch.fetch_table('elements')[['symbol', 'vdw_radius']] 13 | _df = _df.set_index('symbol') 14 | _df = _df / 100 # conversion from pm to angstrom 15 | VDW_RADII = _df.to_dict()['vdw_radius'] 16 | del _df # so it cant be imported anymore 17 | 18 | 19 | def get_clashes(struc: Structure.Structure) -> dict[Clashes, list[CoupleIrregularity]]: 20 | """Iterates over structure and yields a set of clashes.""" 21 | 22 | atoms = [] # contains references to the atom object 23 | residues = [] # contains references to the residue object 24 | atomic_coordinates = [] 25 | atom_to_residue = {} # will have atom index as key and residue number as value 26 | clashes: dict[int, set[int]] = defaultdict(set) 27 | backbone = {'C', 'CA', 'N', 'O', 'CD'} # CD in prolines 28 | 29 | atoms_so_far = 0 30 | for residue_index, residue in enumerate(struc.get_residues()): 31 | residues.append(residue) 32 | for atom_index, atom in enumerate(heavy(residue), start=atoms_so_far): 33 | atoms.append(atom) # reference to atom object 34 | atomic_coordinates.append(atom.coord) # coordinates of atom object 35 | 36 | atom_to_residue[atom_index] = residue_index # mapping of atom index to res index 37 | atoms_so_far += sum(1 for _ in heavy(residue)) # keep count for proper atom indexing 38 | 39 | tree = KDTree(atomic_coordinates) 40 | close_points = tree.query_pairs(5) # within 5 angstroms 41 | 42 | for a, b in close_points: 43 | res_a, res_b = atom_to_residue[a], atom_to_residue[b] 44 | if res_a != res_b and \ 45 | (res_a not in clashes or res_b not in clashes[res_a]) and \ 46 | (res_b - res_a != 1 or ((atoms[a].name in backbone) != (atoms[b].name in backbone))): 47 | # distance of pair is only calculated if a and b: 48 | # not same residue 49 | # and not already computed 50 | # if next to each other, make sure not both are in the backbone 51 | distance = atoms[a] - atoms[b] 52 | combined_radii = VDW_RADII[atoms[a].element] + VDW_RADII[atoms[b].element] 53 | 54 | if distance < combined_radii - 0.5 and not \ 55 | (residues[res_a].resname == 'CYX' and residues[res_b].resname == 'CYX'): 56 | # ignore 'clash' if its a 'clash' between cysteine disulfide bridges 57 | clashes[res_a].add(res_b) 58 | 59 | return {Clashes.VDW: [CoupleIrregularity(residues[a], residues[b], Clashes.VDW.value) 60 | for a, values in sorted(clashes.items()) 61 | for b in sorted(values)] 62 | } 63 | 64 | 65 | def heavy(entity: Entity.Entity): 66 | """Generator that excludes Hydrogens.""" 67 | for atom in entity.get_atoms(): 68 | if atom.element != 'H': 69 | yield atom 70 | -------------------------------------------------------------------------------- /src/topmodel/check/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liedllab/TopModel/4b4998e58b5f9fb60d93a1d0aced8eabe6df6781/src/topmodel/check/py.typed -------------------------------------------------------------------------------- /src/topmodel/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liedllab/TopModel/4b4998e58b5f9fb60d93a1d0aced8eabe6df6781/src/topmodel/py.typed -------------------------------------------------------------------------------- /src/topmodel/util/MMTFParser.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from Bio.PDB.mmtf import MMTFParser as BioMMTFParser 3 | 4 | 5 | class TopModelMMTFParser(BioMMTFParser): 6 | """Wrap MMTFParser from BioPython to fullfill the Parser Protocol.""" 7 | def get_structure(self, struc_id: Any, filename: str): 8 | """Get a structure from a file - given a file path.""" 9 | return super().get_structure(filename) 10 | -------------------------------------------------------------------------------- /src/topmodel/util/__init__.py: -------------------------------------------------------------------------------- 1 | from . import parser 2 | from . import errors 3 | from . import utils 4 | -------------------------------------------------------------------------------- /src/topmodel/util/errors.py: -------------------------------------------------------------------------------- 1 | class ProlineException(Exception): 2 | """Prolines are more likely to be in cis-conformation, especially if they are preceded by 3 | Glycine or an aromatic residue.""" 4 | 5 | 6 | class GlycineException(Exception): 7 | """Glycine has no chiral centre.""" 8 | 9 | 10 | class MissingInformationError(Exception): 11 | """Raised when needed information is missing in Structure.""" 12 | 13 | 14 | class PDBCodeError(Exception): 15 | """Raised when the Code is not a valid PDBCode.""" 16 | -------------------------------------------------------------------------------- /src/topmodel/util/parser.py: -------------------------------------------------------------------------------- 1 | """Handle the loading of the Structure from User input.""" 2 | from __future__ import annotations 3 | from pathlib import Path 4 | import re 5 | import tempfile 6 | from typing import Protocol, runtime_checkable 7 | import warnings 8 | from Bio.PDB.MMCIFParser import MMCIFParser 9 | from Bio.PDB import PDBParser, PDBList, PDBExceptions 10 | from Bio.PDB.Structure import Structure 11 | 12 | from .errors import PDBCodeError 13 | from .utils import BlockSTDOUT, SelectiveStructure 14 | from .MMTFParser import TopModelMMTFParser 15 | 16 | 17 | @runtime_checkable 18 | class Parser(Protocol): 19 | """Parser Protocol which the other functions depend on.""" 20 | def get_structure(self, identifier: str, filename: str) -> Structure: 21 | """get structure from filename.""" 22 | 23 | 24 | def get_parser(filename: Path | str) -> Parser: 25 | """return appropriate parser depending on filetype. 26 | :param filename: str 27 | 28 | :returns: Parser 29 | 30 | :raises: ValueError 31 | if no appropriate parser could be assigned.""" 32 | filename = str(filename) 33 | suffix = filename.rsplit('.', maxsplit=1)[-1].lower() 34 | parser: Parser 35 | if suffix in {'mmcif', 'cif'}: 36 | parser = MMCIFParser() 37 | elif suffix == 'mmtf': 38 | parser = TopModelMMTFParser() 39 | elif suffix == 'pdb': 40 | parser = PDBParser() 41 | else: 42 | raise ValueError(f'No parser available for {suffix} format') 43 | return parser 44 | # match filename.rsplit('.', maxsplit=1)[-1]: 45 | # case 'mmcif' | 'cif': 46 | # parser = MMCIFParser() 47 | # case 'mmtf': 48 | # parser = MMTFParser() 49 | # case 'pdb': 50 | # parser = PDBParser() 51 | # case ending: 52 | # raise ValueError(f"No parser available for {ending} format") 53 | 54 | 55 | class PDBCode(str): 56 | _PATTERN = re.compile('[0-9]{1}[0-9a-zA-Z]{3}') 57 | 58 | @property 59 | def valid(self) -> bool: 60 | return bool(self._PATTERN.fullmatch(self)) 61 | 62 | 63 | def from_database(code: Path | str, directory: Path | str) -> Path: 64 | """Retrieve a code from the PDB database. 65 | 66 | :param code: str 67 | PDB code 68 | :param directory: str 69 | path to directory where the file should be downloaded to. 70 | 71 | :returns: str 72 | path to downloaded file 73 | 74 | :raises: KeyError if no PDB was downloaded.""" 75 | code = PDBCode(code) 76 | if not code.valid: 77 | raise PDBCodeError('Invalid PDB access code') 78 | directory = Path(directory) 79 | 80 | with BlockSTDOUT(): 81 | database = PDBList() 82 | database.retrieve_pdb_file(code, pdir=str(directory)) 83 | try: 84 | file = next(directory.glob('*')) 85 | except StopIteration: 86 | raise KeyError(f'Desired structure \"{code}\" does not exist or could not be retrieved')\ 87 | from None 88 | return file 89 | 90 | 91 | def struc_from_file(filename: Path | str, parser: Parser) -> Structure: 92 | """Construct a Structure given the file and a parser. 93 | 94 | :param filename: str 95 | path to file 96 | :param parser: Parser 97 | Parser that can construct the Structure object. 98 | 99 | :returns: Structure""" 100 | stem = Path(filename).stem 101 | with warnings.catch_warnings(): 102 | warnings.simplefilter("ignore", category=PDBExceptions.PDBConstructionWarning) 103 | structure = parser.get_structure(stem, str(filename)) 104 | return structure 105 | 106 | 107 | def get_structure(filename: Path | str) -> Structure: 108 | """Get Structure from filename or PDB Code. Downloads the files from the PDB into a temporary 109 | directory. 110 | 111 | :param filename: str 112 | path to file or PDB code. 113 | 114 | :returns: Structure""" 115 | with tempfile.TemporaryDirectory() as directory: 116 | try: 117 | parser: Parser = get_parser(filename) 118 | except ValueError: 119 | filename = from_database(filename, directory) 120 | parser = get_parser(filename) 121 | 122 | structure: Structure = struc_from_file(filename, parser) 123 | # replace get_residues() function 124 | structure.__class__ = SelectiveStructure 125 | return structure 126 | 127 | 128 | def main(): 129 | """Main.""" 130 | pass 131 | 132 | 133 | if __name__ == '__main__': 134 | raise SystemExit(main()) 135 | -------------------------------------------------------------------------------- /src/topmodel/util/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liedllab/TopModel/4b4998e58b5f9fb60d93a1d0aced8eabe6df6781/src/topmodel/util/py.typed -------------------------------------------------------------------------------- /src/topmodel/util/utils.py: -------------------------------------------------------------------------------- 1 | """Enumerated Labels and custom exception and errors.""" 2 | from __future__ import annotations 3 | import os 4 | import sys 5 | from typing import Protocol, runtime_checkable 6 | from enum import Enum 7 | from Bio.PDB.Residue import Residue 8 | from Bio.PDB.Structure import Structure 9 | from Bio.SeqUtils import seq1 10 | 11 | 12 | class ChiralCenters(Enum): 13 | """Enumeration of L/D Chirality""" 14 | L = 0 15 | D = 10 16 | 17 | 18 | class AmideBonds(Enum): 19 | """Enumeration to handle trans, cis stereo isomerism""" 20 | TRANS = 0 21 | CIS_PROLINE = 1 22 | NON_PLANAR = 2 23 | CIS = 10 24 | 25 | 26 | class Clashes(Enum): 27 | """Enumeration for clashes.""" 28 | VDW = 1 29 | 30 | 31 | @runtime_checkable 32 | class StructuralIrregularity(Protocol): 33 | """Irregularity interface.""" 34 | score: float 35 | 36 | def to_pymol(self): 37 | ... 38 | 39 | def to_cli(self): 40 | ... 41 | 42 | 43 | class CoupleIrregularity: 44 | """Handles irregularities that depend on two residues.""" 45 | def __init__(self, res_a: Residue, res_b: Residue, score): 46 | self.res_a = SingleIrregularity(res_a, 0) 47 | self.res_b = SingleIrregularity(res_b, 0) 48 | self.score = score 49 | 50 | def to_pymol(self) -> str: 51 | return f'{self.res_a.to_pymol()} or {self.res_b.to_pymol()}' 52 | 53 | def to_cli(self) -> str: 54 | return f'{self.res_a.to_cli()}-{self.res_b.to_cli()}' 55 | 56 | def __repr__(self): 57 | cls = self.__class__ 58 | return f'{cls.__name__}({self.res_a}, {self.res_b}, {self.score!r})' 59 | 60 | 61 | class SingleIrregularity: 62 | """Handles irregularities that only depend on one residue.""" 63 | def __init__(self, residue: Residue, score): 64 | self.code = seq1(residue.get_resname()) 65 | _, _, chain, (_, number, letter) = residue.get_full_id() 66 | self.number = number 67 | self.letter = letter.strip() 68 | self.chain = chain 69 | self.score = score 70 | 71 | def to_pymol(self) -> str: 72 | """Convert to pymol selectable string.""" 73 | return f'(resid {self.number}{self.letter} and chain {self.chain})' 74 | 75 | def to_cli(self) -> str: 76 | """Convert to displayable string in CLI.""" 77 | return f'{self.code}{self.number:03}{self.letter}({self.chain})' 78 | 79 | def __repr__(self): 80 | cls = self.__class__ 81 | return (f'{cls.__name__}({self.code!r}, ' 82 | f'{self.number!r}{self.letter!r}, ' 83 | f'{self.chain!r}, {self.score!r})') 84 | 85 | def __str__(self): 86 | cls = self.__class__ 87 | return (f'{cls.__name__}({self.code!r}, ' 88 | f'{self.number!r}{self.letter!r}, ' 89 | f'{self.chain!r})') 90 | 91 | 92 | class BlockSTDOUT: 93 | """Context manager to block all stdout and stderr calls. 94 | Usefull for library methods that print information instead of using warnings.""" 95 | def __init__(self): 96 | self._original_stdout = sys.stdout 97 | self._original_stderr = sys.stderr 98 | 99 | def __enter__(self): 100 | sys.stdout = open(os.devnull, 'w', encoding='utf8') 101 | sys.stderr = open(os.devnull, 'w', encoding='utf8') 102 | 103 | def __exit__(self, exc_type, exc_val, exc_tb): 104 | sys.stdout.close() 105 | sys.stderr.close() 106 | sys.stdout = self._original_stdout 107 | sys.stderr = self._original_stderr 108 | 109 | 110 | class SelectiveStructure(Structure): 111 | """Wrap Structure functionality with selective output of residues.""" 112 | def get_residues(self): 113 | """return only non heteroatoms.""" 114 | for chain in self.get_chains(): 115 | for residue in chain: 116 | if residue.resname in {'NME', 'HOH', 'ACE', 'WAT'}: 117 | continue 118 | hetero, *_ = residue.get_id() 119 | if hetero.strip() != '': 120 | continue 121 | if residue.resname in {'NME', 'HOH', 'ACE', 'WAT'}: 122 | continue 123 | yield residue 124 | -------------------------------------------------------------------------------- /tests/test_app.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from click.testing import CliRunner 3 | from topmodel import app as application 4 | from topmodel.util.parser import get_structure 5 | 6 | 7 | file = './data/modelled/output.pdb' 8 | struc = get_structure(file) 9 | 10 | 11 | def test_output_failure(): 12 | app = application.App(True, True, True) 13 | with pytest.raises(ValueError): 14 | app.output_to_terminal() 15 | 16 | 17 | def test_score_method_failure(): 18 | app = application.App(True, False, False) 19 | with pytest.raises(ValueError): 20 | app.compute_score() 21 | 22 | 23 | def test_score_attr_failure(): 24 | app = application.App(True, False, False) 25 | with pytest.raises(ValueError): 26 | app.score 27 | 28 | 29 | def test_output_exists(capsys): 30 | app = application.App(True, False, False) 31 | app.process_structure(struc) 32 | app.output_to_terminal() 33 | assert len(capsys.readouterr().out) > 0 34 | 35 | 36 | @pytest.mark.parametrize('file,exit_code', 37 | [[file, 0], 38 | ['./data/alanine_dipeptide/1.pdb', 0], 39 | ['x,asd', 1], 40 | ['./data/fileformats/7SG5_model.pdb', 0]], # now works 41 | ) 42 | def test_app_main_correct_imput(file, exit_code): 43 | runner = CliRunner() 44 | result = runner.invoke(application.main, 45 | args=f"{file} --score", 46 | ) 47 | assert len(result.output) > 0 48 | assert result.exit_code == exit_code 49 | -------------------------------------------------------------------------------- /tests/test_check/test_amide_bond.py: -------------------------------------------------------------------------------- 1 | from itertools import chain 2 | import warnings 3 | import pytest 4 | from Bio.PDB import PDBParser 5 | from Bio.PDB.PDBExceptions import PDBConstructionWarning 6 | from topmodel.check import amide_bond 7 | from topmodel.util.utils import AmideBonds, StructuralIrregularity 8 | from topmodel.util.errors import MissingInformationError 9 | from topmodel.util.parser import get_structure as get_clean_structure 10 | 11 | 12 | parser = PDBParser() 13 | 14 | 15 | def get_plain_structure(file): 16 | with warnings.catch_warnings(): 17 | warnings.simplefilter('ignore', category=PDBConstructionWarning) 18 | struc = parser.get_structure('', file) 19 | return struc 20 | 21 | 22 | @pytest.mark.parametrize("pdb_file,expected", [ 23 | ('./data/alanine_dipeptide/1.pdb', AmideBonds.TRANS), 24 | ('./data/alanine_dipeptide/2.pdb', AmideBonds.TRANS), 25 | ('./data/alanine_dipeptide/3.pdb', AmideBonds.TRANS), 26 | ('./data/alanine_dipeptide/4.pdb', AmideBonds.TRANS), 27 | ('./data/alanine_dipeptide/5.pdb', AmideBonds.CIS), 28 | ('./data/alanine_dipeptide/6.pdb', AmideBonds.CIS), 29 | ('./data/alanine_dipeptide/7.pdb', AmideBonds.CIS), 30 | ('./data/alanine_dipeptide/8.pdb', AmideBonds.CIS), 31 | ]) 32 | @pytest.mark.parametrize("get_structure", [get_plain_structure, get_clean_structure]) 33 | def test_assign_alanine_dipeptide(pdb_file, expected, get_structure): 34 | """test alanine dipeptide.""" 35 | struc = get_structure(pdb_file) 36 | a, b = struc.get_residues() 37 | assert amide_bond.assign_stereo(a, b) is expected 38 | 39 | 40 | @pytest.mark.parametrize("get_structure", [get_plain_structure, get_clean_structure]) 41 | class TestGet: 42 | file = './data/modelled/output.pdb' 43 | 44 | def test_output_type(self, get_structure): 45 | struc = get_structure(self.file) 46 | stereo = amide_bond.get_amide_stereo(struc) 47 | """make sure output format is correct.""" 48 | assert isinstance(stereo, dict) 49 | 50 | def test_output_keys(self, get_structure): 51 | struc = get_structure(self.file) 52 | stereo = amide_bond.get_amide_stereo(struc) 53 | assert all(isinstance(x, AmideBonds) for x in stereo) 54 | 55 | def test_output_values(self, get_structure): 56 | struc = get_structure(self.file) 57 | stereo = amide_bond.get_amide_stereo(struc) 58 | assert all(isinstance(x, StructuralIrregularity) for x in chain(*stereo.values())) 59 | 60 | def test_get_output_length(self, get_structure): 61 | struc = get_structure(self.file) 62 | stereo = amide_bond.get_amide_stereo(struc) 63 | len_values = sum(len(x) for x in stereo.values()) 64 | len_structure = len(list(struc.get_residues())) 65 | # 1 less bond than residues 66 | assert len_values == (len_structure - 1) 67 | 68 | 69 | class Test7SG5: 70 | file = './data/fileformats/7SG5_model.pdb' 71 | 72 | def test_7SG5_failure(self): 73 | struc = get_plain_structure(self.file) 74 | *_, a, b = struc.get_residues() 75 | with pytest.raises(MissingInformationError): 76 | amide_bond.assign_stereo(a, b) 77 | 78 | def test_7SG5_and_solution(self): 79 | struc = get_clean_structure(self.file) 80 | *_, a, b = struc.get_residues() 81 | amide_bond.assign_stereo(a, b) 82 | 83 | def test_7SG5_number_cis_amides(self): 84 | struc = get_clean_structure(self.file) 85 | stereo = amide_bond.get_amide_stereo(struc) 86 | assert len(stereo[AmideBonds.CIS]) == 3 87 | -------------------------------------------------------------------------------- /tests/test_check/test_chirality.py: -------------------------------------------------------------------------------- 1 | from itertools import chain 2 | import warnings 3 | import pytest 4 | from Bio.PDB import PDBParser 5 | from Bio.PDB.PDBExceptions import PDBConstructionWarning 6 | 7 | from topmodel.check import chirality 8 | from topmodel.util.utils import ChiralCenters, StructuralIrregularity 9 | from topmodel.util.errors import MissingInformationError 10 | from topmodel.util.parser import get_structure as get_clean_structure 11 | 12 | 13 | parser = PDBParser() 14 | 15 | 16 | def get_plain_structure(file): 17 | with warnings.catch_warnings(): 18 | warnings.simplefilter('ignore', category=PDBConstructionWarning) 19 | struc = parser.get_structure('', file) 20 | return struc 21 | 22 | 23 | @pytest.mark.parametrize("pdb_file,expected", [ 24 | ('./data/alanine_dipeptide/1.pdb', (ChiralCenters.L, ChiralCenters.L)), 25 | ('./data/alanine_dipeptide/2.pdb', (ChiralCenters.L, ChiralCenters.D)), 26 | ('./data/alanine_dipeptide/3.pdb', (ChiralCenters.D, ChiralCenters.L)), 27 | ('./data/alanine_dipeptide/4.pdb', (ChiralCenters.D, ChiralCenters.D)), 28 | ('./data/alanine_dipeptide/5.pdb', (ChiralCenters.L, ChiralCenters.L)), 29 | ('./data/alanine_dipeptide/6.pdb', (ChiralCenters.L, ChiralCenters.D)), 30 | ('./data/alanine_dipeptide/7.pdb', (ChiralCenters.D, ChiralCenters.L)), 31 | ('./data/alanine_dipeptide/8.pdb', (ChiralCenters.D, ChiralCenters.D)), 32 | ]) 33 | @pytest.mark.parametrize("get_structure", [get_plain_structure, get_clean_structure]) 34 | def test_assign_alanine_dipeptide(pdb_file, expected, get_structure): 35 | """test alanine dipeptide.""" 36 | struc = get_structure(pdb_file) 37 | a, b = struc.get_residues() 38 | chiral_info = chirality.assign_chirality_amino_acid(a), chirality.assign_chirality_amino_acid(b) 39 | assert chiral_info == expected 40 | 41 | 42 | @pytest.mark.parametrize("get_structure", [get_plain_structure, get_clean_structure]) 43 | class TestSGMT: 44 | file = './data/modelled/sg_imgt_protonated.pdb' 45 | 46 | def test_output_type(self, get_structure): 47 | struc = get_structure(self.file) 48 | chiral = chirality.get_chirality(struc) 49 | assert isinstance(chiral, dict) 50 | 51 | def test_output_keys(self, get_structure): 52 | struc = get_structure(self.file) 53 | chiral = chirality.get_chirality(struc) 54 | assert all(isinstance(key, ChiralCenters) for key in chiral) 55 | 56 | def test_output_values(self, get_structure): 57 | struc = get_structure(self.file) 58 | chiral = chirality.get_chirality(struc) 59 | assert all(isinstance(val, StructuralIrregularity) for val in chain(*chiral.values())) 60 | 61 | def test_output_length(self, get_structure): 62 | struc = get_structure(self.file) 63 | chiral = chirality.get_chirality(struc) 64 | len_values = sum(len(x) for x in chiral.values()) 65 | len_structure = len(list(struc.get_residues())) 66 | assert len_values == len_structure 67 | 68 | def test_found_d_aminoacids_length(self, get_structure): 69 | """right answer was checked using moe and cpptraj.""" 70 | struc = get_structure(self.file) 71 | chiral = chirality.get_chirality(struc) 72 | assert len(chiral[ChiralCenters.D]) == 3 73 | 74 | def test_found_d_aminacids(self, get_structure): 75 | """correct answer was checked with moe.""" 76 | # cpptraj assigns 3 different residues? 77 | struc = get_structure(self.file) 78 | chiral = chirality.get_chirality(struc) 79 | found = {109, 112, 115} 80 | expected = {calculated.number for calculated in chiral[ChiralCenters.D]} 81 | assert found == expected 82 | 83 | 84 | class Test7SG5: 85 | file = './data/fileformats/7SG5_model_complex.pdb' 86 | struc = get_clean_structure(file) 87 | chiral = chirality.get_chirality(struc) 88 | 89 | def test_found_d_aminoacids_length(self): 90 | """right answer was checked using moe and cpptraj.""" 91 | assert len(self.chiral[ChiralCenters.D]) == 4 92 | 93 | def test_found_d_aminacids(self): 94 | """correct answer was checked with moe and cpptraj.""" 95 | found = {216, 218, 221, 222} 96 | expected = {calculated.number for calculated in self.chiral[ChiralCenters.D]} 97 | assert found == expected 98 | 99 | def test_error_nme(self): 100 | """CA needed to determine the chirality.""" 101 | struc = get_plain_structure(self.file) 102 | with pytest.raises(MissingInformationError): 103 | chirality.get_chirality(struc) 104 | 105 | 106 | # old implementation raised an error, which now no longer works 107 | @pytest.mark.xfail 108 | @pytest.mark.parametrize("get_structure", [get_plain_structure, get_clean_structure]) 109 | def test_failure_missing_ha(get_structure): 110 | struc = get_structure('./data/modelled/sg_imgt.pdb') 111 | with pytest.raises(MissingInformationError): 112 | chirality.get_chirality(struc) 113 | -------------------------------------------------------------------------------- /tests/test_check/test_clashes.py: -------------------------------------------------------------------------------- 1 | """only small test set to check the overall return format""" 2 | from itertools import chain 3 | from enum import Enum 4 | from topmodel.util.utils import StructuralIrregularity 5 | from topmodel.util.parser import get_structure 6 | from topmodel.check import clashes 7 | 8 | 9 | structure = get_structure('./data/clashes/tryptophan_helix.pdb') 10 | found = clashes.get_clashes(structure) 11 | 12 | 13 | def test_type(): 14 | assert isinstance(found, dict) 15 | 16 | 17 | def test_keys(): 18 | assert all(isinstance(key, Enum) for key in found) 19 | 20 | 21 | def test_values(): 22 | assert all(isinstance(value, StructuralIrregularity) 23 | for value in chain(*found.values())) 24 | -------------------------------------------------------------------------------- /tests/test_util/test_parser.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from hypothesis import given, strategies as st 3 | from topmodel.util import parser as tm_parser 4 | from topmodel.util.errors import PDBCodeError 5 | 6 | 7 | @pytest.mark.parametrize('filename', [ 8 | './data/fileformats/7lka.cif', 9 | './data/fileformats/4HHB.mmtf', 10 | './data/fileformats/7SG5_model.pdb']) 11 | def test_parser_protocol(filename): 12 | parser = tm_parser.get_parser(filename) 13 | assert isinstance(parser, tm_parser.Parser) 14 | 15 | 16 | def test_parser_failure(): 17 | with pytest.raises(ValueError): 18 | tm_parser.get_parser('test.xyz') 19 | 20 | 21 | @given(code=st.from_regex('[0-9]{1}[0-9a-zA-Z]{3}', fullmatch=True)) 22 | def test_valid_pdb_code(code): 23 | assert tm_parser.PDBCode(code).valid 24 | 25 | 26 | def test_db_failure(tmpdir): 27 | with pytest.raises(KeyError): 28 | tm_parser.from_database('9zzz', tmpdir) 29 | 30 | 31 | def test_db(tmpdir): 32 | tm_parser.from_database('1igy', tmpdir) 33 | 34 | 35 | def test_get_structure_pdb_retrieve(): 36 | struc = tm_parser.get_structure('7lka') 37 | struc2 = tm_parser.get_structure('./data/fileformats/7lka.cif') 38 | assert struc == struc2 39 | 40 | 41 | def test_get_structure_failure(): 42 | with pytest.raises(PDBCodeError): 43 | tm_parser.get_structure('test.xyz') 44 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 3.8.1 3 | envlist = py38, py39, py310, flake8, mypy 4 | isolated_build = true 5 | 6 | 7 | [gh-actions] 8 | python = 9 | 3.8: py38 10 | 3.9: py39 11 | 3.10: py310, 12 | 3.11: py311, flake8 13 | 14 | 15 | [testenv] 16 | setenv = 17 | PYTHONPATH = {toxinidir} 18 | deps = 19 | -r{toxinidir}/requirements_dev.txt 20 | commands = 21 | pytest --basetemp={envtmpdir} 22 | 23 | 24 | [testenv:flake8] 25 | basepython = python3.11 26 | deps = flake8 27 | commands = flake8 src tests 28 | 29 | 30 | [testenv:mypy] 31 | basepython = python3.11 32 | deps = 33 | -r{toxinidir}/requirements_dev.txt 34 | commands = mypy src 35 | --------------------------------------------------------------------------------