From 84d6f92fcd89edb757056a164415166de2aab204 Mon Sep 17 00:00:00 2001 From: Patrick Kunzmann Date: Thu, 18 Jul 2024 11:22:41 +0200 Subject: [PATCH] Use linter/formatter --- .gitignore | 1 - doc/conf.py | 60 +++--- doc/figure_scripts/api.py | 15 +- doc/figure_scripts/cover_structure.py | 23 +-- doc/figure_scripts/fragments.py | 29 +-- doc/figure_scripts/rotation_freedom.py | 24 +-- doc/figure_scripts/util.py | 6 +- pyproject.toml | 41 +++- setup.py | 121 +++++++----- src/hydride/__init__.py | 2 +- src/hydride/add.py | 65 +++---- src/hydride/charge.py | 131 ++++++------- src/hydride/cli.py | 231 +++++++++++----------- src/hydride/fragments.py | 139 +++++++------- src/hydride/names.py | 27 +-- tests/conftest.py | 13 +- tests/test_add.py | 126 ++++++------ tests/test_charge.py | 23 +-- tests/test_cli.py | 256 +++++++++++-------------- tests/test_fragments.py | 177 +++++++++-------- tests/test_names.py | 23 ++- tests/test_relax.py | 161 ++++++++-------- tests/util.py | 9 +- 23 files changed, 876 insertions(+), 827 deletions(-) diff --git a/.gitignore b/.gitignore index 23c1f2d..c137f86 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ htmlcov # Ignore all compiled python files (e.g. from running the unit tests) *.pyc *.pyo -*.py{} *.py-e # Ignore files created via Cython diff --git a/doc/conf.py b/doc/conf.py index 313fcef..6f90ebf 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -7,17 +7,17 @@ try: import numpy as np import pyximport + pyximport.install( build_in_temp=False, - setup_args={"include_dirs":np.get_include()}, - language_level=3 + setup_args={"include_dirs": np.get_include()}, + language_level=3, ) except ImportError: pass -from os.path import realpath, dirname, join, isdir, isfile, basename -from os import listdir, makedirs import sys +from os.path import dirname, join, realpath doc_path = dirname(realpath(__file__)) # Include hydride/src in PYTHONPATH @@ -25,6 +25,7 @@ package_path = join(dirname(doc_path), "src") sys.path.insert(0, package_path) import hydride + # Include gecos/doc in PYTHONPATH # in order to import modules for plot genaration etc. sys.path.insert(0, doc_path) @@ -32,12 +33,14 @@ #### General #### -extensions = ["sphinx.ext.autodoc", - "sphinx.ext.autosummary", - "sphinx.ext.doctest", - "sphinx.ext.mathjax", - "sphinx.ext.viewcode", - "numpydoc"] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.doctest", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", + "numpydoc", +] templates_path = ["templates"] source_suffix = [".rst"] @@ -62,25 +65,26 @@ html_theme = "alabaster" html_static_path = ["static"] -html_css_files = [ - "hydride.css", - "fonts.css" -] +html_css_files = ["hydride.css", "fonts.css"] html_favicon = "static/assets/hydride_icon_32p.png" htmlhelp_basename = "HydrideDoc" -html_sidebars = {"**": ["about.html", - "navigation.html", - "relations.html", - "searchbox.html", - "donate.html"]} +html_sidebars = { + "**": [ + "about.html", + "navigation.html", + "relations.html", + "searchbox.html", + "donate.html", + ] +} html_theme_options = { - "description" : "Adding hydrogen atoms to molecular models", - "logo" : "assets/hydride_logo.png", - "logo_name" : "false", - "github_user" : "biotite-dev", - "github_repo" : "hydride", - "github_type" : "star", - "github_banner" : "true", - "page_width" : "85%", - "fixed_sidebar" : "true" + "description": "Adding hydrogen atoms to molecular models", + "logo": "assets/hydride_logo.png", + "logo_name": "false", + "github_user": "biotite-dev", + "github_repo": "hydride", + "github_type": "star", + "github_banner": "true", + "page_width": "85%", + "fixed_sidebar": "true", } diff --git a/doc/figure_scripts/api.py b/doc/figure_scripts/api.py index 94f556e..c3108b0 100644 --- a/doc/figure_scripts/api.py +++ b/doc/figure_scripts/api.py @@ -5,13 +5,12 @@ """ import tempfile +import ammolite import biotite.structure as struc -import biotite.structure.io.mol as mol import biotite.structure.info as info -import ammolite +import biotite.structure.io.mol as mol from util import COLORS, init_pymol_parameters - ZOOM = 1.5 PNG_SIZE = (400, 400) @@ -63,7 +62,7 @@ def color_atoms(pymol_object, atom_array): print("\nEND OF SNIPPET\n") pymol_heavy = ammolite.PyMOLObject.from_structure(molecule, "heavy") -#pymol_heavy.orient() +# pymol_heavy.orient() pymol_heavy.zoom(buffer=ZOOM) color_atoms(pymol_heavy, molecule) ammolite.cmd.png("api_01.png", *PNG_SIZE) @@ -168,9 +167,10 @@ def color_atoms(pymol_object, atom_array): print("\nEND OF SNIPPET\n", end="") -import matplotlib.pyplot as plt import matplotlib as mpl -plt.rcParams['axes.prop_cycle'] = mpl.cycler(color=[COLORS["N"]]) +import matplotlib.pyplot as plt + +plt.rcParams["axes.prop_cycle"] = mpl.cycler(color=[COLORS["N"]]) ######################################################################## @@ -201,6 +201,7 @@ def color_atoms(pymol_object, atom_array): fig.savefig("api_04.png") import biotite.structure.info as info + molecule = info.residue("ASP") @@ -229,4 +230,4 @@ def color_atoms(pymol_object, atom_array): # # -######################################################################## \ No newline at end of file +######################################################################## diff --git a/doc/figure_scripts/cover_structure.py b/doc/figure_scripts/cover_structure.py index ede63bc..58068dc 100644 --- a/doc/figure_scripts/cover_structure.py +++ b/doc/figure_scripts/cover_structure.py @@ -1,19 +1,17 @@ import numpy as np import pyximport + pyximport.install( - build_in_temp=False, - setup_args={"include_dirs":np.get_include()}, - language_level=3 + build_in_temp=False, setup_args={"include_dirs": np.get_include()}, language_level=3 ) -import numpy as np +import ammolite +import biotite.database.rcsb as rcsb import biotite.structure as struc import biotite.structure.io.pdbx as pdbx -import biotite.database.rcsb as rcsb -import hydride -import ammolite +import numpy as np from util import COLORS, init_pymol_parameters - +import hydride pdbx_file = pdbx.BinaryCIFFile.read(rcsb.fetch("1bna", "bcif")) heavy_atoms = pdbx.get_structure( @@ -42,9 +40,7 @@ pymol_all.zoom(buffer=1.0) ammolite.cmd.rotate("z", 90) -for pymol_object, atoms in zip( - (pymol_heavy, pymol_all), (heavy_atoms, all_atoms) -): +for pymol_object, atoms in zip((pymol_heavy, pymol_all), (heavy_atoms, all_atoms)): for element in ("H", "C", "N", "O", "P"): pymol_object.color(COLORS[element], atoms.element == element) @@ -55,9 +51,8 @@ pymol_heavy.disable() pymol_all.enable() for i, (_, atom_i, atom_j) in enumerate(bonds): - color = COLORS["N"] if all_atoms.element[atom_j] == "N"\ - else COLORS["O"] + color = COLORS["N"] if all_atoms.element[atom_j] == "N" else COLORS["O"] pymol_all.distance(str(i), atom_i, atom_j, show_label=False) ammolite.cmd.set_color(f"bond_color_{i}", list(color)) ammolite.cmd.color(f"bond_color_{i}", str(i)) -ammolite.cmd.png("cover_hydrogenated.png", 400, 800) \ No newline at end of file +ammolite.cmd.png("cover_hydrogenated.png", 400, 800) diff --git a/doc/figure_scripts/fragments.py b/doc/figure_scripts/fragments.py index 769efd9..473b695 100644 --- a/doc/figure_scripts/fragments.py +++ b/doc/figure_scripts/fragments.py @@ -1,13 +1,10 @@ -import time -from os.path import join, abspath, dirname -import numpy as np -from sklearn.decomposition import PCA +from os.path import abspath, dirname, join +import ammolite import biotite.structure as struc import biotite.structure.io.mol as mol -import ammolite +import numpy as np from util import COLORS, init_pymol_parameters - PNG_SIZE = (300, 300) ZOOM = 3.5 MOL_DIR = dirname(abspath(__file__)) @@ -20,9 +17,10 @@ def load_and_orient(mol_name): molecule.coord -= struc.centroid(molecule) return molecule -benzene = load_and_orient("benzene") + +benzene = load_and_orient("benzene") butylene = load_and_orient("isobutylene") -toluene = load_and_orient("toluene") +toluene = load_and_orient("toluene") benzene_heavy, butylene_heavy, toluene_heavy = [ atoms[atoms.element != "H"] for atoms in (benzene, butylene, toluene) ] @@ -30,7 +28,7 @@ def load_and_orient(mol_name): init_pymol_parameters() -center = struc.array([struc.Atom([0,0,0], atom_name="C", element="C")]) +center = struc.array([struc.Atom([0, 0, 0], atom_name="C", element="C")]) center.bonds = struc.BondList(1) CENTER = ammolite.PyMOLObject.from_structure(center, "center_") ammolite.cmd.disable("center_") @@ -41,7 +39,9 @@ def load_and_orient(mol_name): ammolite.cmd.png("toluene.png", *PNG_SIZE) ammolite.cmd.disable("toluene") -pymol_toluene_heavy = ammolite.PyMOLObject.from_structure(toluene_heavy, "toluene_heavy") +pymol_toluene_heavy = ammolite.PyMOLObject.from_structure( + toluene_heavy, "toluene_heavy" +) CENTER.zoom(buffer=ZOOM) pymol_toluene_heavy.color(COLORS["O"]) ammolite.cmd.png("toluene_heavy.png", *PNG_SIZE) @@ -70,7 +70,7 @@ def visualize_fragments(molecule, mol_name, color): fragment = molecule[np.append(bonded_i, [i])] fragment_name = f"{mol_name}_frag_{frag_num:02d}" frag_num += 1 - + pymol_fragment = ammolite.PyMOLObject.from_structure( fragment, fragment_name ) @@ -80,6 +80,7 @@ def visualize_fragments(molecule, mol_name, color): ammolite.cmd.png(f"{fragment_name}.png", *PNG_SIZE) ammolite.cmd.disable(fragment_name) -visualize_fragments(toluene_heavy, "toluene", COLORS["O"]) -visualize_fragments(benzene, "benzene", COLORS["N"]) -visualize_fragments(butylene, "butylene", COLORS["N"]) \ No newline at end of file + +visualize_fragments(toluene_heavy, "toluene", COLORS["O"]) +visualize_fragments(benzene, "benzene", COLORS["N"]) +visualize_fragments(butylene, "butylene", COLORS["N"]) diff --git a/doc/figure_scripts/rotation_freedom.py b/doc/figure_scripts/rotation_freedom.py index a98f734..6e90f9e 100644 --- a/doc/figure_scripts/rotation_freedom.py +++ b/doc/figure_scripts/rotation_freedom.py @@ -1,13 +1,10 @@ -import time -from os.path import join, abspath, dirname -import numpy as np -from sklearn.decomposition import PCA +from os.path import abspath, dirname, join +import ammolite import biotite.structure as struc import biotite.structure.io.mol as mol -import ammolite +import numpy as np from util import COLORS, init_pymol_parameters - PNG_SIZE = (300, 300) ZOOM = 1.0 MOL_DIR = dirname(abspath(__file__)) @@ -20,14 +17,15 @@ def load_and_orient(mol_name): molecule.coord -= struc.centroid(molecule) return molecule -propane = load_and_orient("propane") + +propane = load_and_orient("propane") ammolite.launch_interactive_pymol() init_pymol_parameters() -center = struc.array([struc.Atom([0,0,0], atom_name="C", element="C")]) +center = struc.array([struc.Atom([0, 0, 0], atom_name="C", element="C")]) center.bonds = struc.BondList(1) CENTER = ammolite.PyMOLObject.from_structure(center, "center_") ammolite.cmd.disable("center_") @@ -37,12 +35,10 @@ def load_and_orient(mol_name): bonded_i = all_bonds[carbon_i] bonded_i = bonded_i[bonded_i != -1] fragment = propane[np.append(bonded_i, [carbon_i])] - - pymol_fragment = ammolite.PyMOLObject.from_structure( - fragment, name, delete=False - ) + + pymol_fragment = ammolite.PyMOLObject.from_structure(fragment, name, delete=False) CENTER.zoom(buffer=ZOOM) pymol_fragment.color(COLORS["C"], fragment.element != "H") pymol_fragment.color(COLORS["N"], -1) - #ammolite.cmd.png(f"{name}.png", *PNG_SIZE) - #ammolite.cmd.disable(name) \ No newline at end of file + # ammolite.cmd.png(f"{name}.png", *PNG_SIZE) + # ammolite.cmd.disable(name) diff --git a/doc/figure_scripts/util.py b/doc/figure_scripts/util.py index d98ae89..ba0a039 100644 --- a/doc/figure_scripts/util.py +++ b/doc/figure_scripts/util.py @@ -1,5 +1,5 @@ -from matplotlib.colors import to_rgb import ammolite +from matplotlib.colors import to_rgb def init_pymol_parameters(): @@ -20,5 +20,5 @@ def init_pymol_parameters(): "N": to_rgb("#0a6efd"), "O": to_rgb("#e1301d"), "P": to_rgb("#098a07"), - #df059e -} \ No newline at end of file + # df059e +} diff --git a/pyproject.toml b/pyproject.toml index 333a4e2..0a37cfc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,15 +26,50 @@ dependencies = [ ] dynamic = ["version"] +[project.optional-dependencies] +test = [ + "pytest", +] +lint = [ + "ruff==0.5.2", +] + [project.urls] homepage = "https://hydride.biotite-python.org" repository = "https://github.com/biotite-dev/hydride" documentation = "https://hydride.biotite-python.org" -[project.optional-dependencies] -test = [ - "pytest", +[tool.ruff.lint] +# pyflakes, pycodestyle isort and varibale naming +select = ["F", "E", "W", "I", "TID", "N"] +ignore = [ + # In docstrings long lines are often intentional + # Most other ocassions are caught by the ruff formatter + "E501", + # Due to constants and class placeholders defined in functions + "N806", +] + +[tool.ruff.lint.per-file-ignores] +# Due to `from .module import *` imports in `__init__.py` modules +"__init__.py" = ["F403", "TID252"] +# Due to imports in each code snipped +"doc/figure_scripts/api.py" = ["E402"] + +[tool.ruff.lint.flake8-tidy-imports] +ban-relative-imports = "all" + +[tool.ruff.lint.isort] +# No separator lines between import sections +no-lines-before = [ + "future", + "standard-library", + "third-party", + "first-party", + "local-folder", ] +order-by-type = true +known-first-party = ["hydride"] [project.scripts] hydride = "hydride.cli:main" diff --git a/setup.py b/setup.py index 52fd9a7..e854b94 100644 --- a/setup.py +++ b/setup.py @@ -2,20 +2,18 @@ # under the 3-Clause BSD License. Please see 'LICENSE.rst' for further # information. -import warnings -import re -from os.path import join, abspath, dirname, normpath, isfile import fnmatch import os import pickle -from setuptools import setup, find_packages, Extension -from Cython.Build import cythonize -import msgpack -import numpy as np +import warnings +from os.path import abspath, dirname, isfile, join, normpath import biotite.structure as struc import biotite.structure.info as info +import msgpack +import numpy as np from biotite.structure.info.ccd import get_ccd - +from Cython.Build import cythonize +from setuptools import Extension, find_packages, setup # Molecules that appear is most structures # Hence, there is a high importance to get the hydrogen conformations @@ -24,10 +22,37 @@ # from the standard library PROMINENT_MOLECULES = [ # Amino acids - "ALA", "ARG", "ASN", "ASP", "CYS", "GLN", "GLU", "GLY", "HIS", "ILE", - "LEU", "LYS", "MET", "PHE", "PRO", "SER", "THR", "TRP", "TYR", "VAL", + "ALA", + "ARG", + "ASN", + "ASP", + "CYS", + "GLN", + "GLU", + "GLY", + "HIS", + "ILE", + "LEU", + "LYS", + "MET", + "PHE", + "PRO", + "SER", + "THR", + "TRP", + "TYR", + "VAL", # Nucleotides - "A", "C", "G", "T", "U", "DA", "DC", "DG", "DT", "DU", + "A", + "C", + "G", + "T", + "U", + "DA", + "DC", + "DG", + "DT", + "DU", # Solvent "HOH", ] @@ -37,17 +62,11 @@ # Change directory to setup directory to ensure correct file identification os.chdir(dirname(abspath(__file__))) # Import is relative to working directory -from src.hydride import FragmentLibrary, AtomNameLibrary, __version__ - - +from src.hydride import AtomNameLibrary, FragmentLibrary, __version__ # Compile Cython into C try: - cythonize( - "src/**/*.pyx", - include_path=[np.get_include()], - language_level=3 - ) + cythonize("src/**/*.pyx", include_path=[np.get_include()], language_level=3) except ValueError: # This is a source distribution and the directory already contains # only C files @@ -56,23 +75,22 @@ def get_extensions(): ext_sources = [] - for dirpath, _, filenames in os.walk( - normpath(join("src", "hydride")) - ): - for filename in fnmatch.filter(filenames, '*.c'): + for dirpath, _, filenames in os.walk(normpath(join("src", "hydride"))): + for filename in fnmatch.filter(filenames, "*.c"): ext_sources.append(os.path.join(dirpath, filename)) - ext_names = [source - .replace("src"+normpath("/"), "") - .replace(".c", "") - .replace(normpath("/"), ".") - for source in ext_sources] - ext_modules = [Extension(ext_names[i], [ext_sources[i]], - include_dirs=[np.get_include()]) - for i in range(len(ext_sources))] + ext_names = [ + source.replace("src" + normpath("/"), "") + .replace(".c", "") + .replace(normpath("/"), ".") + for source in ext_sources + ] + ext_modules = [ + Extension(ext_names[i], [ext_sources[i]], include_dirs=[np.get_include()]) + for i in range(len(ext_sources)) + ] return ext_modules - def get_protonation_variants(): with open("prot_variants.msgpack", "rb") as file: molecule_data = msgpack.unpack(file, use_list=False, raw=False) @@ -88,17 +106,19 @@ def get_protonation_variants(): molecule.charge = molecule_dict["charge"] molecule.hetero = molecule_dict["hetero"] - molecule.coord[:,0] = molecule_dict["coord_x"] - molecule.coord[:,1] = molecule_dict["coord_y"] - molecule.coord[:,2] = molecule_dict["coord_z"] + molecule.coord[:, 0] = molecule_dict["coord_x"] + molecule.coord[:, 1] = molecule_dict["coord_y"] + molecule.coord[:, 2] = molecule_dict["coord_z"] molecule.bonds = struc.BondList( molecule.array_length(), - bonds = np.stack([ - molecule_dict["bond_i"], - molecule_dict["bond_j"], - molecule_dict["bond_type"] - ]).T + bonds=np.stack( + [ + molecule_dict["bond_i"], + molecule_dict["bond_j"], + molecule_dict["bond_type"], + ] + ).T, ) molecules.append(molecule) @@ -125,10 +145,7 @@ def get_mol_names_in_ccd(): warnings.simplefilter("ignore") for i, mol_name in enumerate(mol_names): if not i % 100: - print( - f"Compiling fragment library... ({i}/{len(mol_names)})", - end="\r" - ) + print(f"Compiling fragment library... ({i}/{len(mol_names)})", end="\r") try: mol = info.residue(mol_name) except KeyError: @@ -151,7 +168,7 @@ def get_mol_names_in_ccd(): print( f"Compiling atom name library... " f"({i+1}/{len(PROMINENT_MOLECULES)})", - end="\r" + end="\r", ) try: mol = info.residue(mol_name) @@ -164,15 +181,15 @@ def get_mol_names_in_ccd(): setup( - version = __version__, - zip_safe = False, - packages = find_packages("src"), - package_dir = {"" : "src"}, - ext_modules = get_extensions(), + version=__version__, + zip_safe=False, + packages=find_packages("src"), + package_dir={"": "src"}, + ext_modules=get_extensions(), # Include fragment and atom name libraries - package_data = {"hydride" : ["*.pickle"]}, + package_data={"hydride": ["*.pickle"]}, ) # Return to original directory -os.chdir(original_wd) \ No newline at end of file +os.chdir(original_wd) diff --git a/src/hydride/__init__.py b/src/hydride/__init__.py index 59bbf91..1b3bf78 100644 --- a/src/hydride/__init__.py +++ b/src/hydride/__init__.py @@ -15,4 +15,4 @@ try: from .relax import * except ImportError: - pass \ No newline at end of file + pass diff --git a/src/hydride/add.py b/src/hydride/add.py index b2e4653..ffea04b 100644 --- a/src/hydride/add.py +++ b/src/hydride/add.py @@ -6,14 +6,13 @@ __author__ = "Patrick Kunzmann, Jacob Marcel Anter" __all__ = ["add_hydrogen"] -import numpy as np import biotite.structure as struc -from .fragments import FragmentLibrary -from .names import AtomNameLibrary +import numpy as np +from hydride.fragments import FragmentLibrary +from hydride.names import AtomNameLibrary -def add_hydrogen(atoms, mask=None, fragment_library=None, name_library=None, - box=None): +def add_hydrogen(atoms, mask=None, fragment_library=None, name_library=None, box=None): """ Add hydrogen atoms to a structure. @@ -74,13 +73,9 @@ def add_hydrogen(atoms, mask=None, fragment_library=None, name_library=None, name_library = AtomNameLibrary.standard_library() if (atoms.element[mask] == "H").any(): - raise struc.BadStructureError( - "Input structure already contains hydrogen atoms" - ) + raise struc.BadStructureError("Input structure already contains hydrogen atoms") - hydrogen_coord = fragment_library.calculate_hydrogen_coord( - atoms, mask, box - ) + hydrogen_coord = fragment_library.calculate_hydrogen_coord(atoms, mask, box) # Count number of hydrogen atoms to be added count = 0 @@ -91,14 +86,12 @@ def add_hydrogen(atoms, mask=None, fragment_library=None, name_library=None, # Create new empty AtomArray with an appropriate length for all # heavy and hydrogen atoms hydrogenated_atoms = struc.AtomArray(atoms.array_length() + count) - original_atom_mask = np.zeros( - hydrogenated_atoms.array_length(), dtype=bool - ) + original_atom_mask = np.zeros(hydrogenated_atoms.array_length(), dtype=bool) # Add all annotation categories of the original AtomArray for category in atoms.get_annotation_categories(): if category not in hydrogenated_atoms.get_annotation_categories(): hydrogenated_atoms.add_annotation( - category, dtype=atoms.get_annotation(category).dtype + category, dtype=atoms.get_annotation(category).dtype ) if atoms.box is not None: hydrogenated_atoms.box = atoms.box.copy() @@ -114,20 +107,21 @@ def add_hydrogen(atoms, mask=None, fragment_library=None, name_library=None, for i in range(len(residue_starts) - 1): # Set annotation and coordinates from input AtomArray start = residue_starts[i] - stop = residue_starts[i+1] + stop = residue_starts[i + 1] res_length = stop - start - index_mapping[start : stop] = np.arange(p, p + res_length) + index_mapping[start:stop] = np.arange(p, p + res_length) original_atom_mask[p : p + res_length] = True hydrogenated_atoms.coord[p : p + res_length] = atoms.coord[start:stop] for category in atoms.get_annotation_categories(): - hydrogenated_atoms.get_annotation(category)[p : p + res_length] \ - = atoms.get_annotation(category)[start : stop] + hydrogenated_atoms.get_annotation(category)[p : p + res_length] = ( + atoms.get_annotation(category)[start:stop] + ) p += res_length # Set annotation and coordinates for hydrogen atoms for j in range(start, stop): hydrogen_coord_for_atom = hydrogen_coord[j] hydrogen_name_generator = name_library.generate_hydrogen_names( - atoms.res_name[j], atoms.atom_name[j] + atoms.res_name[j], atoms.atom_name[j] ) for coord in hydrogen_coord_for_atom: hydrogenated_atoms.coord[p] = coord @@ -145,29 +139,28 @@ def add_hydrogen(atoms, mask=None, fragment_library=None, name_library=None, # Add bonds to combined AtomArray original_bonds = atoms.bonds.as_array() - bond_indices = index_mapping[original_bonds[:,:2]] + bond_indices = index_mapping[original_bonds[:, :2]] heavy_bonds = np.stack( - [ - bond_indices[:, 0], - bond_indices[:, 1], - # The bond types - original_bonds[:,2] - ], - axis=-1 + [ + bond_indices[:, 0], + bond_indices[:, 1], + # The bond types + original_bonds[:, 2], + ], + axis=-1, ) hydrogen_bonds = np.array(hydrogen_bonds, dtype=np.uint32).reshape(-1, 2) # All bonds to hydrogen atoms are single bonds hydrogen_bonds = np.stack( - [ - hydrogen_bonds[:, 0], - hydrogen_bonds[:, 1], - np.ones(len(hydrogen_bonds), dtype=np.uint32) - ], - axis=-1 + [ + hydrogen_bonds[:, 0], + hydrogen_bonds[:, 1], + np.ones(len(hydrogen_bonds), dtype=np.uint32), + ], + axis=-1, ) hydrogenated_atoms.bonds = struc.BondList( - hydrogenated_atoms.array_length(), - np.concatenate([heavy_bonds, hydrogen_bonds]) + hydrogenated_atoms.array_length(), np.concatenate([heavy_bonds, hydrogen_bonds]) ) return hydrogenated_atoms, original_atom_mask diff --git a/src/hydride/charge.py b/src/hydride/charge.py index b84903e..7864d41 100644 --- a/src/hydride/charge.py +++ b/src/hydride/charge.py @@ -6,66 +6,65 @@ __author__ = "Patrick Kunzmann" __all__ = ["estimate_amino_acid_charges"] -import numpy as np import biotite.structure as struc - +import numpy as np PK_NH3 = { - "ALA" : 9.71, - "ARG" : 9.00, - "ASN" : 8.73, - "ASP" : 9.66, - "CYS" : 10.28, - "GLU" : 9.58, - "GLN" : 9.00, - "GLY" : 9.58, - "HIS" : 9.09, - "ILE" : 9.60, - "LEU" : 9.58, - "LYS" : 9.16, - "MET" : 9.08, - "PHE" : 9.09, - "PRO" : 10.47, - "SER" : 9.05, - "THR" : 8.96, - "TRP" : 9.34, - "TYR" : 9.04, - "VAL" : 9.52, + "ALA": 9.71, + "ARG": 9.00, + "ASN": 8.73, + "ASP": 9.66, + "CYS": 10.28, + "GLU": 9.58, + "GLN": 9.00, + "GLY": 9.58, + "HIS": 9.09, + "ILE": 9.60, + "LEU": 9.58, + "LYS": 9.16, + "MET": 9.08, + "PHE": 9.09, + "PRO": 10.47, + "SER": 9.05, + "THR": 8.96, + "TRP": 9.34, + "TYR": 9.04, + "VAL": 9.52, } PK_COOH = { - "ALA" : 2.33, - "ARG" : 2.03, - "ASN" : 2.16, - "ASP" : 1.95, - "CYS" : 1.91, - "GLU" : 2.16, - "GLN" : 2.18, - "GLY" : 2.34, - "HIS" : 1.70, - "ILE" : 2.26, - "LEU" : 2.32, - "LYS" : 2.15, - "MET" : 2.16, - "PHE" : 2.18, - "PRO" : 1.95, - "SER" : 2.13, - "THR" : 2.20, - "TRP" : 2.38, - "TYR" : 2.24, - "VAL" : 2.27, + "ALA": 2.33, + "ARG": 2.03, + "ASN": 2.16, + "ASP": 1.95, + "CYS": 1.91, + "GLU": 2.16, + "GLN": 2.18, + "GLY": 2.34, + "HIS": 1.70, + "ILE": 2.26, + "LEU": 2.32, + "LYS": 2.15, + "MET": 2.16, + "PHE": 2.18, + "PRO": 1.95, + "SER": 2.13, + "THR": 2.20, + "TRP": 2.38, + "TYR": 2.24, + "VAL": 2.27, } # pK values for side chains # The second value gives the expected charge if pH is less than pK PK_SIDE_CHAIN = { - ("ARG", "NH2") : (12.10, 1), - ("ASP", "OD2") : ( 3.71, 0), - ("CYS", "SG") : ( 8.14, 0), - ("GLU", "OE2") : ( 4.15, 0), - ("HIS", "ND1") : ( 6.04, 1), - ("LYS", "NZ") : (10.67, 1), - ("TYR", "OH") : (10.10, 0), + ("ARG", "NH2"): (12.10, 1), + ("ASP", "OD2"): (3.71, 0), + ("CYS", "SG"): (8.14, 0), + ("GLU", "OE2"): (4.15, 0), + ("HIS", "ND1"): (6.04, 1), + ("LYS", "NZ"): (10.67, 1), + ("TYR", "OH"): (10.10, 0), } @@ -80,47 +79,41 @@ def estimate_amino_acid_charges(atoms, ph): The atoms to calculate the charges for. ph : float The charges are estimated based on this *pH* value. - + Returns ------- charges : ndarray, shape=(n,), dtype=int The estimated charges. 0 for all atoms that are not part of an amino acid. - + Notes ----- The protonation state is only estimated for canonical amino acids. - + References ---------- - + .. [1] DR Lide, "CRC Handbook of Chemistry and Physics." CRC Press, (2003). """ - charges_nh3 = { - res_name : 1 if ph < pk else 0 - for res_name, pk in PK_NH3.items() - } - charges_cooh = { - res_name : 0 if ph < pk else -1 - for res_name, pk in PK_COOH.items() - } + charges_nh3 = {res_name: 1 if ph < pk else 0 for res_name, pk in PK_NH3.items()} + charges_cooh = {res_name: 0 if ph < pk else -1 for res_name, pk in PK_COOH.items()} charges_side_chain = { - key : charge if ph < pk else charge - 1 + key: charge if ph < pk else charge - 1 for key, (pk, charge) in PK_SIDE_CHAIN.items() } atom_charges = np.zeros(atoms.array_length(), dtype=int) - + # Charges for termini amino_acid_mask = struc.filter_canonical_amino_acids(atoms) - amino_indices = np.where((atoms.atom_name == "N") & amino_acid_mask)[0] + amino_indices = np.where((atoms.atom_name == "N") & amino_acid_mask)[0] carboxy_indices = np.where((atoms.atom_name == "OXT") & amino_acid_mask)[0] chain_starts = struc.get_chain_starts(atoms, add_exclusive_stop=True) - for i in range(len(chain_starts) -1): + for i in range(len(chain_starts) - 1): start = chain_starts[i] - stop = chain_starts[i+1] + stop = chain_starts[i + 1] chain_amino_indices = amino_indices[ (amino_indices >= start) & (amino_indices < stop) ] @@ -133,10 +126,10 @@ def estimate_amino_acid_charges(atoms, ph): if len(chain_carboxy_indices) > 0: carboxy_i = np.max(chain_carboxy_indices) atom_charges[carboxy_i] = charges_cooh[atoms.res_name[carboxy_i]] - + # Charges for other heavy atoms for (res_name, atom_name), charge in charges_side_chain.items(): - mask = (atoms.res_name == res_name) & (atoms.atom_name == atom_name) + mask = (atoms.res_name == res_name) & (atoms.atom_name == atom_name) atom_charges[mask] = charge - return atom_charges \ No newline at end of file + return atom_charges diff --git a/src/hydride/cli.py b/src/hydride/cli.py index 72e727e..8b81041 100644 --- a/src/hydride/cli.py +++ b/src/hydride/cli.py @@ -5,20 +5,20 @@ __name__ = "hydride" __author__ = "Patrick Kunzmann" -from os.path import splitext -import sys import argparse +import sys import warnings -import numpy as np +from os.path import splitext import biotite.structure as struc +import biotite.structure.io.mol as mol import biotite.structure.io.pdb as pdb import biotite.structure.io.pdbx as pdbx -import biotite.structure.io.mol as mol -from .add import add_hydrogen -from. charge import estimate_amino_acid_charges -from .fragments import FragmentLibrary -from .names import AtomNameLibrary -from .relax import relax_hydrogen +import numpy as np +from hydride.add import add_hydrogen +from hydride.charge import estimate_amino_acid_charges +from hydride.fragments import FragmentLibrary +from hydride.names import AtomNameLibrary +from hydride.relax import relax_hydrogen class UserInputError(Exception): @@ -28,116 +28,145 @@ class UserInputError(Exception): def main(args=None): parser = argparse.ArgumentParser( description="This program adds hydrogen atoms to molecular " - "structures where these are missing.\n" - "For more information, please visit " - "https://hydride.biotite-python.org/.", - formatter_class=argparse.ArgumentDefaultsHelpFormatter + "structures where these are missing.\n" + "For more information, please visit " + "https://hydride.biotite-python.org/.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( - "--infile", "-i", metavar="FILE", + "--infile", + "-i", + metavar="FILE", help="The path to the input structure file containing the model " - "without hydrogen atoms. " - "If omitted, the file is read from STDOUT." + "without hydrogen atoms. " + "If omitted, the file is read from STDOUT.", ) parser.add_argument( - "--outfile", "-o", metavar="FILE", + "--outfile", + "-o", + metavar="FILE", help="The path to the output structure file where the model " - "with added hydrogen atoms should be written to. " - "If omitted, the file is written to STDOUT. " - "Any existing hydrogen atoms will be removed the model." + "with added hydrogen atoms should be written to. " + "If omitted, the file is written to STDOUT. " + "Any existing hydrogen atoms will be removed the model.", ) parser.add_argument( - "--informat", "-I", + "--informat", + "-I", choices=["pdb", "pdbx", "cif", "bcif", "sdf", "mol"], help="The file format of the input file. " - "Must be specified if input file is read from STDIN. " - "If omitted, the file format is guessed from the suffix " - "of the file." + "Must be specified if input file is read from STDIN. " + "If omitted, the file format is guessed from the suffix " + "of the file.", ) parser.add_argument( - "--outformat", "-O", + "--outformat", + "-O", choices=["pdb", "pdbx", "cif", "bcif", "mol"], help="The file format of the output file. " - "Must be specified if output file is written to STDOUT." - "If omitted, the file format is guessed from the suffix " - "of the file." + "Must be specified if output file is written to STDOUT." + "If omitted, the file format is guessed from the suffix " + "of the file.", ) parser.add_argument( - "--verbose", "-v", action="store_true", - help="Verbose display of errors." + "--verbose", "-v", action="store_true", help="Verbose display of errors." ) parser.add_argument( - "--no-relax", action="store_true", + "--no-relax", + action="store_true", help="Omit the relaxation step. " - "Note bond lengths and angles will still be correct. " - "However clashes or electrostatically unfavorable " - "conformations will not be resolved." + "Note bond lengths and angles will still be correct. " + "However clashes or electrostatically unfavorable " + "conformations will not be resolved.", ) parser.add_argument( - "--iterations", "-n", type=int, metavar="NUMBER", + "--iterations", + "-n", + type=int, + metavar="NUMBER", help="The maximum number of relaxation iterations. " - "The runtime of the relaxation scales approximately " - "linear with this value, if the relaxation does not " - "terminate before. " - "By default, the relaxation runs until a local optimum " - "has been reached." + "The runtime of the relaxation scales approximately " + "linear with this value, if the relaxation does not " + "terminate before. " + "By default, the relaxation runs until a local optimum " + "has been reached.", ) parser.add_argument( - "--angle-increment", "-a", type=float, metavar="NUMBER", default=10.0, + "--angle-increment", + "-a", + type=float, + metavar="NUMBER", + default=10.0, help="The angle in degrees that a freely rotatable bond is rotated " - "in each relaxation step." - "Lower values increase the accuracy of hydrogen positioning, " - "but increase the required number of steps until an optimum " - "is found." + "in each relaxation step." + "Lower values increase the accuracy of hydrogen positioning, " + "but increase the required number of steps until an optimum " + "is found.", ) parser.add_argument( - "--fragments", "-f", metavar="FILE", action="append", + "--fragments", + "-f", + metavar="FILE", + action="append", help="Additional structure file to containing fragments for the " - "fragment library. " - "This can be used supply fragments for molecules with uncommon " - "groups, if the standard fragment library does not contain such " - "fragments, yet. " - "May be supplied multiple times." + "fragment library. " + "This can be used supply fragments for molecules with uncommon " + "groups, if the standard fragment library does not contain such " + "fragments, yet. " + "May be supplied multiple times.", ) parser.add_argument( - "--fragformat", "-F", + "--fragformat", + "-F", choices=["pdb", "pdbx", "cif", "bcif", "sdf", "mol"], help="The file format of the additional structure files. " - "If omitted, the file format is guessed from the suffix " - "of the file." + "If omitted, the file format is guessed from the suffix " + "of the file.", ) parser.add_argument( - "--ignore", "-g", metavar="RESIDUE", action="append", nargs=2, + "--ignore", + "-g", + metavar="RESIDUE", + action="append", + nargs=2, help="No hydrogen atoms are added to the specified residue. " - "The format is '{chain} {residue}', e.g. 'A 123'. " - "May be supplied multiple times, if multiple residues should be " - "ignored." + "The format is '{chain} {residue}', e.g. 'A 123'. " + "May be supplied multiple times, if multiple residues should be " + "ignored.", ) parser.add_argument( - "--model", "-m", type=int, metavar="NUMBER", default=1, + "--model", + "-m", + type=int, + metavar="NUMBER", + default=1, help="The model number, if the input structure file contains multiple " - "models." + "models.", ) parser.add_argument( - "--charges", "-c", type=float, metavar="PH", + "--charges", + "-c", + type=float, + metavar="PH", help="Recalculate the charges of atoms in canonical amino acids based " - "on the given pH value. " - "This estimation does not take the surrounding amino acids into " - "account." + "on the given pH value. " + "This estimation does not take the surrounding amino acids into " + "account.", ) parser.add_argument( - "--pbc", "-p", action="store_true", + "--pbc", + "-p", + action="store_true", help="Set hydrogen addition and relaxation aware to periodic boundary " - "conditions. " - "The box is read from the input structure file." + "conditions. " + "The box is read from the input structure file.", ) args = parser.parse_args(args=args) - try: run(args) except UserInputError as e: @@ -146,12 +175,11 @@ def main(args=None): raise else: sys.exit(1) - except Exception as e: + except Exception: print("An unexpected error occured:\n", file=sys.stderr) raise - def run(args): frag_library = FragmentLibrary.standard_library() name_library = AtomNameLibrary.standard_library() @@ -166,10 +194,8 @@ def run(args): f"Missing file permission for reading '{frag_path}'" ) except FileNotFoundError: - raise UserInputError( - f"Input file '{args.infile}' cannot be found" - ) - except: + raise UserInputError(f"Input file '{args.infile}' cannot be found") + except Exception: raise UserInputError( f"Input file '{args.infile}' contains invalid data" ) @@ -181,24 +207,16 @@ def run(args): except UserInputError: raise except PermissionError: - raise UserInputError( - f"Missing file permission for reading '{args.infile}'" - ) + raise UserInputError(f"Missing file permission for reading '{args.infile}'") except FileNotFoundError: - raise UserInputError( - f"Input file '{args.infile}' cannot be found" - ) - except: + raise UserInputError(f"Input file '{args.infile}' cannot be found") + except Exception: if args.infile is None: - raise UserInputError( - "Input file contains invalid data" - ) + raise UserInputError("Input file contains invalid data") else: - raise UserInputError( - f"Input file '{args.infile}' contains invalid data" - ) + raise UserInputError(f"Input file '{args.infile}' contains invalid data") - heavy_mask = (model.element != "H") + heavy_mask = model.element != "H" if not heavy_mask.all(): warnings.warn("Existing hydrogen atoms were removed") model = model[heavy_mask] @@ -213,12 +231,10 @@ def run(args): if args.ignore is not None: for chain_id, res_id in args.ignore: res_id = int(res_id) - removal_mask = (model.chain_id == chain_id) & \ - (model.res_id == res_id) + removal_mask = (model.chain_id == chain_id) & (model.res_id == res_id) if not removal_mask.any(): raise UserInputError( - f"Cannot find '{chain_id} {res_id}' " - "in the input structure" + f"Cannot find '{chain_id} {res_id}' " "in the input structure" ) input_mask &= ~removal_mask @@ -230,18 +246,17 @@ def run(args): ) box = True else: - box=None + box = None - model, _ = add_hydrogen( - model, input_mask, frag_library, name_library, box - ) + model, _ = add_hydrogen(model, input_mask, frag_library, name_library, box) if not args.no_relax: if args.iterations is not None and args.iterations < 0: raise UserInputError("The number of iterations must be positive") model.coord = relax_hydrogen( - model, args.iterations, + model, + args.iterations, angle_increment=np.deg2rad(args.angle_increment), - box=box + box=box, ) try: @@ -249,9 +264,7 @@ def run(args): except UserInputError: raise except PermissionError: - raise UserInputError( - f"Missing file permission for writing '{args.outfile}'" - ) + raise UserInputError(f"Missing file permission for writing '{args.outfile}'") except: raise @@ -281,12 +294,14 @@ def read_structure(path, format, model_number): f"for the input structure with {model_count} models" ) model = pdb.get_structure( - pdb_file, model=model_number, extra_fields=["charge"], - include_bonds=True + pdb_file, + model=model_number, + extra_fields=["charge"], + include_bonds=True, ) # Expect that all ANY bonds are actually SINGLE bonds bond_array = model.bonds.as_array() - unknown_order_mask = (bond_array[:,2] == struc.BondType.ANY) + unknown_order_mask = bond_array[:, 2] == struc.BondType.ANY if unknown_order_mask.any(): warnings.warn( "For some bonds the bond order is unknown, " @@ -310,8 +325,10 @@ def read_structure(path, format, model_number): f"for the input structure with {model_count} models" ) model = pdbx.get_structure( - pdbx_file, model=model_number, extra_fields=["charge"], - include_bonds=True + pdbx_file, + model=model_number, + extra_fields=["charge"], + include_bonds=True, ) model.bonds = struc.connect_via_residue_names(model) case "mol": @@ -402,4 +419,4 @@ def guess_format(path): case ".sdf": return "sdf" case _: - raise UserInputError(f"Unknown file extension '{suffix}'") \ No newline at end of file + raise UserInputError(f"Unknown file extension '{suffix}'") diff --git a/src/hydride/fragments.py b/src/hydride/fragments.py index c8e87d6..8018d1b 100644 --- a/src/hydride/fragments.py +++ b/src/hydride/fragments.py @@ -6,12 +6,12 @@ __author__ = "Patrick Kunzmann" __all__ = ["FragmentLibrary"] -from os.path import join, dirname, abspath -import warnings import pickle -from biotite.structure.error import BadStructureError -from biotite.structure import BondType, displacement +import warnings +from os.path import abspath, dirname, join import numpy as np +from biotite.structure import BondType, displacement +from biotite.structure.error import BadStructureError class FragmentLibrary: @@ -25,7 +25,7 @@ class FragmentLibrary: - A central heavy atom, - bond order and position of its bonded heavy atoms and - and positions of bonded hydrogen atoms. - + The properties of the fragment (central atom element, central atom charge, order of connected bonds) are stored in a dictionary mapping these properties to heavy and hydrogen atom @@ -43,11 +43,11 @@ class FragmentLibrary: References ---------- - + .. [1] W Kabsch, "A solution for the best rotation to relate two sets of vectors." Acta Cryst, 32, 922-923 (1976). - + .. [2] W Kabsch, "A discussion of the solution for the best rotation to relate two sets of vectors." @@ -58,7 +58,6 @@ class FragmentLibrary: def __init__(self): self._frag_dict = {} - @staticmethod def standard_library(): @@ -76,10 +75,8 @@ def standard_library(): FragmentLibrary._std_library = FragmentLibrary() file_name = join(dirname(abspath(__file__)), "fragments.pickle") with open(file_name, "rb") as fragments_file: - FragmentLibrary._std_library._frag_dict \ - = pickle.load(fragments_file) + FragmentLibrary._std_library._frag_dict = pickle.load(fragments_file) return FragmentLibrary._std_library - def add_molecule(self, molecule): """ @@ -100,8 +97,13 @@ def add_molecule(self, molecule): if fragment is None: continue ( - central_element, central_charge, stereo, bond_types, - center_coord, heavy_coord, hydrogen_coord + central_element, + central_charge, + stereo, + bond_types, + center_coord, + heavy_coord, + hydrogen_coord, ) = fragment # Translate the coordinates, # so the central heavy atom is at origin @@ -111,10 +113,12 @@ def add_molecule(self, molecule): (central_element, central_charge, stereo, tuple(bond_types)) ] = ( # Information about the origin of the fragment - # # for debugging purposes - molecule.res_name[i], molecule.atom_name[i], + # # for debugging purposes + molecule.res_name[i], + molecule.atom_name[i], # The interesting information - centered_heavy_coord, centered_hydrogen_coord + centered_heavy_coord, + centered_hydrogen_coord, ) if stereo != 0: # Also include the opposite enantiomer in the library @@ -124,14 +128,15 @@ def add_molecule(self, molecule): refl_centered_hydrogen_coord = centered_hydrogen_coord.copy() refl_centered_heavy_coord[..., 0] *= -1 refl_centered_hydrogen_coord[..., 0] *= -1 - self._frag_dict[( - central_element, central_charge, stereo, tuple(bond_types) - )] = ( - molecule.res_name[i], molecule.atom_name[i], - refl_centered_heavy_coord, refl_centered_hydrogen_coord + self._frag_dict[ + (central_element, central_charge, stereo, tuple(bond_types)) + ] = ( + molecule.res_name[i], + molecule.atom_name[i], + refl_centered_heavy_coord, + refl_centered_hydrogen_coord, ) - def calculate_hydrogen_coord(self, atoms, mask=None, box=None): """ Estimate the hydrogen coordinates for each atom in a given @@ -172,15 +177,9 @@ def calculate_hydrogen_coord(self, atoms, mask=None, box=None): # The target and reference heavy atom coordinates # for each fragment - tar_frag_center_coord = np.zeros( - (atoms.array_length(), 3), dtype=np.float32 - ) - tar_frag_heavy_coord = np.zeros( - (atoms.array_length(), 3, 3), dtype=np.float32 - ) - ref_frag_heavy_coord = np.zeros( - (atoms.array_length(), 3, 3), dtype=np.float32 - ) + tar_frag_center_coord = np.zeros((atoms.array_length(), 3), dtype=np.float32) + tar_frag_heavy_coord = np.zeros((atoms.array_length(), 3, 3), dtype=np.float32) + ref_frag_heavy_coord = np.zeros((atoms.array_length(), 3, 3), dtype=np.float32) # The amount of hydrogens varies for each fragment # -> padding with NaN # The maximum number of bond hydrogen atoms is 4 @@ -195,8 +194,13 @@ def calculate_hydrogen_coord(self, atoms, mask=None, box=None): # This atom is not in mask continue ( - central_element, central_charge, stereo, bond_types, - center_coord, heavy_coord, _ + central_element, + central_charge, + stereo, + bond_types, + center_coord, + heavy_coord, + _, ) = fragment tar_frag_center_coord[i] = center_coord tar_frag_heavy_coord[i] = heavy_coord @@ -213,8 +217,9 @@ def calculate_hydrogen_coord(self, atoms, mask=None, box=None): else: _, _, ref_heavy_coord, ref_hydrogen_coord = hit ref_frag_heavy_coord[i] = ref_heavy_coord - ref_frag_hydrogen_coord[i, :len(ref_hydrogen_coord)] \ - = ref_hydrogen_coord + ref_frag_hydrogen_coord[i, : len(ref_hydrogen_coord)] = ( + ref_hydrogen_coord + ) # Translate the target coordinates, # so the central heavy atom is at origin @@ -231,27 +236,25 @@ def calculate_hydrogen_coord(self, atoms, mask=None, box=None): # Box vectors are given as array-like object box = np.asarray(box) tar_frag_heavy_coord = displacement( - tar_frag_center_coord[:, np.newaxis, :], tar_frag_heavy_coord, - box + tar_frag_center_coord[:, np.newaxis, :], tar_frag_heavy_coord, box ) # Get the rotation matrix required for superimposition of - # the reference coord to the target coord - matrices = _get_rotation_matrices( - tar_frag_heavy_coord, ref_frag_heavy_coord - ) + # the reference coord to the target coord + matrices = _get_rotation_matrices(tar_frag_heavy_coord, ref_frag_heavy_coord) # Rotate the reference hydrogen atoms, so they fit the # target heavy atoms tar_frag_hydrogen_coord = _rotate(ref_frag_hydrogen_coord, matrices) # Translate hydrogen atoms to the position of the # non-centered central heavy target atom tar_frag_hydrogen_coord += tar_frag_center_coord[:, np.newaxis, :] - + # Turn into list and remove NaN paddings tar_frag_hydrogen_coord = [ # If the x-coordinate is NaN it is expected that # y and z are also NaN - coord[~np.isnan(coord[:, 0])] for coord in tar_frag_hydrogen_coord + coord[~np.isnan(coord[:, 0])] + for coord in tar_frag_hydrogen_coord ] return tar_frag_hydrogen_coord @@ -271,7 +274,7 @@ def _fragment(atoms, mask=None): mask : ndarray, shape=(n,), dtype=bool A boolean mask that is true for each heavy atom for which a fragment should be created. - + Returns ------- fragments : list of tuple(str, int, int, ndarray, ndarray, ndarray), length=n @@ -287,19 +290,17 @@ def _fragment(atoms, mask=None): #. 3 coordinates of bonded heavy atoms (includes padding values, if there are not enough heavy atoms), #. the coordinates of bonded hydrogen atoms. - + ``None`` for each atom not included by the `mask`. """ if mask is None: mask = np.ones(atoms.array_length(), dtype=bool) if atoms.bonds is None: - raise BadStructureError( - "The input structure must have an associated BondList" - ) + raise BadStructureError("The input structure must have an associated BondList") fragments = [None] * atoms.array_length() - + all_bond_indices, all_bond_types = atoms.bonds.get_all_bonds() elements = atoms.element charges = atoms.charge @@ -308,7 +309,7 @@ def _fragment(atoms, mask=None): for i in range(atoms.array_length()): if not mask[i]: continue - + if elements[i] == "H": # Only create fragments for heavy atoms continue @@ -317,7 +318,7 @@ def _fragment(atoms, mask=None): bond_indices = bond_indices[bond_indices != -1] bond_types = bond_types[bond_types != -1] - heavy_mask = (elements[bond_indices] != "H") + heavy_mask = elements[bond_indices] != "H" heavy_indices = bond_indices[heavy_mask] heavy_types = bond_types[heavy_mask] if (heavy_types == BondType.ANY).any(): @@ -328,7 +329,7 @@ def _fragment(atoms, mask=None): continue # Order the bonded atoms by their bond types - # to remove atom order dependency in the matching step + # to remove atom order dependency in the matching step order = np.argsort(heavy_types) heavy_indices = heavy_indices[order] heavy_types = heavy_types[order] @@ -351,14 +352,14 @@ def _fragment(atoms, mask=None): rem_bond_indices = rem_bond_indices[rem_bond_indices != -1] rem_bond_types = all_bond_types[remote_index] rem_bond_types = rem_bond_types[rem_bond_types != -1] - for rem_rem_index, bond_type in zip( - rem_bond_indices, rem_bond_types - ): + for rem_rem_index, bond_type in zip(rem_bond_indices, rem_bond_types): # If the adjacent atom has a double bond # the partial double bond condition is fulfilled - if bond_type == BondType.AROMATIC_DOUBLE or \ - bond_type == BondType.DOUBLE: - heavy_types[j] = 7 + if ( + bond_type == BondType.AROMATIC_DOUBLE + or bond_type == BondType.DOUBLE + ): + heavy_types[j] = 7 n_heavy_bonds = np.count_nonzero(heavy_mask) if n_heavy_bonds == 0: @@ -373,7 +374,7 @@ def _fragment(atoms, mask=None): remote_index = heavy_indices[0] rem_bond_indices = all_bond_indices[remote_index] rem_bond_indices = rem_bond_indices[rem_bond_indices != -1] - rem_heavy_mask = (elements[rem_bond_indices] != "H") + rem_heavy_mask = elements[rem_bond_indices] != "H" rem_heavy_indices = rem_bond_indices[rem_heavy_mask] # Use the coord of any heavy atom bonded to the remote # atom @@ -411,8 +412,13 @@ def _fragment(atoms, mask=None): stereo = 0 central_coord = coord[i] fragments[i] = ( - elements[i], charges[i], stereo, heavy_types, - central_coord, heavy_coord, hydrogen_coord + elements[i], + charges[i], + stereo, + heavy_types, + central_coord, + heavy_coord, + hydrogen_coord, ) return fragments @@ -430,17 +436,17 @@ def _get_rotation_matrices(fixed, mobile): The fixed coordinates. mobile : ndarray, shape=(m,n,3), dtype=np.float32 The mobile coordinates. - + Returns ------- matrices : ndarray, shape=(m,3,3), dtype=np.float32 The rotation matrices. """ # Calculate cross-covariance matrices - cov = np.sum(fixed[:,:,:,np.newaxis] * mobile[:,:,np.newaxis,:], axis=1) + cov = np.sum(fixed[:, :, :, np.newaxis] * mobile[:, :, np.newaxis, :], axis=1) v, s, w = np.linalg.svd(cov) # Remove possibility of reflected atom coordinates - reflected_mask = (np.linalg.det(v) * np.linalg.det(w) < 0) + reflected_mask = np.linalg.det(v) * np.linalg.det(w) < 0 v[reflected_mask, :, -1] *= -1 matrices = np.matmul(v, w) return matrices @@ -458,6 +464,5 @@ def _rotate(coord, matrices): The rotation matrices. """ return np.transpose( - np.matmul(matrices, np.transpose(coord, axes=(0, 2, 1))), - axes=(0, 2, 1) - ) \ No newline at end of file + np.matmul(matrices, np.transpose(coord, axes=(0, 2, 1))), axes=(0, 2, 1) + ) diff --git a/src/hydride/names.py b/src/hydride/names.py index 951e33d..60b9821 100644 --- a/src/hydride/names.py +++ b/src/hydride/names.py @@ -6,9 +6,9 @@ __author__ = "Patrick Kunzmann" __all__ = ["AtomNameLibrary"] -from os.path import join, dirname, abspath import pickle import string +from os.path import abspath, dirname, join import numpy as np from biotite.structure.error import BadStructureError @@ -32,7 +32,6 @@ class AtomNameLibrary: def __init__(self): self._name_dict = {} - @staticmethod def standard_library(): @@ -50,10 +49,8 @@ def standard_library(): AtomNameLibrary._std_library = AtomNameLibrary() file_name = join(dirname(abspath(__file__)), "names.pickle") with open(file_name, "rb") as names_file: - AtomNameLibrary._std_library._name_dict \ - = pickle.load(names_file) + AtomNameLibrary._std_library._name_dict = pickle.load(names_file) return AtomNameLibrary._std_library - def add_molecule(self, molecule): """ @@ -69,7 +66,7 @@ def add_molecule(self, molecule): raise BadStructureError( "The input molecule must have an associated BondList" ) - + all_bond_indices, _ = molecule.bonds.get_all_bonds() for i in np.where(molecule.element != "H")[0]: @@ -78,15 +75,15 @@ def add_molecule(self, molecule): bonded_indices = bonded_indices[bonded_indices != -1] # Set atom names of bonded hydrogen atoms as values hydrogen_names = [ - molecule.atom_name[j] for j in bonded_indices + molecule.atom_name[j] + for j in bonded_indices if molecule.element[j] == "H" ] if len(hydrogen_names) > 0: - self._name_dict[ - (molecule.res_name[i], molecule.atom_name[i]) - ] = hydrogen_names + self._name_dict[(molecule.res_name[i], molecule.atom_name[i])] = ( + hydrogen_names + ) - def generate_hydrogen_names(self, heavy_res_name, heavy_atom_name): """ Generate hydrogen atom names for the given residue and heavy @@ -116,7 +113,6 @@ def generate_hydrogen_names(self, heavy_res_name, heavy_atom_name): number += 1 yield f"{hydrogen_name}{number}" - else: if len(heavy_atom_name) == 0: # Atom array has no atom names @@ -128,10 +124,8 @@ def generate_hydrogen_names(self, heavy_res_name, heavy_atom_name): # Atom name ends with number # -> assume ligand atom naming # C1 -> H1, H1A, H1B - number = int( - ''.join([c for c in heavy_atom_name if c.isdigit()]) - ) - element = heavy_atom_name[0] + number = int("".join([c for c in heavy_atom_name if c.isdigit()])) + heavy_atom_name[0] # C1 -> H1, H1A, H1B yield f"H{number}" i = 0 @@ -154,4 +148,3 @@ def generate_hydrogen_names(self, heavy_res_name, heavy_atom_name): while True: yield f"H{number}" number += 1 - \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 33d81b9..21d950d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,11 +2,11 @@ # under the 3-Clause BSD License. Please see 'LICENSE.rst' for further # information. -from .util import data_dir from os.path import join -import pytest -import numpy as np import biotite.structure.io.pdbx as pdbx +import numpy as np +import pytest +from tests.util import data_dir def pytest_sessionstart(session): @@ -16,10 +16,11 @@ def pytest_sessionstart(session): """ try: import pyximport + pyximport.install( build_in_temp=False, - setup_args={"include_dirs":np.get_include()}, - language_level=3 + setup_args={"include_dirs": np.get_include()}, + language_level=3, ) except ImportError: pass @@ -33,4 +34,4 @@ def atoms(): pdbx_file = pdbx.BinaryCIFFile.read(join(data_dir(), "1l2y.bcif")) return pdbx.get_structure( pdbx_file, model=1, include_bonds=True, extra_fields=["charge"] - ) \ No newline at end of file + ) diff --git a/tests/test_add.py b/tests/test_add.py index 5dd0ee9..a0a0cd8 100644 --- a/tests/test_add.py +++ b/tests/test_add.py @@ -2,41 +2,44 @@ # under the 3-Clause BSD License. Please see 'LICENSE.rst' for further # information. -import itertools import glob +import itertools from os.path import join -import pytest -import numpy as np import biotite.structure as struc import biotite.structure.info as info import biotite.structure.io.pdbx as pdbx +import numpy as np +import pytest import hydride -from .util import data_dir, place_over_periodic_boundary - - -@pytest.mark.parametrize("res_name, periodic_dim", itertools.product( - [ - "BNZ", # Benzene - "BZF", # Benzofuran - "IND", # indole - "PZO", # Pyrazole - "BZI", # Benzimidazole - "LOM", # Thiazole - "P1R", # Pyrimidine - "ISQ", # Isoquinoline - "NPY", # Naphthalene - "AN3", # Anthracene - "0PY", # Pyridine - "4FT", # Phthalazine - "URA", # Uracil - "CHX", # Cyclohexane - "CEJ", # 1,3-Cyclopentanedione - "CN", # Hydrogen cyanide - "11X", # N-pyridin-3-ylmethylaniline - "ANL", # Aniline - ], - [None, 0, 1, 2] -)) +from tests.util import data_dir, place_over_periodic_boundary + + +@pytest.mark.parametrize( + "res_name, periodic_dim", + itertools.product( + [ + "BNZ", # Benzene + "BZF", # Benzofuran + "IND", # indole + "PZO", # Pyrazole + "BZI", # Benzimidazole + "LOM", # Thiazole + "P1R", # Pyrimidine + "ISQ", # Isoquinoline + "NPY", # Naphthalene + "AN3", # Anthracene + "0PY", # Pyridine + "4FT", # Phthalazine + "URA", # Uracil + "CHX", # Cyclohexane + "CEJ", # 1,3-Cyclopentanedione + "CN", # Hydrogen cyanide + "11X", # N-pyridin-3-ylmethylaniline + "ANL", # Aniline + ], + [None, 0, 1, 2], + ), +) def test_hydrogen_positions(res_name, periodic_dim): """ Test whether the assigned hydrogen positions approximately match @@ -74,9 +77,7 @@ def test_hydrogen_positions(res_name, periodic_dim): if periodic_dim is not None: # Remove PBC again - ref_molecule.coord = struc.remove_pbc_from_coord( - ref_molecule.coord, box - ) + ref_molecule.coord = struc.remove_pbc_from_coord(ref_molecule.coord, box) for category in ref_molecule.get_annotation_categories(): if category == "atom_name": @@ -84,16 +85,15 @@ def test_hydrogen_positions(res_name, periodic_dim): continue try: assert np.all( - test_molecule.get_annotation(category) == - ref_molecule.get_annotation(category) + test_molecule.get_annotation(category) + == ref_molecule.get_annotation(category) ) except AssertionError: print("Failing category:", category) raise # Atom names are only guessed # -> simply check if the atoms names are unique - assert len(np.unique(test_molecule.atom_name)) \ - == len(test_molecule.atom_name) + assert len(np.unique(test_molecule.atom_name)) == len(test_molecule.atom_name) for heavy_i in np.where(ref_molecule.element != "H")[0]: ref_bond_i, _ = ref_molecule.bonds.get_bonds(heavy_i) @@ -107,23 +107,32 @@ def test_hydrogen_positions(res_name, periodic_dim): elif len(ref_h_indices) == 1: # Only a single hydrogen atom # -> unambiguous assignment to reference hydrogen coord - assert np.max(struc.distance( - test_molecule.coord[test_h_indices], - ref_molecule.coord[ref_h_indices], - box=box - )) <= TOLERANCE + assert ( + np.max( + struc.distance( + test_molecule.coord[test_h_indices], + ref_molecule.coord[ref_h_indices], + box=box, + ) + ) + <= TOLERANCE + ) elif len(ref_h_indices) == 2: # Heavy atom has 2 hydrogen atoms # -> Since the hydrogen atoms are indistinguishable, # there are two possible assignment to reference hydrogens - best_distance = min([ - np.max(struc.distance( - test_molecule.coord[test_h_indices], - ref_molecule.coord[ref_h_indices][::order], - box=box - )) - for order in (1, -1) - ]) + best_distance = min( + [ + np.max( + struc.distance( + test_molecule.coord[test_h_indices], + ref_molecule.coord[ref_h_indices][::order], + box=box, + ) + ) + for order in (1, -1) + ] + ) assert best_distance <= TOLERANCE else: # Heavy atom has 3 hydrogen atoms @@ -162,17 +171,13 @@ def test_atom_mask(atoms): # Hydrogenate the first random half of the molecule random_mask = np.random.choice([False, True], heavy_atoms.array_length()) - half_hydrogenated, orig_mask = hydride.add_hydrogen( - heavy_atoms, random_mask - ) + half_hydrogenated, orig_mask = hydride.add_hydrogen(heavy_atoms, random_mask) # Hydrogenate the second half by inverting mask # Special handling due to additional hydrogen atoms # after first hydrogenation inv_random_mask = np.zeros(half_hydrogenated.array_length(), bool) inv_random_mask[orig_mask] = ~random_mask - test_atoms, _ = hydride.add_hydrogen( - half_hydrogenated, inv_random_mask - ) + test_atoms, _ = hydride.add_hydrogen(half_hydrogenated, inv_random_mask) test_atoms = test_atoms[info.standardize_order(test_atoms)] assert test_atoms == ref_atoms @@ -189,7 +194,7 @@ def test_atom_mask_extreme_case(atoms, fill_value): if fill_value is True: # All hydrogen atom are added - ref_atoms, ref_orig_mask = hydride.add_hydrogen(heavy_atoms) + ref_atoms, ref_orig_mask = hydride.add_hydrogen(heavy_atoms) test_atoms, test_orig_mask = hydride.add_hydrogen(heavy_atoms, mask) assert test_atoms == ref_atoms assert np.array_equal(test_orig_mask, ref_orig_mask) @@ -197,13 +202,10 @@ def test_atom_mask_extreme_case(atoms, fill_value): # No hydrogen atoms are added test_atoms, test_orig_mask = hydride.add_hydrogen(heavy_atoms, mask) assert test_atoms == heavy_atoms - assert (test_orig_mask == True).all() + assert (test_orig_mask is True).all() -@pytest.mark.parametrize( - "path", - glob.glob(join(data_dir(), "*.bcif")) -) +@pytest.mark.parametrize("path", glob.glob(join(data_dir(), "*.bcif"))) def test_original_mask(path): """ Check whether the returned original atom mask is correct @@ -220,4 +222,4 @@ def test_original_mask(path): test_model = hydrogenated_model[original_mask] assert hydrogenated_model.array_length() > ref_model.array_length() - assert test_model == ref_model \ No newline at end of file + assert test_model == ref_model diff --git a/tests/test_charge.py b/tests/test_charge.py index fc4db90..bde5e22 100644 --- a/tests/test_charge.py +++ b/tests/test_charge.py @@ -2,10 +2,7 @@ # under the 3-Clause BSD License. Please see 'LICENSE.rst' for further # information. -from os.path import join -import biotite.structure.io.pdbx as pdbx import hydride -from .util import data_dir def test_estimate_amino_acid_charges(atoms): @@ -15,13 +12,13 @@ def test_estimate_amino_acid_charges(atoms): """ ref_charges = { - ("ARG", "NH2") : 1, - ("ASP", "OD2") : -1, - ("CYS", "SG") : 0, - ("GLU", "OE2") : -1, - ("HIS", "ND1") : 0, - ("LYS", "NZ") : 1, - ("TYR", "OH") : 0, + ("ARG", "NH2"): 1, + ("ASP", "OD2"): -1, + ("CYS", "SG"): 0, + ("GLU", "OE2"): -1, + ("HIS", "ND1"): 0, + ("LYS", "NZ"): 1, + ("TYR", "OH"): 0, } heavy_atoms = atoms[atoms.element != "H"] @@ -30,9 +27,7 @@ def test_estimate_amino_acid_charges(atoms): assert test_charges[0] == 1 assert test_charges[-1] == -1 for res_name, atom_name, test_charge in zip( - heavy_atoms.res_name[1:-1], - heavy_atoms.atom_name[1:-1], - test_charges[1:-1] + heavy_atoms.res_name[1:-1], heavy_atoms.atom_name[1:-1], test_charges[1:-1] ): ref_charge = ref_charges.get((res_name, atom_name), 0) - assert test_charge == ref_charge \ No newline at end of file + assert test_charge == ref_charge diff --git a/tests/test_cli.py b/tests/test_cli.py index 07c80f7..5854c4c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,23 +2,22 @@ # under the 3-Clause BSD License. Please see 'LICENSE.rst' for further # information. -import shutil -import tempfile import itertools import os -from os.path import join, splitext +import shutil import subprocess -import pytest -import numpy as np +import tempfile +from os.path import join, splitext import biotite.structure as struc -import biotite.structure.io as strucio import biotite.structure.info as info -import biotite.structure.io.pdbx as pdbx +import biotite.structure.io as strucio import biotite.structure.io.mol as mol +import biotite.structure.io.pdbx as pdbx +import numpy as np +import pytest import hydride from hydride.cli import main as run_cli -from .util import data_dir - +from tests.util import data_dir PDB_ID = "1l2y" PH = 7.0 @@ -64,9 +63,7 @@ def dummy_output_file(): This file should never be written, as the CLI run should fail before. """ - temp_file = tempfile.NamedTemporaryFile( - "r", suffix=f".pdb", delete=False - ) + temp_file = tempfile.NamedTemporaryFile("r", suffix=".pdb", delete=False) yield temp_file.name @@ -78,12 +75,7 @@ def test_simple(input_file, output_file): """ Test CLI run without optional parameters. """ - run_cli([ - "-v", - "-i", input_file, - "-o", output_file, - "-c", str(PH) - ]) + run_cli(["-v", "-i", input_file, "-o", output_file, "-c", str(PH)]) _assert_hydrogen_addition(output_file) @@ -102,26 +94,15 @@ def test_pdbfile_missing_bond_order(): # cannot be determined using 'connect_via_residue_names()' model.res_id *= 2 - input_file = tempfile.NamedTemporaryFile( - "w", suffix=".pdb", delete=False - ) + input_file = tempfile.NamedTemporaryFile("w", suffix=".pdb", delete=False) strucio.save_structure(input_file.name, model) input_file.close() - output_file = tempfile.NamedTemporaryFile( - "r", suffix=f".pdb", delete=False - ) + output_file = tempfile.NamedTemporaryFile("r", suffix=".pdb", delete=False) output_file.close() - with pytest.warns( - UserWarning, match="For some bonds the bond order is unknown" - ): - run_cli([ - "-v", - "-i", input_file.name, - "-o", output_file.name, - "-c", str(PH) - ]) + with pytest.warns(UserWarning, match="For some bonds the bond order is unknown"): + run_cli(["-v", "-i", input_file.name, "-o", output_file.name, "-c", str(PH)]) _assert_hydrogen_addition(output_file.name) @@ -134,26 +115,26 @@ def test_small_molecule(format): """ Test usage of MOL/SDF files for input and output. """ - sd_file = mol.SDFile.read(join(data_dir(), f"TYR.sdf")) + sd_file = mol.SDFile.read(join(data_dir(), "TYR.sdf")) ref_model = mol.get_structure(sd_file) model = ref_model[ref_model.element != "H"] - input_file = tempfile.NamedTemporaryFile( - "w", suffix=f".{format}", delete=False - ) + input_file = tempfile.NamedTemporaryFile("w", suffix=f".{format}", delete=False) strucio.save_structure(input_file.name, model) input_file.close() - output_file = tempfile.NamedTemporaryFile( - "r", suffix=f".{format}", delete=False - ) + output_file = tempfile.NamedTemporaryFile("r", suffix=f".{format}", delete=False) output_file.close() - run_cli([ - "-v", - "-i", input_file.name, - "-o", output_file.name, - ]) + run_cli( + [ + "-v", + "-i", + input_file.name, + "-o", + output_file.name, + ] + ) if format == "mol": mol_file = mol.MOLFile.read(output_file.name) @@ -172,7 +153,7 @@ def test_std_in_out(input_file, output_file): """ Test CLI run with input from STDIN and output to STDOUT. """ - in_format = splitext(input_file )[-1][1:] + in_format = splitext(input_file)[-1][1:] out_format = splitext(output_file)[-1][1:] with open(input_file, "rb") as file: input_file_content = file.read() @@ -180,15 +161,9 @@ def test_std_in_out(input_file, output_file): # Use subprocess instead of API function call # to be able to use STDIN/STDOUT completed_process = subprocess.run( - [ - "hydride", - "-v", - "-I", in_format, - "-O", out_format, - "-c", str(PH) - ], + ["hydride", "-v", "-I", in_format, "-O", out_format, "-c", str(PH)], input=input_file_content, - capture_output=True + capture_output=True, ) stdout = completed_process.stderr.decode("UTF-8") if "ModuleNotFoundError: No module named 'hydride.relax'" in stdout: @@ -211,9 +186,7 @@ def test_extra_fragments(output_file, res_name): TOLERANCE = 0.01 ref_molecule = info.residue(res_name) - frag_temp_file = tempfile.NamedTemporaryFile( - "w", suffix=f".bcif", delete=False - ) + frag_temp_file = tempfile.NamedTemporaryFile("w", suffix=".bcif", delete=False) # As the test case is constructed with the exact same molecule from the library, # move the molecule to assure that correct hydrogen position calculation # is not an artifact @@ -223,17 +196,20 @@ def test_extra_fragments(output_file, res_name): strucio.save_structure(frag_temp_file.name, frag_molecule) heavy_atoms = ref_molecule[ref_molecule.element != "H"] - input_temp_file = tempfile.NamedTemporaryFile( - "w", suffix=f".bcif", delete=False - ) + input_temp_file = tempfile.NamedTemporaryFile("w", suffix=".bcif", delete=False) strucio.save_structure(input_temp_file.name, heavy_atoms) - run_cli([ - "-v", - "-i", input_temp_file.name, - "-f", frag_temp_file.name, - "-o", output_file, - ]) + run_cli( + [ + "-v", + "-i", + input_temp_file.name, + "-f", + frag_temp_file.name, + "-o", + output_file, + ] + ) frag_temp_file.close() os.remove(frag_temp_file.name) @@ -259,14 +235,20 @@ def test_ignored_residues(input_file, output_file): run_cli( [ "-v", - "-i", input_file, - "-g", "A", "5", - "-g", "A", "13", - "-o", output_file, + "-i", + input_file, + "-g", + "A", + "5", + "-g", + "A", + "13", + "-o", + output_file, ] - + list(itertools.chain( - *[("-g", "A", str(res_id)) for res_id in ignored_residues] - )) + + list( + itertools.chain(*[("-g", "A", str(res_id)) for res_id in ignored_residues]) + ) ) test_model = strucio.load_structure(output_file) @@ -275,22 +257,26 @@ def test_ignored_residues(input_file, output_file): assert (test_model.element == "H").any() # ... but not for the ignored residues for res_id in ignored_residues: - assert not ( - (test_model.element == "H") & (test_model.res_id == res_id) - ).any() + assert not ((test_model.element == "H") & (test_model.res_id == res_id)).any() def test_limited_iterations(input_file, output_file): """ Test CLI run with ``--iterations`` parameter. """ - run_cli([ - "-v", - "-i", input_file, - "-o", output_file, - "-c", str(PH), - "--iterations", str(100) - ]) + run_cli( + [ + "-v", + "-i", + input_file, + "-o", + output_file, + "-c", + str(PH), + "--iterations", + str(100), + ] + ) _assert_hydrogen_addition(output_file) @@ -299,13 +285,19 @@ def test_angle_increment(input_file, output_file): """ Test CLI run with ``--angle_increment`` parameter. """ - run_cli([ - "-v", - "-i", input_file, - "-o", output_file, - "-c", str(PH), - "--angle-increment", str(5) - ]) + run_cli( + [ + "-v", + "-i", + input_file, + "-o", + output_file, + "-c", + str(PH), + "--angle-increment", + str(5), + ] + ) _assert_hydrogen_addition(output_file) @@ -314,69 +306,49 @@ def test_pbc(input_file, output_file): """ Test CLI run with ``--pbc`` parameter. """ - run_cli([ - "-v", - "-i", input_file, - "-o", output_file, - "-c", str(PH), - "--pbc" - ]) + run_cli(["-v", "-i", input_file, "-o", output_file, "-c", str(PH), "--pbc"]) _assert_hydrogen_addition(output_file) def test_invalid_iteratons(input_file, dummy_output_file): with pytest.raises(SystemExit) as wrapped_exception: - run_cli([ - "-i", input_file, - "-o", dummy_output_file, - "-n", str(-1) - ]) + run_cli(["-i", input_file, "-o", dummy_output_file, "-n", str(-1)]) assert wrapped_exception.value.code == 1 @pytest.mark.parametrize("model", [-10, -1, 0, 39, 100]) def test_invalid_model(input_file, dummy_output_file, model): with pytest.raises(SystemExit) as wrapped_exception: - run_cli([ - "-i", input_file, - "-o", dummy_output_file, - "-m", str(model) - ]) + run_cli(["-i", input_file, "-o", dummy_output_file, "-m", str(model)]) assert wrapped_exception.value.code == 1 def test_unknown_input_format(input_file, dummy_output_file): with pytest.raises(SystemExit) as wrapped_exception: - run_cli([ - "-i", input_file, - "-o", dummy_output_file, - "-I", "abc" - ]) + run_cli(["-i", input_file, "-o", dummy_output_file, "-I", "abc"]) assert wrapped_exception.value.code == 2 def test_unknown_output_format(input_file, dummy_output_file): with pytest.raises(SystemExit) as wrapped_exception: - run_cli([ - "-i", input_file, - "-o", dummy_output_file, - "-O", "abc" - ]) + run_cli(["-i", input_file, "-o", dummy_output_file, "-O", "abc"]) assert wrapped_exception.value.code == 2 def test_unknown_extension(input_file, dummy_output_file): - temp_file = tempfile.NamedTemporaryFile( - "w", suffix=".abc", delete=False - ) + temp_file = tempfile.NamedTemporaryFile("w", suffix=".abc", delete=False) shutil.copy(input_file, temp_file.name) with pytest.raises(SystemExit) as wrapped_exception: - run_cli([ - "-i", temp_file.name, - "-o", dummy_output_file, - ]) + run_cli( + [ + "-i", + temp_file.name, + "-o", + dummy_output_file, + ] + ) assert wrapped_exception.value.code == 1 temp_file.close() @@ -385,35 +357,37 @@ def test_unknown_extension(input_file, dummy_output_file): def test_noexisting_file(dummy_output_file): with pytest.raises(SystemExit) as wrapped_exception: - run_cli([ - "-i", "/not/a/file.pdb", - "-o", dummy_output_file, - ]) + run_cli( + [ + "-i", + "/not/a/file.pdb", + "-o", + dummy_output_file, + ] + ) assert wrapped_exception.value.code == 1 def test_invalid_file_data(input_file, dummy_output_file): in_format = splitext(input_file)[-1] - temp_file = tempfile.NamedTemporaryFile( - "wb", suffix=in_format, delete=False - ) + temp_file = tempfile.NamedTemporaryFile("wb", suffix=in_format, delete=False) temp_file.write(b"ATOM Invalid data") with pytest.raises(SystemExit) as wrapped_exception: - run_cli([ - "-i", temp_file.name, - "-o", dummy_output_file, - ]) + run_cli( + [ + "-i", + temp_file.name, + "-o", + dummy_output_file, + ] + ) assert wrapped_exception.value.code == 1 def test_missing_ignore_residue(input_file, dummy_output_file): with pytest.raises(SystemExit) as wrapped_exception: - run_cli([ - "-i", input_file, - "-o", dummy_output_file, - "-g", "A", "42" - ]) + run_cli(["-i", input_file, "-o", dummy_output_file, "-g", "A", "42"]) assert wrapped_exception.value.code == 1 @@ -434,4 +408,4 @@ def _assert_hydrogen_addition(test_file): test_model = strucio.load_structure(test_file) assert test_model.array_length() == ref_model.array_length() - assert test_model.atom_name.tolist() == ref_model.atom_name.tolist() \ No newline at end of file + assert test_model.atom_name.tolist() == ref_model.atom_name.tolist() diff --git a/tests/test_fragments.py b/tests/test_fragments.py index ae47d99..e9de142 100644 --- a/tests/test_fragments.py +++ b/tests/test_fragments.py @@ -3,39 +3,41 @@ # information. import itertools -from biotite.structure.atoms import Atom -import pytest -import numpy as np import biotite.structure as struc import biotite.structure.info as info +import numpy as np +import pytest import hydride -@pytest.mark.parametrize("res_name", [ - "BNZ", # Benzene - "BZF", # Benzofuran - "IND", # indole - "PZO", # Pyrazole - "BZI", # Benzimidazole - "LOM", # Thiazole - "P1R", # Pyrimidine - "ISQ", # Isoquinoline - "NPY", # Naphthalene - "AN3", # Anthracene - "0PY", # Pyridine - "4FT", # Phthalazine - "URA", # Uracil - "CHX", # Cyclohexane - "CEJ", # 1,3-Cyclopentanedione - "CN", # Hydrogen cyanide - "11X", # N-pyridin-3-ylmethylaniline - "ANL", # Aniline -]) +@pytest.mark.parametrize( + "res_name", + [ + "BNZ", # Benzene + "BZF", # Benzofuran + "IND", # indole + "PZO", # Pyrazole + "BZI", # Benzimidazole + "LOM", # Thiazole + "P1R", # Pyrimidine + "ISQ", # Isoquinoline + "NPY", # Naphthalene + "AN3", # Anthracene + "0PY", # Pyridine + "4FT", # Phthalazine + "URA", # Uracil + "CHX", # Cyclohexane + "CEJ", # 1,3-Cyclopentanedione + "CN", # Hydrogen cyanide + "11X", # N-pyridin-3-ylmethylaniline + "ANL", # Aniline + ], +) def test_hydrogen_positions(res_name): """ Test whether the assigned hydrogen positions approximately match the original hydrogen positions for a given molecule. - + All chosen molecules consist completely of heavy atoms without rotational freedom for the bonded hydrogen atoms, such as aromatic or cyclic compounds. @@ -53,10 +55,10 @@ def test_hydrogen_positions(res_name): molecule = info.residue(res_name) # Perform translation of the molecule along the three axes ref_hydrogen_coord = molecule.coord[molecule.element == "H"] - + heavy_atoms = molecule[molecule.element != "H"] test_hydrogen_coord = library.calculate_hydrogen_coord(heavy_atoms) - + test_count = 0 for coord in test_hydrogen_coord: test_count += len(coord) @@ -72,23 +74,32 @@ def test_hydrogen_positions(res_name): elif len(hydrogen_coord) == 1: # Only a single hydrogen atom # -> unambiguous assignment to reference hydrogen coord - assert np.max(struc.distance( - hydrogen_coord, - ref_hydrogen_coord[ref_index : ref_index+1] - )) <= TOLERANCE + assert ( + np.max( + struc.distance( + hydrogen_coord, + ref_hydrogen_coord[ref_index : ref_index + 1], + ) + ) + <= TOLERANCE + ) ref_index += 1 elif len(hydrogen_coord) == 2: # Heavy atom has 2 hydrogen atoms # -> Since the hydrogen atoms are indistinguishable, # there are two possible assignment to reference # hydrogen atoms - best_distance = min([ - np.max(struc.distance( - hydrogen_coord, - ref_hydrogen_coord[ref_index : ref_index+2][::order] - )) - for order in (1, -1) - ]) + best_distance = min( + [ + np.max( + struc.distance( + hydrogen_coord, + ref_hydrogen_coord[ref_index : ref_index + 2][::order], + ) + ) + for order in (1, -1) + ] + ) assert best_distance <= TOLERANCE ref_index += 2 else: @@ -101,7 +112,6 @@ def test_hydrogen_positions(res_name): raise - def test_missing_fragment(): """ If a molecule contains an unknown fragment, check if a warning is @@ -111,13 +121,12 @@ def test_missing_fragment(): lib = hydride.FragmentLibrary.standard_library() - ref_mol = info.residue("BZI") # Benzimidazole + ref_mol = info.residue("BZI") # Benzimidazole # It should not be possible to have a nitrogen at this position # with a positive charge ref_mol.charge[0] = 1 ref_hydrogen_coord = ref_mol.coord[ref_mol.element == "H"] - test_mol = test_mol = ref_mol[ref_mol.element != "H"] with pytest.warns( UserWarning, match="Missing fragment for atom 'N1' at position 0" @@ -128,12 +137,17 @@ def test_missing_fragment(): for coord in hydrogen_coord: flattend_coord += coord.tolist() test_hydrogen_coord = np.array(flattend_coord) - - assert np.max(struc.distance( - test_hydrogen_coord, - # Expect missing first hydrogen due to missing fragment - ref_hydrogen_coord[1:] - )) <= TOLERANCE + + assert ( + np.max( + struc.distance( + test_hydrogen_coord, + # Expect missing first hydrogen due to missing fragment + ref_hydrogen_coord[1:], + ) + ) + <= TOLERANCE + ) @pytest.mark.parametrize( @@ -142,7 +156,7 @@ def test_missing_fragment(): # L-alanine and D-alanine ["ALA", "DAL"], ["ALA", "DAL"], - ) + ), ) def test_stereocenter(lib_enantiomer, subject_enantiomer): """ @@ -166,7 +180,7 @@ def test_stereocenter(lib_enantiomer, subject_enantiomer): test_model, _ = hydride.add_hydrogen(test_model, fragment_library=lib) test_model, _ = struc.superimpose( - ref_model, test_model, atom_mask = (test_model.element != "H") + ref_model, test_model, atom_mask=(test_model.element != "H") ) ref_stereo_h_coord = ref_model.coord[ref_model.atom_name == "HA"][0] test_stereo_h_coord = test_model.coord[test_model.atom_name == "HA"][0] @@ -174,14 +188,17 @@ def test_stereocenter(lib_enantiomer, subject_enantiomer): assert struc.distance(test_stereo_h_coord, ref_stereo_h_coord) <= TOLERANCE -@pytest.mark.parametrize("res_name, nitrogen_index", [ - ("ARG", 7), # Arginine NE - ("ARG", 9), # Arginine NH1 - ("ARG", 10), # Arginine NH2 - ("PRO", 0), # Proline N - ("ASN", 7), # Asparagine N - ("T", 15), # Thymidine N3 -]) +@pytest.mark.parametrize( + "res_name, nitrogen_index", + [ + ("ARG", 7), # Arginine NE + ("ARG", 9), # Arginine NH1 + ("ARG", 10), # Arginine NH2 + ("PRO", 0), # Proline N + ("ASN", 7), # Asparagine N + ("T", 15), # Thymidine N3 + ], +) def test_partial_double_bonds(res_name, nitrogen_index): """ It is difficult to assign hydrogen atoms to nitrogen properly, @@ -204,11 +221,11 @@ def test_partial_double_bonds(res_name, nitrogen_index): molecule = info.residue(res_name) if molecule.element[nitrogen_index] != "N": raise ValueError("Invalid test case") - + np.random.seed(0) molecule = struc.rotate(molecule, np.random.rand(3)) molecule = struc.translate(molecule, np.random.rand(3)) - + bond_indices, _ = molecule.bonds.get_bonds(nitrogen_index) bond_h_indices = bond_indices[molecule.element[bond_indices] == "H"] ref_hydrogen_coord = molecule.coord[bond_h_indices] @@ -224,22 +241,20 @@ def test_partial_double_bonds(res_name, nitrogen_index): elif len(test_hydrogen_coord) == 1: # Only a single hydrogen atom # -> unambiguous assignment to reference hydrogen coord - assert np.max(struc.distance( - test_hydrogen_coord, - ref_hydrogen_coord - )) <= TOLERANCE + assert ( + np.max(struc.distance(test_hydrogen_coord, ref_hydrogen_coord)) <= TOLERANCE + ) elif len(test_hydrogen_coord) == 2: # Heavy atom has 2 hydrogen atoms # -> Since the hydrogen atoms are indistinguishable, # there are two possible assignment to reference # hydrogen atoms - best_distance = min([ - np.max(struc.distance( - test_hydrogen_coord, - ref_hydrogen_coord[::order] - )) - for order in (1, -1) - ]) + best_distance = min( + [ + np.max(struc.distance(test_hydrogen_coord, ref_hydrogen_coord[::order])) + for order in (1, -1) + ] + ) assert best_distance <= TOLERANCE else: # Heavy atom has 3 hydrogen atoms @@ -259,17 +274,19 @@ def test_undefined_bond_type(): molecule.bonds = struc.BondList( molecule.array_length(), # Remove bond type from existing bonds - molecule.bonds.as_array()[:,:2] + molecule.bonds.as_array()[:, :2], ) # Test handing of 'BondType.ANY' in 'add_molecule()' library = hydride.FragmentLibrary() with pytest.warns(UserWarning) as record: library.add_molecule(molecule) - assert any([ - True if "undefined bond type" in warning.message.args[0] else False - for warning in record - ]) + assert any( + [ + True if "undefined bond type" in warning.message.args[0] else False + for warning in record + ] + ) # No fragment should be added assert len(library._frag_dict) == 0 @@ -278,11 +295,13 @@ def test_undefined_bond_type(): library = hydride.FragmentLibrary.standard_library() with pytest.warns(UserWarning) as record: hydrogen_coord = library.calculate_hydrogen_coord(heavy_atoms) - assert any([ - True if "undefined bond type" in warning.message.args[0] else False - for warning in record - ]) + assert any( + [ + True if "undefined bond type" in warning.message.args[0] else False + for warning in record + ] + ) assert len(hydrogen_coord) == heavy_atoms.array_length() for coord in hydrogen_coord: # For each heavy atom there should be no added hydrogen - assert len(coord) == 0 \ No newline at end of file + assert len(coord) == 0 diff --git a/tests/test_names.py b/tests/test_names.py index 127f867..3daa6f2 100644 --- a/tests/test_names.py +++ b/tests/test_names.py @@ -2,9 +2,9 @@ # under the 3-Clause BSD License. Please see 'LICENSE.rst' for further # information. -import pytest -import numpy as np import biotite.structure.info as info +import numpy as np +import pytest import hydride @@ -14,7 +14,7 @@ ("N", ["H", "H1", "H2"]), ("CB", ["HB", "HB1", "HB2"]), ("C42", ["H42", "H42A", "H42B"]), - ] + ], ) def test_generated_names(heavy_atom_name, ref_hydrogen_names): """ @@ -24,16 +24,20 @@ def test_generated_names(heavy_atom_name, ref_hydrogen_names): name_lib = hydride.AtomNameLibrary() gen = name_lib.generate_hydrogen_names("", heavy_atom_name) test_hydrogen_names = [next(gen) for _ in range(len(ref_hydrogen_names))] - + assert test_hydrogen_names == ref_hydrogen_names np.random.seed(0) res_names = info.all_residues() + + @pytest.mark.parametrize( "res_name", - [res_names[i] for i in - np.random.choice(np.arange(len(res_names)), size=1000, replace=False)] + [ + res_names[i] + for i in np.random.choice(np.arange(len(res_names)), size=1000, replace=False) + ], ) def test_names_from_library(res_name): """ @@ -52,11 +56,10 @@ def test_names_from_library(res_name): for i in np.where(residue.element != "H")[0]: bond_indices, _ = residue.bonds.get_bonds(i) ref_hydrogen_names = [ - residue.atom_name[j] for j in bond_indices - if residue.element[j] == "H" + residue.atom_name[j] for j in bond_indices if residue.element[j] == "H" ] - + gen = name_lib.generate_hydrogen_names(res_name, residue.atom_name[i]) test_hydrogen_names = [next(gen) for _ in range(len(ref_hydrogen_names))] - assert test_hydrogen_names == ref_hydrogen_names \ No newline at end of file + assert test_hydrogen_names == ref_hydrogen_names diff --git a/tests/test_relax.py b/tests/test_relax.py index 7ec97aa..6ad1943 100644 --- a/tests/test_relax.py +++ b/tests/test_relax.py @@ -2,16 +2,16 @@ # under the 3-Clause BSD License. Please see 'LICENSE.rst' for further # information. -from os.path import join import itertools -import pytest -import numpy as np +from os.path import join import biotite.structure as struc import biotite.structure.info as info import biotite.structure.io.pdbx as pdbx +import numpy as np +import pytest import hydride from hydride.relax import _find_rotatable_bonds -from .util import data_dir, place_over_periodic_boundary +from tests.util import data_dir, place_over_periodic_boundary @pytest.fixture @@ -19,27 +19,31 @@ def ethane(): # Construct ethane in staggered conformation ethane = struc.AtomArray(8) ethane.element = np.array(["C", "C", "H", "H", "H", "H", "H", "H"]) - ethane.coord = np.array([ - [-0.756, 0.000, 0.000], - [ 0.756, 0.000, 0.000], - [-1.140, 0.659, 0.7845], - [-1.140, 0.350, -0.9626], - [-1.140, -1.009, 0.1781], - [ 1.140, -0.350, 0.9626], - [ 1.140, 1.009, -0.1781], - [ 1.140, -0.659, -0.7845], - ]) + ethane.coord = np.array( + [ + [-0.756, 0.000, 0.000], + [0.756, 0.000, 0.000], + [-1.140, 0.659, 0.7845], + [-1.140, 0.350, -0.9626], + [-1.140, -1.009, 0.1781], + [1.140, -0.350, 0.9626], + [1.140, 1.009, -0.1781], + [1.140, -0.659, -0.7845], + ] + ) ethane.bonds = struc.BondList( 8, - np.array([ - [0, 1, 1], - [0, 2, 1], - [0, 3, 1], - [0, 4, 1], - [1, 5, 1], - [1, 6, 1], - [1, 7, 1], - ]) + np.array( + [ + [0, 1, 1], + [0, 2, 1], + [0, 3, 1], + [0, 4, 1], + [1, 5, 1], + [1, 6, 1], + [1, 7, 1], + ] + ), ) ethane.set_annotation("charge", np.zeros(ethane.array_length(), dtype=int)) @@ -67,9 +71,9 @@ def test_staggered(ethane, seed, periodic_dim): angle = np.random.rand() * 2 * np.pi ethane.coord[5:] = struc.rotate_about_axis( ethane.coord[5:], - angle = angle, - axis = ethane.coord[1] - ethane.coord[0], - support = ethane.coord[0] + angle=angle, + axis=ethane.coord[1] - ethane.coord[0], + support=ethane.coord[0], ) # Check if new conformation ethane is not staggered anymore @@ -90,15 +94,13 @@ def test_staggered(ethane, seed, periodic_dim): ethane, # The angle increment must be smaller # than the expected accuracy (abs=1) - angle_increment = np.deg2rad(0.5), - box=box + angle_increment=np.deg2rad(0.5), + box=box, ) if periodic_dim is not None: # Remove PBC again - ethane.coord = struc.remove_pbc_from_coord( - ethane.coord, box - ) + ethane.coord = struc.remove_pbc_from_coord(ethane.coord, box) # Check if staggered conformation is restored dihed = struc.dihedral(ethane[2], ethane[0], ethane[1], ethane[5]) @@ -147,9 +149,7 @@ def test_hydrogen_bonds(periodic_dim): if periodic_dim is not None: # Remove PBC again - atoms.coord = struc.remove_pbc_from_coord( - atoms.coord, box - ) + atoms.coord = struc.remove_pbc_from_coord(atoms.coord, box) test_num = len(struc.hbond(atoms, mask, mask)) @@ -165,33 +165,44 @@ def test_hydrogen_bonds(periodic_dim): "res_name, ref_bonds", [ # Fructopyranose - ("FRU", [ - ( "O1", "C1", True, ("HO1",)), - ( "O2", "C2", True, ("HO2",)), - ( "O3", "C3", True, ("HO3",)), - ( "O4", "C4", True, ("HO4",)), - ( "O6", "C6", True, ("HO6",)), - ]), + ( + "FRU", + [ + ("O1", "C1", True, ("HO1",)), + ("O2", "C2", True, ("HO2",)), + ("O3", "C3", True, ("HO3",)), + ("O4", "C4", True, ("HO4",)), + ("O6", "C6", True, ("HO6",)), + ], + ), # Arginine with positive side chain - ("ARG", [ - ( "N", "CA", True, ("H", "H2")), - ("OXT", "C", True, ("HXT",)), - ]), + ( + "ARG", + [ + ("N", "CA", True, ("H", "H2")), + ("OXT", "C", True, ("HXT",)), + ], + ), # Isoleucine - ("ILE", [ - ( "N", "CA", True, ("H", "H2")), - ("OXT", "C", True, ("HXT",)), - ("CG2", "CB", True, ("HG21", "HG22", "HG23")), - ("CD1", "CG1", True, ("HD11", "HD12", "HD13")), - ]), + ( + "ILE", + [ + ("N", "CA", True, ("H", "H2")), + ("OXT", "C", True, ("HXT",)), + ("CG2", "CB", True, ("HG21", "HG22", "HG23")), + ("CD1", "CG1", True, ("HD11", "HD12", "HD13")), + ], + ), # 1-phenylguanidine - ("PL0", [ - ( "N3", "C7", False, ("HN3",)), - ]), + ( + "PL0", + [ + ("N3", "C7", False, ("HN3",)), + ], + ), # Water - ("HOH", [ - ]), - ] + ("HOH", []), + ], ) def test_bond_identification(res_name, ref_bonds): """ @@ -209,7 +220,7 @@ def test_bond_identification(res_name, ref_bonds): molecule.atom_name[center_atom_i], molecule.atom_name[bonded_atom_i], is_free, - tuple(np.sort(molecule.atom_name[h_indices])) + tuple(np.sort(molecule.atom_name[h_indices])), ) assert bond_tuple in ref_bonds @@ -236,9 +247,7 @@ def test_return_energies(atoms): energies. """ - _, energies = hydride.relax_hydrogen( - atoms, return_energies=True - ) + _, energies = hydride.relax_hydrogen(atoms, return_energies=True) assert isinstance(energies, np.ndarray) # Energies should monotonically decrease assert (np.diff(energies) <= 0).all() @@ -274,8 +283,8 @@ def test_partial_charges(ethane, repulsive): ethane, # The angle increment must be smaller # than the expected accuracy (abs=1) - angle_increment = np.deg2rad(0.5), - partial_charges = charges + angle_increment=np.deg2rad(0.5), + partial_charges=charges, ) # Check if staggered conformation is restored @@ -298,20 +307,14 @@ def test_limited_iterations(atoms): """ ITERATIONS = 4 - traj_coord = hydride.relax_hydrogen( - atoms, ITERATIONS, return_trajectory=True - ) + traj_coord = hydride.relax_hydrogen(atoms, ITERATIONS, return_trajectory=True) assert traj_coord.shape[0] == ITERATIONS @pytest.mark.parametrize( "iterations, return_trajectory, return_energies", - itertools.product( - [None, 100], - [False, True], - [False, True] - ) + itertools.product([None, 100], [False, True], [False, True]), ) def test_shortcut_return(iterations, return_trajectory, return_energies): """ @@ -321,17 +324,21 @@ def test_shortcut_return(iterations, return_trajectory, return_energies): without rotatable bonds, are compared. """ # Rotatable - ref_atoms = info.residue("GLY") + ref_atoms = info.residue("GLY") # Non-rotatable test_atoms = info.residue("HOH") ref_output = hydride.relax_hydrogen( - ref_atoms, iterations, - return_trajectory=return_trajectory, return_energies=return_energies + ref_atoms, + iterations, + return_trajectory=return_trajectory, + return_energies=return_energies, ) test_output = hydride.relax_hydrogen( - test_atoms, iterations, - return_trajectory=return_trajectory, return_energies=return_energies + test_atoms, + iterations, + return_trajectory=return_trajectory, + return_energies=return_energies, ) if isinstance(ref_output, tuple): @@ -356,4 +363,4 @@ def test_atom_mask(atoms): test_coord = hydride.relax_hydrogen(atoms, mask=mask) assert (test_coord[~mask] == ref_coord[~mask]).all() - assert not (test_coord[mask] == ref_coord[mask]).all() \ No newline at end of file + assert not (test_coord[mask] == ref_coord[mask]).all() diff --git a/tests/util.py b/tests/util.py index 4bbec47..dd15ca0 100644 --- a/tests/util.py +++ b/tests/util.py @@ -2,9 +2,9 @@ # under the 3-Clause BSD License. Please see 'LICENSE.rst' for further # information. -from os.path import join, dirname, realpath -import numpy as np +from os.path import dirname, join, realpath import biotite.structure as struc +import numpy as np def data_dir(): @@ -20,8 +20,7 @@ def place_over_periodic_boundary(atoms, dimension, box_size): box = np.identity(3) * box_size min_coord = np.min(atoms.coord[:, dimension]) max_coord = np.max(atoms.coord[:, dimension]) - atoms.coord[:, dimension] += \ - box_size - min_coord - (max_coord - min_coord) / 2 + atoms.coord[:, dimension] += box_size - min_coord - (max_coord - min_coord) / 2 # Keep the molecule in the center of the box in all other dimensions for d in [0, 1, 2]: if d == dimension: @@ -30,4 +29,4 @@ def place_over_periodic_boundary(atoms, dimension, box_size): atoms.coord[:, d] += box_size / 2 # Perform the 'cut' atoms.coord = struc.move_inside_box(atoms.coord, box) - return atoms \ No newline at end of file + return atoms