From 2aa5c12e47dd18a80f853f98020693e8f67dc9ee Mon Sep 17 00:00:00 2001 From: SimonBoothroyd Date: Thu, 12 Dec 2024 09:08:43 -0500 Subject: [PATCH] Remove dependence on `parmed` (#27) --- README.md | 6 +- devtools/envs/base.yaml | 7 +- docs/guide-atm.md | 53 +- docs/guide-fe.md | 22 +- docs/guide-md.md | 116 +- docs/guide-septop.md | 64 +- docs/index.md | 6 +- docs/migration.md | 51 + examples/eralpha/config-atm-rest.yaml | 129 - examples/eralpha/config-atm.yaml | 26 +- examples/eralpha/config-septop-rest.yaml | 217 -- examples/eralpha/config-septop.yaml | 48 +- femto/fe/atm/__init__.py | 35 +- femto/fe/atm/_analyze.py | 1 + femto/fe/atm/_cli.py | 7 +- femto/fe/atm/_config.py | 17 +- femto/fe/atm/_equilibrate.py | 4 +- femto/fe/atm/_runner.py | 73 +- femto/fe/atm/_sample.py | 4 +- femto/fe/atm/_setup.py | 189 +- femto/fe/atm/_utils.py | 8 +- femto/fe/inputs.py | 12 +- femto/fe/reference.py | 237 +- femto/fe/septop/__init__.py | 51 +- femto/fe/septop/_analyze.py | 2 + femto/fe/septop/_cli.py | 10 +- femto/fe/septop/_config.py | 25 +- femto/fe/septop/_equilibrate.py | 4 +- femto/fe/septop/_runner.py | 128 +- femto/fe/septop/_sample.py | 6 +- femto/fe/septop/_setup.py | 239 +- femto/fe/tests/atm/test_cli.py | 23 +- femto/fe/tests/atm/test_equilibrate.py | 13 +- femto/fe/tests/atm/test_runner.py | 37 +- femto/fe/tests/atm/test_sample.py | 20 +- femto/fe/tests/atm/test_setup.py | 162 +- femto/fe/tests/data/cdk2/1h1q.sdf | 114 + femto/fe/tests/data/cdk2/1h1q.xml | 500 ++++ femto/fe/tests/data/cdk2/1oiu.parm7 | 446 ---- femto/fe/tests/data/cdk2/1oiu.rst7 | 27 - femto/fe/tests/data/cdk2/1oiu.sdf | 193 ++ femto/fe/tests/data/cdk2/1oiu.xml | 549 ++++ femto/fe/tests/data/temoa/g1.mol2 | 51 + femto/fe/tests/data/temoa/g1.parm7 | 225 -- femto/fe/tests/data/temoa/g1.rst7 | 13 - femto/fe/tests/data/temoa/g1.xml | 241 ++ femto/fe/tests/data/temoa/g4.mol2 | 43 + femto/fe/tests/data/temoa/g4.parm7 | 204 -- femto/fe/tests/data/temoa/g4.rst7 | 11 - femto/fe/tests/data/temoa/g4.xml | 192 ++ femto/fe/tests/data/temoa/temoa.parm7 | 1112 -------- femto/fe/tests/data/temoa/temoa.rst7 | 100 - femto/fe/tests/data/temoa/temoa.sdf | 434 ++++ femto/fe/tests/data/temoa/temoa.xml | 2285 +++++++++++++++++ femto/fe/tests/septop/test_cli.py | 37 +- femto/fe/tests/septop/test_equilibrate.py | 8 +- femto/fe/tests/septop/test_runner.py | 104 +- femto/fe/tests/septop/test_sample.py | 21 +- femto/fe/tests/septop/test_setup.py | 135 +- femto/fe/tests/systems.py | 64 +- femto/fe/tests/test_inputs.py | 24 +- femto/fe/tests/test_reference.py | 176 +- femto/fe/utils/cli.py | 3 +- femto/md/config.py | 48 +- femto/md/prepare.py | 397 +++ femto/md/restraints.py | 36 +- femto/md/simulate.py | 8 +- femto/md/solvate.py | 211 -- femto/md/system.py | 143 -- femto/md/tests/conftest.py | 5 + .../data/cdk2 => md/tests/data}/1h1q.parm7 | 0 .../data/cdk2 => md/tests/data}/1h1q.rst7 | 0 femto/md/tests/mocking.py | 32 +- femto/md/tests/test_prepare.py | 271 ++ femto/md/tests/test_restraints.py | 23 +- femto/md/tests/test_simulate.py | 9 +- femto/md/tests/test_solvate.py | 114 - femto/md/tests/test_system.py | 115 - femto/md/tests/utils/test_amber.py | 45 - femto/md/tests/utils/test_openmm.py | 13 +- femto/md/utils/amber.py | 203 +- femto/md/utils/openmm.py | 12 +- mkdocs.yml | 5 +- 83 files changed, 6695 insertions(+), 4359 deletions(-) create mode 100644 docs/migration.md create mode 100644 femto/fe/tests/data/cdk2/1h1q.sdf create mode 100644 femto/fe/tests/data/cdk2/1h1q.xml delete mode 100644 femto/fe/tests/data/cdk2/1oiu.parm7 delete mode 100644 femto/fe/tests/data/cdk2/1oiu.rst7 create mode 100644 femto/fe/tests/data/cdk2/1oiu.sdf create mode 100644 femto/fe/tests/data/cdk2/1oiu.xml create mode 100644 femto/fe/tests/data/temoa/g1.mol2 delete mode 100644 femto/fe/tests/data/temoa/g1.parm7 delete mode 100644 femto/fe/tests/data/temoa/g1.rst7 create mode 100644 femto/fe/tests/data/temoa/g1.xml create mode 100644 femto/fe/tests/data/temoa/g4.mol2 delete mode 100644 femto/fe/tests/data/temoa/g4.parm7 delete mode 100644 femto/fe/tests/data/temoa/g4.rst7 create mode 100644 femto/fe/tests/data/temoa/g4.xml delete mode 100644 femto/fe/tests/data/temoa/temoa.parm7 delete mode 100644 femto/fe/tests/data/temoa/temoa.rst7 create mode 100644 femto/fe/tests/data/temoa/temoa.sdf create mode 100644 femto/fe/tests/data/temoa/temoa.xml create mode 100644 femto/md/prepare.py delete mode 100644 femto/md/solvate.py delete mode 100644 femto/md/system.py rename femto/{fe/tests/data/cdk2 => md/tests/data}/1h1q.parm7 (100%) rename femto/{fe/tests/data/cdk2 => md/tests/data}/1h1q.rst7 (100%) create mode 100644 femto/md/tests/test_prepare.py delete mode 100644 femto/md/tests/test_solvate.py delete mode 100644 femto/md/tests/test_system.py delete mode 100644 femto/md/tests/utils/test_amber.py diff --git a/README.md b/README.md index c1dc1b7..f98d942 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ The `femto` framework aims to offer not only a compact and comprehensive toolkit using methods including `ATM` and `SepTop`, but also a full suite of utilities for running advanced simulations using OpenMM, including support for HREMD and REST2. -_**Warning:** The ATM implementation was recently updated to make use of the new `ATMForce` shipped with OpenMM 8.1. -While every effort was made to ensure correctness, this was a major change that is still being tested and there may -be bugs. Please report any issues you encounter._ +_**Warning:** From version 0.3.0 onwards, the codebase was re-written to completely remove the dependency on `parmed`, +allowing easy use of any force field parameters in OpenFF, Amber, and OpenMM FFXML formats. This re-write also introduced +a number of neccessary API changes. See the [migration guide for more details](https://psivant.github.io/femto/latest/migration/)._ _Further, the default protocols selected for the ATM and SepTop methods are still being tested and optimized, and may not be optimal. It is recommended that you run a few test calculations to ensure that the results are reasonable._ diff --git a/devtools/envs/base.yaml b/devtools/envs/base.yaml index df499f5..6622d87 100644 --- a/devtools/envs/base.yaml +++ b/devtools/envs/base.yaml @@ -29,16 +29,17 @@ dependencies: - networkx - mdtraj - - ambertools - openmm >=8.1.0 + - openmmforcefields + + - mdtop + - rdkit # ligand loading # Optional packages - seaborn-base - tensorboardx # Dev / Testing - - rdkit # creating mock test systems / topologies - - versioneer - pre-commit diff --git a/docs/guide-atm.md b/docs/guide-atm.md index b5088a7..4854e72 100644 --- a/docs/guide-atm.md +++ b/docs/guide-atm.md @@ -35,32 +35,44 @@ config = femto.fe.atm.ATMConfig() The setup procedure is responsible for combining the ligand(s) and receptor structures into a single complex, solvating it, selecting any reference atoms if not provided, applying restraints, and finally creating the main OpenMM system. -It begins with the already prepared (correct binding pose, parameterized, etc.) structures of the ligand(s) and -receptor: +It begins with the already prepared (correct binding pose, protonation, etc.) structures of the ligand(s) and receptor: ```python import pathlib -import femto.md.system +import femto.md.prepare eralpha_dir = pathlib.Path("eralpha") -ligand_1, ligand_2 = femto.md.system.load_ligands( - ligand_1_coords=eralpha_dir / "forcefield/2d/vacuum.mol2", - ligand_1_params=eralpha_dir / "forcefield/2d/vacuum.parm7", - ligand_2_coords=eralpha_dir / "forcefield/2e/vacuum.mol2", - ligand_2_params=eralpha_dir / "forcefield/2e/vacuum.parm7", +ligand_1, ligand_2 = femto.md.prepare.load_ligands( + eralpha_dir / "forcefield/2d/vacuum.mol2", + eralpha_dir / "forcefield/2e/vacuum.mol2", ) -receptor = femto.md.system.load_receptor( - coord_path=eralpha_dir / "proteins/eralpha/protein.pdb", - param_path=None, - tleap_sources=config.setup.solvent.tleap_sources, +receptor = femto.md.prepare.load_receptor( + eralpha_dir / "proteins/eralpha/protein.pdb" ) ``` The ligands must **both** be in reasonable binding poses. The setup code will handle translating the ligands along the displacement vector as required. +If the ligands / receptor has already been parameterized, the OpenMM XML or AMBER prmtop files can additionally be +specified: + +```python +extra_parameters = [ + eralpha_dir / "forcefield/2d/vacuum.parm7", + eralpha_dir / "forcefield/2e/vacuum.parm7", +] +``` + +If unspecified, an OpenFF force field will be used to parameterize the ligands. The +exact force field can be specified in the configuration. + +???+ note + In previous versions of `femto`, the parameters were required. However in the current version, the parameters are + optional and will be automatically generated if not provided. + The vector along which the ligands will be translated must also be defined. `femto` provides an [experimental utility][femto.fe.atm.select_displacement] to automatically compute this: @@ -80,8 +92,8 @@ When running RBFE calculations, the ligand 'reference' atoms (i.e. those that wi can be optionally specified: ```python -ligand_1_ref_query = ["@7", "@11", "@23"] # OR None -ligand_2_ref_query = ["@12", "@7", "@21"] # OR None +ligand_1_ref_query = ["idx. 7", "idx. 11", "idx. 23"] # OR None +ligand_2_ref_query = ["idx. 12", "idx. 7", "idx. 21"] # OR None ``` If not specified, these will be automatically selected. By default (`ligand_method='chen'`), the distance between each @@ -95,21 +107,22 @@ Similarly, the receptor atoms that define the binding site can be optionally spe ```python receptor_ref_query = [ - ":36,39,40,42,43,77,80,81,84,95,97,111,113,114,117,118,214,215,217,218 & @CA" + "resi 36+39+40+42+43+77+80+81+84+95+97+111+113+114+117+118+214+215+217+218 and name CA" ] # OR None ``` If not specified, these will be automatically selected. By default, these will include all alpha carbons within a [defined cutoff][femto.fe.atm.ATMReferenceSelection.receptor_cutoff] from the ligand. -The full complex structure (ParmEd) and OpenMM system can then be created: +The full complex topology and OpenMM system can then be created: ```python -complex_structure, complex_system = femto.fe.atm.setup_system( +complex_topology, complex_system = femto.fe.atm.setup_system( config.setup, receptor, ligand_1, ligand_2, + [], displacement, receptor_ref_query, ligand_1_ref_query, @@ -129,7 +142,7 @@ If HMR or REST2 were enabled in the config, the system will also contain the app The solvated system can be saved for easier inspection and checkpointing: ```python - complex_structure.save("system.pdb") + complex_topology.to_file("system.pdb") ``` ### Equilibration @@ -154,7 +167,7 @@ import femto.md.constants coords = femto.fe.atm.equilibrate_states( complex_system, - complex_structure, + complex_topology, config.states, config.equilibrate, displacement, @@ -183,7 +196,7 @@ config.sample.analysis_interval = 10 femto.fe.atm.run_hremd( complex_system, - complex_structure, + complex_topology, coords, config.states, config.sample, diff --git a/docs/guide-fe.md b/docs/guide-fe.md index 43fd85c..35dad26 100644 --- a/docs/guide-fe.md +++ b/docs/guide-fe.md @@ -16,7 +16,7 @@ The easiest way to prepare the inputs for `femto`, especially when using the CLI ├─ forcefield/ │ ├─ / │ │ ├─ vacuum.mol2 -│ │ └─ vacuum.parm7 +│ │ └─ vacuum.xml │ └─ ... ├─ proteins/ │ └─ / @@ -38,8 +38,8 @@ In particular, it should contain: forcefield A subdirectory for each ligand of interest, which must include:
    -
  • vacuum.[mol2,rst7]: The ligand coordinate file. The ligand should already be in the correct docked pose, with the correct protonation state and tautomeric form.
  • -
  • vacuum.parm7: The parameter file for the ligand.
  • +
  • vacuum.[mol2,sdf]: The ligand file. The ligand should already be in the correct docked pose, with the correct protonation state and tautomeric form.
  • +
  • vacuum.[xml,parm7] (optional): The parameter file for the ligand.
@@ -47,8 +47,8 @@ In particular, it should contain: proteins A single subdirectory named after the protein target, which must include:
    -
  • protein.[pdb,mol2,rst7]: A file containing the target protein and any crystallographic waters in the correct pose.
  • -
  • protein.parm7 (optional): The parameter file for the protein.
  • +
  • protein.[pdb,mol2,sdf]: A file containing the target protein and any crystallographic waters in the correct pose.
  • +
  • protein.[xml,parm7] (optional): The parameter file for the protein.
@@ -91,7 +91,7 @@ If running on a SLURM cluster, all the edges can be run using the `femto \ \ - femto atm --config "eralpha/config-atm.yaml" \ + femto atm --config "eralpha/config-atm.yaml" \ \ run-workflow --ligand-1 "2d" \ --ligand-2 "2e" \ @@ -165,7 +165,7 @@ instead run: ```shell srun --mpi=pmix -n \ \ - femto septop --config "eralpha/config-septop.yaml" \ + femto septop --config "eralpha/config-septop.yaml" \ \ run-complex --ligand-1 "2d" \ --ligand-2 "2e" \ @@ -175,7 +175,7 @@ instead run: srun --mpi=pmix -n \ \ - femto septop --config "eralpha/config-septop.yaml" \ + femto septop --config "eralpha/config-septop.yaml" \ \ run-solution --ligand-1 "2d" \ --ligand-2 "2e" \ @@ -183,7 +183,7 @@ instead run: --output-dir "eralpha/outputs-septop/solution" \ --edges "eralpha/edges-septop.yaml" - femto septop --config "eralpha/config-septop.yaml" \ + femto septop --config "eralpha/config-septop.yaml" \ \ analyze --complex-system eralpha/outputs-septop/complex/_setup/system.xml \ --complex-samples eralpha/outputs-septop/complex/_sample/samples.arrow \ diff --git a/docs/guide-md.md b/docs/guide-md.md index df21a12..7713671 100644 --- a/docs/guide-md.md +++ b/docs/guide-md.md @@ -6,36 +6,32 @@ replica exchange MD (HREMD) sampling across multiple processes. ## Preparing a System -Most utilities within the framework in general expected an OpenMM `System` object and a ParmEd `Structure`. While these +Most utilities within the framework in general expected an OpenMM `System` object and a [mdtop.Topology][]. While these can be loaded and generated from a variety of sources, the framework provides built-in utilities for loading -pre-parameterized ligands from MOL2 and PARM7 files and 'receptors' (e.g. proteins with crystallographic waters) -from PDB files (if un-parameterized) or MOL2 and PARM7 files (if pre-parameterized). +ligands from MOL2 and SDF files and 'receptors' (e.g. proteins with crystallographic waters) from PDB, MOL2 and SDF files. -Single ligands can be easily loaded using [femto.md.system.load_ligand][] +Single ligands can be easily loaded using [femto.md.prepare.load_ligand][] ```python import pathlib import femto.md.constants -import femto.md.system +import femto.md.prepare eralpha_dir = pathlib.Path("eralpha") -ligand = femto.md.system.load_ligand( - coord_path=eralpha_dir / "forcefield/2d/vacuum.mol2", - param_path=eralpha_dir / "forcefield/2d/vacuum.parm7", +ligand = femto.md.prepare.load_ligand( + eralpha_dir / "forcefield/2d/vacuum.mol2", residue_name=femto.md.constants.LIGAND_1_RESIDUE_NAME ) ``` -while two ligands (e.g. for use in an RBFE calculation) can be loaded using [femto.md.system.load_ligands][] +while two ligands (e.g. for use in an RBFE calculation) can be loaded using [femto.md.prepare.load_ligands][] ```python -ligand_1, ligand_2 = femto.md.system.load_ligands( - ligand_1_coords=eralpha_dir / "forcefield/2d/vacuum.mol2", - ligand_1_params=eralpha_dir / "forcefield/2d/vacuum.parm7", - ligand_2_coords=eralpha_dir / "forcefield/2e/vacuum.mol2", - ligand_2_params=eralpha_dir / "forcefield/2e/vacuum.parm7", +ligand_1, ligand_2 = femto.md.prepare.load_ligands( + eralpha_dir / "forcefield/2d/vacuum.mol2", + eralpha_dir / "forcefield/2e/vacuum.mol2", ) ``` @@ -45,44 +41,27 @@ and [femto.md.constants.LIGAND_2_RESIDUE_NAME][] respectively. No modifications will be made to the ligands, so they should already be in the correct protonation state and tautomeric form of interest. -The 'receptor' (namely anything that can be stored in a PDB file and parameterized using tLeap such as a protein and -crystallographic waters, or something pre-parameterized using a 'host' molecule) can be loaded using -[femto.md.system.load_receptor][]. - -Either the parameters should be explicitly specified: +The 'receptor' (possibly also including any crystal waters and ions) can be loaded using +[femto.md.prepare.load_receptor][]: ```python temoa_dir = pathlib.Path("temoa") -receptor = femto.md.system.load_receptor( - coord_path=temoa_dir / "host.mol2", - param_path=temoa_dir / "host.parm7", -) -``` - -or the list of tLeap source files to use when parameterizing the receptor can be optionally specified: - -```python -import femto.md.config - -receptor = femto.md.system.load_receptor( - coord_path=eralpha_dir / "proteins/eralpha/protein.pdb", - param_path=None, - tleap_sources=femto.md.config.DEFAULT_TLEAP_SOURCES -) +receptor = femto.md.prepare.load_receptor(temoa_dir / "host.mol2") ``` -### Solvate the System +### Prepare the System -Once the ligand and / or receptor have been loaded, they can be solvated using [femto.md.solvate.solvate_system][]. This -step also includes neutralizing the system with counter ions, as well as optionally adding a salt concentration. +Once the ligand and / or receptor have been loaded, they can be solvated and parameterized using [femto.md.prepare.prepare_system][]. +This step also includes neutralizing the system with counter ions, as well as optionally adding a salt concentration. ```python import openmm.unit -import femto.md.solvate +import femto.md.config +import femto.md.prepare -solvent_config = femto.md.config.Solvent( +prep_config = femto.md.config.Prepare( ionic_strength=0.15 * openmm.unit.molar, neutralize=True, cation="Na+", @@ -91,38 +70,41 @@ solvent_config = femto.md.config.Solvent( box_padding=10.0 * openmm.unit.angstrom, ) -structure = femto.md.solvate.solvate_system( +topology, system = femto.md.prepare.prepare_system( receptor=receptor, # or None if no receptor ligand_1=ligand_1, ligand_2=None, # or `ligand_2` if setting up FEP for example - solvent=solvent_config, + config=prep_config, ) ``` -The returned ParmEd `Structure` object will contain the fully parameterized system, including the receptor, ligand(s), -solvent, and any counter-ions. - -### Convert to OpenMM +By default, an OpenFF force field will be used to parameterize the ligands / any cofactors. The +exact force field can be specified in the [femto.md.config.Prepare][] configuration. -ParmEd `Structure` objects can easily be converted to OpenMM `System` objects: +If the ligands / receptor has already been parameterized, the OpenMM FFXML or AMBER prmtop files can additionally be +specified: ```python -import openmm.app +extra_params = [ + eralpha_dir / "forcefield/2d/vacuum.parm7", + eralpha_dir / "forcefield/2e/vacuum.parm7", +] -system = structure.createSystem( - nonbondedMethod=openmm.app.PME, - nonbondedCutoff=0.9 * openmm.unit.nanometer, - constraints=openmm.app.HBonds, - rigidWater=True, +topology, system = femto.md.prepare.prepare_system( + receptor=receptor, # or None if no receptor + ligand_1=ligand_1, + ligand_2=None, # or `ligand_2` if setting up FEP for example + config=prep_config, + extra_params=extra_params ) ``` ### HMR and REST2 -HMR can be applied to the system using [femto.md.system.apply_hmr][]: +HMR can be applied to the system using [femto.md.prepare.apply_hmr][]: ```python -femto.md.system.apply_hmr(system, structure) +femto.md.prepare.apply_hmr(system, topology) ``` This modifies the system in-place. @@ -130,15 +112,11 @@ This modifies the system in-place. Similarly, the system can be prepared for REST2 sampling using [femto.md.rest.apply_rest][]: ```python -import parmed.amber - import femto.md.rest rest_config = femto.md.config.REST(scale_torsions=True, scale_nonbonded=True) -solute_mask = parmed.amber.AmberMask(structure, f":{femto.md.constants.LIGAND_1_RESIDUE_NAME}") -solute_idxs = {i for i, matches in enumerate(solute_mask.Selection()) if matches} - +solute_idxs = topology.select(f"resn {femto.md.constants.LIGAND_1_RESIDUE_NAME}") femto.md.rest.apply_rest(system, solute_idxs, rest_config) ``` @@ -163,14 +141,14 @@ The prepared inputs are most easily stored as a coordinate file and an OpenMM XM ```python import openmm -structure.save("system.pdb") +topology.to_file("system.pdb") pathlib.Path("system.xml").write_text(openmm.XmlSerializer.serialize(system)) ``` ## Running MD The `femto.md.simulate` modules provide convenience functions for simulating prepared systems. This includes chaining -together multiple 'stages' such as minimization, anealing, and molecular dynamics. +together multiple 'stages' such as minimization, annealing, and molecular dynamics. The simulation protocol is defined as a list of 'stage' configurations: @@ -184,10 +162,10 @@ angstrom = openmm.unit.angstrom temperature = 300.0 * openmm.unit.kelvin -ligand_mask = f":{femto.md.constants.LIGAND_1_RESIDUE_NAME}" +ligand_mask = f"resn {femto.md.constants.LIGAND_1_RESIDUE_NAME}" restraints = { - # each key should be an Amber style selection mask that defines which + # each key should be an PyMol style selection mask that defines which # atoms in the system should be restrained ligand_mask: femto.md.config.FlatBottomRestraint( k=25.0 * kcal_per_mol / angstrom**2, radius=1.5 * angstrom @@ -240,14 +218,14 @@ import femto.md.simulate state = {femto.md.rest.REST_CTX_PARAM: 1.0} final_coords = femto.md.simulate.simulate_state( - system, structure, state, stages, femto.md.constants.OpenMMPlatform.CUDA + system, topology, state, stages, femto.md.constants.OpenMMPlatform.CUDA ) ``` -The initial coordinates and box vectors are taken from the `structure` object. +The initial coordinates and box vectors are taken from the `topology` object. The `state` dictionary is used to set OpenMM global context parameters. If your system does not use any global context -parameters (e.g. it hasn't been prepared for REST2), or your happy to use the defaults that were set, then you can +parameters (e.g. it hasn't been prepared for REST2), or you're happy to use the defaults that were set, then you can simply pass an empty dictionary. ???+ note @@ -318,8 +296,8 @@ integrator = femto.md.utils.openmm.create_integrator( simulation = femto.md.utils.openmm.create_simulation( system, - structure, - final_coords, # or None to use the coordinates / box in structure + topology, + final_coords, # or None to use the coordinates / box in topology integrator=integrator, state=states[0], platform=femto.md.constants.OpenMMPlatform.CUDA, diff --git a/docs/guide-septop.md b/docs/guide-septop.md index 360f6ef..106dc34 100644 --- a/docs/guide-septop.md +++ b/docs/guide-septop.md @@ -44,31 +44,43 @@ and partitions the options into those [complex][femto.fe.septop.SepTopConfig.com The setup procedure is responsible for combining the ligand(s) and receptor structures into a single complex, solvating it, selecting any reference atoms if not provided, applying restraints, and finally creating the main OpenMM system. -It begins with the already prepared (correct binding pose, parameterized, etc.) structures of the ligand(s) and -receptor: +It begins with the already prepared (correct binding pose, protonation, etc.) structures of the ligand(s) and receptor: ```python import pathlib -import femto.md.system +import femto.md.prepare eralpha_dir = pathlib.Path("eralpha") -ligand_1, ligand_2 = femto.md.system.load_ligands( - ligand_1_coords=eralpha_dir / "forcefield/2d/vacuum.mol2", - ligand_1_params=eralpha_dir / "forcefield/2d/vacuum.parm7", - ligand_2_coords=eralpha_dir / "forcefield/2e/vacuum.mol2", - ligand_2_params=eralpha_dir / "forcefield/2e/vacuum.parm7", +ligand_1, ligand_2 = femto.md.prepare.load_ligands( + eralpha_dir / "forcefield/2d/vacuum.mol2", + eralpha_dir / "forcefield/2e/vacuum.mol2", ) -receptor = femto.md.system.load_receptor( - coord_path=eralpha_dir / "proteins/eralpha/protein.pdb", - param_path=None, - tleap_sources=config.complex.setup.solvent.tleap_sources, +receptor = femto.md.prepare.load_receptor( + eralpha_dir / "proteins/eralpha/protein.pdb" ) ``` +If the ligands / receptor has already been parameterized, the OpenMM XML or AMBER prmtop files can additionally be +specified: + +```python +extra_parameters = [ + eralpha_dir / "forcefield/2d/vacuum.parm7", + eralpha_dir / "forcefield/2e/vacuum.parm7", +] +``` + +If unspecified, an OpenFF force field will be used to parameterize the ligands. The +exact force field can be specified in the configuration. + +???+ note + In previous versions of `femto`, the parameters were required. However in the current version, the parameters are + optional and will be automatically generated if not provided. + The ligand and receptor 'reference' atoms (i.e. those that will be used for the Boresch style restraints used to align -the ligands) can be optionally specified: +the ligands) can be optionally specified by defining PyMol style atom selection queries: ```python ligand_1_ref_query = ["...", "...", "..."] # OR None @@ -77,24 +89,29 @@ ligand_2_ref_query = ["...", "...", "..."] # OR None receiver_ref_query = ["...", "...", "..."] # OR None ``` -If not specified, these will be automatically selected. By default (`ligand_method='baumann'`), this follows the -procedure described in the [Baumann et al. publication](https://pubs.acs.org/doi/full/10.1021/acs.jctc.3c00282). +These should match atoms in the respective ligands in isolation. If not specified, these will be automatically +selected. By default (`ligand_method='baumann'`), this follows the procedure described in the +[Baumann et al. publication](https://pubs.acs.org/doi/full/10.1021/acs.jctc.3c00282). ???+ note Currently the selection procedure only considers a single configuration of the complex rather than a trajectory, and so does not include the variance criteria. -The full complex structure (ParmEd) and OpenMM system can then be created: +The full complex topology and OpenMM system can then be created: ```python -complex_structure, complex_system = femto.fe.septop.setup_complex( +import femto.fe.septop + +complex_topology, complex_system = femto.fe.septop.setup_complex( config.complex.setup, receptor, ligand_1, ligand_2, + [], receptor_ref_query, ligand_1_ref_query, ligand_2_ref_query, + extra_parameters ) ``` @@ -106,7 +123,7 @@ these. The solvated system can be saved for easier inspection and checkpointing: ```python - complex_structure.save("system.pdb") + complex_topology.to_file("system.pdb") ``` ### Setup Solution @@ -119,11 +136,10 @@ machinery to perform absolute HFE calculations if desired. The setup procedure is responsible for combining the ligand(s) and at a fixed distance, solvating, selecting any reference atoms if not provided, applying restraints, and finally creating the main OpenMM system. - ```python import femto.fe.septop -solution_structure, solution_system = femto.fe.septop.setup_solution( +solution_topology, solution_system = femto.fe.septop.setup_solution( config.solution.setup, ligand_1, ligand_2, @@ -159,7 +175,7 @@ where at each stage light flat bottom position restraints are applied to any pro coords = femto.fe.septop.equilibrate_states( complex_system, - complex_structure, + complex_topology, config.complex.states, config.complex.equilibrate, femto.md.constants.OpenMMPlatform.CUDA, @@ -174,7 +190,7 @@ where at each stage light flat bottom position restraints are applied to any pro coords = femto.fe.septop.equilibrate_states( solution_system, - solution_structure, + solution_topology, config.solution.states, config.solution.equilibrate, femto.md.constants.OpenMMPlatform.CUDA, @@ -202,7 +218,7 @@ Following this, 10ns of replica exchange is performed with a timestep of 4 fs, w femto.fe.septop.run_hremd( complex_system, - complex_structure, + complex_topology, coords, config.complex.states, config.complex.sample, @@ -222,7 +238,7 @@ Following this, 10ns of replica exchange is performed with a timestep of 4 fs, w femto.fe.septop.run_hremd( solution_system, - solution_structure, + solution_topology, coords, config.solution.states, config.solution.sample, diff --git a/docs/index.md b/docs/index.md index 858690a..d070a88 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,9 +1,9 @@ --8<-- "README.md::22" !!! warning - The ATM implementation was recently updated to make use of the new `ATMForce` shipped with OpenMM 8.1. While - every effort was made to ensure correctness, this was a major change that is still being tested and there may - be bugs. Please report any issues you encounter. + From version 0.3.0 onwards, the codebase was re-written to completely remove the dependency on `parmed`, + allowing easy use of any force field parameters in OpenFF, Amber, and OpenMM FFXML formats. This re-write also introduced + a number of neccessary API changes. See the [migration guide for more details](https://psivant.github.io/femto/latest/migration/). Further, the default protocols selected for the ATM and SepTop methods are still being tested and optimized, and may not be optimal. It is recommended that you run a few test calculations to ensure that the results are reasonable. diff --git a/docs/migration.md b/docs/migration.md new file mode 100644 index 0000000..a4e6bf4 --- /dev/null +++ b/docs/migration.md @@ -0,0 +1,51 @@ +# Migration Guide + +This document outlines the major API and behaviour changes made to the `femto` codebase between versions, and provides +guidance on how to update your code to the latest version. + +## From `pre 0.3.0` to `0.3.0` + +The `femto` codebase was re-written to completely remove the dependency on `parmed`, allowing easy use of any force +field in OpenFF, Amber, and OpenMM FFXML formats. This re-write also introduced a number of neccessary API changes. + +### Behaviour Changes + +#### MD + +- Atom selection should now be performed using the `PyMol` atom selection language. This is more powerful and flexible + than the previous `Amber` atom selection language, but may require some changes to your configuration files. Amber + atom selection is currently still supported, but will be removed in a future version. +- Parameterization is now performed by + [`openmmforcefields`](https://github.com/openmm/openmmforcefields?tab=readme-ov-file) rather than a combination of + `parmed` and `tleap`. This allows for more flexibility in the force fields that can be used, and should give near + identical parameters to those from `tleap`. +- Ligand force field parameters no longer need to be provided. The [femto.md.config.Prepare][] configuration now + exposes a `default_ligand_ff` field that can be used to automatically parameterize ligands with an OpenFF based + force field. +- The labelling of atoms in Boresch restraints has been updated to be more consistent with the literature, and also + properly documented. See the [femto.md.restraints.create_boresch_restraint][] for more information on the exact + definition of the distances, angles, and dihedrals that will be restrained. + +#### FE + +- The force constant of P1−L1−L2 angle in SepTop Boresch-style restraints is now also scaled based on the P1-L1 distance by + default to improve stability. +- The solution phase of SepTop calculations now has a properly padded box when two ligands are present. +- Support has been added for co-factors, and force fields with virtual sites. + +### API Changes + +- [parmed.Structure][] is no longer used to store topological information. Instead, [mdtop.Topology][] is + used. See the [mdtop documentation](https://simonboothroyd.github.io/mdtop/latest/) for more information. +- `femto.md.config.Solvent` has been renamed to [femto.md.config.Prepare][] to better reflect that it is used to + more generally configure the system for simulation, not just solvation. +- The `tleap_sources` field of the old `femto.md.config.Solvent` configuration has been replaced by + [femto.md.config.Prepare.default_protein_ff][], which now stores the paths to OpenMM force field XML files. See + [`openmmforcefields`](https://github.com/openmm/openmmforcefields?tab=readme-ov-file#using-the-amber-and-charmm-biopolymer-force-fields) + for details. +- A new `default_ligand_ff` field has been added to [femto.md.config.Prepare][]. This will accept the name / path + to an OpenFF force field (e.g. `'openff-2.0.0.offxml'`) to use to automatically parameterize ligands. +- The `femto.md.solvate` and `femto.md.system` modules have been combined into a single `femto.md.prepare` module. +- The `femto.md.solvate.solvate_system` function has been replaced by the [femto.md.prepare.prepare_system][] + function. The syntax is similar, but now also accepts cofactors, and an optional list of force field files to use + for the receptor, ligand, cofactors, and solvent / ions. diff --git a/examples/eralpha/config-atm-rest.yaml b/examples/eralpha/config-atm-rest.yaml index 61cbb5b..998f808 100644 --- a/examples/eralpha/config-atm-rest.yaml +++ b/examples/eralpha/config-atm-rest.yaml @@ -1,139 +1,10 @@ type: atm setup: - displacement: [22.0 A, 22.0 A, -22.0 A] - - solvent: - ionic_strength: 0.0 M - neutralize: true - cation: K+ - anion: Cl- - - water_model: tip3p - tleap_sources: - - leaprc.water.tip3p - - leaprc.protein.ff14SB - - box_padding: 8.5 A - n_waters: null - - reference: - receptor_cutoff: 5.0 A - ligand_method: chen - - restraints: - com: - type: flat-bottom - k: 25.0 kcal * mol**-1 * A**-2 - radius: 5.0 A - alignment: - type: atm - k_distance: 2.5 kcal * mol**-1 * A**-2 - k_angle: 10.0 kcal * mol**-1 - k_dihedral: 10.0 kcal * mol**-1 - receptor: - type: flat-bottom - k: 25.0 kcal * mol**-1 * A**-2 - radius: 1.5 A - receptor_query: '@CA' - - apply_hmr: true - hydrogen_mass: 1.5 Da - apply_rest: true rest_config: scale_torsions: true scale_nonbonded: true states: - lambda_1: [ 0.00, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.50, 0.45, 0.40, 0.35, 0.30, 0.25, 0.20, 0.15, 0.10, 0.05, 0.00 ] - lambda_2: [ 0.00, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.50, 0.45, 0.40, 0.35, 0.30, 0.25, 0.20, 0.15, 0.10, 0.05, 0.00 ] - direction: [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ] - alpha: [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00 ] - u0: [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00 ] - w0: [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00 ] # REST2 bm_b0: [1.0, 0.8320426416621318, 0.7123912835706776, 0.622826166428176, 0.5532669004806176, 0.49768392939114464, 0.45224949185450347, 0.41441666840411706, 0.38242499647268574, 0.3550186648249912, 0.3312777777777777, 0.3312777777777777, 0.3550186648249912, 0.38242499647268574, 0.41441666840411706, 0.45224949185450347, 0.49768392939114464, 0.5532669004806176, 0.622826166428176, 0.7123912835706776, 0.8320426416621318, 1.0] - -equilibrate: - stages: - - type: minimization - restraints: - '!(:WAT,CL,NA,K) & !@/H': - type: flat-bottom - k: 25.0 kcal * mol**-1 * A**-2 - radius: 1.5 A - tolerance: 10.0 kcal * mol**-1 * A**-1 - max_iterations: 0 - - - type: anneal - - integrator: - type: langevin - timestep: 0.002 ps - friction: 1.0 ps**-1 - constraint_tolerance: 1.0e-06 - - restraints: - '!(:WAT,CL,NA,K) & !@/H': - type: flat-bottom - k: 25.0 kcal * mol**-1 * A**-2 - radius: 1.5 A - - temperature_initial: 50.0 K - temperature_final: 298.15 K - - n_steps: 50000 - frequency: 5000 - - - type: simulation - - integrator: - type: langevin - timestep: 0.002 ps - friction: 1.0 ps**-1 - - restraints: - '!(:WAT,CL,NA,K) & !@/H': - type: flat-bottom - k: 25.0 kcal * mol**-1 * A**-2 - radius: 1.5 A - - temperature: 298.15 K - - pressure: 1.0 bar - barostat_frequency: 25 - - n_steps: 150000 - - report_interval: 5000 - - soft_core: - u_max: 1000 kcal * mol**-1 - u0: 500 kcal * mol**-1 - a: 0.0625 - -sample: - integrator: - type: langevin - timestep: 0.004 ps - friction: 1.0 ps**-1 - - temperature: 298.15 K - - n_warmup_steps: 150000 - - n_steps_per_cycle: 1000 - n_cycles: 2500 - - max_step_retries: 5 - swap_mode: all - - max_swaps: null - - trajectory_interval: null - analysis_interval: 100 - - soft_core: - u_max: 100 kcal * mol**-1 - u0: 50 kcal * mol**-1 - a: 0.0625 diff --git a/examples/eralpha/config-atm.yaml b/examples/eralpha/config-atm.yaml index 67ac2e2..deaf43a 100644 --- a/examples/eralpha/config-atm.yaml +++ b/examples/eralpha/config-atm.yaml @@ -6,19 +6,19 @@ setup: # the box size. displacement: [22.0 A, 22.0 A, -22.0 A] - solvent: - ionic_strength: 0.0 M - neutralize: true - cation: K+ - anion: Cl- - - water_model: tip3p - tleap_sources: - - leaprc.water.tip3p - - leaprc.protein.ff14SB - - box_padding: 8.5 A - n_waters: null + ionic_strength: 0.0 M + neutralize: true + cation: K+ + anion: Cl- + + default_protein_ff: + - "amber/protein.ff14SB.xml", + - "amber/tip3p_standard.xml", + - "amber/tip3p_HFE_multivalent.xml" + default_ligand_ff: "openff-2.0.0.offxml" + + box_padding: 8.5 A + n_waters: null reference: receptor_cutoff: 5.0 A diff --git a/examples/eralpha/config-septop-rest.yaml b/examples/eralpha/config-septop-rest.yaml index 3ed056b..e72ecb9 100644 --- a/examples/eralpha/config-septop-rest.yaml +++ b/examples/eralpha/config-septop-rest.yaml @@ -1,239 +1,22 @@ type: septop complex: setup: - solvent: - ionic_strength: 0.0 M - neutralize: true - cation: K+ - anion: Cl- - - water_model: tip3p - tleap_sources: - - leaprc.water.tip3p - - leaprc.protein.ff14SB - - box_padding: 10.0 A - n_waters: null - - restraints: - type: boresch - k_distance: 20.0 kcal * mol**-1 * A**-2 - k_angle_a: 40.0 kcal * rad**-2 * mol**-1 - k_angle_b: 20.0 kcal * rad**-2 * mol**-1 - k_dihedral_a: 20.0 kcal * rad**-2 * mol**-1 - k_dihedral_b: 20.0 kcal * rad**-2 * mol**-1 - k_dihedral_c: 20.0 kcal * rad**-2 * mol**-1 - scale_k_angle_a: true - - apply_hmr: true - hydrogen_mass: 1.5 Da - apply_rest: true rest_config: scale_torsions: true scale_nonbonded: true - fep_config: - scale_vdw: true - scale_charges: true - ligands_can_interact: false - states: - lambda_vdw_ligand_1: [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.14285714285714285, 0.2857142857142857, 0.42857142857142855, 0.5714285714285714, 0.7142857142857142, 0.8571428571428571, 1.0 ] - lambda_vdw_ligand_2: [ 1.0, 0.8571428571428572, 0.7142857142857143, 0.5714285714285714, 0.4285714285714286, 0.2857142857142858, 0.1428571428571429, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] - lambda_charges_ligand_1: [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ] - lambda_charges_ligand_2: [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.75, 0.5, 0.25, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] - lambda_boresch_ligand_1: [ 0.0, 0.05, 0.1, 0.3, 0.5, 0.75, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ] - lambda_boresch_ligand_2: [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.95, 0.9, 0.7, 0.5, 0.25, 0.0, 0.0 ] # REST2 bm_b0: [1.00, 0.90, 0.82, 0.75, 0.69, 0.64, 0.60, 0.56, 0.53, 0.50, 0.53, 0.56, 0.60, 0.64, 0.69, 0.75, 0.82, 0.90, 1.00] - equilibrate: - stages: - - type: minimization - restraints: - '!(:WAT,CL,NA,K) & !@/H': - type: flat-bottom - k: 25.0 kcal * mol**-1 * A**-2 - radius: 1.5 A - tolerance: 10.0 kcal * mol**-1 * A**-1 - max_iterations: 0 - - - type: anneal - integrator: - type: langevin - timestep: 0.002 ps - friction: 1.0 ps**-1 - - restraints: - '!(:WAT,CL,NA,K) & !@/H': - type: flat-bottom - k: 25.0 kcal * mol**-1 * A**-2 - radius: 1.5 A - - temperature_initial: 50.0 K - temperature_final: 298.15 K - - n_steps: 50000 - frequency: 5000 - - - type: simulation - integrator: - type: langevin - timestep: 0.002 ps - friction: 1.0 ps**-1 - - restraints: - '!(:WAT,CL,NA,K) & !@/H': - type: flat-bottom - k: 25.0 kcal * mol**-1 * A**-2 - radius: 1.5 A - - temperature: 298.15 K - pressure: 1.0 bar - barostat_frequency: 25 - - n_steps: 150000 - - report_interval: 5000 - - sample: - integrator: - type: langevin - timestep: 0.004 ps - friction: 1.0 ps**-1 - - temperature: 298.15 K - - pressure: 1.0 bar - barostat_frequency: 25 - - n_warmup_steps: 150000 - - n_steps_per_cycle: 1000 - n_cycles: 2500 - - max_step_retries: 5 - - swap_mode: all - max_swaps: null - - trajectory_interval: null - analysis_interval: 100 - solution: setup: - solvent: - ionic_strength: 0.0 M - neutralize: true - cation: K+ - anion: Cl- - - water_model: tip3p - tleap_sources: - - leaprc.water.tip3p - - leaprc.protein.ff14SB - - box_padding: 10.0 A - n_waters: null - - restraints: - type: harmonic - k_distance: 2.4 kcal * mol**-1 * A**-2 - - apply_hmr: true - hydrogen_mass: 1.5 Da - apply_rest: true rest_config: scale_torsions: true scale_nonbonded: true - fep_config: - scale_vdw: true - scale_charges: true - ligands_can_interact: false - states: - lambda_vdw_ligand_1: [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.12, 0.24, 0.36, 0.48, 0.6, 0.7, 0.77, 0.85, 1.0 ] - lambda_vdw_ligand_2: [ 1.0, 0.85, 0.77, 0.7, 0.6, 0.48, 0.36, 0.24, 0.12, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] - lambda_charges_ligand_1: [ 0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ] - lambda_charges_ligand_2: [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.875, 0.75, 0.625, 0.5, 0.375, 0.25, 0.125, 0.0 ] - lambda_boresch_ligand_1: null - lambda_boresch_ligand_2: null # REST2 bm_b0: [1.00, 0.89, 0.80, 0.72, 0.66, 0.61, 0.57, 0.53, 0.50, 0.50, 0.53, 0.57, 0.61, 0.66, 0.72, 0.80, 0.89, 1.00] - - equilibrate: - stages: - - type: minimization - restraints: - '!(:WAT,CL,NA,K) & !@/H': - type: flat-bottom - k: 25.0 kcal * mol**-1 * A**-2 - radius: 1.5 A - tolerance: 10.0 kcal * mol**-1 * A**-1 - max_iterations: 0 - - - type: anneal - integrator: - type: langevin - timestep: 0.002 ps - friction: 1.0 ps**-1 - - restraints: - '!(:WAT,CL,NA,K) & !@/H': - type: flat-bottom - k: 25.0 kcal * mol**-1 * A**-2 - radius: 1.5 A - - temperature_initial: 50.0 K - temperature_final: 298.15 K - - n_steps: 150000 - frequency: 5000 - - - type: simulation - integrator: - type: langevin - timestep: 0.002 ps - friction: 1.0 ps**-1 - - restraints: - '!(:WAT,CL,NA,K) & !@/H': - type: flat-bottom - k: 25.0 kcal * mol**-1 * A**-2 - radius: 1.5 A - - temperature: 298.15 K - - pressure: 1.0 bar - barostat_frequency: 25 - - n_steps: 150000 - - report_interval: 5000 - - sample: - integrator: - type: langevin - timestep: 0.004 ps - friction: 1.0 ps**-1 - - temperature: 298.15 K - - pressure: 1.0 bar - barostat_frequency: 25 - - n_warmup_steps: 150000 - - n_steps_per_cycle: 1000 - n_cycles: 2500 - - max_step_retries: 5 - - swap_mode: all - max_swaps: null - - trajectory_interval: null - analysis_interval: 100 diff --git a/examples/eralpha/config-septop.yaml b/examples/eralpha/config-septop.yaml index a5c460b..0edbe2a 100644 --- a/examples/eralpha/config-septop.yaml +++ b/examples/eralpha/config-septop.yaml @@ -1,19 +1,19 @@ type: septop complex: setup: - solvent: - ionic_strength: 0.0 M - neutralize: true - cation: K+ - anion: Cl- + ionic_strength: 0.0 M + neutralize: true + cation: K+ + anion: Cl- - water_model: tip3p - tleap_sources: - - leaprc.water.tip3p - - leaprc.protein.ff14SB + default_protein_ff: + - "amber/protein.ff14SB.xml", + - "amber/tip3p_standard.xml", + - "amber/tip3p_HFE_multivalent.xml" + default_ligand_ff: "openff-2.0.0.offxml" - box_padding: 10.0 A - n_waters: null + box_padding: 10.0 A + n_waters: null restraints: type: boresch @@ -121,19 +121,19 @@ complex: solution: setup: - solvent: - ionic_strength: 0.0 M - neutralize: true - cation: K+ - anion: Cl- - - water_model: tip3p - tleap_sources: - - leaprc.water.tip3p - - leaprc.protein.ff14SB - - box_padding: 10.0 A - n_waters: null + ionic_strength: 0.0 M + neutralize: true + cation: K+ + anion: Cl- + + default_protein_ff: + - "amber/protein.ff14SB.xml", + - "amber/tip3p_standard.xml", + - "amber/tip3p_HFE_multivalent.xml" + default_ligand_ff: "openff-2.0.0.offxml" + + box_padding: 10.0 A + n_waters: null restraints: type: harmonic diff --git a/femto/fe/atm/__init__.py b/femto/fe/atm/__init__.py index 56c6414..365c5ee 100644 --- a/femto/fe/atm/__init__.py +++ b/femto/fe/atm/__init__.py @@ -1,36 +1,35 @@ """Automated BFE calculations using the alchemical transfer method""" +from femto.fe.atm._analyze import compute_ddg from femto.fe.atm._config import ( - DEFAULT_LAMBDA_1, - DEFAULT_LAMBDA_2, - DEFAULT_DIRECTION, DEFAULT_ALPHA, - DEFAULT_U0, - DEFAULT_W0, - DEFAULT_MAX_REST_TEMPERATURE, DEFAULT_BM_B0, + DEFAULT_DIRECTION, DEFAULT_EQUILIBRATE_INTEGRATOR, DEFAULT_EQUILIBRATE_RESTRAINTS, - ATMSoftCore, + DEFAULT_LAMBDA_1, + DEFAULT_LAMBDA_2, + DEFAULT_MAX_REST_TEMPERATURE, + DEFAULT_U0, + DEFAULT_W0, ATMAlignmentRestraint, - ATMRestraints, - ATMReferenceSelection, - ATMSetupStage, - ATMStates, - ATMEquilibrateStage, - ATMSamplingStage, ATMConfig, ATMEdge, + ATMEquilibrateStage, ATMNetwork, + ATMReferenceSelection, + ATMRestraints, + ATMSamplingStage, + ATMSetupStage, + ATMSoftCore, + ATMStates, load_config, ) -from femto.fe.atm._utils import create_state_dicts -from femto.fe.atm._analyze import compute_ddg from femto.fe.atm._equilibrate import equilibrate_states -from femto.fe.atm._sample import run_hremd -from femto.fe.atm._setup import setup_system, select_displacement from femto.fe.atm._runner import run_workflow, submit_network - +from femto.fe.atm._sample import run_hremd +from femto.fe.atm._setup import select_displacement, setup_system +from femto.fe.atm._utils import create_state_dicts __all__ = [ "compute_ddg", diff --git a/femto/fe/atm/_analyze.py b/femto/fe/atm/_analyze.py index 2ec575a..a4735ed 100644 --- a/femto/fe/atm/_analyze.py +++ b/femto/fe/atm/_analyze.py @@ -8,6 +8,7 @@ if typing.TYPE_CHECKING: import pandas + import femto.fe.atm diff --git a/femto/fe/atm/_cli.py b/femto/fe/atm/_cli.py index aa53716..8ead884 100644 --- a/femto/fe/atm/_cli.py +++ b/femto/fe/atm/_cli.py @@ -198,17 +198,18 @@ def _run_workflow_cli( femto.fe.atm._runner.run_workflow( config, ligand_1_coords, - ligand_1_params, ligand_2_coords, - ligand_2_params, receptor_coords, - receptor_params, + [], output_dir, report_dir, ligand_displacement, ligand_1_ref_atoms, ligand_2_ref_atoms, receptor_ref_atoms, + [ligand_1_params] + + ([] if ligand_2_params is None else [ligand_2_params]) + + ([] if receptor_params is None else [receptor_params]), ) diff --git a/femto/fe/atm/_config.py b/femto/fe/atm/_config.py index 2abb0d7..f462d78 100644 --- a/femto/fe/atm/_config.py +++ b/femto/fe/atm/_config.py @@ -44,7 +44,7 @@ """The default beta scaling factors to use if running with REST2.""" # fmt: on -DEFAULT_RESTRAINT_MASK = "!(:WAT,CL,NA,K) & !@/H" +DEFAULT_RESTRAINT_MASK = "not (water or ion or elem H)" """The default Amber style selection mask to apply position restraints to.""" DEFAULT_EQUILIBRATE_INTEGRATOR = femto.md.config.LangevinIntegrator( @@ -122,7 +122,7 @@ class ATMRestraints(BaseModel): "initial coordinates.", ) receptor_query: str = pydantic.Field( - "@CA", + "name CA", description="An Amber query used to identify which receptor atoms to restrain.", ) @@ -144,7 +144,7 @@ class ATMReferenceSelection(BaseModel): ) -class ATMSetupStage(BaseModel): +class ATMSetupStage(femto.md.config.Prepare): """Configure how the complex will be solvated and restrained prior to equilibration """ @@ -163,11 +163,6 @@ class ATMSetupStage(BaseModel): "the ligands by.", ) - solvent: femto.md.config.Solvent = pydantic.Field( - femto.md.config.Solvent(), - description="Control how the system should be solvated.", - ) - reference: ATMReferenceSelection = pydantic.Field( ATMReferenceSelection(), description="Selection of receptor and ligand reference atoms.", @@ -359,9 +354,9 @@ class ATMNetwork(femto.fe.config.Network): receptor_ref_query: str | None = pydantic.Field( None, - description="An (optional) AMBER style query to manually select the receptor " - "atoms that define the binding site. If unspecified, they will be determined " - "automatically based on the config.", + description="An (optional) query to manually select the receptor atoms that " + "define the binding site. If unspecified, alpha carbons within a specified " + "distance to either ligand will be selected.", ) edges: list[ATMEdge] = pydantic.Field( diff --git a/femto/fe/atm/_equilibrate.py b/femto/fe/atm/_equilibrate.py index b866ac7..4f02460 100644 --- a/femto/fe/atm/_equilibrate.py +++ b/femto/fe/atm/_equilibrate.py @@ -4,9 +4,9 @@ import logging import typing +import mdtop import numpy import openmm.unit -import parmed import femto.md.constants import femto.md.reporting @@ -22,7 +22,7 @@ def equilibrate_states( system: openmm.System, - topology: parmed.Structure, + topology: mdtop.Topology, states: "femto.fe.atm.ATMStates", config: "femto.fe.atm.ATMEquilibrateStage", offset: openmm.unit.Quantity, diff --git a/femto/fe/atm/_runner.py b/femto/fe/atm/_runner.py index 73d26c1..3db4a8f 100644 --- a/femto/fe/atm/_runner.py +++ b/femto/fe/atm/_runner.py @@ -5,17 +5,17 @@ import pathlib import typing +import mdtop import numpy import openmm -import parmed import yaml import femto.fe.ddg import femto.fe.inputs import femto.fe.utils.queue import femto.md.constants +import femto.md.prepare import femto.md.reporting -import femto.md.system import femto.md.utils.mpi if typing.TYPE_CHECKING: @@ -27,18 +27,17 @@ @femto.md.utils.mpi.run_on_rank_zero def _prepare_system( config: "femto.fe.atm.ATMSetupStage", - ligand_1_coords: pathlib.Path, - ligand_1_params: pathlib.Path, - ligand_2_coords: pathlib.Path | None, - ligand_2_params: pathlib.Path | None, - receptor_coords: pathlib.Path, - receptor_params: pathlib.Path | None, + receptor_path: pathlib.Path, + ligand_1_path: pathlib.Path, + ligand_2_path: pathlib.Path | None, + cofactor_paths: list[pathlib.Path] | None, displacement: openmm.unit.Quantity | None, ligand_1_ref_atoms: tuple[str, str, str], ligand_2_ref_atoms: tuple[str, str, str], receptor_ref_atoms: str | None, output_dir: pathlib.Path, -) -> tuple[parmed.Structure, openmm.System, openmm.unit.Quantity]: + extra_params: list[pathlib.Path] | None, +) -> tuple[mdtop.Topology, openmm.System, openmm.unit.Quantity]: """Prepare the system for running the ATM method, caching the topology and system.""" import femto.fe.atm._setup @@ -51,7 +50,7 @@ def _prepare_system( displacement_path = output_dir / "displacement.yaml" if topology_path.exists() and system_path.exists() and displacement_path.exists(): - topology = parmed.load_file(str(topology_path), structure=True) + topology = mdtop.Topology.from_file(topology_path) system = openmm.XmlSerializer.deserialize(system_path.read_text()) displacement = ( @@ -61,14 +60,11 @@ def _prepare_system( return topology, system, displacement - receptor = femto.md.system.load_receptor( - receptor_coords, - receptor_params, - config.solvent.tleap_sources, - ) - ligand_1, ligand_2 = femto.md.system.load_ligands( - ligand_1_coords, ligand_1_params, ligand_2_coords, ligand_2_params - ) + receptor = femto.md.prepare.load_receptor(receptor_path) + ligand_1, ligand_2 = femto.md.prepare.load_ligands(ligand_1_path, ligand_2_path) + + cofactor_paths = cofactor_paths if cofactor_paths is not None else [] + cofactors = [femto.md.prepare.load_ligand(path, "COF") for path in cofactor_paths] if displacement is None and isinstance(config.displacement, openmm.unit.Quantity): _LOGGER.info("selecting ligand displacement vector") @@ -88,13 +84,15 @@ def _prepare_system( receptor, ligand_1, ligand_2, + cofactors, displacement, receptor_ref_atoms, ligand_1_ref_atoms, ligand_2_ref_atoms, + extra_params, ) - topology.save(str(topology_path), overwrite=True) + topology.to_file(topology_path) system_path.write_text(openmm.XmlSerializer.serialize(system)) displacement_path.write_text( @@ -128,31 +126,28 @@ def _analyze_results( def run_workflow( config: "femto.fe.atm.ATMConfig", - ligand_1_coords: pathlib.Path, - ligand_1_params: pathlib.Path, - ligand_2_coords: pathlib.Path | None, - ligand_2_params: pathlib.Path | None, - receptor_coords: pathlib.Path, - receptor_params: pathlib.Path | None, + ligand_1_path: pathlib.Path, + ligand_2_path: pathlib.Path | None, + receptor_path: pathlib.Path, + cofactor_paths: list[pathlib.Path] | None, output_dir: pathlib.Path, report_dir: pathlib.Path | None = None, displacement: openmm.unit.Quantity | None = None, ligand_1_ref_atoms: tuple[str, str, str] | None = None, ligand_2_ref_atoms: tuple[str, str, str] | None = None, receptor_ref_atoms: str | None = None, + extra_params: list[pathlib.Path] | None = None, ): """Run the setup, equilibration, and sampling phases. Args: config: The configuration. - ligand_1_coords: The path to the first ligand coordinates. - ligand_1_params: The path to the first ligand parameters. - ligand_2_coords: The path to the second ligand coordinates. - ligand_2_params: The path to the second ligand parameters. - receptor_coords: The path to the receptor coordinates. - receptor_params: The path to the receptor parameters. - report_dir: The directory to write any statistics to. + ligand_1_path: The path to the first ligand. + ligand_2_path: The path to the second ligand, if present. + receptor_path: The path to the receptor. + cofactor_paths: The paths to any cofactors. output_dir: The directory to store all outputs in. + report_dir: The directory to store the logs / reports in. displacement: The displacement to offset the ligands by. ligand_1_ref_atoms: The AMBER style query masks that select the first ligands' reference atoms. @@ -160,6 +155,8 @@ def run_workflow( reference atoms. receptor_ref_atoms: The AMBER style query mask that selects the receptor atoms that form the binding site. + extra_params: The paths to any extra parameter files (.xml, .parm) to use + when parameterizing the system. """ import femto.fe.atm._equilibrate import femto.fe.atm._sample @@ -172,19 +169,17 @@ def run_workflow( topology, system, displacement = _prepare_system( config.setup, - ligand_1_coords, - ligand_1_params, - ligand_2_coords, - ligand_2_params, - receptor_coords, - receptor_params, + receptor_path, + ligand_1_path, + ligand_2_path, + cofactor_paths, displacement, ligand_1_ref_atoms, ligand_2_ref_atoms, receptor_ref_atoms, output_dir / "_setup", + extra_params, ) - topology.symmetry = None # needed as attr is lost after pickling by MPI equilibrate_dir = output_dir / "_equilibrate" equilibrate_dir.mkdir(exist_ok=True, parents=True) diff --git a/femto/fe/atm/_sample.py b/femto/fe/atm/_sample.py index 086c3bd..06994db 100644 --- a/femto/fe/atm/_sample.py +++ b/femto/fe/atm/_sample.py @@ -6,9 +6,9 @@ import pathlib import typing +import mdtop import numpy import openmm -import parmed import femto.md.constants import femto.md.hremd @@ -46,7 +46,7 @@ def _analyze( def run_hremd( system: openmm.System, - topology: parmed.Structure, + topology: mdtop.Topology, coords: list[openmm.State], states: "femto.fe.atm.ATMStates", config: "femto.fe.atm.ATMSamplingStage", diff --git a/femto/fe/atm/_setup.py b/femto/fe/atm/_setup.py index d67fb3a..3a2bfa2 100644 --- a/femto/fe/atm/_setup.py +++ b/femto/fe/atm/_setup.py @@ -1,21 +1,21 @@ """Set up the system for ATM calculations.""" +import copy import logging -import tempfile +import pathlib import typing +import mdtop import numpy import openmm import openmm.app import openmm.unit -import parmed import scipy.spatial.distance import femto.fe.reference +import femto.md.prepare import femto.md.rest import femto.md.restraints -import femto.md.solvate -import femto.md.system import femto.md.utils.openmm from femto.md.constants import OpenMMForceGroup, OpenMMForceName @@ -26,9 +26,9 @@ def select_displacement( - receptor: parmed.amber.AmberParm, - ligand_1: parmed.amber.AmberParm, - ligand_2: parmed.amber.AmberParm | None, + receptor: mdtop.Topology, + ligand_1: mdtop.Topology, + ligand_2: mdtop.Topology | None, distance: openmm.unit.Quantity, ) -> openmm.unit.Quantity: """Attempts to automatically select a displacement vector for the ligands. @@ -44,9 +44,14 @@ def select_displacement( """ ligand_coords = numpy.vstack( - [ligand_1.coordinates] + ([] if ligand_2 is None else [ligand_2.coordinates]) + [ligand_1.xyz.value_in_unit(openmm.unit.angstrom)] + + ( + [] + if ligand_2 is None + else [ligand_2.xyz.value_in_unit(openmm.unit.angstrom)] + ) ) - receptor_coords = receptor.coordinates + receptor_coords = receptor.xyz.value_in_unit(openmm.unit.angstrom) directions = numpy.array( [ @@ -77,8 +82,8 @@ def select_displacement( def _offset_ligand( - ligand: parmed.Structure, offset: openmm.unit.Quantity -) -> parmed.Structure: + ligand: mdtop.Topology, offset: openmm.unit.Quantity +) -> mdtop.Topology: """Offsets the coordinates of the specified ligand by a specified amount. Args: @@ -89,20 +94,8 @@ def _offset_ligand( The offset ligand. """ - # we copy in this strange way because parmed doesn't - # copy all attrs correctly when using copy.deepycopy - with tempfile.TemporaryDirectory() as tmpdir: - ligand.save(f"{tmpdir}/ligand.parm7") - ligand.save(f"{tmpdir}/ligand.mol2") - - ligand = parmed.amber.AmberParm( - f"{tmpdir}/ligand.parm7", f"{tmpdir}/ligand.mol2" - ) - - for atom in ligand.atoms: - atom.xx += offset[0].value_in_unit(openmm.unit.angstrom) - atom.xy += offset[1].value_in_unit(openmm.unit.angstrom) - atom.xz += offset[2].value_in_unit(openmm.unit.angstrom) + ligand = copy.deepcopy(ligand) + ligand.xyz += offset return ligand @@ -110,11 +103,11 @@ def _offset_ligand( def _apply_atm_restraints( system: openmm.System, config: "femto.fe.atm.ATMRestraints", - ligand_1_com_idxs: list[int], + ligand_1_com_idxs: numpy.ndarray, ligand_1_ref_idxs: tuple[int, int, int] | None, - ligand_2_com_idxs: list[int] | None, + ligand_2_com_idxs: numpy.ndarray | None, ligand_2_ref_idxs: tuple[int, int, int] | None, - receptor_ref_idxs: list[int], + receptor_ref_idxs: numpy.ndarray, offset: openmm.unit.Quantity, ): """Adds center of mass (COM) and optionally alignment restraints (if running RBFE) @@ -192,8 +185,6 @@ def _apply_receptor_restraints( if len(restrained_coords) == 0: raise RuntimeError("No receptor atoms to restrain were found.") - _LOGGER.info(f"restrained receptor idxs={[*restrained_coords]}") - force = femto.md.restraints.create_flat_bottom_restraint( config.receptor, restrained_coords ) @@ -202,35 +193,37 @@ def _apply_receptor_restraints( def setup_system( config: "femto.fe.atm.ATMSetupStage", - receptor: parmed.amber.AmberParm, - ligand_1: parmed.amber.AmberParm, - ligand_2: parmed.amber.AmberParm | None, + receptor: mdtop.Topology, + ligand_1: mdtop.Topology, + ligand_2: mdtop.Topology | None, + cofactors: list[mdtop.Topology] | None, displacement: openmm.unit.Quantity, receptor_ref_query: str | None, ligand_1_ref_query: tuple[str, str, str] | None = None, ligand_2_ref_query: tuple[str, str, str] | None = None, -) -> tuple[parmed.Structure, openmm.System]: + extra_params: list[pathlib.Path] | None = None, +) -> tuple[mdtop.Topology, openmm.System]: """Prepares a system ready for running the ATM method. + Args: + config: The configuration for setting up the system. + receptor: The receptor topology. + ligand_1: The first ligand. + ligand_2: The second ligand if one is present. + cofactors: Any cofactors. + displacement: The displacement vector to use for the ligands. + receptor_ref_query: The query to select the receptor reference atoms. + ligand_1_ref_query: The query to select the first ligand reference atoms. + ligand_2_ref_query: The query to select the second ligand reference atoms. + extra_params: The paths to any extra parameter files (.xml, .parm) to use + when parameterizing the system. + Returns: The prepared topology and OpenMM system object. """ _LOGGER.info(f"setting up an {'ABFE' if ligand_2 is None else 'RBFE'} calculation") - if receptor_ref_query is None: - # we need to select the receptor cavity atoms before offsetting any ligands - # as the query is distance based - - _LOGGER.info("selecting receptor reference atoms") - receptor_ref_query = femto.fe.reference.select_protein_cavity_atoms( - receptor, - [ligand_1] + ([] if ligand_2 is None else [ligand_2]), - config.reference.receptor_cutoff, - ) - - ligand_1_ref_idxs, ligand_2_ref_idxs = None, None - # we carve out a 'cavity' where the first ligand will be displaced into during the # ATM calculations. this should make equilibration at all states easier. cavity_formers = [_offset_ligand(ligand_1, displacement)] @@ -239,84 +232,98 @@ def setup_system( # we make sure that when placing solvent molecules we don't accidentally place # any on top of the ligands in the cavity itself cavity_formers.append(ligand_2) - - ( - ligand_1_ref_idxs, - ligand_2_ref_idxs, - ) = femto.fe.reference.select_ligand_idxs( - ligand_1, - ligand_2, - config.reference.ligand_method, - ligand_1_ref_query, - ligand_2_ref_query, - ) - assert ligand_2_ref_idxs is not None, "ligand 2 ref atoms were not selected" - ligand_2_ref_idxs = tuple(i + len(ligand_1.atoms) for i in ligand_2_ref_idxs) - ligand_2 = _offset_ligand(ligand_2, displacement) - _LOGGER.info("solvating system") - topology = femto.md.solvate.solvate_system( + _LOGGER.info("preparing system") + topology, system = femto.md.prepare.prepare_system( receptor, ligand_1, ligand_2, - config.solvent, + cofactors, + config, displacement, cavity_formers=cavity_formers, - ) - - _LOGGER.info("creating OpenMM system") - system = topology.createSystem( - nonbondedMethod=openmm.app.PME, - nonbondedCutoff=0.9 * openmm.unit.nanometer, - constraints=openmm.app.HBonds, - rigidWater=True, + extra_params=extra_params, ) if config.apply_hmr: _LOGGER.info("applying HMR.") + femto.md.prepare.apply_hmr(system, topology, config.hydrogen_mass) - hydrogen_mass = config.hydrogen_mass - femto.md.system.apply_hmr(system, topology, hydrogen_mass) - - ligand_1_idxs = list(range(len(ligand_1.atoms))) - ligand_2_idxs = None + ligand_1_idxs = topology.select(f"resn {femto.md.constants.LIGAND_1_RESIDUE_NAME}") + ligand_2_idxs = topology.select(f"resn {femto.md.constants.LIGAND_2_RESIDUE_NAME}") - if ligand_2 is not None: - ligand_2_idxs = [i + len(ligand_1_idxs) for i in range(len(ligand_2.atoms))] + ligand_1 = topology.subset(ligand_1_idxs) + ligand_2 = topology.subset(ligand_2_idxs) if ligand_2 is not None else None if config.apply_rest: _LOGGER.info("applying REST2.") - solute_idxs = ligand_1_idxs + ([] if ligand_2_idxs is None else ligand_2_idxs) - femto.md.rest.apply_rest(system, set(solute_idxs), config.rest_config) + solute_idxs = {*ligand_1_idxs, *({} if ligand_2 is None else ligand_2_idxs)} + femto.md.rest.apply_rest(system, solute_idxs, config.rest_config) _LOGGER.info("applying restraints.") - ligands = [ligand_1] + ([] if ligand_2 is None else [ligand_2]) - idx_offset = sum(len(ligand.atoms) for ligand in ligands) + ligand_1_ref_idxs, ligand_2_ref_idxs = None, None - receptor_ref_mask = parmed.amber.AmberMask(receptor, receptor_ref_query).Selection() - receptor_ref_idxs = [i + idx_offset for i, m in enumerate(receptor_ref_mask) if m] - _LOGGER.info(f"receptor ref idxs={receptor_ref_idxs}") + if ligand_2 is not None: + ( + ligand_1_ref_idxs_0, + ligand_2_ref_idxs_0, + ) = femto.fe.reference.select_ligand_idxs( + ligand_1, + ligand_2, + config.reference.ligand_method, + ligand_1_ref_query, + ligand_2_ref_query, + ) + assert ligand_2_ref_idxs_0 is not None, "ligand 2 ref atoms were not selected" + + ligand_1_ref_idxs = [ligand_1_idxs[i] for i in ligand_1_ref_idxs_0] + ligand_2_ref_idxs = [ligand_2_idxs[i] for i in ligand_2_ref_idxs_0] + + _LOGGER.info(f"ligand 1 ref idxs={ligand_1_idxs}") + _LOGGER.info(f"ligand 2 ref idxs={ligand_2_idxs}") + + receptor_start_idx = ligand_1.n_atoms + ( + 0 if ligand_2 is None else ligand_2.n_atoms + ) + + if receptor_ref_query is not None: + receptor_ref_idxs = receptor.select(receptor_ref_query) + receptor_start_idx + else: + # we need to select the receptor cavity atoms before offsetting any ligands + # as the query is distance based + receptor_cutoff = config.reference.receptor_cutoff.value_in_unit( + openmm.unit.angstrom + ) + receptor_ref_query = ( + f"name CA near_to {receptor_cutoff} of " + f"resn {femto.md.constants.LIGAND_1_RESIDUE_NAME}" + ) + receptor_ref_idxs = topology.select(receptor_ref_query) + + _LOGGER.info(f"receptor cavity idxs={receptor_ref_idxs}") _apply_atm_restraints( system, config.restraints, ligand_1_com_idxs=ligand_1_idxs, ligand_1_ref_idxs=ligand_1_ref_idxs, - ligand_2_com_idxs=ligand_2_idxs, + ligand_2_com_idxs=ligand_2_idxs if ligand_2 is not None else None, ligand_2_ref_idxs=ligand_2_ref_idxs, receptor_ref_idxs=receptor_ref_idxs, offset=displacement, ) - restraint_query = config.restraints.receptor_query - restraint_mask = parmed.amber.AmberMask(receptor, restraint_query).Selection() - restraint_idxs = [i + idx_offset for i, match in enumerate(restraint_mask) if match] + restraint_idxs = ( + receptor.select(config.restraints.receptor_query) + receptor_start_idx + ) + _LOGGER.info(f"receptor restrained idxs={restraint_idxs}") _apply_receptor_restraints( - system, config.restraints, {i: topology.positions[i] for i in restraint_idxs} + system, config.restraints, {i: topology.xyz[i] for i in restraint_idxs} ) + femto.md.utils.openmm.assign_force_groups(system) return topology, system diff --git a/femto/fe/atm/_utils.py b/femto/fe/atm/_utils.py index 4432808..0da14ea 100644 --- a/femto/fe/atm/_utils.py +++ b/femto/fe/atm/_utils.py @@ -3,10 +3,10 @@ import copy import typing +import mdtop import openmm import openmm.app import openmm.unit -import parmed import femto.md.constants import femto.md.rest @@ -51,7 +51,7 @@ def create_state_dicts( def create_atm_force( - topology: parmed.Structure, + topology: mdtop.Topology, soft_core: "femto.fe.atm.ATMSoftCore", offset: openmm.unit.Quantity, ) -> openmm.ATMForce: @@ -107,7 +107,7 @@ def create_atm_force( def add_atm_force( system: openmm.System, - topology: parmed.Structure, + topology: mdtop.Topology, soft_core: "femto.fe.atm.ATMSoftCore", offset: openmm.unit.Quantity, ): @@ -120,8 +120,6 @@ def add_atm_force( soft_core: The soft core parameters to use. offset: The ligand offset. """ - import femto.fe.atm._utils - nonbonded_idxs = [ i for i, force in enumerate(system.getForces()) diff --git a/femto/fe/inputs.py b/femto/fe/inputs.py index 2f8b9ef..b3a2fb1 100644 --- a/femto/fe/inputs.py +++ b/femto/fe/inputs.py @@ -122,7 +122,7 @@ def _find_receptor( receptor_paths = [ path - for suffix in ("pdb", "rst7", "mol2") + for suffix in ("pdb", "sdf", "mol2") for path in (root_dir / "proteins").glob(f"*/protein.{suffix}") ] @@ -133,7 +133,7 @@ def _find_receptor( raise RuntimeError("Expected to find exactly one receptor file.") receptor_coords = receptor_paths[0] - receptor_params = receptor_coords.with_suffix(".parm7") + receptor_params = receptor_coords.with_suffix(".xml") if not receptor_params.exists(): receptor_params = None @@ -155,9 +155,9 @@ def _find_edge(root_dir: pathlib.Path, edge: femto.fe.config.Edge) -> Edge: ligand_1_coords = root_dir / "forcefield" / edge.ligand_1 / "vacuum.mol2" if not ligand_1_coords.exists(): - ligand_1_coords = ligand_1_coords.with_suffix(".rst7") + ligand_1_coords = ligand_1_coords.with_suffix(".sdf") - ligand_1_params = root_dir / "forcefield" / edge.ligand_1 / "vacuum.parm7" + ligand_1_params = root_dir / "forcefield" / edge.ligand_1 / "vacuum.xml" if not ligand_1_coords.exists() or not ligand_1_params.exists(): raise RuntimeError(f"Could not find files for {edge.ligand_1}") @@ -169,10 +169,10 @@ def _find_edge(root_dir: pathlib.Path, edge: femto.fe.config.Edge) -> Edge: if edge.ligand_2 is not None: ligand_2_coords = root_dir / "forcefield" / edge.ligand_2 / "vacuum.mol2" - ligand_2_params = root_dir / "forcefield" / edge.ligand_2 / "vacuum.parm7" + ligand_2_params = root_dir / "forcefield" / edge.ligand_2 / "vacuum.xml" if not ligand_2_coords.exists(): - ligand_2_coords = ligand_2_coords.with_suffix(".rst7") + ligand_2_coords = ligand_2_coords.with_suffix(".sdf") if not ligand_2_coords.exists() or not ligand_2_params.exists(): raise RuntimeError(f"Could not find files for {edge.ligand_2}") diff --git a/femto/fe/reference.py b/femto/fe/reference.py index 2adb0c0..e54cb39 100644 --- a/femto/fe/reference.py +++ b/femto/fe/reference.py @@ -5,13 +5,12 @@ import logging import typing +import mdtop import mdtraj import networkx import numpy import openmm.unit -import parmed -import scipy.spatial -import scipy.spatial.distance +import scipy import femto.fe.config import femto.md.utils.geometry @@ -122,14 +121,13 @@ def _are_collinear( def queries_to_idxs( - structure: parmed.Structure, queries: typing.Iterable[str] + topology: mdtop.Topology, queries: typing.Iterable[str] ) -> tuple[int, ...]: - """Find the indices of those atoms matched by a set of AMBER style reference atom - queries. + """Find the indices of those atoms matched by a set of atom queries. Args: - structure: The ligand to query. - queries: The amber style selection queries. + topology: The ligand to query. + queries: The atom selection queries. Returns: The indices of the matched atoms. @@ -137,13 +135,11 @@ def queries_to_idxs( ref_idxs = [] for query in queries: - mask = parmed.amber.AmberMask(structure, query).Selection() - mask_idxs = tuple(i for i, matches in enumerate(mask) if matches) + mask_idxs = topology.select(query) if len(mask_idxs) != 1: raise ValueError( - f"{query} matched {len(mask_idxs)} atoms while exactly 1 atom was " - f"expected." + f"{query} matched {len(mask_idxs)} atoms. exactly 1 atom was expected." ) ref_idxs.extend(mask_idxs) @@ -152,7 +148,7 @@ def queries_to_idxs( def _create_ligand_queries_baumann( - ligand: parmed.Structure, snapshots: list[openmm.unit.Quantity] | None + ligand: mdtop.Topology, snapshots: list[openmm.unit.Quantity] | None ) -> tuple[str, str, str]: """Creates AMBER style masks for selecting three atoms from a ligand for use in Boresch-likes restraints using the method described by Baumann et al. @@ -162,10 +158,12 @@ def _create_ligand_queries_baumann( calculations using a Separated Topologies approach." (2023). """ + atoms = ligand.atoms + ligand_graph = networkx.from_edgelist( - (bond.atom1.idx, bond.atom2.idx) + (bond.idx_1, bond.idx_2) for bond in ligand.bonds - if bond.atom1.atomic_number != 1 and bond.atom2.atomic_number != 1 + if atoms[bond.idx_1].atomic_num != 1 and atoms[bond.idx_2].atomic_num != 1 ) all_paths = [ @@ -183,7 +181,7 @@ def _create_ligand_queries_baumann( if len(cycles) >= 1 and snapshots is not None: ligand_trajectory = mdtraj.Trajectory( [snapshot.value_in_unit(openmm.unit.nanometers) for snapshot in snapshots], - ligand.topology, + ligand.to_openmm(), ) ligand_trajectory.superpose(ligand_trajectory) @@ -193,7 +191,7 @@ def _create_ligand_queries_baumann( if len(cycles) >= 1: open_list = [atom_idx for cycle in cycles for atom_idx in cycle] else: - open_list = [atom.idx for atom in ligand.atoms if atom.atomic_number != 1] + open_list = [atom.index for atom in ligand.atoms if atom.atomic_num != 1] distances = [path_lengths[(center_idx, atom_idx)] for atom_idx in open_list] closest_idx = open_list[numpy.argmin(distances)] @@ -213,7 +211,11 @@ def _create_ligand_queries_baumann( for _, idx in sorted(zip(distances, open_list, strict=True)) if idx != closest_idx ] - ref_masks = (f"@{open_list[0] + 1}", f"@{closest_idx + 1}", f"@{open_list[1] + 1}") + ref_masks = ( + f"index {open_list[0] + 1}", + f"index {closest_idx + 1}", + f"index {open_list[1] + 1}", + ) # TODO: check if the reference atoms are co-linear # TODO: handle the unhappy paths of not enough atoms are found. @@ -222,13 +224,13 @@ def _create_ligand_queries_baumann( def _create_ligand_queries_chen( - ligand_1: parmed.Structure, ligand_2: parmed.Structure + ligand_1: mdtop.Topology, ligand_2: mdtop.Topology ) -> tuple[tuple[str, str, str], tuple[str, str, str]]: - """Creates AMBER style masks for selecting three atoms from a ligand for use in + """Creates selection masks for selecting three atoms from a ligand for use in Boresch-likes restraints using the approach defined in ``siflow`` by Erik Chen.""" - coords_1 = numpy.array(ligand_1.coordinates) - coords_2 = numpy.array(ligand_2.coordinates) + coords_1 = ligand_1.xyz.value_in_unit(openmm.unit.angstrom) + coords_2 = ligand_2.xyz.value_in_unit(openmm.unit.angstrom) distances = scipy.spatial.distance_matrix(coords_1, coords_2) @@ -243,8 +245,8 @@ def _create_ligand_queries_chen( counter += 1 if ( - ligand_1.atoms[idx_1].atomic_number == 1 - or ligand_2.atoms[idx_2].atomic_number == 1 + ligand_1.atoms[idx_1].atomic_num == 1 + or ligand_2.atoms[idx_2].atomic_num == 1 ): continue @@ -264,17 +266,17 @@ def _create_ligand_queries_chen( raise RuntimeError("Could not find three non-co-linear reference atoms.") return ( - tuple(f"@{ref_atoms_1[i] + 1}" for i in range(3)), - tuple(f"@{ref_atoms_2[i] + 1}" for i in range(3)), + tuple(f"index {ref_atoms_1[i] + 1}" for i in range(3)), + tuple(f"index {ref_atoms_2[i] + 1}" for i in range(3)), ) def _create_ligand_queries( - ligand_1: parmed.Structure, - ligand_2: parmed.Structure | None, + ligand_1: mdtop.Topology, + ligand_2: mdtop.Topology | None, method: femto.fe.config.LigandReferenceMethod, ) -> tuple[tuple[str, str, str], tuple[str, str, str] | None]: - """Creates AMBER style masks for selecting three atoms from a ligand for use in + """Creates selection masks for selecting three atoms from a ligand for use in Boresch-likes alignment restraints. Args: @@ -283,7 +285,7 @@ def _create_ligand_queries( method: The method to use to select the reference atoms. Returns: - The AMBER style queries that will select the reference atoms of the first and + The atom queries that will select the reference atoms of the first and second ligands respectively. """ @@ -306,8 +308,8 @@ def _create_ligand_queries( def select_ligand_idxs( - ligand_1: parmed.amber.AmberParm, - ligand_2: parmed.amber.AmberParm | None, + ligand_1: mdtop.Topology, + ligand_2: mdtop.Topology | None, method: femto.fe.config.LigandReferenceMethod, ligand_1_queries: tuple[str, str, str] | None = None, ligand_2_queries: tuple[str, str, str] | None = None, @@ -323,15 +325,15 @@ def select_ligand_idxs( ligand_1: The first ligand. ligand_2: The second ligand. method: The method to use to select the reference atoms if none are specified. - ligand_1_queries: Three (optional) AMBER style queries to use to manually + ligand_1_queries: Three (optional) selection queries to use to manually select atoms from the first ligand. - ligand_2_queries: Three (optional) AMBER style queries to use to manually + ligand_2_queries: Three (optional) selection queries to use to manually select atoms from the second ligand Returns: The indices of the first and second ligand respectively. No offset is applied - to the second ligand indices so a query of ``"@1"`` would yield ``0`` rather - than ``n_ligand_1_atoms``. + to the second ligand indices so a query of ``"idx. 1"`` would yield ``0`` + rather than ``n_ligand_1_atoms``. """ if ligand_1_queries is None or (ligand_2 is not None and ligand_2_queries is None): _LOGGER.info("selecting ligand reference atoms") @@ -347,11 +349,9 @@ def select_ligand_idxs( _LOGGER.info(f"ligand 2 ref queries={ligand_2_queries}") ligand_1_idxs = queries_to_idxs(ligand_1, ligand_1_queries) - _LOGGER.info(f"ligand 1 ref idxs={ligand_1_idxs}") if ligand_2 is not None: ligand_2_idxs = queries_to_idxs(ligand_2, ligand_2_queries) - _LOGGER.info(f"ligand 2 ref idxs={ligand_2_idxs}") else: ligand_2_idxs = None @@ -359,8 +359,7 @@ def select_ligand_idxs( def _filter_receptor_atoms( - receptor: mdtraj.Trajectory, - ligand: mdtraj.Trajectory, + topology: mdtraj.Trajectory, ligand_ref_idx: int, min_helix_size: int = 8, min_sheet_size: int = 8, @@ -373,8 +372,7 @@ def _filter_receptor_atoms( outlined by Baumann et al. Args: - receptor: The receptor structure. - ligand: The ligand structure. + topology: The system topology. ligand_ref_idx: The index of the first reference ligand atom. min_helix_size: The minimum number of residues that have to be in an alpha-helix for it to be considered stable. @@ -397,8 +395,8 @@ def _filter_receptor_atoms( assert min_helix_size >= 7, "helices must be at least 7 residues long" assert min_sheet_size >= 7, "sheets must be at least 7 residues long" - backbone_idxs = receptor.top.select("protein and (backbone or name CB)") - backbone: mdtraj.Trajectory = receptor.atom_slice(backbone_idxs) + backbone_idxs = topology.top.select("protein and (backbone or name CB)") + backbone: mdtraj.Trajectory = topology.atom_slice(backbone_idxs) structure = mdtraj.compute_dssp(backbone, simplified=True).tolist()[0] @@ -442,7 +440,7 @@ def _filter_receptor_atoms( ] distances = scipy.spatial.distance.cdist( - backbone.xyz[0, rigid_backbone_idxs, :], ligand.xyz[0, [ligand_ref_idx], :] + backbone.xyz[0, rigid_backbone_idxs, :], topology.xyz[0, [ligand_ref_idx], :] ) minimum_distance = minimum_distance.value_in_unit(openmm.unit.nanometer) @@ -455,28 +453,16 @@ def _filter_receptor_atoms( def _is_valid_r1( - receptor: mdtraj.Trajectory, - receptor_idx: int, - ligand: mdtraj.Trajectory, - ligand_ref_idxs: tuple[int, int, int], + topology: mdtraj.Trajectory, r1: int, l1: int, l2: int, l3: int ) -> bool: """Check whether a given receptor atom would be a valid 'R1' atom given the following criteria: * L2,L1,R1 angle not 'close' to 0 or 180 degrees * L3,L2,L1,R1 dihedral between -150 and 150 degrees - - Args: - receptor: The receptor structure. - receptor_idx: The index of the receptor atom to check. - ligand: The ligand structure. - ligand_ref_idxs: The three reference ligand atoms. """ - coords = numpy.concatenate([ligand.xyz, receptor.xyz], axis=1) - - l1, l2, l3 = ligand_ref_idxs - r1 = receptor_idx + ligand.n_atoms + coords = topology.xyz if _are_collinear(coords, (r1, l1, l2, l3)): return False @@ -491,11 +477,7 @@ def _is_valid_r1( def _is_valid_r2( - receptor: mdtraj.Trajectory, - receptor_idx: int, - receptor_ref_idx_1: int, - ligand: mdtraj.Trajectory, - ligand_ref_idxs: tuple[int, int, int], + topology: mdtraj.Trajectory, r1: int, r2: int, l1: int, l2: int ) -> bool: """Check whether a given receptor atom would be a valid 'R2' atom given the following criteria: @@ -504,19 +486,9 @@ def _is_valid_r2( * R2,R1,L1,L2 are not collinear * R2,R1,L1 angle not 'close' to 0 or 180 degrees * R2,R1,L1,L2 dihedral between -150 and 150 degrees - - Args: - receptor: The receptor structure. - receptor_idx: The index of the receptor atom to check. - receptor_ref_idx_1: The index of the first receptor reference atom. - ligand: The ligand structure. - ligand_ref_idxs: The three reference ligand atoms. """ - coords = numpy.concatenate([ligand.xyz, receptor.xyz], axis=1) - - l1, l2, l3 = ligand_ref_idxs - r1, r2 = receptor_ref_idx_1 + ligand.n_atoms, receptor_idx + ligand.n_atoms + coords = topology.xyz if r1 == r2: return False @@ -537,36 +509,16 @@ def _is_valid_r2( def _is_valid_r3( - receptor: mdtraj.Trajectory, - receptor_idx: int, - receptor_ref_idx_1: int, - receptor_ref_idx_2: int, - ligand: mdtraj.Trajectory, - ligand_ref_idxs: tuple[int, int, int], + topology: mdtraj.Trajectory, r1: int, r2: int, r3: int, l1: int ) -> bool: """Check whether a given receptor atom would be a valid 'R3' atom given the following criteria: * R1,R2,R3,L1 are not collinear * R3,R2,R1,L1 dihedral between -150 and 150 degrees - - Args: - receptor: The receptor structure. - receptor_idx: The index of the receptor atom to check. - receptor_ref_idx_1: The index of the first receptor reference atom. - receptor_ref_idx_2: The index of the second receptor reference atom. - ligand: The ligand structure. - ligand_ref_idxs: The three reference ligand atoms. """ - coords = numpy.concatenate([ligand.xyz, receptor.xyz], axis=1) - - l1, l2, l3 = ligand_ref_idxs - r1, r2, r3 = ( - receptor_ref_idx_1 + ligand.n_atoms, - receptor_ref_idx_2 + ligand.n_atoms, - receptor_idx + ligand.n_atoms, - ) + coords = topology.xyz if len({r1, r2, r3}) != 3: return False @@ -580,10 +532,8 @@ def _is_valid_r3( return True -def _structure_to_mdtraj(structure: parmed.Structure) -> mdtraj.Trajectory: - coords = (structure.coordinates * openmm.unit.angstrom).value_in_unit( - openmm.unit.nanometer - ) +def _topology_to_mdtraj(topology: mdtop.Topology) -> mdtraj.Trajectory: + coords = topology.xyz.value_in_unit(openmm.unit.nanometer) # if the structure has no box vectors defined, or the box vectors are smaller than # the structure, we will use the structure's coordinates to define the box so we @@ -595,8 +545,8 @@ def _structure_to_mdtraj(structure: parmed.Structure) -> mdtraj.Trajectory: box_from_coords = numpy.diag(box_delta) box = ( - numpy.array(structure.box_vectors.value_in_unit(openmm.unit.nanometer)) - if structure.box_vectors is not None + numpy.array(topology.box.value_in_unit(openmm.unit.nanometer)) + if topology.box is not None else None ) @@ -604,7 +554,7 @@ def _structure_to_mdtraj(structure: parmed.Structure) -> mdtraj.Trajectory: box = box_from_coords trajectory = mdtraj.Trajectory( - coords, mdtraj.Topology.from_openmm(structure.topology) + coords, mdtraj.Topology.from_openmm(topology.to_openmm()) ) trajectory.unitcell_vectors = box.reshape(1, 3, 3) @@ -612,8 +562,7 @@ def _structure_to_mdtraj(structure: parmed.Structure) -> mdtraj.Trajectory: def select_receptor_idxs( - receptor: parmed.Structure | mdtraj.Trajectory, - ligand: parmed.Structure | mdtraj.Trajectory, + topology: mdtop.Topology | mdtraj.Trajectory, ligand_ref_idxs: tuple[int, int, int], ) -> tuple[int, int, int]: """Select possible protein atoms for Boresch-style restraints using the method @@ -624,30 +573,22 @@ def select_receptor_idxs( calculations using a Separated Topologies approach." (2023). Args: - receptor: The receptor structure. - ligand: The ligand structure. + topology: The topology containing the receptor and ligands. ligand_ref_idxs: The indices of the three ligands atoms that will be restrained. Returns: The indices of the three atoms to use for the restraint """ - if not (isinstance(receptor, type(ligand)) or isinstance(ligand, type(receptor))): - raise ValueError("receptor and ligand must be the same type") - if isinstance(receptor, parmed.Structure) and isinstance(ligand, parmed.Structure): - receptor = _structure_to_mdtraj(receptor) - ligand = _structure_to_mdtraj(ligand) + if isinstance(topology, mdtop.Topology): + topology = _topology_to_mdtraj(topology) - assert ( - receptor.n_frames == ligand.n_frames - ), "receptor and ligand must have the same number of frames" + receptor_idxs = _filter_receptor_atoms(topology, ligand_ref_idxs[0]) - receptor_idxs = _filter_receptor_atoms(receptor, ligand, ligand_ref_idxs[0]) + l1, l2, l3 = ligand_ref_idxs valid_r1_idxs = [ - idx - for idx in receptor_idxs - if _is_valid_r1(receptor, idx, ligand, ligand_ref_idxs) + r1 for r1 in receptor_idxs if _is_valid_r1(topology, r1, l1, l2, l3) ] found_r1, found_r2 = next( @@ -655,7 +596,7 @@ def select_receptor_idxs( (r1, r2) for r1 in valid_r1_idxs for r2 in receptor_idxs - if _is_valid_r2(receptor, r2, r1, ligand, ligand_ref_idxs) + if _is_valid_r2(topology, r1, r2, l1, l2) ), None, ) @@ -664,9 +605,7 @@ def select_receptor_idxs( raise ValueError("could not find valid R1 / R2 atoms") valid_r3_idxs = [ - idx - for idx in receptor_idxs - if _is_valid_r3(receptor, idx, found_r1, found_r2, ligand, ligand_ref_idxs) + r3 for r3 in receptor_idxs if _is_valid_r3(topology, found_r1, found_r2, r3, l1) ] if len(valid_r3_idxs) == 0: @@ -674,18 +613,18 @@ def select_receptor_idxs( r3_distances_per_frame = [] - for frame_r, frame_l in zip(receptor.xyz, ligand.xyz, strict=True): + for frame in topology.xyz: r3_r_distances = scipy.spatial.distance.cdist( - frame_r[valid_r3_idxs, :], frame_r[[found_r1, found_r2], :] + frame[valid_r3_idxs, :], frame[[found_r1, found_r2], :] ) r3_l_distances = scipy.spatial.distance.cdist( - frame_r[valid_r3_idxs, :], frame_l[[ligand_ref_idxs[0]], :] + frame[valid_r3_idxs, :], frame[[ligand_ref_idxs[0]], :] ) r3_distances_per_frame.append(numpy.hstack([r3_r_distances, r3_l_distances])) # chosen to match the SepTop reference implementation at commit 3705ba5 - max_distance = 0.8 * (receptor.unitcell_lengths.mean(axis=0).min(axis=-1) / 2) + max_distance = 0.8 * (topology.unitcell_lengths.mean(axis=0).min(axis=-1) / 2) r3_distances_avg = numpy.stack(r3_distances_per_frame).mean(axis=0) @@ -701,9 +640,8 @@ def select_receptor_idxs( def check_receptor_idxs( - receptor: parmed.Structure | mdtraj.Trajectory, + topology: mdtop.Topology | mdtraj.Trajectory, receptor_idxs: tuple[int, int, int], - ligand: parmed.Structure | mdtraj.Trajectory, ligand_ref_idxs: tuple[int, int, int], ) -> bool: """Check if the specified receptor atoms meet the criteria for use in Boresch-style @@ -714,46 +652,39 @@ def check_receptor_idxs( calculations using a Separated Topologies approach." (2023). Args: - receptor: The receptor structure. + topology: The system topology. receptor_idxs: The indices of the three receptor atoms that will be restrained. - ligand: The ligand structure. ligand_ref_idxs: The indices of the three ligand atoms that will be restrained. Returns: True if the atoms meet the criteria, False otherwise. """ - if not (isinstance(receptor, type(ligand)) or isinstance(ligand, type(receptor))): - raise ValueError("receptor and ligand must be the same type") - - if isinstance(receptor, parmed.Structure) and isinstance(ligand, parmed.Structure): - receptor = _structure_to_mdtraj(receptor) - ligand = _structure_to_mdtraj(ligand) - assert ( - receptor.n_frames == ligand.n_frames - ), "receptor and ligand must have the same number of frames" + if isinstance(topology, mdtop.Topology): + topology = _topology_to_mdtraj(topology) r1, r2, r3 = receptor_idxs + l1, l2, l3 = ligand_ref_idxs - is_valid_r1 = _is_valid_r1(receptor, r1, ligand, ligand_ref_idxs) - is_valid_r2 = _is_valid_r2(receptor, r2, r1, ligand, ligand_ref_idxs) - is_valid_r3 = _is_valid_r3(receptor, r3, r1, r2, ligand, ligand_ref_idxs) + is_valid_r1 = _is_valid_r1(topology, r1, l1, l2, l3) + is_valid_r2 = _is_valid_r2(topology, r1, r2, l1, l2) + is_valid_r3 = _is_valid_r3(topology, r1, r2, r3, l1) r3_distances_per_frame = [ scipy.spatial.distance.cdist(frame[[r3], :], frame[[r1, r2], :]) - for frame in receptor.xyz + for frame in topology.xyz ] r3_distance_avg = numpy.stack(r3_distances_per_frame).mean(axis=0) - max_distance = 0.8 * (receptor.unitcell_lengths[-1][0] / 2) + max_distance = 0.8 * (topology.unitcell_lengths[-1][0] / 2) is_valid_distance = r3_distance_avg.max(axis=-1) < max_distance return is_valid_r1 and is_valid_r2 and is_valid_r3 and is_valid_distance def select_protein_cavity_atoms( - protein: parmed.Structure, - ligands: list[parmed.Structure], + protein: mdtop.Topology, + ligands: list[mdtop.Topology], cutoff: openmm.unit.Quantity, ) -> str: """Select the alpha carbon atoms that define the binding cavity of the protein based @@ -772,27 +703,27 @@ def select_protein_cavity_atoms( for residue in protein.residues: if ( - sorted(a.element for a in residue.atoms) == [1, 1, 8] + sorted(a.atomic_num for a in residue.atoms) == [1, 1, 8] # a bit of a hack to check for ions. or len(residue.atoms) == 1 ): continue - c_alpha_idxs = [a.idx for a in residue.atoms if a.name == "CA"] + c_alpha_idxs = [a.index for a in residue.atoms if a.name == "CA"] if len(c_alpha_idxs) < 1: continue ref_atoms.extend(c_alpha_idxs) - protein_coords = numpy.array(protein.coordinates)[ref_atoms, :] + protein_coords = protein.xyz.value_in_unit(openmm.unit.angstrom)[ref_atoms, :] cutoff = cutoff.value_in_unit(openmm.unit.angstrom) is_reference = numpy.array([False] * len(ref_atoms)) for ligand in ligands: - ligand_coords = numpy.array(ligand.coordinates) + ligand_coords = ligand.xyz.value_in_unit(openmm.unit.angstrom) distances = scipy.spatial.distance_matrix(protein_coords, ligand_coords) is_reference = is_reference | (distances < cutoff).any(axis=1) @@ -802,7 +733,7 @@ def select_protein_cavity_atoms( if n_atoms < 1: raise RuntimeError("Could not find the protein binding site reference atoms.") - ref_mask = "@" + ",".join( + ref_mask = "index " + "+".join( str(i + 1) for i, keep in zip(ref_atoms, is_reference, strict=True) if keep ) return ref_mask diff --git a/femto/fe/septop/__init__.py b/femto/fe/septop/__init__.py index a5e11f6..fcc99b8 100644 --- a/femto/fe/septop/__init__.py +++ b/femto/fe/septop/__init__.py @@ -1,49 +1,48 @@ """Automated BFE calculations using the seperated topology method""" +from femto.fe.septop._analyze import compute_ddg from femto.fe.septop._config import ( - DEFAULT_LAMBDA_VDW_1_COMPLEX, - DEFAULT_LAMBDA_CHARGES_1_COMPLEX, - DEFAULT_LAMBDA_VDW_2_COMPLEX, - DEFAULT_LAMBDA_CHARGES_2_COMPLEX, + DEFAULT_BORESCH_K_DISTANCE, + DEFAULT_BORESCH_K_THETA, + DEFAULT_COMPLEX_RESTRAINTS, + DEFAULT_EQUILIBRATE_INTEGRATOR, + DEFAULT_EQUILIBRATE_RESTRAINTS, DEFAULT_LAMBDA_BORESCH_LIGAND_1, DEFAULT_LAMBDA_BORESCH_LIGAND_2, - DEFAULT_LAMBDA_VDW_1_SOLUTION, + DEFAULT_LAMBDA_CHARGES_1_COMPLEX, DEFAULT_LAMBDA_CHARGES_1_SOLUTION, - DEFAULT_LAMBDA_VDW_2_SOLUTION, + DEFAULT_LAMBDA_CHARGES_2_COMPLEX, DEFAULT_LAMBDA_CHARGES_2_SOLUTION, - DEFAULT_BORESCH_K_DISTANCE, - DEFAULT_BORESCH_K_THETA, + DEFAULT_LAMBDA_VDW_1_COMPLEX, + DEFAULT_LAMBDA_VDW_1_SOLUTION, + DEFAULT_LAMBDA_VDW_2_COMPLEX, + DEFAULT_LAMBDA_VDW_2_SOLUTION, DEFAULT_RESTRAINT_MASK, - DEFAULT_EQUILIBRATE_INTEGRATOR, - DEFAULT_EQUILIBRATE_RESTRAINTS, - DEFAULT_COMPLEX_RESTRAINTS, DEFAULT_SOLUTION_RESTRAINTS, SepTopComplexRestraints, - SepTopSolutionRestraints, - SepTopSetupStage, - SepTopStates, + SepTopConfig, SepTopEquilibrateStage, - SepTopSamplingStage, SepTopPhaseConfig, - SepTopConfig, + SepTopSamplingStage, + SepTopSetupStage, + SepTopSolutionRestraints, + SepTopStates, load_config, ) -from femto.fe.septop._utils import create_state_dicts -from femto.fe.septop._analyze import compute_ddg from femto.fe.septop._equilibrate import equilibrate_states +from femto.fe.septop._runner import ( + run_complex_phase, + run_solution_phase, + submit_network, +) from femto.fe.septop._sample import run_hremd from femto.fe.septop._setup import ( - setup_complex, - setup_solution, LAMBDA_BORESCH_LIGAND_1, LAMBDA_BORESCH_LIGAND_2, + setup_complex, + setup_solution, ) -from femto.fe.septop._runner import ( - run_solution_phase, - run_complex_phase, - submit_network, -) - +from femto.fe.septop._utils import create_state_dicts __all__ = [ "compute_ddg", diff --git a/femto/fe/septop/_analyze.py b/femto/fe/septop/_analyze.py index bf2a243..4527e73 100644 --- a/femto/fe/septop/_analyze.py +++ b/femto/fe/septop/_analyze.py @@ -11,6 +11,7 @@ if typing.TYPE_CHECKING: import pandas + import femto.fe.septop @@ -143,6 +144,7 @@ def compute_ddg( A pandas DataFrame containing the total binding free energy and its components. """ import pandas + import femto.fe.septop samples = { diff --git a/femto/fe/septop/_cli.py b/femto/fe/septop/_cli.py index c8aef0f..08fe682 100644 --- a/femto/fe/septop/_cli.py +++ b/femto/fe/septop/_cli.py @@ -193,13 +193,12 @@ def _run_solution_cli( femto.fe.septop.run_solution_phase( config, ligand_1_coords, - ligand_1_params, ligand_2_coords, - ligand_2_params, output_dir, report_dir, ligand_1_ref_atoms, ligand_2_ref_atoms, + [ligand_1_params] + ([] if ligand_2_params is None else [ligand_2_params]), ) @@ -283,16 +282,17 @@ def _run_complex_cli( femto.fe.septop.run_complex_phase( config, ligand_1_coords, - ligand_1_params, ligand_2_coords, - ligand_2_params, receptor_coords, - receptor_params, + [], output_dir, report_dir, ligand_1_ref_atoms, ligand_2_ref_atoms, receptor_ref_atoms, + [ligand_1_params] + + ([] if ligand_2_params is None else [ligand_2_params]) + + ([] if receptor_params is None else [receptor_params]), ) diff --git a/femto/fe/septop/_config.py b/femto/fe/septop/_config.py index c4986ab..52cb0c8 100644 --- a/femto/fe/septop/_config.py +++ b/femto/fe/septop/_config.py @@ -52,7 +52,7 @@ DEFAULT_BORESCH_K_THETA = 20.0 * _KCAL_PER_RAD_SQR """The default force constant of the Boresch angle restraint.""" "" -DEFAULT_RESTRAINT_MASK = "!(:WAT,CL,NA,K) & !@/H" +DEFAULT_RESTRAINT_MASK = "not (water or ion or elem H)" """The default Amber style selection mask to apply position restraints to.""" DEFAULT_EQUILIBRATE_INTEGRATOR = femto.md.config.LangevinIntegrator( @@ -71,10 +71,15 @@ class SepTopComplexRestraints(femto.md.config.BoreschRestraint): """Configure the restraints to apply in the complex phase.""" - scale_k_angle_a: typing.Literal[True] = pydantic.Field( + scale_k_angle_a: bool = pydantic.Field( True, - description="Whether to scale the force constant for the r2, r3, and l1 angle " - "based upon the *initial* distance between r3 and l1.", + description="Whether to scale the force constant for the P2, P1, and L1 angle " + "based on the *initial* distance between P1 and L1.", + ) + scale_k_angle_b: bool = pydantic.Field( + True, + description="Whether to scale the force constant for the P1, L1, and L2 angle " + "based on the *initial* distance between P1 and L1.", ) @@ -101,16 +106,11 @@ class SepTopSolutionRestraints(BaseModel): DEFAULT_SOLUTION_RESTRAINTS = SepTopSolutionRestraints() -class SepTopSetupStage(BaseModel): +class SepTopSetupStage(femto.md.config.Prepare): """Configure how the complex will be solvated and restrained prior to equilibration """ - solvent: femto.md.config.Solvent = pydantic.Field( - femto.md.config.Solvent(), - description="Control how the system should be solvated.", - ) - restraints: SepTopComplexRestraints | SepTopSolutionRestraints = pydantic.Field( ..., description="Control how the system should be restrained.", @@ -305,7 +305,10 @@ class SepTopConfig(BaseModel): ) solution: SepTopPhaseConfig = pydantic.Field( SepTopPhaseConfig( - setup=SepTopSetupStage(restraints=DEFAULT_SOLUTION_RESTRAINTS), + setup=SepTopSetupStage( + box_shape="cube", + restraints=DEFAULT_SOLUTION_RESTRAINTS, + ), states=SepTopStates( lambda_vdw_ligand_1=DEFAULT_LAMBDA_VDW_1_SOLUTION, lambda_charges_ligand_1=DEFAULT_LAMBDA_CHARGES_1_SOLUTION, diff --git a/femto/fe/septop/_equilibrate.py b/femto/fe/septop/_equilibrate.py index caf165e..0f49ae8 100644 --- a/femto/fe/septop/_equilibrate.py +++ b/femto/fe/septop/_equilibrate.py @@ -3,8 +3,8 @@ import logging import typing +import mdtop import openmm.unit -import parmed import femto.md.constants import femto.md.reporting @@ -19,7 +19,7 @@ def equilibrate_states( system: openmm.System, - topology: parmed.Structure, + topology: mdtop.Topology, states: "femto.fe.septop.SepTopStates", config: "femto.fe.septop.SepTopEquilibrateStage", platform: femto.md.constants.OpenMMPlatform, diff --git a/femto/fe/septop/_runner.py b/femto/fe/septop/_runner.py index 29f7ec6..3252ec5 100644 --- a/femto/fe/septop/_runner.py +++ b/femto/fe/septop/_runner.py @@ -5,14 +5,14 @@ import pathlib import typing +import mdtop import openmm -import parmed import femto.fe.inputs import femto.fe.utils.queue import femto.md.constants +import femto.md.prepare import femto.md.reporting -import femto.md.system import femto.md.utils.mpi if typing.TYPE_CHECKING: @@ -22,65 +22,65 @@ @femto.md.utils.mpi.run_on_rank_zero def _prepare_solution_phase( config: "femto.fe.septop.SepTopPhaseConfig", - ligand_1_coords: pathlib.Path, - ligand_1_params: pathlib.Path, - ligand_2_coords: pathlib.Path | None, - ligand_2_params: pathlib.Path | None, - ligand_1_ref_atoms: tuple[str, str, str] | None = None, - ligand_2_ref_atoms: tuple[str, str, str] | None = None, -) -> tuple[parmed.Structure, openmm.System]: - ligand_1, ligand_2 = femto.md.system.load_ligands( - ligand_1_coords, ligand_1_params, ligand_2_coords, ligand_2_params - ) + ligand_1_path: pathlib.Path, + ligand_2_path: pathlib.Path | None, + ligand_1_ref_atoms: tuple[str, str, str] | None, + ligand_2_ref_atoms: tuple[str, str, str] | None, + extra_params: list[pathlib.Path] | None, +) -> tuple[mdtop.Topology, openmm.System]: + ligand_1, ligand_2 = femto.md.prepare.load_ligands(ligand_1_path, ligand_2_path) + return femto.fe.septop._setup.setup_solution( - config.setup, ligand_1, ligand_2, ligand_1_ref_atoms, ligand_2_ref_atoms + config.setup, + ligand_1, + ligand_2, + ligand_1_ref_atoms, + ligand_2_ref_atoms, + extra_params, ) @femto.md.utils.mpi.run_on_rank_zero def _prepare_complex_phase( config: "femto.fe.septop.SepTopPhaseConfig", - ligand_1_coords: pathlib.Path, - ligand_1_params: pathlib.Path, - ligand_2_coords: pathlib.Path | None, - ligand_2_params: pathlib.Path | None, - receptor_coords: pathlib.Path, - receptor_params: pathlib.Path | None, - ligand_1_ref_atoms: tuple[str, str, str] | None = None, - ligand_2_ref_atoms: tuple[str, str, str] | None = None, - receptor_ref_atoms: tuple[str, str, str] | None = None, -) -> tuple[parmed.Structure, openmm.System]: + receptor_path: pathlib.Path, + ligand_1_path: pathlib.Path, + ligand_2_path: pathlib.Path | None, + cofactor_paths: list[pathlib.Path] | None, + ligand_1_ref_atoms: tuple[str, str, str] | None, + ligand_2_ref_atoms: tuple[str, str, str] | None, + receptor_ref_atoms: tuple[str, str, str] | None, + extra_params: list[pathlib.Path] | None, +) -> tuple[mdtop.Topology, openmm.System]: import femto.fe.septop - receptor = femto.md.system.load_receptor( - receptor_coords, - receptor_params, - config.setup.solvent.tleap_sources, - ) + receptor = femto.md.prepare.load_receptor(receptor_path) + ligand_1, ligand_2 = femto.md.prepare.load_ligands(ligand_1_path, ligand_2_path) - ligand_1, ligand_2 = femto.md.system.load_ligands( - ligand_1_coords, ligand_1_params, ligand_2_coords, ligand_2_params - ) + cofactor_paths = cofactor_paths if cofactor_paths is not None else [] + cofactors = [femto.md.prepare.load_ligand(path, "COF") for path in cofactor_paths] return femto.fe.septop.setup_complex( config.setup, receptor, ligand_1, ligand_2, + cofactors, receptor_ref_atoms, ligand_1_ref_atoms, ligand_2_ref_atoms, + extra_params, ) @femto.md.utils.mpi.run_on_rank_zero def _cache_setup_outputs( - topology: parmed.Structure, + topology: mdtop.Topology, topology_path: pathlib.Path, system: openmm.System, system_path: pathlib.Path, ): - topology.save(str(topology_path), overwrite=True) + topology.to_file(topology_path) system_path.write_text(openmm.XmlSerializer.serialize(system)) @@ -92,7 +92,7 @@ def _cache_equilibrate_outputs(coords: list[openmm.State], paths: list[pathlib.P def _run_phase( config: "femto.fe.septop.SepTopPhaseConfig", - prepare_fn: typing.Callable[[], tuple[parmed.Structure, openmm.System]], + prepare_fn: typing.Callable[[], tuple[mdtop.Topology, openmm.System]], output_dir: pathlib.Path, report_dir: pathlib.Path | None = None, ): @@ -119,11 +119,9 @@ def _run_phase( if not topology_path.exists() or not system_path.exists(): topology, system = prepare_fn() - topology.symmetry = None # needed as attr is lost after pickling by MPI - _cache_setup_outputs(topology, topology_path, system, system_path) else: - topology = parmed.load_file(str(topology_path), structure=True) + topology = mdtop.Topology.from_file(topology_path) system = openmm.XmlSerializer.deserialize(system_path.read_text()) equilibrate_dir = output_dir / "_equilibrate" @@ -166,68 +164,63 @@ def _run_phase( def run_solution_phase( config: "femto.fe.septop.SepTopConfig", - ligand_1_coords: pathlib.Path, - ligand_1_params: pathlib.Path, - ligand_2_coords: pathlib.Path | None, - ligand_2_params: pathlib.Path | None, + ligand_1_path: pathlib.Path, + ligand_2_path: pathlib.Path | None, output_dir: pathlib.Path, report_dir: pathlib.Path | None = None, ligand_1_ref_atoms: tuple[str, str, str] | None = None, ligand_2_ref_atoms: tuple[str, str, str] | None = None, + extra_params: list[pathlib.Path] | None = None, ): """Run the solution phase of the SepTop calculation. Args: config: The configuration. - ligand_1_coords: The coordinates of the first ligand. - ligand_1_params: The parameters of the first ligand. - ligand_2_coords: The coordinates of the second ligand. - ligand_2_params: The parameters of the second ligand. + ligand_1_path: The path to the first ligand. + ligand_2_path: The path to the second ligand, if present. output_dir: The directory to store all outputs in. report_dir: The directory to store the report in. ligand_1_ref_atoms: The AMBER style query masks that select the first ligands reference atoms. ligand_2_ref_atoms: The AMBER style query masks that select the second ligands reference atoms. + extra_params: The paths to any extra parameter files (.xml, .parm) to use + when parameterizing the system. """ prepare_fn = functools.partial( _prepare_solution_phase, config.solution, - ligand_1_coords, - ligand_1_params, - ligand_2_coords, - ligand_2_params, + ligand_1_path, + ligand_2_path, ligand_1_ref_atoms, ligand_2_ref_atoms, + extra_params, ) _run_phase(config.solution, prepare_fn, output_dir, report_dir) def run_complex_phase( config: "femto.fe.septop.SepTopConfig", - ligand_1_coords: pathlib.Path, - ligand_1_params: pathlib.Path, - ligand_2_coords: pathlib.Path | None, - ligand_2_params: pathlib.Path | None, - receptor_coords: pathlib.Path, - receptor_params: pathlib.Path | None, + ligand_1_path: pathlib.Path, + ligand_2_path: pathlib.Path | None, + receptor_path: pathlib.Path, + cofactor_paths: list[pathlib.Path] | None, output_dir: pathlib.Path, report_dir: pathlib.Path | None = None, ligand_1_ref_atoms: tuple[str, str, str] | None = None, ligand_2_ref_atoms: tuple[str, str, str] | None = None, receptor_ref_atoms: tuple[str, str, str] | None = None, + extra_params: list[pathlib.Path] | None = None, ): """Run the complex phase of the SepTop calculation. Args: config: The configuration. - ligand_1_coords: The coordinates of the first ligand. - ligand_1_params: The parameters of the first ligand. - ligand_2_coords: The coordinates of the second ligand. - ligand_2_params: The parameters of the second ligand. - receptor_coords: The coordinates of the receptor. - receptor_params: The parameters of the receptor. + ligand_1_path: The path to the first ligand. + ligand_2_path: The path to the second ligand, if present. + receptor_path: The path to the receptor. + cofactor_paths: The paths to any cofactors. output_dir: The directory to store all outputs in. report_dir: The directory to store the logs / reports in. ligand_1_ref_atoms: The AMBER style query masks that select the first ligands @@ -236,20 +229,21 @@ def run_complex_phase( reference atoms. receptor_ref_atoms: The AMBER style query mask that selects the receptor atoms used to align the ligand. + extra_params: The paths to any extra parameter files (.xml, .parm) to use + when parameterizing the system. """ prepare_fn = functools.partial( _prepare_complex_phase, config.complex, - ligand_1_coords, - ligand_1_params, - ligand_2_coords, - ligand_2_params, - receptor_coords, - receptor_params, + receptor_path, + ligand_1_path, + ligand_2_path, + cofactor_paths, ligand_1_ref_atoms, ligand_2_ref_atoms, receptor_ref_atoms, + extra_params, ) _run_phase(config.complex, prepare_fn, output_dir, report_dir) diff --git a/femto/fe/septop/_sample.py b/femto/fe/septop/_sample.py index 2f3f9f0..83668db 100644 --- a/femto/fe/septop/_sample.py +++ b/femto/fe/septop/_sample.py @@ -6,15 +6,15 @@ import pathlib import typing +import mdtop import numpy import openmm -import parmed +import femto.fe.ddg import femto.md.constants import femto.md.hremd import femto.md.reporting import femto.md.utils.openmm -import femto.fe.ddg if typing.TYPE_CHECKING: import femto.fe.septop @@ -49,7 +49,7 @@ def _analyze( def run_hremd( system: openmm.System, - topology: parmed.Structure, + topology: mdtop.Topology, coords: list[openmm.State], states: "femto.fe.septop.SepTopStates", config: "femto.fe.septop.SepTopSamplingStage", diff --git a/femto/fe/septop/_setup.py b/femto/fe/septop/_setup.py index 682878f..b9cee83 100644 --- a/femto/fe/septop/_setup.py +++ b/femto/fe/septop/_setup.py @@ -2,20 +2,20 @@ import copy import logging +import pathlib +import mdtop import numpy.linalg.linalg import openmm import openmm.app import openmm.unit -import parmed import femto.fe.fep import femto.fe.reference import femto.md.constants +import femto.md.prepare import femto.md.rest import femto.md.restraints -import femto.md.solvate -import femto.md.system import femto.md.utils.openmm _LOGGER = logging.getLogger(__name__) @@ -32,22 +32,8 @@ second ligand.""" -def _offset_ligand(ligand: parmed.Structure, offset: openmm.unit.Quantity): - """Offsets the coordinates of the specified ligand by an offset. - - Args: - ligand: The ligand to offset. - offset: The amount to offset the ligand by. - """ - - for atom in ligand.atoms: - atom.xx += offset[0].value_in_unit(openmm.unit.angstrom) - atom.xy += offset[1].value_in_unit(openmm.unit.angstrom) - atom.xz += offset[2].value_in_unit(openmm.unit.angstrom) - - def _compute_ligand_offset( - ligand_1: parmed.Structure, ligand_2: parmed.Structure + ligand_1: mdtop.Topology, ligand_2: mdtop.Topology ) -> openmm.unit.Quantity: """Computes the amount to offset the second ligand by in the solution phase during RBFE calculations. @@ -60,20 +46,19 @@ def _compute_ligand_offset( The amount to offset the second ligand by. """ - ligand_1_radius = numpy.linalg.norm( - ligand_1.coordinates - ligand_1.coordinates.mean(axis=0), axis=1 - ).max() - ligand_2_radius = numpy.linalg.norm( - ligand_2.coordinates - ligand_2.coordinates.mean(axis=0), axis=1 - ).max() + xyz_1 = ligand_1.xyz.value_in_unit(openmm.unit.angstrom) + xyz_2 = ligand_2.xyz.value_in_unit(openmm.unit.angstrom) + + ligand_1_radius = numpy.linalg.norm(xyz_1 - xyz_1.mean(axis=0), axis=1).max() + ligand_2_radius = numpy.linalg.norm(xyz_2 - xyz_2.mean(axis=0), axis=1).max() ligand_distance = (ligand_1_radius + ligand_2_radius) * 1.5 - ligand_offset = ligand_1.coordinates.mean(0) - ligand_2.coordinates.mean(0) + ligand_offset = xyz_1.mean(0) - xyz_2.mean(0) ligand_offset[0] += ligand_distance return ligand_offset * _ANGSTROM def _apply_complex_restraints( - topology: parmed.Structure, + topology: mdtop.Topology, receptor_ref_idxs: tuple[int, int, int], ligand_ref_idxs: tuple[int, int, int], config: "femto.fe.septop.SepTopComplexRestraints", @@ -91,17 +76,22 @@ def _apply_complex_restraints( strength. """ - coords = topology.coordinates + coords = topology.xyz.value_in_unit(openmm.unit.angstrom) distance_0 = 5.0 # based on original SepTop implementation. distance_r1_l1 = numpy.linalg.norm( coords[receptor_ref_idxs[0]] - coords[ligand_ref_idxs[0]] ) - scale = (distance_r1_l1 / distance_0) ** 2 if config.scale_k_angle_a else 1.0 + + scale = (distance_r1_l1 / distance_0) ** 2 config_scaled = copy.deepcopy(config) - config_scaled.k_angle_a *= scale + + if config.scale_k_angle_a: + config_scaled.k_angle_a *= scale + if config.scale_k_angle_b: + config_scaled.k_angle_b *= scale force = femto.md.restraints.create_boresch_restraint( config_scaled, @@ -112,9 +102,11 @@ def _apply_complex_restraints( ) system.addForce(force) + femto.md.utils.openmm.assign_force_groups(system) + def _apply_solution_restraints( - topology: parmed.Structure, + topology: mdtop.Topology, ligand_1_ref_idx: int, ligand_2_ref_idx: int, config: "femto.fe.septop.SepTopSolutionRestraints", @@ -129,7 +121,7 @@ def _apply_solution_restraints( system: The OpenMM system to add the restraints to. """ - coords = topology.coordinates * openmm.unit.angstrom + coords = topology.xyz.value_in_unit(openmm.unit.angstrom) distance = numpy.linalg.norm(coords[ligand_1_ref_idx] - coords[ligand_2_ref_idx]) @@ -148,40 +140,43 @@ def _apply_solution_restraints( def _setup_system( config: "femto.fe.septop.SepTopSetupStage", - ligand_1: parmed.amber.AmberParm, - ligand_2: parmed.amber.AmberParm | None, - receptor: parmed.amber.AmberParm | None, + ligand_1: mdtop.Topology, + ligand_2: mdtop.Topology | None, + receptor: mdtop.Topology | None, + cofactors: list[mdtop.Topology] | None, ligand_1_ref_query: tuple[str, str, str] | None, ligand_2_ref_query: tuple[str, str, str] | None, - ligand_2_offset: openmm.unit.Quantity | None = None, + ligand_2_offset: openmm.unit.Quantity | None, + extra_params: list[pathlib.Path] | None, ) -> tuple[ - openmm.System, parmed.Structure, tuple[int, int, int], tuple[int, int, int] | None + openmm.System, mdtop.Topology, tuple[int, int, int], tuple[int, int, int] | None ]: - _LOGGER.info("solvating system") - topology = femto.md.solvate.solvate_system( - receptor, ligand_1, ligand_2, config.solvent, ligand_2_offset=ligand_2_offset - ) - - _LOGGER.info("creating OpenMM system") - system = topology.createSystem( - nonbondedMethod=openmm.app.PME, - nonbondedCutoff=0.9 * openmm.unit.nanometer, - constraints=openmm.app.HBonds, - rigidWater=True, + _LOGGER.info("preparing system.") + topology, system = femto.md.prepare.prepare_system( + receptor, + ligand_1, + ligand_2, + cofactors, + config, + ligand_1_offset=None, + ligand_2_offset=ligand_2_offset, + extra_params=extra_params, ) if config.apply_hmr: _LOGGER.info("applying HMR.") - femto.md.system.apply_hmr(system, topology, config.hydrogen_mass) + femto.md.prepare.apply_hmr(system, topology, config.hydrogen_mass) _LOGGER.info("applying FEP.") - ligand_1_idxs = set(range(len(ligand_1.atoms))) - ligand_2_idxs = None + ligand_1_idxs = topology.select(f"resn {femto.md.constants.LIGAND_1_RESIDUE_NAME}") + ligand_2_idxs = topology.select(f"resn {femto.md.constants.LIGAND_2_RESIDUE_NAME}") - if ligand_2 is not None: - ligand_2_idxs = {i + len(ligand_1_idxs) for i in range(len(ligand_2.atoms))} + ligand_1 = topology.subset(ligand_1_idxs) + ligand_2 = topology.subset(ligand_2_idxs) if ligand_2 is not None else None - femto.fe.fep.apply_fep(system, ligand_1_idxs, ligand_2_idxs, config.fep_config) + femto.fe.fep.apply_fep( + system, {*ligand_1_idxs}, {*ligand_2_idxs}, config.fep_config + ) if config.apply_rest: _LOGGER.info("applying REST2.") @@ -189,115 +184,142 @@ def _setup_system( solute_idxs = {*ligand_1_idxs, *({} if ligand_2 is None else ligand_2_idxs)} femto.md.rest.apply_rest(system, solute_idxs, config.rest_config) - ligand_ref_idxs = femto.fe.reference.select_ligand_idxs( + ligand_1_ref_idxs, ligand_2_ref_idxs = femto.fe.reference.select_ligand_idxs( ligand_1, ligand_2, "baumann", ligand_1_ref_query, ligand_2_ref_query ) - ligand_1_ref_idxs, ligand_2_ref_idxs = ligand_ref_idxs + ligand_1_ref_idxs = tuple(int(ligand_1_idxs[i]) for i in ligand_1_ref_idxs) + _LOGGER.info(f"ligand 1 ref idxs={ligand_1_idxs}") - if ligand_2_ref_idxs is not None: - ligand_2_ref_idxs = tuple(i + len(ligand_1.atoms) for i in ligand_2_ref_idxs) + if ligand_2 is not None and ligand_2_ref_idxs is not None: + ligand_2_ref_idxs = tuple(int(ligand_2_idxs[i]) for i in ligand_2_ref_idxs) + _LOGGER.info(f"ligand 2 ref idxs={ligand_2_idxs}") return system, topology, ligand_1_ref_idxs, ligand_2_ref_idxs def setup_complex( config: "femto.fe.septop.SepTopSetupStage", - receptor: parmed.amber.AmberParm, - ligand_1: parmed.amber.AmberParm, - ligand_2: parmed.amber.AmberParm | None, + receptor: mdtop.Topology, + ligand_1: mdtop.Topology, + ligand_2: mdtop.Topology | None, + cofactors: list[mdtop.Topology] | None, receptor_ref_query: tuple[str, str, str] | None = None, ligand_1_ref_query: tuple[str, str, str] | None = None, ligand_2_ref_query: tuple[str, str, str] | None = None, -) -> tuple[parmed.Structure, openmm.System]: + extra_params: list[pathlib.Path] | None = None, +) -> tuple[mdtop.Topology, openmm.System]: """Prepares a system ready for running the SepTop method. + Args: + config: The configuration for setting up the system. + receptor: The receptor. + ligand_1: The first ligand. + ligand_2: The second ligand if one is present. + cofactors: Any cofactors. + receptor_ref_query: The query to select the reference atoms of the receptor. + ligand_1_ref_query: The query to select the reference atoms of the first ligand. + ligand_2_ref_query: The query to select the reference atoms of the second ligand + extra_params: The paths to any extra parameter files (.xml, .parm) to use + when parameterizing the system. + Returns: The prepared topology and OpenMM system object. """ import femto.fe.septop - config = copy.deepcopy(config) - - restraint_config = config.restraints - - if not isinstance(restraint_config, femto.fe.septop.SepTopComplexRestraints): + if not isinstance(config.restraints, femto.fe.septop.SepTopComplexRestraints): raise ValueError("invalid restraint config") system, topology, ligand_1_ref_idxs, ligand_2_ref_idxs = _setup_system( - config, ligand_1, ligand_2, receptor, ligand_1_ref_query, ligand_2_ref_query + config, + ligand_1, + ligand_2, + receptor, + cofactors, + ligand_1_ref_query, + ligand_2_ref_query, + None, + extra_params, ) - ligands = [ligand_1] + ([] if ligand_2 is None else [ligand_2]) - idx_offset = sum(len(ligand.atoms) for ligand in ligands) - _LOGGER.info("applying restraints.") if receptor_ref_query is None: _LOGGER.info("selecting receptor reference atoms") receptor_ref_idxs_1 = femto.fe.reference.select_receptor_idxs( - receptor, ligand_1, ligand_1_ref_idxs + topology, ligand_1_ref_idxs ) - receptor_ref_idxs_2 = receptor_ref_idxs_1 - - # Remove the offset of ligand 2 atom indices. - # `ligand_2_ref_idxs` should be in the range 0:ligand_2.atoms to match - # `ligand_2` parmed.amber.AmberParm. - _ligand_2_ref_idxs = tuple(i - len(ligand_1.atoms) for i in ligand_2_ref_idxs) - if ligand_2 is not None and not femto.fe.reference.check_receptor_idxs( - receptor, receptor_ref_idxs_1, ligand_2, _ligand_2_ref_idxs - ): - _LOGGER.info("selecting alternate receptor reference atoms for ligand 2") - receptor_ref_idxs_2 = femto.fe.reference.select_receptor_idxs( - receptor, ligand_2, _ligand_2_ref_idxs - ) - else: receptor_ref_idxs_1 = femto.fe.reference.queries_to_idxs( receptor, receptor_ref_query ) - receptor_ref_idxs_2 = receptor_ref_idxs_1 + + ligand_idxs = topology.select( + f"resn {femto.md.constants.LIGAND_1_RESIDUE_NAME} | " + f"resn {femto.md.constants.LIGAND_2_RESIDUE_NAME}" + ) + receptor_start_idx = 0 if len(ligand_idxs) == 0 else (max(ligand_idxs) + 1) + receptor_ref_idxs_1 = tuple(v + receptor_start_idx for v in receptor_ref_idxs_1) _LOGGER.info(f"receptor ref idxs for ligand 1={receptor_ref_idxs_1}") - receptor_ref_idxs_1 = tuple(i + idx_offset for i in receptor_ref_idxs_1) _apply_complex_restraints( topology, receptor_ref_idxs_1, ligand_1_ref_idxs, - restraint_config, + config.restraints, system, LAMBDA_BORESCH_LIGAND_1, ) - if ligand_2 is not None: - _LOGGER.info(f"receptor ref idxs for ligand 2={receptor_ref_idxs_2}") - receptor_ref_idxs_2 = tuple(i + idx_offset for i in receptor_ref_idxs_2) + if ligand_2 is None: + return topology, system - _apply_complex_restraints( - topology, - receptor_ref_idxs_2, - ligand_2_ref_idxs, - restraint_config, - system, - LAMBDA_BORESCH_LIGAND_2, + receptor_ref_idxs_2 = receptor_ref_idxs_1 + + if receptor_ref_query is None and femto.fe.reference.check_receptor_idxs( + topology, receptor_ref_idxs_2, ligand_2_ref_idxs + ): + _LOGGER.info("selecting alternate receptor reference atoms for ligand 2") + + receptor_ref_idxs_2 = femto.fe.reference.select_receptor_idxs( + topology, ligand_2_ref_idxs ) - femto.md.utils.openmm.assign_force_groups(system) + _LOGGER.info(f"receptor ref idxs for ligand 2={receptor_ref_idxs_2}") + _apply_complex_restraints( + topology, + receptor_ref_idxs_2, + ligand_2_ref_idxs, + config.restraints, + system, + LAMBDA_BORESCH_LIGAND_2, + ) return topology, system def setup_solution( config: "femto.fe.septop.SepTopSetupStage", - ligand_1: parmed.amber.AmberParm, - ligand_2: parmed.amber.AmberParm | None, + ligand_1: mdtop.Topology, + ligand_2: mdtop.Topology | None, ligand_1_ref_query: tuple[str, str, str] | None = None, ligand_2_ref_query: tuple[str, str, str] | None = None, -) -> tuple[parmed.Structure, openmm.System]: + extra_params: list[pathlib.Path] | None = None, +) -> tuple[mdtop.Topology, openmm.System]: """Prepares a system ready for running the SepTop method. + Args: + config: The configuration for setting up the system. + ligand_1: The first ligand. + ligand_2: The second ligand if one is present. + ligand_1_ref_query: The query to select the reference atoms of the first ligand. + ligand_2_ref_query: The query to select the reference atoms of the second ligand + extra_params: The paths to any extra parameter files (.xml, .parm) to use + when parameterizing the system. + Returns: The prepared topology and OpenMM system object. """ @@ -305,28 +327,27 @@ def setup_solution( config = copy.deepcopy(config) - if config.solvent.box_padding is None: - raise NotImplementedError + if config.box_padding is None: + raise NotImplementedError("box padding must be set for solution phase") restraint_config = config.restraints if not isinstance(restraint_config, femto.fe.septop.SepTopSolutionRestraints): raise ValueError("invalid restraint config") - ligand_offset = None - if ligand_2 is not None: - ligand_offset = _compute_ligand_offset(ligand_1, ligand_2) - _offset_ligand(ligand_2, ligand_offset) + ligand_2.xyz += _compute_ligand_offset(ligand_1, ligand_2) system, topology, ligand_1_ref_idxs, ligand_2_ref_idxs = _setup_system( config, ligand_1, ligand_2, None, + None, ligand_1_ref_query, ligand_2_ref_query, - None if ligand_2 is None else -ligand_offset, + None, + extra_params, ) if ligand_2 is not None: diff --git a/femto/fe/tests/atm/test_cli.py b/femto/fe/tests/atm/test_cli.py index 7e05fa3..ef5611a 100644 --- a/femto/fe/tests/atm/test_cli.py +++ b/femto/fe/tests/atm/test_cli.py @@ -87,17 +87,20 @@ def test_run_workflow_with_paths(click_runner, mock_cuda_devices, tmp_cwd, mocke mock_run.assert_called_once_with( mocker.ANY, TEMOA_SYSTEM.ligand_1_coords, - TEMOA_SYSTEM.ligand_1_params, TEMOA_SYSTEM.ligand_2_coords, - TEMOA_SYSTEM.ligand_2_params, TEMOA_SYSTEM.receptor_coords, - TEMOA_SYSTEM.receptor_params, + [], expected_output_dir, expected_report_dir, None, TEMOA_SYSTEM.ligand_1_ref_atoms, TEMOA_SYSTEM.ligand_2_ref_atoms, TEMOA_SYSTEM.receptor_cavity_mask, + [ + TEMOA_SYSTEM.ligand_1_params, + TEMOA_SYSTEM.ligand_2_params, + TEMOA_SYSTEM.receptor_params, + ], ) @@ -153,14 +156,13 @@ def test_run_workflow_with_directory( if result.exit_code != 0: raise result.exception - expected_ligand_1_coords = mock_bfe_directory / "forcefield/1h1q/vacuum.mol2" - expected_ligand_1_params = mock_bfe_directory / "forcefield/1h1q/vacuum.parm7" + expected_ligand_1_coords = mock_bfe_directory / "forcefield/1h1q/vacuum.sdf" + expected_ligand_1_params = mock_bfe_directory / "forcefield/1h1q/vacuum.xml" - expected_ligand_2_coords = mock_bfe_directory / "forcefield/1oiu/vacuum.mol2" - expected_ligand_2_params = mock_bfe_directory / "forcefield/1oiu/vacuum.parm7" + expected_ligand_2_coords = mock_bfe_directory / "forcefield/1oiu/vacuum.sdf" + expected_ligand_2_params = mock_bfe_directory / "forcefield/1oiu/vacuum.xml" expected_receptor_coords = mock_bfe_directory / "proteins/cdk2/protein.pdb" - expected_receptor_params = None expected_ligand_1_ref = None expected_ligand_2_ref = None @@ -169,17 +171,16 @@ def test_run_workflow_with_directory( mock_run.assert_called_once_with( mocker.ANY, expected_ligand_1_coords, - expected_ligand_1_params, expected_ligand_2_coords, - expected_ligand_2_params, expected_receptor_coords, - expected_receptor_params, + [], expected_output_dir, None, None, expected_ligand_1_ref, expected_ligand_2_ref, expected_receptor_ref, + [expected_ligand_1_params, expected_ligand_2_params], ) diff --git a/femto/fe/tests/atm/test_equilibrate.py b/femto/fe/tests/atm/test_equilibrate.py index 4044d35..91dada3 100644 --- a/femto/fe/tests/atm/test_equilibrate.py +++ b/femto/fe/tests/atm/test_equilibrate.py @@ -1,8 +1,8 @@ +import mdtop import numpy import openmm import openmm.app import openmm.unit -import parmed import pytest import femto.fe.atm @@ -37,11 +37,16 @@ def mock_system() -> openmm.System: @pytest.fixture -def mock_topology(mock_system) -> parmed.Structure: +def mock_topology(mock_system) -> mdtop.Topology: topology = build_mock_structure(["[Ar]"]) - topology.coordinates = [[0.0, 0.0, 0.0]] + topology.xyz = numpy.array([[0.0, 0.0, 0.0]]) * openmm.unit.angstrom topology.residues[0].name = femto.md.constants.LIGAND_1_RESIDUE_NAME - topology.box_vectors = mock_system.getDefaultPeriodicBoxVectors() + + box = [ + v.value_in_unit(openmm.unit.angstrom) + for v in mock_system.getDefaultPeriodicBoxVectors() + ] + topology.box = box return topology diff --git a/femto/fe/tests/atm/test_runner.py b/femto/fe/tests/atm/test_runner.py index 548af65..2e68a1f 100644 --- a/femto/fe/tests/atm/test_runner.py +++ b/femto/fe/tests/atm/test_runner.py @@ -1,10 +1,10 @@ import functools +import mdtop import numpy import openmm import openmm.unit import pandas -import parmed import pytest import femto.fe.inputs @@ -30,28 +30,31 @@ def test_prepare_system(tmp_cwd, mocker): prepare_system_fn = functools.partial( _prepare_system, mock_config, + TEMOA_SYSTEM.receptor_coords, TEMOA_SYSTEM.ligand_1_coords, - TEMOA_SYSTEM.ligand_1_params, TEMOA_SYSTEM.ligand_2_coords, - TEMOA_SYSTEM.ligand_2_params, - TEMOA_SYSTEM.receptor_coords, - TEMOA_SYSTEM.receptor_params, + [], numpy.array([22, 22, 22]) * openmm.unit.angstrom, TEMOA_SYSTEM.ligand_1_ref_atoms, TEMOA_SYSTEM.ligand_2_ref_atoms, TEMOA_SYSTEM.receptor_cavity_mask, expected_output_dir, + [ + TEMOA_SYSTEM.ligand_1_params, + TEMOA_SYSTEM.ligand_2_params, + TEMOA_SYSTEM.receptor_params, + ], ) topology, system, _ = prepare_system_fn() - assert isinstance(topology, parmed.Structure) + assert isinstance(topology, mdtop.Topology) assert isinstance(system, openmm.System) # caching should be hit now topology, system, _ = prepare_system_fn() - assert isinstance(topology, parmed.Structure) + assert isinstance(topology, mdtop.Topology) assert isinstance(system, openmm.System) assert mock_setup_system.call_count == 1 @@ -98,34 +101,40 @@ def test_run_workflow(tmp_cwd, mocker): run_workflow, mock_config, TEMOA_SYSTEM.ligand_1_coords, - TEMOA_SYSTEM.ligand_1_params, TEMOA_SYSTEM.ligand_2_coords, - TEMOA_SYSTEM.ligand_2_params, TEMOA_SYSTEM.receptor_coords, - TEMOA_SYSTEM.receptor_params, + [], expected_output_dir, expected_output_dir, expected_offset, TEMOA_SYSTEM.ligand_1_ref_atoms, TEMOA_SYSTEM.ligand_2_ref_atoms, TEMOA_SYSTEM.receptor_cavity_mask, + [ + TEMOA_SYSTEM.ligand_1_params, + TEMOA_SYSTEM.ligand_2_params, + TEMOA_SYSTEM.receptor_params, + ], ) run_workflow_fn() mock_prepare_system.assert_called_once_with( mock_config.setup, + TEMOA_SYSTEM.receptor_coords, TEMOA_SYSTEM.ligand_1_coords, - TEMOA_SYSTEM.ligand_1_params, TEMOA_SYSTEM.ligand_2_coords, - TEMOA_SYSTEM.ligand_2_params, - TEMOA_SYSTEM.receptor_coords, - TEMOA_SYSTEM.receptor_params, + [], pytest.approx(expected_offset), TEMOA_SYSTEM.ligand_1_ref_atoms, TEMOA_SYSTEM.ligand_2_ref_atoms, TEMOA_SYSTEM.receptor_cavity_mask, expected_output_dir / "_setup", + [ + TEMOA_SYSTEM.ligand_1_params, + TEMOA_SYSTEM.ligand_2_params, + TEMOA_SYSTEM.receptor_params, + ], ) mock_equilibrate.assert_called_once_with( mock_system, diff --git a/femto/fe/tests/atm/test_sample.py b/femto/fe/tests/atm/test_sample.py index 04d3ad9..0f4da5d 100644 --- a/femto/fe/tests/atm/test_sample.py +++ b/femto/fe/tests/atm/test_sample.py @@ -1,5 +1,6 @@ +import mdtop +import numpy import openmm -import parmed import pytest from pymbar.testsystems import harmonic_oscillators @@ -9,7 +10,7 @@ import femto.md.reporting import femto.md.rest import femto.md.utils.openmm -from femto.fe.atm._sample import run_hremd, _analyze +from femto.fe.atm._sample import _analyze, run_hremd from femto.md.tests.mocking import build_mock_structure @@ -24,11 +25,16 @@ def mock_system() -> openmm.System: @pytest.fixture() -def mock_topology(mock_system) -> parmed.Structure: +def mock_topology(mock_system) -> mdtop.Topology: topology = build_mock_structure(["[Ar]"]) - topology.coordinates = [[0.0, 0.0, 0.0]] + topology.xyz = numpy.array([[0.0, 0.0, 0.0]]) * openmm.unit.angstrom topology.residues[0].name = femto.md.constants.LIGAND_1_RESIDUE_NAME - topology.box_vectors = mock_system.getDefaultPeriodicBoxVectors() + + box = [ + v.value_in_unit(openmm.unit.angstrom) + for v in mock_system.getDefaultPeriodicBoxVectors() + ] + topology.box = box return topology @@ -36,8 +42,8 @@ def mock_topology(mock_system) -> parmed.Structure: @pytest.fixture() def mock_coords(mock_system, mock_topology): context = openmm.Context(mock_system, openmm.VerletIntegrator(0.001)) - context.setPeriodicBoxVectors(*mock_topology.box_vectors) - context.setPositions(mock_topology.positions) + context.setPeriodicBoxVectors(*mock_topology.box) + context.setPositions(mock_topology.xyz) state = context.getState(getPositions=True) diff --git a/femto/fe/tests/atm/test_setup.py b/femto/fe/tests/atm/test_setup.py index e2d34a4..46b9930 100644 --- a/femto/fe/tests/atm/test_setup.py +++ b/femto/fe/tests/atm/test_setup.py @@ -1,14 +1,14 @@ import collections +import mdtop import numpy import openmm.app import openmm.unit -import parmed import pytest -import femto.md.config -import femto.md.system import femto.fe.atm +import femto.md.config +import femto.md.prepare from femto.fe.atm._setup import _offset_ligand, select_displacement, setup_system from femto.fe.tests.systems import TEMOA_SYSTEM from femto.md.constants import ( @@ -23,55 +23,50 @@ @pytest.fixture def mock_setup_config() -> femto.fe.atm.ATMSetupStage: return femto.fe.atm.ATMSetupStage( - solvent=femto.md.config.Solvent( - box_padding=10.0 * openmm.unit.angstrom, cation="Na+" - ), + box_padding=10.0 * openmm.unit.angstrom, + cation="Na+", displacement=38.0 * openmm.unit.angstrom, restraints=femto.fe.atm.ATMRestraints(receptor_query=":1"), ) @pytest.fixture -def temoa_ligand_1() -> parmed.amber.AmberParm: - return femto.md.system.load_ligand( - TEMOA_SYSTEM.ligand_1_coords, - TEMOA_SYSTEM.ligand_1_params, - LIGAND_1_RESIDUE_NAME, +def temoa_ligand_1() -> mdtop.Topology: + return femto.md.prepare.load_ligand( + TEMOA_SYSTEM.ligand_1_coords, LIGAND_1_RESIDUE_NAME ) @pytest.fixture -def temoa_ligand_2() -> parmed.amber.AmberParm: - return femto.md.system.load_ligand( - TEMOA_SYSTEM.ligand_2_coords, - TEMOA_SYSTEM.ligand_2_params, - LIGAND_2_RESIDUE_NAME, +def temoa_ligand_2() -> mdtop.Topology: + return femto.md.prepare.load_ligand( + TEMOA_SYSTEM.ligand_2_coords, LIGAND_2_RESIDUE_NAME ) @pytest.fixture -def temoa_receptor() -> parmed.amber.AmberParm: - return parmed.amber.AmberParm( - str(TEMOA_SYSTEM.receptor_params), - str(TEMOA_SYSTEM.receptor_coords), - ) +def temoa_receptor() -> mdtop.Topology: + return femto.md.prepare.load_receptor(TEMOA_SYSTEM.receptor_coords) def test_select_displacement(): ligand_1 = build_mock_structure(["[Ar]"]) receptor = build_mock_structure(["CC"]) - receptor.coordinates = numpy.array( - [ - [+1, -1, -1], - [+1, +1, -1], - [-1, +1, -1], - [-1, -1, +1], - [-0.5, -0.5, -0.5], # should be selected as furthest from ligand - [+1, -1, +1], - [+1, +1, +1], - [-1, +1, +1], - ] + receptor.xyz = ( + numpy.array( + [ + [+1, -1, -1], + [+1, +1, -1], + [-1, +1, -1], + [-1, -1, +1], + [-0.5, -0.5, -0.5], # should be selected as furthest from ligand + [+1, -1, +1], + [+1, +1, +1], + [-1, +1, +1], + ] + ) + * openmm.unit.angstrom ) expected_distance = 10.0 * openmm.unit.angstrom @@ -93,35 +88,45 @@ def test_offset_ligand(): force.addParticle(0.0, 1.0, 0.0) system.addForce(force) - ligand = parmed.openmm.load_topology(ligand.topology, system, ligand.coordinates) - - coords_0 = ligand.coordinates + coords_0 = ligand.xyz.value_in_unit(openmm.unit.angstrom) offset = numpy.array([5.0, 4.0, 3.0]) ligand_offset = _offset_ligand(ligand, offset * openmm.unit.angstrom) - assert numpy.allclose(ligand.coordinates, coords_0) - assert numpy.allclose(ligand_offset.coordinates, coords_0 + offset) + assert numpy.allclose(ligand.xyz.value_in_unit(openmm.unit.angstrom), coords_0) + assert numpy.allclose(ligand_offset.xyz, coords_0 + offset) def test_setup_system_abfe(temoa_ligand_1, temoa_receptor, mock_setup_config, mocker): n_ligand_atoms = len(temoa_ligand_1.atoms) n_receptor_atoms = len(temoa_receptor.atoms) - def mock_solvate_fn(receptor, lig_1, lig_2, *_, **__): + def mock_prepare_system(receptor, lig_1, lig_2, *_, **__): assert lig_2 is None - complex = lig_1 + receptor - complex.box = [100, 100, 100, 90, 90, 90] - return complex + lig_1.residues[0].name = "L1" + + bound = lig_1 + receptor + bound.box = numpy.eye(3) * 100.0 * openmm.unit.angstrom + + mock_system = openmm.System() + nb_force = openmm.NonbondedForce() + + for _ in range(bound.n_atoms): + mock_system.addParticle(1.0 * openmm.unit.amu) + nb_force.addParticle(0.0, 1.0, 0.0) + + mock_system.addForce(nb_force) - mock_solvate = mocker.patch( - "femto.md.solvate.solvate_system", + return bound, mock_system + + mock_prepare = mocker.patch( + "femto.md.prepare.prepare_system", autospec=True, - side_effect=mock_solvate_fn, + side_effect=mock_prepare_system, ) - mock_apply_hmr = mocker.patch("femto.md.system.apply_hmr", autospec=True) + mock_apply_hmr = mocker.patch("femto.md.prepare.apply_hmr", autospec=True) mock_apply_rest = mocker.patch("femto.md.rest.apply_rest", autospec=True) mock_setup_config.apply_rest = True @@ -134,23 +139,24 @@ def mock_solvate_fn(receptor, lig_1, lig_2, *_, **__): temoa_receptor, temoa_ligand_1, None, + [], numpy.ones(3) * 22.0 * openmm.unit.angstrom, - receptor_ref_query=":1", + receptor_ref_query="resi 1", ligand_1_ref_query=None, ligand_2_ref_query=None, ) - assert isinstance(topology, parmed.Structure) + assert isinstance(topology, mdtop.Topology) assert isinstance(system, openmm.System) - mock_solvate.assert_called_once() + mock_prepare.assert_called_once() mock_apply_hmr.assert_called_once_with(mocker.ANY, mocker.ANY, expected_h_mass) mock_apply_rest.assert_called_once() assert mock_apply_rest.call_args.args[1] == set(range(n_ligand_atoms)) assert len(topology.atoms) == system.getNumParticles() - assert len(topology[f":{LIGAND_1_RESIDUE_NAME}"].residues) == 1 + assert len(topology[f"resn {LIGAND_1_RESIDUE_NAME}"].residues) == 1 forces = collections.defaultdict(list) @@ -189,9 +195,9 @@ def mock_solvate_fn(receptor, lig_1, lig_2, *_, **__): x0, y0, z0, k, radius = restraint_params - assert numpy.isclose(x0, 1.76658000) - assert numpy.isclose(y0, 2.76679000) - assert numpy.isclose(z0, 2.33774000) + assert numpy.isclose(x0, 0.39454) + assert numpy.isclose(y0, -0.02540) + assert numpy.isclose(z0, 0.24646) expected_k = mock_setup_config.restraints.receptor.k.value_in_unit_system( openmm.unit.md_unit_system @@ -211,34 +217,44 @@ def test_setup_system_rbfe( n_ligand_2_atoms = len(temoa_ligand_2.atoms) n_receptor_atoms = len(temoa_receptor.atoms) - def mock_solvate_fn(receptor, lig_1, lig_2, *_, **__): - complex: parmed.Structure = lig_1 + lig_2 + receptor - complex.box = [100, 100, 100, 90, 90, 90] - return complex + def mock_prepare_system(receptor, lig_1, lig_2, *_, **__): + lig_1.residues[0].name = "L1" + lig_2.residues[0].name = "R1" + + bound = lig_1 + lig_2 + receptor + bound.box = numpy.eye(3) * 100.0 * openmm.unit.angstrom + + mock_system = openmm.System() + + for _ in range(bound.n_atoms): + mock_system.addParticle(1.0 * openmm.unit.amu) - mock_solvate = mocker.patch( - "femto.md.solvate.solvate_system", + return bound, mock_system + + mock_prepare = mocker.patch( + "femto.md.prepare.prepare_system", autospec=True, - side_effect=mock_solvate_fn, + side_effect=mock_prepare_system, ) - mock_setup_config.solvent.cation = "K+" + mock_setup_config.cation = "K+" topology, system = setup_system( mock_setup_config, receptor=temoa_receptor, - receptor_ref_query=":1", + receptor_ref_query="resi 1", ligand_1=temoa_ligand_1, - ligand_1_ref_query=("@1", "@2", "@3"), + ligand_1_ref_query=("index 1", "index 2", "index 3"), ligand_2=temoa_ligand_2, - ligand_2_ref_query=("@4", "@5", "@6"), + ligand_2_ref_query=("index 4", "index 5", "index 6"), + cofactors=[], displacement=numpy.ones(3) * 22.0 * openmm.unit.angstrom, ) - mock_solvate.assert_called_once() + mock_prepare.assert_called_once() - assert len(topology[f":{LIGAND_1_RESIDUE_NAME}"].atoms) == n_ligand_1_atoms - assert len(topology[f":{LIGAND_2_RESIDUE_NAME}"].atoms) == n_ligand_2_atoms + assert len(topology[f"resn {LIGAND_1_RESIDUE_NAME}"].atoms) == n_ligand_1_atoms + assert len(topology[f"resn {LIGAND_2_RESIDUE_NAME}"].atoms) == n_ligand_2_atoms forces = collections.defaultdict(list) @@ -277,8 +293,16 @@ def mock_solvate_fn(receptor, lig_1, lig_2, *_, **__): assert len(forces[OpenMMForceGroup.ALIGNMENT_RESTRAINT]) == 3 - ligand_1_center = topology[f":{LIGAND_1_RESIDUE_NAME}"].coordinates.mean(axis=0) - ligand_2_center = topology[f":{LIGAND_2_RESIDUE_NAME}"].coordinates.mean(axis=0) + ligand_2_center_orig = temoa_ligand_2.xyz.value_in_unit(openmm.unit.angstrom).mean( + axis=0 + ) + ligand_2_center = ( + topology[f":{LIGAND_2_RESIDUE_NAME}"] + .xyz.value_in_unit(openmm.unit.angstrom) + .mean(axis=0) + ) for i in range(3): - assert numpy.isclose(ligand_2_center[i] - ligand_1_center[i], 22.0, atol=0.5) + assert numpy.isclose( + ligand_2_center[i] - ligand_2_center_orig[i], 22.0, atol=0.5 + ) diff --git a/femto/fe/tests/data/cdk2/1h1q.sdf b/femto/fe/tests/data/cdk2/1h1q.sdf new file mode 100644 index 0000000..8c86dd8 --- /dev/null +++ b/femto/fe/tests/data/cdk2/1h1q.sdf @@ -0,0 +1,114 @@ +1H1Q + 3D + Schrodinger Suite 2022-2. + 45 48 0 0 1 0 999 V2000 + 7.8210 43.7020 49.3990 C 0 0 0 0 0 0 + 10.7390 46.1550 51.6470 C 0 0 0 0 0 0 + 5.1160 45.2740 51.6170 C 0 0 0 0 0 0 + 4.1070 45.7240 52.7030 C 0 0 0 0 0 0 + 2.7660 45.9860 51.9860 C 0 0 0 0 0 0 + 1.5620 46.0860 52.9390 C 0 0 0 0 0 0 + 1.4480 44.9690 53.9870 C 0 0 0 0 0 0 + 2.7870 44.7400 54.7250 C 0 0 0 0 0 0 + 4.0090 44.6090 53.7920 C 0 0 0 0 0 0 + 4.6670 40.9170 47.4030 C 0 0 0 0 0 0 + 4.0590 40.6480 48.6330 C 0 0 0 0 0 0 + 4.6450 41.0930 49.8150 C 0 0 0 0 0 0 + 5.8520 41.7890 49.8080 C 0 0 0 0 0 0 + 6.8700 44.4890 50.1810 N 0 0 0 0 0 0 + 7.2370 45.3850 51.0370 C 0 0 0 0 0 0 + 6.3200 46.0740 51.7360 O 0 0 0 0 0 0 + 8.6590 45.6250 51.1990 C 0 0 0 0 0 0 + 9.5490 46.4520 51.9630 N 0 0 0 0 0 0 + 10.7730 45.2130 50.7360 N 0 0 0 0 0 0 + 9.5340 44.9120 50.4780 C 0 0 0 0 0 0 + 9.0610 43.9200 49.5710 N 0 0 0 0 0 0 + 7.6000 42.7620 48.5320 N 0 0 0 0 0 0 + 6.4450 42.0610 48.5810 C 0 0 0 0 0 0 + 5.8550 41.6320 47.3960 C 0 0 0 0 0 0 + 4.6775 45.4225 50.6302 H 0 0 0 0 0 0 + 5.3628 44.2225 51.7641 H 0 0 0 0 0 0 + 4.4581 46.6481 53.1622 H 0 0 0 0 0 0 + 2.5854 45.1961 51.2570 H 0 0 0 0 0 0 + 2.8416 46.9029 51.4015 H 0 0 0 0 0 0 + 0.6428 46.1264 52.3546 H 0 0 0 0 0 0 + 1.5815 47.0521 53.4434 H 0 0 0 0 0 0 + 1.1399 44.0442 53.4993 H 0 0 0 0 0 0 + 0.6748 45.2279 54.7104 H 0 0 0 0 0 0 + 2.7092 43.8475 55.3459 H 0 0 0 0 0 0 + 2.9556 45.5551 55.4288 H 0 0 0 0 0 0 + 4.9208 44.6063 54.3892 H 0 0 0 0 0 0 + 3.9911 43.6323 53.3084 H 0 0 0 0 0 0 + 4.2108 40.5699 46.4877 H 0 0 0 0 0 0 + 3.1327 40.0939 48.6677 H 0 0 0 0 0 0 + 4.1634 40.9006 50.7623 H 0 0 0 0 0 0 + 6.3008 42.1012 50.7394 H 0 0 0 0 0 0 + 8.2898 42.5561 47.8235 H 0 0 0 0 0 0 + 6.3393 41.8654 46.4593 H 0 0 0 0 0 0 + 11.5200 44.7341 50.2536 H 0 0 0 0 0 0 + 11.5662 46.6643 52.1189 H 0 0 0 0 0 0 + 1 14 2 0 0 0 + 1 21 1 0 0 0 + 1 22 1 0 0 0 + 2 18 2 0 0 0 + 2 19 1 0 0 0 + 2 45 1 0 0 0 + 3 4 1 0 0 0 + 3 16 1 0 0 0 + 3 25 1 0 0 0 + 3 26 1 0 0 0 + 4 5 1 0 0 0 + 4 9 1 0 0 0 + 4 27 1 0 0 0 + 5 6 1 0 0 0 + 5 28 1 0 0 0 + 5 29 1 0 0 0 + 6 7 1 0 0 0 + 6 30 1 0 0 0 + 6 31 1 0 0 0 + 7 8 1 0 0 0 + 7 32 1 0 0 0 + 7 33 1 0 0 0 + 8 9 1 0 0 0 + 8 34 1 0 0 0 + 8 35 1 0 0 0 + 9 36 1 0 0 0 + 9 37 1 0 0 0 + 10 11 2 0 0 0 + 10 24 1 0 0 0 + 10 38 1 0 0 0 + 11 12 1 0 0 0 + 11 39 1 0 0 0 + 12 13 2 0 0 0 + 12 40 1 0 0 0 + 13 23 1 0 0 0 + 13 41 1 0 0 0 + 14 15 1 0 0 0 + 15 16 1 0 0 0 + 15 17 2 0 0 0 + 17 18 1 0 0 0 + 17 20 1 0 0 0 + 19 20 1 0 0 0 + 19 44 1 0 0 0 + 20 21 2 0 0 0 + 22 23 1 0 0 0 + 22 42 1 0 0 0 + 23 24 2 0 0 0 + 24 43 1 0 0 0 +M END +> +9 + +> +1H1Q.1 + +> +/Users/sheenam/Downloads + +> +1H1Q.mol2 + +> +1 + +$$$$ diff --git a/femto/fe/tests/data/cdk2/1h1q.xml b/femto/fe/tests/data/cdk2/1h1q.xml new file mode 100644 index 0000000..32f88b0 --- /dev/null +++ b/femto/fe/tests/data/cdk2/1h1q.xml @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/femto/fe/tests/data/cdk2/1oiu.parm7 b/femto/fe/tests/data/cdk2/1oiu.parm7 deleted file mode 100644 index 80c736f..0000000 --- a/femto/fe/tests/data/cdk2/1oiu.parm7 +++ /dev/null @@ -1,446 +0,0 @@ -%VERSION VERSION_STAMP = V0001.000 DATE = 04/17/23 11:04:11 -%FLAG TITLE -%FORMAT(20a4) -MOL -%FLAG POINTERS -%FORMAT(10I8) - 50 13 22 31 50 44 93 77 0 0 - 279 1 31 44 77 20 36 22 16 0 - 0 0 0 0 0 0 0 0 50 0 - 0 -%FLAG ATOM_NAME -%FORMAT(20a4) -C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 N1 C12 C13 N2 N3 C14 O1 C15 N4 -N5 C16 N6 C17 C18 S1 O2 O3 H1 H2 H3 H4 H5 H6 H7 H8 H9 H10 H11 H12 -H13 H14 H15 H16 H17 H18 H19 H20 H21 H22 -%FLAG CHARGE -%FORMAT(5E16.8) - 1.65986931E+01 7.15589721E+00 2.48552172E+00 -1.30653891E+00 -1.34662797E+00 - -1.38307257E+00 -1.42862832E+00 -1.38307257E+00 -1.34662797E+00 -5.28446700E-01 - -6.67847295E+00 -1.27975213E+01 -1.03867110E+00 1.90605258E+00 -1.87088354E+01 - -1.50151752E+01 1.37869922E+01 -7.60052133E+00 -3.79388286E+00 -9.11297223E+00 - -6.97914090E+00 8.59728114E+00 -1.40129487E+01 -8.01781200E-01 -2.73334500E+00 - 2.78108743E+01 -1.19775178E+01 -1.19775178E+01 1.36302804E+00 1.00587096E+00 - 1.00587096E+00 1.32476121E+00 8.05425660E-01 8.05425660E-01 7.59869910E-01 - 7.59869910E-01 7.14314160E-01 7.14314160E-01 7.59869910E-01 7.59869910E-01 - 8.05425660E-01 8.05425660E-01 2.76978960E+00 7.97772294E+00 2.84267880E+00 - 8.15994594E+00 8.15994594E+00 5.80744701E+00 2.87912340E+00 2.60578890E+00 -%FLAG ATOMIC_NUMBER -%FORMAT(10I8) - 6 6 6 6 6 6 6 6 6 6 - 6 7 6 6 7 7 6 8 6 7 - 7 6 7 6 6 16 8 8 1 1 - 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 -%FLAG MASS -%FORMAT(5E16.8) - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.40100000E+01 1.20100000E+01 1.20100000E+01 1.40100000E+01 - 1.40100000E+01 1.20100000E+01 1.60000000E+01 1.20100000E+01 1.40100000E+01 - 1.40100000E+01 1.20100000E+01 1.40100000E+01 1.20100000E+01 1.20100000E+01 - 3.20600000E+01 1.60000000E+01 1.60000000E+01 1.00800000E+00 1.00800000E+00 - 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.00800000E+00 - 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.00800000E+00 - 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.00800000E+00 - 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.00800000E+00 -%FLAG ATOM_TYPE_INDEX -%FORMAT(10I8) - 1 1 2 2 2 2 2 2 2 1 - 1 3 1 1 3 4 1 5 1 4 - 6 1 4 1 1 7 8 8 9 10 - 10 11 11 11 11 11 11 11 11 11 - 11 11 12 13 12 13 13 13 12 12 -%FLAG NUMBER_EXCLUDED_ATOMS -%FORMAT(10I8) - 12 8 16 18 16 12 12 10 12 13 - 14 11 11 9 5 7 8 6 6 5 - 4 3 2 6 4 6 3 2 1 2 - 1 4 3 2 3 2 3 2 3 2 - 1 1 1 1 1 1 1 1 1 1 -%FLAG NONBONDED_PARM_INDEX -%FORMAT(10I8) - 1 2 4 7 11 16 22 29 37 46 - 56 67 79 2 3 5 8 12 17 23 - 30 38 47 57 68 80 4 5 6 9 - 13 18 24 31 39 48 58 69 81 7 - 8 9 10 14 19 25 32 40 49 59 - 70 82 11 12 13 14 15 20 26 33 - 41 50 60 71 83 16 17 18 19 20 - 21 27 34 42 51 61 72 84 22 23 - 24 25 26 27 28 35 43 52 62 73 - 85 29 30 31 32 33 34 35 36 44 - 53 63 74 86 37 38 39 40 41 42 - 43 44 45 54 64 75 87 46 47 48 - 49 50 51 52 53 54 55 65 76 88 - 56 57 58 59 60 61 62 63 64 65 - 66 77 89 67 68 69 70 71 72 73 - 74 75 76 77 78 90 79 80 81 82 - 83 84 85 86 87 88 89 90 91 -%FLAG RESIDUE_LABEL -%FORMAT(20a4) -MOL -%FLAG RESIDUE_POINTER -%FORMAT(10I8) - 1 -%FLAG BOND_FORCE_CONSTANT -%FORMAT(5E16.8) - 4.17900000E+02 4.14200000E+02 4.50700000E+02 3.54500000E+02 4.03500000E+02 - 2.32500000E+02 2.84800000E+02 3.75900000E+02 3.75900000E+02 3.78600000E+02 - 3.95700000E+02 2.09800000E+02 4.04600000E+02 2.97400000E+02 3.92400000E+02 - 3.57500000E+02 3.94600000E+02 3.49500000E+02 5.35100000E+02 6.53200000E+02 -%FLAG BOND_EQUIL_VALUE -%FORMAT(5E16.8) - 1.38600000E+00 1.33900000E+00 1.31700000E+00 1.38000000E+00 1.08200000E+00 - 1.53800000E+00 1.43200000E+00 1.09700000E+00 1.09700000E+00 1.39800000E+00 - 1.08600000E+00 1.79100000E+00 1.01200000E+00 1.69600000E+00 1.01900000E+00 - 1.37000000E+00 1.35200000E+00 1.38400000E+00 1.01000000E+00 1.46600000E+00 -%FLAG ANGLE_FORCE_CONSTANT -%FORMAT(5E16.8) - 6.33000000E+01 4.84000000E+01 7.04000000E+01 7.46000000E+01 6.94000000E+01 - 4.71000000E+01 6.49000000E+01 4.68000000E+01 6.61000000E+01 8.53000000E+01 - 4.69000000E+01 6.88000000E+01 6.34000000E+01 4.87000000E+01 6.40000000E+01 - 9.43000000E+01 7.27000000E+01 6.83000000E+01 7.04000000E+01 1.09400000E+02 - 1.11500000E+02 8.68000000E+01 8.76000000E+01 6.24000000E+01 8.73000000E+01 - 8.72000000E+01 1.15500000E+02 6.25000000E+01 6.12000000E+01 1.07600000E+02 - 4.70000000E+01 4.42000000E+01 1.26400000E+02 3.88000000E+01 3.90000000E+01 - 4.14000000E+01 -%FLAG ANGLE_EQUIL_VALUE -%FORMAT(5E16.8) - 2.22459762E+00 2.02580453E+00 2.04587583E+00 1.83050210E+00 1.97484090E+00 - 2.19038915E+00 1.94621748E+00 1.91637234E+00 2.05879127E+00 1.88443280E+00 - 1.91218355E+00 2.09474507E+00 2.08427309E+00 2.09230160E+00 1.78791605E+00 - 1.89106506E+00 2.04098890E+00 2.11097664E+00 1.86977203E+00 2.22110696E+00 - 2.08950908E+00 2.14570870E+00 2.08950908E+00 1.91602327E+00 2.08043336E+00 - 2.06542352E+00 1.95860933E+00 2.19073822E+00 2.12144862E+00 2.21813990E+00 - 2.19108728E+00 1.91113635E+00 2.11900515E+00 1.89298492E+00 1.87762601E+00 - 1.85703112E+00 -%FLAG DIHEDRAL_FORCE_CONSTANT -%FORMAT(5E16.8) - 1.05000000E+00 4.80000000E+00 6.03000000E-01 3.00000000E-01 1.10000000E-01 - 2.90000000E-01 1.30000000E-01 8.00000000E-02 9.00000000E-01 1.61000000E+00 - 3.83333333E-01 1.55555556E-01 3.62500000E+00 1.30000000E+00 3.13333333E+00 - 2.50000000E-01 0.00000000E+00 4.75000000E+00 1.70000000E+00 1.20000000E-01 - 1.05000000E+01 1.10000000E+00 -%FLAG DIHEDRAL_PERIODICITY -%FORMAT(5E16.8) - 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 1.00000000E+00 - 2.00000000E+00 3.00000000E+00 3.00000000E+00 2.00000000E+00 2.00000000E+00 - 3.00000000E+00 3.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 - 1.00000000E+00 3.00000000E+00 2.00000000E+00 2.00000000E+00 3.00000000E+00 - 2.00000000E+00 2.00000000E+00 -%FLAG DIHEDRAL_PHASE -%FORMAT(5E16.8) - 3.14159400E+00 3.14159400E+00 3.14159400E+00 3.14159400E+00 0.00000000E+00 - 3.14159400E+00 0.00000000E+00 0.00000000E+00 3.14159400E+00 3.14159400E+00 - 0.00000000E+00 0.00000000E+00 3.14159400E+00 3.14159400E+00 0.00000000E+00 - 0.00000000E+00 0.00000000E+00 3.14159400E+00 3.14159400E+00 0.00000000E+00 - 3.14159400E+00 3.14159400E+00 -%FLAG SCEE_SCALE_FACTOR -%FORMAT(5E16.8) - 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 - 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 - 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 - 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 - 0.00000000E+00 0.00000000E+00 -%FLAG SCNB_SCALE_FACTOR -%FORMAT(5E16.8) - 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 - 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 - 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 - 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 - 0.00000000E+00 0.00000000E+00 -%FLAG SOLTY -%FORMAT(5E16.8) - 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 - 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 - 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 - 0.00000000E+00 -%FLAG LENNARD_JONES_ACOEF -%FORMAT(5E16.8) - 6.96551276E+05 8.43969952E+05 1.02073671E+06 8.11497350E+05 9.84684600E+05 - 9.44293233E+05 7.69642101E+05 9.31115275E+05 8.97750460E+05 8.49322032E+05 - 4.46121722E+05 5.42502534E+05 5.18220260E+05 4.94433451E+05 2.83662575E+05 - 8.20152428E+05 9.96191545E+05 9.53586341E+05 9.08092643E+05 5.22690526E+05 - 9.62429652E+05 1.73376823E+06 2.09089895E+06 2.02752779E+06 1.90820499E+06 - 1.12086734E+06 2.05449504E+06 4.26366419E+06 5.17507491E+05 6.30925504E+05 - 5.99897901E+05 5.74782316E+05 3.27367585E+05 6.04208667E+05 1.30884564E+06 - 3.76435470E+05 5.22195041E+04 6.46910138E+04 5.97535946E+04 5.87815925E+04 - 3.19887757E+04 5.96470418E+04 1.37628606E+05 3.59538577E+04 2.97255425E+03 - 5.63013422E+04 6.97998263E+04 6.43853349E+04 6.34160397E+04 3.44374471E+04 - 6.42436358E+04 1.48672098E+05 3.86646725E+04 3.17496391E+03 3.39005767E+03 - 8.12662403E+04 1.00235148E+05 9.33217616E+04 9.11435380E+04 5.02224902E+04 - 9.33839501E+04 2.11783234E+05 5.68021765E+04 4.88692384E+03 5.22955465E+03 - 7.94617769E+03 7.52549148E+04 9.27554496E+04 8.64679274E+04 8.43518904E+04 - 4.65732396E+04 8.65595848E+04 1.95762765E+05 5.27277400E+04 4.56536476E+03 - 4.88692384E+03 7.41026906E+03 6.90855956E+03 1.71458823E+03 2.23577481E+03 - 1.88178855E+03 2.01475241E+03 9.46795083E+02 1.82491629E+03 5.15374979E+03 - 9.87857350E+02 5.02830284E+01 5.24570081E+01 9.47421632E+01 9.04434904E+01 - 1.34728399E-01 -%FLAG LENNARD_JONES_BCOEF -%FORMAT(5E16.8) - 5.24668528E+02 5.90251928E+02 6.63431737E+02 6.48597119E+02 7.30206155E+02 - 8.01323529E+02 5.44830041E+02 6.12468585E+02 6.73934129E+02 5.65406768E+02 - 3.88758656E+02 4.38146883E+02 4.79881022E+02 4.04310927E+02 2.87011518E+02 - 6.82622272E+02 7.68900328E+02 8.43016687E+02 7.09588248E+02 5.04545572E+02 - 8.86629877E+02 1.07629402E+03 1.20800124E+03 1.33303578E+03 1.11546536E+03 - 8.01230425E+02 1.40479403E+03 2.19459223E+03 4.98871431E+02 5.62969425E+02 - 6.15165511E+02 5.19385932E+02 3.67361018E+02 6.46321327E+02 1.03157656E+03 - 4.69350655E+02 9.12729468E+01 1.03827676E+02 1.11822890E+02 9.56653235E+01 - 6.61407978E+01 1.16962018E+02 1.92666848E+02 8.35449674E+01 1.38359132E+01 - 1.01040256E+02 1.14981426E+02 1.23751804E+02 1.05935811E+02 7.31636661E+01 - 1.29412081E+02 2.13489607E+02 9.23664132E+01 1.52447987E+01 1.67944276E+01 - 1.21392023E+02 1.37787728E+02 1.48987394E+02 1.27000718E+02 8.83545880E+01 - 1.56025608E+02 2.54804909E+02 1.11953944E+02 1.89134207E+01 2.08590256E+01 - 2.57122925E+01 1.09570372E+02 1.24325688E+02 1.34516705E+02 1.14599119E+02 - 7.98066064E+01 1.40899023E+02 2.29782888E+02 1.01173582E+02 1.71467049E+01 - 1.89134207E+01 2.32899892E+01 2.10929191E+01 1.46824710E+01 1.71355763E+01 - 1.76168249E+01 1.57231024E+01 1.01016461E+01 1.81620725E+01 3.30984635E+01 - 1.22938581E+01 1.59752321E+00 1.73959275E+00 2.33785449E+00 2.14252117E+00 - 7.34107347E-02 -%FLAG BONDS_INC_HYDROGEN -%FORMAT(10I8) - 3 84 5 6 87 8 6 90 8 9 - 93 9 12 96 9 12 99 9 15 102 - 9 15 105 9 18 108 9 18 111 9 - 21 114 9 21 117 9 24 120 9 24 - 123 9 27 126 11 33 129 13 36 132 - 11 42 135 15 42 138 15 60 141 19 - 69 144 11 72 147 11 -%FLAG BONDS_WITHOUT_HYDROGEN -%FORMAT(10I8) - 0 33 1 0 45 2 0 66 2 3 - 57 3 3 60 4 6 9 6 6 51 - 7 9 12 6 9 24 6 12 15 6 - 15 18 6 18 21 6 21 24 6 27 - 30 10 27 72 10 30 36 10 30 75 - 12 33 39 1 36 39 10 39 69 10 - 42 75 14 45 48 2 48 51 16 48 - 54 10 54 57 17 54 63 10 60 63 - 18 63 66 2 69 72 10 75 78 20 - 75 81 20 -%FLAG ANGLES_INC_HYDROGEN -%FORMAT(10I8) - 0 33 129 2 3 60 141 6 6 9 - 93 8 9 6 87 11 9 6 90 11 - 9 12 96 8 9 12 99 8 9 24 - 120 8 9 24 123 8 12 9 93 8 - 12 15 102 8 12 15 105 8 15 12 - 96 8 15 12 99 8 15 18 108 8 - 15 18 111 8 18 15 102 8 18 15 - 105 8 18 21 114 8 18 21 117 8 - 21 18 108 8 21 18 111 8 21 24 - 120 8 21 24 123 8 24 9 93 8 - 24 21 114 8 24 21 117 8 27 72 - 147 14 30 27 126 14 30 36 132 14 - 39 33 129 2 39 36 132 14 39 69 - 144 14 51 6 87 24 51 6 90 24 - 57 3 84 28 60 3 84 29 63 60 - 141 31 69 72 147 14 72 27 126 14 - 72 69 144 14 75 42 135 32 75 42 - 138 32 87 6 90 34 96 12 99 35 - 102 15 105 35 108 18 111 35 114 21 - 117 35 120 24 123 35 135 42 138 36 -%FLAG ANGLES_WITHOUT_HYDROGEN -%FORMAT(10I8) - 0 33 39 1 0 45 48 3 0 66 - 63 3 3 57 54 4 3 60 63 5 - 6 9 12 7 6 9 24 7 6 51 - 48 9 9 6 51 10 9 12 15 7 - 9 24 21 7 12 9 24 7 12 15 - 18 7 15 18 21 7 18 21 24 7 - 27 30 36 12 27 30 75 13 27 72 - 69 12 30 27 72 12 30 36 39 12 - 30 75 42 15 30 75 78 16 30 75 - 81 16 33 0 45 17 33 0 66 17 - 33 39 36 18 33 39 69 18 36 30 - 75 13 36 39 69 12 39 69 72 12 - 42 75 78 19 42 75 81 19 45 0 - 66 20 45 48 51 21 45 48 54 22 - 48 54 57 23 48 54 63 12 51 48 - 54 25 54 63 60 26 54 63 66 22 - 57 3 60 27 57 54 63 23 60 63 - 66 30 78 75 81 33 -%FLAG DIHEDRALS_INC_HYDROGEN -%FORMAT(10I8) - 6 9 12 96 8 6 9 12 99 8 - 6 9 24 120 8 6 9 24 123 8 - 9 12 15 102 8 9 12 15 105 8 - 9 24 21 114 8 9 24 21 117 8 - 12 9 6 87 12 12 9 6 90 12 - 12 9 24 120 8 12 9 24 123 8 - 12 15 18 108 8 12 15 18 111 8 - 15 12 9 93 8 15 18 21 114 8 - 15 18 21 117 8 18 15 12 96 8 - 18 15 12 99 8 18 21 24 120 8 - 18 21 24 123 8 21 18 15 102 8 - 21 18 15 105 8 21 24 9 93 8 - 24 9 6 87 12 24 9 6 90 12 - 24 9 12 96 8 24 9 12 99 8 - 24 21 18 108 8 24 21 18 111 8 - 27 30 36 132 13 27 72 69 144 13 - 30 27 72 147 13 30 75 42 135 15 - 30 75 42 138 15 33 39 36 132 13 - 33 39 69 144 13 36 30 27 126 13 - 36 39 33 129 1 36 39 69 144 13 - 39 69 72 147 13 45 0 33 129 1 - 48 51 6 87 11 48 51 6 90 11 - 51 6 9 93 16 51 6 -9 93 17 - 54 57 3 84 18 54 63 60 141 4 - 57 3 60 141 19 63 60 3 84 19 - 66 0 33 129 1 66 63 60 141 4 - 69 39 33 129 1 69 39 36 132 13 - 69 72 27 126 13 75 30 27 126 13 - 75 30 36 132 13 78 75 42 135 15 - 78 75 42 138 15 81 75 42 135 15 - 81 75 42 138 15 84 3 60 141 19 - 87 6 9 93 12 90 6 9 93 12 - 93 9 12 96 20 93 9 12 99 20 - 93 9 24 120 20 93 9 24 123 20 - 96 12 15 102 20 96 12 15 105 20 - 99 12 15 102 20 99 12 15 105 20 - 102 15 18 108 20 102 15 18 111 20 - 105 15 18 108 20 105 15 18 111 20 - 108 18 21 114 20 108 18 21 117 20 - 111 18 21 114 20 111 18 21 117 20 - 114 21 24 120 20 114 21 24 123 20 - 117 21 24 120 20 117 21 24 123 20 - 126 27 72 147 13 144 69 72 147 13 - 84 60 -3 -57 22 30 72 -27 -126 22 - 0 39 -33 -129 22 30 39 -36 -132 22 - 63 3 -60 -141 22 39 72 -69 -144 22 - 27 69 -72 -147 22 -%FLAG DIHEDRALS_WITHOUT_HYDROGEN -%FORMAT(10I8) - 0 33 39 36 1 0 33 39 69 1 - 0 45 48 51 2 0 45 48 54 2 - 0 66 -63 54 2 0 66 63 60 2 - 3 57 54 48 2 3 57 -54 63 2 - 3 60 -63 54 3 3 60 63 66 4 - 6 9 12 15 5 6 9 -12 15 6 - 6 9 -12 15 7 6 9 24 21 5 - 6 9 -24 21 6 6 9 -24 21 7 - 6 51 48 45 9 6 51 48 54 10 - 9 6 51 48 11 9 12 15 18 5 - 9 12 -15 18 6 9 12 -15 18 7 - 9 24 -21 18 5 9 24 -21 18 6 - 9 24 -21 18 7 12 9 6 51 12 - 12 9 24 21 5 12 9 -24 21 6 - 12 9 -24 21 7 12 15 -18 21 5 - 12 15 -18 21 6 12 15 -18 21 7 - 15 12 9 24 5 15 12 -9 24 6 - 15 12 -9 24 7 15 18 -21 24 5 - 15 18 -21 24 6 15 18 -21 24 7 - 24 9 6 51 12 27 30 36 39 13 - 27 30 75 42 14 27 30 75 78 14 - 27 30 75 81 14 27 72 -69 39 13 - 30 27 72 69 13 30 36 39 33 13 - 30 36 -39 69 13 33 0 45 48 2 - 33 0 66 63 2 33 39 69 72 13 - 36 30 27 72 13 36 30 75 42 14 - 36 30 75 78 14 36 30 75 81 14 - 36 39 -69 72 13 45 0 33 39 1 - 66 0 33 39 1 39 36 30 75 13 - 45 0 66 63 2 45 48 54 57 13 - 45 48 -54 63 13 66 0 45 48 2 - 48 54 63 60 13 48 54 -63 66 13 - 51 48 54 57 13 51 48 54 63 13 - 54 57 -3 60 18 57 3 -60 63 19 - 57 54 -63 60 13 57 54 63 66 13 - 72 27 30 75 13 33 0 -66 -45 21 - 27 36 -30 -75 22 36 69 -39 -33 22 - 54 45 -48 -51 22 48 63 -54 -57 22 - 54 60 -63 -66 22 -%FLAG EXCLUDED_ATOMS_LIST -%FORMAT(10I8) - 12 13 14 16 17 18 19 21 22 23 - 24 44 17 19 20 21 22 23 29 48 - 4 5 6 8 9 16 17 18 19 30 - 31 32 33 34 41 42 5 6 7 8 - 9 17 18 30 31 32 33 34 35 36 - 39 40 41 42 6 7 8 9 18 30 - 31 32 33 34 35 36 37 38 41 42 - 7 8 9 32 33 34 35 36 37 38 - 39 40 8 9 33 34 35 36 37 38 - 39 40 41 42 9 32 35 36 37 38 - 39 40 41 42 18 30 31 32 33 34 - 37 38 39 40 41 42 11 13 14 15 - 24 25 26 27 28 43 45 49 50 12 - 13 14 15 24 25 26 27 28 43 45 - 46 47 50 13 14 16 17 22 23 24 - 25 44 45 49 14 15 24 25 26 27 - 28 43 44 45 49 16 23 24 25 26 - 44 45 49 50 26 27 28 46 47 17 - 18 19 20 22 23 44 18 19 20 21 - 22 23 30 31 19 20 22 30 31 32 - 20 21 22 23 29 48 21 22 23 29 - 48 22 23 29 48 23 29 48 44 48 - 25 43 44 45 49 50 26 43 49 50 - 27 28 43 45 46 47 28 46 47 46 - 47 48 31 32 32 33 34 41 42 34 - 35 36 35 36 36 37 38 37 38 38 - 39 40 39 40 40 41 42 41 42 42 - 0 50 0 0 47 0 0 50 0 -%FLAG HBOND_ACOEF -%FORMAT(5E16.8) - -%FLAG HBOND_BCOEF -%FORMAT(5E16.8) - -%FLAG HBCUT -%FORMAT(5E16.8) - -%FLAG AMBER_ATOM_TYPE -%FORMAT(20a4) -ca cc c3 c3 c3 c3 c3 c3 c3 ca ca nu ca ca n8 nb ca os ca nd -na ca nb ca ca sy o o h5 h1 h1 hc hc hc hc hc hc hc hc hc -hc hc ha hn ha hn hn hn ha ha -%FLAG TREE_CHAIN_CLASSIFICATION -%FORMAT(20a4) -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -%FLAG JOIN_ARRAY -%FORMAT(10I8) - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 -%FLAG IROTAT -%FORMAT(10I8) - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 -%FLAG RADIUS_SET -%FORMAT(1a80) -modified Bondi radii (mbondi) -%FLAG RADII -%FORMAT(5E16.8) - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.55000000E+00 1.70000000E+00 1.70000000E+00 1.55000000E+00 - 1.55000000E+00 1.70000000E+00 1.50000000E+00 1.70000000E+00 1.55000000E+00 - 1.55000000E+00 1.70000000E+00 1.55000000E+00 1.70000000E+00 1.70000000E+00 - 1.80000000E+00 1.50000000E+00 1.50000000E+00 1.30000000E+00 1.30000000E+00 - 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.30000000E+00 - 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.30000000E+00 - 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.30000000E+00 - 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.30000000E+00 -%FLAG SCREEN -%FORMAT(5E16.8) - 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 7.90000000E-01 7.20000000E-01 7.20000000E-01 7.90000000E-01 - 7.90000000E-01 7.20000000E-01 8.50000000E-01 7.20000000E-01 7.90000000E-01 - 7.90000000E-01 7.20000000E-01 7.90000000E-01 7.20000000E-01 7.20000000E-01 - 9.60000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 -%FLAG IPOL -%FORMAT(1I8) - 0 diff --git a/femto/fe/tests/data/cdk2/1oiu.rst7 b/femto/fe/tests/data/cdk2/1oiu.rst7 deleted file mode 100644 index 2001edd..0000000 --- a/femto/fe/tests/data/cdk2/1oiu.rst7 +++ /dev/null @@ -1,27 +0,0 @@ -MOL - 50 - 7.9160000 43.6120000 49.8510000 10.5520000 46.4410000 52.0420000 - 4.8670000 45.3510000 51.9070000 4.0240000 45.8620000 53.0570000 - 2.5620000 45.5450000 52.7500000 1.6820000 46.0090000 53.9120000 - 2.1170000 45.3790000 55.2320000 3.6010000 45.6060000 55.5280000 - 4.4730000 45.1740000 54.3530000 3.9800000 40.7090000 48.5370000 - 4.8080000 40.9170000 47.4370000 7.6260000 42.6110000 48.9930000 - 6.0320000 41.5580000 47.6210000 6.4230000 41.9980000 48.8840000 - 4.0430000 38.8600000 46.0080000 6.9350000 44.2280000 50.5510000 - 7.1960000 45.2310000 51.4250000 6.1920000 45.8270000 52.1090000 - 8.5840000 45.6720000 51.6220000 9.2230000 46.6190000 52.3980000 - 10.8110000 45.4370000 51.1020000 9.5550000 44.9660000 50.8410000 - 9.2100000 43.9690000 49.9980000 5.5980000 41.7740000 49.9860000 - 4.3750000 41.1310000 49.8100000 4.3510000 40.4270000 45.9660000 - 5.3810000 40.6740000 45.0070000 3.1650000 41.1500000 45.6150000 - 11.3180000 47.0600000 52.4860000 4.4790000 45.7470000 50.9690000 - 4.8680000 44.2610000 51.9140000 4.1520000 46.9400000 53.1530000 - 2.4450000 44.4700000 52.6120000 2.2620000 46.0620000 51.8390000 - 0.6440000 45.7450000 53.7090000 1.7320000 47.0950000 53.9940000 - 1.9120000 44.3090000 55.2080000 1.5180000 45.7910000 56.0450000 - 3.8840000 45.0420000 56.4170000 3.7720000 46.6620000 55.7380000 - 5.5120000 45.4300000 54.5590000 4.4080000 44.0930000 54.2310000 - 3.0260000 40.2190000 48.4100000 8.3360000 42.2630000 48.3650000 - 6.6890000 41.7190000 46.7790000 3.7420000 38.3820000 45.1710000 - 4.1490000 38.3470000 46.8720000 11.7600000 45.2510000 50.8100000 - 5.9090000 42.0990000 50.9680000 3.7310000 40.9590000 50.6600000 diff --git a/femto/fe/tests/data/cdk2/1oiu.sdf b/femto/fe/tests/data/cdk2/1oiu.sdf new file mode 100644 index 0000000..ca01642 --- /dev/null +++ b/femto/fe/tests/data/cdk2/1oiu.sdf @@ -0,0 +1,193 @@ +1OIU + 3D + Schrodinger Suite 2022-2. + 50 53 0 0 1 0 999 V2000 + 7.9160 43.6120 49.8510 C 0 0 0 0 0 0 + 10.5520 46.4410 52.0420 C 0 0 0 0 0 0 + 4.8670 45.3510 51.9070 C 0 0 0 0 0 0 + 4.0240 45.8620 53.0570 C 0 0 0 0 0 0 + 2.5620 45.5450 52.7500 C 0 0 0 0 0 0 + 1.6820 46.0090 53.9120 C 0 0 0 0 0 0 + 2.1170 45.3790 55.2320 C 0 0 0 0 0 0 + 3.6010 45.6060 55.5280 C 0 0 0 0 0 0 + 4.4730 45.1740 54.3530 C 0 0 0 0 0 0 + 3.9800 40.7090 48.5370 C 0 0 0 0 0 0 + 4.8080 40.9170 47.4370 C 0 0 0 0 0 0 + 7.6260 42.6110 48.9930 N 0 0 0 0 0 0 + 6.0320 41.5580 47.6210 C 0 0 0 0 0 0 + 6.4230 41.9980 48.8840 C 0 0 0 0 0 0 + 4.0430 38.8600 46.0080 N 0 0 0 0 0 0 + 6.9350 44.2280 50.5510 N 0 0 0 0 0 0 + 7.1960 45.2310 51.4250 C 0 0 0 0 0 0 + 6.1920 45.8270 52.1090 O 0 0 0 0 0 0 + 8.5840 45.6720 51.6220 C 0 0 0 0 0 0 + 9.2230 46.6190 52.3980 N 0 0 0 0 0 0 + 10.8110 45.4370 51.1020 N 0 0 0 0 0 0 + 9.5550 44.9660 50.8410 C 0 0 0 0 0 0 + 9.2100 43.9690 49.9980 N 0 0 0 0 0 0 + 5.5980 41.7740 49.9860 C 0 0 0 0 0 0 + 4.3750 41.1310 49.8100 C 0 0 0 0 0 0 + 4.3510 40.4270 45.9660 S 0 0 0 0 0 0 + 5.3810 40.6740 45.0070 O 0 0 0 0 0 0 + 3.1650 41.1500 45.6150 O 0 0 0 0 0 0 + 11.3175 47.0605 52.4855 H 0 0 0 0 0 0 + 4.4785 45.7468 50.9686 H 0 0 0 0 0 0 + 4.8681 44.2610 51.9144 H 0 0 0 0 0 0 + 4.1517 46.9403 53.1526 H 0 0 0 0 0 0 + 2.4447 44.4701 52.6124 H 0 0 0 0 0 0 + 2.2621 46.0620 51.8385 H 0 0 0 0 0 0 + 0.6441 45.7452 53.7088 H 0 0 0 0 0 0 + 1.7325 47.0947 53.9942 H 0 0 0 0 0 0 + 1.9120 44.3087 55.2076 H 0 0 0 0 0 0 + 1.5184 45.7906 56.0446 H 0 0 0 0 0 0 + 3.8841 45.0418 56.4166 H 0 0 0 0 0 0 + 3.7718 46.6619 55.7376 H 0 0 0 0 0 0 + 5.5122 45.4304 54.5588 H 0 0 0 0 0 0 + 4.4082 44.0928 54.2312 H 0 0 0 0 0 0 + 3.0260 40.2190 48.4098 H 0 0 0 0 0 0 + 8.3361 42.2626 48.3650 H 0 0 0 0 0 0 + 6.6894 41.7186 46.7793 H 0 0 0 0 0 0 + 3.7424 38.3818 45.1707 H 0 0 0 0 0 0 + 4.1489 38.3475 46.8719 H 0 0 0 0 0 0 + 11.7599 45.2510 50.8102 H 0 0 0 0 0 0 + 5.9088 42.0985 50.9681 H 0 0 0 0 0 0 + 3.7313 40.9587 50.6599 H 0 0 0 0 0 0 + 1 12 1 0 0 0 + 1 16 2 0 0 0 + 1 23 1 0 0 0 + 2 20 2 0 0 0 + 2 21 1 0 0 0 + 2 29 1 0 0 0 + 3 4 1 0 0 0 + 3 18 1 0 0 0 + 3 30 1 0 0 0 + 3 31 1 0 0 0 + 4 5 1 0 0 0 + 4 9 1 0 0 0 + 4 32 1 0 0 0 + 5 6 1 0 0 0 + 5 33 1 0 0 0 + 5 34 1 0 0 0 + 6 7 1 0 0 0 + 6 35 1 0 0 0 + 6 36 1 0 0 0 + 7 8 1 0 0 0 + 7 37 1 0 0 0 + 7 38 1 0 0 0 + 8 9 1 0 0 0 + 8 39 1 0 0 0 + 8 40 1 0 0 0 + 9 41 1 0 0 0 + 9 42 1 0 0 0 + 10 11 2 0 0 0 + 10 25 1 0 0 0 + 10 43 1 0 0 0 + 11 13 1 0 0 0 + 11 26 1 0 0 0 + 12 14 1 0 0 0 + 12 44 1 0 0 0 + 13 14 2 0 0 0 + 13 45 1 0 0 0 + 14 24 1 0 0 0 + 15 26 1 0 0 0 + 15 46 1 0 0 0 + 15 47 1 0 0 0 + 16 17 1 0 0 0 + 17 18 1 0 0 0 + 17 19 2 0 0 0 + 19 20 1 0 0 0 + 19 22 1 0 0 0 + 21 22 1 0 0 0 + 21 48 1 0 0 0 + 22 23 2 0 0 0 + 24 25 2 0 0 0 + 24 49 1 0 0 0 + 25 50 1 0 0 0 + 26 27 2 0 0 0 + 26 28 2 0 0 0 +M END +> +5 + +> +1oiu.1 + +> +STRUCTURE OF HUMAN THR160-PHOSPHO CDK2/CYCLIN A COMPLEXED WITH A 6-CYCLOHEXYLMETHYLOXY-2-ANILINO-PURINE INHIBITOR + +> +1OIU + +> +73.648 + +> +133.778 + +> +148.228 + +> +90 + +> +90 + +> +90 + +> +P 21 21 21 + +> +8 + +> +KINASE + +> +26-JUN-03 + +> +3.15 + +> +0.2158 + +> +0.2532 + +> +2 + +> +X-RAY DIFFRACTION + +> +100 + +> +7 + +> +A, B + +> +1.000000 0.000000 0.000000 0.000000;0.000000 1.000000 0.000000 0.000000;0.000000 0.000000 1.000000 0.000000 + +> +C, D + +> +1.000000 0.000000 0.000000 0.000000;0.000000 1.000000 0.000000 0.000000;0.000000 0.000000 1.000000 0.000000 + +> +/Users/sheenam + +> +1oiu.pdb + +> +1 + +$$$$ diff --git a/femto/fe/tests/data/cdk2/1oiu.xml b/femto/fe/tests/data/cdk2/1oiu.xml new file mode 100644 index 0000000..2b6951f --- /dev/null +++ b/femto/fe/tests/data/cdk2/1oiu.xml @@ -0,0 +1,549 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/femto/fe/tests/data/temoa/g1.mol2 b/femto/fe/tests/data/temoa/g1.mol2 new file mode 100644 index 0000000..553692f --- /dev/null +++ b/femto/fe/tests/data/temoa/g1.mol2 @@ -0,0 +1,51 @@ +@MOLECULE +***** + 21 22 0 0 0 +SMALL +USER_CHARGES + +@ATOM + 1 C1 -0.4966 -2.0341 0.4940 C.ar 1 UNL1 -0.1736 + 2 C2 0.3500 -1.0275 0.0392 C.ar 1 UNL1 -0.1415 + 3 C3 -0.0479 -3.3542 0.5448 C.ar 1 UNL1 -0.0955 + 4 C4 1.6484 -1.3369 -0.3665 C.ar 1 UNL1 -0.1367 + 5 C5 1.7243 -5.0072 0.1848 C.ar 1 UNL1 -0.0907 + 6 C6 3.4207 -2.9899 -0.7267 C.ar 1 UNL1 -0.2873 + 7 C7 1.2560 -3.6839 0.1403 C.ar 1 UNL1 -0.0746 + 8 C8 2.1168 -2.6602 -0.3222 C.ar 1 UNL1 -0.0000 + 9 C9 3.0228 -5.3167 -0.2210 C.ar 1 UNL1 -0.1331 + 10 C10 3.8693 -4.3100 -0.6758 C.ar 1 UNL1 0.1768 + 11 C11 3.4818 -6.7161 -0.1618 C.2 1 UNL1 0.9141 + 12 O1 2.6670 -7.6159 0.2654 O.co2 1 UNL1 -0.8392 + 13 O2 4.6814 -6.9892 -0.5392 O.co2 1 UNL1 -0.7972 + 14 O3 5.1411 -4.5934 -1.0757 O.3 1 UNL1 -0.4329 + 15 H1 -1.5076 -1.7931 0.8095 H 1 UNL1 0.1110 + 16 H2 0.0006 0.0002 0.0000 H 1 UNL1 0.1072 + 17 H3 -0.7272 -4.1246 0.9034 H 1 UNL1 0.1245 + 18 H4 2.2924 -0.5342 -0.7199 H 1 UNL1 0.1138 + 19 H5 1.0705 -5.8025 0.5393 H 1 UNL1 0.1621 + 20 H6 4.0949 -2.2136 -1.0843 H 1 UNL1 0.1118 + 21 H7 5.5907 -3.7825 -1.3603 H 1 UNL1 0.3810 +@BOND + 1 1 2 ar + 2 1 3 ar + 3 2 4 ar + 4 3 7 ar + 5 4 8 ar + 6 5 7 ar + 7 5 9 ar + 8 6 8 ar + 9 6 10 ar + 10 7 8 ar + 11 9 10 ar + 12 9 11 1 + 13 10 14 1 + 14 11 12 1 + 15 11 13 2 + 16 1 15 1 + 17 2 16 1 + 18 3 17 1 + 19 4 18 1 + 20 5 19 1 + 21 6 20 1 + 22 14 21 1 diff --git a/femto/fe/tests/data/temoa/g1.parm7 b/femto/fe/tests/data/temoa/g1.parm7 deleted file mode 100644 index b0948f7..0000000 --- a/femto/fe/tests/data/temoa/g1.parm7 +++ /dev/null @@ -1,225 +0,0 @@ -%VERSION VERSION_STAMP = V0001.000 DATE = 04/01/23 10:04:34 -%FLAG TITLE -%FORMAT(20a4) -LIG -%FLAG POINTERS -%FORMAT(10I8) - 21 5 7 15 13 21 29 32 0 0 - 106 1 15 21 32 6 7 4 6 0 - 0 0 0 0 0 0 0 0 21 0 - 0 -%FLAG ATOM_NAME -%FORMAT(20a4) -C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 O12 O13 O14 H15 H16 H17 H18 H19 H20 -H21 -%FLAG CHARGE -%FORMAT(5E16.8) - -3.17068020E+00 -2.56934430E+00 -2.49645510E+00 0.00000000E+00 -5.22980010E+00 - 3.22716933E+00 -2.41627698E+00 -1.65822930E+00 -1.34845020E+00 -1.74934080E+00 - 1.66588267E+01 -1.49113081E+01 -1.49113081E+01 -7.91030043E+00 2.02267530E+00 - 1.96800840E+00 2.05911990E+00 2.04089760E+00 2.97023490E+00 2.25956520E+00 - 6.94269630E+00 -%FLAG ATOMIC_NUMBER -%FORMAT(10I8) - 6 6 6 6 6 6 6 6 6 6 - 6 8 8 8 1 1 1 1 1 1 - 1 -%FLAG MASS -%FORMAT(5E16.8) - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.60000000E+01 1.60000000E+01 1.60000000E+01 1.00800000E+00 - 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.00800000E+00 - 1.00800000E+00 -%FLAG ATOM_TYPE_INDEX -%FORMAT(10I8) - 1 1 1 1 1 1 1 1 1 1 - 1 2 2 3 4 4 4 4 4 4 - 5 -%FLAG NUMBER_EXCLUDED_ATOMS -%FORMAT(10I8) - 10 9 10 12 10 10 10 8 7 4 - 4 1 1 2 2 1 1 1 1 1 - 1 -%FLAG NONBONDED_PARM_INDEX -%FORMAT(10I8) - 1 2 4 7 11 2 3 5 8 12 - 4 5 6 9 13 7 8 9 10 14 - 11 12 13 14 15 -%FLAG RESIDUE_LABEL -%FORMAT(20a4) -LIG -%FLAG RESIDUE_POINTER -%FORMAT(10I8) - 1 -%FLAG BOND_FORCE_CONSTANT -%FORMAT(5E16.8) - 3.78600000E+02 3.95700000E+02 3.65600000E+02 2.72700000E+02 6.52600000E+02 - 5.63500000E+02 -%FLAG BOND_EQUIL_VALUE -%FORMAT(5E16.8) - 1.39800000E+00 1.08600000E+00 1.36400000E+00 1.49100000E+00 1.21800000E+00 - 9.73000000E-01 -%FLAG ANGLE_FORCE_CONSTANT -%FORMAT(5E16.8) - 6.88000000E+01 4.87000000E+01 8.72000000E+01 6.64000000E+01 5.07000000E+01 - 8.62000000E+01 1.18800000E+02 -%FLAG ANGLE_EQUIL_VALUE -%FORMAT(5E16.8) - 2.09474507E+00 2.09230160E+00 2.09265067E+00 2.10015559E+00 1.89507931E+00 - 2.13977458E+00 2.27329233E+00 -%FLAG DIHEDRAL_FORCE_CONSTANT -%FORMAT(5E16.8) - 3.62500000E+00 8.35000000E-01 5.22500000E-01 1.10000000E+00 -%FLAG DIHEDRAL_PERIODICITY -%FORMAT(5E16.8) - 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 -%FLAG DIHEDRAL_PHASE -%FORMAT(5E16.8) - 3.14159400E+00 3.14159400E+00 3.14159400E+00 3.14159400E+00 -%FLAG SCEE_SCALE_FACTOR -%FORMAT(5E16.8) - 1.20000000E+00 1.20000000E+00 1.20000000E+00 0.00000000E+00 -%FLAG SCNB_SCALE_FACTOR -%FORMAT(5E16.8) - 2.00000000E+00 2.00000000E+00 2.00000000E+00 0.00000000E+00 -%FLAG SOLTY -%FORMAT(5E16.8) - 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 - 0.00000000E+00 -%FLAG LENNARD_JONES_ACOEF -%FORMAT(5E16.8) - 6.96551276E+05 5.17507491E+05 3.76435470E+05 5.92438683E+05 4.37717034E+05 - 5.03152404E+05 7.52549148E+04 5.27277400E+04 6.30298721E+04 6.90855956E+03 - 2.25372434E+02 1.15813907E+02 1.74176419E+02 8.53132435E+00 1.10360882E-05 -%FLAG LENNARD_JONES_BCOEF -%FORMAT(5E16.8) - 5.24668528E+02 4.98871431E+02 4.69350655E+02 4.76608379E+02 4.51916854E+02 - 4.32634597E+02 1.09570372E+02 1.01173582E+02 9.87712192E+01 2.10929191E+01 - 4.40751799E+00 3.48534600E+00 3.81654050E+00 5.44840215E-01 4.55498033E-04 -%FLAG BONDS_INC_HYDROGEN -%FORMAT(10I8) - 0 42 2 3 45 2 6 48 2 12 - 51 2 21 54 2 27 57 2 39 60 - 6 -%FLAG BONDS_WITHOUT_HYDROGEN -%FORMAT(10I8) - 0 27 1 0 3 1 3 6 1 6 - 9 1 9 24 1 9 12 1 12 15 - 1 15 18 1 15 39 3 18 21 1 - 18 30 4 21 24 1 24 27 1 30 - 33 5 30 36 5 -%FLAG ANGLES_INC_HYDROGEN -%FORMAT(10I8) - 0 27 57 2 0 3 45 2 3 6 - 48 2 3 0 42 2 6 3 45 2 - 9 12 51 2 9 6 48 2 15 39 - 60 5 15 12 51 2 18 21 54 2 - 24 27 57 2 24 21 54 2 27 0 - 42 2 -%FLAG ANGLES_WITHOUT_HYDROGEN -%FORMAT(10I8) - 0 27 24 1 0 3 6 1 3 6 - 9 1 3 0 27 1 6 9 24 1 - 6 9 12 1 9 24 27 1 9 24 - 21 1 9 12 15 1 12 15 18 1 - 12 15 39 3 12 9 24 1 15 18 - 21 1 15 18 30 4 18 21 24 1 - 18 30 33 6 18 30 36 6 18 15 - 39 3 21 24 27 1 21 18 30 4 - 33 30 36 7 -%FLAG DIHEDRALS_INC_HYDROGEN -%FORMAT(10I8) - 0 3 6 48 1 3 0 27 57 1 - 6 9 12 51 1 42 0 3 6 1 - 9 24 27 57 1 9 24 21 54 1 - 9 6 3 45 1 12 15 39 60 2 - 12 9 6 48 1 15 18 21 54 1 - 18 15 39 60 2 18 15 12 51 1 - 21 24 27 57 1 42 0 27 24 1 - 24 9 12 51 1 24 9 6 48 1 - 27 24 21 54 1 27 0 3 45 1 - 30 18 21 54 1 39 15 12 51 1 - 42 0 27 57 1 42 0 3 45 1 - 45 3 6 48 1 42 0 -27 -3 4 - 0 6 -3 -45 4 3 9 -6 -48 4 - 9 15 -12 -51 4 18 24 -21 -54 4 - 0 24 -27 -57 4 -%FLAG DIHEDRALS_WITHOUT_HYDROGEN -%FORMAT(10I8) - 0 27 24 21 1 0 27 24 9 1 - 0 3 -6 9 1 3 6 9 24 1 - 3 6 9 12 1 3 0 -27 24 1 - 6 9 24 27 1 6 9 24 21 1 - 6 9 12 15 1 27 0 -3 6 1 - 9 24 21 18 1 9 12 -15 18 1 - 9 12 15 39 1 12 15 18 21 1 - 12 15 18 30 1 12 9 24 27 1 - 12 9 -24 21 1 15 18 21 24 1 - 15 18 30 33 3 15 18 30 36 3 - 15 12 -9 24 1 18 21 24 27 1 - 21 18 30 33 3 21 18 30 36 3 - 21 18 15 39 1 24 21 18 30 1 - 30 18 15 39 1 6 12 -9 -24 4 - 12 18 -15 -39 4 30 15 -18 -21 4 - 9 21 -24 -27 4 18 33 -30 -36 4 -%FLAG EXCLUDED_ATOMS_LIST -%FORMAT(10I8) - 2 3 4 8 9 10 15 16 17 20 - 3 4 5 9 10 15 16 17 20 4 - 5 6 8 9 10 15 16 17 18 5 - 6 7 8 9 10 14 16 17 18 19 - 20 6 7 8 9 10 11 14 17 18 - 21 7 8 9 11 12 13 14 18 19 - 21 8 9 10 11 12 13 14 18 19 - 21 9 10 11 12 13 14 19 20 10 - 11 15 17 18 19 20 15 16 19 20 - 12 13 14 19 13 0 18 21 16 20 - 17 0 0 0 0 0 -%FLAG HBOND_ACOEF -%FORMAT(5E16.8) - -%FLAG HBOND_BCOEF -%FORMAT(5E16.8) - -%FLAG HBCUT -%FORMAT(5E16.8) - -%FLAG AMBER_ATOM_TYPE -%FORMAT(20a4) -ca ca ca ca ca ca ca ca ca ca c o o oh ha ha ha ha ha ha -ho -%FLAG TREE_CHAIN_CLASSIFICATION -%FORMAT(20a4) -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -BLA -%FLAG JOIN_ARRAY -%FORMAT(10I8) - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 -%FLAG IROTAT -%FORMAT(10I8) - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 -%FLAG RADIUS_SET -%FORMAT(1a80) -modified Bondi radii (mbondi) -%FLAG RADII -%FORMAT(5E16.8) - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.50000000E+00 1.50000000E+00 1.50000000E+00 1.30000000E+00 - 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.30000000E+00 - 8.00000000E-01 -%FLAG SCREEN -%FORMAT(5E16.8) - 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 8.50000000E-01 -%FLAG IPOL -%FORMAT(1I8) - 0 diff --git a/femto/fe/tests/data/temoa/g1.rst7 b/femto/fe/tests/data/temoa/g1.rst7 deleted file mode 100644 index 8833b28..0000000 --- a/femto/fe/tests/data/temoa/g1.rst7 +++ /dev/null @@ -1,13 +0,0 @@ -LIG - 21 - 22.1758000 25.1109000 24.2844000 21.6948000 26.0969000 23.4294000 - 22.0178000 27.4349000 23.6414000 22.8418000 27.8079000 24.7334000 - 23.1908000 29.1529000 24.9804000 24.0028000 29.5009000 26.0604000 - 24.5028000 28.5139000 26.9424000 24.1558000 27.1739000 26.6974000 - 23.3338000 26.7989000 25.6084000 22.9868000 25.4459000 25.3654000 - 25.3858000 28.8319000 28.1264000 25.7718000 27.8789000 28.8424000 - 25.6998000 30.0219000 28.3534000 24.2728000 30.8409000 26.1974000 - 21.9178000 24.0769000 24.1084000 21.0668000 25.8229000 22.5954000 - 21.6408000 28.1959000 22.9724000 22.8168000 29.9189000 24.3144000 - 24.5128000 26.3839000 27.3434000 23.3568000 24.6739000 26.0254000 - 23.8588000 31.3769000 25.5174000 diff --git a/femto/fe/tests/data/temoa/g1.xml b/femto/fe/tests/data/temoa/g1.xml new file mode 100644 index 0000000..a06bf41 --- /dev/null +++ b/femto/fe/tests/data/temoa/g1.xml @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/femto/fe/tests/data/temoa/g4.mol2 b/femto/fe/tests/data/temoa/g4.mol2 new file mode 100644 index 0000000..2a8e0ba --- /dev/null +++ b/femto/fe/tests/data/temoa/g4.mol2 @@ -0,0 +1,43 @@ +@MOLECULE +***** + 17 18 0 0 0 +SMALL +USER_CHARGES + +@ATOM + 1 C1 0.8726 1.2794 1.5062 C.ar 1 UNL1 -0.1102 + 2 C2 0.7042 1.9049 2.7438 C.ar 1 UNL1 -0.1411 + 3 C3 -1.3491 0.2943 1.6535 C.ar 1 UNL1 -0.0782 + 4 C4 -0.1481 0.4787 0.9641 C.ar 1 UNL1 -0.1297 + 5 C5 -0.4910 1.7067 3.4041 C.ar 1 UNL1 0.0191 + 6 C6 -1.4901 0.9230 2.8735 C.ar 1 UNL1 0.0170 + 7 C7 0.0509 -0.1711 -0.3421 C.2 1 UNL1 0.9098 + 8 C8 -2.1588 1.6916 4.7825 C.3 1 UNL1 0.3249 + 9 O1 -0.8957 -0.9006 -0.8214 O.co2 1 UNL1 -0.8269 + 10 O2 1.1650 0.0117 -0.9613 O.co2 1 UNL1 -0.8284 + 11 O3 -0.8328 2.2326 4.6117 O.3 1 UNL1 -0.3569 + 12 O4 -2.5825 0.8600 3.6827 O.3 1 UNL1 -0.3505 + 13 H1 1.8043 1.4193 0.9628 H 1 UNL1 0.1610 + 14 H2 1.4890 2.5235 3.1639 H 1 UNL1 0.1252 + 15 H3 -2.1453 -0.3210 1.2481 H 1 UNL1 0.1738 + 16 H4 -2.1779 1.0939 5.7013 H 1 UNL1 0.0458 + 17 H5 -2.8663 2.5220 4.8879 H 1 UNL1 0.0454 +@BOND + 1 1 2 ar + 2 1 4 ar + 3 2 5 ar + 4 3 4 ar + 5 3 6 ar + 6 4 7 1 + 7 5 6 ar + 8 5 11 1 + 9 6 12 1 + 10 7 9 1 + 11 7 10 2 + 12 8 11 1 + 13 8 12 1 + 14 1 13 1 + 15 2 14 1 + 16 3 15 1 + 17 8 16 1 + 18 8 17 1 diff --git a/femto/fe/tests/data/temoa/g4.parm7 b/femto/fe/tests/data/temoa/g4.parm7 deleted file mode 100644 index a4ce47e..0000000 --- a/femto/fe/tests/data/temoa/g4.parm7 +++ /dev/null @@ -1,204 +0,0 @@ -%VERSION VERSION_STAMP = V0001.000 DATE = 04/01/23 10:04:39 -%FLAG TITLE -%FORMAT(20a4) -LIG -%FLAG POINTERS -%FORMAT(10I8) - 17 6 5 13 11 18 18 27 0 0 - 81 1 13 18 27 7 10 5 7 0 - 0 0 0 0 0 0 0 0 17 0 - 0 -%FLAG ATOM_NAME -%FORMAT(20a4) -C1 C2 C3 C4 C5 C6 C7 O8 O9 O10 C11 O12 H13 H14 H15 H16 H17 -%FLAG CHARGE -%FORMAT(5E16.8) - -2.07734220E+00 -2.53289970E+00 3.48045930E-01 2.93379030E-01 -1.42133940E+00 - -2.32516548E+00 1.65859375E+01 -1.50661976E+01 -1.50661976E+01 -6.37598277E+00 - 5.92589196E+00 -6.50353887E+00 2.89734570E+00 2.33245440E+00 3.15245790E+00 - 8.32759110E-01 8.32759110E-01 -%FLAG ATOMIC_NUMBER -%FORMAT(10I8) - 6 6 6 6 6 6 6 8 8 8 - 6 8 1 1 1 1 1 -%FLAG MASS -%FORMAT(5E16.8) - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.20100000E+01 1.60000000E+01 1.60000000E+01 1.60000000E+01 - 1.20100000E+01 1.60000000E+01 1.00800000E+00 1.00800000E+00 1.00800000E+00 - 1.00800000E+00 1.00800000E+00 -%FLAG ATOM_TYPE_INDEX -%FORMAT(10I8) - 1 1 1 1 1 1 1 2 2 3 - 4 3 5 5 5 6 6 -%FLAG NUMBER_EXCLUDED_ATOMS -%FORMAT(10I8) - 12 10 11 10 9 7 4 1 1 5 - 3 3 1 1 1 1 1 -%FLAG NONBONDED_PARM_INDEX -%FORMAT(10I8) - 1 2 4 7 11 16 2 3 5 8 - 12 17 4 5 6 9 13 18 7 8 - 9 10 14 19 11 12 13 14 15 20 - 16 17 18 19 20 21 -%FLAG RESIDUE_LABEL -%FORMAT(20a4) -LIG -%FLAG RESIDUE_POINTER -%FORMAT(10I8) - 1 -%FLAG BOND_FORCE_CONSTANT -%FORMAT(5E16.8) - 3.78600000E+02 3.95700000E+02 3.57500000E+02 2.72700000E+02 6.52600000E+02 - 2.84800000E+02 3.77300000E+02 -%FLAG BOND_EQUIL_VALUE -%FORMAT(5E16.8) - 1.39800000E+00 1.08600000E+00 1.37000000E+00 1.49100000E+00 1.21800000E+00 - 1.43200000E+00 1.09600000E+00 -%FLAG ANGLE_FORCE_CONSTANT -%FORMAT(5E16.8) - 6.64000000E+01 6.88000000E+01 4.87000000E+01 8.73000000E+01 6.61000000E+01 - 8.62000000E+01 1.18800000E+02 1.10900000E+02 6.24000000E+01 3.85000000E+01 -%FLAG ANGLE_EQUIL_VALUE -%FORMAT(5E16.8) - 2.10015559E+00 2.09474507E+00 2.09230160E+00 2.08043336E+00 2.05879127E+00 - 2.13977458E+00 2.27329233E+00 1.89001786E+00 1.91253261E+00 1.92335366E+00 -%FLAG DIHEDRAL_FORCE_CONSTANT -%FORMAT(5E16.8) - 5.22500000E-01 3.62500000E+00 1.61000000E+00 3.83333333E-01 1.10000000E+00 -%FLAG DIHEDRAL_PERIODICITY -%FORMAT(5E16.8) - 2.00000000E+00 2.00000000E+00 2.00000000E+00 3.00000000E+00 2.00000000E+00 -%FLAG DIHEDRAL_PHASE -%FORMAT(5E16.8) - 3.14159400E+00 3.14159400E+00 3.14159400E+00 0.00000000E+00 3.14159400E+00 -%FLAG SCEE_SCALE_FACTOR -%FORMAT(5E16.8) - 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 0.00000000E+00 -%FLAG SCNB_SCALE_FACTOR -%FORMAT(5E16.8) - 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 0.00000000E+00 -%FLAG SOLTY -%FORMAT(5E16.8) - 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 - 0.00000000E+00 0.00000000E+00 -%FLAG LENNARD_JONES_ACOEF -%FORMAT(5E16.8) - 6.96551276E+05 5.17507491E+05 3.76435470E+05 4.46121722E+05 3.27367585E+05 - 2.83662575E+05 8.43969952E+05 6.30925504E+05 5.42502534E+05 1.02073671E+06 - 7.52549148E+04 5.27277400E+04 4.65732396E+04 9.27554496E+04 6.90855956E+03 - 3.85565555E+04 2.59855087E+04 2.33261683E+04 4.80619274E+04 3.17496391E+03 - 1.35510742E+03 -%FLAG LENNARD_JONES_BCOEF -%FORMAT(5E16.8) - 5.24668528E+02 4.98871431E+02 4.69350655E+02 3.88758656E+02 3.67361018E+02 - 2.87011518E+02 5.90251928E+02 5.62969425E+02 4.38146883E+02 6.63431737E+02 - 1.09570372E+02 1.01173582E+02 7.98066064E+01 1.24325688E+02 2.10929191E+01 - 8.36149973E+01 7.57220777E+01 6.02145835E+01 9.54115851E+01 1.52447987E+01 - 1.06181419E+01 -%FLAG BONDS_INC_HYDROGEN -%FORMAT(10I8) - 0 36 2 3 39 2 12 42 2 30 - 45 7 30 48 7 -%FLAG BONDS_WITHOUT_HYDROGEN -%FORMAT(10I8) - 0 15 1 0 3 1 3 6 1 6 - 33 3 6 9 1 9 12 1 9 27 - 3 12 15 1 15 18 4 18 21 5 - 18 24 5 27 30 6 30 33 6 -%FLAG ANGLES_INC_HYDROGEN -%FORMAT(10I8) - 0 3 39 3 3 0 36 3 6 3 - 39 3 9 12 42 3 15 12 42 3 - 15 0 36 3 27 30 45 9 27 30 - 48 9 33 30 45 9 33 30 48 9 - 45 30 48 10 -%FLAG ANGLES_WITHOUT_HYDROGEN -%FORMAT(10I8) - 0 15 18 1 0 15 12 2 0 3 - 6 2 3 6 33 4 3 6 9 2 - 3 0 15 2 6 33 30 5 6 9 - 12 2 6 9 27 4 9 12 15 2 - 9 27 30 5 9 6 33 4 12 15 - 18 1 12 9 27 4 15 18 21 6 - 15 18 24 6 21 18 24 7 27 30 - 33 8 -%FLAG DIHEDRALS_INC_HYDROGEN -%FORMAT(10I8) - 0 15 12 42 2 6 33 30 45 4 - 6 33 30 48 4 6 9 12 42 2 - 36 0 3 6 2 9 27 30 45 4 - 9 27 30 48 4 9 6 3 39 2 - 36 0 15 12 2 15 0 3 39 2 - 18 15 12 42 2 36 0 15 18 2 - 27 9 12 42 2 33 6 3 39 2 - 36 0 3 39 2 36 0 -15 -3 5 - 0 6 -3 -39 5 9 15 -12 -42 5 -%FLAG DIHEDRALS_WITHOUT_HYDROGEN -%FORMAT(10I8) - 0 15 18 21 1 0 15 18 24 1 - 0 15 12 9 2 0 3 6 33 2 - 0 3 -6 9 2 3 6 33 30 3 - 3 6 9 12 2 3 6 9 27 2 - 3 0 15 18 2 3 0 -15 12 2 - 6 33 -30 27 4 6 9 12 15 2 - 6 9 -27 30 3 15 0 -3 6 2 - 9 12 15 18 2 9 27 -30 33 4 - 9 6 -33 30 3 12 15 18 21 1 - 12 15 18 24 1 12 9 27 30 3 - 12 9 6 33 2 15 12 9 27 2 - 27 9 -6 33 2 3 9 -6 -33 5 - 6 12 -9 -27 5 18 0 -15 -12 5 - 15 21 -18 -24 5 -%FLAG EXCLUDED_ATOMS_LIST -%FORMAT(10I8) - 2 3 4 5 6 7 8 9 12 13 - 14 15 3 4 5 6 7 10 11 12 - 13 14 4 5 6 10 11 12 13 14 - 15 16 17 5 6 7 10 11 12 14 - 15 16 17 6 7 8 9 10 11 12 - 13 15 7 8 9 10 13 14 15 8 - 9 13 15 9 0 11 12 15 16 17 - 12 16 17 14 16 17 14 0 0 17 - 0 -%FLAG HBOND_ACOEF -%FORMAT(5E16.8) - -%FLAG HBOND_BCOEF -%FORMAT(5E16.8) - -%FLAG HBCUT -%FORMAT(5E16.8) - -%FLAG AMBER_ATOM_TYPE -%FORMAT(20a4) -ca ca ca ca ca ca c o o os c3 os ha ha ha h2 h2 -%FLAG TREE_CHAIN_CLASSIFICATION -%FORMAT(20a4) -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -%FLAG JOIN_ARRAY -%FORMAT(10I8) - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 -%FLAG IROTAT -%FORMAT(10I8) - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 -%FLAG RADIUS_SET -%FORMAT(1a80) -modified Bondi radii (mbondi) -%FLAG RADII -%FORMAT(5E16.8) - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.70000000E+00 1.50000000E+00 1.50000000E+00 1.50000000E+00 - 1.70000000E+00 1.50000000E+00 1.30000000E+00 1.30000000E+00 1.30000000E+00 - 1.30000000E+00 1.30000000E+00 -%FLAG SCREEN -%FORMAT(5E16.8) - 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 7.20000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 7.20000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 8.50000000E-01 8.50000000E-01 -%FLAG IPOL -%FORMAT(1I8) - 0 diff --git a/femto/fe/tests/data/temoa/g4.rst7 b/femto/fe/tests/data/temoa/g4.rst7 deleted file mode 100644 index 7cc4355..0000000 --- a/femto/fe/tests/data/temoa/g4.rst7 +++ /dev/null @@ -1,11 +0,0 @@ - - 17 0.0000000e+00 - 24.0388000 29.4589000 26.0724000 23.2108000 29.1379000 24.9884000 - 22.8538000 27.8059000 24.8224000 23.2868000 26.8359000 25.6774000 - 24.1088000 27.1259000 26.7614000 24.4988000 28.4669000 26.9714000 - 25.3858000 28.8319000 28.1264000 25.7638000 27.9149000 28.8904000 - 25.7018000 30.0359000 28.2674000 22.7918000 25.6329000 25.2864000 - 22.0198000 25.8749000 24.1384000 22.0708000 27.2509000 23.8624000 - 24.3198000 30.4939000 26.2064000 22.8688000 29.9069000 24.3124000 - 24.4358000 26.3359000 27.4214000 22.4428000 25.3239000 23.2974000 - 20.9868000 25.5859000 24.3324000 diff --git a/femto/fe/tests/data/temoa/g4.xml b/femto/fe/tests/data/temoa/g4.xml new file mode 100644 index 0000000..5344269 --- /dev/null +++ b/femto/fe/tests/data/temoa/g4.xml @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/femto/fe/tests/data/temoa/temoa.parm7 b/femto/fe/tests/data/temoa/temoa.parm7 deleted file mode 100644 index 16caf53..0000000 --- a/femto/fe/tests/data/temoa/temoa.parm7 +++ /dev/null @@ -1,1112 +0,0 @@ -%VERSION VERSION_STAMP = V0001.000 DATE = 04/01/23 10:05:22 -%FLAG TITLE -%FORMAT(20a4) -<0> -%FLAG POINTERS -%FORMAT(10I8) - 196 7 64 152 144 232 288 392 0 0 - 1141 1 152 232 392 11 22 18 8 0 - 0 0 0 0 0 0 0 0 196 0 - 0 -%FLAG ATOM_NAME -%FORMAT(20a4) -H1 C1 C2 C3 C4 C5 C6 H2 C7 H3 C8 H4 C9 C10 C11 C12 C13 C14 H5 H6 -C15 C16 C17 C18 C19 C20 H7 H8 C21 H9 C22 H10 C23 C24 C25 C26 C27 C28 H11 H12 -C29 H13 C30 H14 O1 O2 O3 O4 O5 O6 C31 H15 C32 H16 O7 C33 H17 O8 C34 H18 -C35 C36 C37 C38 C39 C40 H19 H20 C41 C42 C43 C44 C45 C46 H21 H22 C47 C48 C49 C50 -C51 C52 H23 H24 C53 C54 C55 C56 C57 C58 H25 H26 O9 O10 O11 O12 O13 O14 O15 O16 -C59 C60 C61 C62 C63 C64 H27 H28 C65 C66 C67 C68 C69 C70 H29 H30 C71 C72 C73 C74 -C75 C76 H31 H32 C77 C78 C79 C80 C81 C82 H33 H34 H35 C83 C84 H36 H37 H38 H39 H40 -C85 C86 C87 C88 O17 O18 O19 O20 C89 H41 H42 C90 H43 H44 C91 H45 H46 C92 H47 H48 -C93 C94 C95 C96 O21 O22 O23 O24 H49 H50 H51 H52 O25 O26 O27 O28 O29 O30 O31 O32 -C97 H61 H62 H63 C98 H64 H65 H66 C99 H67 H68 H69 C100H70 H71 H72 -%FLAG CHARGE -%FORMAT(5E16.8) - 2.58756660E+00 -2.95656818E+00 -1.67189603E+00 1.19310509E+00 1.19310509E+00 - -9.39359565E-01 -9.39359565E-01 2.92923473E+00 1.80400770E-01 1.75298526E+00 - 1.80400770E-01 1.75298526E+00 -9.39359565E-01 1.19310509E+00 1.19310509E+00 - -1.67189603E+00 -9.39359565E-01 -2.95656818E+00 2.92923473E+00 2.58756660E+00 - -9.39359565E-01 1.19310509E+00 -1.67189603E+00 1.19310509E+00 -2.95656818E+00 - -9.39359565E-01 2.92923473E+00 2.58756660E+00 1.80400770E-01 1.75298526E+00 - 1.80400770E-01 1.75298526E+00 -9.39359565E-01 1.19310509E+00 1.19310509E+00 - -1.67189603E+00 -9.39359565E-01 -2.95656818E+00 2.92923473E+00 2.58756660E+00 - -1.16440497E+00 1.02181547E+00 -1.16440497E+00 1.02181547E+00 -6.11859278E+00 - -6.11859278E+00 -6.11859278E+00 -6.11859278E+00 -6.11859278E+00 -6.11859278E+00 - 6.65478396E+00 1.23364971E+00 6.65478396E+00 1.23364971E+00 -6.11859278E+00 - 6.65478396E+00 1.23364971E+00 -6.11859278E+00 6.65478396E+00 1.23364971E+00 - -5.56691265E-01 -3.51690390E+00 -3.22079153E+00 -3.22079153E+00 2.48005503E+00 - 2.48005503E+00 3.04312410E+00 3.04312410E+00 -5.56691265E-01 -3.51690390E+00 - -3.22079153E+00 -3.22079153E+00 2.48005503E+00 2.48005503E+00 3.04312410E+00 - 3.04312410E+00 -5.56691265E-01 -3.51690390E+00 -3.22079153E+00 -3.22079153E+00 - 2.48005503E+00 2.48005503E+00 3.04312410E+00 3.04312410E+00 -5.56691265E-01 - -3.51690390E+00 -3.22079153E+00 -3.22079153E+00 2.48005503E+00 2.48005503E+00 - 3.04312410E+00 3.04312410E+00 -4.89633201E+00 -4.89633201E+00 -4.89633201E+00 - -4.89633201E+00 -4.89633201E+00 -4.89633201E+00 -4.89633201E+00 -4.89633201E+00 - 1.53932879E+00 -2.09784229E+00 -2.38347684E+00 -2.09784229E+00 -2.03816426E+00 - 1.53932879E+00 2.94290145E+00 2.94290145E+00 1.53932879E+00 -2.09784229E+00 - -2.38347684E+00 -2.09784229E+00 -2.03816426E+00 1.53932879E+00 2.94290145E+00 - 2.94290145E+00 1.53932879E+00 -2.09784229E+00 -2.09784229E+00 -2.38347684E+00 - 1.53932879E+00 -2.03816426E+00 2.94290145E+00 2.94290145E+00 1.53932879E+00 - -2.09784229E+00 -2.38347684E+00 -2.09784229E+00 -2.03816426E+00 1.53932879E+00 - 2.94290145E+00 2.94290145E+00 1.02181547E+00 -1.16440497E+00 -1.16440497E+00 - 1.02181547E+00 1.02181547E+00 1.02181547E+00 1.02181547E+00 1.02181547E+00 - 1.66041598E+01 1.66041598E+01 1.66041598E+01 1.66041598E+01 -1.54670882E+01 - -1.54670882E+01 -1.54670882E+01 -1.54670882E+01 -3.41485902E+00 6.39147173E-01 - 6.39147173E-01 -3.41485902E+00 6.39147173E-01 6.39147173E-01 -3.41485902E+00 - 6.39147173E-01 6.39147173E-01 -3.41485902E+00 6.39147173E-01 6.39147173E-01 - 1.65021149E+01 1.65021149E+01 1.65021149E+01 1.65021149E+01 -1.58793678E+01 - -1.58793678E+01 -1.58793678E+01 -1.58793678E+01 2.66045580E+00 2.66045580E+00 - 2.66045580E+00 2.66045580E+00 -1.54670882E+01 -1.54670882E+01 -1.54670882E+01 - -1.54670882E+01 -1.58793678E+01 -1.58793678E+01 -1.58793678E+01 -1.58793678E+01 - -1.03502664E+00 8.96537160E-01 8.96537160E-01 8.96537160E-01 -1.03502664E+00 - 8.96537160E-01 8.96537160E-01 8.96537160E-01 -1.03502664E+00 8.96537160E-01 - 8.96537160E-01 8.96537160E-01 -1.03502664E+00 8.96537160E-01 8.96537160E-01 - 8.96537160E-01 -%FLAG ATOMIC_NUMBER -%FORMAT(10I8) - 1 6 6 6 6 6 6 1 6 1 - 6 1 6 6 6 6 6 6 1 1 - 6 6 6 6 6 6 1 1 6 1 - 6 1 6 6 6 6 6 6 1 1 - 6 1 6 1 8 8 8 8 8 8 - 6 1 6 1 8 6 1 8 6 1 - 6 6 6 6 6 6 1 1 6 6 - 6 6 6 6 1 1 6 6 6 6 - 6 6 1 1 6 6 6 6 6 6 - 1 1 8 8 8 8 8 8 8 8 - 6 6 6 6 6 6 1 1 6 6 - 6 6 6 6 1 1 6 6 6 6 - 6 6 1 1 6 6 6 6 6 6 - 1 1 1 6 6 1 1 1 1 1 - 6 6 6 6 8 8 8 8 6 1 - 1 6 1 1 6 1 1 6 1 1 - 6 6 6 6 8 8 8 8 1 1 - 1 1 8 8 8 8 8 8 8 8 - 6 1 1 1 6 1 1 1 6 1 - 1 1 6 1 1 1 -%FLAG MASS -%FORMAT(5E16.8) - 1.00800000E+00 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.20100000E+01 1.00800000E+00 1.20100000E+01 1.00800000E+00 - 1.20100000E+01 1.00800000E+00 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.00800000E+00 1.00800000E+00 - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.00800000E+00 1.00800000E+00 1.20100000E+01 1.00800000E+00 - 1.20100000E+01 1.00800000E+00 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.00800000E+00 1.00800000E+00 - 1.20100000E+01 1.00800000E+00 1.20100000E+01 1.00800000E+00 1.60000000E+01 - 1.60000000E+01 1.60000000E+01 1.60000000E+01 1.60000000E+01 1.60000000E+01 - 1.20100000E+01 1.00800000E+00 1.20100000E+01 1.00800000E+00 1.60000000E+01 - 1.20100000E+01 1.00800000E+00 1.60000000E+01 1.20100000E+01 1.00800000E+00 - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.00800000E+00 1.00800000E+00 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.00800000E+00 - 1.00800000E+00 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.20100000E+01 1.00800000E+00 1.00800000E+00 1.20100000E+01 - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.00800000E+00 1.00800000E+00 1.60000000E+01 1.60000000E+01 1.60000000E+01 - 1.60000000E+01 1.60000000E+01 1.60000000E+01 1.60000000E+01 1.60000000E+01 - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.00800000E+00 1.00800000E+00 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.00800000E+00 - 1.00800000E+00 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.20100000E+01 1.20100000E+01 1.00800000E+00 1.00800000E+00 1.20100000E+01 - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 - 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.20100000E+01 1.20100000E+01 - 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.00800000E+00 - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.60000000E+01 - 1.60000000E+01 1.60000000E+01 1.60000000E+01 1.20100000E+01 1.00800000E+00 - 1.00800000E+00 1.20100000E+01 1.00800000E+00 1.00800000E+00 1.20100000E+01 - 1.00800000E+00 1.00800000E+00 1.20100000E+01 1.00800000E+00 1.00800000E+00 - 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.20100000E+01 1.60000000E+01 - 1.60000000E+01 1.60000000E+01 1.60000000E+01 1.00800000E+00 1.00800000E+00 - 1.00800000E+00 1.00800000E+00 1.60000000E+01 1.60000000E+01 1.60000000E+01 - 1.60000000E+01 1.60000000E+01 1.60000000E+01 1.60000000E+01 1.60000000E+01 - 1.20100000E+01 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.20100000E+01 - 1.00800000E+00 1.00800000E+00 1.00800000E+00 1.20100000E+01 1.00800000E+00 - 1.00800000E+00 1.00800000E+00 1.20100000E+01 1.00800000E+00 1.00800000E+00 - 1.00800000E+00 -%FLAG ATOM_TYPE_INDEX -%FORMAT(10I8) - 1 2 2 2 2 2 2 1 3 4 - 3 4 2 2 2 2 2 2 1 1 - 2 2 2 2 2 2 1 1 3 4 - 3 4 2 2 2 2 2 2 1 1 - 3 4 3 4 5 5 5 5 5 5 - 3 6 3 6 5 3 6 5 3 6 - 2 2 2 2 2 2 1 1 2 2 - 2 2 2 2 1 1 2 2 2 2 - 2 2 1 1 2 2 2 2 2 2 - 1 1 5 5 5 5 5 5 5 5 - 2 2 2 2 2 2 1 1 2 2 - 2 2 2 2 1 1 2 2 2 2 - 2 2 1 1 2 2 2 2 2 2 - 1 1 4 3 3 4 4 4 4 4 - 2 2 2 2 7 7 7 7 3 4 - 4 3 4 4 3 4 4 3 4 4 - 2 2 2 2 7 7 7 7 1 1 - 1 1 7 7 7 7 7 7 7 7 - 3 4 4 4 3 4 4 4 3 4 - 4 4 3 4 4 4 -%FLAG NUMBER_EXCLUDED_ATOMS -%FORMAT(10I8) - 7 11 15 14 13 14 13 2 16 7 - 16 7 14 16 12 10 14 6 1 2 - 14 16 11 11 7 13 1 2 16 7 - 16 7 13 13 12 7 9 5 1 2 - 8 5 8 5 6 5 6 6 5 5 - 8 3 8 3 6 9 4 3 8 3 - 9 11 8 7 8 7 1 1 9 11 - 8 7 8 7 1 1 9 11 8 7 - 8 7 1 1 9 11 8 7 8 7 - 1 1 8 8 8 8 8 8 8 8 - 11 10 9 8 6 6 1 1 11 10 - 9 8 6 6 1 1 11 10 9 8 - 7 5 1 1 11 10 9 8 6 6 - 1 1 4 8 8 4 5 4 5 4 - 2 2 2 2 1 1 1 1 5 4 - 3 5 4 3 5 4 3 5 4 3 - 2 2 2 2 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 - 3 2 1 1 3 2 1 1 3 2 - 1 1 3 2 1 1 -%FLAG NONBONDED_PARM_INDEX -%FORMAT(10I8) - 1 2 4 7 11 16 22 2 3 5 - 8 12 17 23 4 5 6 9 13 18 - 24 7 8 9 10 14 19 25 11 12 - 13 14 15 20 26 16 17 18 19 20 - 21 27 22 23 24 25 26 27 28 -%FLAG RESIDUE_LABEL -%FORMAT(20a4) -<0> -%FLAG RESIDUE_POINTER -%FORMAT(10I8) - 1 -%FLAG BOND_FORCE_CONSTANT -%FORMAT(5E16.8) - 3.95700000E+02 3.78600000E+02 3.57500000E+02 2.50300000E+02 2.32500000E+02 - 3.75900000E+02 2.84800000E+02 3.77300000E+02 2.72700000E+02 6.52600000E+02 - 2.43200000E+02 -%FLAG BOND_EQUIL_VALUE -%FORMAT(5E16.8) - 1.08600000E+00 1.39800000E+00 1.37000000E+00 1.51600000E+00 1.53800000E+00 - 1.09700000E+00 1.43200000E+00 1.09600000E+00 1.49100000E+00 1.21800000E+00 - 1.52400000E+00 -%FLAG ANGLE_FORCE_CONSTANT -%FORMAT(5E16.8) - 4.87000000E+01 6.88000000E+01 8.73000000E+01 6.56000000E+01 6.61000000E+01 - 6.56000000E+01 6.52000000E+01 4.73000000E+01 6.49000000E+01 4.68000000E+01 - 6.53000000E+01 3.90000000E+01 8.56000000E+01 1.10900000E+02 6.24000000E+01 - 4.75000000E+01 6.71000000E+01 6.64000000E+01 8.62000000E+01 1.18800000E+02 - 8.46000000E+01 4.74000000E+01 -%FLAG ANGLE_EQUIL_VALUE -%FORMAT(5E16.8) - 2.09230160E+00 2.09474507E+00 2.08043336E+00 2.10783504E+00 2.05879127E+00 - 1.95895839E+00 1.95599133E+00 1.92806605E+00 1.94621748E+00 1.91637234E+00 - 1.93801443E+00 1.87762601E+00 1.90153704E+00 1.89001786E+00 1.91253261E+00 - 1.91462701E+00 2.09247614E+00 2.10015559E+00 2.13977458E+00 2.27329233E+00 - 2.15024656E+00 1.89839544E+00 -%FLAG DIHEDRAL_FORCE_CONSTANT -%FORMAT(5E16.8) - 3.62500000E+00 1.61000000E+00 0.00000000E+00 2.45000000E-01 3.83333333E-01 - 1.55555556E-01 1.00000000E-01 8.00000000E-02 1.20000000E-01 7.40000000E-01 - 2.70000000E-01 5.50000000E-01 0.00000000E+00 9.00000000E-01 5.22500000E-01 - 8.30000000E-01 4.00000000E-02 1.10000000E+00 -%FLAG DIHEDRAL_PERIODICITY -%FORMAT(5E16.8) - 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 3.00000000E+00 - 3.00000000E+00 3.00000000E+00 3.00000000E+00 3.00000000E+00 1.00000000E+00 - 2.00000000E+00 3.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 - 1.00000000E+00 3.00000000E+00 2.00000000E+00 -%FLAG DIHEDRAL_PHASE -%FORMAT(5E16.8) - 3.14159400E+00 3.14159400E+00 3.14159400E+00 3.14159400E+00 0.00000000E+00 - 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 - 3.14159400E+00 3.14159400E+00 0.00000000E+00 3.14159400E+00 3.14159400E+00 - 0.00000000E+00 3.14159400E+00 3.14159400E+00 -%FLAG SCEE_SCALE_FACTOR -%FORMAT(5E16.8) - 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 - 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 - 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 1.20000000E+00 - 1.20000000E+00 1.20000000E+00 0.00000000E+00 -%FLAG SCNB_SCALE_FACTOR -%FORMAT(5E16.8) - 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 - 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 - 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 2.00000000E+00 - 2.00000000E+00 2.00000000E+00 0.00000000E+00 -%FLAG SOLTY -%FORMAT(5E16.8) - 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 0.00000000E+00 - 0.00000000E+00 0.00000000E+00 0.00000000E+00 -%FLAG LENNARD_JONES_ACOEF -%FORMAT(5E16.8) - 6.90855956E+03 7.52549148E+04 6.96551276E+05 9.27554496E+04 8.43969952E+05 - 1.02073671E+06 7.41026906E+03 8.12662403E+04 1.00235148E+05 7.94617769E+03 - 4.65732396E+04 4.46121722E+05 5.42502534E+05 5.02224902E+04 2.83662575E+05 - 3.17496391E+03 3.85565555E+04 4.80619274E+04 3.39005767E+03 2.33261683E+04 - 1.35510742E+03 5.27277400E+04 5.17507491E+05 6.30925504E+05 5.68021765E+04 - 3.27367585E+05 2.59855087E+04 3.76435470E+05 -%FLAG LENNARD_JONES_BCOEF -%FORMAT(5E16.8) - 2.10929191E+01 1.09570372E+02 5.24668528E+02 1.24325688E+02 5.90251928E+02 - 6.63431737E+02 2.32899892E+01 1.21392023E+02 1.37787728E+02 2.57122925E+01 - 7.98066064E+01 3.88758656E+02 4.38146883E+02 8.83545880E+01 2.87011518E+02 - 1.52447987E+01 8.36149973E+01 9.54115851E+01 1.67944276E+01 6.02145835E+01 - 1.06181419E+01 1.01173582E+02 4.98871431E+02 5.62969425E+02 1.11953944E+02 - 3.67361018E+02 7.57220777E+01 4.69350655E+02 -%FLAG BONDS_INC_HYDROGEN -%FORMAT(10I8) - 0 3 1 6 21 1 24 27 6 30 - 33 6 45 54 1 51 57 1 66 78 - 1 72 81 1 84 87 6 90 93 6 - 105 114 1 111 117 1 120 405 6 120 - 123 6 126 396 6 126 129 6 150 153 - 8 156 159 8 165 168 8 174 177 8 - 183 510 1 186 198 1 189 201 1 207 - 507 1 210 222 1 213 225 1 231 504 - 1 234 246 1 237 249 1 255 513 1 - 258 270 1 261 273 1 303 321 1 309 - 318 1 327 345 1 333 342 1 351 369 - 1 354 366 1 375 393 1 381 390 1 - 399 414 6 399 417 6 402 408 6 402 - 411 6 444 447 6 444 450 6 453 456 - 6 453 459 6 462 465 6 462 468 6 - 471 474 6 471 477 6 540 543 6 540 - 546 6 540 549 6 552 555 6 552 558 - 6 552 561 6 564 567 6 564 570 6 - 564 573 6 576 579 6 576 582 6 576 - 585 6 -%FLAG BONDS_WITHOUT_HYDROGEN -%FORMAT(10I8) - 3 12 2 3 9 2 6 15 2 6 - 18 2 9 18 2 9 141 3 12 15 - 2 12 162 3 15 30 4 18 24 4 - 24 36 4 24 126 5 30 60 4 30 - 120 5 36 45 2 36 42 2 39 48 - 2 39 51 2 39 147 3 42 51 2 - 42 144 3 45 48 2 48 90 4 60 - 69 2 60 66 2 63 72 2 63 75 - 2 63 135 3 66 75 2 69 72 2 - 69 171 3 75 84 4 84 108 4 84 - 402 5 90 96 4 90 399 5 96 105 - 2 96 102 2 99 108 2 99 111 2 - 99 132 3 102 111 2 102 138 3 105 - 108 2 120 453 5 126 471 5 132 174 - 7 135 174 7 138 156 7 141 150 7 - 144 150 7 147 156 7 150 252 4 156 - 228 4 162 165 7 165 171 7 165 180 - 4 174 204 4 180 189 2 180 186 2 - 183 192 2 183 195 2 186 195 2 189 - 192 2 192 276 3 195 297 3 204 213 - 2 204 210 2 207 216 2 207 219 2 - 210 219 2 213 216 2 216 282 3 219 - 279 3 228 237 2 228 234 2 231 240 - 2 231 243 2 234 243 2 237 240 2 - 240 291 3 243 288 3 252 261 2 252 - 258 2 255 264 2 255 267 2 258 267 - 2 261 264 2 264 294 3 267 285 3 - 276 360 3 279 387 3 282 300 3 285 - 348 3 288 315 3 291 324 3 294 339 - 3 297 372 3 300 309 2 300 306 2 - 303 312 2 303 315 2 306 315 2 306 - 576 4 309 312 2 312 426 9 324 333 - 2 324 330 2 327 336 2 327 339 2 - 330 339 2 330 540 4 333 336 2 336 - 423 9 348 357 2 348 354 2 351 360 - 2 351 363 2 354 363 2 357 360 2 - 357 552 4 363 420 9 372 381 2 372 - 378 2 375 384 2 375 387 2 378 387 - 2 378 564 4 381 384 2 384 429 9 - 399 444 5 402 462 5 420 432 10 420 - 522 10 423 435 10 423 519 10 426 438 - 10 426 516 10 429 441 10 429 525 10 - 444 489 11 453 480 11 462 483 11 471 - 486 11 480 498 10 480 534 10 483 492 - 10 483 531 10 486 501 10 486 537 10 - 489 495 10 489 528 10 -%FLAG ANGLES_INC_HYDROGEN -%FORMAT(10I8) - 0 3 12 1 0 3 9 1 15 30 - 33 8 15 6 21 1 18 24 27 8 - 18 6 21 1 24 126 396 10 24 126 - 129 10 27 24 36 8 27 24 126 10 - 30 120 405 10 30 120 123 10 33 30 - 60 8 33 30 120 10 36 45 54 1 - 39 51 57 1 42 51 57 1 48 90 - 93 8 48 45 54 1 60 66 78 1 - 63 72 81 1 69 72 81 1 75 84 - 87 8 75 66 78 1 84 402 408 10 - 84 402 411 10 87 84 108 8 87 84 - 402 10 90 399 414 10 90 399 417 10 - 93 90 96 8 93 90 399 10 96 105 - 114 1 99 111 117 1 102 111 117 1 - 108 105 114 1 120 453 456 10 120 453 - 459 10 123 120 453 10 123 120 405 12 - 126 471 474 10 126 471 477 10 129 126 - 471 10 129 126 396 12 132 174 177 15 - 135 174 177 15 138 156 159 15 141 150 - 153 15 144 150 153 15 147 156 159 15 - 153 150 252 16 159 156 228 16 162 165 - 168 15 168 165 171 15 168 165 180 16 - 177 174 204 16 180 189 201 1 180 186 - 198 1 192 189 201 1 192 183 510 1 - 195 186 198 1 195 183 510 1 204 213 - 225 1 204 210 222 1 216 213 225 1 - 216 207 507 1 219 210 222 1 219 207 - 507 1 228 237 249 1 228 234 246 1 - 240 237 249 1 240 231 504 1 243 234 - 246 1 243 231 504 1 252 261 273 1 - 252 258 270 1 264 261 273 1 264 255 - 513 1 267 258 270 1 267 255 513 1 - 300 309 318 1 306 576 579 8 306 576 - 582 8 306 576 585 8 312 309 318 1 - 312 303 321 1 315 303 321 1 324 333 - 342 1 330 540 543 8 330 540 546 8 - 330 540 549 8 336 333 342 1 336 327 - 345 1 339 327 345 1 348 354 366 1 - 357 552 555 8 357 552 558 8 357 552 - 561 8 360 351 369 1 363 354 366 1 - 363 351 369 1 372 381 390 1 378 564 - 567 8 378 564 570 8 378 564 573 8 - 384 381 390 1 384 375 393 1 387 375 - 393 1 396 126 471 10 399 444 447 10 - 399 444 450 10 402 462 465 10 402 462 - 468 10 405 120 453 10 408 402 462 10 - 408 402 411 12 411 402 462 10 414 399 - 444 10 414 399 417 12 417 399 444 10 - 447 444 489 22 447 444 450 12 450 444 - 489 22 456 453 480 22 456 453 459 12 - 459 453 480 22 465 462 483 22 465 462 - 468 12 468 462 483 22 474 471 486 22 - 474 471 477 12 477 471 486 22 543 540 - 546 12 543 540 549 12 546 540 549 12 - 555 552 558 12 555 552 561 12 558 552 - 561 12 567 564 570 12 567 564 573 12 - 570 564 573 12 579 576 582 12 579 576 - 585 12 582 576 585 12 -%FLAG ANGLES_WITHOUT_HYDROGEN -%FORMAT(10I8) - 3 12 15 2 3 12 162 3 3 9 - 18 2 3 9 141 3 6 15 30 4 - 6 15 12 2 6 18 24 4 6 18 - 9 2 9 18 24 4 9 141 150 5 - 9 3 12 2 12 15 30 4 12 162 - 165 5 15 30 60 6 15 30 120 7 - 15 12 162 3 15 6 18 2 18 24 - 36 6 18 24 126 7 18 9 141 3 - 24 36 45 4 24 36 42 4 24 126 - 471 9 30 60 69 4 30 60 66 4 - 30 120 453 9 36 45 48 2 36 42 - 51 2 36 42 144 3 36 24 126 7 - 39 48 90 4 39 48 45 2 39 51 - 42 2 39 147 156 5 42 144 150 5 - 42 36 45 2 45 48 90 4 48 90 - 96 6 48 90 399 7 48 39 51 2 - 48 39 147 3 51 42 144 3 51 39 - 147 3 60 69 72 2 60 69 171 3 - 60 66 75 2 60 30 120 7 63 72 - 69 2 63 75 84 4 63 75 66 2 - 63 135 174 5 66 75 84 4 66 60 - 69 2 69 171 165 5 72 69 171 3 - 72 63 75 2 72 63 135 3 75 84 - 108 6 75 84 402 7 75 63 135 3 - 84 108 105 4 84 108 99 4 84 402 - 462 9 90 96 105 4 90 96 102 4 - 90 399 444 9 96 105 108 2 96 102 - 111 2 96 102 138 3 96 90 399 7 - 99 108 105 2 99 111 102 2 99 132 - 174 5 102 138 156 5 102 96 105 2 - 108 99 111 2 108 99 132 3 108 84 - 402 7 111 102 138 3 111 99 132 3 - 120 453 480 11 126 471 486 11 132 174 - 204 13 132 174 135 14 135 174 204 13 - 138 156 228 13 138 156 147 14 141 150 - 252 13 141 150 144 14 144 150 252 13 - 147 156 228 13 150 252 261 4 150 252 - 258 4 156 228 237 4 156 228 234 4 - 162 165 171 14 162 165 180 13 165 180 - 189 4 165 180 186 4 171 165 180 13 - 174 204 213 4 174 204 210 4 180 189 - 192 2 180 186 195 2 183 192 276 3 - 183 192 189 2 183 195 297 3 183 195 - 186 2 186 195 297 3 186 180 189 2 - 189 192 276 3 192 276 360 17 192 183 - 195 2 195 297 372 17 204 213 216 2 - 204 210 219 2 207 216 282 3 207 216 - 213 2 207 219 279 3 207 219 210 2 - 210 219 279 3 210 204 213 2 213 216 - 282 3 216 282 300 17 216 207 219 2 - 219 279 387 17 228 237 240 2 228 234 - 243 2 231 240 291 3 231 240 237 2 - 231 243 288 3 231 243 234 2 234 243 - 288 3 234 228 237 2 237 240 291 3 - 240 291 324 17 240 231 243 2 243 288 - 315 17 252 261 264 2 252 258 267 2 - 255 264 294 3 255 264 261 2 255 267 - 285 3 255 267 258 2 258 267 285 3 - 258 252 261 2 261 264 294 3 264 294 - 339 17 264 255 267 2 267 285 348 17 - 276 360 357 3 276 360 351 3 279 387 - 378 3 279 387 375 3 282 300 309 3 - 282 300 306 3 285 348 357 3 285 348 - 354 3 288 315 306 3 288 315 303 3 - 291 324 333 3 291 324 330 3 294 339 - 330 3 294 339 327 3 297 372 381 3 - 297 372 378 3 300 309 312 2 300 306 - 315 2 300 306 576 4 303 312 426 18 - 303 312 309 2 303 315 306 2 306 300 - 309 2 309 312 426 18 312 426 438 19 - 312 426 516 19 312 303 315 2 315 306 - 576 4 324 333 336 2 324 330 339 2 - 324 330 540 4 327 336 423 18 327 336 - 333 2 327 339 330 2 330 324 333 2 - 333 336 423 18 336 423 435 19 336 423 - 519 19 336 327 339 2 339 330 540 4 - 348 357 360 2 348 357 552 4 348 354 - 363 2 351 360 357 2 351 363 420 18 - 351 363 354 2 354 363 420 18 354 348 - 357 2 360 357 552 4 360 351 363 2 - 363 420 432 19 363 420 522 19 372 381 - 384 2 372 378 387 2 372 378 564 4 - 375 384 429 18 375 384 381 2 375 387 - 378 2 378 372 381 2 381 384 429 18 - 384 429 441 19 384 429 525 19 384 375 - 387 2 387 378 564 4 399 444 489 11 - 402 462 483 11 432 420 522 20 435 423 - 519 20 438 426 516 20 441 429 525 20 - 444 489 495 21 444 489 528 21 453 480 - 498 21 453 480 534 21 462 483 492 21 - 462 483 531 21 471 486 501 21 471 486 - 537 21 492 483 531 20 495 489 528 20 - 498 480 534 20 501 486 537 20 -%FLAG DIHEDRALS_INC_HYDROGEN -%FORMAT(10I8) - 0 3 12 15 1 0 3 12 162 1 - 0 3 9 18 1 0 3 9 141 1 - 6 15 30 33 3 6 18 24 27 3 - 9 18 24 27 3 9 18 6 21 1 - 9 141 150 153 5 12 15 30 33 3 - 12 15 6 21 1 12 162 165 168 5 - 15 30 120 405 6 15 30 120 123 6 - 18 24 126 396 6 18 24 126 129 6 - 21 6 15 30 1 21 6 18 24 1 - 24 36 45 54 1 24 126 471 474 8 - 24 126 471 477 8 27 24 36 45 3 - 27 24 36 42 3 27 24 126 471 8 - 27 24 126 396 9 27 24 126 129 9 - 30 60 66 78 1 30 120 453 456 8 - 30 120 453 459 8 33 30 60 69 3 - 33 30 60 66 3 33 30 120 453 8 - 33 30 120 405 9 33 30 120 123 9 - 36 42 51 57 1 36 24 126 396 6 - 36 24 126 129 6 39 48 90 93 3 - 39 48 45 54 1 39 147 156 159 5 - 42 144 150 153 5 42 36 45 54 1 - 45 48 90 93 3 48 90 399 414 6 - 48 90 399 417 6 48 39 51 57 1 - 54 45 48 90 1 57 51 42 144 1 - 57 51 39 147 1 60 69 72 81 1 - 60 30 120 405 6 60 30 120 123 6 - 63 75 84 87 3 63 75 66 78 1 - 63 135 174 177 5 66 75 84 87 3 - 69 171 165 168 5 69 60 66 78 1 - 75 84 402 408 6 75 84 402 411 6 - 75 63 72 81 1 78 66 75 84 1 - 81 72 69 171 1 81 72 63 135 1 - 84 108 105 114 1 84 402 462 465 8 - 84 402 462 468 8 87 84 108 105 3 - 87 84 108 99 3 87 84 402 462 8 - 87 84 402 408 9 87 84 402 411 9 - 90 96 105 114 1 90 399 444 447 8 - 90 399 444 450 8 93 90 96 105 3 - 93 90 96 102 3 93 90 399 444 8 - 93 90 399 414 9 93 90 399 417 9 - 96 102 111 117 1 96 90 399 414 6 - 96 90 399 417 6 99 108 105 114 1 - 99 132 174 177 5 102 138 156 159 5 - 102 96 105 114 1 108 99 111 117 1 - 108 84 402 408 6 108 84 402 411 6 - 117 111 102 138 1 117 111 99 132 1 - 123 120 453 480 6 123 120 453 456 9 - 123 120 453 459 9 129 126 471 486 6 - 129 126 471 474 9 129 126 471 477 9 - 150 252 261 273 1 150 252 258 270 1 - 153 150 252 261 13 153 150 252 258 13 - 156 228 237 249 1 156 228 234 246 1 - 159 156 228 237 13 159 156 228 234 13 - 165 180 189 201 1 165 180 186 198 1 - 168 165 180 189 13 168 165 180 186 13 - 174 204 213 225 1 174 204 210 222 1 - 177 174 204 213 13 177 174 204 210 13 - 183 192 189 201 1 183 195 186 198 1 - 186 195 183 510 1 186 180 189 201 1 - 189 192 183 510 1 189 180 186 198 1 - 198 186 195 297 1 201 189 192 276 1 - 207 216 213 225 1 207 219 210 222 1 - 210 219 207 507 1 210 204 213 225 1 - 213 216 207 507 1 213 204 210 222 1 - 222 210 219 279 1 225 213 216 282 1 - 231 240 237 249 1 231 243 234 246 1 - 234 243 231 504 1 234 228 237 249 1 - 237 240 231 504 1 237 228 234 246 1 - 246 234 243 288 1 249 237 240 291 1 - 255 264 261 273 1 255 267 258 270 1 - 258 267 255 513 1 258 252 261 273 1 - 261 264 255 513 1 261 252 258 270 1 - 270 258 267 285 1 273 261 264 294 1 - 276 360 351 369 1 276 192 183 510 1 - 279 387 375 393 1 279 219 207 507 1 - 282 300 309 318 1 282 216 207 507 1 - 285 348 354 366 1 285 267 255 513 1 - 288 315 303 321 1 288 243 231 504 1 - 291 324 333 342 1 291 240 231 504 1 - 294 339 327 345 1 294 264 255 513 1 - 297 372 381 390 1 297 195 183 510 1 - 300 306 576 579 3 300 306 576 582 3 - 300 306 576 585 3 303 312 309 318 1 - 306 315 303 321 1 306 300 309 318 1 - 309 312 303 321 1 315 306 576 579 3 - 315 306 576 582 3 315 306 576 585 3 - 318 309 312 426 1 321 303 312 426 1 - 324 330 540 543 3 324 330 540 546 3 - 324 330 540 549 3 327 336 333 342 1 - 330 339 327 345 1 330 324 333 342 1 - 333 336 327 345 1 339 330 540 543 3 - 339 330 540 546 3 339 330 540 549 3 - 342 333 336 423 1 345 327 336 423 1 - 348 357 552 555 3 348 357 552 558 3 - 348 357 552 561 3 351 363 354 366 1 - 354 363 351 369 1 357 360 351 369 1 - 357 348 354 366 1 360 357 552 555 3 - 360 357 552 558 3 360 357 552 561 3 - 366 354 363 420 1 369 351 363 420 1 - 372 378 564 567 3 372 378 564 570 3 - 372 378 564 573 3 375 384 381 390 1 - 378 387 375 393 1 378 372 381 390 1 - 381 384 375 393 1 387 378 564 567 3 - 387 378 564 570 3 387 378 564 573 3 - 390 381 384 429 1 393 375 384 429 1 - 396 126 471 486 6 396 126 471 474 9 - 396 126 471 477 9 405 120 453 480 6 - 405 120 453 456 9 405 120 453 459 9 - 408 402 462 483 6 408 402 462 465 9 - 408 402 462 468 9 411 402 462 483 6 - 411 402 462 465 9 411 402 462 468 9 - 414 399 444 489 6 414 399 444 447 9 - 414 399 444 450 9 417 399 444 489 6 - 417 399 444 447 9 417 399 444 450 9 - 447 444 489 495 16 447 444 -489 495 17 - 447 444 489 528 16 447 444 -489 528 17 - 450 444 489 495 16 450 444 -489 495 17 - 450 444 489 528 16 450 444 -489 528 17 - 456 453 480 498 16 456 453 -480 498 17 - 456 453 480 534 16 456 453 -480 534 17 - 459 453 480 498 16 459 453 -480 498 17 - 459 453 480 534 16 459 453 -480 534 17 - 465 462 483 492 16 465 462 -483 492 17 - 465 462 483 531 16 465 462 -483 531 17 - 468 462 483 492 16 468 462 -483 492 17 - 468 462 483 531 16 468 462 -483 531 17 - 474 471 486 501 16 474 471 -486 501 17 - 474 471 486 537 16 474 471 -486 537 17 - 477 471 486 501 16 477 471 -486 501 17 - 477 471 486 537 16 477 471 -486 537 17 - 0 3 -12 -9 18 15 18 -6 -21 18 - 36 48 -45 -54 18 39 42 -51 -57 18 - 60 75 -66 -78 18 63 69 -72 -81 18 - 96 108 -105 -114 18 99 102 -111 -117 18 - 192 195 -183 -510 18 180 195 -186 -198 18 - 180 192 -189 -201 18 216 219 -207 -507 18 - 204 219 -210 -222 18 204 216 -213 -225 18 - 240 243 -231 -504 18 228 243 -234 -246 18 - 228 240 -237 -249 18 264 267 -255 -513 18 - 252 267 -258 -270 18 252 264 -261 -273 18 - 312 315 -303 -321 18 300 312 -309 -318 18 - 336 339 -327 -345 18 324 336 -333 -342 18 - 360 363 -351 -369 18 348 363 -354 -366 18 - 384 387 -375 -393 18 372 384 -381 -390 18 -%FLAG DIHEDRALS_WITHOUT_HYDROGEN -%FORMAT(10I8) - 3 12 15 30 1 3 12 15 6 1 - 3 12 162 165 2 3 9 18 24 1 - 3 9 -18 6 1 3 9 141 150 2 - 6 15 30 60 3 6 15 30 120 4 - 6 15 12 162 1 6 18 24 36 3 - 6 18 24 126 4 6 18 9 141 1 - 9 18 24 36 3 9 18 24 126 4 - 9 18 6 15 1 9 141 150 252 5 - 9 141 150 144 5 9 3 -12 15 1 - 9 3 12 162 1 12 15 30 60 3 - 12 15 30 120 4 12 15 6 18 1 - 12 162 165 171 5 12 162 165 180 5 - 12 3 -9 18 1 12 3 9 141 1 - 15 30 60 69 3 15 30 60 66 3 - 15 30 120 453 6 15 12 162 165 2 - 15 6 18 24 1 18 24 36 45 3 - 18 24 36 42 3 18 24 126 471 6 - 18 9 141 150 2 18 6 15 30 1 - 24 36 45 48 1 24 36 42 51 1 - 24 36 42 144 1 24 126 471 486 7 - 24 18 9 141 1 30 60 69 72 1 - 30 60 69 171 1 30 60 66 75 1 - 30 120 453 480 7 30 15 12 162 1 - 36 45 48 90 1 36 45 48 39 1 - 36 42 -51 39 1 36 42 144 150 2 - 36 24 126 471 6 39 48 90 96 3 - 39 48 90 399 4 39 51 42 144 1 - 39 147 156 228 5 39 147 156 138 5 - 42 51 39 48 1 42 51 39 147 1 - 42 144 150 252 5 42 144 150 141 5 - 42 36 -45 48 1 42 36 24 126 4 - 45 48 90 96 3 45 48 90 399 4 - 45 48 39 51 1 45 48 39 147 1 - 45 36 -42 51 1 45 36 42 144 1 - 45 36 24 126 4 48 90 96 105 3 - 48 90 96 102 3 48 90 399 444 6 - 48 39 147 156 2 51 42 144 150 2 - 51 39 48 90 1 51 39 147 156 2 - 60 69 72 63 1 60 69 171 165 2 - 60 66 75 84 1 60 66 -75 63 1 - 60 30 120 453 6 63 72 69 171 1 - 63 75 84 108 3 63 75 84 402 4 - 63 135 174 204 5 63 135 174 132 5 - 66 75 84 108 3 66 75 84 402 4 - 66 75 63 72 1 66 75 63 135 1 - 66 60 -69 72 1 66 60 69 171 1 - 66 60 30 120 4 69 72 63 75 1 - 69 72 63 135 1 69 171 165 180 5 - 69 171 165 162 5 69 60 -66 75 1 - 69 60 30 120 4 72 69 171 165 2 - 72 63 75 84 1 72 63 135 174 2 - 75 84 108 105 3 75 84 108 99 3 - 75 84 402 462 6 75 63 135 174 2 - 84 108 105 96 1 84 108 99 111 1 - 84 108 99 132 1 84 402 462 483 7 - 84 75 63 135 1 90 96 105 108 1 - 90 96 102 111 1 90 96 102 138 1 - 90 399 444 489 7 90 48 39 147 1 - 96 105 108 99 1 96 102 -111 99 1 - 96 102 138 156 2 96 90 399 444 6 - 99 108 84 402 4 99 111 102 138 1 - 99 132 174 204 5 99 132 174 135 5 - 102 111 99 108 1 102 111 99 132 1 - 102 138 156 228 5 102 138 156 147 5 - 102 96 -105 108 1 102 96 90 399 4 - 105 108 99 111 1 105 108 99 132 1 - 105 108 84 402 4 105 96 -102 111 1 - 105 96 102 138 1 105 96 90 399 4 - 108 99 132 174 2 108 84 402 462 6 - 111 102 138 156 2 111 99 132 174 2 - 120 453 480 498 10 120 453 -480 498 11 - 120 453 -480 498 12 120 453 480 534 10 - 120 453 -480 534 11 120 453 -480 534 12 - 126 471 486 501 10 126 471 -486 501 11 - 126 471 -486 501 12 126 471 486 537 10 - 126 471 -486 537 11 126 471 -486 537 12 - 132 174 204 213 13 132 174 204 210 13 - 135 174 204 213 13 135 174 204 210 13 - 138 156 228 237 13 138 156 228 234 13 - 141 150 252 261 13 141 150 252 258 13 - 144 150 252 261 13 144 150 252 258 13 - 147 156 228 237 13 147 156 228 234 13 - 150 252 261 264 1 150 252 258 267 1 - 156 228 237 240 1 156 228 234 243 1 - 162 165 180 189 13 162 165 180 186 13 - 165 180 189 192 1 165 180 186 195 1 - 171 165 180 189 13 171 165 180 186 13 - 174 204 213 216 1 174 204 210 219 1 - 180 189 192 276 1 180 189 192 183 1 - 180 186 195 297 1 180 186 -195 183 1 - 183 192 276 360 14 183 195 297 372 14 - 186 195 297 372 14 186 195 183 192 1 - 186 180 -189 192 1 189 192 276 360 14 - 189 192 183 195 1 189 180 -186 195 1 - 192 276 360 357 14 192 276 360 351 14 - 192 183 195 297 1 195 297 372 381 14 - 195 297 372 378 14 195 183 192 276 1 - 204 213 216 282 1 204 213 216 207 1 - 204 210 219 279 1 204 210 -219 207 1 - 207 216 282 300 14 207 219 279 387 14 - 210 219 279 387 14 210 219 207 216 1 - 210 204 -213 216 1 213 216 282 300 14 - 213 216 207 219 1 213 204 -210 219 1 - 216 282 300 309 14 216 282 300 306 14 - 216 207 219 279 1 219 279 387 378 14 - 219 279 387 375 14 219 207 216 282 1 - 228 237 240 291 1 228 237 240 231 1 - 228 234 243 288 1 228 234 -243 231 1 - 231 240 291 324 14 231 243 288 315 14 - 234 243 288 315 14 234 243 231 240 1 - 234 228 -237 240 1 237 240 291 324 14 - 237 240 231 243 1 237 228 -234 243 1 - 240 291 324 333 14 240 291 324 330 14 - 240 231 243 288 1 243 288 315 306 14 - 243 288 315 303 14 243 231 240 291 1 - 252 261 264 294 1 252 261 264 255 1 - 252 258 267 285 1 252 258 -267 255 1 - 255 264 294 339 14 255 267 285 348 14 - 258 267 285 348 14 258 267 255 264 1 - 258 252 -261 264 1 261 264 294 339 14 - 261 264 255 267 1 261 252 -258 267 1 - 264 294 339 330 14 264 294 339 327 14 - 264 255 267 285 1 267 285 348 357 14 - 267 285 348 354 14 267 255 264 294 1 - 276 360 357 552 1 276 360 357 348 1 - 276 360 351 363 1 279 387 378 564 1 - 279 387 378 372 1 279 387 375 384 1 - 282 300 309 312 1 282 300 306 315 1 - 282 300 306 576 1 285 348 357 360 1 - 285 348 357 552 1 285 348 354 363 1 - 288 315 306 576 1 288 315 306 300 1 - 288 315 303 312 1 291 324 333 336 1 - 291 324 330 339 1 291 324 330 540 1 - 294 339 330 540 1 294 339 330 324 1 - 294 339 327 336 1 297 372 381 384 1 - 297 372 378 387 1 297 372 378 564 1 - 300 309 312 426 1 300 309 312 303 1 - 300 306 -315 303 1 303 312 426 438 15 - 303 312 426 516 15 303 315 306 576 1 - 306 315 303 312 1 306 300 -309 312 1 - 309 312 426 438 15 309 312 426 516 15 - 309 312 303 315 1 309 300 -306 315 1 - 309 300 306 576 1 315 303 312 426 1 - 324 333 336 423 1 324 333 336 327 1 - 324 330 -339 327 1 327 336 423 435 15 - 327 336 423 519 15 327 339 330 540 1 - 330 339 327 336 1 330 324 -333 336 1 - 333 336 423 435 15 333 336 423 519 15 - 333 336 327 339 1 333 324 -330 339 1 - 333 324 330 540 1 339 327 336 423 1 - 348 357 360 351 1 348 354 363 420 1 - 348 354 -363 351 1 351 360 357 552 1 - 351 363 420 432 15 351 363 420 522 15 - 354 363 420 432 15 354 363 420 522 15 - 354 363 351 360 1 354 348 -357 360 1 - 354 348 357 552 1 357 360 351 363 1 - 357 348 -354 363 1 360 351 363 420 1 - 372 381 384 429 1 372 381 384 375 1 - 372 378 -387 375 1 375 384 429 441 15 - 375 384 429 525 15 375 387 378 564 1 - 378 387 375 384 1 378 372 -381 384 1 - 381 384 429 441 15 381 384 429 525 15 - 381 384 375 387 1 381 372 -378 387 1 - 381 372 378 564 1 387 375 384 429 1 - 399 444 489 495 10 399 444 -489 495 11 - 399 444 -489 495 12 399 444 489 528 10 - 399 444 -489 528 11 399 444 -489 528 12 - 402 462 483 492 10 402 462 -483 492 11 - 402 462 -483 492 12 402 462 483 531 10 - 402 462 -483 531 11 402 462 -483 531 12 - 3 18 -9 -141 18 3 15 -12 -162 18 - 6 12 -15 -30 18 24 6 -18 -9 18 - 24 42 -36 -45 18 48 51 -39 -147 18 - 36 51 -42 -144 18 90 39 -48 -45 18 - 30 66 -60 -69 18 72 75 -63 -135 18 - 60 72 -69 -171 18 84 63 -75 -66 18 - 90 102 -96 -105 18 108 111 -99 -132 18 - 96 111 -102 -138 18 84 99 -108 -105 18 - 165 186 -180 -189 18 183 189 -192 -276 18 - 183 186 -195 -297 18 174 210 -204 -213 18 - 207 213 -216 -282 18 207 210 -219 -279 18 - 156 234 -228 -237 18 231 237 -240 -291 18 - 231 234 -243 -288 18 150 258 -252 -261 18 - 255 261 -264 -294 18 255 258 -267 -285 18 - 306 309 -300 -282 18 576 300 -306 -315 18 - 426 303 -312 -309 18 303 306 -315 -288 18 - 330 333 -324 -291 18 540 324 -330 -339 18 - 423 327 -336 -333 18 327 330 -339 -294 18 - 354 357 -348 -285 18 552 348 -357 -360 18 - 351 357 -360 -276 18 420 351 -363 -354 18 - 378 381 -372 -297 18 564 372 -378 -387 18 - 429 375 -384 -381 18 375 378 -387 -279 18 - 363 432 -420 -522 18 336 435 -423 -519 18 - 312 438 -426 -516 18 384 441 -429 -525 18 - 453 498 -480 -534 18 462 492 -483 -531 18 - 471 501 -486 -537 18 444 495 -489 -528 18 -%FLAG EXCLUDED_ATOMS_LIST -%FORMAT(10I8) - 2 4 5 6 7 48 55 3 4 5 - 6 7 9 11 48 51 55 56 4 5 - 6 7 8 9 10 11 12 13 21 41 - 43 48 55 5 6 7 8 9 10 13 - 43 48 49 51 52 55 85 6 7 8 - 11 12 21 41 48 55 56 57 58 61 - 7 8 9 11 12 21 23 24 41 42 - 55 56 136 152 8 9 10 11 13 15 - 16 43 44 48 51 133 158 9 11 10 - 13 15 16 17 18 19 43 44 48 49 - 133 158 159 160 163 13 15 16 43 44 - 133 158 12 21 23 24 25 26 27 41 - 42 55 58 136 152 153 154 161 21 23 - 24 41 42 136 152 14 15 16 17 18 - 19 20 31 43 44 49 51 133 158 15 - 16 17 18 19 20 31 32 33 47 49 - 50 53 54 77 134 16 17 18 19 20 - 43 48 49 50 51 52 85 17 18 19 - 31 32 33 43 49 50 134 18 19 20 - 31 32 33 35 36 50 53 134 139 140 - 149 20 31 49 50 51 53 31 49 50 - 22 23 24 25 26 27 28 29 41 42 - 56 58 136 152 23 24 25 26 27 28 - 29 30 37 45 46 58 59 60 69 135 - 24 25 26 27 29 30 37 41 46 58 - 135 25 26 27 28 41 46 55 56 57 - 58 61 26 28 29 46 56 58 59 27 - 28 29 30 34 36 37 46 59 135 137 - 138 155 29 46 58 30 33 34 36 37 - 38 39 45 46 135 137 138 155 156 157 - 162 34 36 37 135 137 138 155 32 33 - 35 36 37 38 39 47 50 134 139 140 - 149 150 151 164 33 35 36 134 139 140 - 149 34 35 36 37 38 39 40 47 53 - 134 139 140 149 35 36 37 38 39 40 - 45 46 47 59 60 69 135 36 37 38 - 39 40 45 47 50 53 54 77 134 37 - 38 39 45 47 134 135 38 39 40 45 - 59 135 137 138 155 40 45 47 53 59 - 0 45 47 42 136 152 153 154 161 167 - 179 136 152 153 154 161 44 133 158 159 - 160 163 168 180 133 158 159 160 163 46 - 59 60 69 71 72 59 60 69 71 72 - 50 53 54 77 79 80 49 51 52 85 - 87 88 51 52 85 87 88 53 54 77 - 79 80 52 85 87 88 89 90 91 92 - 85 87 88 54 77 79 80 81 82 83 - 84 77 79 80 56 57 58 61 63 64 - 57 58 61 63 64 65 66 67 68 58 - 61 63 64 61 63 64 60 69 71 72 - 73 74 75 76 69 71 72 62 63 64 - 65 66 67 68 93 100 63 64 65 66 - 67 68 93 100 121 125 171 64 65 66 - 67 68 100 125 171 65 66 67 68 93 - 121 171 66 68 93 100 118 120 121 171 - 67 93 100 125 127 128 171 100 93 70 - 71 72 73 74 75 76 94 95 71 72 - 73 74 75 76 94 95 101 130 170 72 - 73 74 75 76 94 130 170 73 74 75 - 76 95 101 170 74 76 94 95 101 103 - 104 170 75 94 95 126 127 130 170 94 - 95 78 79 80 81 82 83 84 97 98 - 79 80 81 82 83 84 97 98 106 109 - 169 80 81 82 83 84 97 106 169 81 - 82 83 84 98 109 169 82 84 97 98 - 109 111 112 169 83 97 98 102 103 106 - 169 97 98 86 87 88 89 90 91 92 - 96 99 87 88 89 90 91 92 96 99 - 114 117 172 88 89 90 91 92 96 117 - 172 89 90 91 92 99 114 172 90 92 - 96 99 110 111 114 172 91 96 99 117 - 119 120 172 96 99 117 118 120 121 122 - 124 171 185 125 126 127 129 130 132 170 - 189 101 103 104 105 106 107 170 193 117 - 119 120 121 122 123 172 185 101 102 103 - 105 106 108 169 193 109 111 112 113 114 - 115 169 181 109 110 111 113 114 116 172 - 181 125 127 128 129 130 131 171 189 102 - 103 104 105 106 107 143 193 194 195 196 - 103 104 105 106 107 108 143 147 173 193 - 104 105 106 107 108 193 194 195 196 105 - 106 107 108 143 147 173 193 106 107 108 - 143 147 173 108 143 193 194 195 196 143 - 143 110 111 112 113 114 115 142 181 182 - 183 184 111 112 113 114 115 116 142 146 - 174 181 112 113 114 115 116 181 182 183 - 184 113 114 115 116 142 146 174 181 114 - 115 116 142 146 174 116 142 181 182 183 - 184 142 142 118 119 120 121 122 123 141 - 185 186 187 188 119 120 121 122 123 124 - 141 145 175 185 120 121 122 123 124 141 - 145 175 185 121 122 123 124 185 186 187 - 188 122 124 141 185 186 187 188 123 124 - 141 145 175 141 141 126 127 128 129 130 - 131 144 189 190 191 192 127 128 129 130 - 131 132 144 148 176 189 128 129 130 131 - 132 189 190 191 192 129 130 131 132 144 - 148 176 189 130 131 132 144 148 176 132 - 144 189 190 191 192 144 144 158 159 160 - 163 139 140 149 150 151 164 166 177 137 - 138 155 156 157 162 165 178 152 153 154 - 161 138 155 156 157 162 155 156 157 162 - 140 149 150 151 164 149 150 151 164 145 - 175 146 174 147 173 148 176 175 174 173 - 176 150 151 164 166 177 151 164 166 177 - 164 166 177 153 154 161 167 179 154 161 - 167 179 161 167 179 156 157 162 165 178 - 157 162 165 178 162 165 178 159 160 163 - 168 180 160 163 168 180 163 168 180 167 - 179 165 178 168 180 166 177 178 177 179 - 180 0 0 0 0 0 0 0 0 0 - 0 0 0 182 183 184 183 184 184 0 - 186 187 188 187 188 188 0 190 191 192 - 191 192 192 0 194 195 196 195 196 196 - 0 -%FLAG HBOND_ACOEF -%FORMAT(5E16.8) - -%FLAG HBOND_BCOEF -%FORMAT(5E16.8) - -%FLAG HBCUT -%FORMAT(5E16.8) - -%FLAG AMBER_ATOM_TYPE -%FORMAT(20a4) -ha ca ca ca ca ca ca ha c3 hc c3 hc ca ca ca ca ca ca ha ha -ca ca ca ca ca ca ha ha c3 hc c3 hc ca ca ca ca ca ca ha ha -c3 hc c3 hc os os os os os os c3 h2 c3 h2 os c3 h2 os c3 h2 -ca ca ca ca ca ca ha ha ca ca ca ca ca ca ha ha ca ca ca ca -ca ca ha ha ca ca ca ca ca ca ha ha os os os os os os os os -ca ca ca ca ca ca ha ha ca ca ca ca ca ca ha ha ca ca ca ca -ca ca ha ha ca ca ca ca ca ca ha ha hc c3 c3 hc hc hc hc hc -c c c c o o o o c3 hc hc c3 hc hc c3 hc hc c3 hc hc -c c c c o o o o ha ha ha ha o o o o o o o o -c3 hc hc hc c3 hc hc hc c3 hc hc hc c3 hc hc hc -%FLAG TREE_CHAIN_CLASSIFICATION -%FORMAT(20a4) -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA BLA -%FLAG JOIN_ARRAY -%FORMAT(10I8) - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 -%FLAG IROTAT -%FORMAT(10I8) - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 -%FLAG RADIUS_SET -%FORMAT(1a80) -modified Bondi radii (mbondi) -%FLAG RADII -%FORMAT(5E16.8) - 1.30000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.70000000E+00 1.30000000E+00 1.70000000E+00 1.30000000E+00 - 1.70000000E+00 1.30000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.30000000E+00 1.30000000E+00 - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.30000000E+00 1.30000000E+00 1.70000000E+00 1.30000000E+00 - 1.70000000E+00 1.30000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.30000000E+00 1.30000000E+00 - 1.70000000E+00 1.30000000E+00 1.70000000E+00 1.30000000E+00 1.50000000E+00 - 1.50000000E+00 1.50000000E+00 1.50000000E+00 1.50000000E+00 1.50000000E+00 - 1.70000000E+00 1.30000000E+00 1.70000000E+00 1.30000000E+00 1.50000000E+00 - 1.70000000E+00 1.30000000E+00 1.50000000E+00 1.70000000E+00 1.30000000E+00 - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.30000000E+00 1.30000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.30000000E+00 - 1.30000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.70000000E+00 1.30000000E+00 1.30000000E+00 1.70000000E+00 - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.30000000E+00 1.30000000E+00 1.50000000E+00 1.50000000E+00 1.50000000E+00 - 1.50000000E+00 1.50000000E+00 1.50000000E+00 1.50000000E+00 1.50000000E+00 - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.30000000E+00 1.30000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.30000000E+00 - 1.30000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.70000000E+00 1.70000000E+00 1.30000000E+00 1.30000000E+00 1.70000000E+00 - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 - 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.70000000E+00 1.70000000E+00 - 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.30000000E+00 - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.50000000E+00 - 1.50000000E+00 1.50000000E+00 1.50000000E+00 1.70000000E+00 1.30000000E+00 - 1.30000000E+00 1.70000000E+00 1.30000000E+00 1.30000000E+00 1.70000000E+00 - 1.30000000E+00 1.30000000E+00 1.70000000E+00 1.30000000E+00 1.30000000E+00 - 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.70000000E+00 1.50000000E+00 - 1.50000000E+00 1.50000000E+00 1.50000000E+00 1.30000000E+00 1.30000000E+00 - 1.30000000E+00 1.30000000E+00 1.50000000E+00 1.50000000E+00 1.50000000E+00 - 1.50000000E+00 1.50000000E+00 1.50000000E+00 1.50000000E+00 1.50000000E+00 - 1.70000000E+00 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.70000000E+00 - 1.30000000E+00 1.30000000E+00 1.30000000E+00 1.70000000E+00 1.30000000E+00 - 1.30000000E+00 1.30000000E+00 1.70000000E+00 1.30000000E+00 1.30000000E+00 - 1.30000000E+00 -%FLAG SCREEN -%FORMAT(5E16.8) - 8.50000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 7.20000000E-01 8.50000000E-01 7.20000000E-01 8.50000000E-01 - 7.20000000E-01 8.50000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 7.20000000E-01 7.20000000E-01 8.50000000E-01 8.50000000E-01 - 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 8.50000000E-01 8.50000000E-01 7.20000000E-01 8.50000000E-01 - 7.20000000E-01 8.50000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 7.20000000E-01 7.20000000E-01 8.50000000E-01 8.50000000E-01 - 7.20000000E-01 8.50000000E-01 7.20000000E-01 8.50000000E-01 8.50000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 7.20000000E-01 8.50000000E-01 7.20000000E-01 8.50000000E-01 8.50000000E-01 - 7.20000000E-01 8.50000000E-01 8.50000000E-01 7.20000000E-01 8.50000000E-01 - 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 8.50000000E-01 8.50000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 8.50000000E-01 - 8.50000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 7.20000000E-01 8.50000000E-01 8.50000000E-01 7.20000000E-01 - 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 8.50000000E-01 8.50000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 8.50000000E-01 - 8.50000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 7.20000000E-01 7.20000000E-01 8.50000000E-01 8.50000000E-01 7.20000000E-01 - 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 7.20000000E-01 7.20000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 8.50000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 7.20000000E-01 8.50000000E-01 - 8.50000000E-01 7.20000000E-01 8.50000000E-01 8.50000000E-01 7.20000000E-01 - 8.50000000E-01 8.50000000E-01 7.20000000E-01 8.50000000E-01 8.50000000E-01 - 7.20000000E-01 7.20000000E-01 7.20000000E-01 7.20000000E-01 8.50000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 - 7.20000000E-01 8.50000000E-01 8.50000000E-01 8.50000000E-01 7.20000000E-01 - 8.50000000E-01 8.50000000E-01 8.50000000E-01 7.20000000E-01 8.50000000E-01 - 8.50000000E-01 8.50000000E-01 7.20000000E-01 8.50000000E-01 8.50000000E-01 - 8.50000000E-01 -%FLAG IPOL -%FORMAT(1I8) - 0 diff --git a/femto/fe/tests/data/temoa/temoa.rst7 b/femto/fe/tests/data/temoa/temoa.rst7 deleted file mode 100644 index fe552e5..0000000 --- a/femto/fe/tests/data/temoa/temoa.rst7 +++ /dev/null @@ -1,100 +0,0 @@ -<0> - 196 - 17.6658000 27.6679000 23.3774000 17.5868000 26.7169000 22.8764000 - 17.3308000 24.2569000 21.5914000 17.1598000 25.5969000 23.5974000 - 17.9528000 26.6089000 21.5264000 17.8158000 25.3659000 20.8764000 - 17.0318000 24.3529000 22.9564000 17.1968000 23.3059000 21.0864000 - 16.5248000 23.1529000 23.7764000 15.8938000 23.5819000 24.5614000 - 18.1928000 25.2639000 19.3904000 18.0278000 26.2679000 18.9764000 - 17.6768000 22.4569000 24.5424000 19.7968000 21.1889000 25.8964000 - 18.0568000 22.8829000 25.8294000 18.3458000 21.3689000 23.9804000 - 19.4358000 20.7699000 24.6044000 19.0798000 22.2119000 26.5214000 - 18.0618000 21.0229000 22.9974000 19.3438000 22.5109000 27.5254000 - 19.6978000 24.9789000 19.2914000 22.4418000 24.4859000 19.5344000 - 20.1788000 23.6689000 19.4034000 20.6078000 26.0479000 19.1974000 - 21.9758000 25.7909000 19.3314000 21.5438000 23.4019000 19.5314000 - 19.4728000 22.8499000 19.4484000 22.6718000 26.6159000 19.3444000 - 22.0748000 21.9589000 19.6434000 23.1048000 22.0229000 19.2704000 - 20.2588000 19.6869000 23.8924000 20.7258000 19.1229000 24.7034000 - 21.3958000 20.3519000 23.1044000 23.4128000 21.8079000 21.8124000 - 22.6228000 20.5749000 23.7554000 21.2288000 20.7789000 21.7774000 - 22.2228000 21.5149000 21.1174000 23.6258000 21.2969000 23.1034000 - 20.3058000 20.5469000 21.2634000 24.5418000 21.5149000 23.6314000 - 17.2748000 24.3689000 18.5054000 17.3588000 23.3149000 18.7924000 - 15.5468000 22.1809000 23.0574000 16.1098000 21.5119000 22.3994000 - 24.4068000 22.6039000 21.2594000 23.7898000 24.3099000 19.7894000 - 22.7958000 20.2659000 25.1064000 16.8888000 25.7399000 24.9484000 - 17.4808000 24.0009000 26.4154000 20.9268000 20.6789000 26.5204000 - 17.8638000 25.2469000 25.8464000 18.8288000 25.1429000 25.3454000 - 22.1578000 21.1539000 26.0004000 21.9448000 22.0189000 25.4004000 - 18.4468000 27.7369000 20.8664000 19.8398000 27.7879000 20.5804000 - 20.3798000 27.0889000 21.1974000 20.1788000 27.3759000 19.2674000 - 24.1438000 23.9899000 21.1254000 23.3648000 24.2969000 21.8424000 - 20.3938000 29.2119000 20.9464000 21.6218000 31.5269000 21.9424000 - 21.6468000 29.6209000 20.4534000 19.7608000 29.9929000 21.9334000 - 20.3818000 31.1499000 22.4444000 22.2668000 30.7729000 20.9714000 - 22.1518000 29.0219000 19.7064000 18.8058000 29.6819000 22.3394000 - 25.4608000 24.7669000 21.4444000 27.7538000 26.2169000 22.1284000 - 25.6968000 26.0289000 20.8804000 26.3858000 24.2399000 22.3584000 - 27.5328000 24.9699000 22.7034000 26.8478000 26.7609000 21.2194000 - 24.9978000 26.4409000 20.1664000 26.2168000 23.2629000 22.7914000 - 23.1158000 21.6549000 27.1324000 24.7758000 22.9449000 28.9834000 - 24.4928000 21.7779000 26.8854000 22.5838000 22.1789000 28.3254000 - 23.4178000 22.8339000 29.2494000 25.3288000 22.4289000 27.8104000 - 24.9218000 21.3439000 25.9914000 21.5338000 22.0519000 28.5444000 - 18.0648000 26.2979000 26.9724000 18.6528000 28.2029000 28.9264000 - 17.8918000 27.6669000 26.7014000 18.5428000 25.8979000 28.2224000 - 18.8388000 26.8499000 29.2114000 18.1918000 28.6229000 27.6824000 - 17.5168000 27.9799000 25.7354000 18.6828000 24.8459000 28.4324000 - 19.8808000 31.9329000 23.4634000 27.1638000 28.0019000 20.7044000 - 28.4908000 24.5249000 23.5944000 18.0498000 29.9889000 27.5154000 - 26.6898000 22.5919000 27.6664000 22.9918000 23.3789000 30.4484000 - 19.2998000 26.5299000 30.4714000 23.5288000 31.2119000 20.6174000 - 28.2348000 23.4519000 24.4194000 27.8518000 21.2449000 26.0814000 - 27.5008000 23.5959000 25.6094000 28.7718000 22.2059000 24.0754000 - 28.5898000 21.0879000 24.8924000 27.3078000 22.4749000 26.4374000 - 29.3488000 22.1219000 23.1584000 27.7138000 20.3969000 26.7434000 - 21.6688000 23.6929000 30.6734000 19.0208000 24.3169000 31.3294000 - 21.1708000 24.9809000 30.4094000 20.8428000 22.7409000 31.2874000 - 19.5148000 23.0319000 31.6254000 19.8288000 25.2759000 30.7204000 - 21.2628000 21.7679000 31.5214000 17.9948000 24.5679000 31.5814000 - 17.9018000 30.5499000 26.2654000 17.5298000 31.8879000 23.8434000 - 16.6168000 30.9249000 25.8514000 19.0118000 30.8539000 25.4594000 - 18.8128000 31.5239000 24.2384000 16.4108000 31.6009000 24.6444000 - 15.7758000 30.7089000 26.5044000 17.3988000 32.4369000 22.9184000 - 24.4178000 30.3709000 19.9904000 26.2858000 28.8079000 18.6394000 - 25.2828000 29.5429000 20.7224000 24.4888000 30.4059000 18.5934000 - 25.4068000 29.6179000 17.8944000 26.2288000 28.7639000 20.0314000 - 23.8118000 31.0639000 18.0574000 27.0258000 28.2109000 18.1194000 - 15.1478000 21.5189000 23.8234000 19.4318000 18.6119000 23.1374000 - 21.4188000 20.9349000 18.6524000 16.2338000 24.6399000 18.7204000 - 20.4388000 20.6039000 19.0214000 21.2208000 21.4759000 17.7194000 - 18.8978000 19.0709000 22.2994000 18.6428000 18.2979000 23.8174000 - 15.0448000 32.0719000 24.2414000 18.6428000 22.0179000 32.3114000 - 29.1968000 19.7629000 24.5404000 25.4788000 29.6489000 16.3954000 - 14.0898000 31.8689000 25.0264000 19.1318000 20.9009000 32.5894000 - 29.8418000 19.6739000 23.4674000 24.6828000 30.3939000 15.7774000 - 20.2538000 17.3549000 22.7124000 21.2748000 17.3879000 23.1104000 - 19.7988000 16.4449000 23.1014000 17.5898000 24.5569000 16.9884000 - 17.7988000 25.6099000 16.7844000 18.4828000 23.9919000 16.7074000 - 22.3148000 19.6829000 18.3414000 22.2018000 18.9349000 19.1294000 - 23.3688000 19.9699000 18.3324000 14.3358000 22.8309000 22.3234000 - 14.2788000 23.9109000 22.5124000 13.4048000 22.3989000 22.6894000 - 16.4658000 24.2319000 15.9954000 22.1178000 19.0169000 16.9594000 - 14.3098000 22.6159000 20.8034000 20.3988000 17.1539000 21.2054000 - 22.5738000 17.8569000 16.8064000 19.9878000 18.0279000 20.4254000 - 15.2758000 24.4989000 16.2894000 15.1928000 21.9219000 20.2574000 - 25.4288000 23.4129000 29.7244000 28.6518000 26.7739000 22.3884000 - 22.1188000 32.4049000 22.3524000 18.8618000 28.9459000 29.6934000 - 29.0338000 18.8049000 25.3394000 17.4528000 22.3419000 32.5714000 - 14.9298000 32.6739000 23.1394000 26.3328000 28.9139000 15.8314000 - 20.9118000 16.0499000 20.8594000 21.6628000 19.6999000 16.0104000 - 16.8348000 23.8549000 14.8584000 13.3138000 23.1079000 20.2114000 - 22.0808000 26.0759000 29.8524000 21.8548000 27.0329000 30.3284000 - 21.9208000 26.1989000 28.7814000 23.1388000 25.8759000 30.0444000 - 20.4188000 30.5099000 25.9364000 21.1588000 31.2129000 25.5474000 - 20.6728000 29.4999000 25.6144000 20.4868000 30.5569000 27.0254000 - 25.2278000 29.5139000 22.2464000 24.8668000 30.4639000 22.6434000 - 26.2178000 29.3539000 22.6794000 24.5698000 28.7089000 22.5794000 - 26.9668000 24.9609000 26.0214000 25.9328000 25.0889000 25.6914000 - 27.5798000 25.7629000 25.6074000 27.0018000 25.0809000 27.1054000 diff --git a/femto/fe/tests/data/temoa/temoa.sdf b/femto/fe/tests/data/temoa/temoa.sdf new file mode 100644 index 0000000..f47d973 --- /dev/null +++ b/femto/fe/tests/data/temoa/temoa.sdf @@ -0,0 +1,434 @@ +M0001 + -OEChem-10162003453D + +204224 0 0 0 0 0 0 0999 V2000 + 3.9454 -0.2540 2.4646 H 0 0 0 0 0 0 0 0 0 0 0 0 + 3.4557 0.6844 2.2127 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.1884 3.1083 1.5645 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.5448 1.2823 3.0848 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.7175 1.3371 1.0068 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.1122 2.5601 0.6653 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.9082 2.5059 2.7960 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.6551 4.0278 1.2891 H 0 0 0 0 0 0 0 0 0 0 0 0 + 0.9348 3.0921 3.7957 C 0 0 2 0 0 0 0 0 0 0 0 0 + 1.2403 2.7140 4.8201 H 0 0 0 0 0 0 0 0 0 0 0 0 + 3.4702 3.2377 -0.6431 C 0 0 1 0 0 0 0 0 0 0 0 0 + 4.5115 2.8891 -0.9298 H 0 0 0 0 0 0 0 0 0 0 0 0 + -0.4360 2.5240 3.4845 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.8800 1.3249 2.8374 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.8147 1.2852 4.0353 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.3319 3.1485 2.6049 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.5569 2.5661 2.2549 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.0363 0.6770 3.7406 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.0490 4.1096 2.1574 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.3178 -0.2713 4.1944 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.5201 2.7399 -1.7084 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.7699 1.5892 -3.5584 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2997 3.3592 -2.0141 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.8378 1.5398 -2.3753 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.9896 0.9572 -3.3169 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.3963 2.7909 -2.9231 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.0346 4.2908 -1.5052 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.2651 0.0422 -3.8373 H 0 0 0 0 0 0 0 0 0 0 0 0 + -0.9756 3.3611 -3.2158 C 0 0 1 0 0 0 0 0 0 0 0 0 + -1.2564 3.0083 -4.2598 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.5102 3.1906 1.2555 C 0 0 2 0 0 0 0 0 0 0 0 0 + -4.5529 2.8206 1.5100 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.1760 2.6619 -0.1238 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.5905 1.5281 -2.6145 C 0 0 0 0 0 0 0 0 0 0 0 0 + -3.7673 1.4531 -0.5426 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.2810 3.2869 -1.0028 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.9688 2.7394 -2.2550 C 0 0 0 0 0 0 0 0 0 0 0 0 + -3.5010 0.8746 -1.7837 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.8056 4.2331 -0.7038 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.9853 -0.0494 -2.0921 H 0 0 0 0 0 0 0 0 0 0 0 0 + 3.5197 4.7613 -0.4587 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.5012 5.1829 -0.2992 H 0 0 0 0 0 0 0 0 0 0 0 0 + 0.9147 4.6325 3.8626 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.3355 5.0489 3.0047 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.3002 0.8959 -3.8320 O 0 0 0 0 0 0 0 0 0 0 0 0 + -0.0944 0.9410 -4.4528 O 0 0 0 0 0 0 0 0 0 0 0 0 + -4.6468 0.7464 0.2907 O 0 0 0 0 0 0 0 0 0 0 0 0 + 2.2535 0.5722 4.2585 O 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0463 0.5706 4.8809 O 0 0 0 0 0 0 0 0 0 0 0 0 + -4.0634 0.6517 2.5048 O 0 0 0 0 0 0 0 0 0 0 0 0 + 1.0284 -0.1828 4.1378 C 0 0 2 0 0 0 0 0 0 0 0 0 + 0.7269 -0.2880 3.0694 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.9481 -0.0658 1.2571 C 0 0 1 0 0 0 0 0 0 0 0 0 + -2.8799 -0.2014 0.9698 H 0 0 0 0 0 0 0 0 0 0 0 0 + 4.5932 0.6895 0.1256 O 0 0 0 0 0 0 0 0 0 0 0 0 + 3.8903 -0.0029 -0.9290 C 0 0 1 0 0 0 0 0 0 0 0 0 + 2.8185 -0.1548 -0.6639 H 0 0 0 0 0 0 0 0 0 0 0 0 + 4.0221 0.8428 -2.0913 O 0 0 0 0 0 0 0 0 0 0 0 0 + -1.0772 0.1319 -3.7720 C 0 0 2 0 0 0 0 0 0 0 0 0 + -0.7760 -0.0614 -2.7168 H 0 0 0 0 0 0 0 0 0 0 0 0 + 4.5665 -1.3318 -1.1797 C 0 0 0 0 0 0 0 0 0 0 0 0 + 5.4706 -3.9732 -1.5481 C 0 0 0 0 0 0 0 0 0 0 0 0 + 4.4880 -1.9060 -2.4509 C 0 0 0 0 0 0 0 0 0 0 0 0 + 5.0902 -2.0315 -0.0887 C 0 0 0 0 0 0 0 0 0 0 0 0 + 5.5335 -3.3451 -0.2968 C 0 0 0 0 0 0 0 0 0 0 0 0 + 4.9454 -3.2226 -2.6082 C 0 0 0 0 0 0 0 0 0 0 0 0 + 4.1159 -1.3268 -3.2941 H 0 0 0 0 0 0 0 0 0 0 0 0 + 5.1785 -1.5465 0.8823 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.2659 -1.1702 -4.5167 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.5246 -3.7739 -5.5581 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.1557 -1.7806 -5.1060 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.5033 -1.8153 -4.4385 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.6070 -3.1111 -4.9640 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.3103 -3.0772 -5.6176 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.7862 -1.2416 -5.1930 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.3638 -1.3017 -4.0124 H 0 0 0 0 0 0 0 0 0 0 0 0 + -4.6354 -1.4065 1.3837 C 0 0 0 0 0 0 0 0 0 0 0 0 + -5.5571 -4.0639 1.5207 C 0 0 0 0 0 0 0 0 0 0 0 0 + -5.1722 -2.0007 0.2382 C 0 0 0 0 0 0 0 0 0 0 0 0 + -4.5524 -2.0941 2.5975 C 0 0 0 0 0 0 0 0 0 0 0 0 + -5.0182 -3.4161 2.6402 C 0 0 0 0 0 0 0 0 0 0 0 0 + -5.6243 -3.3247 0.3319 C 0 0 0 0 0 0 0 0 0 0 0 0 + -5.2645 -1.4300 -0.6844 H 0 0 0 0 0 0 0 0 0 0 0 0 + -4.1696 -1.5951 3.4863 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2099 -1.5461 4.7653 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.4456 -4.2392 5.5537 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.4422 -2.1911 4.6264 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0942 -2.2005 5.2940 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.2371 -3.5414 5.6791 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.5342 -3.5321 5.0254 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.3073 -1.6469 4.2509 H 0 0 0 0 0 0 0 0 0 0 0 0 + -0.8436 -1.6646 5.4313 H 0 0 0 0 0 0 0 0 0 0 0 0 + 6.0516 -4.1561 0.7017 O 0 0 0 0 0 0 0 0 0 0 0 0 + 0.7117 -3.8007 -6.2146 O 0 0 0 0 0 0 0 0 0 0 0 0 + -3.7683 -3.8688 -4.9341 O 0 0 0 0 0 0 0 0 0 0 0 0 + 3.6880 -4.2950 4.9225 O 0 0 0 0 0 0 0 0 0 0 0 0 + -6.1602 -4.0388 -0.7294 O 0 0 0 0 0 0 0 0 0 0 0 0 + -4.9799 -4.2148 3.7736 O 0 0 0 0 0 0 0 0 0 0 0 0 + -0.7920 -4.3109 6.2019 O 0 0 0 0 0 0 0 0 0 0 0 0 + 4.9140 -3.9147 -3.8101 O 0 0 0 0 0 0 0 0 0 0 0 0 + -4.8170 -3.4325 -4.1018 C 0 0 0 0 0 0 0 0 0 0 0 0 + -7.0642 -2.8000 -2.5758 C 0 0 0 0 0 0 0 0 0 0 0 0 + -4.8383 -3.8504 -2.7554 C 0 0 0 0 0 0 0 0 0 0 0 0 + -5.8598 -2.7165 -4.6941 C 0 0 0 0 0 0 0 0 0 0 0 0 + -6.9855 -2.4050 -3.9175 C 0 0 0 0 0 0 0 0 0 0 0 0 + -5.9968 -3.5151 -2.0261 C 0 0 0 0 0 0 0 0 0 0 0 0 + -5.8008 -2.4161 -5.7471 H 0 0 0 0 0 0 0 0 0 0 0 0 + -7.9475 -2.5646 -1.9700 H 0 0 0 0 0 0 0 0 0 0 0 0 + -4.1762 -3.7925 4.8497 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.7011 -3.2046 7.1428 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.8124 -4.1494 4.8656 C 0 0 0 0 0 0 0 0 0 0 0 0 + -4.8101 -3.1570 5.9200 C 0 0 0 0 0 0 0 0 0 0 0 0 + -4.0589 -2.8676 7.0685 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.1096 -3.8396 6.0475 C 0 0 0 0 0 0 0 0 0 0 0 0 + -5.8752 -2.9023 5.8641 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.1147 -2.9867 8.0434 H 0 0 0 0 0 0 0 0 0 0 0 0 + 4.7349 -3.8019 4.1205 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.9790 -3.0723 2.6334 C 0 0 0 0 0 0 0 0 0 0 0 0 + 5.7873 -3.1424 4.7594 C 0 0 0 0 0 0 0 0 0 0 0 0 + 4.7448 -4.1146 2.7455 C 0 0 0 0 0 0 0 0 0 0 0 0 + 5.9021 -3.7339 2.0369 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.9113 -2.7813 4.0020 C 0 0 0 0 0 0 0 0 0 0 0 0 + 5.7372 -2.9239 5.8329 H 0 0 0 0 0 0 0 0 0 0 0 0 + 7.8607 -2.7980 2.0420 H 0 0 0 0 0 0 0 0 0 0 0 0 + 4.1048 -3.4040 -4.8425 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.6191 -2.6259 -7.0710 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.7402 -3.7566 -4.8807 C 0 0 0 0 0 0 0 0 0 0 0 0 + 4.7344 -2.6830 -5.8597 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.9779 -2.2985 -6.9765 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.0319 -3.3484 -6.0288 C 0 0 0 0 0 0 0 0 0 0 0 0 + 5.8002 -2.4352 -5.7882 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.0283 -2.3332 -7.9472 H 0 0 0 0 0 0 0 0 0 0 0 0 + 0.3395 4.9287 4.7648 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.5291 4.7253 1.3706 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.0061 4.8977 -3.2299 C 0 0 0 0 0 0 0 0 0 0 0 0 + 4.0721 4.9827 0.4854 H 0 0 0 0 0 0 0 0 0 0 0 0 + -0.9977 5.3124 -2.1960 H 0 0 0 0 0 0 0 0 0 0 0 0 + -0.0934 5.2887 -3.7282 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.5105 5.1422 1.2003 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.7830 4.9948 2.4174 H 0 0 0 0 0 0 0 0 0 0 0 0 + 8.0252 -2.0862 4.6947 C 0 0 0 0 0 0 0 0 0 0 0 0 + -4.7461 -2.1981 8.2012 C 0 0 0 0 0 0 0 0 0 0 0 0 + -8.0890 -1.6487 -4.5608 C 0 0 0 0 0 0 0 0 0 0 0 0 + 4.6605 -1.5382 -8.0533 C 0 0 0 0 0 0 0 0 0 0 0 0 + 8.1220 -1.7810 5.8598 O 0 0 0 0 0 0 0 0 0 0 0 0 + -5.9021 -1.8595 8.2952 O 0 0 0 0 0 0 0 0 0 0 0 0 + -8.1741 -1.2500 -5.6983 O 0 0 0 0 0 0 0 0 0 0 0 0 + 5.8144 -1.1861 -8.1201 O 0 0 0 0 0 0 0 0 0 0 0 0 + -4.5261 5.3919 0.4190 C 0 0 0 0 0 0 0 0 0 0 0 0 + -5.2324 4.6520 -0.0234 H 0 0 0 0 0 0 0 0 0 0 0 0 + -5.1874 6.0983 0.9699 H 0 0 0 0 0 0 0 0 0 0 0 0 + 4.2040 5.4868 -1.6163 C 0 0 0 0 0 0 0 0 0 0 0 0 + 5.1572 4.9842 -1.9036 H 0 0 0 0 0 0 0 0 0 0 0 0 + 3.5823 5.4542 -2.5394 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.2501 5.4055 -3.9711 C 0 0 0 0 0 0 0 0 0 0 0 0 + -3.1738 5.0297 -3.4738 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.2752 5.0021 -5.0100 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.2983 5.2901 3.9036 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.1041 4.5473 4.1053 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.3711 6.0047 4.7537 H 0 0 0 0 0 0 0 0 0 0 0 0 + 4.5001 6.9033 -1.2260 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.2939 6.9016 -4.0258 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.5957 6.0210 2.6238 C 0 0 0 0 0 0 0 0 0 0 0 0 + -3.8309 6.1342 -0.6863 C 0 0 0 0 0 0 0 0 0 0 0 0 + -3.1768 7.6572 -3.6634 O 0 0 0 0 0 0 0 0 0 0 0 0 + -2.6374 6.2462 -0.8925 O 0 0 0 0 0 0 0 0 0 0 0 0 + 4.6195 7.4078 -0.1281 O 0 0 0 0 0 0 0 0 0 0 0 0 + 1.8654 6.2006 1.6681 O 0 0 0 0 0 0 0 0 0 0 0 0 + -5.9156 -5.0903 1.5742 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.6249 -4.7794 -5.9629 H 0 0 0 0 0 0 0 0 0 0 0 0 + 5.8221 -4.9934 -1.6918 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.5372 -5.2797 5.8603 H 0 0 0 0 0 0 0 0 0 0 0 0 + -9.1117 -1.4102 -3.6714 O 0 0 0 0 0 0 0 0 0 0 0 0 + -9.8627 -0.9015 -4.0871 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.8763 -1.9663 9.2424 O 0 0 0 0 0 0 0 0 0 0 0 0 + -4.3219 -1.5160 10.0133 H 0 0 0 0 0 0 0 0 0 0 0 0 + 9.0423 -1.7833 3.8187 O 0 0 0 0 0 0 0 0 0 0 0 0 + 9.7998 -1.3134 4.2675 H 0 0 0 0 0 0 0 0 0 0 0 0 + 3.7893 -1.2317 -9.0736 O 0 0 0 0 0 0 0 0 0 0 0 0 + 4.2320 -0.7194 -9.8068 H 0 0 0 0 0 0 0 0 0 0 0 0 + -4.7354 6.7092 -1.5213 O 0 0 0 0 0 0 0 0 0 0 0 0 + -4.3013 7.2332 -2.2915 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.1748 7.4238 -4.6146 O 0 0 0 0 0 0 0 0 0 0 0 0 + -1.1921 8.4216 -4.6573 H 0 0 0 0 0 0 0 0 0 0 0 0 + 4.6653 7.6801 -2.3429 O 0 0 0 0 0 0 0 0 0 0 0 0 + 4.8778 8.6309 -2.1286 H 0 0 0 0 0 0 0 0 0 0 0 0 + 3.8771 6.4798 2.5999 O 0 0 0 0 0 0 0 0 0 0 0 0 + 4.1102 6.9704 1.7338 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.1619 -4.8252 3.7161 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.3408 -5.4849 4.0436 H 0 0 0 0 0 0 0 0 0 0 0 0 + -1.7354 -4.0961 3.0112 H 0 0 0 0 0 0 0 0 0 0 0 0 + -2.8771 -5.4476 3.1523 H 0 0 0 0 0 0 0 0 0 0 0 0 + 3.6141 -4.8160 2.0892 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.9550 -5.4297 1.2384 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.8707 -4.1032 1.7020 H 0 0 0 0 0 0 0 0 0 0 0 0 + 3.0908 -5.4883 2.7898 H 0 0 0 0 0 0 0 0 0 0 0 0 + 2.0944 -4.5250 -3.7881 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.8108 -5.1943 -3.2828 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2695 -5.1529 -4.1649 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1.6738 -3.8566 -3.0220 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.7178 -4.6106 -2.1490 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.9705 -3.9366 -1.7042 H 0 0 0 0 0 0 0 0 0 0 0 0 + -3.1961 -5.2317 -2.8965 H 0 0 0 0 0 0 0 0 0 0 0 0 + -4.0691 -5.2850 -1.3500 H 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 5 2 0 0 0 0 + 5 6 1 0 0 0 0 + 3 6 2 0 0 0 0 + 3 7 1 0 0 0 0 + 4 7 2 0 0 0 0 + 2 4 1 0 0 0 0 + 3 8 1 0 0 0 0 + 9 10 1 0 0 0 0 + 7 9 1 0 0 0 0 + 11 12 1 0 0 0 0 + 6 11 1 0 0 0 0 + 13 16 2 0 0 0 0 + 16 17 1 0 0 0 0 + 14 17 2 0 0 0 0 + 14 18 1 0 0 0 0 + 15 18 2 0 0 0 0 + 13 15 1 0 0 0 0 + 18 20 1 0 0 0 0 + 16 19 1 0 0 0 0 + 9 13 1 0 0 0 0 + 21 24 2 0 0 0 0 + 24 25 1 0 0 0 0 + 22 25 2 0 0 0 0 + 22 26 1 0 0 0 0 + 23 26 2 0 0 0 0 + 21 23 1 0 0 0 0 + 25 28 1 0 0 0 0 + 23 27 1 0 0 0 0 + 11 21 1 0 0 0 0 + 29 30 1 0 0 0 0 + 26 29 1 0 0 0 0 + 31 32 1 0 0 0 0 + 17 31 1 0 0 0 0 + 33 36 2 0 0 0 0 + 36 37 1 0 0 0 0 + 34 37 2 0 0 0 0 + 34 38 1 0 0 0 0 + 35 38 2 0 0 0 0 + 33 35 1 0 0 0 0 + 38 40 1 0 0 0 0 + 36 39 1 0 0 0 0 + 31 33 1 0 0 0 0 + 29 37 1 0 0 0 0 + 41136 1 0 0 0 0 + 41 42 1 0 0 0 0 + 11 41 1 0 0 0 0 + 43133 1 0 0 0 0 + 43 44 1 0 0 0 0 + 9 43 1 0 0 0 0 + 31134 1 0 0 0 0 + 29135 1 0 0 0 0 + 34 45 1 0 0 0 0 + 22 46 1 0 0 0 0 + 35 47 1 0 0 0 0 + 4 48 1 0 0 0 0 + 15 49 1 0 0 0 0 + 14 50 1 0 0 0 0 + 51 52 1 0 0 0 0 + 48 51 1 0 0 0 0 + 53 54 1 0 0 0 0 + 50 53 1 0 0 0 0 + 47 53 1 0 0 0 0 + 49 51 1 0 0 0 0 + 5 55 1 0 0 0 0 + 56 57 1 0 0 0 0 + 55 56 1 0 0 0 0 + 24 58 1 0 0 0 0 + 56 58 1 0 0 0 0 + 59 60 1 0 0 0 0 + 46 59 1 0 0 0 0 + 45 59 1 0 0 0 0 + 61 64 2 0 0 0 0 + 64 65 1 0 0 0 0 + 62 65 2 0 0 0 0 + 62 66 1 0 0 0 0 + 63 66 2 0 0 0 0 + 61 63 1 0 0 0 0 + 63 67 1 0 0 0 0 + 64 68 1 0 0 0 0 + 56 61 1 0 0 0 0 + 69 72 2 0 0 0 0 + 72 73 1 0 0 0 0 + 70 73 2 0 0 0 0 + 70 74 1 0 0 0 0 + 71 74 2 0 0 0 0 + 69 71 1 0 0 0 0 + 71 75 1 0 0 0 0 + 72 76 1 0 0 0 0 + 59 69 1 0 0 0 0 + 77 80 2 0 0 0 0 + 80 81 1 0 0 0 0 + 78 81 2 0 0 0 0 + 78 82 1 0 0 0 0 + 79 82 2 0 0 0 0 + 77 79 1 0 0 0 0 + 79 83 1 0 0 0 0 + 80 84 1 0 0 0 0 + 53 77 1 0 0 0 0 + 85 88 2 0 0 0 0 + 88 89 1 0 0 0 0 + 86 89 2 0 0 0 0 + 86 90 1 0 0 0 0 + 87 90 2 0 0 0 0 + 85 87 1 0 0 0 0 + 87 91 1 0 0 0 0 + 88 92 1 0 0 0 0 + 51 85 1 0 0 0 0 + 65 93 1 0 0 0 0 + 74 94 1 0 0 0 0 + 73 95 1 0 0 0 0 + 90 96 1 0 0 0 0 + 82 97 1 0 0 0 0 + 81 98 1 0 0 0 0 + 89 99 1 0 0 0 0 + 66100 1 0 0 0 0 +101104 2 0 0 0 0 +104105 1 0 0 0 0 +102105 2 0 0 0 0 +102106 1 0 0 0 0 +103106 2 0 0 0 0 +101103 1 0 0 0 0 +102108 1 0 0 0 0 +104107 1 0 0 0 0 + 95101 1 0 0 0 0 +109112 2 0 0 0 0 +112113 1 0 0 0 0 +110113 2 0 0 0 0 +110114 1 0 0 0 0 +111114 2 0 0 0 0 +109111 1 0 0 0 0 +110116 1 0 0 0 0 +112115 1 0 0 0 0 + 98109 1 0 0 0 0 +117120 2 0 0 0 0 +120121 1 0 0 0 0 +118121 2 0 0 0 0 +118122 1 0 0 0 0 +119122 2 0 0 0 0 +117119 1 0 0 0 0 +118124 1 0 0 0 0 +119123 1 0 0 0 0 +125128 2 0 0 0 0 +128129 1 0 0 0 0 +126129 2 0 0 0 0 +126130 1 0 0 0 0 +127130 2 0 0 0 0 +125127 1 0 0 0 0 +126132 1 0 0 0 0 +128131 1 0 0 0 0 +100125 1 0 0 0 0 + 97106 1 0 0 0 0 + 93121 1 0 0 0 0 + 96117 1 0 0 0 0 + 99114 1 0 0 0 0 + 94130 1 0 0 0 0 +135137 1 0 0 0 0 +135138 1 0 0 0 0 +134139 1 0 0 0 0 +134140 1 0 0 0 0 +122141 1 0 0 0 0 +113142 1 0 0 0 0 +105143 1 0 0 0 0 +129144 1 0 0 0 0 +141145 2 0 0 0 0 +142146 2 0 0 0 0 +143147 2 0 0 0 0 +144148 2 0 0 0 0 +149150 1 0 0 0 0 +149151 1 0 0 0 0 +134149 1 0 0 0 0 +152153 1 0 0 0 0 +152154 1 0 0 0 0 + 41152 1 0 0 0 0 +155156 1 0 0 0 0 +155157 1 0 0 0 0 +135155 1 0 0 0 0 +158159 1 0 0 0 0 +158160 1 0 0 0 0 + 43158 1 0 0 0 0 +152161 1 0 0 0 0 +155162 1 0 0 0 0 +158163 1 0 0 0 0 +149164 1 0 0 0 0 +162165 2 0 0 0 0 +164166 2 0 0 0 0 +161167 2 0 0 0 0 +163168 2 0 0 0 0 + 78169 1 0 0 0 0 + 70170 1 0 0 0 0 + 62171 1 0 0 0 0 + 86172 1 0 0 0 0 +173174 1 0 0 0 0 +143173 1 0 0 0 0 +175176 1 0 0 0 0 +142175 1 0 0 0 0 +177178 1 0 0 0 0 +141177 1 0 0 0 0 +179180 1 0 0 0 0 +144179 1 0 0 0 0 +181182 1 0 0 0 0 +164181 1 0 0 0 0 +183184 1 0 0 0 0 +162183 1 0 0 0 0 +185186 1 0 0 0 0 +161185 1 0 0 0 0 +187188 1 0 0 0 0 +163187 1 0 0 0 0 +189190 1 0 0 0 0 +189191 1 0 0 0 0 +189192 1 0 0 0 0 +111189 1 0 0 0 0 +193194 1 0 0 0 0 +193195 1 0 0 0 0 +193196 1 0 0 0 0 +120193 1 0 0 0 0 +197198 1 0 0 0 0 +197199 1 0 0 0 0 +197200 1 0 0 0 0 +127197 1 0 0 0 0 +201202 1 0 0 0 0 +201203 1 0 0 0 0 +201204 1 0 0 0 0 +103201 1 0 0 0 0 +M END +$$$$ diff --git a/femto/fe/tests/data/temoa/temoa.xml b/femto/fe/tests/data/temoa/temoa.xml new file mode 100644 index 0000000..423f049 --- /dev/null +++ b/femto/fe/tests/data/temoa/temoa.xml @@ -0,0 +1,2285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/femto/fe/tests/septop/test_cli.py b/femto/fe/tests/septop/test_cli.py index 22f1278..90556df 100644 --- a/femto/fe/tests/septop/test_cli.py +++ b/femto/fe/tests/septop/test_cli.py @@ -84,13 +84,12 @@ def test_run_solution_with_paths(click_runner, mock_cuda_devices, tmp_cwd, mocke mock_run.assert_called_once_with( mocker.ANY, TEMOA_SYSTEM.ligand_1_coords, - TEMOA_SYSTEM.ligand_1_params, TEMOA_SYSTEM.ligand_2_coords, - TEMOA_SYSTEM.ligand_2_params, expected_output_dir, expected_report_dir, TEMOA_SYSTEM.ligand_1_ref_atoms, TEMOA_SYSTEM.ligand_2_ref_atoms, + [TEMOA_SYSTEM.ligand_1_params, TEMOA_SYSTEM.ligand_2_params], ) @@ -119,11 +118,11 @@ def test_run_solution_with_directory( if result.exit_code != 0: raise result.exception - expected_ligand_1_coords = mock_bfe_directory / "forcefield/1h1q/vacuum.mol2" - expected_ligand_1_params = mock_bfe_directory / "forcefield/1h1q/vacuum.parm7" + expected_ligand_1_coords = mock_bfe_directory / "forcefield/1h1q/vacuum.sdf" + expected_ligand_1_params = mock_bfe_directory / "forcefield/1h1q/vacuum.xml" - expected_ligand_2_coords = mock_bfe_directory / "forcefield/1oiu/vacuum.mol2" - expected_ligand_2_params = mock_bfe_directory / "forcefield/1oiu/vacuum.parm7" + expected_ligand_2_coords = mock_bfe_directory / "forcefield/1oiu/vacuum.sdf" + expected_ligand_2_params = mock_bfe_directory / "forcefield/1oiu/vacuum.xml" expected_ligand_1_ref = None expected_ligand_2_ref = None @@ -131,13 +130,12 @@ def test_run_solution_with_directory( mock_run.assert_called_once_with( mocker.ANY, expected_ligand_1_coords, - expected_ligand_1_params, expected_ligand_2_coords, - expected_ligand_2_params, expected_output_dir, None, expected_ligand_1_ref, expected_ligand_2_ref, + [expected_ligand_1_params, expected_ligand_2_params], ) @@ -182,16 +180,19 @@ def test_run_complex_with_paths(click_runner, mock_cuda_devices, tmp_cwd, mocker mock_run.assert_called_once_with( mocker.ANY, TEMOA_SYSTEM.ligand_1_coords, - TEMOA_SYSTEM.ligand_1_params, TEMOA_SYSTEM.ligand_2_coords, - TEMOA_SYSTEM.ligand_2_params, TEMOA_SYSTEM.receptor_coords, - TEMOA_SYSTEM.receptor_params, + [], expected_output_dir, expected_report_dir, TEMOA_SYSTEM.ligand_1_ref_atoms, TEMOA_SYSTEM.ligand_2_ref_atoms, TEMOA_SYSTEM.receptor_ref_atoms, + [ + TEMOA_SYSTEM.ligand_1_params, + TEMOA_SYSTEM.ligand_2_params, + TEMOA_SYSTEM.receptor_params, + ], ) @@ -247,14 +248,13 @@ def test_run_complex_with_directory( if result.exit_code != 0: raise result.exception - expected_ligand_1_coords = mock_bfe_directory / "forcefield/1h1q/vacuum.mol2" - expected_ligand_1_params = mock_bfe_directory / "forcefield/1h1q/vacuum.parm7" + expected_ligand_1_coords = mock_bfe_directory / "forcefield/1h1q/vacuum.sdf" + expected_ligand_1_params = mock_bfe_directory / "forcefield/1h1q/vacuum.xml" - expected_ligand_2_coords = mock_bfe_directory / "forcefield/1oiu/vacuum.mol2" - expected_ligand_2_params = mock_bfe_directory / "forcefield/1oiu/vacuum.parm7" + expected_ligand_2_coords = mock_bfe_directory / "forcefield/1oiu/vacuum.sdf" + expected_ligand_2_params = mock_bfe_directory / "forcefield/1oiu/vacuum.xml" expected_receptor_coords = mock_bfe_directory / "proteins/cdk2/protein.pdb" - expected_receptor_params = None expected_ligand_1_ref = None expected_ligand_2_ref = None @@ -263,16 +263,15 @@ def test_run_complex_with_directory( mock_run.assert_called_once_with( mocker.ANY, expected_ligand_1_coords, - expected_ligand_1_params, expected_ligand_2_coords, - expected_ligand_2_params, expected_receptor_coords, - expected_receptor_params, + [], expected_output_dir, None, expected_ligand_1_ref, expected_ligand_2_ref, expected_receptor_ref, + [expected_ligand_1_params, expected_ligand_2_params], ) diff --git a/femto/fe/tests/septop/test_equilibrate.py b/femto/fe/tests/septop/test_equilibrate.py index 763f42d..73cb60c 100644 --- a/femto/fe/tests/septop/test_equilibrate.py +++ b/femto/fe/tests/septop/test_equilibrate.py @@ -1,8 +1,8 @@ +import mdtop import numpy import openmm import openmm.app import openmm.unit -import parmed import pytest import femto.fe.config @@ -18,10 +18,10 @@ @pytest.fixture -def mock_topology() -> parmed.Structure: +def mock_topology() -> mdtop.Topology: topology = build_mock_structure(["[Ar]"]) topology.residues[0].name = femto.md.constants.LIGAND_1_RESIDUE_NAME - topology.box = numpy.array([50.0, 50.0, 50.0, 90.0, 90.0, 90.0]) + topology.box = numpy.eye(3) * 50.0 return topology @@ -31,7 +31,7 @@ def mock_system(mock_topology) -> openmm.System: system = openmm.System() system.addParticle(1.0) - system.setDefaultPeriodicBoxVectors(*mock_topology.box_vectors) + system.setDefaultPeriodicBoxVectors(*mock_topology.box) force = openmm.NonbondedForce() force.setNonbondedMethod(openmm.NonbondedForce.CutoffPeriodic) diff --git a/femto/fe/tests/septop/test_runner.py b/femto/fe/tests/septop/test_runner.py index 0f26688..0f581cf 100644 --- a/femto/fe/tests/septop/test_runner.py +++ b/femto/fe/tests/septop/test_runner.py @@ -1,5 +1,5 @@ +import mdtop import openmm -import parmed import femto.fe.inputs import femto.fe.septop @@ -19,35 +19,39 @@ def test_prepare_solution_phase(mock_bfe_directory, mocker): mock_setup = mocker.patch( "femto.fe.septop._setup.setup_solution", autospec=True, - return_value=(parmed.Structure(), openmm.System()), + return_value=(mdtop.Topology(), openmm.System()), ) - ligand_1_coords = mock_bfe_directory / "forcefield/1h1q/vacuum.mol2" - ligand_1_params = mock_bfe_directory / "forcefield/1h1q/vacuum.parm7" + ligand_1_coords = mock_bfe_directory / "forcefield/1h1q/vacuum.sdf" + ligand_1_params = mock_bfe_directory / "forcefield/1h1q/vacuum.xml" - ligand_2_coords = mock_bfe_directory / "forcefield/1oiu/vacuum.mol2" - ligand_2_params = mock_bfe_directory / "forcefield/1oiu/vacuum.parm7" + ligand_2_coords = mock_bfe_directory / "forcefield/1oiu/vacuum.sdf" + ligand_2_params = mock_bfe_directory / "forcefield/1oiu/vacuum.xml" - ligand_1_ref_atoms = ("@1", "@2", "@3") - ligand_2_ref_atoms = ("@4", "@5", "@6") + ligand_1_ref_atoms = ("index 1", "index 2", "index 3") + ligand_2_ref_atoms = ("index 4", "index 5", "index 6") config = femto.fe.septop.SepTopConfig().solution topology, system = _prepare_solution_phase( config, ligand_1_coords, - ligand_1_params, ligand_2_coords, - ligand_2_params, ligand_1_ref_atoms, ligand_2_ref_atoms, + [ligand_1_params, ligand_2_params], ) assert isinstance(system, openmm.System) - assert isinstance(topology, parmed.Structure) + assert isinstance(topology, mdtop.Topology) mock_setup.assert_called_once_with( - config.setup, mocker.ANY, mocker.ANY, ligand_1_ref_atoms, ligand_2_ref_atoms + config.setup, + mocker.ANY, + mocker.ANY, + ligand_1_ref_atoms, + ligand_2_ref_atoms, + [ligand_1_params, ligand_2_params], ) @@ -55,57 +59,52 @@ def test_prepare_complex_phase(mock_bfe_directory, mocker): mock_setup = mocker.patch( "femto.fe.septop.setup_complex", autospec=True, - return_value=(parmed.Structure(), openmm.System()), - ) - mock_parameterize = mocker.patch( - "femto.md.utils.amber.parameterize_structure", autospec=True + return_value=(mdtop.Topology(), openmm.System()), ) receptor_coords = mock_bfe_directory / "proteins/cdk2/protein.pdb" - receptor_params = None - ligand_1_coords = mock_bfe_directory / "forcefield/1h1q/vacuum.mol2" - ligand_1_params = mock_bfe_directory / "forcefield/1h1q/vacuum.parm7" + ligand_1_coords = mock_bfe_directory / "forcefield/1h1q/vacuum.sdf" + ligand_1_params = mock_bfe_directory / "forcefield/1h1q/vacuum.xml" - ligand_2_coords = mock_bfe_directory / "forcefield/1oiu/vacuum.mol2" - ligand_2_params = mock_bfe_directory / "forcefield/1oiu/vacuum.parm7" + ligand_2_coords = mock_bfe_directory / "forcefield/1oiu/vacuum.sdf" + ligand_2_params = mock_bfe_directory / "forcefield/1oiu/vacuum.xml" - ligand_1_ref_atoms = ("@1", "@2", "@3") - ligand_2_ref_atoms = ("@4", "@5", "@6") - receptor_ref_atoms = ("@7", "@8", "@9") + ligand_1_ref_atoms = ("index 1", "index 2", "index 3") + ligand_2_ref_atoms = ("index 4", "index 5", "index 6") + receptor_ref_atoms = ("index 7", "index 8", "index 9") config = femto.fe.septop.SepTopConfig().complex topology, system = _prepare_complex_phase( config, + receptor_coords, ligand_1_coords, - ligand_1_params, ligand_2_coords, - ligand_2_params, - receptor_coords, - receptor_params, + [], ligand_1_ref_atoms, ligand_2_ref_atoms, receptor_ref_atoms, + [ligand_1_params, ligand_2_params], ) assert isinstance(system, openmm.System) - assert isinstance(topology, parmed.Structure) + assert isinstance(topology, mdtop.Topology) mock_setup.assert_called_once_with( config.setup, mocker.ANY, mocker.ANY, mocker.ANY, + [], receptor_ref_atoms, ligand_1_ref_atoms, ligand_2_ref_atoms, + [ligand_1_params, ligand_2_params], ) - mock_parameterize.assert_called_once() def test_run_solution_phase(tmp_cwd, mock_bfe_directory, mocker): - # needed so parmed can load the PDB, as it is confused by empty files... mock_topology = build_mock_structure(["O"]) config = femto.fe.septop.SepTopConfig() @@ -122,25 +121,24 @@ def test_run_solution_phase(tmp_cwd, mock_bfe_directory, mocker): ) mock_sample = mocker.patch("femto.fe.septop.run_hremd", autospec=True) - ligand_1_coords = mock_bfe_directory / "forcefield/1h1q/vacuum.mol2" - ligand_1_params = mock_bfe_directory / "forcefield/1h1q/vacuum.parm7" - ligand_2_coords = mock_bfe_directory / "forcefield/1oiu/vacuum.mol2" - ligand_2_params = mock_bfe_directory / "forcefield/1oiu/vacuum.parm7" - ligand_1_ref_atoms = ("@1", "@2", "@3") - ligand_2_ref_atoms = ("@4", "@5", "@6") + ligand_1_coords = mock_bfe_directory / "forcefield/1h1q/vacuum.sdf" + ligand_1_params = mock_bfe_directory / "forcefield/1h1q/vacuum.xml" + ligand_2_coords = mock_bfe_directory / "forcefield/1oiu/vacuum.sdf" + ligand_2_params = mock_bfe_directory / "forcefield/1oiu/vacuum.xml" + ligand_1_ref_atoms = ("index 1", "index 2", "index 3") + ligand_2_ref_atoms = ("index 4", "index 5", "index 6") output_dir = tmp_cwd / "outputs" run_solution_phase( config, ligand_1_coords, - ligand_1_params, ligand_2_coords, - ligand_2_params, output_dir, None, ligand_1_ref_atoms, ligand_2_ref_atoms, + [ligand_1_params, ligand_2_params], ) mock_setup.assert_called_once() @@ -152,13 +150,12 @@ def test_run_solution_phase(tmp_cwd, mock_bfe_directory, mocker): run_solution_phase( config, ligand_1_coords, - ligand_1_params, ligand_2_coords, - ligand_2_params, output_dir, None, ligand_1_ref_atoms, ligand_2_ref_atoms, + [ligand_1_params, ligand_2_params], ) # caching should have taken place. @@ -183,30 +180,28 @@ def test_run_complex_phase(tmp_cwd, mock_bfe_directory, mocker): mock_sample = mocker.patch("femto.fe.septop.run_hremd", autospec=True) receptor_coords = mock_bfe_directory / "proteins/cdk2/protein.pdb" - receptor_params = None - ligand_1_coords = mock_bfe_directory / "forcefield/1h1q/vacuum.mol2" - ligand_1_params = mock_bfe_directory / "forcefield/1h1q/vacuum.parm7" - ligand_2_coords = mock_bfe_directory / "forcefield/1oiu/vacuum.mol2" - ligand_2_params = mock_bfe_directory / "forcefield/1oiu/vacuum.parm7" - ligand_1_ref_atoms = ("@1", "@2", "@3") - ligand_2_ref_atoms = ("@4", "@5", "@6") - receptor_ref_atoms = ("@7", "@8", "@9") + ligand_1_coords = mock_bfe_directory / "forcefield/1h1q/vacuum.sdf" + ligand_1_params = mock_bfe_directory / "forcefield/1h1q/vacuum.xml" + ligand_2_coords = mock_bfe_directory / "forcefield/1oiu/vacuum.sdf" + ligand_2_params = mock_bfe_directory / "forcefield/1oiu/vacuum.xml" + ligand_1_ref_atoms = ("index 1", "index 2", "index 3") + ligand_2_ref_atoms = ("index 4", "index 5", "index 6") + receptor_ref_atoms = ("index 7", "index 8", "index 9") output_dir = tmp_cwd / "outputs" run_complex_phase( config, ligand_1_coords, - ligand_1_params, ligand_2_coords, - ligand_2_params, receptor_coords, - receptor_params, + [], output_dir, None, ligand_1_ref_atoms, ligand_2_ref_atoms, receptor_ref_atoms, + [ligand_1_params, ligand_2_params], ) mock_setup.assert_called_once() @@ -218,16 +213,15 @@ def test_run_complex_phase(tmp_cwd, mock_bfe_directory, mocker): run_complex_phase( config, ligand_1_coords, - ligand_1_params, ligand_2_coords, - ligand_2_params, receptor_coords, - receptor_params, + [], output_dir, None, ligand_1_ref_atoms, ligand_2_ref_atoms, receptor_ref_atoms, + [ligand_1_params, ligand_2_params], ) # caching should have taken place. diff --git a/femto/fe/tests/septop/test_sample.py b/femto/fe/tests/septop/test_sample.py index 26e092d..2e1e83a 100644 --- a/femto/fe/tests/septop/test_sample.py +++ b/femto/fe/tests/septop/test_sample.py @@ -1,6 +1,6 @@ +import mdtop import numpy import openmm -import parmed import pytest from pymbar.testsystems import harmonic_oscillators @@ -11,7 +11,7 @@ import femto.md.reporting import femto.md.rest import femto.md.utils.openmm -from femto.fe.septop._sample import run_hremd, _analyze +from femto.fe.septop._sample import _analyze, run_hremd from femto.md.tests.mocking import build_mock_structure @@ -35,20 +35,25 @@ def mock_system() -> openmm.System: @pytest.fixture() -def mock_topology(mock_system) -> parmed.Structure: +def mock_topology(mock_system) -> mdtop.Topology: topology = build_mock_structure(["[Ar]"]) - topology.coordinates = [[0.0, 0.0, 0.0]] + topology.xyz = numpy.array([[0.0, 0.0, 0.0]]) * openmm.unit.angstrom topology.residues[0].name = femto.md.constants.LIGAND_1_RESIDUE_NAME - topology.box_vectors = mock_system.getDefaultPeriodicBoxVectors() + + box = [ + v.value_in_unit(openmm.unit.angstrom) + for v in mock_system.getDefaultPeriodicBoxVectors() + ] + topology.box = box return topology @pytest.fixture() -def mock_coords(mock_system, mock_topology): +def mock_coords(mock_system, mock_topology) -> openmm.State: context = openmm.Context(mock_system, openmm.VerletIntegrator(0.001)) - context.setPeriodicBoxVectors(*mock_topology.box_vectors) - context.setPositions(mock_topology.positions) + context.setPeriodicBoxVectors(*mock_topology.box) + context.setPositions(mock_topology.xyz) state = context.getState(getPositions=True) diff --git a/femto/fe/tests/septop/test_setup.py b/femto/fe/tests/septop/test_setup.py index 4d92f8a..4c0aac3 100644 --- a/femto/fe/tests/septop/test_setup.py +++ b/femto/fe/tests/septop/test_setup.py @@ -1,16 +1,15 @@ import copy +import mdtop import numpy import openmm.app import openmm.unit -import parmed import pytest import femto.fe.config import femto.fe.septop import femto.md.config -import femto.md.system -import femto.md.utils.amber +import femto.md.prepare import femto.md.utils.openmm from femto.fe.tests.systems import CDK2_SYSTEM from femto.md.tests.mocking import build_mock_structure @@ -24,44 +23,29 @@ def mock_setup_config() -> femto.fe.septop.SepTopSetupStage: @pytest.fixture -def cdk2_ligand_1() -> parmed.amber.AmberParm: - return parmed.amber.AmberParm( - str(CDK2_SYSTEM.ligand_1_params), str(CDK2_SYSTEM.ligand_1_coords) - ) +def cdk2_ligand_1() -> mdtop.Topology: + return femto.md.prepare.load_ligand(CDK2_SYSTEM.ligand_1_coords) @pytest.fixture -def cdk2_ligand_2() -> parmed.amber.AmberParm: - return parmed.amber.AmberParm( - str(CDK2_SYSTEM.ligand_2_params), str(CDK2_SYSTEM.ligand_2_coords) - ) +def cdk2_ligand_2() -> mdtop.Topology: + return femto.md.prepare.load_ligand(CDK2_SYSTEM.ligand_2_coords) @pytest.fixture -def cdk2_receptor() -> parmed.amber.AmberParm: - structure = parmed.load_file(str(CDK2_SYSTEM.receptor_coords), structure=True) - return femto.md.utils.amber.parameterize_structure( - structure, femto.md.config.DEFAULT_TLEAP_SOURCES - ) - - -def test_offset_ligand(): - ligand = build_mock_structure(["[Ar]"]) - - coords_0 = ligand.coordinates - offset = numpy.array([5.0, 4.0, 3.0]) - - femto.fe.septop._setup._offset_ligand(ligand, offset * openmm.unit.angstrom) - - coords_1 = ligand.coordinates - assert numpy.allclose(coords_1, coords_0 + offset) +def cdk2_receptor() -> mdtop.Topology: + return femto.md.prepare.load_receptor(CDK2_SYSTEM.receptor_coords) def test_compute_ligand_offset(): ligand_1 = build_mock_structure(["[H]Cl"]) - ligand_1.coordinates = numpy.array([[-2.0, 0.0, 0.0], [2.0, 0.0, 0.0]]) + ligand_1.xyz = ( + numpy.array([[-2.0, 0.0, 0.0], [2.0, 0.0, 0.0]]) * openmm.unit.angstrom + ) ligand_2 = build_mock_structure(["[H]Cl"]) - ligand_2.coordinates = numpy.array([[0.0, 0.0, 0.0], [0.0, 0.0, 2.0]]) + ligand_2.xyz = ( + numpy.array([[0.0, 0.0, 0.0], [0.0, 0.0, 2.0]]) * openmm.unit.angstrom + ) expected_distance = (2.0 + 1.0) * 1.5 expected_offset = numpy.array([expected_distance, 0.0, -1.0]) * openmm.unit.angstrom @@ -78,7 +62,7 @@ def test_apply_complex_restraints(mocker): receptor = build_mock_structure(["[Ar]"]) ligand_1 = build_mock_structure(["[Ar]"]) - ligand_1.coordinates = numpy.array([[expected_distance, 0.0, 0.0]]) + ligand_1.xyz = numpy.array([[expected_distance, 0.0, 0.0]]) * openmm.unit.angstrom expected_name = "test restraint" @@ -125,7 +109,7 @@ def test_apply_solution_restraints(): ligand_1 = build_mock_structure(["[Ar]"]) ligand_2 = build_mock_structure(["[Ar]"]) - ligand_2.coordinates = numpy.array([[expected_distance, 0.0, 0.0]]) + ligand_2.xyz = numpy.array([[expected_distance, 0.0, 0.0]]) * openmm.unit.angstrom system = openmm.System() @@ -155,20 +139,28 @@ def test_apply_solution_restraints(): def test_setup_system_abfe(cdk2_ligand_1, cdk2_receptor, mock_setup_config, mocker): n_ligand_atoms = len(cdk2_ligand_1.atoms) - def mock_solvate_fn(receptor, lig_1, lig_2, *_, **__): + def mock_prepare_system(receptor, lig_1, lig_2, *_, **__): assert lig_2 is None - complex = receptor + lig_1 - complex.box = [100, 100, 100, 90, 90, 90] - return complex + lig_1.residues[0].name = "L1" + + bound = lig_1 + receptor + bound.box = numpy.eye(3) * 100.0 * openmm.unit.angstrom + + mock_system = openmm.System() - mock_solvate = mocker.patch( - "femto.md.solvate.solvate_system", + for _ in range(bound.n_atoms): + mock_system.addParticle(1.0 * openmm.unit.amu) + + return bound, mock_system + + mock_prepare = mocker.patch( + "femto.md.prepare.prepare_system", autospec=True, - side_effect=mock_solvate_fn, + side_effect=mock_prepare_system, ) - mock_apply_hmr = mocker.patch("femto.md.system.apply_hmr", autospec=True) + mock_apply_hmr = mocker.patch("femto.md.prepare.apply_hmr", autospec=True) mock_apply_rest = mocker.patch("femto.md.rest.apply_rest", autospec=True) mock_apply_fep = mocker.patch("femto.fe.fep.apply_fep", autospec=True) @@ -187,24 +179,26 @@ def mock_solvate_fn(receptor, lig_1, lig_2, *_, **__): cdk2_ligand_1, None, cdk2_receptor, + [], ligand_1_ref_query=None, ligand_2_ref_query=None, ligand_2_offset=None, + extra_params=None, ) - assert isinstance(topology, parmed.Structure) + assert isinstance(topology, mdtop.Topology) assert isinstance(system, openmm.System) expected_ligand_idxs = set(range(n_ligand_atoms)) - mock_solvate.assert_called_once() + mock_prepare.assert_called_once() mock_apply_hmr.assert_called_once_with(mocker.ANY, mocker.ANY, expected_h_mass) mock_apply_rest.assert_called_once_with( mocker.ANY, expected_ligand_idxs, mock_setup_config.rest_config ) mock_apply_fep.assert_called_once_with( - mocker.ANY, expected_ligand_idxs, None, mock_setup_config.fep_config + mocker.ANY, expected_ligand_idxs, set(), mock_setup_config.fep_config ) assert len(topology.atoms) == system.getNumParticles() @@ -216,18 +210,27 @@ def test_setup_system_rbfe( n_ligand_1_atoms = len(cdk2_ligand_1.atoms) n_ligand_2_atoms = len(cdk2_ligand_2.atoms) - def mock_solvate_fn(receptor, lig_1, lig_2, *_, **__): - complex = receptor + lig_1 + lig_2 - complex.box = [100, 100, 100, 90, 90, 90] - return complex + def mock_prepare_system(receptor, lig_1, lig_2, *_, **__): + lig_1.residues[0].name = "L1" + lig_2.residues[0].name = "R1" + + bound = lig_1 + lig_2 + receptor + bound.box = numpy.eye(3) * 100.0 * openmm.unit.angstrom + + mock_system = openmm.System() - mock_solvate = mocker.patch( - "femto.md.solvate.solvate_system", + for _ in range(bound.n_atoms): + mock_system.addParticle(1.0 * openmm.unit.amu) + + return bound, mock_system + + mock_prepare = mocker.patch( + "femto.md.prepare.prepare_system", autospec=True, - side_effect=mock_solvate_fn, + side_effect=mock_prepare_system, ) - mocker.patch("femto.md.system.apply_hmr", autospec=True) + mocker.patch("femto.md.prepare.apply_hmr", autospec=True) mock_apply_rest = mocker.patch("femto.md.rest.apply_rest", autospec=True) mock_apply_fep = mocker.patch("femto.fe.fep.apply_fep", autospec=True) @@ -252,18 +255,20 @@ def mock_solvate_fn(receptor, lig_1, lig_2, *_, **__): cdk2_ligand_1, cdk2_ligand_2, cdk2_receptor, + [], ligand_1_ref_query=None, ligand_2_ref_query=None, ligand_2_offset=None, + extra_params=None, ) - assert isinstance(topology, parmed.Structure) + assert isinstance(topology, mdtop.Topology) assert isinstance(system, openmm.System) expected_ligand_1_idxs = set(range(n_ligand_1_atoms)) expected_ligand_2_idxs = {i + n_ligand_1_atoms for i in range(n_ligand_2_atoms)} - mock_solvate.assert_called_once() + mock_prepare.assert_called_once() mock_apply_rest.assert_called_once_with( mocker.ANY, @@ -290,6 +295,11 @@ def mock_solvate_fn(receptor, lig_1, lig_2, *_, **__): def test_setup_complex(cdk2_ligand_1, cdk2_ligand_2, cdk2_receptor, mocker): n_ligand_atoms = len(cdk2_ligand_1.atoms) + len(cdk2_ligand_2.atoms) + cdk2_ligand_1.residues[0].name = "L1" + cdk2_ligand_2.residues[0].name = "R1" + + topology = cdk2_ligand_1 + cdk2_ligand_2 + cdk2_receptor + mocker.patch( "femto.fe.reference.queries_to_idxs", autospec=True, @@ -299,7 +309,7 @@ def test_setup_complex(cdk2_ligand_1, cdk2_ligand_2, cdk2_receptor, mocker): mock_setup_system = mocker.patch( "femto.fe.septop._setup._setup_system", autospec=True, - return_value=(openmm.System(), mocker.MagicMock(), (0, 1, 2), (3, 4, 5)), + return_value=(openmm.System(), topology, (0, 1, 2), (3, 4, 5)), ) mock_apply_restraints = mocker.patch( "femto.fe.septop._setup._apply_complex_restraints", autospec=True @@ -313,13 +323,22 @@ def test_setup_complex(cdk2_ligand_1, cdk2_ligand_2, cdk2_receptor, mocker): cdk2_receptor, cdk2_ligand_1, cdk2_ligand_2, - ("@1", "@2", "@3"), + [], + ("index 1", "index 2", "index 3"), ligand_1_ref_query=None, ligand_2_ref_query=None, ) mock_setup_system.assert_called_once_with( - mock_config, cdk2_ligand_1, cdk2_ligand_2, cdk2_receptor, None, None + mock_config, + cdk2_ligand_1, + cdk2_ligand_2, + cdk2_receptor, + [], + None, + None, + None, + None, ) mock_apply_restraints.assert_has_calls( [ @@ -376,7 +395,9 @@ def test_setup_solution(cdk2_ligand_1, cdk2_ligand_2, mock_setup_config, mocker) None, None, None, - pytest.approx(-expected_offset), + None, + None, + None, ) mock_apply_restraints.assert_called_once_with( mocker.ANY, 1, 4, mock_setup_config.restraints, mocker.ANY diff --git a/femto/fe/tests/systems.py b/femto/fe/tests/systems.py index 34a62e6..45ee876 100644 --- a/femto/fe/tests/systems.py +++ b/femto/fe/tests/systems.py @@ -1,9 +1,8 @@ import pathlib import shutil -import parmed - import femto.fe.inputs +import femto.md.utils.models _DATA_DIR = pathlib.Path(__file__).parent / "data" @@ -101,40 +100,38 @@ def rbfe_network(self) -> femto.fe.inputs.Network: TEMOA_SYSTEM = TestSystem( directory=TEMOA_DATA_DIR, receptor_name="temoa", - receptor_coords=TEMOA_DATA_DIR / "temoa.rst7", - receptor_params=TEMOA_DATA_DIR / "temoa.parm7", - receptor_cavity_mask="@1-40", - receptor_ref_atoms=("@1", "@2", "@3"), + receptor_coords=TEMOA_DATA_DIR / "temoa.sdf", + receptor_params=TEMOA_DATA_DIR / "temoa.xml", + receptor_cavity_mask="index 1-40", + receptor_ref_atoms=("index 1", "index 2", "index 3"), ligand_1_name="g1", - ligand_1_coords=TEMOA_DATA_DIR / "g1.rst7", - ligand_1_params=TEMOA_DATA_DIR / "g1.parm7", - ligand_1_ref_atoms=("@8", "@6", "@4"), + ligand_1_coords=TEMOA_DATA_DIR / "g1.mol2", + ligand_1_params=TEMOA_DATA_DIR / "g1.xml", + ligand_1_ref_atoms=("index 8", "index 6", "index 4"), ligand_2_name="g4", - ligand_2_coords=TEMOA_DATA_DIR / "g4.rst7", - ligand_2_params=TEMOA_DATA_DIR / "g4.parm7", - ligand_2_ref_atoms=("@3", "@5", "@1"), + ligand_2_coords=TEMOA_DATA_DIR / "g4.mol2", + ligand_2_params=TEMOA_DATA_DIR / "g4.xml", + ligand_2_ref_atoms=("index 3", "index 5", "index 1"), ) CDK2_SYSTEM = TestSystem( directory=CDK2_DATA_DIR, receptor_name="cdk2", receptor_coords=CDK2_DATA_DIR / "cdk2.pdb", receptor_params=None, - receptor_cavity_mask=":12,14,16,22,84,87,88,134,146,147 & @CA", - receptor_ref_atoms=("@1", "@2", "@3"), + receptor_cavity_mask="resi 12+14+16+22+84+87+88+134+146+147 and name CA", + receptor_ref_atoms=("index 1", "index 2", "index 3"), ligand_1_name="1h1q", - ligand_1_coords=CDK2_DATA_DIR / "1h1q.rst7", - ligand_1_params=CDK2_DATA_DIR / "1h1q.parm7", - ligand_1_ref_atoms=("@14", "@21", "@18"), + ligand_1_coords=CDK2_DATA_DIR / "1h1q.sdf", + ligand_1_params=CDK2_DATA_DIR / "1h1q.xml", + ligand_1_ref_atoms=("index 14", "index 21", "index 18"), ligand_2_name="1oiu", - ligand_2_coords=CDK2_DATA_DIR / "1oiu.rst7", - ligand_2_params=CDK2_DATA_DIR / "1oiu.parm7", - ligand_2_ref_atoms=("@16", "@23", "@20"), + ligand_2_coords=CDK2_DATA_DIR / "1oiu.sdf", + ligand_2_params=CDK2_DATA_DIR / "1oiu.xml", + ligand_2_ref_atoms=("index 16", "index 23", "index 20"), ) -def _create_standard_inputs( - root_dir: pathlib.Path, system: TestSystem, ligand_coord_suffix: str -): +def _create_standard_inputs(root_dir: pathlib.Path, system: TestSystem): """Create a standard BFE directory structure with the given inputs. Notes: @@ -154,7 +151,7 @@ def _create_standard_inputs( if system.receptor_params is not None: shutil.copyfile( - system.receptor_params, output_receptor_path.with_suffix(".parm7") + system.receptor_params, output_receptor_path.with_suffix(".xml") ) ligands = [ @@ -166,24 +163,17 @@ def _create_standard_inputs( ligand_dir = root_dir / "forcefield" / ligand_name ligand_dir.mkdir(exist_ok=True, parents=True) - structure = parmed.amber.AmberParm(str(ligand_params), str(ligand_coords)) - structure.save(str(ligand_dir / "vacuum.parm7"), overwrite=True) - structure.save( - str(ligand_dir / f"vacuum.{ligand_coord_suffix}"), overwrite=True - ) + shutil.copyfile(ligand_coords, ligand_dir / f"vacuum{ligand_coords.suffix}") + shutil.copyfile(ligand_params, ligand_dir / "vacuum.xml") return root_dir -def create_temoa_input_directory( - root_dir: pathlib.Path, ligand_coord_suffix: str = "rst7" -): +def create_temoa_input_directory(root_dir: pathlib.Path): """Create a directory structure containing the TEMOA input files""" - _create_standard_inputs(root_dir, TEMOA_SYSTEM, ligand_coord_suffix) + _create_standard_inputs(root_dir, TEMOA_SYSTEM) -def create_cdk2_input_directory( - root_dir: pathlib.Path, ligand_coord_suffix: str = "mol2" -): +def create_cdk2_input_directory(root_dir: pathlib.Path): """Create a directory structure containing the CDK2 input files""" - _create_standard_inputs(root_dir, CDK2_SYSTEM, ligand_coord_suffix) + _create_standard_inputs(root_dir, CDK2_SYSTEM) diff --git a/femto/fe/tests/test_inputs.py b/femto/fe/tests/test_inputs.py index e4e6be9..7e6540a 100644 --- a/femto/fe/tests/test_inputs.py +++ b/femto/fe/tests/test_inputs.py @@ -29,8 +29,8 @@ def test_find_receptor(tmp_cwd): receptor = _find_receptor(tmp_cwd) assert receptor.name == "temoa" - assert receptor.coords == tmp_cwd / "proteins" / "temoa" / "protein.rst7" - assert receptor.params == tmp_cwd / "proteins" / "temoa" / "protein.parm7" + assert receptor.coords == tmp_cwd / "proteins" / "temoa" / "protein.sdf" + assert receptor.params == tmp_cwd / "proteins" / "temoa" / "protein.xml" def test_find_receptor_no_params(tmp_cwd): @@ -51,19 +51,19 @@ def test_find_receptor_missing(tmp_cwd): def test_find_edge(tmp_cwd): - create_temoa_input_directory(tmp_cwd, ligand_coord_suffix="rst7") + create_temoa_input_directory(tmp_cwd) edge_config = femto.fe.config.Edge(ligand_1="g1", ligand_2="g4") found_edge = _find_edge(tmp_cwd, edge_config) assert found_edge.ligand_1.name == "g1" - assert found_edge.ligand_1.coords == tmp_cwd / "forcefield" / "g1" / "vacuum.rst7" - assert found_edge.ligand_1.params == tmp_cwd / "forcefield" / "g1" / "vacuum.parm7" + assert found_edge.ligand_1.coords == tmp_cwd / "forcefield" / "g1" / "vacuum.mol2" + assert found_edge.ligand_1.params == tmp_cwd / "forcefield" / "g1" / "vacuum.xml" assert found_edge.ligand_2.name == "g4" - assert found_edge.ligand_2.coords == tmp_cwd / "forcefield" / "g4" / "vacuum.rst7" - assert found_edge.ligand_2.params == tmp_cwd / "forcefield" / "g4" / "vacuum.parm7" + assert found_edge.ligand_2.coords == tmp_cwd / "forcefield" / "g4" / "vacuum.mol2" + assert found_edge.ligand_2.params == tmp_cwd / "forcefield" / "g4" / "vacuum.xml" @pytest.mark.parametrize( @@ -74,7 +74,7 @@ def test_find_edge(tmp_cwd): ], ) def test_find_edge_missing(ligand_1, ligand_2, expected_match, tmp_cwd): - create_temoa_input_directory(tmp_cwd, ligand_coord_suffix="rst7") + create_temoa_input_directory(tmp_cwd) edge_config = femto.fe.config.Edge(ligand_1=ligand_1, ligand_2=ligand_2) @@ -83,7 +83,7 @@ def test_find_edge_missing(ligand_1, ligand_2, expected_match, tmp_cwd): def test_find_edges(tmp_cwd): - create_cdk2_input_directory(tmp_cwd, ligand_coord_suffix="mol2") + create_cdk2_input_directory(tmp_cwd) (tmp_cwd / "Morph.in").write_text( f"{CDK2_SYSTEM.ligand_1_name}~{CDK2_SYSTEM.ligand_1_name}" ) @@ -98,7 +98,5 @@ def test_find_edges(tmp_cwd): found_edge = network.edges[0] assert found_edge.ligand_1.name == "1h1q" - assert found_edge.ligand_1.coords == tmp_cwd / "forcefield" / "1h1q" / "vacuum.mol2" - assert ( - found_edge.ligand_1.params == tmp_cwd / "forcefield" / "1h1q" / "vacuum.parm7" - ) + assert found_edge.ligand_1.coords == tmp_cwd / "forcefield" / "1h1q" / "vacuum.sdf" + assert found_edge.ligand_1.params == tmp_cwd / "forcefield" / "1h1q" / "vacuum.xml" diff --git a/femto/fe/tests/test_reference.py b/femto/fe/tests/test_reference.py index 7a20361..5f7ad12 100644 --- a/femto/fe/tests/test_reference.py +++ b/femto/fe/tests/test_reference.py @@ -1,43 +1,42 @@ import copy +import mdtop import mdtraj import numpy import openmm.unit -import parmed import pytest import femto.fe.reference from femto.fe.reference import ( _create_ligand_queries, - _structure_to_mdtraj, + _topology_to_mdtraj, queries_to_idxs, select_ligand_idxs, select_protein_cavity_atoms, ) from femto.fe.tests.systems import CDK2_SYSTEM +from femto.md.prepare import load_receptor from femto.md.tests.mocking import build_mock_structure @pytest.fixture -def cdk2_receptor() -> parmed.Structure: - return parmed.load_file(str(CDK2_SYSTEM.receptor_coords), structure=True) +def cdk2_receptor() -> mdtop.Topology: + return load_receptor(CDK2_SYSTEM.receptor_coords) @pytest.fixture def cdk2_receptor_traj(cdk2_receptor) -> mdtraj.Trajectory: - return _structure_to_mdtraj(cdk2_receptor) + return _topology_to_mdtraj(cdk2_receptor) @pytest.fixture -def cdk2_ligand_1() -> parmed.amber.AmberParm: - return parmed.amber.AmberParm( - str(CDK2_SYSTEM.ligand_1_params), str(CDK2_SYSTEM.ligand_1_coords) - ) +def cdk2_ligand_1() -> mdtop.Topology: + return femto.md.prepare.load_ligand(CDK2_SYSTEM.ligand_1_coords) @pytest.fixture def cdk2_ligand_1_traj(cdk2_ligand_1) -> mdtraj.Trajectory: - return _structure_to_mdtraj(cdk2_ligand_1) + return _topology_to_mdtraj(cdk2_ligand_1) @pytest.fixture @@ -47,27 +46,25 @@ def cdk2_ligand_1_ref_idxs() -> tuple[int, int, int]: @pytest.fixture -def cdk2_ligand_2() -> parmed.amber.AmberParm: - return parmed.amber.AmberParm( - str(CDK2_SYSTEM.ligand_2_params), str(CDK2_SYSTEM.ligand_2_coords) - ) +def cdk2_ligand_2() -> mdtop.Topology: + return femto.md.prepare.load_ligand(CDK2_SYSTEM.ligand_2_coords) @pytest.fixture def cdk2_ligand_2_traj(cdk2_ligand_2) -> mdtraj.Trajectory: - return _structure_to_mdtraj(cdk2_ligand_2) + return _topology_to_mdtraj(cdk2_ligand_2) def test_queries_to_idxs(cdk2_ligand_1): - actual_idxs = queries_to_idxs(cdk2_ligand_1, ("@11", "@10", "@12")) + actual_idxs = queries_to_idxs(cdk2_ligand_1, ("index 11", "index 10", "index 12")) expected_idxs = 10, 9, 11 assert actual_idxs == expected_idxs def test_queries_to_idxs_multiple_matched(cdk2_ligand_1): - with pytest.raises(ValueError, match="atoms while exactly 1 atom was"): - queries_to_idxs(cdk2_ligand_1, ("@11,12", "@10", "@10")) + with pytest.raises(ValueError, match="exactly 1 atom was expected"): + queries_to_idxs(cdk2_ligand_1, ("index 11+12", "index 10", "index 10")) def test_create_ligand_queries_chen(cdk2_ligand_1, cdk2_ligand_2): @@ -75,8 +72,8 @@ def test_create_ligand_queries_chen(cdk2_ligand_1, cdk2_ligand_2): cdk2_ligand_1, cdk2_ligand_2, "chen" ) - expected_ligand_1_ref_masks = ("@11", "@10", "@12") - expected_ligand_2_ref_masks = ("@10", "@11", "@25") + expected_ligand_1_ref_masks = ("index 11", "index 10", "index 12") + expected_ligand_2_ref_masks = ("index 10", "index 11", "index 25") assert ligand_1_ref_masks == expected_ligand_1_ref_masks assert ligand_2_ref_masks == expected_ligand_2_ref_masks @@ -85,40 +82,56 @@ def test_create_ligand_queries_chen(cdk2_ligand_1, cdk2_ligand_2): def test_create_ligand_queries_collinear(cdk2_ligand_1, mocker): mocker.patch("femto.fe.reference._COLLINEAR_THRESHOLD", 1.0 - 1.0e-6) - subset_1 = copy.deepcopy(cdk2_ligand_1["@/C"][0:4]) - subset_1.coordinates = [ - [0.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [2.0, 0.0, 0.0], - [2.0, 0.1, 0.0], - ] - - subset_2 = copy.deepcopy(cdk2_ligand_1["@/C"][0:4]) - subset_2.coordinates = [ - [0.001, 0.0, 0.0], - [1.002, 0.0, 0.0], - [2.003, 0.0, 0.0], - [2.004, 0.1, 0.0], - ] + subset_1 = copy.deepcopy(cdk2_ligand_1["elem C"][0:4]) + subset_1.xyz = ( + numpy.array( + [ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [2.0, 0.0, 0.0], + [2.0, 0.1, 0.0], + ] + ) + * openmm.unit.angstrom + ) + + subset_2 = copy.deepcopy(cdk2_ligand_1["elem C"][0:4]) + subset_2.xyz = ( + numpy.array( + [ + [0.001, 0.0, 0.0], + [1.002, 0.0, 0.0], + [2.003, 0.0, 0.0], + [2.004, 0.1, 0.0], + ] + ) + * openmm.unit.angstrom + ) ligand_1_ref_masks, ligand_2_ref_masks = _create_ligand_queries( subset_1, subset_2, "chen" ) # @1 @2 @3 would from a straight line so @4 should be chosen over @3 - expected_ligand_1_ref_masks = ("@1", "@2", "@4") - expected_ligand_2_ref_masks = ("@1", "@2", "@4") + expected_ligand_1_ref_masks = ("index 1", "index 2", "index 4") + expected_ligand_2_ref_masks = ("index 1", "index 2", "index 4") assert ligand_1_ref_masks == expected_ligand_1_ref_masks assert ligand_2_ref_masks == expected_ligand_2_ref_masks def test_create_ligand_queries_chen_all_co_linear(cdk2_ligand_1): - subset_1 = copy.deepcopy(cdk2_ligand_1["@/C"][0:3]) - subset_1.coordinates = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]] + subset_1 = copy.deepcopy(cdk2_ligand_1["elem C"][0:3]) + subset_1.xyz = ( + numpy.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]]) + * openmm.unit.angstrom + ) - subset_2 = copy.deepcopy(cdk2_ligand_1["@/C"][0:3]) - subset_2.coordinates = [[0.001, 0.0, 0.0], [1.002, 0.0, 0.0], [2.003, 0.0, 0.0]] + subset_2 = copy.deepcopy(cdk2_ligand_1["elem C"][0:3]) + subset_2.xyz = ( + numpy.array([[0.001, 0.0, 0.0], [1.002, 0.0, 0.0], [2.003, 0.0, 0.0]]) + * openmm.unit.angstrom + ) with pytest.raises( RuntimeError, match="Could not find three non-co-linear reference atoms" @@ -130,7 +143,7 @@ def test_create_ligand_queries_baumann(): ligand = build_mock_structure(["C1=CC(=CC2=C1CC(=C2)C)C"]) ref_atoms, _ = _create_ligand_queries(ligand, ligand, "baumann") - assert ref_atoms == ("@6", "@5", "@9") + assert ref_atoms == ("index 6", "index 5", "index 9") def test_create_ligand_queries_required_ligands(cdk2_ligand_1): @@ -142,13 +155,13 @@ def test_create_ligand_queries_required_ligands(cdk2_ligand_1): "ligand_1_queries, expected_ligand_1_idxs", [ (None, (10, 9, 11)), - (("@1", "@2", "@3"), (0, 1, 2)), + (("index 1", "index 2", "index 3"), (0, 1, 2)), ], ) def test_select_ligand_idxs_one_ligand( ligand_1_queries, expected_ligand_1_idxs, cdk2_ligand_1, mocker ): - mock_ligand_1_queries = ("@11", "@10", "@12") + mock_ligand_1_queries = ("index 11", "index 10", "index 12") mocker.patch( "femto.fe.reference._create_ligand_queries_baumann", @@ -173,8 +186,8 @@ def test_select_ligand_idxs_one_ligand( "expected_ligand_2_idxs", [ (None, None, (10, 9, 11), (9, 10, 24)), - (None, ("@1", "@2", "@3"), (10, 9, 11), (0, 1, 2)), - (("@1", "@2", "@3"), None, (0, 1, 2), (9, 10, 24)), + (None, ("index 1", "index 2", "index 3"), (10, 9, 11), (0, 1, 2)), + (("index 1", "index 2", "index 3"), None, (0, 1, 2), (9, 10, 24)), ], ) def test_select_ligand_idxs_two_ligands( @@ -186,8 +199,8 @@ def test_select_ligand_idxs_two_ligands( cdk2_ligand_2, mocker, ): - mock_ligand_1_queries = ("@11", "@10", "@12") - mock_ligand_2_queries = ("@10", "@11", "@25") + mock_ligand_1_queries = ("index 11", "index 10", "index 12") + mock_ligand_2_queries = ("index 10", "index 11", "index 25") mock_create_queries = mocker.patch( "femto.fe.reference._create_ligand_queries", @@ -211,16 +224,18 @@ def test_select_ligand_idxs_two_ligands( assert ligand_2_idxs == expected_ligand_2_idxs -def test_filter_receptor_atoms( - cdk2_receptor_traj, cdk2_ligand_1_traj, cdk2_ligand_1_ref_idxs -): +def test_filter_receptor_atoms(cdk2_receptor, cdk2_ligand_1, cdk2_ligand_1_ref_idxs): # computed using the reference SepTop implementation at commit 7af0b4d # fmt: off expected_idxs = [380, 381, 382, 383, 384, 388, 389, 390, 391, 392, 399, 400, 401, 402, 403, 408, 409, 410, 411, 412, 830, 831, 832, 833, 834, 838, 839, 840, 841, 842, 847, 848, 849, 850, 851, 853, 854, 855, 856, 857, 865, 866, 867, 868, 869, 873, 874, 875, 876, 877, 884, 885, 886, 887, 888, 893, 894, 895, 896, 897, 901, 902, 903, 904, 905, 909, 910, 911, 912, 913, 918, 919, 920, 921, 922, 923, 924, 925, 926, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 1494, 1495, 1496, 1497, 1498, 1502, 1503, 1504, 1505, 1506, 1516, 1517, 1518, 1519, 1520, 1522, 1523, 1524, 1525, 1526, 1530, 1531, 1532, 1533, 1534, 1535, 1536, 1537, 1538, 1540, 1541, 1542, 1543, 1544, 1548, 1549, 1550, 1551, 1552, 1559, 1560, 1561, 1562, 1563, 1564, 1565, 1566, 1567, 1568, 1691, 1692, 1693, 1694, 1695, 1723, 1734, 2109, 2110, 2111, 2112, 2116, 2117, 2118, 2119, 2120] # noqa: E201,E221,E241,E501 + expected_idxs = [i + cdk2_ligand_1.n_atoms for i in expected_idxs] # fmt: on + topology = _topology_to_mdtraj(cdk2_ligand_1 + cdk2_receptor) + topology.box = cdk2_receptor.box + receptor_idxs = femto.fe.reference._filter_receptor_atoms( - cdk2_receptor_traj, cdk2_ligand_1_traj, cdk2_ligand_1_ref_idxs[0] + topology, cdk2_ligand_1_ref_idxs[0] ) assert receptor_idxs == expected_idxs @@ -327,9 +342,12 @@ def test_is_valid_r1( mocker, ): ligand = build_mock_structure(["O"]) - ligand.coordinates = ligand_coords + ligand.xyz = ligand_coords * openmm.unit.angstrom receptor = build_mock_structure(["O"]) - receptor.coordinates = receptor_coords + receptor.xyz = receptor_coords * openmm.unit.angstrom + + topology = _topology_to_mdtraj(ligand + receptor) + topology.box = receptor.box mocker.patch("femto.fe.reference._COLLINEAR_THRESHOLD", 1.0 - 1.0e-6) @@ -337,9 +355,7 @@ def test_is_valid_r1( spied_linear = mocker.spy(femto.fe.reference, "_is_angle_linear") spied_trans = mocker.spy(femto.fe.reference, "_is_dihedral_trans") - is_valid = femto.fe.reference._is_valid_r1( - _structure_to_mdtraj(receptor), 0, _structure_to_mdtraj(ligand), (0, 1, 2) - ) + is_valid = femto.fe.reference._is_valid_r1(topology, ligand.n_atoms, 0, 1, 2) assert is_valid == expected_valid assert spied_collinear.spy_return == expected_collinear @@ -425,9 +441,12 @@ def test_is_valid_r2( mocker, ): ligand = build_mock_structure(["O"]) - ligand.coordinates = ligand_coords + ligand.xyz = ligand_coords * openmm.unit.angstrom receptor = build_mock_structure(["O"]) - receptor.coordinates = receptor_coords + receptor.xyz = receptor_coords * openmm.unit.angstrom + + topology = _topology_to_mdtraj(ligand + receptor) + topology.box = receptor.box mocker.patch("femto.fe.reference._COLLINEAR_THRESHOLD", 1.0 - 1.0e-6) @@ -436,11 +455,7 @@ def test_is_valid_r2( spied_trans = mocker.spy(femto.fe.reference, "_is_dihedral_trans") is_valid = femto.fe.reference._is_valid_r2( - _structure_to_mdtraj(receptor), - 1, - receptor_ref_idx, - _structure_to_mdtraj(ligand), - (0, 1, 2), + topology, ligand.n_atoms + receptor_ref_idx, ligand.n_atoms + 1, 0, 1 ) assert is_valid == expected_valid @@ -501,9 +516,12 @@ def test_is_valid_r3( mocker, ): ligand = build_mock_structure(["O"]) - ligand.coordinates = ligand_coords + ligand.xyz = ligand_coords * openmm.unit.angstrom receptor = build_mock_structure(["O"]) - receptor.coordinates = receptor_coords + receptor.xyz = receptor_coords * openmm.unit.angstrom + + topology = _topology_to_mdtraj(ligand + receptor) + topology.box = receptor.box mocker.patch("femto.fe.reference._COLLINEAR_THRESHOLD", 1.0 - 1.0e-6) @@ -511,12 +529,11 @@ def test_is_valid_r3( spied_trans = mocker.spy(femto.fe.reference, "_is_dihedral_trans") is_valid = femto.fe.reference._is_valid_r3( - _structure_to_mdtraj(receptor), - 2, - receptor_ref_idxs[0], - receptor_ref_idxs[1], - _structure_to_mdtraj(ligand), - (0, 1, 2), + topology, + ligand.n_atoms + receptor_ref_idxs[0], + ligand.n_atoms + receptor_ref_idxs[1], + ligand.n_atoms + 2, + 0, ) assert is_valid == expected_valid @@ -526,15 +543,22 @@ def test_is_valid_r3( def test_select_receptor_idxs(cdk2_receptor, cdk2_ligand_1, cdk2_ligand_1_ref_idxs): # computed using the reference SepTop implementation at commit 3705ba5 - expected_receptor_idxs = 830, 841, 399 + expected_receptor_idxs = ( + cdk2_ligand_1.n_atoms + 830, + cdk2_ligand_1.n_atoms + 841, + cdk2_ligand_1.n_atoms + 399, + ) + + topology = cdk2_ligand_1 + cdk2_receptor + topology.box = cdk2_receptor.box receptor_idxs = femto.fe.reference.select_receptor_idxs( - cdk2_receptor, cdk2_ligand_1, cdk2_ligand_1_ref_idxs + topology, cdk2_ligand_1_ref_idxs ) assert receptor_idxs == expected_receptor_idxs assert femto.fe.reference.check_receptor_idxs( - cdk2_receptor, receptor_idxs, cdk2_ligand_1, cdk2_ligand_1_ref_idxs + topology, receptor_idxs, cdk2_ligand_1_ref_idxs ) @@ -548,7 +572,7 @@ def test_select_protein_cavity_atoms(cdk2_receptor, cdk2_ligand_1, cdk2_ligand_2 # set sel [atomselect top "protein and (within 5 of resname MOL) and name CA"] # $sel get index expected_mask = ( - "@90,98,102,111,145,248,507,640,651,660,671,679,689,698,1059,1068,1163" + "index 90+98+102+111+145+248+507+640+651+660+671+679+689+698+1059+1068+1163" ) mask = select_protein_cavity_atoms( diff --git a/femto/fe/utils/cli.py b/femto/fe/utils/cli.py index 459ddfd..a2f4e2b 100644 --- a/femto/fe/utils/cli.py +++ b/femto/fe/utils/cli.py @@ -330,8 +330,7 @@ def validate_mutually_exclusive_groups( def configure_logging(log_level: int | None): - """Set up basic logging for the CLI, silencing any overly verbose modules (e.g. - parmed). + """Set up basic logging for the CLI, silencing any overly verbose modules. Args: log_level: The log level to use. diff --git a/femto/md/config.py b/femto/md/config.py index 556db94..2c4e351 100644 --- a/femto/md/config.py +++ b/femto/md/config.py @@ -25,9 +25,12 @@ """The default pressure to simulate at""" -DEFAULT_TLEAP_SOURCES = ["leaprc.water.tip3p", "leaprc.protein.ff14SB"] -"""The default Leap parameter files to load when parameterizing the solvent / -receptor""" +DEFAULT_OPENMM_FF_SOURCES = [ + "amber/protein.ff14SB.xml", + "amber/tip3p_standard.xml", + "amber/tip3p_HFE_multivalent.xml", +] +"""The default parameter files to load when parameterizing the solvent / receptor""" class FlatBottomRestraint(BaseModel): @@ -46,6 +49,9 @@ class FlatBottomRestraint(BaseModel): class BoreschRestraint(BaseModel): """Configuration for a Boresch style restraint between three receptor atoms (r1, r2, r3) and three ligand atoms (l1, l2, l3). + + See Also: + `femto.md.restraints.create_boresch_restraint` """ type: typing.Literal["boresch"] = "boresch" @@ -84,8 +90,8 @@ class BoreschRestraint(BaseModel): ) -class Solvent(BaseModel): - """Configuration for solvating a system.""" +class Prepare(BaseModel): + """Configuration for preparing a system for simulation.""" ionic_strength: OpenMMQuantity[openmm.unit.molar] = pydantic.Field( 0.0 * openmm.unit.molar, @@ -106,12 +112,23 @@ class Solvent(BaseModel): ) water_model: typing.Literal["tip3p"] = pydantic.Field( - "tip3p", description="The water model to use." + "tip3p", + description="The water model to use when generating solvent coordinates. The " + "actual force field parameters used for the solvent are determined by the " + "``default_protein_ff`` or any extra parameters provided while preparing the " + "system.", + ) + + default_protein_ff: list[str] = pydantic.Field( + [*DEFAULT_OPENMM_FF_SOURCES], + description="The default parameters to use when parameterizing the protein, " + "solvent, and ions.", ) - tleap_sources: list[str] = pydantic.Field( - [*DEFAULT_TLEAP_SOURCES], - description="The tLeap parameters to source when parameterizing the system " - "minus any ligands (and possibly receptors) which should be handled separately", + default_ligand_ff: str | None = pydantic.Field( + "openff-2.0.0.offxml", + description="The default parameters to apply when parameterizing ligands, or " + "``None`` otherwise. Currently, only the path to an OpenFF ``offxml`` file " + "can be specified.", ) box_padding: OpenMMQuantity[_ANGSTROM] | None = pydantic.Field( @@ -120,6 +137,11 @@ class Solvent(BaseModel): "offset ligands) and the box wall. This option is mutually exclusive with " "``n_waters``.", ) + box_shape: typing.Literal["cube", "cubeoid"] = pydantic.Field( + "cubeoid", + description="The shape of the box to use when solvating the complex, when " + "``box_padding`` is specified.", + ) n_waters: int | None = pydantic.Field( None, @@ -128,7 +150,7 @@ class Solvent(BaseModel): ) @pydantic.model_validator(mode="after") - def _validate_n_waters(self) -> "Solvent": + def _validate_n_waters(self) -> "Prepare": assert ( self.box_padding is None or self.n_waters is None ), "`box_padding` and `n_waters` are mutually exclusive" @@ -142,10 +164,10 @@ class LangevinIntegrator(BaseModel): type: typing.Literal["langevin"] = "langevin" timestep: OpenMMQuantity[openmm.unit.picosecond] = pydantic.Field( - 0.002 * openmm.unit.picosecond + 0.002 * openmm.unit.picosecond, description="The timestep to use." ) friction: OpenMMQuantity[openmm.unit.picosecond**-1] = pydantic.Field( - 0.5 / openmm.unit.picosecond + 0.5 / openmm.unit.picosecond, description="The friction coefficient." ) constraint_tolerance: float = pydantic.Field( diff --git a/femto/md/prepare.py b/femto/md/prepare.py new file mode 100644 index 0000000..10193ad --- /dev/null +++ b/femto/md/prepare.py @@ -0,0 +1,397 @@ +"""Preparing systems ready for simulation.""" + +import copy +import logging +import os.path +import pathlib +import tempfile + +import mdtop +import numpy +import openmm +import openmm.app +import openmm.unit +from rdkit import Chem + +import femto.md.config +import femto.md.constants + +_LOGGER = logging.getLogger(__name__) + + +def load_ligand(path: pathlib.Path, residue_name: str = "LIG") -> mdtop.Topology: + """Load a ligand from disk. + + Args: + path: The path to the ligand file (.sdf, .mol2) + residue_name: The residue name to assign to the ligand. + + Returns: + The loaded ligand + """ + residue_name = "LIG" if residue_name is None else residue_name + + if path.suffix.lower() == ".sdf": + mol = Chem.AddHs(Chem.MolFromMolFile(str(path), removeHs=False)) + elif path.suffix.lower() == ".mol2": + mol = Chem.AddHs(Chem.MolFromMol2File(str(path), removeHs=False)) + else: + raise NotImplementedError(f"unsupported file format: {path.suffix}") + + top = mdtop.Topology.from_rdkit(mol, residue_name) + + return top + + +def load_ligands( + ligand_1_path: pathlib.Path, + ligand_2_path: pathlib.Path | None, +) -> tuple[mdtop.Topology, mdtop.Topology | None]: + """Load the first, and optionally second, ligand from disk. + + Args: + ligand_1_path: The path to the first ligand. + ligand_2_path: The (optional) path of the second ligand. + + Returns: + The loaded ligands. + """ + + ligand_1 = load_ligand(ligand_1_path, femto.md.constants.LIGAND_1_RESIDUE_NAME) + + if ligand_2_path is None: + return ligand_1, None + + ligand_2 = load_ligand(ligand_2_path, femto.md.constants.LIGAND_2_RESIDUE_NAME) + + return ligand_1, ligand_2 + + +def load_receptor(path: pathlib.Path) -> mdtop.Topology: + """Loads a receptor from disk. + + Args: + path: The path to the receptor (.pdb, .mol2, .sdf). + + Returns: + The loaded receptor. + """ + if path.suffix.lower() == ".pdb": + return mdtop.Topology.from_file(path) + elif path.suffix.lower() in {".mol2", ".sdf"}: + return femto.md.prepare.load_ligand(path, "REC") + + raise NotImplementedError(f"unsupported file format: {path.suffix}") + + +def apply_hmr( + system: openmm.System, + topology: mdtop.Topology, + hydrogen_mass: openmm.unit.Quantity = 1.5 * openmm.unit.amu, +): + """Apply hydrogen mass repartitioning to a system. + + Args: + system: The system to modify in-place. + topology: The topology of the system. + hydrogen_mass: The mass to use ofr hydrogen atoms. + """ + + atoms = topology.atoms + + for bond in topology.bonds: + atom_1: mdtop.Atom = atoms[bond.idx_1] + atom_2: mdtop.Atom = atoms[bond.idx_2] + + if atom_1.atomic_num == 1: + (atom_1, atom_2) = (atom_2, atom_1) + + if atom_2.atomic_num != 1: + continue + if atom_1.atomic_num == 1: + continue + + elements = sorted(a.atomic_num for a in atom_2.residue.atoms) + + if elements == [1, 1, 8]: + continue + + mass_delta = hydrogen_mass - system.getParticleMass(atom_2.index) + + system.setParticleMass(atom_2.index, hydrogen_mass) + system.setParticleMass( + atom_1.index, system.getParticleMass(atom_1.index) - mass_delta + ) + + +def _compute_box_size( + receptor: mdtop.Topology | None, + ligand_1: mdtop.Topology, + ligand_2: mdtop.Topology | None, + cofactors: list[mdtop.Topology], + padding: openmm.unit.Quantity, + ligand_1_offset: openmm.unit.Quantity | None = None, + ligand_2_offset: openmm.unit.Quantity | None = None, + cavity_formers: list[mdtop.Topology] | None = None, +) -> openmm.unit.Quantity: + """Computes the size of the simulation box based on the coordinates of complex with + the ligand offset outside the binding site and a specified additional padding. + + Args: + receptor: The (optional) receptor. + ligand_1: The first ligand. + ligand_2: The (optional) second ligand. + padding: The amount of extra padding to add. + ligand_1_offset: The amount to offset the first ligand by before computing the + box size. + ligand_2_offset: The amount to offset the second ligand by before computing the + box size. + + Returns: + The length of each box axis. + """ + cavity_formers = [] if cavity_formers is None else [] + + ligand_1_offset = ( + ligand_1_offset.value_in_unit(openmm.unit.angstrom) + if ligand_1_offset is not None + else numpy.zeros((1, 3)) + ) + ligand_2_offset = ( + ligand_2_offset.value_in_unit(openmm.unit.angstrom) + if ligand_2_offset is not None + else numpy.zeros((1, 3)) + ) + + def _rmin(atom: mdtop.Atom) -> float: + return Chem.PeriodicTable.GetRvdw(Chem.GetPeriodicTable(), atom.atomic_num) + + vdw_radii = numpy.array( + [ + *([_rmin(atom) for atom in receptor.atoms] if receptor is not None else []), + *[_rmin(atom) for atom in ligand_1.atoms], + *([_rmin(atom) for atom in ligand_2.atoms] if ligand_2 is not None else []), + *([_rmin(atom) for cofactor in cofactors for atom in cofactor.atoms]), + *([_rmin(atom) for former in cavity_formers for atom in former.atoms]), + ] + ) + vdw_radii = vdw_radii.reshape(-1, 1) + + complex_coords = numpy.vstack( + [ + receptor.xyz.value_in_unit(openmm.unit.angstrom) + if receptor is not None + else numpy.zeros((0, 3)), + ligand_1.xyz.value_in_unit(openmm.unit.angstrom) + ligand_1_offset, + ligand_2.xyz.value_in_unit(openmm.unit.angstrom) + ligand_2_offset + if ligand_2 is not None + else numpy.zeros((0, 3)), + ] + + [cofactor.xyz.value_in_unit(openmm.unit.angstrom) for cofactor in cofactors] + + [former.xyz.value_in_unit(openmm.unit.angstrom) for former in cavity_formers] + ) + + padding = padding.value_in_unit(openmm.unit.angstrom) + + min_coords = (complex_coords - vdw_radii).min(axis=0) - padding + max_coords = (complex_coords + vdw_radii).max(axis=0) + padding + + box_lengths = numpy.maximum( + max_coords - min_coords, + 2.0 * numpy.abs(numpy.maximum(ligand_1_offset, ligand_2_offset)), + ) + return box_lengths.flatten() * openmm.unit.angstrom + + +def _register_openff_generator( + ligand_1: mdtop.Topology | None, + ligand_2: mdtop.Topology | None, + cofactors: list[mdtop.Topology], + force_field: openmm.app.ForceField, + config: femto.md.config.Prepare, +): + """Registers an OpenFF template generator with the force field, to use to + parameterize ligands and cofactors.""" + from openff.toolkit.topology import Molecule + from openmmforcefields.generators import SMIRNOFFTemplateGenerator + + def _top_to_openff(top: mdtop.Topology) -> Molecule: + return Molecule.from_rdkit(top.to_rdkit(), allow_undefined_stereo=True) + + molecules = [ + *([_top_to_openff(ligand_1)] if ligand_1 is not None else []), + *([_top_to_openff(ligand_2)] if ligand_2 is not None else []), + *([_top_to_openff(cofactor) for cofactor in cofactors]), + ] + + smirnoff = SMIRNOFFTemplateGenerator( + molecules=molecules, forcefield=config.default_ligand_ff + ).generator + force_field.registerTemplateGenerator(smirnoff) + + +def _load_force_field(*paths: pathlib.Path | str) -> openmm.app.ForceField: + """Load a force field from a list of paths. + + Notes: + Any Amber parameter files (.parm) will be converted to OpenMM XML format. + """ + + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_dir = pathlib.Path(tmp_dir) + + paths_converted = [] + + for i, path in enumerate(paths): + suffix = os.path.splitext(path)[-1].lower() + + if suffix in {".xml", ".ffxml"}: + paths_converted.append(str(path)) + elif suffix in {".parm", ".prmtop", ".parm7"}: + import femto.md.utils.amber + + ffxml = femto.md.utils.amber.convert_parm_to_xml(path) + + path = tmp_dir / f"{i}.xml" + path.write_text(ffxml) + + paths_converted.append(str(path)) + else: + raise NotImplementedError(f"unsupported file format: {suffix}") + + return openmm.app.ForceField(*paths_converted) + + +def prepare_system( + receptor: mdtop.Topology | None, + ligand_1: mdtop.Topology | None, + ligand_2: mdtop.Topology | None, + cofactors: list[mdtop.Topology] | None = None, + config: femto.md.config.Prepare | None = None, + ligand_1_offset: openmm.unit.Quantity | None = None, + ligand_2_offset: openmm.unit.Quantity | None = None, + cavity_formers: list[mdtop.Topology] | None = None, + extra_params: list[pathlib.Path] | None = None, +) -> tuple[mdtop.Topology, openmm.System]: + """Solvates and parameterizes a system. + + Args: + receptor: A receptor to include in the system. + ligand_1: A primary ligand to include in the system. + ligand_2: A secondary ligand to include in the system if doing RBFE. + cofactors: Any cofactors to include in the system. + config: Configuration options for the preparation. + ligand_1_offset: The amount to offset the first ligand by before computing the + box size if using a padded box. + ligand_2_offset: The amount to offset the second ligand by before computing the + box size if using a padded box. + cavity_formers: A (optional) list of topologies that should be considered + 'present' when placing the solvent molecules such that they leave cavities, + but are not added to the final topology themselves. + + Note that cavity formers will be considered when determining the box size. + extra_params: The paths to any extra parameter files (.xml, .parm) to use + when parameterizing the system. + + Returns: + The solvated and parameterized topology and system, containing the ligands, the + receptor, cofactors, ions and solvent. + """ + cofactors = [] if cofactors is None else cofactors + cavity_formers = [] if cavity_formers is None else copy.deepcopy(cavity_formers) + + config = config if config is not None else femto.md.config.Prepare() + + force_field = _load_force_field( + *config.default_protein_ff, *([] if extra_params is None else extra_params) + ) + + if config.default_ligand_ff is not None: + _register_openff_generator(ligand_1, ligand_2, cofactors, force_field, config) + + topology = mdtop.Topology.merge( + *([] if ligand_1 is None else [ligand_1]), + *([] if ligand_2 is None else [ligand_2]), + *([] if receptor is None else [receptor]), + *cofactors, + ) + + box_size = None + + if config.box_padding is not None: + box_size = _compute_box_size( + receptor, + ligand_1, + ligand_2, + cofactors, + config.box_padding, + ligand_1_offset, + ligand_2_offset, + cavity_formers, + ) + + if config.box_shape.lower() == "cube": + box_size = ( + numpy.array([max(box_size.value_in_unit(openmm.unit.angstrom))] * 3) + * openmm.unit.angstrom + ) + + _LOGGER.info(f"using a box size of {box_size}") + + for former in cavity_formers: + for residue in former.residues: + residue.name = "CAV" + + cavity = mdtop.Topology.merge(topology, *cavity_formers) + + _LOGGER.info("adding solvent and ions") + modeller = openmm.app.Modeller(cavity.to_openmm(), cavity.xyz) + modeller.addExtraParticles(force_field) + modeller.addSolvent( + force_field, + model=config.water_model.lower(), + boxSize=box_size, + numAdded=None if box_size is not None else config.n_waters, + boxShape="cube", + positiveIon=config.cation, + negativeIon=config.anion, + neutralize=config.neutralize, + ionicStrength=config.ionic_strength, + ) + + topology = mdtop.Topology.from_openmm(modeller.topology) + topology.xyz = ( + numpy.array(modeller.positions.value_in_unit(openmm.unit.angstrom)) + * openmm.unit.angstrom + ) + topology = topology["not r. CAV"] # strip cavity formers + + _LOGGER.info("parameterizing the system") + + system = force_field.createSystem( + topology.to_openmm(), + nonbondedMethod=openmm.app.PME, + nonbondedCutoff=0.9 * openmm.unit.nanometer, + constraints=openmm.app.HBonds, + rigidWater=True, + ) + + # TODO: is this still needed?? + bound = mdtop.Topology.merge( + *([] if ligand_1 is None else [ligand_1]), + *([] if ligand_2 is None else [ligand_2]), + *([] if receptor is None else [receptor]), + *cofactors, + ) + + center_offset = ( + bound.xyz.value_in_unit(openmm.unit.angstrom).mean(axis=0) + * openmm.unit.angstrom + ) + box_center = ( + numpy.diag(topology.box.value_in_unit(openmm.unit.angstrom)) * 0.5 + ) * openmm.unit.angstrom + + topology.xyz = topology.xyz - center_offset + box_center + + return topology, system diff --git a/femto/md/restraints.py b/femto/md/restraints.py index 055ef12..e29ad45 100644 --- a/femto/md/restraints.py +++ b/femto/md/restraints.py @@ -2,9 +2,9 @@ import typing +import mdtop import numpy.linalg import openmm.unit -import parmed import femto.md.config import femto.md.constants @@ -95,7 +95,7 @@ def create_flat_bottom_restraint( def create_position_restraints( - topology: parmed.Structure, + topology: mdtop.Topology, mask: str, config: femto.md.config.FlatBottomRestraint, ) -> openmm.CustomExternalForce: @@ -110,16 +110,13 @@ def create_position_restraints( The created restraint force. """ - selection = parmed.amber.AmberMask(topology, mask).Selection() - selection_idxs = tuple(i for i, matches in enumerate(selection) if matches) - + selection_idxs = topology.select(mask) assert len(selection_idxs) > 0, "no atoms were found to restrain" - coords = { - i: openmm.Vec3(topology.atoms[i].xx, topology.atoms[i].xy, topology.atoms[i].xz) - * openmm.unit.angstrom - for i in selection_idxs - } + assert topology.xyz is not None, "topology must have coordinates to restrain" + xyz = topology.xyz.value_in_unit(openmm.unit.angstrom) + + coords = {i: openmm.Vec3(*xyz[i]) * openmm.unit.angstrom for i in selection_idxs} if not isinstance(config, femto.md.config.FlatBottomRestraint): raise NotImplementedError("only flat bottom restraints are currently supported") @@ -183,12 +180,24 @@ def create_boresch_restraint( coords: openmm.unit.Quantity, ctx_parameter: str | None = None, ) -> openmm.CustomCompoundBondForce: - """Creates a Boresch restraint force useful in aligning a receptor and ligand. + """Creates a 'Boresch' style restraint force, useful for aligning a receptor and + ligand. + + It applies to three receptor atoms (r1, r2, r3), and three ligand atoms + (l1, l2, l3). + + Namely, the following will be restrained: + * ``r3`` -- ``l1`` distance. + * (θ_a) ``r2`` -- ``r3`` -- ``l1`` angle. + * (θ_b) ``r3`` -- ``l1`` -- ``l2`` angle. + * (φ_a) ``r1`` -- ``r2`` -- ``r3`` -- ``l1`` dih. + * (φ_b) ``r2`` -- ``r3`` -- ``l1`` -- ``l2`` dih. + * (φ_c) ``r3`` -- ``l1`` -- ``l2`` -- ``l3`` dih. Args: config: The restraint configuration. - receptor_atoms: The indices of the receptor atoms to restrain. - ligand_atoms: The indices of the ligand atoms to restrain. + receptor_atoms: The indices of the three receptor atoms to restrain. + ligand_atoms: The indices of the three ligand atoms to restrain. coords: The coordinates of the *full* system. ctx_parameter: An optional context parameter to use to scale the strength of the restraint. @@ -196,6 +205,7 @@ def create_boresch_restraint( Returns: The restraint force. """ + n_particles = 6 # 3 receptor + 3 ligand energy_fn = _BORESCH_ENERGY_FN diff --git a/femto/md/simulate.py b/femto/md/simulate.py index 1566ca6..7007955 100644 --- a/femto/md/simulate.py +++ b/femto/md/simulate.py @@ -4,9 +4,9 @@ import copy import logging +import mdtop import openmm.app import openmm.unit -import parmed import femto.md.anneal import femto.md.config @@ -36,7 +36,7 @@ def _validate_system(system: openmm.System): def _prepare_simulation( system: openmm.System, - topology: parmed.Structure, + topology: mdtop.Topology, state: dict[str, float], coords: openmm.State | None, config: femto.md.config.SimulationStage, @@ -76,7 +76,7 @@ def _prepare_simulation( def simulate_state( system: openmm.System, - topology: parmed.Structure, + topology: mdtop.Topology, state: dict[str, float], stages: list[femto.md.config.SimulationStage], platform: femto.md.constants.OpenMMPlatform, @@ -160,7 +160,7 @@ def simulate_state( def simulate_states( system: openmm.System, - topology: parmed.Structure, + topology: mdtop.Topology, states: list[dict[str, float]], stages: list[femto.md.config.SimulationStage], platform: femto.md.constants.OpenMMPlatform, diff --git a/femto/md/solvate.py b/femto/md/solvate.py deleted file mode 100644 index 1ae8480..0000000 --- a/femto/md/solvate.py +++ /dev/null @@ -1,211 +0,0 @@ -"""Solvate protein ligand systems""" - -import logging - -import numpy -import openmm.app -import openmm.unit -import parmed -import parmed.openmm - -import femto.md.config -from femto.md.utils import amber as amber_utils - -_LOGGER = logging.getLogger(__name__) - - -class _MockForceField: - """Create a mocked version of the OpenMM force field class so that we can - provide a parmed structure directly to Modeller - """ - - def __init__(self, structure: parmed.structure): - self._system = openmm.System() - force = openmm.NonbondedForce() - - for atom in structure.atoms: - self._system.addParticle(atom.mass * openmm.unit.dalton) - force.addParticle( - atom.charge * openmm.unit.elementary_charge, - atom.sigma * openmm.unit.angstrom, - abs(atom.epsilon) * openmm.unit.kilocalories_per_mole, - ) - - self._system.addForce(force) - - def createSystem(self, *_, **__) -> openmm.System: - return self._system - - -def _compute_box_size( - receptor: parmed.Structure | None, - ligand_1: parmed.Structure, - ligand_2: parmed.Structure | None, - padding: openmm.unit.Quantity, - ligand_1_offset: openmm.unit.Quantity | None = None, - ligand_2_offset: openmm.unit.Quantity | None = None, - cavity_formers: list[parmed.Structure] | None = None, -) -> openmm.unit.Quantity: - """Computes the size of the simulation box based on the coordinates of complex with - the ligand offset outside the binding site and a specified additional padding. - - Args: - receptor: The (optional) receptor. - ligand_1: The first ligand. - ligand_2: The (optional) second ligand. - padding: The amount of extra padding to add. - ligand_1_offset: The amount to offset the first ligand by before computing the - box size. - ligand_2_offset: The amount to offset the second ligand by before computing the - box size. - - Returns: - The length of each box axis. - """ - cavity_formers = [] if cavity_formers is None else cavity_formers - - ligand_1_offset = ( - ligand_1_offset.value_in_unit(openmm.unit.angstrom) - if ligand_1_offset is not None - else numpy.zeros((1, 3)) - ) - ligand_2_offset = ( - ligand_2_offset.value_in_unit(openmm.unit.angstrom) - if ligand_2_offset is not None - else numpy.zeros((1, 3)) - ) - - vdw_radii = numpy.array( - [ - *([atom.rmin for atom in receptor.atoms] if receptor is not None else []), - *[atom.rmin for atom in ligand_1.atoms], - *([atom.rmin for atom in ligand_2.atoms] if ligand_2 is not None else []), - *([atom.rmin for former in cavity_formers for atom in former.atoms]), - ] - ) - vdw_radii = vdw_radii.reshape(-1, 1) * 2.0 - - complex_coords = numpy.vstack( - [ - numpy.array(receptor.coordinates) - if receptor is not None - else numpy.zeros((0, 3)), - numpy.array(ligand_1.coordinates) + ligand_1_offset, - numpy.array(ligand_2.coordinates) + ligand_2_offset - if ligand_2 is not None - else numpy.zeros((0, 3)), - ] - + [former.coordinates for former in cavity_formers] - ) - - padding = padding.value_in_unit(openmm.unit.angstrom) - - min_coords = (complex_coords - vdw_radii).min(axis=0) - padding - max_coords = (complex_coords + vdw_radii).max(axis=0) + padding - - box_lengths = numpy.maximum( - max_coords - min_coords, - 2.0 * numpy.abs(numpy.maximum(ligand_1_offset, ligand_2_offset)), - ) - return box_lengths.flatten() * openmm.unit.angstrom - - -def solvate_system( - receptor: parmed.Structure | None, - ligand_1: parmed.Structure, - ligand_2: parmed.Structure | None, - solvent: femto.md.config.Solvent, - ligand_1_offset: openmm.unit.Quantity | None = None, - ligand_2_offset: openmm.unit.Quantity | None = None, - cavity_formers: list[parmed.Structure] | None = None, -) -> parmed.Structure: - """Solvates a system. - - Args: - receptor: The (optional) receptor. - ligand_1: The first ligand. - ligand_2: The (optional) second ligand. - solvent: The solvent configuration. - ligand_1_offset: The amount to offset the first ligand by before computing the - box size if using a padded box. - ligand_2_offset: The amount to offset the second ligand by before computing the - box size if using a padded box. - cavity_formers: The (optional) list of structures that should be considered - 'present' when placing the solvent molecules such that they leave cavities, - but are not added to the final topology themselves. - - Note that cavity formers will be considered when determining the box size. - - Returns: - The solvated system containing the ligands, the receptor, ions and the solvent. - """ - - bound = ligand_1 - - if ligand_2 is not None: - bound = bound + ligand_2 - if receptor is not None: - bound = bound + receptor - - cavity = bound - - if cavity_formers is not None: - for former in cavity_formers: - cavity = cavity + former - - modeller = openmm.app.Modeller(cavity.topology, cavity.positions) - - box_size = None - - if solvent.box_padding is not None: - box_size = _compute_box_size( - receptor, - ligand_1, - ligand_2, - solvent.box_padding, - ligand_1_offset, - ligand_2_offset, - cavity_formers, - ) - - _LOGGER.info(f"using a box size of {box_size}") - - modeller.addSolvent( - _MockForceField(cavity), - model=solvent.water_model.lower(), - boxSize=box_size, - numAdded=None if box_size is not None else solvent.n_waters, - boxShape="cube", - positiveIon=solvent.cation, - negativeIon=solvent.anion, - neutralize=solvent.neutralize, - ionicStrength=solvent.ionic_strength, - ) - - solution: parmed.Structure = parmed.openmm.load_topology( - modeller.topology, None, modeller.positions - ) - solution_box = solution.box - - _LOGGER.info("parameterizing solvent") - - solvent_mask = amber_utils.extract_water_and_ions_mask(bound) - bound.strip(solvent_mask) - - complex_mask = [1 - i for i in amber_utils.extract_water_and_ions_mask(solution)] - solution.strip(complex_mask) - - solution = amber_utils.parameterize_structure(solution, solvent.tleap_sources) - - system = bound + solution - system.box = solution_box - - center_offset = numpy.array(bound.coordinates).mean(axis=0) - - coordinates = numpy.array(system.coordinates) - coordinates -= center_offset - coordinates += system.box[:3] * 0.5 - - system.coordinates = coordinates.tolist() - - return system diff --git a/femto/md/system.py b/femto/md/system.py deleted file mode 100644 index 964a740..0000000 --- a/femto/md/system.py +++ /dev/null @@ -1,143 +0,0 @@ -"""Utilities for preparing and modifying OpenMM systems.""" - -import logging -import pathlib - -import openmm -import parmed - -import femto.md.config -import femto.md.utils.amber -from femto.md.constants import LIGAND_1_RESIDUE_NAME, LIGAND_2_RESIDUE_NAME - -_LOGGER = logging.getLogger(__name__) - - -def load_ligand( - coord_path: pathlib.Path, param_path: pathlib.Path, residue_name: str | None = None -) -> parmed.amber.AmberParm: - """Load a ligand from its coordinate and parameter definition. - - Args: - coord_path: The path to the ligand coordinate file (.rst7 / .mol2) - param_path: The path to the ligand parameter file (.parm7) - residue_name: The (optional) residue name to assign to the ligand. - - Returns: - The loaded ligand - """ - - ligand = parmed.amber.AmberParm(str(param_path), str(coord_path)) - - for residue in ligand.residues: - residue.name = residue_name - - ligand.parm_data["RESIDUE_LABEL"] = [residue_name] - - return ligand - - -def load_ligands( - ligand_1_coords: pathlib.Path, - ligand_1_params: pathlib.Path, - ligand_2_coords: pathlib.Path | None, - ligand_2_params: pathlib.Path | None, -) -> tuple[parmed.amber.AmberParm, parmed.amber.AmberParm | None]: - """Load the first, and optionally second, ligand from their coordinates and - parameters. - - Args: - ligand_1_coords: The coordinates of the first ligand. - ligand_1_params: The parameters of the first ligand. - ligand_2_coords: The (optional) coordinates of the second ligand. - ligand_2_params: The (optional) parameters of the second ligand. - - Returns: - The loaded ligands. - """ - - assert (ligand_2_params is None and ligand_2_coords is None) or ( - ligand_2_params is not None and ligand_2_coords is not None - ), "both or neither of ligand_2_coords and ligand_2_params must be provided" - - ligand_1 = load_ligand(ligand_1_coords, ligand_1_params, LIGAND_1_RESIDUE_NAME) - - if ligand_2_coords is None: - return ligand_1, None - - ligand_2 = load_ligand(ligand_2_coords, ligand_2_params, LIGAND_2_RESIDUE_NAME) - - return ligand_1, ligand_2 - - -def load_receptor( - coord_path: pathlib.Path, - param_path: pathlib.Path | None, - tleap_sources: list[str] | None = None, -) -> parmed.amber.AmberParm: - """Loads a receptor from its coordinates and optionally parameters. - - If no parameters are provided, the receptor will be parameterized using tLeap. - - Args: - coord_path: The coordinates of the receptor. - param_path: The parameters of the receptor. - tleap_sources: The tLeap sources to use to parameterize the receptor. - See ``femto.md.config.DEFAULT_TLEAP_SOURCES`` for the defaults. - - Returns: - The loaded receptor. - """ - tleap_sources = ( - femto.md.config.DEFAULT_TLEAP_SOURCES - if tleap_sources is None - else tleap_sources - ) - - if param_path is not None: - return parmed.amber.AmberParm(str(param_path), str(coord_path)) - - receptor = parmed.load_file(str(coord_path), structure=True) - - _LOGGER.info( - f"no receptor parameters provided, the receptor will parameterize using " - f"tLeap: {tleap_sources}" - ) - return femto.md.utils.amber.parameterize_structure(receptor, tleap_sources) - - -def apply_hmr( - system: openmm.System, - topology: parmed.Structure, - hydrogen_mass: openmm.unit.Quantity = 1.5 * openmm.unit.amu, -): - """Apply hydrogen mass repartitioning to a system. - - Args: - system: The system to modify in-place. - topology: The topology of the system. - hydrogen_mass: The mass to use ofr hydrogen atoms. - """ - - for bond in topology.bonds: - atom_1, atom_2 = bond.atom1, bond.atom2 - - if atom_1.atomic_number == 1: - (atom_1, atom_2) = (atom_2, atom_1) - - if atom_2.atomic_number != 1: - continue - if atom_1.atomic_number == 1: - continue - - elements = sorted(a.atomic_number for a in atom_2.residue.atoms) - - if elements == [1, 1, 8]: - continue - - mass_delta = hydrogen_mass - system.getParticleMass(atom_2.idx) - - system.setParticleMass(atom_2.idx, hydrogen_mass) - system.setParticleMass( - atom_1.idx, system.getParticleMass(atom_1.idx) - mass_delta - ) diff --git a/femto/md/tests/conftest.py b/femto/md/tests/conftest.py index 818d5ee..d5816ea 100644 --- a/femto/md/tests/conftest.py +++ b/femto/md/tests/conftest.py @@ -7,3 +7,8 @@ def tmp_cwd(tmp_path, monkeypatch) -> pathlib.Path: monkeypatch.chdir(tmp_path) yield tmp_path + + +@pytest.fixture +def test_data_dir(): + return pathlib.Path(__file__).parent / "data" diff --git a/femto/fe/tests/data/cdk2/1h1q.parm7 b/femto/md/tests/data/1h1q.parm7 similarity index 100% rename from femto/fe/tests/data/cdk2/1h1q.parm7 rename to femto/md/tests/data/1h1q.parm7 diff --git a/femto/fe/tests/data/cdk2/1h1q.rst7 b/femto/md/tests/data/1h1q.rst7 similarity index 100% rename from femto/fe/tests/data/cdk2/1h1q.rst7 rename to femto/md/tests/data/1h1q.rst7 diff --git a/femto/md/tests/mocking.py b/femto/md/tests/mocking.py index 79bc756..9a487c3 100644 --- a/femto/md/tests/mocking.py +++ b/femto/md/tests/mocking.py @@ -1,14 +1,11 @@ """Utilities for mocking common objects and data""" -import collections -import tempfile - -import parmed +import mdtop from rdkit import Chem from rdkit.Chem import AllChem -def build_mock_structure(smiles: list[str]) -> parmed.Structure: +def build_mock_structure(smiles: list[str]) -> mdtop.Topology: """Build a mock structure from a list of SMILES patterns Notes: @@ -21,13 +18,11 @@ def build_mock_structure(smiles: list[str]) -> parmed.Structure: The mock structure. """ molecules = [Chem.MolFromSmiles(pattern) for pattern in smiles] + topologies = [] for molecule, pattern in zip(molecules, smiles, strict=True): assert molecule is not None, f"{pattern} is not a valid SMILES pattern" - complex = Chem.Mol() - - for i, molecule in enumerate(molecules): molecule = Chem.AddHs(molecule) AllChem.EmbedMolecule(molecule) @@ -37,26 +32,11 @@ def build_mock_structure(smiles: list[str]) -> parmed.Structure: "WAT" if is_water else ( - f"{molecule.GetAtomWithIdx(0).GetSymbol()}" + f"{molecule.GetAtomWithIdx(0).GetSymbol().upper()}" if molecule.GetNumAtoms() == 1 else "UNK" ) ) - symbol_count = collections.defaultdict(int) - - for atom in molecule.GetAtoms(): - atom_name = f"{atom.GetSymbol()}{symbol_count[atom.GetSymbol()] + 1}" - atom_info = Chem.AtomPDBResidueInfo( - atom_name.ljust(4, " "), atom.GetIdx(), "", residue_name, i - ) - atom.SetMonomerInfo(atom_info) - - symbol_count[atom.GetSymbol()] += 1 - - complex = Chem.CombineMols(complex, molecule) - - with tempfile.NamedTemporaryFile(suffix=".pdb") as tmp_file: - Chem.MolToPDBFile(complex, tmp_file.name) - structure = parmed.load_file(tmp_file.name, structure=True) + topologies.append(mdtop.Topology.from_rdkit(molecule, residue_name)) - return structure + return mdtop.Topology.merge(*topologies) diff --git a/femto/md/tests/test_prepare.py b/femto/md/tests/test_prepare.py new file mode 100644 index 0000000..c57efb1 --- /dev/null +++ b/femto/md/tests/test_prepare.py @@ -0,0 +1,271 @@ +import mdtop +import numpy +import openmm +import openmm.app +import openmm.unit +import scipy.spatial.distance + +import femto.md.config +from femto.fe.tests.systems import CDK2_SYSTEM, TEMOA_SYSTEM +from femto.md.constants import LIGAND_1_RESIDUE_NAME, LIGAND_2_RESIDUE_NAME +from femto.md.prepare import ( + _compute_box_size, + _load_force_field, + apply_hmr, + load_ligand, + load_ligands, + load_receptor, + prepare_system, +) +from femto.md.tests.mocking import build_mock_structure +from femto.md.utils import openmm as openmm_utils +from femto.md.utils.openmm import is_close + + +def _compute_energy(system: openmm.System, coords: openmm.unit.Quantity) -> float: + ctx = openmm.Context( + system, + openmm.VerletIntegrator(0.01), + openmm.Platform.getPlatformByName("Reference"), + ) + ctx.setPositions(coords) + + state = ctx.getState(getEnergy=True) + return state.getPotentialEnergy().value_in_unit(openmm.unit.kilojoules_per_mole) + + +def test_hmr(): + topology = build_mock_structure(["CC"]) + + system = openmm.System() + system.addParticle(12.0 * openmm.unit.amu) + system.addParticle(12.0 * openmm.unit.amu) + system.addParticle(1.0 * openmm.unit.amu) + system.addParticle(1.0 * openmm.unit.amu) + system.addParticle(1.0 * openmm.unit.amu) + system.addParticle(1.0 * openmm.unit.amu) + system.addParticle(1.0 * openmm.unit.amu) + system.addParticle(1.0 * openmm.unit.amu) + + original_mass = sum( + [system.getParticleMass(i) for i in range(system.getNumParticles())], + 0.0 * openmm.unit.amu, + ) + + expected_h_mass = 1.5 * openmm.unit.amu + apply_hmr(system, topology, hydrogen_mass=expected_h_mass) + + new_masses = [system.getParticleMass(i) for i in range(system.getNumParticles())] + new_mass = sum(new_masses, 0.0 * openmm.unit.amu) + + assert is_close(new_mass, original_mass) + + expected_masses = [ + (12.0 - 0.5 * 3.0) * openmm.unit.amu, + (12.0 - 0.5 * 3.0) * openmm.unit.amu, + ] + ([expected_h_mass] * 6) + + assert all( + is_close(new_mass, expected_mass) + for new_mass, expected_mass in zip(new_masses, expected_masses, strict=True) + ) + + +def test_hmr_water(): + """HMR should not modify water molecules.""" + topology = build_mock_structure(["O"]) + + expected_masses = [16.0, 1.0, 1.0] * openmm.unit.amu + + system = openmm.System() + + for mass in expected_masses: + system.addParticle(mass) + + apply_hmr(system, topology) + + new_masses = [system.getParticleMass(i) for i in range(system.getNumParticles())] + + assert all( + is_close(new_mass, expected_mass) + for new_mass, expected_mass in zip(new_masses, expected_masses, strict=True) + ) + + +def test_load_ligand(): + reside_name = "ABC" + + ligand = load_ligand(TEMOA_SYSTEM.ligand_1_coords, reside_name) + assert ligand[f"r. {reside_name}"].n_atoms == ligand.n_atoms + + +def test_load_ligands(): + coord_path = CDK2_SYSTEM.ligand_1_coords + + ligand_1, ligand_2 = load_ligands(coord_path, None) + assert ligand_2 is None + + assert ligand_1.residues[0].name == LIGAND_1_RESIDUE_NAME + + ligand_1, ligand_2 = load_ligands(coord_path, coord_path) + assert isinstance(ligand_2, mdtop.Topology) + + assert ligand_1.residues[0].name == LIGAND_1_RESIDUE_NAME + assert ligand_2.residues[0].name == LIGAND_2_RESIDUE_NAME + + +def test_load_receptor(): + receptor = load_receptor(CDK2_SYSTEM.receptor_coords) + assert isinstance(receptor, mdtop.Topology) + assert receptor.residues[0].name == "ACE" + + +def test_compute_box_size(): + box_size = _compute_box_size( + None, + build_mock_structure(["[Ar]"]), + build_mock_structure(["[Ar]"]), + [], + 10.0 * openmm.unit.angstrom, + numpy.array([0.0, 10.0, 0.0]) * openmm.unit.angstrom, + numpy.array([0.0, 0.0, 20.0]) * openmm.unit.angstrom, + ) + + assert openmm_utils.all_close( + box_size, numpy.array([23.76, 33.76, 43.76]) * openmm.unit.angstrom + ) + + +def test_load_force_field(test_data_dir): + import parmed + + param_path = test_data_dir / "1h1q.parm7" + coord_path = test_data_dir / "1h1q.rst7" + + structure = parmed.amber.AmberParm(str(param_path), str(coord_path)) + system_parmed = structure.createSystem(removeCMMotion=False) + + ff = _load_force_field("amber/tip3p_standard.xml", param_path) + assert isinstance(ff, openmm.app.ForceField) + assert "tip3p_standard-Li+" in ff._atomClasses + + system_ff = ff.createSystem(structure.topology, removeCMMotion=False) + + for _ in range(5): + xyz = ( + structure.coordinates + + numpy.random.randn(*structure.coordinates.shape) * 0.5 + ) + + energy_parmed = _compute_energy(system_parmed, xyz * openmm.unit.angstrom) + energy_ff = _compute_energy(system_ff, xyz * openmm.unit.angstrom) + + assert numpy.isclose(energy_parmed, energy_ff, atol=1e-2) + + +def test_prepare_system(mocker): + receptor = build_mock_structure(["O", "CC"]) + receptor.residues[-1].name = "RECEPTOR" + + ligand_1 = build_mock_structure(["CO"]) + ligand_1.residues[-1].name = "L1" + ligand_2 = build_mock_structure(["CCl"]) + ligand_2.residues[-1].name = "L2" + + box_size = 2.0 + + topology_input = ligand_1 + ligand_2 + receptor + topology_input.box = box_size * numpy.eye(3) * openmm.unit.angstrom + + mock_modeller = mocker.patch("openmm.app.Modeller", autospec=True) + mock_modeller.return_value.topology = topology_input.to_openmm() + mock_modeller.return_value.positions = topology_input.xyz + + mock_create_system = mocker.patch( + "openmm.app.ForceField.createSystem", + autospec=True, + return_value=openmm.System(), + ) + + topology, system = prepare_system( + receptor, + ligand_1, + ligand_2, + config=femto.md.config.Prepare(default_ligand_ff="openff-2.0.0.offxml"), + ) + assert isinstance(system, openmm.System) + + assert topology.n_atoms == topology_input.n_atoms + + # waters should be grouped at the end + expected_residues = ["L1", "L2", "WAT", "RECEPTOR"] + assert [r.name for r in topology.residues] == expected_residues + + com = topology.xyz.value_in_unit(openmm.unit.angstrom).mean(axis=0) + assert numpy.allclose(com, numpy.ones(3) * box_size * 0.5, atol=0.1) + + mock_modeller.return_value.addSolvent.assert_called_once() + mock_create_system.assert_called_once() + + +def test_prepare_system_with_cavities(mocker): + # define the mock system with well define 'rmin' values which are used to determine + # radius of our dummy ligands. + r_min = 10.0 + sigma = r_min / (2.0 ** (1.0 / 6.0)) * openmm.unit.angstrom + + mock_system = openmm.System() + mock_system.addParticle(1.0) + mock_system.addParticle(1.0) + mock_system.addParticle(1.0) + mock_system.addParticle(1.0) + mock_force = openmm.NonbondedForce() + mock_force.addParticle(0.0, sigma, 1.0) + mock_force.addParticle(0.0, sigma, 1.0) + mock_force.addParticle(0.0, sigma, 1.0) + mock_force.addParticle(0.0, sigma, 1.0) + mock_system.addForce(mock_force) + + mocker.patch( + "openmm.app.ForceField.createSystem", + return_value=mock_system, + ) + mocker.patch("openmm.app.Modeller.addExtraParticles", autospec=True) + + ligand_1 = build_mock_structure(["ClCl"]) + ligand_1.xyz = ( + numpy.array([[-10.0, 0.0, 0.0], [-10.0, 0.0, 0.0]]) * openmm.unit.angstrom + ) + ligand_2 = build_mock_structure(["ClCl"]) + ligand_2.xyz = ( + numpy.array([[+10.0, 0.0, 0.0], [+10.0, 0.0, 0.0]]) * openmm.unit.angstrom + ) + + topology, _ = prepare_system( + None, ligand_1, None, None, femto.md.config.Prepare(), None, None, [ligand_2] + ) + assert topology["! r. HOH"].n_residues == 1 # no cavity ligand + + com_offset = topology["index 1"].xyz[0, :] - ligand_1["index 1"].xyz[0, :] + + cavity_1_center = (ligand_1[0].xyz[0, :] + com_offset).value_in_unit( + openmm.unit.angstrom + ) + cavity_2_center = (ligand_2[0].xyz[0, :] + com_offset).value_in_unit( + openmm.unit.angstrom + ) + + water_distances_1 = scipy.spatial.distance.cdist( + cavity_1_center.reshape(1, -1), + topology[":HOH"].xyz.value_in_unit(openmm.unit.angstrom), + ) + water_distances_2 = scipy.spatial.distance.cdist( + cavity_2_center.reshape(1, -1), + topology[":HOH"].xyz.value_in_unit(openmm.unit.angstrom), + ) + + min_water_distance_1 = water_distances_1.min() + min_water_distance_2 = water_distances_2.min() + + assert r_min * 0.5 + 1.5 > min_water_distance_1 > r_min * 0.5 + assert r_min * 0.5 + 1.5 > min_water_distance_2 > r_min * 0.5 diff --git a/femto/md/tests/test_restraints.py b/femto/md/tests/test_restraints.py index bd241bc..6f79b76 100644 --- a/femto/md/tests/test_restraints.py +++ b/femto/md/tests/test_restraints.py @@ -52,19 +52,7 @@ def mock_boresch_coords( dihedral_b: float = 0.0, dihedral_c: float = 180.0, ) -> numpy.ndarray: - """Create coords for 3 'receptor' and 3 ligand atoms in a ``\\/ \\/`` shape . - - Args: - dist: distance between r3 and l1 - theta_a: angle between r2, r3, and l1 - theta_b: angle between r3, l1, and l2 - dihedral_a: dihedral between r1, r2, r3, and l1 - dihedral_b: dihedral between r2, r3, l1, and l2 - dihedral_c: dihedral between r3, l1, l2, and l3 - - Returns: - - """ + """Create coords for 3 'receptor' and 3 ligand atoms in a ``\\/ \\/`` shape .""" # initial theta_a and theta_b = 135 degrees theta_initial = 135.0 @@ -155,18 +143,21 @@ def test_create_flat_bottom_restraint(distance, expected_energy): def test_create_position_restraints(): - topology = build_mock_structure(["CO", "O", "C", "O"]) + topology = build_mock_structure(["CO", "O", "C", "[Na+]", "O"]) topology.residues[0].name = femto.md.constants.LIGAND_1_RESIDUE_NAME topology.residues[2].name = femto.md.constants.LIGAND_2_RESIDUE_NAME - topology.coordinates = [[float(i), 0.0, 0.0] for i in range(len(topology.atoms))] + topology.xyz = ( + numpy.array([[float(i), 0.0, 0.0] for i in range(len(topology.atoms))]) + * _ANGSTROM + ) expected_k = 25.0 * openmm.unit.kilocalorie_per_mole / _ANGSTROM**2 expected_radius = 1.5 * _ANGSTROM # ligand 1 and 2 excluding hydrogens - restraint_mask = "!(:WAT,CL,NA,K) & !@/H" + restraint_mask = "not (water or ion or elem H)" restraint = create_position_restraints( topology, diff --git a/femto/md/tests/test_simulate.py b/femto/md/tests/test_simulate.py index 9195385..25ae30a 100644 --- a/femto/md/tests/test_simulate.py +++ b/femto/md/tests/test_simulate.py @@ -1,6 +1,6 @@ +import mdtop import numpy import openmm -import parmed import pytest import femto.fe.fep @@ -12,10 +12,10 @@ @pytest.fixture -def mock_topology() -> parmed.Structure: +def mock_topology() -> mdtop.Topology: topology = build_mock_structure(["[Ar]"]) topology.residues[0].name = femto.md.constants.LIGAND_1_RESIDUE_NAME - topology.box = numpy.array([50.0, 50.0, 50.0, 90.0, 90.0, 90.0]) + topology.box = numpy.eye(3) * 50.0 * openmm.unit.angstrom return topology @@ -24,8 +24,7 @@ def mock_topology() -> parmed.Structure: def mock_system(mock_topology) -> openmm.System: system = openmm.System() system.addParticle(1.0) - - system.setDefaultPeriodicBoxVectors(*mock_topology.box_vectors) + system.setDefaultPeriodicBoxVectors(*mock_topology.box) force = openmm.NonbondedForce() force.setNonbondedMethod(openmm.NonbondedForce.CutoffPeriodic) diff --git a/femto/md/tests/test_solvate.py b/femto/md/tests/test_solvate.py deleted file mode 100644 index 539fb7f..0000000 --- a/femto/md/tests/test_solvate.py +++ /dev/null @@ -1,114 +0,0 @@ -import numpy -import openmm.app -import openmm.unit -import scipy.spatial.distance - -import femto.md.config -from femto.md.solvate import _compute_box_size, solvate_system -from femto.md.tests.mocking import build_mock_structure -from femto.md.utils import openmm as openmm_utils - - -def test_compute_box_size(): - box_size = _compute_box_size( - None, - build_mock_structure(["[Ar]"]), - build_mock_structure(["[Ar]"]), - 10.0 * openmm.unit.angstrom, - numpy.array([0.0, 10.0, 0.0]) * openmm.unit.angstrom, - numpy.array([0.0, 0.0, 20.0]) * openmm.unit.angstrom, - ) - - assert openmm_utils.all_close( - box_size, numpy.array([20.0, 30.0, 40.0]) * openmm.unit.angstrom - ) - - -def test_solvate_system(mocker): - receptor = build_mock_structure(["O", "CC"]) - receptor.residues[-1].name = "RECEPTOR" - - ligand_1 = build_mock_structure(["CO"]) - ligand_1.residues[-1].name = "L1" - ligand_2 = build_mock_structure(["CCl"]) - ligand_2.residues[-1].name = "L2" - - box_size = 2.0 - - topology_input = ligand_1 + ligand_2 + receptor - topology_input.box_vectors = box_size * numpy.eye(3) * openmm.unit.angstrom - - mock_modeller = mocker.patch("openmm.app.Modeller", autospec=True) - mock_modeller.return_value.topology = topology_input.topology - mock_modeller.return_value.positions = topology_input.positions - - mock_parameterize = mocker.patch( - "femto.md.utils.amber.parameterize_structure", - side_effect=lambda structure, *_: structure, - autospec=True, - ) - - topology = solvate_system(receptor, ligand_1, ligand_2, femto.md.config.Solvent()) - assert len(topology.atoms) == len(topology_input.atoms) - - # waters should be grouped at the end - expected_residues = ["L1", "L2", "RECEPTOR", "WAT"] - assert [r.name for r in topology.residues] == expected_residues - - com = numpy.array(topology.coordinates).mean(axis=0) - assert numpy.allclose(com, numpy.ones(3) * box_size * 0.5, atol=0.1) - - mock_parameterize.assert_called_once() - mock_modeller.return_value.addSolvent.assert_called_once() - - -def test_solvate_system_with_cavities(mocker): - # define the mock system with well define 'rmin' values which are used to determine - # radius of our dummy ligands. - r_min = 10.0 - sigma = r_min / (2.0 ** (1.0 / 6.0)) * openmm.unit.angstrom - - mock_system = openmm.System() - mock_system.addParticle(1.0) - mock_system.addParticle(1.0) - mock_system.addParticle(1.0) - mock_system.addParticle(1.0) - mock_force = openmm.NonbondedForce() - mock_force.addParticle(0.0, sigma, 1.0) - mock_force.addParticle(0.0, sigma, 1.0) - mock_force.addParticle(0.0, sigma, 1.0) - mock_force.addParticle(0.0, sigma, 1.0) - mock_system.addForce(mock_force) - - mocker.patch( - "femto.md.solvate._MockForceField.createSystem", - return_value=mock_system, - ) - - ligand_1 = build_mock_structure(["ClCl"]) - ligand_1.coordinates = numpy.array([[-10.0, 0.0, 0.0], [-10.0, 0.0, 0.0]]) - ligand_2 = build_mock_structure(["ClCl"]) - ligand_2.coordinates = numpy.array([[+10.0, 0.0, 0.0], [+10.0, 0.0, 0.0]]) - - topology = solvate_system( - None, ligand_1, None, femto.md.config.Solvent(), None, None, [ligand_2] - ) - assert len(topology["!:WAT"].residues) == 1 # no cavity ligand - - com_offset = topology["@1"].coordinates[0, :] - ligand_1["@1"].coordinates[0, :] - - cavity_1_center = ligand_1["@1"].coordinates[0, :] + com_offset - cavity_2_center = ligand_2["@1"].coordinates[0, :] + com_offset - - water_distances_1 = scipy.spatial.distance.cdist( - cavity_1_center.reshape(1, -1), topology[":WAT"].coordinates - ) - water_distances_2 = scipy.spatial.distance.cdist( - cavity_2_center.reshape(1, -1), topology[":WAT"].coordinates - ) - - min_water_distance_1 = water_distances_1.min() - min_water_distance_2 = water_distances_2.min() - - assert r_min * 0.5 + 1.5 > min_water_distance_1 > r_min * 0.5 - assert r_min * 0.5 + 1.5 > min_water_distance_2 > r_min * 0.5 diff --git a/femto/md/tests/test_system.py b/femto/md/tests/test_system.py deleted file mode 100644 index e2c3ddc..0000000 --- a/femto/md/tests/test_system.py +++ /dev/null @@ -1,115 +0,0 @@ -import openmm -import openmm.unit -import parmed - -from femto.fe.tests.systems import CDK2_SYSTEM, TEMOA_SYSTEM -from femto.md.constants import LIGAND_1_RESIDUE_NAME, LIGAND_2_RESIDUE_NAME -from femto.md.system import apply_hmr, load_ligand, load_ligands, load_receptor -from femto.md.tests.mocking import build_mock_structure -from femto.md.utils.openmm import is_close - - -def test_hmr(): - topology = build_mock_structure(["CC"]) - - system = openmm.System() - system.addParticle(12.0 * openmm.unit.amu) - system.addParticle(12.0 * openmm.unit.amu) - system.addParticle(1.0 * openmm.unit.amu) - system.addParticle(1.0 * openmm.unit.amu) - system.addParticle(1.0 * openmm.unit.amu) - system.addParticle(1.0 * openmm.unit.amu) - system.addParticle(1.0 * openmm.unit.amu) - system.addParticle(1.0 * openmm.unit.amu) - - original_mass = sum( - [system.getParticleMass(i) for i in range(system.getNumParticles())], - 0.0 * openmm.unit.amu, - ) - - expected_h_mass = 1.5 * openmm.unit.amu - apply_hmr(system, topology, hydrogen_mass=expected_h_mass) - - new_masses = [system.getParticleMass(i) for i in range(system.getNumParticles())] - new_mass = sum(new_masses, 0.0 * openmm.unit.amu) - - assert is_close(new_mass, original_mass) - - expected_masses = [ - (12.0 - 0.5 * 3.0) * openmm.unit.amu, - (12.0 - 0.5 * 3.0) * openmm.unit.amu, - ] + ([expected_h_mass] * 6) - - assert all( - is_close(new_mass, expected_mass) - for new_mass, expected_mass in zip(new_masses, expected_masses, strict=True) - ) - - -def test_hmr_water(): - """HMR should not modify water molecules.""" - topology = build_mock_structure(["O"]) - - expected_masses = [16.0, 1.0, 1.0] * openmm.unit.amu - - system = openmm.System() - - for mass in expected_masses: - system.addParticle(mass) - - apply_hmr(system, topology) - - new_masses = [system.getParticleMass(i) for i in range(system.getNumParticles())] - - assert all( - is_close(new_mass, expected_mass) - for new_mass, expected_mass in zip(new_masses, expected_masses, strict=True) - ) - - -def test_load_ligand(): - reside_name = "ABC" - - ligand = load_ligand( - CDK2_SYSTEM.ligand_1_coords, CDK2_SYSTEM.ligand_1_params, reside_name - ) - - assert len(ligand[":ABC"].atoms) == len(ligand.atoms) - - -def test_load_ligands(): - coord_path = CDK2_SYSTEM.ligand_1_coords - param_path = CDK2_SYSTEM.ligand_1_params - - ligand_1, ligand_2 = load_ligands(coord_path, param_path, None, None) - assert ligand_2 is None - - assert ligand_1.residues[0].name == LIGAND_1_RESIDUE_NAME - - ligand_1, ligand_2 = load_ligands(coord_path, param_path, coord_path, param_path) - assert isinstance(ligand_2, parmed.Structure) - - assert ligand_1.residues[0].name == LIGAND_1_RESIDUE_NAME - assert ligand_2.residues[0].name == LIGAND_2_RESIDUE_NAME - - -def test_load_receptor_with_params(mocker): - mock_parameterize = mocker.patch( - "femto.md.utils.amber.parameterize_structure", autospec=True - ) - - receptor = load_receptor(TEMOA_SYSTEM.receptor_coords, TEMOA_SYSTEM.receptor_params) - assert isinstance(receptor, parmed.Structure) - assert receptor.residues[0].name == "<0>" - - assert mock_parameterize.call_count == 0 - - -def test_load_receptor_without_params(mocker): - mock_parameterize = mocker.patch( - "femto.md.utils.amber.parameterize_structure", autospec=True - ) - mock_sources = ["source1"] - - load_receptor(CDK2_SYSTEM.receptor_coords, None, mock_sources) - mock_parameterize.assert_called_once_with(mocker.ANY, mock_sources) diff --git a/femto/md/tests/utils/test_amber.py b/femto/md/tests/utils/test_amber.py deleted file mode 100644 index d4a4053..0000000 --- a/femto/md/tests/utils/test_amber.py +++ /dev/null @@ -1,45 +0,0 @@ -import numpy -import openmm.app -import parmed.openmm -import pytest - -from femto.md.tests.mocking import build_mock_structure -from femto.md.utils.amber import extract_water_and_ions_mask, parameterize_structure - - -def test_extract_water_and_ions_mask(tmp_cwd): - structure = build_mock_structure(["O", "O", "CC", "[K+]", "[Cl-]", "O", "O", "O"]) - - solvent_mask = extract_water_and_ions_mask(structure) - assert len(solvent_mask) == len(structure.atoms) - - n_ethanol_atoms = 8 - expected_mask = ( - [True] * 3 - + [True] * 3 - + [False] * n_ethanol_atoms - + [True] * 1 - + [True] * 1 - + [True] * 3 - + [True] * 3 - + [True] * 3 - ) - - assert solvent_mask == expected_mask - - -def test_parameterize_empty_structure(tmp_cwd): - structure = parameterize_structure(parmed.Structure(), []) - assert isinstance(structure, parmed.Structure) - - -def test_parameterize_structure_fail(tmp_cwd): - topology = openmm.app.Topology() - - residue = topology.addResidue("X", topology.addChain()) - topology.addAtom("X", openmm.app.Element.getBySymbol("Na"), residue) - - with pytest.raises(RuntimeError, match="TLeap failed - "): - parameterize_structure( - parmed.openmm.load_topology(topology, xyz=numpy.zeros((1, 3))), [] - ) diff --git a/femto/md/tests/utils/test_openmm.py b/femto/md/tests/utils/test_openmm.py index 0d7b6b7..a1b001d 100644 --- a/femto/md/tests/utils/test_openmm.py +++ b/femto/md/tests/utils/test_openmm.py @@ -210,11 +210,11 @@ def test_get_simulation_summary(mocker): def test_create_simulation(): topology = build_mock_structure(["[Ar]"]) topology.residues[0].name = femto.md.constants.LIGAND_1_RESIDUE_NAME - topology.box = numpy.array([50.0, 50.0, 50.0, 90.0, 90.0, 90.0]) + topology.box = (numpy.eye(3) * 50.0) * openmm.unit.angstrom system = openmm.System() system.addParticle(1.0) - system.setDefaultPeriodicBoxVectors(*topology.box_vectors) + system.setDefaultPeriodicBoxVectors(*topology.box) force = openmm.NonbondedForce() force.setNonbondedMethod(openmm.NonbondedForce.CutoffPeriodic) force.addGlobalParameter(femto.fe.fep.LAMBDA_VDW_LIGAND_1, 1.0) @@ -239,14 +239,9 @@ def test_create_simulation(): positions = simulation.context.getState(getPositions=True).getPositions( asNumpy=True ) - assert femto.md.utils.openmm.all_close( - positions, topology.coordinates * openmm.unit.angstrom - ) + assert femto.md.utils.openmm.all_close(positions, topology.xyz) - expected_box_vectors = ( - numpy.array(topology.box_vectors.value_in_unit(openmm.unit.angstrom)) - * openmm.unit.angstrom - ) + expected_box_vectors = topology.box box_vectors = simulation.context.getState().getPeriodicBoxVectors(asNumpy=True) assert femto.md.utils.openmm.all_close(box_vectors, expected_box_vectors) diff --git a/femto/md/utils/amber.py b/femto/md/utils/amber.py index 3b6439d..b3d6b96 100644 --- a/femto/md/utils/amber.py +++ b/femto/md/utils/amber.py @@ -1,79 +1,172 @@ -"""Utilities for manipulating AMBER data and tools""" +"""Utilities for working with Amber files and objects.""" -import copy +import dataclasses import pathlib -import subprocess -import tempfile +import typing +import uuid +import warnings -import parmed +import openmm.app +import openmmforcefields.generators.template_generators -def extract_water_and_ions_mask(structure: parmed.Structure) -> list[bool]: - """Returns a per atom mask that is true if the atom belongs to a water molecule - or an ion, and false otherwise. +@dataclasses.dataclass +class _MockAtom: + """A shim so we can pass an atom object to ``openmmforcefields``""" - Args: - structure: The structure to extract the mask from. + index: int + name: str + symbol: str + typename: str + mass: float + neighbours: list[int] - Returns: - The selection mask. - """ + def is_bonded_to(self, other): + return other.index in self.neighbours + + +@dataclasses.dataclass +class _MockBond: + """A shim so we can pass a bond object to ``openmmforcefields``""" + + atom1: _MockAtom + atom2: _MockAtom + + def is_bonded_to(self, other): + return False + + +@dataclasses.dataclass +class _MockMolecule: + """A shim so we can pass a molecule object to ``openmmforcefields``""" - solvent_residues = { - i - for i, residue in enumerate(structure.residues) - if sorted(a.element for a in residue.atoms) == [1, 1, 8] - # a bit of a hack to check for ions. - or len(residue.atoms) == 1 - } - solvent_mask = [ - i in solvent_residues - for i, residue in enumerate(structure.residues) - for _ in residue.atoms + atoms: list[_MockAtom] + bonds: list[_MockBond] + + def to_smiles(self): + uuid4 = uuid.uuid4() + return str(uuid4).replace("-", "")[:8] + + +def _is_central_atom(idx: int, others: tuple[int, ...], mol: _MockMolecule) -> bool: + return all(mol.atoms[idx].is_bonded_to(mol.atoms[other]) for other in others) + + +def _reorder_torsions( + system: openmm.System, mol: _MockMolecule +) -> list[tuple[typing.Any, ...]]: + forces = [ + (i, force) + for i, force in enumerate(system.getForces()) + if isinstance(force, openmm.PeriodicTorsionForce) ] + assert len(forces) <= 1, "expected at most one PeriodicTorsionForce" + + if len(forces) == 0: + return [] - return solvent_mask + force_idx, force = forces[0] + force_new = openmm.PeriodicTorsionForce() + force_new.setForceGroup(force.getForceGroup()) + force_new.setName(force.getName()) + force_new.setUsesPeriodicBoundaryConditions(force.usesPeriodicBoundaryConditions()) -def parameterize_structure( - structure: parmed.Structure, tleap_sources: list[str] -) -> parmed.amber.AmberParm: - """Parameterizes a given structure using tLeap. + for i in range(force.getNumTorsions()): + idx_0, idx_1, idx_2, idx_3, *params = force.getTorsionParameters(i) + + is_proper = ( + mol.atoms[idx_0].is_bonded_to(mol.atoms[idx_1]) + and mol.atoms[idx_1].is_bonded_to(mol.atoms[idx_2]) + and mol.atoms[idx_2].is_bonded_to(mol.atoms[idx_3]) + ) + + if is_proper: + force_new.addTorsion(idx_0, idx_1, idx_2, idx_3, *params) + continue + + is_0_central = _is_central_atom(idx_0, (idx_1, idx_2, idx_3), mol) + is_1_central = _is_central_atom(idx_1, (idx_0, idx_2, idx_3), mol) + is_2_central = _is_central_atom(idx_2, (idx_0, idx_1, idx_3), mol) + is_3_central = _is_central_atom(idx_3, (idx_0, idx_1, idx_2), mol) + + n_central = sum([is_0_central, is_1_central, is_2_central, is_3_central]) + + if n_central != 1: + raise ValueError(f"expected exactly one central atom, got {n_central}") + + if is_0_central: + central_idx, other_idxs = idx_0, (idx_1, idx_2, idx_3) + elif is_1_central: + central_idx, other_idxs = idx_1, (idx_0, idx_2, idx_3) + elif is_2_central: + central_idx, other_idxs = idx_2, (idx_0, idx_1, idx_3) + else: + central_idx, other_idxs = idx_3, (idx_0, idx_1, idx_2) + + # openmmforcefields expects to find (central, other1, other2, other3, *params) + force_new.addTorsion(central_idx, *other_idxs, *params) + + system.removeForce(force_idx) + system.addForce(force_new) + + +def convert_parm_to_xml(path: pathlib.Path) -> str: + """Convert an Amber parameter file to OpenMM XML format. + + Notes: + * This function requires that ParmEd is installed. + * Doing any kind of interconversion of force fields is inherently dangerous. + This should only be used as a last resort when you have no other option. Args: - structure: The structure to parameterize. - tleap_sources: The tLeap parameters to source. + path: The path to the Amber parameter file (.parm7, .prmtop, .parm). Returns: - The parameterized structure + The OpenMM XML representation of the Amber parameter file. """ - - if len(structure.atoms) == 0: - return copy.deepcopy(structure) - - control_file = "\n".join( - [ - *[f"source {source}" for source in tleap_sources], - 'addPdbAtomMap { { "Na" "NA" } { "Na+" "NA" } { "Cl" "CL" } { "Cl-" "CL" } }', # noqa: E501 - "structure = loadpdb structure.pdb", - "saveAmberParm structure structure.parm7 structure.rst7", - "quit", - ] + import parmed + + if path.suffix.lower() not in {".prmtop", ".parm7", ".parm"}: + raise ValueError(f"Unsupported Amber file format: {path}") + + warnings.warn( + f"Converting {path.name} from {path.suffix} to OpenMM FFXML. It is assumed " + f"that the parameter was generated by an Amber based FF, e.g. GAFF, and treats " + f"any improper torsions as such. Such conversion is inherently dangerous, and " + f"support to do so will be removed in the future.", + DeprecationWarning, + stacklevel=2, ) - with tempfile.TemporaryDirectory() as tmp_dir: - tmp_dir = pathlib.Path(tmp_dir) - - structure.save(str(tmp_dir / "structure.pdb")) - (tmp_dir / "tleap.in").write_text(control_file) + structure = parmed.amber.AmberParm(str(path)) - result = subprocess.run( - ["tleap", "-f", "tleap.in"], cwd=tmp_dir, capture_output=True, text=True + mock_atoms = [ + _MockAtom( + index=atom.idx, + name=atom.name, + symbol=openmm.app.Element.getByAtomicNumber(atom.element).symbol, + typename=atom.type, + mass=atom.mass, + neighbours=[neigh.idx for neigh in atom.bond_partners], ) + for atom in structure.atoms + ] + mock_bonds = [ + _MockBond( + atom1=mock_atoms[bond.atom1.idx], + atom2=mock_atoms[bond.atom2.idx], + ) + for bond in structure.bonds + ] + mock_mol = _MockMolecule(atoms=mock_atoms, bonds=mock_bonds) + + system = structure.createSystem(removeCMMotion=False) + _reorder_torsions(system, mock_mol) - param_path, coord_path = tmp_dir / "structure.parm7", tmp_dir / "structure.rst7" + mixin = openmmforcefields.generators.template_generators.OpenMMSystemMixin() - if result.returncode != 0 or not param_path.exists() or not coord_path.exists(): - raise RuntimeError(f"TLeap failed - {result.stdout}\n{result.stderr}") + ffxml = mixin.convert_system_to_ffxml(mock_mol, system, "amber") + ffxml = ffxml.replace('ordering="smirnoff"', 'ordering="amber"') - return parmed.amber.AmberParm(str(param_path), str(coord_path)) + return ffxml diff --git a/femto/md/utils/openmm.py b/femto/md/utils/openmm.py index 1bd150c..fe219b2 100644 --- a/femto/md/utils/openmm.py +++ b/femto/md/utils/openmm.py @@ -1,10 +1,10 @@ """Utilities for manipulating OpenMM objects.""" +import mdtop import numpy import openmm import openmm.app import openmm.unit -import parmed import femto.md.config from femto.md.constants import OpenMMForceGroup, OpenMMForceName, OpenMMPlatform @@ -247,7 +247,7 @@ def create_integrator( def create_simulation( system: openmm.System, - topology: parmed.Structure, + topology: mdtop.Topology, coords: openmm.State | None, integrator: openmm.Integrator, state: dict[str, float] | None, @@ -276,15 +276,15 @@ def create_simulation( if coords is not None: system.setDefaultPeriodicBoxVectors(*coords.getPeriodicBoxVectors()) else: - system.setDefaultPeriodicBoxVectors(*topology.box_vectors) + system.setDefaultPeriodicBoxVectors(*topology.box) simulation = openmm.app.Simulation( - topology.topology, system, integrator, platform, platform_properties + topology.to_openmm(), system, integrator, platform, platform_properties ) if coords is None: - simulation.context.setPeriodicBoxVectors(*topology.box_vectors) - simulation.context.setPositions(topology.positions) + simulation.context.setPeriodicBoxVectors(*topology.box) + simulation.context.setPositions(topology.xyz) else: simulation.context.setState(coords) diff --git a/mkdocs.yml b/mkdocs.yml index f46cc60..c81d2ef 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -28,7 +28,9 @@ nav: - ATM: guide-atm.md - SepTop: guide-septop.md - API reference: reference/ -- Development: development.md +- Development: + - Overview: development.md + - Migration: migration.md theme: name: material @@ -102,6 +104,7 @@ plugins: - https://parmed.github.io/ParmEd/html/objects.inv - http://docs.openmm.org/latest/api-python/objects.inv - https://www.mdtraj.org/1.9.6/objects.inv + - https://simonboothroyd.github.io/mdtop/latest/objects.inv options: extensions: [ griffe_pydantic ] docstring_options: