From 7a379c4532b01d91df305304c26d65a812809b7d Mon Sep 17 00:00:00 2001 From: Richard Morris Date: Sat, 30 Nov 2024 02:49:18 +1100 Subject: [PATCH 1/8] Add pytests for polyconf --- tests/data/CODI_bonds.pdb | 59 +++++++++++++++++++ tests/polyconf/__init__.py | 0 tests/polyconf/test_PDB.py | 46 +++++++++++++++ tests/polyconf/test_data.py | 7 +++ tests/polyconf/test_import.py | 5 ++ tests/polyconf/test_monomer.py | 29 +++++++++ tests/polyconf/test_polyconf_automatic.py | 12 ++++ tests/polyconf/test_polymer.py | 34 +++++++++++ tests/polytop/__init__.py | 0 tests/{ => polytop}/test_angles.py | 0 tests/{ => polytop}/test_atoms.py | 0 tests/{ => polytop}/test_bonds.py | 0 tests/{ => polytop}/test_data.py | 0 tests/{ => polytop}/test_dihedrals.py | 0 tests/{ => polytop}/test_gromacs.py | 0 tests/{ => polytop}/test_junctions.py | 0 tests/{ => polytop}/test_moleculetype.py | 0 tests/{ => polytop}/test_monomer.py | 0 tests/{ => polytop}/test_polymer.py | 0 .../{ => polytop}/test_polymerization_type.py | 0 tests/{ => polytop}/test_topology.py | 0 tests/{ => polytop}/test_visualize.py | 0 22 files changed, 192 insertions(+) create mode 100644 tests/data/CODI_bonds.pdb create mode 100644 tests/polyconf/__init__.py create mode 100644 tests/polyconf/test_PDB.py create mode 100644 tests/polyconf/test_data.py create mode 100644 tests/polyconf/test_import.py create mode 100644 tests/polyconf/test_monomer.py create mode 100644 tests/polyconf/test_polyconf_automatic.py create mode 100644 tests/polyconf/test_polymer.py create mode 100644 tests/polytop/__init__.py rename tests/{ => polytop}/test_angles.py (100%) rename tests/{ => polytop}/test_atoms.py (100%) rename tests/{ => polytop}/test_bonds.py (100%) rename tests/{ => polytop}/test_data.py (100%) rename tests/{ => polytop}/test_dihedrals.py (100%) rename tests/{ => polytop}/test_gromacs.py (100%) rename tests/{ => polytop}/test_junctions.py (100%) rename tests/{ => polytop}/test_moleculetype.py (100%) rename tests/{ => polytop}/test_monomer.py (100%) rename tests/{ => polytop}/test_polymer.py (100%) rename tests/{ => polytop}/test_polymerization_type.py (100%) rename tests/{ => polytop}/test_topology.py (100%) rename tests/{ => polytop}/test_visualize.py (100%) diff --git a/tests/data/CODI_bonds.pdb b/tests/data/CODI_bonds.pdb new file mode 100644 index 0000000..410570b --- /dev/null +++ b/tests/data/CODI_bonds.pdb @@ -0,0 +1,59 @@ +HEADER UNCLASSIFIED 22-Feb-24 +TITLE UNITED ATOM STRUCTURE FOR MOLECULE UNL +AUTHOR AUTOMATED TOPOLOGY BUILDER (ATB) REVISION 2023-06-14 20:38:16 +AUTHOR 2 https://atb.uq.edu.au +HETATM 1 O1B CODI 0 -4.879 3.567 -0.008 1.00 0.00 O +HETATM 2 C1C CODI 0 -3.936 2.789 0.287 1.00 0.00 C +HETATM 3 O1A CODI 0 -3.278 2.748 1.364 1.00 0.00 O +HETATM 4 C1B CODI 0 -3.516 1.771 -0.816 1.00 0.00 C +HETATM 5 C1A CODI 0 -2.746 0.598 -0.238 1.00 0.00 C +HETATM 6 OG1 CODI 0 -2.378 -0.288 -1.342 1.00 0.00 O +HETATM 7 CB CODI 0 -1.416 -1.221 -1.313 1.00 0.00 C +HETATM 8 OG2 CODI 0 -1.267 -1.923 -2.298 1.00 0.00 O +HETATM 9 CA CODI 0 -0.494 -1.414 -0.098 1.00 0.00 C +HETATM 10 HB CODI 0 0.197 -2.204 -0.405 1.00 0.00 H +HETATM 11 C CODI 0 -1.125 -1.829 1.248 1.00 0.00 C +HETATM 12 CP CODI 0 -1.858 -3.187 1.213 1.00 0.00 C +HETATM 13 CQ CODI 0 -3.239 -3.189 0.542 1.00 0.00 C +HETATM 14 S1 CODI 0 0.580 0.090 0.045 1.00 0.00 S +HETATM 15 CIA CODI 0 2.224 -0.537 0.246 1.00 0.00 C +HETATM 16 S2 CODI 0 2.625 -2.120 0.529 1.00 0.00 S +HETATM 17 CIB CODI 0 3.226 0.554 0.162 1.00 0.00 C +HETATM 18 CIC2 CODI 0 3.092 1.591 -0.782 1.00 0.00 C +HETATM 19 HIC2 CODI 0 2.256 1.588 -1.472 1.00 0.00 H +HETATM 20 CID2 CODI 0 4.054 2.596 -0.870 1.00 0.00 C +HETATM 21 HID2 CODI 0 3.946 3.376 -1.618 1.00 0.00 H +HETATM 22 CIE1 CODI 0 5.152 2.593 -0.008 1.00 0.00 C +HETATM 23 HIE1 CODI 0 5.898 3.379 -0.074 1.00 0.00 H +HETATM 24 CID1 CODI 0 5.291 1.573 0.940 1.00 0.00 C +HETATM 25 HID1 CODI 0 6.140 1.572 1.617 1.00 0.00 H +HETATM 26 CIC1 CODI 0 4.344 0.556 1.020 1.00 0.00 C +HETATM 27 HIC1 CODI 0 4.448 -0.233 1.757 1.00 0.00 H +CONECT 1 2 +CONECT 2 1 3 4 +CONECT 3 2 +CONECT 4 2 5 +CONECT 5 4 6 +CONECT 6 5 7 +CONECT 7 6 8 9 +CONECT 8 7 +CONECT 9 7 10 11 14 +CONECT 10 9 +CONECT 11 9 12 +CONECT 12 11 13 +CONECT 13 12 +CONECT 14 9 15 +CONECT 15 14 16 17 +CONECT 16 15 +CONECT 17 15 18 26 +CONECT 18 17 19 20 +CONECT 19 18 +CONECT 20 18 21 22 +CONECT 21 20 +CONECT 22 20 23 24 +CONECT 23 22 +CONECT 24 22 25 26 +CONECT 25 24 +CONECT 26 17 24 27 +CONECT 27 26 +END diff --git a/tests/polyconf/__init__.py b/tests/polyconf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/polyconf/test_PDB.py b/tests/polyconf/test_PDB.py new file mode 100644 index 0000000..205d2c5 --- /dev/null +++ b/tests/polyconf/test_PDB.py @@ -0,0 +1,46 @@ +import random +import pytest +import MDAnalysis as mda +from polyconf.PDB import PDB +from .test_data import test_pdbs + +@pytest.mark.parametrize("pdb_file", test_pdbs) +def test_pdb_initialization(data_dir, pdb_file): + polymer = mda.Universe(f"{data_dir}/{pdb_file}") + pdb = PDB(polymer) + assert pdb.polymer == polymer + assert len(pdb.atoms) == len(polymer.atoms) + +@pytest.mark.parametrize("pdb_file", test_pdbs) +def test_cleanup(data_dir, pdb_file): + polymer = mda.Universe(f"{data_dir}/{pdb_file}") + pdb = PDB(polymer) + pdb.cleanup() + box = (pdb.atoms.positions).max(axis=0) - (pdb.atoms.positions).min(axis=0) + [10, 10, 10] + assert pdb.dimensions[:3] == pytest.approx(box, rel=1e-1), "Box is not at least 10 Angstroms larger than the polymer" + assert pdb.dimensions[3:] == [90, 90, 90], "Box angles are not 90 degrees" + assert (pdb.atoms.positions >= -box/2).all(), "Some atoms are located outside the box" + +@pytest.mark.parametrize("pdb_file", test_pdbs) +def test_write(data_dir, pdb_file, tmp_path): + polymer = mda.Universe(f"{data_dir}/{pdb_file}") + pdb = PDB(polymer) + output_file = tmp_path / "output.pdb" + for atom in pdb.polymer.atoms: # Test for every atom in the polymer + pdb._write(f"name {atom.name}", str(output_file)) + assert output_file.exists() + with open(output_file, 'r') as f: + content = f.read() + assert f" {atom.name} " in content, f"Atom {atom.name} not found in output file" + +@pytest.mark.parametrize("pdb_file", test_pdbs) +def test_save(data_dir, pdb_file, tmp_path): + polymer = mda.Universe(f"{data_dir}/{pdb_file}") + pdb = PDB(polymer) + output_file = tmp_path / "output.gro" + random_atom = random.choice(pdb.polymer.atoms) # just test one atom at random + pdb.save(dummyAtoms=random_atom.name, fname=str(output_file), selectionString="all", pdb=False) + assert output_file.exists() + with open(output_file, 'r') as f: + content = f.read() + assert f" {random_atom.name} " in content, f"Atom {random_atom.name} not found in output file" diff --git a/tests/polyconf/test_data.py b/tests/polyconf/test_data.py new file mode 100644 index 0000000..420c688 --- /dev/null +++ b/tests/polyconf/test_data.py @@ -0,0 +1,7 @@ +import pytest + +test_pdbs = ['CODI_bonds.pdb'] + +@pytest.mark.parametrize("pdb_filename", test_pdbs) +def test_data_files_exist(data_dir, pdb_filename): + assert (data_dir / pdb_filename).exists(), f"{pdb_filename} does not exist in {data_dir}" diff --git a/tests/polyconf/test_import.py b/tests/polyconf/test_import.py new file mode 100644 index 0000000..c1b80d5 --- /dev/null +++ b/tests/polyconf/test_import.py @@ -0,0 +1,5 @@ +def test_import_polyconf(): + try: + import polyconf + except ImportError: + assert False, "Failed to import polyconf" diff --git a/tests/polyconf/test_monomer.py b/tests/polyconf/test_monomer.py new file mode 100644 index 0000000..314586e --- /dev/null +++ b/tests/polyconf/test_monomer.py @@ -0,0 +1,29 @@ +import random +import pytest +import MDAnalysis as mda +from polyconf.monomer import Monomer +from .test_data import test_pdbs # This is a list of test pdb files that we tested for existence in the test_data.py file + + +@pytest.mark.parametrize("pdb_filename", test_pdbs) # this parameterization will run this test for every element in test_pbs +def test_monomer_initialization(data_dir, pdb_filename: str): + """ + Test that we can initialize a Monomer object from a pdb file + """ + monomer = Monomer(data_dir / pdb_filename) + assert isinstance(monomer.monomer, mda.Universe), "Monomer.monomer should be an MDAnalysis Universe object" + # count the lines in the pdb file that begin with HETATM + with open(data_dir / pdb_filename) as f: + hetatm_lines = [line for line in f if line.startswith("HETATM")] + assert len(monomer.atoms) == len(hetatm_lines), "The number of atoms in the Monomer should match the number of HETATM lines in the pdb file" + +@pytest.mark.parametrize("pdb_filename", test_pdbs) +def test_select_atoms(data_dir, pdb_filename: str): + """ + Test that we can find an atom by name in the monomer using the select_atoms method + """ + monomer = Monomer(data_dir / pdb_filename) + random_atom = random.choice(monomer.atoms) + selected_atoms = monomer.select_atoms(f"name {random_atom.name}") + assert len(selected_atoms) == 1, f"There should only be one {random_atom.name} atom in {pdb_filename}" + assert selected_atoms[0].name == random_atom.name, f"The selected atom should be named {random_atom.name}" \ No newline at end of file diff --git a/tests/polyconf/test_polyconf_automatic.py b/tests/polyconf/test_polyconf_automatic.py new file mode 100644 index 0000000..13c00de --- /dev/null +++ b/tests/polyconf/test_polyconf_automatic.py @@ -0,0 +1,12 @@ +import pytest +import subprocess +from pathlib import Path + + +def test_polyconf_automatic_help(): + ''' + Test that the cli script for polyconf_automatic.py runs and prints help message + ''' + result = subprocess.run(['polyconf-automatic', '--help'], capture_output=True, text=True) + assert result.returncode == 0 + assert "usage:" in result.stdout diff --git a/tests/polyconf/test_polymer.py b/tests/polyconf/test_polymer.py new file mode 100644 index 0000000..4c410c7 --- /dev/null +++ b/tests/polyconf/test_polymer.py @@ -0,0 +1,34 @@ +import pytest +import MDAnalysis as mda +from polyconf.polymer import Polymer +from .test_data import test_pdbs + +@pytest.mark.parametrize("pdb_file", test_pdbs) +def test_polymer_initialization(data_dir, pdb_file): + ''' + Test that we can initialize a Polymer object from a pdb file + And that it defines a bunch of attributes and functions that we expect + ''' + + first_monomer = mda.Universe(f"{data_dir}/{pdb_file}") + polymer = Polymer(first_monomer) + assert polymer.first == first_monomer + assert polymer.first.residues.resids[0] == 1 + + # let's make sure some attributes we expect are set + assert hasattr(polymer, 'first') + assert hasattr(polymer, 'polymer') + assert hasattr(polymer, 'atoms') + + # let's make sure some functions we expect are defined + methods = ['select_atoms', 'renamer', 'extend', 'genconf','gencomp','shuffle','newresid'] + for method in methods: + assert hasattr(Polymer, method), f"Polymer class should have a method named {method}" + +@pytest.mark.parametrize("pdb_file", test_pdbs) +def test_polymer_attributes(data_dir, pdb_file): + first_monomer = mda.Universe(f"{data_dir}/{pdb_file}") + polymer = Polymer(first_monomer) + assert polymer.first.residues.resids[0] == 1 + +# Add more tests for other methods and functionalities of the Polymer class as needed \ No newline at end of file diff --git a/tests/polytop/__init__.py b/tests/polytop/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_angles.py b/tests/polytop/test_angles.py similarity index 100% rename from tests/test_angles.py rename to tests/polytop/test_angles.py diff --git a/tests/test_atoms.py b/tests/polytop/test_atoms.py similarity index 100% rename from tests/test_atoms.py rename to tests/polytop/test_atoms.py diff --git a/tests/test_bonds.py b/tests/polytop/test_bonds.py similarity index 100% rename from tests/test_bonds.py rename to tests/polytop/test_bonds.py diff --git a/tests/test_data.py b/tests/polytop/test_data.py similarity index 100% rename from tests/test_data.py rename to tests/polytop/test_data.py diff --git a/tests/test_dihedrals.py b/tests/polytop/test_dihedrals.py similarity index 100% rename from tests/test_dihedrals.py rename to tests/polytop/test_dihedrals.py diff --git a/tests/test_gromacs.py b/tests/polytop/test_gromacs.py similarity index 100% rename from tests/test_gromacs.py rename to tests/polytop/test_gromacs.py diff --git a/tests/test_junctions.py b/tests/polytop/test_junctions.py similarity index 100% rename from tests/test_junctions.py rename to tests/polytop/test_junctions.py diff --git a/tests/test_moleculetype.py b/tests/polytop/test_moleculetype.py similarity index 100% rename from tests/test_moleculetype.py rename to tests/polytop/test_moleculetype.py diff --git a/tests/test_monomer.py b/tests/polytop/test_monomer.py similarity index 100% rename from tests/test_monomer.py rename to tests/polytop/test_monomer.py diff --git a/tests/test_polymer.py b/tests/polytop/test_polymer.py similarity index 100% rename from tests/test_polymer.py rename to tests/polytop/test_polymer.py diff --git a/tests/test_polymerization_type.py b/tests/polytop/test_polymerization_type.py similarity index 100% rename from tests/test_polymerization_type.py rename to tests/polytop/test_polymerization_type.py diff --git a/tests/test_topology.py b/tests/polytop/test_topology.py similarity index 100% rename from tests/test_topology.py rename to tests/polytop/test_topology.py diff --git a/tests/test_visualize.py b/tests/polytop/test_visualize.py similarity index 100% rename from tests/test_visualize.py rename to tests/polytop/test_visualize.py From fbc9348136fa3daa93db13092e527fd86bca437b Mon Sep 17 00:00:00 2001 From: Richard Morris Date: Sat, 30 Nov 2024 13:32:35 +1100 Subject: [PATCH 2/8] Add guard clauses to polymer init --- polyconf/polyconf/polymer.py | 8 +++++++- tests/polyconf/test_polymer.py | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/polyconf/polyconf/polymer.py b/polyconf/polyconf/polymer.py index feb6e78..0480fd0 100644 --- a/polyconf/polyconf/polymer.py +++ b/polyconf/polyconf/polymer.py @@ -19,7 +19,7 @@ class Polymer: """ Docstrings go brr """ - def __init__(self, firstMonomer) -> None: + def __init__(self, firstMonomer: Monomer) -> None: """ Initiate the polymer by building its first monomer. Takes a copy of its atoms and sets the residue resids to 1. @@ -27,6 +27,12 @@ def __init__(self, firstMonomer) -> None: Args: firstMonomer (MDAnalysis Universe): first monomer of the polymer """ + # Guard clauses to ensure that firstMonomer.residues.resids is a sane call + if firstMonomer is None: + raise ValueError('firstMonomer must be a Monomer object') + if not isinstance(firstMonomer, Monomer): + raise TypeError('firstMonomer must be a Monomer object') + firstMonomer.residues.resids = 1 self.first = firstMonomer self.polymer = self.first diff --git a/tests/polyconf/test_polymer.py b/tests/polyconf/test_polymer.py index 4c410c7..d3a1616 100644 --- a/tests/polyconf/test_polymer.py +++ b/tests/polyconf/test_polymer.py @@ -25,6 +25,15 @@ def test_polymer_initialization(data_dir, pdb_file): for method in methods: assert hasattr(Polymer, method), f"Polymer class should have a method named {method}" +def test_polymer_initialization_arguments(): + ''' + Test that the Polymer class raises an error when initialized with a non-Monomer object + ''' + with pytest.raises(ValueError): + Polymer(None) + with pytest.raises(TypeError): + Polymer("not a Monomer") + @pytest.mark.parametrize("pdb_file", test_pdbs) def test_polymer_attributes(data_dir, pdb_file): first_monomer = mda.Universe(f"{data_dir}/{pdb_file}") From 3512dd8933ec00d7e971e37cfc7b389d69308bea Mon Sep 17 00:00:00 2001 From: Richard Morris Date: Sat, 30 Nov 2024 13:56:03 +1100 Subject: [PATCH 3/8] revert guard clause --- polyconf/polyconf/polymer.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/polyconf/polyconf/polymer.py b/polyconf/polyconf/polymer.py index 0480fd0..feb6e78 100644 --- a/polyconf/polyconf/polymer.py +++ b/polyconf/polyconf/polymer.py @@ -19,7 +19,7 @@ class Polymer: """ Docstrings go brr """ - def __init__(self, firstMonomer: Monomer) -> None: + def __init__(self, firstMonomer) -> None: """ Initiate the polymer by building its first monomer. Takes a copy of its atoms and sets the residue resids to 1. @@ -27,12 +27,6 @@ def __init__(self, firstMonomer: Monomer) -> None: Args: firstMonomer (MDAnalysis Universe): first monomer of the polymer """ - # Guard clauses to ensure that firstMonomer.residues.resids is a sane call - if firstMonomer is None: - raise ValueError('firstMonomer must be a Monomer object') - if not isinstance(firstMonomer, Monomer): - raise TypeError('firstMonomer must be a Monomer object') - firstMonomer.residues.resids = 1 self.first = firstMonomer self.polymer = self.first From 92bf1651b6f9f130310541dc29dfabf0936f23eb Mon Sep 17 00:00:00 2001 From: lunamorrow Date: Wed, 5 Feb 2025 16:24:42 +1000 Subject: [PATCH 4/8] Update existing polyconf tests on this branch to match with with the current usage of the package --- polyconf/polyconf/PDB.py | 6 ++-- tests/polyconf/test_PDB.py | 35 ++++++++++++----------- tests/polyconf/test_import.py | 2 +- tests/polyconf/test_monomer.py | 3 +- tests/polyconf/test_polyconf_automatic.py | 12 -------- tests/polyconf/test_polymer.py | 12 +++++--- 6 files changed, 33 insertions(+), 37 deletions(-) delete mode 100644 tests/polyconf/test_polyconf_automatic.py diff --git a/polyconf/polyconf/PDB.py b/polyconf/polyconf/PDB.py index 42f8f6f..18a7e99 100644 --- a/polyconf/polyconf/PDB.py +++ b/polyconf/polyconf/PDB.py @@ -55,9 +55,9 @@ def save(self, dummyAtoms="X*", fname="polymer", selectionString = None, gmx = F """ if selectionString: if gmx: - self.select_atoms(f"{selectionString} and not name {dummyAtoms}").atoms._write(f"{fname}.gro") + self.select_atoms(f"{selectionString} and not name {dummyAtoms}")._write(f"{fname}.gro") else: - self.select_atoms(f"{selectionString} and not name {dummyAtoms}").atoms._write(f"{fname}.pdb") + self.select_atoms(f"{selectionString} and not name {dummyAtoms}")._write(f"{fname}.pdb") else: if gmx: self._write(f"not name {dummyAtoms}", f"{fname}.gro") @@ -72,7 +72,7 @@ def crudesave(self,fname="polymer_crude"): :param fname: name of the output file, defaults to "polymer_crude" :type fname: str, optional """ - self.atoms._write(f"{fname}.pdb") + self._write("all", f"{fname}.pdb") def select_atoms(self, selection) -> mda.AtomGroup: """ diff --git a/tests/polyconf/test_PDB.py b/tests/polyconf/test_PDB.py index 205d2c5..8e2e99e 100644 --- a/tests/polyconf/test_PDB.py +++ b/tests/polyconf/test_PDB.py @@ -1,19 +1,22 @@ +#!/usr/bin/env python import random import pytest -import MDAnalysis as mda from polyconf.PDB import PDB +from polyconf.Polymer import Polymer +from polyconf.Monomer import Monomer +import MDAnalysis as mda from .test_data import test_pdbs @pytest.mark.parametrize("pdb_file", test_pdbs) def test_pdb_initialization(data_dir, pdb_file): - polymer = mda.Universe(f"{data_dir}/{pdb_file}") + polymer = Polymer(Monomer(f"{data_dir}/{pdb_file}")) pdb = PDB(polymer) assert pdb.polymer == polymer assert len(pdb.atoms) == len(polymer.atoms) @pytest.mark.parametrize("pdb_file", test_pdbs) def test_cleanup(data_dir, pdb_file): - polymer = mda.Universe(f"{data_dir}/{pdb_file}") + polymer = Polymer(Monomer(f"{data_dir}/{pdb_file}")) pdb = PDB(polymer) pdb.cleanup() box = (pdb.atoms.positions).max(axis=0) - (pdb.atoms.positions).min(axis=0) + [10, 10, 10] @@ -22,25 +25,25 @@ def test_cleanup(data_dir, pdb_file): assert (pdb.atoms.positions >= -box/2).all(), "Some atoms are located outside the box" @pytest.mark.parametrize("pdb_file", test_pdbs) -def test_write(data_dir, pdb_file, tmp_path): - polymer = mda.Universe(f"{data_dir}/{pdb_file}") +def test_crude_save(data_dir, pdb_file, output_dir): + polymer = Polymer(Monomer(f"{data_dir}/{pdb_file}")) pdb = PDB(polymer) - output_file = tmp_path / "output.pdb" + output_file = output_dir / "output.pdb" + pdb.crudesave(fname=str(output_dir / "output")) + assert output_file.exists() + with open(output_file, 'r') as f: + content = f.read() for atom in pdb.polymer.atoms: # Test for every atom in the polymer - pdb._write(f"name {atom.name}", str(output_file)) - assert output_file.exists() - with open(output_file, 'r') as f: - content = f.read() assert f" {atom.name} " in content, f"Atom {atom.name} not found in output file" @pytest.mark.parametrize("pdb_file", test_pdbs) -def test_save(data_dir, pdb_file, tmp_path): - polymer = mda.Universe(f"{data_dir}/{pdb_file}") +def test_save(data_dir, pdb_file, output_dir): + polymer = Polymer(Monomer(f"{data_dir}/{pdb_file}")) pdb = PDB(polymer) - output_file = tmp_path / "output.gro" - random_atom = random.choice(pdb.polymer.atoms) # just test one atom at random - pdb.save(dummyAtoms=random_atom.name, fname=str(output_file), selectionString="all", pdb=False) + output_file = output_dir / "output.gro" + pdb.save(fname=str(output_dir / "output"), gmx=True) assert output_file.exists() with open(output_file, 'r') as f: content = f.read() - assert f" {random_atom.name} " in content, f"Atom {random_atom.name} not found in output file" + for atom in pdb.polymer.atoms: # Test for every atom in the polymer + assert f" {atom.name} " in content, f"Atom {atom.name} not found in output file" diff --git a/tests/polyconf/test_import.py b/tests/polyconf/test_import.py index c1b80d5..a8bbc3c 100644 --- a/tests/polyconf/test_import.py +++ b/tests/polyconf/test_import.py @@ -2,4 +2,4 @@ def test_import_polyconf(): try: import polyconf except ImportError: - assert False, "Failed to import polyconf" + assert False, "Failed to import PolyConf" \ No newline at end of file diff --git a/tests/polyconf/test_monomer.py b/tests/polyconf/test_monomer.py index 314586e..c3c51ab 100644 --- a/tests/polyconf/test_monomer.py +++ b/tests/polyconf/test_monomer.py @@ -1,7 +1,8 @@ +#!/usr/bin/env python import random import pytest +from polyconf.Monomer import Monomer import MDAnalysis as mda -from polyconf.monomer import Monomer from .test_data import test_pdbs # This is a list of test pdb files that we tested for existence in the test_data.py file diff --git a/tests/polyconf/test_polyconf_automatic.py b/tests/polyconf/test_polyconf_automatic.py deleted file mode 100644 index 13c00de..0000000 --- a/tests/polyconf/test_polyconf_automatic.py +++ /dev/null @@ -1,12 +0,0 @@ -import pytest -import subprocess -from pathlib import Path - - -def test_polyconf_automatic_help(): - ''' - Test that the cli script for polyconf_automatic.py runs and prints help message - ''' - result = subprocess.run(['polyconf-automatic', '--help'], capture_output=True, text=True) - assert result.returncode == 0 - assert "usage:" in result.stdout diff --git a/tests/polyconf/test_polymer.py b/tests/polyconf/test_polymer.py index d3a1616..7b33bd5 100644 --- a/tests/polyconf/test_polymer.py +++ b/tests/polyconf/test_polymer.py @@ -1,6 +1,8 @@ +#!/usr/bin/env python import pytest +from polyconf.Polymer import Polymer +from polyconf.Monomer import Monomer import MDAnalysis as mda -from polyconf.polymer import Polymer from .test_data import test_pdbs @pytest.mark.parametrize("pdb_file", test_pdbs) @@ -10,7 +12,7 @@ def test_polymer_initialization(data_dir, pdb_file): And that it defines a bunch of attributes and functions that we expect ''' - first_monomer = mda.Universe(f"{data_dir}/{pdb_file}") + first_monomer = Monomer(f"{data_dir}/{pdb_file}") polymer = Polymer(first_monomer) assert polymer.first == first_monomer assert polymer.first.residues.resids[0] == 1 @@ -21,7 +23,9 @@ def test_polymer_initialization(data_dir, pdb_file): assert hasattr(polymer, 'atoms') # let's make sure some functions we expect are defined - methods = ['select_atoms', 'renamer', 'extend', 'genconf','gencomp','shuffle','newresid'] + methods = ['select_atoms', 'copy', 'renamer', 'extend', 'dist', 'gencomp', + 'shuffle', 'shuffler', 'dihedral_solver', 'gen_pairlist', + 'newresid', 'maxresid'] for method in methods: assert hasattr(Polymer, method), f"Polymer class should have a method named {method}" @@ -36,7 +40,7 @@ def test_polymer_initialization_arguments(): @pytest.mark.parametrize("pdb_file", test_pdbs) def test_polymer_attributes(data_dir, pdb_file): - first_monomer = mda.Universe(f"{data_dir}/{pdb_file}") + first_monomer = Monomer(f"{data_dir}/{pdb_file}") polymer = Polymer(first_monomer) assert polymer.first.residues.resids[0] == 1 From 1a3a115789d77048dce93ba2aed0dee4108e9ae8 Mon Sep 17 00:00:00 2001 From: lunamorrow Date: Wed, 5 Feb 2025 16:28:24 +1000 Subject: [PATCH 5/8] Updated the polyconf import paths to appease the GitHub Actions god --- tests/polyconf/test_PDB.py | 6 +++--- tests/polyconf/test_monomer.py | 2 +- tests/polyconf/test_polymer.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/polyconf/test_PDB.py b/tests/polyconf/test_PDB.py index 8e2e99e..53db9c2 100644 --- a/tests/polyconf/test_PDB.py +++ b/tests/polyconf/test_PDB.py @@ -1,9 +1,9 @@ #!/usr/bin/env python import random import pytest -from polyconf.PDB import PDB -from polyconf.Polymer import Polymer -from polyconf.Monomer import Monomer +from polyconf.polyconf.PDB import PDB +from polyconf.polyconf.Polymer import Polymer +from polyconf.polyconf.Monomer import Monomer import MDAnalysis as mda from .test_data import test_pdbs diff --git a/tests/polyconf/test_monomer.py b/tests/polyconf/test_monomer.py index c3c51ab..de4196f 100644 --- a/tests/polyconf/test_monomer.py +++ b/tests/polyconf/test_monomer.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import random import pytest -from polyconf.Monomer import Monomer +from polyconf.polyconf.Monomer import Monomer import MDAnalysis as mda from .test_data import test_pdbs # This is a list of test pdb files that we tested for existence in the test_data.py file diff --git a/tests/polyconf/test_polymer.py b/tests/polyconf/test_polymer.py index 7b33bd5..9977f11 100644 --- a/tests/polyconf/test_polymer.py +++ b/tests/polyconf/test_polymer.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import pytest -from polyconf.Polymer import Polymer -from polyconf.Monomer import Monomer +from polyconf.polyconf.Polymer import Polymer +from polyconf.polyconf.Monomer import Monomer import MDAnalysis as mda from .test_data import test_pdbs From be15097e5350823908592b426c1fc959a418f8d7 Mon Sep 17 00:00:00 2001 From: lunamorrow Date: Wed, 5 Feb 2025 17:00:18 +1000 Subject: [PATCH 6/8] PolyConf base tests covered --- polyconf/polyconf/Monomer.py | 1 + tests/polyconf/test_monomer.py | 11 ++++++++++- tests/polyconf/test_polymer.py | 12 ++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/polyconf/polyconf/Monomer.py b/polyconf/polyconf/Monomer.py index febf3d4..ce1bcc2 100644 --- a/polyconf/polyconf/Monomer.py +++ b/polyconf/polyconf/Monomer.py @@ -31,6 +31,7 @@ def __init__(self, monomerName, fromUni = False) -> None: @classmethod def monomer_from_u(cls, universe:mda.Universe) -> 'Monomer': """ + Create Monomer from an MDAnalysis Universe :param universe: _description_ :type universe: mda.Universe diff --git a/tests/polyconf/test_monomer.py b/tests/polyconf/test_monomer.py index de4196f..c07f209 100644 --- a/tests/polyconf/test_monomer.py +++ b/tests/polyconf/test_monomer.py @@ -27,4 +27,13 @@ def test_select_atoms(data_dir, pdb_filename: str): random_atom = random.choice(monomer.atoms) selected_atoms = monomer.select_atoms(f"name {random_atom.name}") assert len(selected_atoms) == 1, f"There should only be one {random_atom.name} atom in {pdb_filename}" - assert selected_atoms[0].name == random_atom.name, f"The selected atom should be named {random_atom.name}" \ No newline at end of file + assert selected_atoms[0].name == random_atom.name, f"The selected atom should be named {random_atom.name}" + +@pytest.mark.parametrize("pdb_filename", test_pdbs) +def test_monomer_from_u(data_dir, pdb_filename: str): + """ + Test that the monomer_from_u Monomer Class method works + """ + universe = mda.Universe(data_dir / pdb_filename) + monomer = Monomer.monomer_from_u(universe) + assert len(monomer.atoms) == len(universe.atoms) \ No newline at end of file diff --git a/tests/polyconf/test_polymer.py b/tests/polyconf/test_polymer.py index 9977f11..1029b0d 100644 --- a/tests/polyconf/test_polymer.py +++ b/tests/polyconf/test_polymer.py @@ -1,5 +1,6 @@ #!/usr/bin/env python import pytest +import random from polyconf.polyconf.Polymer import Polymer from polyconf.polyconf.Monomer import Monomer import MDAnalysis as mda @@ -44,4 +45,15 @@ def test_polymer_attributes(data_dir, pdb_file): polymer = Polymer(first_monomer) assert polymer.first.residues.resids[0] == 1 +@pytest.mark.parametrize("pdb_file", test_pdbs) +def test_select_atoms(data_dir, pdb_file: str): + """ + Test that we can find an atom by name in the polymer using the select_atoms method + """ + polymer = Polymer(Monomer(data_dir / pdb_file)) + random_atom = random.choice(polymer.atoms) + selected_atoms = polymer.select_atoms(f"name {random_atom.name}") + assert len(selected_atoms) == 1, f"There should only be one {random_atom.name} atom in {pdb_file}" + assert selected_atoms[0].name == random_atom.name, f"The selected atom should be named {random_atom.name}" + # Add more tests for other methods and functionalities of the Polymer class as needed \ No newline at end of file From 2675cd5adecd70c69274b8061da7e9581265088c Mon Sep 17 00:00:00 2001 From: lunamorrow Date: Thu, 6 Feb 2025 12:18:55 +1000 Subject: [PATCH 7/8] Add copy() method to Monomer to support this functionality, as well as dimensions attribute to both Polymer and Monomer for easy query/checking --- polyconf/polyconf/Monomer.py | 21 ++++++++++++++++++++- polyconf/polyconf/Polymer.py | 1 + 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/polyconf/polyconf/Monomer.py b/polyconf/polyconf/Monomer.py index ce1bcc2..1fc5719 100644 --- a/polyconf/polyconf/Monomer.py +++ b/polyconf/polyconf/Monomer.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +from __future__ import annotations import MDAnalysis as mda from MDAnalysis import Merge @@ -27,6 +28,7 @@ def __init__(self, monomerName, fromUni = False) -> None: self.monomer = mda.Universe(monomerName) self.residues = self.monomer.residues self.atoms = self.monomer.atoms + self.dimensions = self.monomer.dimensions @classmethod def monomer_from_u(cls, universe:mda.Universe) -> 'Monomer': @@ -54,4 +56,21 @@ def select_atoms(self, selection:str) -> mda.AtomGroup: :return: An MDAnalysis AtomGroup containing only the selected atoms :rtype: mda.AtomGroup """ - return self.monomer.select_atoms(selection) \ No newline at end of file + return self.monomer.select_atoms(selection) + + def copy(self) -> Monomer: + """ + Use MDAnalysis Universe.copy() to create a new MDAnalysis Universe that + is an exact copy of this one containing the polymer. Deepcopy is not + used as it can be problematic with open file sockets. + + :return: A new Polymer that is an exact copy of this polymer + :rtype: Polymer + """ + new = self.monomer.copy() + return Monomer.monomer_from_u(new) + + # def __eq__(self, value): + # new = self.atoms.subtract(value.atoms) + # print(len(new)) + # return self.select_atoms("all") == value.select_atoms("all") # and self.residues.equals(value.residues) \ No newline at end of file diff --git a/polyconf/polyconf/Polymer.py b/polyconf/polyconf/Polymer.py index c3fff1b..dd9fbd7 100644 --- a/polyconf/polyconf/Polymer.py +++ b/polyconf/polyconf/Polymer.py @@ -45,6 +45,7 @@ def __init__(self, firstMonomer: Monomer, keep_resids=False) -> None: self.first = firstMonomer self.polymer = self.first self.atoms = self.polymer.atoms + self.dimensions = self.polymer.dimensions def select_atoms(self, selection) -> mda.AtomGroup: """ From 2031d64ce59ad71c81b73fd979ce837809ac76dd Mon Sep 17 00:00:00 2001 From: lunamorrow Date: Thu, 6 Feb 2025 12:20:09 +1000 Subject: [PATCH 8/8] Extend PolyConf tests for more complex Polymer functionality like polymer extension, dihedral shuffling/solving and generating pairlists --- tests/data/PEI_end.pdb | 15 +++ tests/data/PEI_monomer.pdb | 15 +++ tests/data/PEI_start.pdb | 15 +++ tests/polyconf/test_data.py | 9 ++ tests/polyconf/test_polymer.py | 189 ++++++++++++++++++++++++++++++++- 5 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 tests/data/PEI_end.pdb create mode 100644 tests/data/PEI_monomer.pdb create mode 100644 tests/data/PEI_start.pdb diff --git a/tests/data/PEI_end.pdb b/tests/data/PEI_end.pdb new file mode 100644 index 0000000..31f85fe --- /dev/null +++ b/tests/data/PEI_end.pdb @@ -0,0 +1,15 @@ +HETATM 1 N1 ELPR 0 -1.757 2.627 0.115 1.00 0.00 N +HETATM 2 H1 ELPR 0 -1.791 3.280 -0.670 1.00 0.00 H +HETATM 3 C2 ELPR 0 -2.966 1.803 0.070 1.00 0.00 C +HETATM 4 C1 ELPR 0 -4.221 2.675 0.069 1.00 0.00 C +HETATM 5 NX ELPR 0 -5.436 1.859 0.055 1.00 0.00 N +HETATM 6 H2 ELPR 0 -1.732 3.143 0.983 1.00 0.00 H +HETATM 7 CZ ELPR 0 -0.469 1.795 -0.026 1.00 0.00 C +CONECT 4 3 5 +CONECT 3 4 1 +CONECT 2 1 +CONECT 6 1 +CONECT 1 3 6 7 2 +CONECT 5 4 +CONECT 7 1 +END diff --git a/tests/data/PEI_monomer.pdb b/tests/data/PEI_monomer.pdb new file mode 100644 index 0000000..aaabc19 --- /dev/null +++ b/tests/data/PEI_monomer.pdb @@ -0,0 +1,15 @@ +HETATM 1 N1 ELNR 0 -1.757 2.627 0.115 1.00 0.00 N +HETATM 2 H1 ELNR 0 -1.791 3.280 -0.670 1.00 0.00 H +HETATM 3 C2 ELNR 0 -2.966 1.803 0.070 1.00 0.00 C +HETATM 4 C1 ELNR 0 -4.221 2.675 0.069 1.00 0.00 C +HETATM 5 NX ELNR 0 -5.436 1.859 0.055 1.00 0.00 N +HETATM 6 H2 ELNR 0 -1.732 3.143 0.983 1.00 0.00 H +HETATM 7 CX ELNR 0 -0.469 1.795 -0.026 1.00 0.00 C +CONECT 4 3 5 +CONECT 3 4 1 +CONECT 2 1 +CONECT 6 1 +CONECT 1 3 6 7 2 +CONECT 5 4 +CONECT 7 1 +END diff --git a/tests/data/PEI_start.pdb b/tests/data/PEI_start.pdb new file mode 100644 index 0000000..9f2c263 --- /dev/null +++ b/tests/data/PEI_start.pdb @@ -0,0 +1,15 @@ +HETATM 1 N1 ELPR 0 -1.757 2.627 0.115 1.00 0.00 N +HETATM 2 H1 ELPR 0 -1.791 3.280 -0.670 1.00 0.00 H +HETATM 3 C2 ELPR 0 -2.966 1.803 0.070 1.00 0.00 C +HETATM 4 C1 ELPR 0 -4.221 2.675 0.069 1.00 0.00 C +HETATM 5 C0 ELPR 0 -5.436 1.859 0.055 1.00 0.00 N +HETATM 6 H2 ELPR 0 -1.732 3.143 0.983 1.00 0.00 H +HETATM 7 CX ELPR 0 -0.469 1.795 -0.026 1.00 0.00 C +CONECT 4 3 5 +CONECT 3 4 1 +CONECT 2 1 +CONECT 6 1 +CONECT 1 3 6 7 2 +CONECT 5 4 +CONECT 7 1 +END diff --git a/tests/polyconf/test_data.py b/tests/polyconf/test_data.py index 420c688..b768a0d 100644 --- a/tests/polyconf/test_data.py +++ b/tests/polyconf/test_data.py @@ -5,3 +5,12 @@ @pytest.mark.parametrize("pdb_filename", test_pdbs) def test_data_files_exist(data_dir, pdb_filename): assert (data_dir / pdb_filename).exists(), f"{pdb_filename} does not exist in {data_dir}" + +PEI_pdbs = ['PEI_start.pdb', 'PEI_end.pdb', 'PEI_monomer.pdb'] +PEI_start = ['PEI_start.pdb'] +PEI_monomer = ['PEI_monomer.pdb'] +PEI_end = ['PEI_end.pdb'] + +@pytest.mark.parametrize("PEI_filename", PEI_pdbs) +def test_PEI_files_exist(data_dir, PEI_filename): + assert (data_dir / PEI_filename).exists(), f"{PEI_filename} does not exist in {data_dir}" \ No newline at end of file diff --git a/tests/polyconf/test_polymer.py b/tests/polyconf/test_polymer.py index 1029b0d..b54e31d 100644 --- a/tests/polyconf/test_polymer.py +++ b/tests/polyconf/test_polymer.py @@ -4,7 +4,7 @@ from polyconf.polyconf.Polymer import Polymer from polyconf.polyconf.Monomer import Monomer import MDAnalysis as mda -from .test_data import test_pdbs +from .test_data import test_pdbs, PEI_pdbs, PEI_start, PEI_monomer, PEI_end @pytest.mark.parametrize("pdb_file", test_pdbs) def test_polymer_initialization(data_dir, pdb_file): @@ -56,4 +56,189 @@ def test_select_atoms(data_dir, pdb_file: str): assert len(selected_atoms) == 1, f"There should only be one {random_atom.name} atom in {pdb_file}" assert selected_atoms[0].name == random_atom.name, f"The selected atom should be named {random_atom.name}" -# Add more tests for other methods and functionalities of the Polymer class as needed \ No newline at end of file +@pytest.mark.parametrize("pdb_file", test_pdbs) +def test_polymer_copy(data_dir, pdb_file: str): + """ + Test that the Polymer copy() function creates a new, identical Polymer. + """ + polymer = Polymer(Monomer(data_dir / pdb_file)) + new_polymer = Polymer.copy(polymer) + assert len(polymer.select_atoms("all")) == len(new_polymer.select_atoms("all")) + for atom in polymer.atoms: + assert len(new_polymer.select_atoms(f"name {atom.name}")) == 1 + assert polymer.polymer.dimensions == new_polymer.polymer.dimensions + random_atom = random.choice(polymer.atoms) + selected_atom = polymer.select_atoms(f"name {random_atom.name}")[0] + selected_atom.name = "different" + assert len(polymer.select_atoms(f"name different")) == 1 + assert len(new_polymer.select_atoms(f"name different")) == 0 # changing atom in original should not change atom in the copy + +@pytest.mark.parametrize("pdb_file", test_pdbs) +def test_polymer_renamer(data_dir, pdb_file: str): + """ + Test that the Polymer renamer() function renames the correct atoms to the + provided new name. + """ + polymer = Polymer(Monomer(data_dir / pdb_file)) + name = random.choice(polymer.atoms).name + polymer.renamer(1, name, nameout='X') + assert len(polymer.select_atoms(f"name X*")) == 1 + name2 = random.choice(polymer.atoms).name + polymer.renamer(1, name2, nameout='DUMMY') + assert len(polymer.select_atoms(f"name DUMMY1")) == 1 + +def test_polymer_extend(data_dir): + """ + Test that the Polymer extend works as expected. + """ + polymer = Polymer(Monomer(data_dir / 'PEI_start.pdb')) + + adds=128 + for i in range (0,adds): + polymer.extend( # extend with one monomer, aligned along this step's linearization vector + Monomer(data_dir / 'PEI_monomer.pdb'), # extend with this monomer + n=polymer.maxresid(), # we will allways add onto the existing monomer with the highest resid + nn=polymer.newresid(), # the incoming monomer needs a new resid + names=dict(P1='CX',P2='C1',Q1='N1',Q2='NX'), # C1_i+1 fit to CX_i, then rotate so NX_i+1 fit to N1_i + joins=[('N1','C1')],# new connection between N1_i and C1_i+1 + ) + + polymer.extend( + Monomer(data_dir / 'PEI_end.pdb'), + n=polymer.maxresid(), + nn=polymer.newresid(), + names=dict(P1='CX',P2='C1',Q1='N1',Q2='NX'), + joins=[('N1','C1')], + ) + + assert polymer.maxresid() == 130 + +def test_polymer_maxresid(data_dir): + """ + Test that the Polymer maxresid() function returns the correct number. + """ + polymer = Polymer(Monomer(data_dir / 'PEI_start.pdb')) + assert polymer.maxresid() == 1 + + adds=8 + for i in range (0,adds): + polymer.extend( # extend with one monomer, aligned along this step's linearization vector + Monomer(data_dir / 'PEI_monomer.pdb'), # extend with this monomer + n=polymer.maxresid(), # we will allways add onto the existing monomer with the highest resid + nn=polymer.newresid(), # the incoming monomer needs a new resid + names=dict(P1='CX',P2='C1',Q1='N1',Q2='NX'), # C1_i+1 fit to CX_i, then rotate so NX_i+1 fit to N1_i + joins=[('N1','C1')],# new connection between N1_i and C1_i+1 + ) + + polymer.extend( + Monomer(data_dir / 'PEI_end.pdb'), + n=polymer.maxresid(), + nn=polymer.newresid(), + names=dict(P1='CX',P2='C1',Q1='N1',Q2='NX'), + joins=[('N1','C1')], + ) + + assert polymer.maxresid() == 10 + + new_polymer = Polymer(Monomer.monomer_from_u(polymer.polymer), keep_resids=False) + assert new_polymer.maxresid() == 1 # if keep_resids==False, the new polymer will only have resid 1 + +def test_polymer_newresid(data_dir): + """ + Test that the Polymer newresid() function returns the correct number. + """ + polymer = Polymer(Monomer(data_dir / 'PEI_start.pdb')) + assert polymer.newresid() == 2 + + adds=8 + for i in range (0,adds): + polymer.extend( # extend with one monomer, aligned along this step's linearization vector + Monomer(data_dir / 'PEI_monomer.pdb'), # extend with this monomer + n=polymer.maxresid(), # we will allways add onto the existing monomer with the highest resid + nn=polymer.newresid(), # the incoming monomer needs a new resid + names=dict(P1='CX',P2='C1',Q1='N1',Q2='NX'), # C1_i+1 fit to CX_i, then rotate so NX_i+1 fit to N1_i + joins=[('N1','C1')],# new connection between N1_i and C1_i+1 + ) + assert polymer.newresid() == i+3 + + polymer.extend( + Monomer(data_dir / 'PEI_end.pdb'), + n=polymer.maxresid(), + nn=polymer.newresid(), + names=dict(P1='CX',P2='C1',Q1='N1',Q2='NX'), + joins=[('N1','C1')], + ) + + assert polymer.newresid() == 11 + +def test_polymer_dihedralsolver_and_genpairlist(data_dir): + """ + Test that the Polymer dihedral_solver() and gen_pairlist() work as expected + and are able to resolve the polymer dihedrals. + """ + polymer = Polymer(Monomer(data_dir / 'PEI_start.pdb')) + adds=128 + for i in range (0,adds): + polymer.extend( # extend with one monomer, aligned along this step's linearization vector + Monomer(data_dir / 'PEI_monomer.pdb'), # extend with this monomer + n=polymer.maxresid(), # we will allways add onto the existing monomer with the highest resid + nn=polymer.newresid(), # the incoming monomer needs a new resid + names=dict(P1='CX',P2='C1',Q1='N1',Q2='NX'), # C1_i+1 fit to CX_i, then rotate so NX_i+1 fit to N1_i + joins=[('N1','C1')],# new connection between N1_i and C1_i+1 + ) + + polymer.extend( + Monomer(data_dir / 'PEI_end.pdb'), + n=polymer.maxresid(), + nn=polymer.newresid(), + names=dict(P1='CX',P2='C1',Q1='N1',Q2='NX'), + joins=[('N1','C1')], + ) + + CN_dihedrals=polymer.gen_pairlist(a1='C2',a2='N1',first_resid=1,last_resid=130,mult=3) + + cutoff = 0.7 + + polymer.dihedral_solver(CN_dihedrals,dummy='CX,NX',cutoff=cutoff) + + for dh in CN_dihedrals: + # check that the dihedral_solver has removed all clashes + assert polymer.dist('C2',dh['a1_resid'],'N1',dh['a2_resid'],'CX,NX',backwards_only=False) >= cutoff + +def test_polymer_shuffler(data_dir): + """ + Test that the Polymer shuffler() works as expected. + """ + polymer = Polymer(Monomer(data_dir / 'PEI_start.pdb')) + adds=128 + for i in range (0,adds): + polymer.extend( # extend with one monomer, aligned along this step's linearization vector + Monomer(data_dir / 'PEI_monomer.pdb'), # extend with this monomer + n=polymer.maxresid(), # we will allways add onto the existing monomer with the highest resid + nn=polymer.newresid(), # the incoming monomer needs a new resid + names=dict(P1='CX',P2='C1',Q1='N1',Q2='NX'), # C1_i+1 fit to CX_i, then rotate so NX_i+1 fit to N1_i + joins=[('N1','C1')],# new connection between N1_i and C1_i+1 + ) + + polymer.extend( + Monomer(data_dir / 'PEI_end.pdb'), + n=polymer.maxresid(), + nn=polymer.newresid(), + names=dict(P1='CX',P2='C1',Q1='N1',Q2='NX'), + joins=[('N1','C1')], + ) + + CN_dihedrals=polymer.gen_pairlist(a1='C2',a2='N1',first_resid=1,last_resid=130,mult=3) + + distances_before = [] + + for dh in CN_dihedrals: + distances_before.append(polymer.dist('C2',dh['a1_resid'],'N1',dh['a2_resid'],'CX,NX',backwards_only=False)) + + polymer.shuffler(CN_dihedrals) + + for i, dh in enumerate(CN_dihedrals): + # check that the shuffler has chaged all the dihedrals + assert polymer.dist('C2',dh['a1_resid'],'N1',dh['a2_resid'],'CX,NX',backwards_only=False) != distances_before[i] + +# TODO: Add more tests for other methods and functionalities of the Polymer class as needed \ No newline at end of file