From d1eae1540d4d801ae7b671eb5846ba010328fda7 Mon Sep 17 00:00:00 2001 From: benoistlaurent Date: Mon, 9 Feb 2026 14:29:51 +0100 Subject: [PATCH 01/14] fixes type checking issues- --- src/grodecoder/databases/api.py | 2 +- src/grodecoder/identifier.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/grodecoder/databases/api.py b/src/grodecoder/databases/api.py index 5a4df93..f4a4b08 100644 --- a/src/grodecoder/databases/api.py +++ b/src/grodecoder/databases/api.py @@ -49,7 +49,7 @@ def assert_database_exists(path: Path): assert_database_exists(CSML_DB_PATH) -ModelType = TypeVar("ModelType", Ion, Solvent, Nucleotide, AminoAcid) +ModelType = TypeVar("ModelType", Ion, Solvent, Nucleotide, AminoAcid, mad.Residue, csml.Residue) def _read_database(path: Path, model: type[ModelType]) -> list[ModelType]: diff --git a/src/grodecoder/identifier.py b/src/grodecoder/identifier.py index 024a308..8021c17 100644 --- a/src/grodecoder/identifier.py +++ b/src/grodecoder/identifier.py @@ -72,7 +72,8 @@ def _select_protein(universe: UniverseLike) -> AtomGroup: logger.debug("excluding possible methanol residues (MET) from protein") methanol = _find_methanol(universe) if methanol: - selection_str += f" and not index {' '.join(map(str, methanol))}" + indexes = " ".join(str(i) for i in methanol) # ty complains when using map + selection_str += f" and not index {indexes}" logger.debug("selecting protein") protein = universe.select_atoms(selection_str) From bdfc7f6244f95127ebc3aefa074a3662c4f19a00 Mon Sep 17 00:00:00 2001 From: benoistlaurent Date: Mon, 9 Feb 2026 14:44:34 +0100 Subject: [PATCH 02/14] upgrade dependencies: urllib3, protobuf --- uv.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/uv.lock b/uv.lock index 626bc4a..3380543 100644 --- a/uv.lock +++ b/uv.lock @@ -960,17 +960,17 @@ wheels = [ [[package]] name = "protobuf" -version = "6.33.0" +version = "6.33.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/ff/64a6c8f420818bb873713988ca5492cba3a7946be57e027ac63495157d97/protobuf-6.33.0.tar.gz", hash = "sha256:140303d5c8d2037730c548f8c7b93b20bb1dc301be280c378b82b8894589c954", size = 443463, upload-time = "2025-10-15T20:39:52.159Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/ee/52b3fa8feb6db4a833dfea4943e175ce645144532e8a90f72571ad85df4e/protobuf-6.33.0-cp310-abi3-win32.whl", hash = "sha256:d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035", size = 425593, upload-time = "2025-10-15T20:39:40.29Z" }, - { url = "https://files.pythonhosted.org/packages/7b/c6/7a465f1825872c55e0341ff4a80198743f73b69ce5d43ab18043699d1d81/protobuf-6.33.0-cp310-abi3-win_amd64.whl", hash = "sha256:9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee", size = 436882, upload-time = "2025-10-15T20:39:42.841Z" }, - { url = "https://files.pythonhosted.org/packages/e1/a9/b6eee662a6951b9c3640e8e452ab3e09f117d99fc10baa32d1581a0d4099/protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:905b07a65f1a4b72412314082c7dbfae91a9e8b68a0cc1577515f8df58ecf455", size = 427521, upload-time = "2025-10-15T20:39:43.803Z" }, - { url = "https://files.pythonhosted.org/packages/10/35/16d31e0f92c6d2f0e77c2a3ba93185130ea13053dd16200a57434c882f2b/protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e0697ece353e6239b90ee43a9231318302ad8353c70e6e45499fa52396debf90", size = 324445, upload-time = "2025-10-15T20:39:44.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/eb/2a981a13e35cda8b75b5585aaffae2eb904f8f351bdd3870769692acbd8a/protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:e0a1715e4f27355afd9570f3ea369735afc853a6c3951a6afe1f80d8569ad298", size = 339159, upload-time = "2025-10-15T20:39:46.186Z" }, - { url = "https://files.pythonhosted.org/packages/21/51/0b1cbad62074439b867b4e04cc09b93f6699d78fd191bed2bbb44562e077/protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:35be49fd3f4fefa4e6e2aacc35e8b837d6703c37a2168a55ac21e9b1bc7559ef", size = 323172, upload-time = "2025-10-15T20:39:47.465Z" }, - { url = "https://files.pythonhosted.org/packages/07/d1/0a28c21707807c6aacd5dc9c3704b2aa1effbf37adebd8caeaf68b17a636/protobuf-6.33.0-py3-none-any.whl", hash = "sha256:25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995", size = 170477, upload-time = "2025-10-15T20:39:51.311Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, ] [[package]] @@ -1520,11 +1520,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]] From 3f89b9cacdf5ff5a9b651fca19edcea09330ff4c Mon Sep 17 00:00:00 2001 From: benoistlaurent Date: Mon, 9 Feb 2026 15:59:03 +0100 Subject: [PATCH 03/14] removed streamlit from dev dependencies (already in main dependencies) --- pyproject.toml | 1 - uv.lock | 2 -- 2 files changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d3133f7..a72a52f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,6 @@ dev = [ "biopython>=1.85", "playwright>=1.51.0", "requests>=2.32.3", - "streamlit>=1.44.0", "watchdog>=6.0.0", "ty==0.0.1a26", ] diff --git a/uv.lock b/uv.lock index 3380543..8775b48 100644 --- a/uv.lock +++ b/uv.lock @@ -382,7 +382,6 @@ dev = [ { name = "pytest" }, { name = "requests" }, { name = "ruff" }, - { name = "streamlit" }, { name = "ty" }, { name = "types-requests" }, { name = "watchdog" }, @@ -407,7 +406,6 @@ dev = [ { name = "pytest", specifier = ">=8.3.5" }, { name = "requests", specifier = ">=2.32.3" }, { name = "ruff", specifier = ">=0.11.2" }, - { name = "streamlit", specifier = ">=1.44.0" }, { name = "ty", specifier = "==0.0.1a26" }, { name = "types-requests", specifier = ">=2.32.4.20250809" }, { name = "watchdog", specifier = ">=6.0.0" }, From 160b281bfbba3966700029d5fd46206038e7daf9 Mon Sep 17 00:00:00 2001 From: benoistlaurent Date: Mon, 9 Feb 2026 16:04:10 +0100 Subject: [PATCH 04/14] fixes minor type hinting issue --- src/grodecoder/identifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/grodecoder/identifier.py b/src/grodecoder/identifier.py index 8021c17..a9a3d2e 100644 --- a/src/grodecoder/identifier.py +++ b/src/grodecoder/identifier.py @@ -1,5 +1,5 @@ import time -from typing import Iterable, Iterator +from typing import Iterable, Iterator, Sequence from loguru import logger from MDAnalysis import AtomGroup @@ -136,7 +136,7 @@ def _unique_definitions(definitions: Iterable[DB.ResidueDefinition]) -> dict[str return unique_items -def _remove_identified_atoms(universe: AtomGroup, molecules: list[HasAtoms]) -> AtomGroup: +def _remove_identified_atoms(universe: AtomGroup, molecules: Sequence[HasAtoms]) -> AtomGroup: """Removes the atoms of the identified molecules from the universe.""" for molecule in molecules: universe -= molecule.atoms From 4f034efa3564048f673e84f67d2d74a1d518841a Mon Sep 17 00:00:00 2001 From: benoistlaurent Date: Thu, 12 Feb 2026 10:06:22 +0100 Subject: [PATCH 05/14] new guess_resolution --- src/grodecoder/core.py | 4 +- src/grodecoder/guesser.py | 168 ++++++++++++++++++ src/grodecoder/models.py | 5 - src/grodecoder/toputils.py | 57 ------ tests/test_guesser.py | 124 +++++++++++++ tests/test_toputils/test_toputils.py | 52 ------ .../test_toputils_integration.py | 17 -- 7 files changed, 294 insertions(+), 133 deletions(-) create mode 100644 src/grodecoder/guesser.py create mode 100644 tests/test_guesser.py diff --git a/src/grodecoder/core.py b/src/grodecoder/core.py index 18a27db..12745c0 100644 --- a/src/grodecoder/core.py +++ b/src/grodecoder/core.py @@ -6,7 +6,7 @@ from .identifier import identify from .io import read_universe from .models import Decoded -from .toputils import guess_resolution +from .guesser import guess_resolution from .settings import get_settings @@ -20,7 +20,7 @@ def decode(universe: UniverseLike) -> Decoded: settings = get_settings() - resolution = guess_resolution(universe, cutoff_distance=settings.resolution_detection.distance_cutoff) + resolution = guess_resolution(universe) logger.info(f"Guessed resolution: {resolution}") # Guesses the chain dection distance cutoff if not provided by the user. diff --git a/src/grodecoder/guesser.py b/src/grodecoder/guesser.py new file mode 100644 index 0000000..d992745 --- /dev/null +++ b/src/grodecoder/guesser.py @@ -0,0 +1,168 @@ +from enum import StrEnum, auto +from itertools import islice +from typing import Self + +import MDAnalysis as MDA +import numpy as np +from loguru import logger +from pydantic import BaseModel, ConfigDict, model_validator + +from .toputils import has_bonds + + +class ResolutionValue(StrEnum): + ALL_ATOM = auto() + COARSE_GRAIN = auto() + + +class AllAtomResolutionReason(StrEnum): + DEFAULT = auto() + HAS_HYDROGENS = auto() + RESIDUES_HAVE_BONDS_WITHIN_CUTOFF = auto() + + +class CoarseGrainResolutionReason(StrEnum): + HAS_BB_ATOMS = auto() + HAS_ONE_GRAIN_PER_RESIDUE = auto() + HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF = auto() + PROTEIN_HAS_NO_HYDROGEN = auto() + + +class MolecularResolution(BaseModel): + value: ResolutionValue + reason: AllAtomResolutionReason | CoarseGrainResolutionReason | None = None + + model_config = ConfigDict(validate_assignment=True) + + def is_all_atom(self) -> bool: + return self.value == ResolutionValue.ALL_ATOM + + def is_coarse_grain(self) -> bool: + return self.value == ResolutionValue.COARSE_GRAIN + + @model_validator(mode="after") + def check_reason(self) -> Self: + """Validate that reason is compatible with value.""" + if self.value == ResolutionValue.ALL_ATOM: + if not isinstance(self.reason, AllAtomResolutionReason): + raise ValueError( + f"reason must be AllAtomResolutionReason when value is 'all-atom', " + f"got {type(self.reason).__name__}" + ) + elif self.value == ResolutionValue.COARSE_GRAIN: + if not isinstance(self.reason, CoarseGrainResolutionReason): + raise ValueError( + f"reason must be CoarseGrainResolutionReason when value is 'coarse-grain', " + f"got {type(self.reason).__name__}" + ) + return self + + @classmethod + def AllAtomWithHydrogen(cls) -> Self: + """System that contains hydrogen atoms.""" + return cls(value=ResolutionValue.ALL_ATOM, reason=AllAtomResolutionReason.HAS_HYDROGENS) + + @classmethod + def AllAtomWithStandardBonds(cls) -> Self: + """System in which atoms within residues have distances that are typical of all-atom models.""" + return cls( + value=ResolutionValue.ALL_ATOM, reason=AllAtomResolutionReason.RESIDUES_HAVE_BONDS_WITHIN_CUTOFF + ) + + @classmethod + def CoarseGrainMartini(cls) -> Self: + """System that contains atoms named BB*.""" + return cls(value=ResolutionValue.COARSE_GRAIN, reason=CoarseGrainResolutionReason.HAS_BB_ATOMS) + + @classmethod + def CoarseGrainSingleParticle(cls) -> Self: + """System with residues made of a single particle.""" + return cls( + value=ResolutionValue.COARSE_GRAIN, reason=CoarseGrainResolutionReason.HAS_ONE_GRAIN_PER_RESIDUE + ) + + @classmethod + def CoarseGrainOther(cls) -> Self: + """Other coarse grain systems, typically with bond length between particle greater that standard + all-atom models.""" + return cls( + value=ResolutionValue.COARSE_GRAIN, reason=CoarseGrainResolutionReason.HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF + ) + + @classmethod + def CoarseGrainProteinHasNoHydrogen(cls) -> Self: + """Protein is detected and residues do not have hydrogen atoms.""" + return cls( + value=ResolutionValue.COARSE_GRAIN, reason=CoarseGrainResolutionReason.PROTEIN_HAS_NO_HYDROGEN + ) + + +def _has_hydrogen(model: MDA.AtomGroup) -> bool: + """Returns True if a model contains hydrogen atoms.""" + return "H" in model.atoms.types + + +def _is_martini(model: MDA.AtomGroup) -> bool: + """Returns True if a model contains atoms named BB*.""" + return bool(np.any(np.char.startswith(model.atoms.names.astype("U"), "BB"))) + + +def _has_bonds_within_all_atom_cutoff(model: MDA.AtomGroup) -> bool: + cutoff_distance = 1.6 + for residue in model.residues: + if has_bonds(residue, cutoff_distance): + return True + return False + + +def _has_protein(model: MDA.AtomGroup) -> bool: + return len(model.select_atoms("protein").atoms) > 0 + + +def _protein_has_hydrogen(model: MDA.AtomGroup) -> bool: + """Return True if protein residues have hydrogen atoms.""" + return _has_hydrogen(model.select_atoms("protein")) + + +def guess_resolution(universe) -> MolecularResolution: + """Guesses a system resolution (all-atom or coarse-grain).""" + # Only one atom in the system: defaulting to all-atom. + if len(universe.atoms) == 1: + return MolecularResolution(value=ResolutionValue.ALL_ATOM, reason=AllAtomResolutionReason.DEFAULT) + + # Selects the first five residues with at least two atoms. + resindexes = list( + islice((residue.resindex for residue in universe.residues if len(residue.atoms) > 1), 5) + ) + + # If no residue with more than one atom, definitely coarse grain. + no_residue_with_more_than_1_particle = len(resindexes) == 0 + if no_residue_with_more_than_1_particle: + logger.debug("No residues with more than one atom: resolution is coarse grain") + return MolecularResolution.CoarseGrainSingleParticle() + + small_u: MDA.AtomGroup = universe.select_atoms(f"resindex {' '.join(str(i) for i in resindexes)}") + + # If we find any hydrogen atom, it's all-atom. + if _has_hydrogen(small_u): + logger.debug("Found hydrogen atoms: resolution is all-atom") + return MolecularResolution.AllAtomWithHydrogen() + + # If we find any atom named "BB*", it's Martini (coarse grain). + if _is_martini(small_u): + logger.debug("Found residues named BB*: resolution is coarse grain") + return MolecularResolution.CoarseGrainMartini() + + if _has_protein(universe) and not _protein_has_hydrogen(universe): + logger.debug("Found protein with hydrogen: resolution is coarse grain") + return MolecularResolution.CoarseGrainProteinHasNoHydrogen() + + # Last chance: if we find any bond within a given distance, it's all-atom. + # If we reach this point, it means that, for some reason, no hydrogen atom was detected before. + if _has_bonds_within_all_atom_cutoff(small_u): + logger.debug("Found bonds within all-atom distance cutoff: resolution is all-atom") + return MolecularResolution.AllAtomWithStandardBonds() + + # Coarse grain not detected before, no bonds within cutoff distance, it's coarse grain. + logger.debug("No bonds found within all-atom distance cutoff: resolution is coarse grain") + return MolecularResolution.CoarseGrainOther() diff --git a/src/grodecoder/models.py b/src/grodecoder/models.py index 300762a..fcc443c 100644 --- a/src/grodecoder/models.py +++ b/src/grodecoder/models.py @@ -20,11 +20,6 @@ from .settings import Settings -class MolecularResolution(StrEnum): - COARSE_GRAINED = "coarse-grained" - ALL_ATOM = "all-atom" - - class MolecularType(StrEnum): PROTEIN = "protein" NUCLEIC = "nucleic_acid" diff --git a/src/grodecoder/toputils.py b/src/grodecoder/toputils.py index 49df27d..be41229 100644 --- a/src/grodecoder/toputils.py +++ b/src/grodecoder/toputils.py @@ -1,16 +1,12 @@ """Defines utility functions for working with molecular structures.""" import collections -from itertools import islice from typing import Iterable import numpy as np -from loguru import logger from ._typing import Residue, UniverseLike -from .logging import is_logging_debug from .databases import get_amino_acid_name_map, get_nucleotide_name_map -from .models import MolecularResolution def _first_alpha(s: str) -> str: @@ -124,56 +120,3 @@ def end_of_chain(): segments.append((start_current_chain, len(residues) - 1)) return segments - - -def guess_resolution(universe: UniverseLike, cutoff_distance: float) -> MolecularResolution: - """Guesses the resolution (i.e. all-atom or coarse grain) of the universe. - - The resolution is considered coarse-grained if a residue has at least two atoms within a distance of - `cutoff_distance` Å. - - Finds the first five residues with at least two atoms and checks if they have bonds. - If any of them have bonds, the resolution is considered all-atom. - If none of the first five residues have bonds, the resolution is considered coarse-grained. - """ - - def debug(msg): - where = f"{__name__}.guess_resolution" - logger.debug(f"{where}: {msg}") - - def print_bonds(residue): - """Print bonds between atoms inside a residue. Used for debug purposes.""" - - def distance(atom1, atom2): - return (np.linalg.norm(atom1.position - atom2.position) ** 2.0) ** 0.5 - - bonds = get_bonds(residue, cutoff_distance) - for bond in bonds: - left, right = residue.atoms[bond] - bond_str = f"residue {left.resname}:{left.resid}, atoms {left.name}-{right.name}" - debug(f"guess_resolution: Found bond: {bond_str} (distance={distance(left, right):.2f})") - pair_str = f"pair{'s' if len(bonds) > 1 else ''}" - debug(f"guess_resolution: detected {len(bonds)} {pair_str} with distance < {cutoff_distance=:.2f}") - - debug(f"start ; {cutoff_distance=:.2f}") - - # Makes ty happy. - assert (residues := getattr(universe, "residues", [])) and len(residues) > 0 - - # Selects the first five residues with at least two atoms. - residues = list(islice((residue for residue in residues if len(residue.atoms) > 1), 10)) - - for residue in residues: - if has_bonds(residue, cutoff_distance): - if is_logging_debug(): - try: - print_bonds(residue) # will not work during unit test as we use mocks - except Exception: - pass - debug("end: detected resolution: ALL_ATOM") - return MolecularResolution.ALL_ATOM - debug( - f"No intra-atomic distance within {cutoff_distance:.2f} Å found in the first {len(residues)} residues" - ) - debug("end: detected resolution: COARSE_GRAINED") - return MolecularResolution.COARSE_GRAINED diff --git a/tests/test_guesser.py b/tests/test_guesser.py new file mode 100644 index 0000000..5520c50 --- /dev/null +++ b/tests/test_guesser.py @@ -0,0 +1,124 @@ +import numpy as np +from MDAnalysis import Universe + +from grodecoder.guesser import ( + guess_resolution, + ResolutionValue, + AllAtomResolutionReason, + CoarseGrainResolutionReason, +) + + +def create_mock_universe(n_residues: int = 10, n_atoms_per_residue: int = 10) -> Universe: + n_atoms = n_residues * n_atoms_per_residue + + resindices = np.repeat(range(n_residues), n_atoms_per_residue) + assert len(resindices) == n_atoms + + segindices = [0] * n_residues + + universe = Universe.empty( + n_atoms, + n_residues=n_residues, + atom_resindex=resindices, + residue_segindex=segindices, + trajectory=True, # required to add coordinates + ) + + universe.add_TopologyAttr("name", ["C"] * n_atoms) + universe.add_TopologyAttr("type", ["C"] * n_atoms) + universe.add_TopologyAttr("resname", ["UNK"] * n_residues) + universe.add_TopologyAttr("resid", list(range(1, n_residues + 1))) + + return universe + + +class TestGuessResolutionAllAtom: + """Test cases for the guess_resolution function.""" + + def test_defaulting_to_aa_when_only_one_particle_in_the_system(self): + universe = create_mock_universe(n_residues=1, n_atoms_per_residue=1) + result = guess_resolution(universe) + + assert result.value == ResolutionValue.ALL_ATOM + assert result.reason == AllAtomResolutionReason.DEFAULT + + def test_system_has_hydrogen(self): + """Ensures a system is detected all-atom if it contains hydrogen atoms.""" + universe = create_mock_universe() + universe.atoms.types = ["H"] * len(universe.atoms) + result = guess_resolution(universe) + + assert result.value == ResolutionValue.ALL_ATOM + assert result.reason == AllAtomResolutionReason.HAS_HYDROGENS + + def test_has_bonds_within_all_atom_cutoff(self): + """Ensures a system is detected all atoms when atoms are close enough. + + Even if no hydrogen is present in the system, if atoms are within a typical all-atom distance + to each other, the system should be detected all-atom. + """ + universe = create_mock_universe() + n_atoms = len(universe.atoms) + + # Sets distances between atoms to 0.0 + universe.atoms.positions = np.zeros((n_atoms, 3)) + + result = guess_resolution(universe) + assert result.value == ResolutionValue.ALL_ATOM + assert result.reason == AllAtomResolutionReason.RESIDUES_HAVE_BONDS_WITHIN_CUTOFF + + # Sets distances between atoms to 5.0 + coordinates = np.zeros((n_atoms, 3)) + for i in range(n_atoms): + coordinates[i] = np.array((5 * i, 0.0, 0.0)) + universe.atoms.positions = coordinates + + result = guess_resolution(universe) + assert result.value == ResolutionValue.COARSE_GRAIN + assert result.reason == CoarseGrainResolutionReason.HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF + + # def test_guess_resolution_all_atom(self, monkeypatch): + # """Test guess_resolution returns ALL_ATOM when bonds are found.""" + # + # def mock_has_bonds(residue, cutoff): + # return True + # + # monkeypatch.setattr("grodecoder.toputils.has_bonds", mock_has_bonds) + # + # universe = create_mock_universe() + # universe.atoms.types = ["C"] + # + # + # result = guess_resolution(universe) + # # ic(result) + # assert False + + # def test_guess_resolution_coarse_grained(self, monkeypatch): + # def mock_has_bonds(residue, cutoff): + # return False # simulates no bonds found in any residue + # + # monkeypatch.setattr("grodecoder.toputils.has_bonds", mock_has_bonds) + # + # mock_residue = Mock() + # mock_residue.atoms = [Mock(), Mock()] + # + # mock_universe = Mock() + # mock_universe.residues = [mock_residue] * 5 # Ensure we have enough residues + # + # result = guess_resolution(mock_universe, cutoff_distance=12) + # assert result == ResolutionValue.COARSE_GRAINED + # + # def test_guess_resolution_mixed_first_has_bonds(self, monkeypatch): + # """Test guess_resolution when at least one residue has bonds.""" + # mock_has_bonds = Mock(side_effect=[False, False, True, False, False]) + # monkeypatch.setattr("grodecoder.toputils.has_bonds", mock_has_bonds) + # + # mock_residue = Mock() + # mock_residue.atoms = [Mock(), Mock()] + # + # mock_universe = Mock() + # mock_universe.residues = [mock_residue] * 5 + # + # result = guess_resolution(mock_universe, cutoff_distance=12) + # assert result == ResolutionValue.ALL_ATOM diff --git a/tests/test_toputils/test_toputils.py b/tests/test_toputils/test_toputils.py index be36a9a..2f5982e 100644 --- a/tests/test_toputils/test_toputils.py +++ b/tests/test_toputils/test_toputils.py @@ -10,8 +10,6 @@ has_bonds, has_bonds_between, detect_chains, - guess_resolution, - MolecularResolution, ) @@ -227,53 +225,3 @@ def mock_bonds(res1, res2, cutoff): result = detect_chains(mock_universe, cutoff_distance=5.0) # result: first chain is residue 0 and 1, second chain is residue 2 (starts at 2, ends at 2) assert result == [(0, 1), (2, 2)] - - -class TestGuessResolution: - """Test cases for the guess_resolution function.""" - - def test_guess_resolution_all_atom(self, monkeypatch): - """Test guess_resolution returns ALL_ATOM when bonds are found.""" - - def mock_has_bonds(residue, cutoff): - return True - - monkeypatch.setattr("grodecoder.toputils.has_bonds", mock_has_bonds) - - mock_residue = Mock() - mock_residue.atoms = [Mock(), Mock()] - - mock_universe = Mock() - mock_universe.residues = [mock_residue] - - result = guess_resolution(mock_universe, cutoff_distance=12) - assert result == MolecularResolution.ALL_ATOM - - def test_guess_resolution_coarse_grained(self, monkeypatch): - def mock_has_bonds(residue, cutoff): - return False # simulates no bonds found in any residue - - monkeypatch.setattr("grodecoder.toputils.has_bonds", mock_has_bonds) - - mock_residue = Mock() - mock_residue.atoms = [Mock(), Mock()] - - mock_universe = Mock() - mock_universe.residues = [mock_residue] * 5 # Ensure we have enough residues - - result = guess_resolution(mock_universe, cutoff_distance=12) - assert result == MolecularResolution.COARSE_GRAINED - - def test_guess_resolution_mixed_first_has_bonds(self, monkeypatch): - """Test guess_resolution when at least one residue has bonds.""" - mock_has_bonds = Mock(side_effect=[False, False, True, False, False]) - monkeypatch.setattr("grodecoder.toputils.has_bonds", mock_has_bonds) - - mock_residue = Mock() - mock_residue.atoms = [Mock(), Mock()] - - mock_universe = Mock() - mock_universe.residues = [mock_residue] * 5 - - result = guess_resolution(mock_universe, cutoff_distance=12) - assert result == MolecularResolution.ALL_ATOM diff --git a/tests/test_toputils/test_toputils_integration.py b/tests/test_toputils/test_toputils_integration.py index f826081..e8bedcb 100644 --- a/tests/test_toputils/test_toputils_integration.py +++ b/tests/test_toputils/test_toputils_integration.py @@ -11,10 +11,7 @@ has_bonds, has_bonds_between, detect_chains, - guess_resolution, - MolecularResolution, ) -from grodecoder.settings import get_settings TEST_DATA_DIR = Path(__file__).parent.parent / "data" GRO_SMALL = TEST_DATA_DIR / "barstar_water_ions.gro" @@ -85,20 +82,6 @@ def test_detect_chains(self, protein_atoms): assert len(result) == 1 assert result[0] == (0, len(protein_atoms.residues) - 1) - def test_guess_resolution(self, small_universe): - """Test guess_resolution function with real data.""" - result = guess_resolution( - small_universe, cutoff_distance=get_settings().resolution_detection.distance_cutoff - ) - assert result == MolecularResolution.ALL_ATOM - - -def test_guess_resolution_cg(): - """Test guess_resolution with coarse-grained data.""" - universe = mda.Universe(GRO_CG) - result = guess_resolution(universe, cutoff_distance=get_settings().resolution_detection.distance_cutoff) - assert result == MolecularResolution.COARSE_GRAINED - def test_detect_chains_big(): """Test detect_chains with a larger universe.""" From a54a0dc97a72e0cae4abed3c373d8979578b8412 Mon Sep 17 00:00:00 2001 From: benoistlaurent Date: Thu, 12 Feb 2026 10:18:07 +0100 Subject: [PATCH 06/14] upgrade dependencies --- uv.lock | 64 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/uv.lock b/uv.lock index 8775b48..0f31ee2 100644 --- a/uv.lock +++ b/uv.lock @@ -254,44 +254,44 @@ wheels = [ [[package]] name = "filelock" -version = "3.20.0" +version = "3.20.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, + { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, ] [[package]] name = "fonttools" -version = "4.60.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823, upload-time = "2025-09-29T21:13:27.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb", size = 2825777, upload-time = "2025-09-29T21:12:01.22Z" }, - { url = "https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4", size = 2348080, upload-time = "2025-09-29T21:12:03.785Z" }, - { url = "https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c", size = 4903082, upload-time = "2025-09-29T21:12:06.382Z" }, - { url = "https://files.pythonhosted.org/packages/04/05/06b1455e4bc653fcb2117ac3ef5fa3a8a14919b93c60742d04440605d058/fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77", size = 4960125, upload-time = "2025-09-29T21:12:09.314Z" }, - { url = "https://files.pythonhosted.org/packages/8e/37/f3b840fcb2666f6cb97038793606bdd83488dca2d0b0fc542ccc20afa668/fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199", size = 4901454, upload-time = "2025-09-29T21:12:11.931Z" }, - { url = "https://files.pythonhosted.org/packages/fd/9e/eb76f77e82f8d4a46420aadff12cec6237751b0fb9ef1de373186dcffb5f/fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c", size = 5044495, upload-time = "2025-09-29T21:12:15.241Z" }, - { url = "https://files.pythonhosted.org/packages/f8/b3/cede8f8235d42ff7ae891bae8d619d02c8ac9fd0cfc450c5927a6200c70d/fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272", size = 2217028, upload-time = "2025-09-29T21:12:17.96Z" }, - { url = "https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac", size = 2266200, upload-time = "2025-09-29T21:12:20.14Z" }, - { url = "https://files.pythonhosted.org/packages/9a/83/752ca11c1aa9a899b793a130f2e466b79ea0cf7279c8d79c178fc954a07b/fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3", size = 2822830, upload-time = "2025-09-29T21:12:24.406Z" }, - { url = "https://files.pythonhosted.org/packages/57/17/bbeab391100331950a96ce55cfbbff27d781c1b85ebafb4167eae50d9fe3/fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85", size = 2345524, upload-time = "2025-09-29T21:12:26.819Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2e/d4831caa96d85a84dd0da1d9f90d81cec081f551e0ea216df684092c6c97/fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537", size = 4843490, upload-time = "2025-09-29T21:12:29.123Z" }, - { url = "https://files.pythonhosted.org/packages/49/13/5e2ea7c7a101b6fc3941be65307ef8df92cbbfa6ec4804032baf1893b434/fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003", size = 4944184, upload-time = "2025-09-29T21:12:31.414Z" }, - { url = "https://files.pythonhosted.org/packages/0c/2b/cf9603551c525b73fc47c52ee0b82a891579a93d9651ed694e4e2cd08bb8/fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08", size = 4890218, upload-time = "2025-09-29T21:12:33.936Z" }, - { url = "https://files.pythonhosted.org/packages/fd/2f/933d2352422e25f2376aae74f79eaa882a50fb3bfef3c0d4f50501267101/fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99", size = 4999324, upload-time = "2025-09-29T21:12:36.637Z" }, - { url = "https://files.pythonhosted.org/packages/38/99/234594c0391221f66216bc2c886923513b3399a148defaccf81dc3be6560/fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6", size = 2220861, upload-time = "2025-09-29T21:12:39.108Z" }, - { url = "https://files.pythonhosted.org/packages/3e/1d/edb5b23726dde50fc4068e1493e4fc7658eeefcaf75d4c5ffce067d07ae5/fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987", size = 2270934, upload-time = "2025-09-29T21:12:41.339Z" }, - { url = "https://files.pythonhosted.org/packages/fb/da/1392aaa2170adc7071fe7f9cfd181a5684a7afcde605aebddf1fb4d76df5/fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299", size = 2894340, upload-time = "2025-09-29T21:12:43.774Z" }, - { url = "https://files.pythonhosted.org/packages/bf/a7/3b9f16e010d536ce567058b931a20b590d8f3177b2eda09edd92e392375d/fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01", size = 2375073, upload-time = "2025-09-29T21:12:46.437Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b5/e9bcf51980f98e59bb5bb7c382a63c6f6cac0eec5f67de6d8f2322382065/fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801", size = 4849758, upload-time = "2025-09-29T21:12:48.694Z" }, - { url = "https://files.pythonhosted.org/packages/e3/dc/1d2cf7d1cba82264b2f8385db3f5960e3d8ce756b4dc65b700d2c496f7e9/fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc", size = 5085598, upload-time = "2025-09-29T21:12:51.081Z" }, - { url = "https://files.pythonhosted.org/packages/5d/4d/279e28ba87fb20e0c69baf72b60bbf1c4d873af1476806a7b5f2b7fac1ff/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc", size = 4957603, upload-time = "2025-09-29T21:12:53.423Z" }, - { url = "https://files.pythonhosted.org/packages/78/d4/ff19976305e0c05aa3340c805475abb00224c954d3c65e82c0a69633d55d/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed", size = 4974184, upload-time = "2025-09-29T21:12:55.962Z" }, - { url = "https://files.pythonhosted.org/packages/63/22/8553ff6166f5cd21cfaa115aaacaa0dc73b91c079a8cfd54a482cbc0f4f5/fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259", size = 2282241, upload-time = "2025-09-29T21:12:58.179Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cb/fa7b4d148e11d5a72761a22e595344133e83a9507a4c231df972e657579b/fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c", size = 2345760, upload-time = "2025-09-29T21:13:00.375Z" }, - { url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" }, +version = "4.61.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, + { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, + { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, + { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, + { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, + { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, + { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, + { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, ] [[package]] From c77e751530bae85f220cd7d243ec9199bf6ed3dd Mon Sep 17 00:00:00 2001 From: benoistlaurent Date: Thu, 12 Feb 2026 11:27:33 +0100 Subject: [PATCH 07/14] unit tests for guess resolution coarse grain --- tests/test_guesser.py | 118 ++++++++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 50 deletions(-) diff --git a/tests/test_guesser.py b/tests/test_guesser.py index 5520c50..9fceda3 100644 --- a/tests/test_guesser.py +++ b/tests/test_guesser.py @@ -9,7 +9,7 @@ ) -def create_mock_universe(n_residues: int = 10, n_atoms_per_residue: int = 10) -> Universe: +def create_mock_universe(n_residues: int, n_atoms_per_residue: int) -> Universe: n_atoms = n_residues * n_atoms_per_residue resindices = np.repeat(range(n_residues), n_atoms_per_residue) @@ -33,11 +33,22 @@ def create_mock_universe(n_residues: int = 10, n_atoms_per_residue: int = 10) -> return universe +def create_mock_all_atom(n_residues: int = 10, n_atoms_per_residue: int = 10) -> Universe: + return create_mock_universe(n_residues, n_atoms_per_residue) + + +def create_mock_coarse_grain(n_residues: int = 10, n_atoms_per_residue: int = 3) -> Universe: + return create_mock_universe(n_residues, n_atoms_per_residue) + + class TestGuessResolutionAllAtom: - """Test cases for the guess_resolution function.""" + """Test cases for the guess_resolution function. + + All test cases in this ensures that all-atom resolution is detected. + """ def test_defaulting_to_aa_when_only_one_particle_in_the_system(self): - universe = create_mock_universe(n_residues=1, n_atoms_per_residue=1) + universe = create_mock_all_atom(n_residues=1, n_atoms_per_residue=1) result = guess_resolution(universe) assert result.value == ResolutionValue.ALL_ATOM @@ -45,7 +56,7 @@ def test_defaulting_to_aa_when_only_one_particle_in_the_system(self): def test_system_has_hydrogen(self): """Ensures a system is detected all-atom if it contains hydrogen atoms.""" - universe = create_mock_universe() + universe = create_mock_all_atom() universe.atoms.types = ["H"] * len(universe.atoms) result = guess_resolution(universe) @@ -58,7 +69,7 @@ def test_has_bonds_within_all_atom_cutoff(self): Even if no hydrogen is present in the system, if atoms are within a typical all-atom distance to each other, the system should be detected all-atom. """ - universe = create_mock_universe() + universe = create_mock_all_atom() n_atoms = len(universe.atoms) # Sets distances between atoms to 0.0 @@ -68,7 +79,7 @@ def test_has_bonds_within_all_atom_cutoff(self): assert result.value == ResolutionValue.ALL_ATOM assert result.reason == AllAtomResolutionReason.RESIDUES_HAVE_BONDS_WITHIN_CUTOFF - # Sets distances between atoms to 5.0 + # Sets distances between atoms to 5.0 (distance > all-atom cutoff) coordinates = np.zeros((n_atoms, 3)) for i in range(n_atoms): coordinates[i] = np.array((5 * i, 0.0, 0.0)) @@ -78,47 +89,54 @@ def test_has_bonds_within_all_atom_cutoff(self): assert result.value == ResolutionValue.COARSE_GRAIN assert result.reason == CoarseGrainResolutionReason.HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF - # def test_guess_resolution_all_atom(self, monkeypatch): - # """Test guess_resolution returns ALL_ATOM when bonds are found.""" - # - # def mock_has_bonds(residue, cutoff): - # return True - # - # monkeypatch.setattr("grodecoder.toputils.has_bonds", mock_has_bonds) - # - # universe = create_mock_universe() - # universe.atoms.types = ["C"] - # - # - # result = guess_resolution(universe) - # # ic(result) - # assert False - - # def test_guess_resolution_coarse_grained(self, monkeypatch): - # def mock_has_bonds(residue, cutoff): - # return False # simulates no bonds found in any residue - # - # monkeypatch.setattr("grodecoder.toputils.has_bonds", mock_has_bonds) - # - # mock_residue = Mock() - # mock_residue.atoms = [Mock(), Mock()] - # - # mock_universe = Mock() - # mock_universe.residues = [mock_residue] * 5 # Ensure we have enough residues - # - # result = guess_resolution(mock_universe, cutoff_distance=12) - # assert result == ResolutionValue.COARSE_GRAINED - # - # def test_guess_resolution_mixed_first_has_bonds(self, monkeypatch): - # """Test guess_resolution when at least one residue has bonds.""" - # mock_has_bonds = Mock(side_effect=[False, False, True, False, False]) - # monkeypatch.setattr("grodecoder.toputils.has_bonds", mock_has_bonds) - # - # mock_residue = Mock() - # mock_residue.atoms = [Mock(), Mock()] - # - # mock_universe = Mock() - # mock_universe.residues = [mock_residue] * 5 - # - # result = guess_resolution(mock_universe, cutoff_distance=12) - # assert result == ResolutionValue.ALL_ATOM + +class TestGuessResolutionCoarseGrain: + """Test cases for the guess_resolution function. + + All test cases in this ensures that coarse-grain resolution is detected. + """ + + def test_model_with_one_grain_per_residue(self): + """Ensures models with a single grain per residue are detected as coarse-grain.""" + universe = create_mock_coarse_grain(n_atoms_per_residue=1) + result = guess_resolution(universe) + + assert result.value == ResolutionValue.COARSE_GRAIN + assert result.reason == CoarseGrainResolutionReason.HAS_ONE_GRAIN_PER_RESIDUE + + def test_model_is_martini(self): + """Ensures Martini models are detected as coarse-grain. + + Martini systems are expected to contain grains named BB*. + """ + universe = create_mock_coarse_grain() + universe.atoms[0].name = "BB1" # a single grain named BB* is supposed to be enough + result = guess_resolution(universe) + + assert result.value == ResolutionValue.COARSE_GRAIN + assert result.reason == CoarseGrainResolutionReason.HAS_BB_ATOMS + + def test_protein_has_no_hydrogen(self): + """Ensures models where protein is detected but no hydrogen atoms are found are detected as coarse-grain.""" + universe = create_mock_coarse_grain() + universe.residues.resnames = np.repeat("ALA", len(universe.residues)) + result = guess_resolution(universe) + + assert result.value == ResolutionValue.COARSE_GRAIN + assert result.reason == CoarseGrainResolutionReason.PROTEIN_HAS_NO_HYDROGEN + + def test_no_bond_within_all_atom_distance(self): + """Ensures models where no bond are found within typical all-atom distance are detected as coarse-grain.""" + universe = create_mock_coarse_grain() + + # Sets distances between atoms to 5.0 (distance > all-atom cutoff) + n_atoms = len(universe.atoms) + coordinates = np.zeros((len(universe.atoms), 3)) + for i in range(n_atoms): + coordinates[i] = np.array((5 * i, 0.0, 0.0)) + universe.atoms.positions = coordinates + + result = guess_resolution(universe) + + assert result.value == ResolutionValue.COARSE_GRAIN + assert result.reason == CoarseGrainResolutionReason.HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF From c5b1d5532a700b3635caf37203a2f78bb642d31d Mon Sep 17 00:00:00 2001 From: benoistlaurent Date: Thu, 12 Feb 2026 11:38:08 +0100 Subject: [PATCH 08/14] new MolecularResolution class: update regression test dataset --- src/grodecoder/models.py | 1 + .../expected_results/1BRS.json | 45 +++++++++-------- .../expected_results/1QJ8.json | 39 ++++++++------- .../1QJ8_ETH_ACN_MET_URE_SOL.json | 19 ++++--- .../expected_results/1QJ8_membrane.json | 39 ++++++++------- .../expected_results/1QJ8_solution.json | 17 ++++--- .../expected_results/2MAT.json | 17 ++++--- .../expected_results/4MQJ_ABCD.json | 27 +++++----- .../expected_results/4ZRY.json | 49 ++++++++++--------- .../expected_results/5MBA.json | 45 +++++++++-------- .../expected_results/5ZOA.json | 39 ++++++++------- .../expected_results/DMPC_PI.json | 19 ++++--- .../expected_results/DNA_start.json | 15 +++--- .../expected_results/RNA_start.json | 15 +++--- .../expected_results/barstar.json | 13 +++-- .../expected_results/noriega_AA_CRD_3CAL.json | 17 ++++--- .../expected_results/noriega_CG_CRD_3CAL.json | 17 ++++--- 17 files changed, 241 insertions(+), 192 deletions(-) diff --git a/src/grodecoder/models.py b/src/grodecoder/models.py index fcc443c..f65de5c 100644 --- a/src/grodecoder/models.py +++ b/src/grodecoder/models.py @@ -1,4 +1,5 @@ from __future__ import annotations +from .guesser import MolecularResolution from enum import StrEnum from typing import Protocol diff --git a/tests/data/regression_data/expected_results/1BRS.json b/tests/data/regression_data/expected_results/1BRS.json index 60dca3c..051c03d 100644 --- a/tests/data/regression_data/expected_results/1BRS.json +++ b/tests/data/regression_data/expected_results/1BRS.json @@ -145,8 +145,8 @@ 10540, 10541 ], - "description": "calcium ion", "molecular_type": "ion", + "description": "calcium ion", "number_of_atoms": 141, "number_of_residues": 141, "name": "CAL" @@ -1000,8 +1000,8 @@ 11527, 11528 ], - "description": "chloride ion", "molecular_type": "ion", + "description": "chloride ion", "number_of_atoms": 846, "number_of_residues": 846, "name": "CLA" @@ -1150,8 +1150,8 @@ 10681, 10682 ], - "description": "magnesium ion", "molecular_type": "ion", + "description": "magnesium ion", "number_of_atoms": 141, "number_of_residues": 141, "name": "MG" @@ -1723,8 +1723,8 @@ 10104, 10105 ], - "description": "hydroxide ion", "molecular_type": "ion", + "description": "hydroxide ion", "number_of_atoms": 564, "number_of_residues": 282, "name": "OH" @@ -1886,8 +1886,8 @@ 10258, 10259 ], - "description": "potassium ion", "molecular_type": "ion", + "description": "potassium ion", "number_of_atoms": 154, "number_of_residues": 154, "name": "POT" @@ -2036,8 +2036,8 @@ 10399, 10400 ], - "description": "sodium ion", "molecular_type": "ion", + "description": "sodium ion", "number_of_atoms": 141, "number_of_residues": 141, "name": "SOD" @@ -2186,8 +2186,8 @@ 9540, 9541 ], - "description": "zinc ion", "molecular_type": "ion", + "description": "zinc ion", "number_of_atoms": 141, "number_of_residues": 141, "name": "ZN2" @@ -137576,8 +137576,8 @@ 146908, 146909 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 135381, "number_of_residues": 45127, "name": "TIP3" @@ -139287,10 +139287,10 @@ 1698, 1699 ], - "sequence": "VINTFDGVADYLQTYHKLPDNYITKSEAQALGWVASKGNLADVAPGKSIGGDIFSNREGKLPGKSGRTWREADINYTSGFRNSDRILYSSDWLIYKTTDHYQTFTKIR", "molecular_type": "protein", "number_of_atoms": 1700, - "number_of_residues": 108 + "number_of_residues": 108, + "sequence": "VINTFDGVADYLQTYHKLPDNYITKSEAQALGWVASKGNLADVAPGKSIGGDIFSNREGKLPGKSGRTWREADINYTSGFRNSDRILYSSDWLIYKTTDHYQTFTKIR" }, { "atoms": [ @@ -141022,10 +141022,10 @@ 3425, 3426 ], - "sequence": "AQVINTFDGVADYLQTYHKLPDNYITKSEAQALGWVASKGNLADVAPGKSIGGDIFSNREGKLPGKSGRTWREADINYTSGFRNSDRILYSSDWLIYKTTDHYQTFTKIR", "molecular_type": "protein", "number_of_atoms": 1727, - "number_of_residues": 110 + "number_of_residues": 110, + "sequence": "AQVINTFDGVADYLQTYHKLPDNYITKSEAQALGWVASKGNLADVAPGKSIGGDIFSNREGKLPGKSGRTWREADINYTSGFRNSDRILYSSDWLIYKTTDHYQTFTKIR" }, { "atoms": [ @@ -142730,10 +142730,10 @@ 5125, 5126 ], - "sequence": "VINTFDGVADYLQTYHKLPDNYITKSEAQALGWVASKGNLADVAPGKSIGGDIFSNREGKLPGKSGRTWREADINYTSGFRNSDRILYSSDWLIYKTTDHYQTFTKIR", "molecular_type": "protein", "number_of_atoms": 1700, - "number_of_residues": 108 + "number_of_residues": 108, + "sequence": "VINTFDGVADYLQTYHKLPDNYITKSEAQALGWVASKGNLADVAPGKSIGGDIFSNREGKLPGKSGRTWREADINYTSGFRNSDRILYSSDWLIYKTTDHYQTFTKIR" }, { "atoms": [ @@ -144170,10 +144170,10 @@ 6557, 6558 ], - "sequence": "KKAVINGEQIRSISDLHQTLKKELALPEYYGENLDALWDALTGWVEYPLVLEWRQFEQSKQLTENGAESVLQVFREAKAEGADITIILS", "molecular_type": "protein", "number_of_atoms": 1432, - "number_of_residues": 89 + "number_of_residues": 89, + "sequence": "KKAVINGEQIRSISDLHQTLKKELALPEYYGENLDALWDALTGWVEYPLVLEWRQFEQSKQLTENGAESVLQVFREAKAEGADITIILS" }, { "atoms": [ @@ -145588,10 +145588,10 @@ 7967, 7968 ], - "sequence": "KAVINGEQIRSISDLHQTLKKELALPEYYGENLDALWDALTGWVEYPLVLEWRQFEQSKQLTENGAESVLQVFREAKAEGADITIILS", "molecular_type": "protein", "number_of_atoms": 1410, - "number_of_residues": 88 + "number_of_residues": 88, + "sequence": "KAVINGEQIRSISDLHQTLKKELALPEYYGENLDALWDALTGWVEYPLVLEWRQFEQSKQLTENGAESVLQVFREAKAEGADITIILS" }, { "atoms": [ @@ -147028,13 +147028,16 @@ 9399, 9400 ], - "sequence": "KKAVINGEQIRSISDLHQTLKKELALPEYYGENLDALWDALTGWVEYPLVLEWRQFEQSKQLTENGAESVLQVFREAKAEGADITIILS", "molecular_type": "protein", "number_of_atoms": 1432, - "number_of_residues": 89 + "number_of_residues": 89, + "sequence": "KKAVINGEQIRSISDLHQTLKKELALPEYYGENLDALWDALTGWVEYPLVLEWRQFEQSKQLTENGAESVLQVFREAKAEGADITIILS" } ], "total_number_of_atoms": 146910 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "has_hydrogens" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/1QJ8.json b/tests/data/regression_data/expected_results/1QJ8.json index 9bb63dd..629a8ac 100644 --- a/tests/data/regression_data/expected_results/1QJ8.json +++ b/tests/data/regression_data/expected_results/1QJ8.json @@ -64,8 +64,8 @@ 38910, 38911 ], - "description": "calcium ion", "molecular_type": "ion", + "description": "calcium ion", "number_of_atoms": 60, "number_of_residues": 60, "name": "CAL" @@ -433,8 +433,8 @@ 39330, 39331 ], - "description": "chloride ion", "molecular_type": "ion", + "description": "chloride ion", "number_of_atoms": 360, "number_of_residues": 360, "name": "CLA" @@ -502,8 +502,8 @@ 38970, 38971 ], - "description": "magnesium ion", "molecular_type": "ion", + "description": "magnesium ion", "number_of_atoms": 60, "number_of_residues": 60, "name": "MG" @@ -825,8 +825,8 @@ 38790, 38791 ], - "description": "potassium ion", "molecular_type": "ion", + "description": "potassium ion", "number_of_atoms": 314, "number_of_residues": 314, "name": "POT" @@ -894,8 +894,8 @@ 38850, 38851 ], - "description": "sodium ion", "molecular_type": "ion", + "description": "sodium ion", "number_of_atoms": 60, "number_of_residues": 60, "name": "SOD" @@ -66768,8 +66768,8 @@ 105195, 105196 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 65865, "number_of_residues": 21955, "name": "TIP3" @@ -71097,8 +71097,8 @@ 33688, 33689 ], - "description": "2,3-DIOLEOYL-D-GLYCERO-1-PHOSPHATIDIC ACID", "molecular_type": "lipid", + "description": "2,3-DIOLEOYL-D-GLYCERO-1-PHOSPHATIDIC ACID", "number_of_atoms": 4320, "number_of_residues": 36, "name": "DOPA" @@ -75390,8 +75390,8 @@ 16480, 16481 ], - "description": "DI-PALMITOLEIC-PHOSPHATIDYLGLYCEROL DIC161", "molecular_type": "lipid", + "description": "DI-PALMITOLEIC-PHOSPHATIDYLGLYCEROL DIC161", "number_of_atoms": 4284, "number_of_residues": 36, "name": "DYPG" @@ -79971,8 +79971,8 @@ 29368, 29369 ], - "description": "3-PALMITOYL-2-OLEOYL-D-GLYCERO-1-PHOSPHATIDYLSERINE", "molecular_type": "lipid", + "description": "3-PALMITOYL-2-OLEOYL-D-GLYCERO-1-PHOSPHATIDYLSERINE", "number_of_atoms": 4572, "number_of_residues": 36, "name": "POPS" @@ -83904,8 +83904,8 @@ 8812, 8813 ], - "description": "2,3 DIMYRISTOYL-D-GLYCERO-1-PHOSPHATIDYLETHANOLAMINE", "molecular_type": "lipid", + "description": "2,3 DIMYRISTOYL-D-GLYCERO-1-PHOSPHATIDYLETHANOLAMINE", "number_of_atoms": 3924, "number_of_residues": 36, "name": "DMPE" @@ -87909,8 +87909,8 @@ 24796, 24797 ], - "description": "2,3 DIMYRISTOYL-D-GLYCERO-1-PHOSPHATIDYLSERINE", "molecular_type": "lipid", + "description": "2,3 DIMYRISTOYL-D-GLYCERO-1-PHOSPHATIDYLSERINE", "number_of_atoms": 3996, "number_of_residues": 36, "name": "DMPS" @@ -90582,8 +90582,8 @@ 4888, 4889 ], - "description": "CHOLESTEROL NAME TO AVOID CONFLICT WITH CHOLINE", "molecular_type": "lipid", + "description": "CHOLESTEROL NAME TO AVOID CONFLICT WITH CHOLINE", "number_of_atoms": 2664, "number_of_residues": 36, "name": "CHL1" @@ -93975,8 +93975,8 @@ 12196, 12197 ], - "description": "1,2 DIDECANOYL-D-GLYCERO-3-PHOSPHATIDYLCHOLINE", "molecular_type": "lipid", + "description": "1,2 DIDECANOYL-D-GLYCERO-3-PHOSPHATIDYLCHOLINE", "number_of_atoms": 3384, "number_of_residues": 36, "name": "DDPC" @@ -98304,8 +98304,8 @@ 20800, 20801 ], - "description": "2,3-DIOLEOYL-D-GLYCERO-1-PYROPHOSPHATE", "molecular_type": "lipid", + "description": "2,3-DIOLEOYL-D-GLYCERO-1-PYROPHOSPHATE", "number_of_atoms": 4320, "number_of_residues": 36, "name": "POPP2" @@ -103101,8 +103101,8 @@ 38476, 38477 ], - "description": "1-STEAROYL-2-OLEOYL-PHOSPHATIDYLGLYCEROL", "molecular_type": "lipid", + "description": "1-STEAROYL-2-OLEOYL-PHOSPHATIDYLGLYCEROL", "number_of_atoms": 4788, "number_of_residues": 36, "name": "SOPG" @@ -105338,13 +105338,16 @@ 2224, 2225 ], - "sequence": "ATSTVTGGYAQSDAQGQMNKMGGFNLKYRYEEDNSPLGVIGSFTYTEKSRTASSGDYNKNQYYGITAGPAYRINDWASIYGVVGVGYGKFQTTEYPTYKNDTSDYGFSYGAGLQFNPMENVALDFSYEQSRIRSVDVGTWIAGVGYRF", "molecular_type": "protein", "number_of_atoms": 2226, - "number_of_residues": 148 + "number_of_residues": 148, + "sequence": "ATSTVTGGYAQSDAQGQMNKMGGFNLKYRYEEDNSPLGVIGSFTYTEKSRTASSGDYNKNQYYGITAGPAYRINDWASIYGVVGVGYGKFQTTEYPTYKNDTSDYGFSYGAGLQFNPMENVALDFSYEQSRIRSVDVGTWIAGVGYRF" } ], "total_number_of_atoms": 105197 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "has_hydrogens" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/1QJ8_ETH_ACN_MET_URE_SOL.json b/tests/data/regression_data/expected_results/1QJ8_ETH_ACN_MET_URE_SOL.json index 2a52265..4a24ab1 100644 --- a/tests/data/regression_data/expected_results/1QJ8_ETH_ACN_MET_URE_SOL.json +++ b/tests/data/regression_data/expected_results/1QJ8_ETH_ACN_MET_URE_SOL.json @@ -604,8 +604,8 @@ 6956, 6957 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 600, "number_of_residues": 200, "name": "SOL" @@ -2213,8 +2213,8 @@ 2756, 2757 ], - "description": "urea solvant", "molecular_type": "solvent", + "description": "urea solvant", "number_of_atoms": 1600, "number_of_residues": 200, "name": "URE" @@ -3422,8 +3422,8 @@ 3956, 3957 ], - "description": "organic solvant methanol/OPLS", "molecular_type": "solvent", + "description": "organic solvant methanol/OPLS", "number_of_atoms": 1200, "number_of_residues": 200, "name": "MET" @@ -5231,8 +5231,8 @@ 6356, 6357 ], - "description": "organic solvant ethanol/OPLS", "molecular_type": "solvent", + "description": "organic solvant ethanol/OPLS", "number_of_atoms": 1800, "number_of_residues": 200, "name": "ETH" @@ -5840,8 +5840,8 @@ 4556, 4557 ], - "description": "acetonitrile", "molecular_type": "solvent", + "description": "acetonitrile", "number_of_atoms": 600, "number_of_residues": 200, "name": "ACN" @@ -7009,13 +7009,16 @@ 1156, 1157 ], - "sequence": "ATSTVTGGYAQSDAQGQMNKMGGFNLKYRYEEDNSPLGVIGSFTYTEKSRTASSGDYNKNQYYGITAGPAYRINDWASIYGVVGVGYGKFQTTEYPTYKNDTSDYGFSYGAGLQFNPMENVALDFSYEQSRIRSVDVGTWIAGVGYRF", "molecular_type": "protein", "number_of_atoms": 1158, - "number_of_residues": 148 + "number_of_residues": 148, + "sequence": "ATSTVTGGYAQSDAQGQMNKMGGFNLKYRYEEDNSPLGVIGSFTYTEKSRTASSGDYNKNQYYGITAGPAYRINDWASIYGVVGVGYGKFQTTEYPTYKNDTSDYGFSYGAGLQFNPMENVALDFSYEQSRIRSVDVGTWIAGVGYRF" } ], "total_number_of_atoms": 6958 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "residues_have_bonds_within_cutoff" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/1QJ8_membrane.json b/tests/data/regression_data/expected_results/1QJ8_membrane.json index 9bb63dd..629a8ac 100644 --- a/tests/data/regression_data/expected_results/1QJ8_membrane.json +++ b/tests/data/regression_data/expected_results/1QJ8_membrane.json @@ -64,8 +64,8 @@ 38910, 38911 ], - "description": "calcium ion", "molecular_type": "ion", + "description": "calcium ion", "number_of_atoms": 60, "number_of_residues": 60, "name": "CAL" @@ -433,8 +433,8 @@ 39330, 39331 ], - "description": "chloride ion", "molecular_type": "ion", + "description": "chloride ion", "number_of_atoms": 360, "number_of_residues": 360, "name": "CLA" @@ -502,8 +502,8 @@ 38970, 38971 ], - "description": "magnesium ion", "molecular_type": "ion", + "description": "magnesium ion", "number_of_atoms": 60, "number_of_residues": 60, "name": "MG" @@ -825,8 +825,8 @@ 38790, 38791 ], - "description": "potassium ion", "molecular_type": "ion", + "description": "potassium ion", "number_of_atoms": 314, "number_of_residues": 314, "name": "POT" @@ -894,8 +894,8 @@ 38850, 38851 ], - "description": "sodium ion", "molecular_type": "ion", + "description": "sodium ion", "number_of_atoms": 60, "number_of_residues": 60, "name": "SOD" @@ -66768,8 +66768,8 @@ 105195, 105196 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 65865, "number_of_residues": 21955, "name": "TIP3" @@ -71097,8 +71097,8 @@ 33688, 33689 ], - "description": "2,3-DIOLEOYL-D-GLYCERO-1-PHOSPHATIDIC ACID", "molecular_type": "lipid", + "description": "2,3-DIOLEOYL-D-GLYCERO-1-PHOSPHATIDIC ACID", "number_of_atoms": 4320, "number_of_residues": 36, "name": "DOPA" @@ -75390,8 +75390,8 @@ 16480, 16481 ], - "description": "DI-PALMITOLEIC-PHOSPHATIDYLGLYCEROL DIC161", "molecular_type": "lipid", + "description": "DI-PALMITOLEIC-PHOSPHATIDYLGLYCEROL DIC161", "number_of_atoms": 4284, "number_of_residues": 36, "name": "DYPG" @@ -79971,8 +79971,8 @@ 29368, 29369 ], - "description": "3-PALMITOYL-2-OLEOYL-D-GLYCERO-1-PHOSPHATIDYLSERINE", "molecular_type": "lipid", + "description": "3-PALMITOYL-2-OLEOYL-D-GLYCERO-1-PHOSPHATIDYLSERINE", "number_of_atoms": 4572, "number_of_residues": 36, "name": "POPS" @@ -83904,8 +83904,8 @@ 8812, 8813 ], - "description": "2,3 DIMYRISTOYL-D-GLYCERO-1-PHOSPHATIDYLETHANOLAMINE", "molecular_type": "lipid", + "description": "2,3 DIMYRISTOYL-D-GLYCERO-1-PHOSPHATIDYLETHANOLAMINE", "number_of_atoms": 3924, "number_of_residues": 36, "name": "DMPE" @@ -87909,8 +87909,8 @@ 24796, 24797 ], - "description": "2,3 DIMYRISTOYL-D-GLYCERO-1-PHOSPHATIDYLSERINE", "molecular_type": "lipid", + "description": "2,3 DIMYRISTOYL-D-GLYCERO-1-PHOSPHATIDYLSERINE", "number_of_atoms": 3996, "number_of_residues": 36, "name": "DMPS" @@ -90582,8 +90582,8 @@ 4888, 4889 ], - "description": "CHOLESTEROL NAME TO AVOID CONFLICT WITH CHOLINE", "molecular_type": "lipid", + "description": "CHOLESTEROL NAME TO AVOID CONFLICT WITH CHOLINE", "number_of_atoms": 2664, "number_of_residues": 36, "name": "CHL1" @@ -93975,8 +93975,8 @@ 12196, 12197 ], - "description": "1,2 DIDECANOYL-D-GLYCERO-3-PHOSPHATIDYLCHOLINE", "molecular_type": "lipid", + "description": "1,2 DIDECANOYL-D-GLYCERO-3-PHOSPHATIDYLCHOLINE", "number_of_atoms": 3384, "number_of_residues": 36, "name": "DDPC" @@ -98304,8 +98304,8 @@ 20800, 20801 ], - "description": "2,3-DIOLEOYL-D-GLYCERO-1-PYROPHOSPHATE", "molecular_type": "lipid", + "description": "2,3-DIOLEOYL-D-GLYCERO-1-PYROPHOSPHATE", "number_of_atoms": 4320, "number_of_residues": 36, "name": "POPP2" @@ -103101,8 +103101,8 @@ 38476, 38477 ], - "description": "1-STEAROYL-2-OLEOYL-PHOSPHATIDYLGLYCEROL", "molecular_type": "lipid", + "description": "1-STEAROYL-2-OLEOYL-PHOSPHATIDYLGLYCEROL", "number_of_atoms": 4788, "number_of_residues": 36, "name": "SOPG" @@ -105338,13 +105338,16 @@ 2224, 2225 ], - "sequence": "ATSTVTGGYAQSDAQGQMNKMGGFNLKYRYEEDNSPLGVIGSFTYTEKSRTASSGDYNKNQYYGITAGPAYRINDWASIYGVVGVGYGKFQTTEYPTYKNDTSDYGFSYGAGLQFNPMENVALDFSYEQSRIRSVDVGTWIAGVGYRF", "molecular_type": "protein", "number_of_atoms": 2226, - "number_of_residues": 148 + "number_of_residues": 148, + "sequence": "ATSTVTGGYAQSDAQGQMNKMGGFNLKYRYEEDNSPLGVIGSFTYTEKSRTASSGDYNKNQYYGITAGPAYRINDWASIYGVVGVGYGKFQTTEYPTYKNDTSDYGFSYGAGLQFNPMENVALDFSYEQSRIRSVDVGTWIAGVGYRF" } ], "total_number_of_atoms": 105197 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "has_hydrogens" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/1QJ8_solution.json b/tests/data/regression_data/expected_results/1QJ8_solution.json index 58699fd..e39a0c0 100644 --- a/tests/data/regression_data/expected_results/1QJ8_solution.json +++ b/tests/data/regression_data/expected_results/1QJ8_solution.json @@ -25,8 +25,8 @@ 14068, 14069 ], - "description": "chloride ion", "molecular_type": "ion", + "description": "chloride ion", "number_of_atoms": 21, "number_of_residues": 21, "name": "CLA" @@ -57,8 +57,8 @@ 14047, 14048 ], - "description": "sodium ion", "molecular_type": "ion", + "description": "sodium ion", "number_of_atoms": 23, "number_of_residues": 23, "name": "SOD" @@ -24306,8 +24306,8 @@ 38308, 38309 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 24240, "number_of_residues": 8080, "name": "TIP3" @@ -36115,8 +36115,8 @@ 14024, 14025 ], - "description": "2,3 DIMYRISTOYL-D-GLYCERO-1-PHOSPHATIDYLCHOLINE", "molecular_type": "lipid", + "description": "2,3 DIMYRISTOYL-D-GLYCERO-1-PHOSPHATIDYLCHOLINE", "number_of_atoms": 11800, "number_of_residues": 100, "name": "DMPC" @@ -38352,13 +38352,16 @@ 2224, 2225 ], - "sequence": "ATSTVTGGYAQSDAQGQMNKMGGFNLKYRYEEDNSPLGVIGSFTYTEKSRTASSGDYNKNQYYGITAGPAYRINDWASIYGVVGVGYGKFQTTEYPTYKNDTSDYGFSYGAGLQFNPMENVALDFSYEQSRIRSVDVGTWIAGVGYRF", "molecular_type": "protein", "number_of_atoms": 2226, - "number_of_residues": 148 + "number_of_residues": 148, + "sequence": "ATSTVTGGYAQSDAQGQMNKMGGFNLKYRYEEDNSPLGVIGSFTYTEKSRTASSGDYNKNQYYGITAGPAYRINDWASIYGVVGVGYGKFQTTEYPTYKNDTSDYGFSYGAGLQFNPMENVALDFSYEQSRIRSVDVGTWIAGVGYRF" } ], "total_number_of_atoms": 38310 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "has_hydrogens" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/2MAT.json b/tests/data/regression_data/expected_results/2MAT.json index f5107c7..7f3a5c0 100644 --- a/tests/data/regression_data/expected_results/2MAT.json +++ b/tests/data/regression_data/expected_results/2MAT.json @@ -604,8 +604,8 @@ 7189, 7190 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 600, "number_of_residues": 200, "name": "SOL" @@ -2213,8 +2213,8 @@ 3589, 3590 ], - "description": "urea solvant", "molecular_type": "solvent", + "description": "urea solvant", "number_of_atoms": 1600, "number_of_residues": 200, "name": "URE" @@ -3422,8 +3422,8 @@ 4789, 4790 ], - "description": "organic solvant methanol/OPLS", "molecular_type": "solvent", + "description": "organic solvant methanol/OPLS", "number_of_atoms": 1200, "number_of_residues": 200, "name": "MET" @@ -5231,8 +5231,8 @@ 6589, 6590 ], - "description": "organic solvant ethanol/OPLS", "molecular_type": "solvent", + "description": "organic solvant ethanol/OPLS", "number_of_atoms": 1800, "number_of_residues": 200, "name": "ETH" @@ -7233,13 +7233,16 @@ 1989, 1990 ], - "sequence": "AISIKTPEDIEKMRVAGRLAAEVLEMIEPYVKPGVSTGELDRICNDYIVNEQHAVSACLGYHGYPKSVCISINEVVCHGIPDDAKLLKDGDIVNIDVTVIKDGFHGDTSKMFIVGKPTIMGERLCRITQESLYLALRMVKPGINLREIGAAIQKFVEAEGFSVVREYCGHGIGQGFHEEPQVLHYDSRETNVVLKPGMTFTIEPMVNAGKKEIRTMKDGWTVKTKDRSLSAQYEHTIVVTDNGCEILTLRKDDTIPAIISHD", "molecular_type": "protein", "number_of_atoms": 1991, - "number_of_residues": 262 + "number_of_residues": 262, + "sequence": "AISIKTPEDIEKMRVAGRLAAEVLEMIEPYVKPGVSTGELDRICNDYIVNEQHAVSACLGYHGYPKSVCISINEVVCHGIPDDAKLLKDGDIVNIDVTVIKDGFHGDTSKMFIVGKPTIMGERLCRITQESLYLALRMVKPGINLREIGAAIQKFVEAEGFSVVREYCGHGIGQGFHEEPQVLHYDSRETNVVLKPGMTFTIEPMVNAGKKEIRTMKDGWTVKTKDRSLSAQYEHTIVVTDNGCEILTLRKDDTIPAIISHD" } ], "total_number_of_atoms": 7191 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "residues_have_bonds_within_cutoff" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/4MQJ_ABCD.json b/tests/data/regression_data/expected_results/4MQJ_ABCD.json index 134dabb..83828c2 100644 --- a/tests/data/regression_data/expected_results/4MQJ_ABCD.json +++ b/tests/data/regression_data/expected_results/4MQJ_ABCD.json @@ -63,8 +63,8 @@ 8928, 8929 ], - "description": "chloride ion", "molecular_type": "ion", + "description": "chloride ion", "number_of_atoms": 59, "number_of_residues": 59, "name": "CLA" @@ -127,8 +127,8 @@ 8869, 8870 ], - "description": "potassium ion", "molecular_type": "ion", + "description": "potassium ion", "number_of_atoms": 55, "number_of_residues": 55, "name": "POT" @@ -59152,8 +59152,8 @@ 67944, 67945 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 59016, "number_of_residues": 19672, "name": "TIP3" @@ -61306,10 +61306,10 @@ 2141, 2142 ], - "sequence": "VLSPADKTNVKAAWGKVGAHAGEYGAEALERMFLSFPTTKTYFPHFDLSHGSAQVKGHGKKVADALTNAVAHVDDMPNALSALSDLHAHKLRVDPVNFKLLSHCLLVTLAAHLPAEFTPAVHASLDKFLASVSTVLTSKYR", "molecular_type": "protein", "number_of_atoms": 2143, - "number_of_residues": 141 + "number_of_residues": 141, + "sequence": "VLSPADKTNVKAAWGKVGAHAGEYGAEALERMFLSFPTTKTYFPHFDLSHGSAQVKGHGKKVADALTNAVAHVDDMPNALSALSDLHAHKLRVDPVNFKLLSHCLLVTLAAHLPAEFTPAVHASLDKFLASVSTVLTSKYR" }, { "atoms": [ @@ -63580,10 +63580,10 @@ 4407, 4408 ], - "sequence": "VHFTEEDKATITSLWGKVNVEDAGGETLGRLLVVYPWTQRFFDSFGNLSSASAIMGNPKVKAHGKKVLTSLGDAIKHLDDLKGTFAQLSELHCDKLHVDPENFKLLGNVLVTVLAIHFGKEFTPEVQASWQKMVTGVASALSSRYH", "molecular_type": "protein", "number_of_atoms": 2266, - "number_of_residues": 146 + "number_of_residues": 146, + "sequence": "VHFTEEDKATITSLWGKVNVEDAGGETLGRLLVVYPWTQRFFDSFGNLSSASAIMGNPKVKAHGKKVLTSLGDAIKHLDDLKGTFAQLSELHCDKLHVDPENFKLLGNVLVTVLAIHFGKEFTPEVQASWQKMVTGVASALSSRYH" }, { "atoms": [ @@ -65730,10 +65730,10 @@ 6549, 6550 ], - "sequence": "VLSPADKTNVKAAWGKVGAHAGEYGAEALERMFLSFPTTKTYFPHFDLSHGSAQVKGHGKKVADALTNAVAHVDDMPNALSALSDLHAHKLRVDPVNFKLLSHCLLVTLAAHLPAEFTPAVHASLDKFLASVSTVLTSKYR", "molecular_type": "protein", "number_of_atoms": 2142, - "number_of_residues": 141 + "number_of_residues": 141, + "sequence": "VLSPADKTNVKAAWGKVGAHAGEYGAEALERMFLSFPTTKTYFPHFDLSHGSAQVKGHGKKVADALTNAVAHVDDMPNALSALSDLHAHKLRVDPVNFKLLSHCLLVTLAAHLPAEFTPAVHASLDKFLASVSTVLTSKYR" }, { "atoms": [ @@ -68003,13 +68003,16 @@ 8814, 8815 ], - "sequence": "VHFTEEDKATITSLWGKVNVEDAGGETLGRLLVVYPWTQRFFDSFGNLSSASAIMGNPKVKAHGKKVLTSLGDAIKHLDDLKGTFAQLSELHCDKLHVDPENFKLLGNVLVTVLAIHFGKEFTPEVQASWQKMVTGVASALSSRYH", "molecular_type": "protein", "number_of_atoms": 2265, - "number_of_residues": 146 + "number_of_residues": 146, + "sequence": "VHFTEEDKATITSLWGKVNVEDAGGETLGRLLVVYPWTQRFFDSFGNLSSASAIMGNPKVKAHGKKVLTSLGDAIKHLDDLKGTFAQLSELHCDKLHVDPENFKLLGNVLVTVLAIHFGKEFTPEVQASWQKMVTGVASALSSRYH" } ], "total_number_of_atoms": 67946 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "has_hydrogens" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/4ZRY.json b/tests/data/regression_data/expected_results/4ZRY.json index 926dedb..8648c1b 100644 --- a/tests/data/regression_data/expected_results/4ZRY.json +++ b/tests/data/regression_data/expected_results/4ZRY.json @@ -145,8 +145,8 @@ 45359, 45360 ], - "description": "calcium ion", "molecular_type": "ion", + "description": "calcium ion", "number_of_atoms": 141, "number_of_residues": 141, "name": "CAL" @@ -1000,8 +1000,8 @@ 46346, 46347 ], - "description": "chloride ion", "molecular_type": "ion", + "description": "chloride ion", "number_of_atoms": 846, "number_of_residues": 846, "name": "CLA" @@ -1150,8 +1150,8 @@ 45500, 45501 ], - "description": "magnesium ion", "molecular_type": "ion", + "description": "magnesium ion", "number_of_atoms": 141, "number_of_residues": 141, "name": "MG" @@ -1382,8 +1382,8 @@ 45077, 45078 ], - "description": "potassium ion", "molecular_type": "ion", + "description": "potassium ion", "number_of_atoms": 223, "number_of_residues": 223, "name": "POT" @@ -1532,8 +1532,8 @@ 45218, 45219 ], - "description": "sodium ion", "molecular_type": "ion", + "description": "sodium ion", "number_of_atoms": 141, "number_of_residues": 141, "name": "SOD" @@ -155849,8 +155849,8 @@ 200654, 200655 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 154308, "number_of_residues": 51436, "name": "TIP3" @@ -159292,8 +159292,8 @@ 6910, 6911 ], - "description": "SITOSTERYL GLUCOSIDE", "molecular_type": "lipid", + "description": "SITOSTERYL GLUCOSIDE", "number_of_atoms": 3434, "number_of_residues": 34, "name": "GSITO" @@ -163755,8 +163755,8 @@ 40502, 40503 ], - "description": "181 BIS MONOOLEOYLGLYCEROL PHOSPHATE", "molecular_type": "lipid", + "description": "181 BIS MONOOLEOYLGLYCEROL PHOSPHATE", "number_of_atoms": 4454, "number_of_residues": 34, "name": "BMGP" @@ -169034,8 +169034,8 @@ 19932, 19933 ], - "description": "", "molecular_type": "lipid", + "description": "", "number_of_atoms": 5270, "number_of_residues": 34, "name": "DEPS" @@ -173769,8 +173769,8 @@ 33838, 33839 ], - "description": "PALMITOYL-ERUCIC-PHOSPHATIDYLSERINE PS160_221", "molecular_type": "lipid", + "description": "PALMITOYL-ERUCIC-PHOSPHATIDYLSERINE PS160_221", "number_of_atoms": 4726, "number_of_residues": 34, "name": "PEPS" @@ -175988,8 +175988,8 @@ 36048, 36049 ], - "description": "MONOLEIN-1", "molecular_type": "lipid", + "description": "MONOLEIN-1", "number_of_atoms": 2210, "number_of_residues": 34, "name": "MLN1" @@ -180451,8 +180451,8 @@ 29112, 29113 ], - "description": "2,3-DIOLEOYL-D-GLYCERO-1-PHOSPHATIDYLETHANOLAMINE", "molecular_type": "lipid", + "description": "2,3-DIOLEOYL-D-GLYCERO-1-PHOSPHATIDYLETHANOLAMINE", "number_of_atoms": 4454, "number_of_residues": 34, "name": "DOPEE" @@ -185186,8 +185186,8 @@ 24658, 24659 ], - "description": "PLASMALOGEN PHOSPHATIDYLCHOLINE 18/204", "molecular_type": "lipid", + "description": "PLASMALOGEN PHOSPHATIDYLCHOLINE 18/204", "number_of_atoms": 4726, "number_of_residues": 34, "name": "PLC20" @@ -187031,8 +187031,8 @@ 12384, 12385 ], - "description": "DECYL-ALPHA-D-GLUCOSIDE", "molecular_type": "lipid", + "description": "DECYL-ALPHA-D-GLUCOSIDE", "number_of_atoms": 1836, "number_of_residues": 34, "name": "ADG" @@ -188468,8 +188468,8 @@ 8338, 8339 ], - "description": "N-DECYL-N,N-DIMETHYLAMINE-N-OXIDE PROTONATED", "molecular_type": "unknown", + "description": "N-DECYL-N,N-DIMETHYLAMINE-N-OXIDE PROTONATED", "number_of_atoms": 1428, "number_of_residues": 34, "name": "DDAOP" @@ -190755,8 +190755,8 @@ 14662, 14663 ], - "description": "N-TRIDECYLPHOSPHOCHOLINE, TPC", "molecular_type": "unknown", + "description": "N-TRIDECYLPHOSPHOCHOLINE, TPC", "number_of_atoms": 2278, "number_of_residues": 34, "name": "FOS14" @@ -195116,8 +195116,8 @@ 44854, 44855 ], - "description": "Unknown small molecule", "molecular_type": "unknown", + "description": "Unknown small molecule", "number_of_atoms": 4352, "number_of_residues": 34, "name": "PLESE" @@ -197335,8 +197335,8 @@ 10548, 10549 ], - "description": "Unknown small molecule", "molecular_type": "unknown", + "description": "Unknown small molecule", "number_of_atoms": 2210, "number_of_residues": 34, "name": "SB3-1" @@ -199127,10 +199127,10 @@ 1779, 1780 ], - "sequence": "LTTEIDNNIEQISSYKSEITELRRNVQALEIELQSQLALKQSLEASLAETEGRYCVQLSQIQAQISALEEQLQQIRAETECQNTEYQQLLDIKIRLENEIQTYRSLLEGE", "molecular_type": "protein", "number_of_atoms": 1781, - "number_of_residues": 110 + "number_of_residues": 110, + "sequence": "LTTEIDNNIEQISSYKSEITELRRNVQALEIELQSQLALKQSLEASLAETEGRYCVQLSQIQAQISALEEQLQQIRAETECQNTEYQQLLDIKIRLENEIQTYRSLLEGE" }, { "atoms": [ @@ -200832,13 +200832,16 @@ 3476, 3477 ], - "sequence": "RHGDSVRNSKIEISELNRVIQRLRSEIDNVKKQISNLQQSISDAEQRGENALKDAKNKLNDLEDALQQAKEDLARLLRDYQELMNTKLALDLEIATYRTLLEGE", "molecular_type": "protein", "number_of_atoms": 1697, - "number_of_residues": 104 + "number_of_residues": 104, + "sequence": "RHGDSVRNSKIEISELNRVIQRLRSEIDNVKKQISNLQQSISDAEQRGENALKDAKNKLNDLEDALQQAKEDLARLLRDYQELMNTKLALDLEIATYRTLLEGE" } ], "total_number_of_atoms": 200656 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "has_hydrogens" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/5MBA.json b/tests/data/regression_data/expected_results/5MBA.json index 0d1c230..e66016d 100644 --- a/tests/data/regression_data/expected_results/5MBA.json +++ b/tests/data/regression_data/expected_results/5MBA.json @@ -214,8 +214,8 @@ 39038, 39039 ], - "description": "chloride ion", "molecular_type": "ion", + "description": "chloride ion", "number_of_atoms": 210, "number_of_residues": 210, "name": "CLA" @@ -265,8 +265,8 @@ 38828, 38829 ], - "description": "magnesium ion", "molecular_type": "ion", + "description": "magnesium ion", "number_of_atoms": 42, "number_of_residues": 42, "name": "MG" @@ -484,8 +484,8 @@ 38583, 38584 ], - "description": "ammonium", "molecular_type": "ion", + "description": "ammonium", "number_of_atoms": 210, "number_of_residues": 42, "name": "NH4" @@ -654,8 +654,8 @@ 38744, 38745 ], - "description": "potassium ion", "molecular_type": "ion", + "description": "potassium ion", "number_of_atoms": 161, "number_of_residues": 161, "name": "POT" @@ -705,8 +705,8 @@ 38786, 38787 ], - "description": "sodium ion", "molecular_type": "ion", + "description": "sodium ion", "number_of_atoms": 42, "number_of_residues": 42, "name": "SOD" @@ -47538,8 +47538,8 @@ 85862, 85863 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 46824, "number_of_residues": 15608, "name": "TIP3" @@ -50211,8 +50211,8 @@ 7197, 7198 ], - "description": "2,3 DIMYRISTOYL-D-GLYCERO-1-PHOSPHATIDYLSERINE", "molecular_type": "lipid", + "description": "2,3 DIMYRISTOYL-D-GLYCERO-1-PHOSPHATIDYLSERINE", "number_of_atoms": 2664, "number_of_residues": 24, "name": "DMPS" @@ -52884,8 +52884,8 @@ 30021, 30022 ], - "description": "CERAMIDE T162/181", "molecular_type": "lipid", + "description": "CERAMIDE T162/181", "number_of_atoms": 2664, "number_of_residues": 24, "name": "CER3E" @@ -56565,8 +56565,8 @@ 33693, 33694 ], - "description": "SPHINGOMYELIN D180/240", "molecular_type": "lipid", + "description": "SPHINGOMYELIN D180/240", "number_of_atoms": 3672, "number_of_residues": 24, "name": "DSM" @@ -60150,8 +60150,8 @@ 18765, 18766 ], - "description": "PHOSPHOGLYCERO-ARCHAEOL PHOSPHOGLYCERO-DI-O-PHYTANYLGLYCEROL", "molecular_type": "lipid", + "description": "PHOSPHOGLYCERO-ARCHAEOL PHOSPHOGLYCERO-DI-O-PHYTANYLGLYCEROL", "number_of_atoms": 3576, "number_of_residues": 24, "name": "PGAR" @@ -61383,8 +61383,8 @@ 11181, 11182 ], - "description": "UBIQUINOL-2", "molecular_type": "lipid", + "description": "UBIQUINOL-2", "number_of_atoms": 1224, "number_of_residues": 24, "name": "UQOL2" @@ -64176,8 +64176,8 @@ 21549, 21550 ], - "description": "UBIQUINOL-7", "molecular_type": "lipid", + "description": "UBIQUINOL-7", "number_of_atoms": 2784, "number_of_residues": 24, "name": "UQOL7" @@ -66561,8 +66561,8 @@ 4533, 4534 ], - "description": "STIGMASTERYL GLUCOSIDE", "molecular_type": "lipid", + "description": "STIGMASTERYL GLUCOSIDE", "number_of_atoms": 2376, "number_of_residues": 24, "name": "GSTIG" @@ -68826,8 +68826,8 @@ 38373, 38374 ], - "description": "Unknown small molecule", "molecular_type": "unknown", + "description": "Unknown small molecule", "number_of_atoms": 2256, "number_of_residues": 24, "name": "CPS04" @@ -71595,8 +71595,8 @@ 9957, 9958 ], - "description": "Unknown small molecule", "molecular_type": "unknown", + "description": "Unknown small molecule", "number_of_atoms": 2760, "number_of_residues": 24, "name": "CPU12" @@ -74028,8 +74028,8 @@ 36117, 36118 ], - "description": "Unknown small molecule", "molecular_type": "unknown", + "description": "Unknown small molecule", "number_of_atoms": 2424, "number_of_residues": 24, "name": "ESU08" @@ -79845,8 +79845,8 @@ 27357, 27358 ], - "description": "Unknown small molecule", "molecular_type": "unknown", + "description": "Unknown small molecule", "number_of_atoms": 5808, "number_of_residues": 24, "name": "LOACL" @@ -83862,8 +83862,8 @@ 15189, 15190 ], - "description": "Unknown small molecule", "molecular_type": "unknown", + "description": "Unknown small molecule", "number_of_atoms": 4008, "number_of_residues": 24, "name": "PHPGP" @@ -86032,13 +86032,16 @@ 2157, 2158 ], - "sequence": "SLSAAEADLAGKSWAPVFANKNANGLDFLVALFEKFPDSANFFADFKGKSVADIKASPKLRDVSSRIFTRLNEFVNNAANAGKMSAMLSQFAKEHVGFGVGSAQFENVRSMFPGFVASVAAPPAGADAAWTKLFGLIIDALKAAGA", "molecular_type": "protein", "number_of_atoms": 2159, - "number_of_residues": 146 + "number_of_residues": 146, + "sequence": "SLSAAEADLAGKSWAPVFANKNANGLDFLVALFEKFPDSANFFADFKGKSVADIKASPKLRDVSSRIFTRLNEFVNNAANAGKMSAMLSQFAKEHVGFGVGSAQFENVRSMFPGFVASVAAPPAGADAAWTKLFGLIIDALKAAGA" } ], "total_number_of_atoms": 85864 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "has_hydrogens" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/5ZOA.json b/tests/data/regression_data/expected_results/5ZOA.json index c78df9c..84e4a42 100644 --- a/tests/data/regression_data/expected_results/5ZOA.json +++ b/tests/data/regression_data/expected_results/5ZOA.json @@ -53,8 +53,8 @@ 40826, 40827 ], - "description": "calcium ion", "molecular_type": "ion", + "description": "calcium ion", "number_of_atoms": 49, "number_of_residues": 49, "name": "CAL" @@ -258,8 +258,8 @@ 41022, 41023 ], - "description": "chloride ion", "molecular_type": "ion", + "description": "chloride ion", "number_of_atoms": 196, "number_of_residues": 196, "name": "CLA" @@ -623,8 +623,8 @@ 40728, 40729 ], - "description": "potassium ion", "molecular_type": "ion", + "description": "potassium ion", "number_of_atoms": 356, "number_of_residues": 356, "name": "POT" @@ -681,8 +681,8 @@ 40777, 40778 ], - "description": "sodium ion", "molecular_type": "ion", + "description": "sodium ion", "number_of_atoms": 49, "number_of_residues": 49, "name": "SOD" @@ -54507,8 +54507,8 @@ 94839, 94840 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 53817, "number_of_residues": 17939, "name": "TIP3" @@ -59169,8 +59169,8 @@ 21292, 21293 ], - "description": "2,3 DILAUROYL-D-GLYCERO-1-PHOSPHATIDYLGLYCEROL", "molecular_type": "lipid", + "description": "2,3 DILAUROYL-D-GLYCERO-1-PHOSPHATIDYLGLYCEROL", "number_of_atoms": 4653, "number_of_residues": 47, "name": "DLPG" @@ -63434,8 +63434,8 @@ 16639, 16640 ], - "description": "2,3 DIPALMITOYL-D-GLYCERO-1-PHOSPHATIDYLCHOLINE", "molecular_type": "lipid", + "description": "2,3 DIPALMITOYL-D-GLYCERO-1-PHOSPHATIDYLCHOLINE", "number_of_atoms": 4256, "number_of_residues": 32, "name": "DPPI" @@ -67507,8 +67507,8 @@ 25356, 25357 ], - "description": "PG-1819Z/160", "molecular_type": "lipid", + "description": "PG-1819Z/160", "number_of_atoms": 4064, "number_of_residues": 32, "name": "OPPG" @@ -75196,8 +75196,8 @@ 33036, 33037 ], - "description": "TETRALINOLEOYL CARDIOLIPIN WITH HEAD GROUP CHARGE = -2", "molecular_type": "lipid", + "description": "TETRALINOLEOYL CARDIOLIPIN WITH HEAD GROUP CHARGE = -2", "number_of_atoms": 7680, "number_of_residues": 32, "name": "TLCL2" @@ -78683,8 +78683,8 @@ 7408, 7409 ], - "description": "CHOLESTEROL NAME TO AVOID CONFLICT WITH CHOLINE", "molecular_type": "lipid", + "description": "CHOLESTEROL NAME TO AVOID CONFLICT WITH CHOLINE", "number_of_atoms": 3478, "number_of_residues": 47, "name": "CHL1" @@ -81548,8 +81548,8 @@ 40372, 40373 ], - "description": "CHOLESTERYL HEMISUCCINATE DEPROTONATED Y01", "molecular_type": "lipid", + "description": "CHOLESTERYL HEMISUCCINATE DEPROTONATED Y01", "number_of_atoms": 2856, "number_of_residues": 34, "name": "CHSD" @@ -84117,8 +84117,8 @@ 11213, 11214 ], - "description": "BETA-SITOSTEROL", "molecular_type": "lipid", + "description": "BETA-SITOSTEROL", "number_of_atoms": 2560, "number_of_residues": 32, "name": "SITO" @@ -85296,8 +85296,8 @@ 12383, 12384 ], - "description": "STIGMASTEROL", "molecular_type": "lipid", + "description": "STIGMASTEROL", "number_of_atoms": 1170, "number_of_residues": 15, "name": "STIG" @@ -86550,8 +86550,8 @@ 8653, 8654 ], - "description": "Unknown small molecule", "molecular_type": "unknown", + "description": "Unknown small molecule", "number_of_atoms": 1245, "number_of_residues": 15, "name": "DPOP" @@ -91039,8 +91039,8 @@ 37516, 37517 ], - "description": "Unknown small molecule", "molecular_type": "unknown", + "description": "Unknown small molecule", "number_of_atoms": 4480, "number_of_residues": 32, "name": "POPI1" @@ -94982,13 +94982,16 @@ 3930, 3931 ], - "sequence": "ANPYERGPNPTDALLEASSGPFSVSEENVSRLSASGFGGGTIYYPRENNTYGAVAISPGYTGTEASIAWLGERIASHGFVVITIDTITTLDQPDSRAEQLNAALNHMINRASSTVRSRIDSSRLAVMGHSMGGGGTLRLASQRPDLKAAIPLTPWHLNKNWSSVTVPTLIIGADLDTIAPVATHAKPFYNSLPSSISKAYLELDGATHFAPNIPNKIIGKYSVAWLKRFVDNDTRYTQFLCPGPRDGLFGEVEEYRSTCPF", "molecular_type": "protein", "number_of_atoms": 3932, - "number_of_residues": 261 + "number_of_residues": 261, + "sequence": "ANPYERGPNPTDALLEASSGPFSVSEENVSRLSASGFGGGTIYYPRENNTYGAVAISPGYTGTEASIAWLGERIASHGFVVITIDTITTLDQPDSRAEQLNAALNHMINRASSTVRSRIDSSRLAVMGHSMGGGGTLRLASQRPDLKAAIPLTPWHLNKNWSSVTVPTLIIGADLDTIAPVATHAKPFYNSLPSSISKAYLELDGATHFAPNIPNKIIGKYSVAWLKRFVDNDTRYTQFLCPGPRDGLFGEVEEYRSTCPF" } ], "total_number_of_atoms": 94841 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "has_hydrogens" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/DMPC_PI.json b/tests/data/regression_data/expected_results/DMPC_PI.json index 164e0d7..fff7010 100644 --- a/tests/data/regression_data/expected_results/DMPC_PI.json +++ b/tests/data/regression_data/expected_results/DMPC_PI.json @@ -32,8 +32,8 @@ 32114, 32115 ], - "description": "chloride ion", "molecular_type": "ion", + "description": "chloride ion", "number_of_atoms": 28, "number_of_residues": 28, "name": "CLA" @@ -145,8 +145,8 @@ 32086, 32087 ], - "description": "sodium ion", "molecular_type": "ion", + "description": "sodium ion", "number_of_atoms": 104, "number_of_residues": 104, "name": "SOD" @@ -38554,8 +38554,8 @@ 70514, 70515 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 38400, "number_of_residues": 12800, "name": "TIP3" @@ -40755,8 +40755,8 @@ 23430, 23431 ], - "description": "1-PALMITOYL-2-OLEOYL-INOSITOL", "molecular_type": "lipid", + "description": "1-PALMITOYL-2-OLEOYL-INOSITOL", "number_of_atoms": 2192, "number_of_residues": 16, "name": "POPI" @@ -62004,8 +62004,8 @@ 21238, 21239 ], - "description": "2,3 DIMYRISTOYL-D-GLYCERO-1-PHOSPHATIDYLCHOLINE", "molecular_type": "lipid", + "description": "2,3 DIMYRISTOYL-D-GLYCERO-1-PHOSPHATIDYLCHOLINE", "number_of_atoms": 21240, "number_of_residues": 180, "name": "DMPC" @@ -68591,8 +68591,8 @@ 30008, 30009 ], - "description": "PHOSPHATIDYLINOSITOL", "molecular_type": "lipid", + "description": "PHOSPHATIDYLINOSITOL", "number_of_atoms": 6578, "number_of_residues": 46, "name": "SAPI" @@ -70574,8 +70574,8 @@ 31982, 31983 ], - "description": "1-STEAROYL-2-LINOLEOYL-PHOSPHATIDYLCHOLINE", "molecular_type": "lipid", + "description": "1-STEAROYL-2-LINOLEOYL-PHOSPHATIDYLCHOLINE", "number_of_atoms": 1974, "number_of_residues": 14, "name": "SLPI" @@ -70584,5 +70584,8 @@ "segments": [], "total_number_of_atoms": 70516 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "has_hydrogens" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/DNA_start.json b/tests/data/regression_data/expected_results/DNA_start.json index 41a783b..6194127 100644 --- a/tests/data/regression_data/expected_results/DNA_start.json +++ b/tests/data/regression_data/expected_results/DNA_start.json @@ -92,8 +92,8 @@ 96389, 96390 ], - "description": "chloride ion", "molecular_type": "ion", + "description": "chloride ion", "number_of_atoms": 88, "number_of_residues": 88, "name": "CL" @@ -223,8 +223,8 @@ 96301, 96302 ], - "description": "potassium ion", "molecular_type": "ion", + "description": "potassium ion", "number_of_atoms": 122, "number_of_residues": 122, "name": "K" @@ -95281,8 +95281,8 @@ 96179, 96180 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 95049, "number_of_residues": 31683, "name": "SOL" @@ -96424,13 +96424,16 @@ 1130, 1131 ], - "sequence": "GCGGGGGGGGGGGGGGGCGCCCCCCCCCCCCCCCGC", "molecular_type": "nucleic_acid", "number_of_atoms": 1132, - "number_of_residues": 36 + "number_of_residues": 36, + "sequence": "GCGGGGGGGGGGGGGGGCGCCCCCCCCCCCCCCCGC" } ], "total_number_of_atoms": 96391 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "has_hydrogens" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/RNA_start.json b/tests/data/regression_data/expected_results/RNA_start.json index 93f25ef..f7232f8 100644 --- a/tests/data/regression_data/expected_results/RNA_start.json +++ b/tests/data/regression_data/expected_results/RNA_start.json @@ -80,8 +80,8 @@ 80255, 80256 ], - "description": "chloride ion", "molecular_type": "ion", + "description": "chloride ion", "number_of_atoms": 76, "number_of_residues": 76, "name": "CL" @@ -199,8 +199,8 @@ 80179, 80180 ], - "description": "potassium ion", "molecular_type": "ion", + "description": "potassium ion", "number_of_atoms": 110, "number_of_residues": 110, "name": "K" @@ -79111,8 +79111,8 @@ 80069, 80070 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 78903, "number_of_residues": 26301, "name": "SOL" @@ -80290,13 +80290,16 @@ 1166, 1167 ], - "sequence": "GCGGGGGGGGGGGGGGGCGCCCCCCCCCCCCCCCGC", "molecular_type": "nucleic_acid", "number_of_atoms": 1168, - "number_of_residues": 36 + "number_of_residues": 36, + "sequence": "GCGGGGGGGGGGGGGGGCGCCCCCCCCCCCCCCCGC" } ], "total_number_of_atoms": 80257 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "has_hydrogens" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/barstar.json b/tests/data/regression_data/expected_results/barstar.json index 3d472a2..51de048 100644 --- a/tests/data/regression_data/expected_results/barstar.json +++ b/tests/data/regression_data/expected_results/barstar.json @@ -10,8 +10,8 @@ 18697, 18698 ], - "description": "sodium ion", "molecular_type": "ion", + "description": "sodium ion", "number_of_atoms": 6, "number_of_residues": 6, "name": "NA+" @@ -17278,8 +17278,8 @@ 18691, 18692 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 17259, "number_of_residues": 5753, "name": "SOL" @@ -18723,13 +18723,16 @@ 1432, 1433 ], - "sequence": "KKAVINGEQIRSISDLHQTLKKELALPEYYGENLDALWDCLTGWVEYPLVLEWRQFEQSKQLTENGAESVLQVFREAKAEGCDITIILS", "molecular_type": "protein", "number_of_atoms": 1434, - "number_of_residues": 89 + "number_of_residues": 89, + "sequence": "KKAVINGEQIRSISDLHQTLKKELALPEYYGENLDALWDCLTGWVEYPLVLEWRQFEQSKQLTENGAESVLQVFREAKAEGCDITIILS" } ], "total_number_of_atoms": 18699 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "has_hydrogens" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/noriega_AA_CRD_3CAL.json b/tests/data/regression_data/expected_results/noriega_AA_CRD_3CAL.json index d103290..8e77ebc 100644 --- a/tests/data/regression_data/expected_results/noriega_AA_CRD_3CAL.json +++ b/tests/data/regression_data/expected_results/noriega_AA_CRD_3CAL.json @@ -7,8 +7,8 @@ 2069, 2070 ], - "description": "calcium ion", "molecular_type": "ion", + "description": "calcium ion", "number_of_atoms": 3, "number_of_residues": 3, "name": "CAL" @@ -35,8 +35,8 @@ 2088, 2089 ], - "description": "chloride ion", "molecular_type": "ion", + "description": "chloride ion", "number_of_atoms": 19, "number_of_residues": 19, "name": "CLA" @@ -63,8 +63,8 @@ 2066, 2067 ], - "description": "sodium ion", "molecular_type": "ion", + "description": "sodium ion", "number_of_atoms": 19, "number_of_residues": 19, "name": "SOD" @@ -21279,8 +21279,8 @@ 23295, 23296 ], - "description": "water TIP3P solvant", "molecular_type": "solvent", + "description": "water TIP3P solvant", "number_of_atoms": 21207, "number_of_residues": 7069, "name": "TIP3" @@ -23339,13 +23339,16 @@ 2047, 2048 ], - "sequence": "CCPLNWEYFQSSCYFFSTDTKSWALSLKNCSAMGAHLVVINSQEEQEFLSYKKPKMREFFIGLSDQVVEGQWQWVDGTPLTKSLSFWDVGEPNNIATLEDCATMRDSSNPRQNWNDVTCFLNYFRICEMV", "molecular_type": "protein", "number_of_atoms": 2049, - "number_of_residues": 130 + "number_of_residues": 130, + "sequence": "CCPLNWEYFQSSCYFFSTDTKSWALSLKNCSAMGAHLVVINSQEEQEFLSYKKPKMREFFIGLSDQVVEGQWQWVDGTPLTKSLSFWDVGEPNNIATLEDCATMRDSSNPRQNWNDVTCFLNYFRICEMV" } ], "total_number_of_atoms": 23297 }, - "resolution": "all-atom" + "resolution": { + "value": "all_atom", + "reason": "has_hydrogens" + } } \ No newline at end of file diff --git a/tests/data/regression_data/expected_results/noriega_CG_CRD_3CAL.json b/tests/data/regression_data/expected_results/noriega_CG_CRD_3CAL.json index dac28ea..c0a3272 100644 --- a/tests/data/regression_data/expected_results/noriega_CG_CRD_3CAL.json +++ b/tests/data/regression_data/expected_results/noriega_CG_CRD_3CAL.json @@ -39,8 +39,8 @@ 2120, 2121 ], - "description": "calcium ion - CG model with MARTINI", "molecular_type": "ion", + "description": "calcium ion - CG model with MARTINI", "number_of_atoms": 35, "number_of_residues": 35, "name": "ION" @@ -1818,8 +1818,8 @@ 2085, 2086 ], - "description": "water W - in CG model with MARTINI", "molecular_type": "solvent", + "description": "water W - in CG model with MARTINI", "number_of_atoms": 1770, "number_of_residues": 1770, "name": "W" @@ -1920,10 +1920,10 @@ 89, 90 ], - "sequence": "CCPLNWEYFQSSCYFFSTDTKSWALSLKNCSAMGA", "molecular_type": "protein", "number_of_atoms": 91, - "number_of_residues": 35 + "number_of_residues": 35, + "sequence": "CCPLNWEYFQSSCYFFSTDTKSWALSLKNCSAMGA" }, { "atoms": [ @@ -2154,13 +2154,16 @@ 315, 316 ], - "sequence": "LVVINSQEEQEFLSYKKPKMREFFIGLSDQVVEGQWQWVDGTPLTKSLSFWDVGEPNNIATLEDCATMRDSSNPRQNWNDVTCFLNYFRICEMV", "molecular_type": "protein", "number_of_atoms": 226, - "number_of_residues": 94 + "number_of_residues": 94, + "sequence": "LVVINSQEEQEFLSYKKPKMREFFIGLSDQVVEGQWQWVDGTPLTKSLSFWDVGEPNNIATLEDCATMRDSSNPRQNWNDVTCFLNYFRICEMV" } ], "total_number_of_atoms": 2122 }, - "resolution": "coarse-grained" + "resolution": { + "value": "coarse_grain", + "reason": "has_bb_atoms" + } } \ No newline at end of file From fc5168f15886934677fece9e7eae4f9faecd85a3 Mon Sep 17 00:00:00 2001 From: benoistlaurent Date: Thu, 12 Feb 2026 11:56:33 +0100 Subject: [PATCH 09/14] guess_resolution: takes all atom cutoff distance as argument --- src/grodecoder/core.py | 2 +- src/grodecoder/guesser.py | 9 ++++----- tests/test_guesser.py | 10 +++++----- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/grodecoder/core.py b/src/grodecoder/core.py index 12745c0..7106e98 100644 --- a/src/grodecoder/core.py +++ b/src/grodecoder/core.py @@ -20,7 +20,7 @@ def decode(universe: UniverseLike) -> Decoded: settings = get_settings() - resolution = guess_resolution(universe) + resolution = guess_resolution(universe, settings.resolution_detection.distance_cutoff) logger.info(f"Guessed resolution: {resolution}") # Guesses the chain dection distance cutoff if not provided by the user. diff --git a/src/grodecoder/guesser.py b/src/grodecoder/guesser.py index d992745..ecf6f77 100644 --- a/src/grodecoder/guesser.py +++ b/src/grodecoder/guesser.py @@ -107,10 +107,9 @@ def _is_martini(model: MDA.AtomGroup) -> bool: return bool(np.any(np.char.startswith(model.atoms.names.astype("U"), "BB"))) -def _has_bonds_within_all_atom_cutoff(model: MDA.AtomGroup) -> bool: - cutoff_distance = 1.6 +def _has_bonds_within_all_atom_cutoff(model: MDA.AtomGroup, distance_cutoff: float) -> bool: for residue in model.residues: - if has_bonds(residue, cutoff_distance): + if has_bonds(residue, distance_cutoff): return True return False @@ -124,7 +123,7 @@ def _protein_has_hydrogen(model: MDA.AtomGroup) -> bool: return _has_hydrogen(model.select_atoms("protein")) -def guess_resolution(universe) -> MolecularResolution: +def guess_resolution(universe, all_atom_cutoff_distance: float = 1.6) -> MolecularResolution: """Guesses a system resolution (all-atom or coarse-grain).""" # Only one atom in the system: defaulting to all-atom. if len(universe.atoms) == 1: @@ -159,7 +158,7 @@ def guess_resolution(universe) -> MolecularResolution: # Last chance: if we find any bond within a given distance, it's all-atom. # If we reach this point, it means that, for some reason, no hydrogen atom was detected before. - if _has_bonds_within_all_atom_cutoff(small_u): + if _has_bonds_within_all_atom_cutoff(small_u, all_atom_cutoff_distance): logger.debug("Found bonds within all-atom distance cutoff: resolution is all-atom") return MolecularResolution.AllAtomWithStandardBonds() diff --git a/tests/test_guesser.py b/tests/test_guesser.py index 9fceda3..e980016 100644 --- a/tests/test_guesser.py +++ b/tests/test_guesser.py @@ -75,17 +75,17 @@ def test_has_bonds_within_all_atom_cutoff(self): # Sets distances between atoms to 0.0 universe.atoms.positions = np.zeros((n_atoms, 3)) - result = guess_resolution(universe) + result = guess_resolution(universe, all_atom_cutoff_distance=2) assert result.value == ResolutionValue.ALL_ATOM assert result.reason == AllAtomResolutionReason.RESIDUES_HAVE_BONDS_WITHIN_CUTOFF - # Sets distances between atoms to 5.0 (distance > all-atom cutoff) + # Sets distances between atoms to 5.0 coordinates = np.zeros((n_atoms, 3)) for i in range(n_atoms): coordinates[i] = np.array((5 * i, 0.0, 0.0)) universe.atoms.positions = coordinates - result = guess_resolution(universe) + result = guess_resolution(universe, all_atom_distance_cutoff=2) assert result.value == ResolutionValue.COARSE_GRAIN assert result.reason == CoarseGrainResolutionReason.HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF @@ -129,14 +129,14 @@ def test_no_bond_within_all_atom_distance(self): """Ensures models where no bond are found within typical all-atom distance are detected as coarse-grain.""" universe = create_mock_coarse_grain() - # Sets distances between atoms to 5.0 (distance > all-atom cutoff) + # Sets distances between atoms to 5.0 n_atoms = len(universe.atoms) coordinates = np.zeros((len(universe.atoms), 3)) for i in range(n_atoms): coordinates[i] = np.array((5 * i, 0.0, 0.0)) universe.atoms.positions = coordinates - result = guess_resolution(universe) + result = guess_resolution(universe, all_atom_distance_cutoff=2) assert result.value == ResolutionValue.COARSE_GRAIN assert result.reason == CoarseGrainResolutionReason.HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF From f16fa68dbeb39dce0589f2def637fe8acf5b54b4 Mon Sep 17 00:00:00 2001 From: benoistlaurent Date: Thu, 12 Feb 2026 13:15:49 +0100 Subject: [PATCH 10/14] minor fix: spelling issue: distance_cutoff -> cutoff_distance --- src/grodecoder/core.py | 12 ++++---- src/grodecoder/guesser.py | 4 +-- src/grodecoder/main.py | 2 +- src/grodecoder/settings.py | 60 +++++++++++++++++++------------------- tests/test_guesser.py | 4 +-- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/grodecoder/core.py b/src/grodecoder/core.py index 7106e98..89be8f5 100644 --- a/src/grodecoder/core.py +++ b/src/grodecoder/core.py @@ -20,23 +20,23 @@ def decode(universe: UniverseLike) -> Decoded: settings = get_settings() - resolution = guess_resolution(universe, settings.resolution_detection.distance_cutoff) + resolution = guess_resolution(universe, settings.resolution_detection.cutoff_distance) logger.info(f"Guessed resolution: {resolution}") # Guesses the chain dection distance cutoff if not provided by the user. chain_detection_settings = get_settings().chain_detection - if chain_detection_settings.distance_cutoff.is_set(): - value = chain_detection_settings.distance_cutoff.get() + if chain_detection_settings.cutoff_distance.is_set(): + value = chain_detection_settings.cutoff_distance.get() logger.debug(f"chain detection: using user-defined value: {value:.2f}") else: logger.debug("chain detection: guessing distance cutoff based on resolution") - chain_detection_settings.distance_cutoff.guess(resolution) + chain_detection_settings.cutoff_distance.guess(resolution) - distance_cutoff = chain_detection_settings.distance_cutoff.get() + cutoff_distance = chain_detection_settings.cutoff_distance.get() return Decoded( - inventory=identify(universe, bond_threshold=distance_cutoff), + inventory=identify(universe, bond_threshold=cutoff_distance), resolution=resolution, ) diff --git a/src/grodecoder/guesser.py b/src/grodecoder/guesser.py index ecf6f77..2ad2f0a 100644 --- a/src/grodecoder/guesser.py +++ b/src/grodecoder/guesser.py @@ -107,9 +107,9 @@ def _is_martini(model: MDA.AtomGroup) -> bool: return bool(np.any(np.char.startswith(model.atoms.names.astype("U"), "BB"))) -def _has_bonds_within_all_atom_cutoff(model: MDA.AtomGroup, distance_cutoff: float) -> bool: +def _has_bonds_within_all_atom_cutoff(model: MDA.AtomGroup, cutoff_distance: float) -> bool: for residue in model.residues: - if has_bonds(residue, distance_cutoff): + if has_bonds(residue, cutoff_distance): return True return False diff --git a/src/grodecoder/main.py b/src/grodecoder/main.py index c5294e4..5fb3ad8 100644 --- a/src/grodecoder/main.py +++ b/src/grodecoder/main.py @@ -30,7 +30,7 @@ def main(args: "CliArgs"): # Storing cli arguments into settings. settings = get_settings() - settings.chain_detection.distance_cutoff = args.bond_threshold + settings.chain_detection.cutoff_distance = args.bond_threshold settings.output.atom_ids = not args.no_atom_ids logger.info(f"Processing structure file: {structure_path}") diff --git a/src/grodecoder/settings.py b/src/grodecoder/settings.py index 2b7915c..f0d5507 100644 --- a/src/grodecoder/settings.py +++ b/src/grodecoder/settings.py @@ -15,10 +15,10 @@ @dataclass(init=False) class DistanceCutoff: - default_distance_cutoff_all_atom: ClassVar[float] = 5.0 - default_distance_cutoff_coarse_grain: ClassVar[float] = 6.0 - _user_distance_cutoff: float | None = None - _guessed_distance_cutoff: float | None = None + default_cutoff_distance_all_atom: ClassVar[float] = 5.0 + default_cutoff_distance_coarse_grain: ClassVar[float] = 6.0 + _user_cutoff_distance: float | None = None + _guessed_cutoff_distance: float | None = None def __init__(self, user_value: float | None = None): if user_value is not None: @@ -26,38 +26,38 @@ def __init__(self, user_value: float | None = None): def is_defined(self) -> bool: """Returns True if the distance cutoff has been set or guessed.""" - return any((self._user_distance_cutoff, self._guessed_distance_cutoff)) + return any((self._user_cutoff_distance, self._guessed_cutoff_distance)) def is_set(self) -> bool: """Returns True if the distance cutoff has been set.""" - return self._user_distance_cutoff is not None + return self._user_cutoff_distance is not None def is_guessed(self) -> bool: """Returns True if the distance cutoff has been guessed.""" - return self._guessed_distance_cutoff is not None + return self._guessed_cutoff_distance is not None def get(self) -> float: if not self.is_defined(): - raise ValueError("`distance_cutoff` must be set or guessed before it is used.") - return self._user_distance_cutoff or self._guessed_distance_cutoff # ty: ignore[invalid-return-type] + raise ValueError("`cutoff_distance` must be set or guessed before it is used.") + return self._user_cutoff_distance or self._guessed_cutoff_distance # ty: ignore[invalid-return-type] def set(self, value: float): if self.is_guessed(): - self._guessed_distance_cutoff = None - self._user_distance_cutoff = value + self._guessed_cutoff_distance = None + self._user_cutoff_distance = value def guess(self, resolution: "MolecularResolution"): if resolution == "ALL_ATOM": - distance_cutoff = self.default_distance_cutoff_all_atom + cutoff_distance = self.default_cutoff_distance_all_atom logger.debug( - f"chain detection: using default distance cutoff for all atom structures: {distance_cutoff:.2f}" + f"chain detection: using default distance cutoff for all atom structures: {cutoff_distance:.2f}" ) else: - distance_cutoff = self.default_distance_cutoff_coarse_grain + cutoff_distance = self.default_cutoff_distance_coarse_grain logger.debug( - f"chain detection: using default distance cutoff for coarse grain structures: {distance_cutoff:.2f}" + f"chain detection: using default distance cutoff for coarse grain structures: {cutoff_distance:.2f}" ) - self._guessed_distance_cutoff = distance_cutoff + self._guessed_cutoff_distance = cutoff_distance class _DistanceCutoffPydanticAnnotation: @@ -67,28 +67,28 @@ class _DistanceCutoffPydanticAnnotation: Examples: >>> from grodecoder.settings import ChainDetectionSettings >>> cds = ChainDetectionSettings() - >>> cds.distance_cutoff - DistanceCutoff(_user_distance_cutoff=None, _guessed_distance_cutoff=None) + >>> cds.cutoff_distance + DistanceCutoff(_user_cutoff_distance=None, _guessed_cutoff_distance=None) >>> # Float assignement - >>> cds.distance_cutoff = 12 - >>> cds.distance_cutoff - DistanceCutoff(_user_distance_cutoff=12.0, _guessed_distance_cutoff=None) + >>> cds.cutoff_distance = 12 + >>> cds.cutoff_distance + DistanceCutoff(_user_cutoff_distance=12.0, _guessed_cutoff_distance=None) >>> # None assignment - >>> cds.distance_cutoff = None - >>> cds.distance_cutoff - DistanceCutoff(_user_distance_cutoff=None, _guessed_distance_cutoff=None) + >>> cds.cutoff_distance = None + >>> cds.cutoff_distance + DistanceCutoff(_user_cutoff_distance=None, _guessed_cutoff_distance=None) >>> # Serialization - >>> cds.distance_cutoff = 12 + >>> cds.cutoff_distance = 12 >>> cds.model_dump() - {'distance_cutoff': 12.0} + {'cutoff_distance': 12.0} >>> # Validation >>> as_json = cds.model_dump() >>> ChainDetectionSettings.model_validate(as_json) - ChainDetectionSettings(distance_cutoff=DistanceCutoff(_user_distance_cutoff=12.0, _guessed_distance_cutoff=None)) + ChainDetectionSettings(cutoff_distance=DistanceCutoff(_user_cutoff_distance=12.0, _guessed_cutoff_distance=None)) """ @classmethod @@ -96,7 +96,7 @@ def __get_pydantic_core_schema__(cls, _source_type, _handler) -> core_schema.Cor """ We return a pydantic_core.CoreSchema that behaves in the following ways: - * floats will be parsed as `DistanceCutoff` instances with the float as the `_user_distance_cutoff` attribute + * floats will be parsed as `DistanceCutoff` instances with the float as the `_user_cutoff_distance` attribute * `DistanceCutoff` instances will be parsed as `DistanceCutoff` instances without any changes * Nothing else will pass validation * Serialization will always return just a float @@ -148,11 +148,11 @@ def __get_pydantic_json_schema__( class ChainDetectionSettings(BaseSettings): model_config = ConfigDict(validate_assignment=True) - distance_cutoff: PydanticDistanceCutoff = Field(default_factory=DistanceCutoff) + cutoff_distance: PydanticDistanceCutoff = Field(default_factory=DistanceCutoff) class ResolutionDetectionSettings(BaseSettings): - distance_cutoff: float = 1.6 + cutoff_distance: float = 1.6 class OutputSettings(BaseSettings): diff --git a/tests/test_guesser.py b/tests/test_guesser.py index e980016..f6d2684 100644 --- a/tests/test_guesser.py +++ b/tests/test_guesser.py @@ -85,7 +85,7 @@ def test_has_bonds_within_all_atom_cutoff(self): coordinates[i] = np.array((5 * i, 0.0, 0.0)) universe.atoms.positions = coordinates - result = guess_resolution(universe, all_atom_distance_cutoff=2) + result = guess_resolution(universe, all_atom_cutoff_distance=2) assert result.value == ResolutionValue.COARSE_GRAIN assert result.reason == CoarseGrainResolutionReason.HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF @@ -136,7 +136,7 @@ def test_no_bond_within_all_atom_distance(self): coordinates[i] = np.array((5 * i, 0.0, 0.0)) universe.atoms.positions = coordinates - result = guess_resolution(universe, all_atom_distance_cutoff=2) + result = guess_resolution(universe, all_atom_cutoff_distance=2) assert result.value == ResolutionValue.COARSE_GRAIN assert result.reason == CoarseGrainResolutionReason.HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF From 1634b3ee10e64774bea7881234c4337a116ed709 Mon Sep 17 00:00:00 2001 From: Benoist LAURENT Date: Thu, 12 Feb 2026 12:25:36 +0000 Subject: [PATCH 11/14] tests/test_guesser.py: fixes english mistake in comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/test_guesser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_guesser.py b/tests/test_guesser.py index f6d2684..2c1055b 100644 --- a/tests/test_guesser.py +++ b/tests/test_guesser.py @@ -44,7 +44,7 @@ def create_mock_coarse_grain(n_residues: int = 10, n_atoms_per_residue: int = 3) class TestGuessResolutionAllAtom: """Test cases for the guess_resolution function. - All test cases in this ensures that all-atom resolution is detected. + All test cases in this class ensure that all-atom resolution is detected. """ def test_defaulting_to_aa_when_only_one_particle_in_the_system(self): From b6f9f7d1df6f9e88a8ea1c82898874fdc58048b8 Mon Sep 17 00:00:00 2001 From: benoistlaurent Date: Thu, 12 Feb 2026 13:29:44 +0100 Subject: [PATCH 12/14] settings.DistanceCutoff.guess: fixes bug introduced with new MolecularResolution class --- src/grodecoder/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grodecoder/settings.py b/src/grodecoder/settings.py index f0d5507..366f628 100644 --- a/src/grodecoder/settings.py +++ b/src/grodecoder/settings.py @@ -47,7 +47,7 @@ def set(self, value: float): self._user_cutoff_distance = value def guess(self, resolution: "MolecularResolution"): - if resolution == "ALL_ATOM": + if resolution.is_all_atom(): cutoff_distance = self.default_cutoff_distance_all_atom logger.debug( f"chain detection: using default distance cutoff for all atom structures: {cutoff_distance:.2f}" From 4d72d820910a1a88acc14dfb39fee3288a7b481e Mon Sep 17 00:00:00 2001 From: benoistlaurent Date: Thu, 12 Feb 2026 13:37:02 +0100 Subject: [PATCH 13/14] minor fixes --- src/grodecoder/guesser.py | 11 ++++++----- src/grodecoder/settings.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/grodecoder/guesser.py b/src/grodecoder/guesser.py index 2ad2f0a..fd3f45b 100644 --- a/src/grodecoder/guesser.py +++ b/src/grodecoder/guesser.py @@ -44,13 +44,13 @@ def is_coarse_grain(self) -> bool: def check_reason(self) -> Self: """Validate that reason is compatible with value.""" if self.value == ResolutionValue.ALL_ATOM: - if not isinstance(self.reason, AllAtomResolutionReason): + if self.reason is not None and not isinstance(self.reason, AllAtomResolutionReason): raise ValueError( f"reason must be AllAtomResolutionReason when value is 'all-atom', " f"got {type(self.reason).__name__}" ) elif self.value == ResolutionValue.COARSE_GRAIN: - if not isinstance(self.reason, CoarseGrainResolutionReason): + if self.reason is not None and not isinstance(self.reason, CoarseGrainResolutionReason): raise ValueError( f"reason must be CoarseGrainResolutionReason when value is 'coarse-grain', " f"got {type(self.reason).__name__}" @@ -83,10 +83,11 @@ def CoarseGrainSingleParticle(cls) -> Self: @classmethod def CoarseGrainOther(cls) -> Self: - """Other coarse grain systems, typically with bond length between particle greater that standard + """Other coarse grain systems, typically with bond length between particle greater than standard all-atom models.""" return cls( - value=ResolutionValue.COARSE_GRAIN, reason=CoarseGrainResolutionReason.HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF + value=ResolutionValue.COARSE_GRAIN, + reason=CoarseGrainResolutionReason.HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF, ) @classmethod @@ -153,7 +154,7 @@ def guess_resolution(universe, all_atom_cutoff_distance: float = 1.6) -> Molecul return MolecularResolution.CoarseGrainMartini() if _has_protein(universe) and not _protein_has_hydrogen(universe): - logger.debug("Found protein with hydrogen: resolution is coarse grain") + logger.debug("Found protein without hydrogen: resolution is coarse grain") return MolecularResolution.CoarseGrainProteinHasNoHydrogen() # Last chance: if we find any bond within a given distance, it's all-atom. diff --git a/src/grodecoder/settings.py b/src/grodecoder/settings.py index 366f628..937e013 100644 --- a/src/grodecoder/settings.py +++ b/src/grodecoder/settings.py @@ -26,7 +26,7 @@ def __init__(self, user_value: float | None = None): def is_defined(self) -> bool: """Returns True if the distance cutoff has been set or guessed.""" - return any((self._user_cutoff_distance, self._guessed_cutoff_distance)) + return self._user_cutoff_distance is not None or self._guessed_cutoff_distance is not None def is_set(self) -> bool: """Returns True if the distance cutoff has been set.""" From 5792ae7810dda06bdc36ac37e3caa6ac43fe0c09 Mon Sep 17 00:00:00 2001 From: benoistlaurent Date: Thu, 12 Feb 2026 11:56:33 +0100 Subject: [PATCH 14/14] guess_resolution: takes all atom cutoff distance as argument - settings.DistanceCutoff.guess: update to account for new MolecularResolution class - various minor fixes --- src/grodecoder/core.py | 12 ++++---- src/grodecoder/guesser.py | 18 +++++------ src/grodecoder/main.py | 2 +- src/grodecoder/settings.py | 62 +++++++++++++++++++------------------- tests/test_guesser.py | 12 ++++---- 5 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/grodecoder/core.py b/src/grodecoder/core.py index 12745c0..89be8f5 100644 --- a/src/grodecoder/core.py +++ b/src/grodecoder/core.py @@ -20,23 +20,23 @@ def decode(universe: UniverseLike) -> Decoded: settings = get_settings() - resolution = guess_resolution(universe) + resolution = guess_resolution(universe, settings.resolution_detection.cutoff_distance) logger.info(f"Guessed resolution: {resolution}") # Guesses the chain dection distance cutoff if not provided by the user. chain_detection_settings = get_settings().chain_detection - if chain_detection_settings.distance_cutoff.is_set(): - value = chain_detection_settings.distance_cutoff.get() + if chain_detection_settings.cutoff_distance.is_set(): + value = chain_detection_settings.cutoff_distance.get() logger.debug(f"chain detection: using user-defined value: {value:.2f}") else: logger.debug("chain detection: guessing distance cutoff based on resolution") - chain_detection_settings.distance_cutoff.guess(resolution) + chain_detection_settings.cutoff_distance.guess(resolution) - distance_cutoff = chain_detection_settings.distance_cutoff.get() + cutoff_distance = chain_detection_settings.cutoff_distance.get() return Decoded( - inventory=identify(universe, bond_threshold=distance_cutoff), + inventory=identify(universe, bond_threshold=cutoff_distance), resolution=resolution, ) diff --git a/src/grodecoder/guesser.py b/src/grodecoder/guesser.py index d992745..fd3f45b 100644 --- a/src/grodecoder/guesser.py +++ b/src/grodecoder/guesser.py @@ -44,13 +44,13 @@ def is_coarse_grain(self) -> bool: def check_reason(self) -> Self: """Validate that reason is compatible with value.""" if self.value == ResolutionValue.ALL_ATOM: - if not isinstance(self.reason, AllAtomResolutionReason): + if self.reason is not None and not isinstance(self.reason, AllAtomResolutionReason): raise ValueError( f"reason must be AllAtomResolutionReason when value is 'all-atom', " f"got {type(self.reason).__name__}" ) elif self.value == ResolutionValue.COARSE_GRAIN: - if not isinstance(self.reason, CoarseGrainResolutionReason): + if self.reason is not None and not isinstance(self.reason, CoarseGrainResolutionReason): raise ValueError( f"reason must be CoarseGrainResolutionReason when value is 'coarse-grain', " f"got {type(self.reason).__name__}" @@ -83,10 +83,11 @@ def CoarseGrainSingleParticle(cls) -> Self: @classmethod def CoarseGrainOther(cls) -> Self: - """Other coarse grain systems, typically with bond length between particle greater that standard + """Other coarse grain systems, typically with bond length between particle greater than standard all-atom models.""" return cls( - value=ResolutionValue.COARSE_GRAIN, reason=CoarseGrainResolutionReason.HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF + value=ResolutionValue.COARSE_GRAIN, + reason=CoarseGrainResolutionReason.HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF, ) @classmethod @@ -107,8 +108,7 @@ def _is_martini(model: MDA.AtomGroup) -> bool: return bool(np.any(np.char.startswith(model.atoms.names.astype("U"), "BB"))) -def _has_bonds_within_all_atom_cutoff(model: MDA.AtomGroup) -> bool: - cutoff_distance = 1.6 +def _has_bonds_within_all_atom_cutoff(model: MDA.AtomGroup, cutoff_distance: float) -> bool: for residue in model.residues: if has_bonds(residue, cutoff_distance): return True @@ -124,7 +124,7 @@ def _protein_has_hydrogen(model: MDA.AtomGroup) -> bool: return _has_hydrogen(model.select_atoms("protein")) -def guess_resolution(universe) -> MolecularResolution: +def guess_resolution(universe, all_atom_cutoff_distance: float = 1.6) -> MolecularResolution: """Guesses a system resolution (all-atom or coarse-grain).""" # Only one atom in the system: defaulting to all-atom. if len(universe.atoms) == 1: @@ -154,12 +154,12 @@ def guess_resolution(universe) -> MolecularResolution: return MolecularResolution.CoarseGrainMartini() if _has_protein(universe) and not _protein_has_hydrogen(universe): - logger.debug("Found protein with hydrogen: resolution is coarse grain") + logger.debug("Found protein without hydrogen: resolution is coarse grain") return MolecularResolution.CoarseGrainProteinHasNoHydrogen() # Last chance: if we find any bond within a given distance, it's all-atom. # If we reach this point, it means that, for some reason, no hydrogen atom was detected before. - if _has_bonds_within_all_atom_cutoff(small_u): + if _has_bonds_within_all_atom_cutoff(small_u, all_atom_cutoff_distance): logger.debug("Found bonds within all-atom distance cutoff: resolution is all-atom") return MolecularResolution.AllAtomWithStandardBonds() diff --git a/src/grodecoder/main.py b/src/grodecoder/main.py index c5294e4..5fb3ad8 100644 --- a/src/grodecoder/main.py +++ b/src/grodecoder/main.py @@ -30,7 +30,7 @@ def main(args: "CliArgs"): # Storing cli arguments into settings. settings = get_settings() - settings.chain_detection.distance_cutoff = args.bond_threshold + settings.chain_detection.cutoff_distance = args.bond_threshold settings.output.atom_ids = not args.no_atom_ids logger.info(f"Processing structure file: {structure_path}") diff --git a/src/grodecoder/settings.py b/src/grodecoder/settings.py index 2b7915c..937e013 100644 --- a/src/grodecoder/settings.py +++ b/src/grodecoder/settings.py @@ -15,10 +15,10 @@ @dataclass(init=False) class DistanceCutoff: - default_distance_cutoff_all_atom: ClassVar[float] = 5.0 - default_distance_cutoff_coarse_grain: ClassVar[float] = 6.0 - _user_distance_cutoff: float | None = None - _guessed_distance_cutoff: float | None = None + default_cutoff_distance_all_atom: ClassVar[float] = 5.0 + default_cutoff_distance_coarse_grain: ClassVar[float] = 6.0 + _user_cutoff_distance: float | None = None + _guessed_cutoff_distance: float | None = None def __init__(self, user_value: float | None = None): if user_value is not None: @@ -26,38 +26,38 @@ def __init__(self, user_value: float | None = None): def is_defined(self) -> bool: """Returns True if the distance cutoff has been set or guessed.""" - return any((self._user_distance_cutoff, self._guessed_distance_cutoff)) + return self._user_cutoff_distance is not None or self._guessed_cutoff_distance is not None def is_set(self) -> bool: """Returns True if the distance cutoff has been set.""" - return self._user_distance_cutoff is not None + return self._user_cutoff_distance is not None def is_guessed(self) -> bool: """Returns True if the distance cutoff has been guessed.""" - return self._guessed_distance_cutoff is not None + return self._guessed_cutoff_distance is not None def get(self) -> float: if not self.is_defined(): - raise ValueError("`distance_cutoff` must be set or guessed before it is used.") - return self._user_distance_cutoff or self._guessed_distance_cutoff # ty: ignore[invalid-return-type] + raise ValueError("`cutoff_distance` must be set or guessed before it is used.") + return self._user_cutoff_distance or self._guessed_cutoff_distance # ty: ignore[invalid-return-type] def set(self, value: float): if self.is_guessed(): - self._guessed_distance_cutoff = None - self._user_distance_cutoff = value + self._guessed_cutoff_distance = None + self._user_cutoff_distance = value def guess(self, resolution: "MolecularResolution"): - if resolution == "ALL_ATOM": - distance_cutoff = self.default_distance_cutoff_all_atom + if resolution.is_all_atom(): + cutoff_distance = self.default_cutoff_distance_all_atom logger.debug( - f"chain detection: using default distance cutoff for all atom structures: {distance_cutoff:.2f}" + f"chain detection: using default distance cutoff for all atom structures: {cutoff_distance:.2f}" ) else: - distance_cutoff = self.default_distance_cutoff_coarse_grain + cutoff_distance = self.default_cutoff_distance_coarse_grain logger.debug( - f"chain detection: using default distance cutoff for coarse grain structures: {distance_cutoff:.2f}" + f"chain detection: using default distance cutoff for coarse grain structures: {cutoff_distance:.2f}" ) - self._guessed_distance_cutoff = distance_cutoff + self._guessed_cutoff_distance = cutoff_distance class _DistanceCutoffPydanticAnnotation: @@ -67,28 +67,28 @@ class _DistanceCutoffPydanticAnnotation: Examples: >>> from grodecoder.settings import ChainDetectionSettings >>> cds = ChainDetectionSettings() - >>> cds.distance_cutoff - DistanceCutoff(_user_distance_cutoff=None, _guessed_distance_cutoff=None) + >>> cds.cutoff_distance + DistanceCutoff(_user_cutoff_distance=None, _guessed_cutoff_distance=None) >>> # Float assignement - >>> cds.distance_cutoff = 12 - >>> cds.distance_cutoff - DistanceCutoff(_user_distance_cutoff=12.0, _guessed_distance_cutoff=None) + >>> cds.cutoff_distance = 12 + >>> cds.cutoff_distance + DistanceCutoff(_user_cutoff_distance=12.0, _guessed_cutoff_distance=None) >>> # None assignment - >>> cds.distance_cutoff = None - >>> cds.distance_cutoff - DistanceCutoff(_user_distance_cutoff=None, _guessed_distance_cutoff=None) + >>> cds.cutoff_distance = None + >>> cds.cutoff_distance + DistanceCutoff(_user_cutoff_distance=None, _guessed_cutoff_distance=None) >>> # Serialization - >>> cds.distance_cutoff = 12 + >>> cds.cutoff_distance = 12 >>> cds.model_dump() - {'distance_cutoff': 12.0} + {'cutoff_distance': 12.0} >>> # Validation >>> as_json = cds.model_dump() >>> ChainDetectionSettings.model_validate(as_json) - ChainDetectionSettings(distance_cutoff=DistanceCutoff(_user_distance_cutoff=12.0, _guessed_distance_cutoff=None)) + ChainDetectionSettings(cutoff_distance=DistanceCutoff(_user_cutoff_distance=12.0, _guessed_cutoff_distance=None)) """ @classmethod @@ -96,7 +96,7 @@ def __get_pydantic_core_schema__(cls, _source_type, _handler) -> core_schema.Cor """ We return a pydantic_core.CoreSchema that behaves in the following ways: - * floats will be parsed as `DistanceCutoff` instances with the float as the `_user_distance_cutoff` attribute + * floats will be parsed as `DistanceCutoff` instances with the float as the `_user_cutoff_distance` attribute * `DistanceCutoff` instances will be parsed as `DistanceCutoff` instances without any changes * Nothing else will pass validation * Serialization will always return just a float @@ -148,11 +148,11 @@ def __get_pydantic_json_schema__( class ChainDetectionSettings(BaseSettings): model_config = ConfigDict(validate_assignment=True) - distance_cutoff: PydanticDistanceCutoff = Field(default_factory=DistanceCutoff) + cutoff_distance: PydanticDistanceCutoff = Field(default_factory=DistanceCutoff) class ResolutionDetectionSettings(BaseSettings): - distance_cutoff: float = 1.6 + cutoff_distance: float = 1.6 class OutputSettings(BaseSettings): diff --git a/tests/test_guesser.py b/tests/test_guesser.py index 9fceda3..2c1055b 100644 --- a/tests/test_guesser.py +++ b/tests/test_guesser.py @@ -44,7 +44,7 @@ def create_mock_coarse_grain(n_residues: int = 10, n_atoms_per_residue: int = 3) class TestGuessResolutionAllAtom: """Test cases for the guess_resolution function. - All test cases in this ensures that all-atom resolution is detected. + All test cases in this class ensure that all-atom resolution is detected. """ def test_defaulting_to_aa_when_only_one_particle_in_the_system(self): @@ -75,17 +75,17 @@ def test_has_bonds_within_all_atom_cutoff(self): # Sets distances between atoms to 0.0 universe.atoms.positions = np.zeros((n_atoms, 3)) - result = guess_resolution(universe) + result = guess_resolution(universe, all_atom_cutoff_distance=2) assert result.value == ResolutionValue.ALL_ATOM assert result.reason == AllAtomResolutionReason.RESIDUES_HAVE_BONDS_WITHIN_CUTOFF - # Sets distances between atoms to 5.0 (distance > all-atom cutoff) + # Sets distances between atoms to 5.0 coordinates = np.zeros((n_atoms, 3)) for i in range(n_atoms): coordinates[i] = np.array((5 * i, 0.0, 0.0)) universe.atoms.positions = coordinates - result = guess_resolution(universe) + result = guess_resolution(universe, all_atom_cutoff_distance=2) assert result.value == ResolutionValue.COARSE_GRAIN assert result.reason == CoarseGrainResolutionReason.HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF @@ -129,14 +129,14 @@ def test_no_bond_within_all_atom_distance(self): """Ensures models where no bond are found within typical all-atom distance are detected as coarse-grain.""" universe = create_mock_coarse_grain() - # Sets distances between atoms to 5.0 (distance > all-atom cutoff) + # Sets distances between atoms to 5.0 n_atoms = len(universe.atoms) coordinates = np.zeros((len(universe.atoms), 3)) for i in range(n_atoms): coordinates[i] = np.array((5 * i, 0.0, 0.0)) universe.atoms.positions = coordinates - result = guess_resolution(universe) + result = guess_resolution(universe, all_atom_cutoff_distance=2) assert result.value == ResolutionValue.COARSE_GRAIN assert result.reason == CoarseGrainResolutionReason.HAS_NO_BOND_WITHIN_ALL_ATOM_CUTOFF