From 1e08d25a17e72877a606edd916f475d39fcb034c Mon Sep 17 00:00:00 2001 From: "f.grunewald" Date: Wed, 7 Sep 2022 17:09:25 +0200 Subject: [PATCH 01/18] raise warning if geometry is out of bounds --- polyply/src/build_file_parser.py | 58 ++++++++++++++++++++----- polyply/tests/test_build_file_parser.py | 24 ++++++++++ 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/polyply/src/build_file_parser.py b/polyply/src/build_file_parser.py index 1e961c00..21df56ad 100644 --- a/polyply/src/build_file_parser.py +++ b/polyply/src/build_file_parser.py @@ -53,8 +53,9 @@ def _molecule(self, line, lineno=0): self.current_molidxs = np.arange(float(tokens[1]), float(tokens[2]), 1., dtype=int) for idx in self.current_molidxs: if idx not in self.topology.mol_idx_by_name[tokens[0]]: - LOGGER.warning("parsing build file: could not find molecule with name {name} and index {index}.", - **{"index": idx, "name": tokens[0]}) + msg = ("parsing build file: could not find molecule with " + "name {name} and index {index}.") + LOGGER.warning(msg, **{"index": idx, "name": tokens[0]}) @SectionLineParser.section_parser('molecule', 'cylinder', geom_type="cylinder") @SectionLineParser.section_parser('molecule', 'sphere', geom_type="sphere") @@ -231,18 +232,17 @@ def _tag_nodes(molecule, keyword, option, molname=""): [option['parameters']] # broadcast warning if we find the resid but it doesn't match the resname elif molecule.nodes[node]["resid"] in resids and not\ - molecule.nodes[node]["resname"] == resname: - msg = "parsing build file: could not find resid {resid} with resname {resname} in molecule {molname}." - LOGGER.warning(msg, **{"resid": molecule.nodes[node]["resid"], "resname": resname, - "molname": molname}) + molecule.nodes[node]["resname"] == resname: + msg = "parsing build file: could not find resid {resid} with resname {resname} in molecule {molname}." + LOGGER.warning(msg, **{"resid": molecule.nodes[node]["resid"], "resname": resname, + "molname": molname}) # broadcast warning if we find the resname but it doesn't match the resid elif molecule.nodes[node]["resname"] == resname and not\ - molecule.nodes[node]["resid"]: - msg = "parsing build file: could not find residue {resname} with resid {resid} in molecule {molname}." - LOGGER.warning(msg, **{"resid": molecule.nodes[node]["resid"], "resname": resname, - "molname": molname}) - + molecule.nodes[node]["resid"]: + msg = "parsing build file: could not find residue {resname} with resid {resid} in molecule {molname}." + LOGGER.warning(msg, **{"resid": molecule.nodes[node]["resid"], "resname": resname, + "molname": molname}) @staticmethod def _base_parser_geometry(tokens, _type): geometry_def = {} @@ -258,8 +258,44 @@ def _base_parser_geometry(tokens, _type): parameters.append(_type) geometry_def["parameters"] = parameters + + _check_geometry_def(geometry_def, _type) return geometry_def +def _check_geometry_def(geom_def, geom_type): + """ + Raise a warning if the point of reference + for the geometry type is too close to the + origin. + + Parameters + ---------- + geom_def: dict + dict with entries: resname, start, stop, point, parameters + geom_type: str + one of sphere, cylinder, rectangle + """ + msg = ("Geometry restriction {geom_def} extends beyond definite " + "positive coordiantes relative to the center point " + "{x:.3f} {y:.3f} {z:.3f}. Be aware polyply only builds " + "in positive coordinate space.") + + point = geom_def['parameters'][1] + if geom_type == "sphere": + radius = geom_def['parameters'][2] + if any( i < j for i, j in zip([radius, radius, radius], point)): + LOGGER.warning(msg, geom_def=geom_def, x=point[0], y=point[1], z=point[2]) + + if geom_type == "rectangle": + a, b, c = geom_def['parameters'][2:5] + if any( i < j for i, j in zip([a, b, c], point)): + LOGGER.warning(msg, geom_def=geom_def, x=point[0], y=point[1], z=point[2]) + + if geom_type == "cylinder": + z, r = geom_def['parameters'][2:4] + if z < point[2] or r < np.linalg.norm(point[:2]): + LOGGER.warning(msg, geom_def=geom_def, x=point[0], y=point[1], z=point[2]) + def read_build_file(lines, molecules, topology): """ Parses `lines` of itp format and adds the diff --git a/polyply/tests/test_build_file_parser.py b/polyply/tests/test_build_file_parser.py index fb4447d5..fccb9ce2 100644 --- a/polyply/tests/test_build_file_parser.py +++ b/polyply/tests/test_build_file_parser.py @@ -14,6 +14,7 @@ """ Test that build files are properly read. """ +import logging import textwrap import pytest import numpy as np @@ -51,6 +52,29 @@ def test_base_parser_geometry(tokens, _type, expected): for result_param, expected_param in zip(result[key][2:], expected[key][2:]): assert result_param == expected_param +@pytest.mark.parametrize('tokens, _type', ( + # for cylinder z is not covered + (["PEO", "63", "154", "in", "8", "8", "6", "6", "7"], + "cylinder",), + # for cylinder z is not covered + (["PEO", "63", "154", "in", "2", "2", "8", "6", "7"], + "cylinder",), + # for recangle one of the sides is out + (["PEO", "0", "10", "out", "11", "0", "13", "1", "2", "3"], + "rectangle",), + # for sphere radius is to large/small + (["PEO", "0", "10", "in", "0", "12", "13", "5"], + "sphere",), + )) +def test_base_parser_geometry_warning(caplog, tokens, _type): + with caplog.at_level(logging.WARNING): + result = polyply.src.build_file_parser.BuildDirector._base_parser_geometry(tokens, _type) + for record in caplog.records: + assert record.levelname == "WARNING" + break + else: + assert False + @pytest.fixture def test_molecule(): # dummy vermouth force-field From f1178e30deac04d81729b6d60fd3d9b943056a37 Mon Sep 17 00:00:00 2001 From: Fabian Grunewald <32294573+fgrunewald@users.noreply.github.com> Date: Fri, 9 Sep 2022 13:28:10 +0200 Subject: [PATCH 02/18] Update polyply/src/build_file_parser.py Co-authored-by: Peter C Kroon --- polyply/src/build_file_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polyply/src/build_file_parser.py b/polyply/src/build_file_parser.py index 21df56ad..79d7f023 100644 --- a/polyply/src/build_file_parser.py +++ b/polyply/src/build_file_parser.py @@ -55,7 +55,7 @@ def _molecule(self, line, lineno=0): if idx not in self.topology.mol_idx_by_name[tokens[0]]: msg = ("parsing build file: could not find molecule with " "name {name} and index {index}.") - LOGGER.warning(msg, **{"index": idx, "name": tokens[0]}) + LOGGER.warning(msg, index=idx, name=tokens[0]) @SectionLineParser.section_parser('molecule', 'cylinder', geom_type="cylinder") @SectionLineParser.section_parser('molecule', 'sphere', geom_type="sphere") From 53adbcf3513f66043b7699adcff0d39625237d91 Mon Sep 17 00:00:00 2001 From: Fabian Grunewald <32294573+fgrunewald@users.noreply.github.com> Date: Fri, 9 Sep 2022 13:28:16 +0200 Subject: [PATCH 03/18] Update polyply/src/build_file_parser.py Co-authored-by: Peter C Kroon --- polyply/src/build_file_parser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/polyply/src/build_file_parser.py b/polyply/src/build_file_parser.py index 79d7f023..542cb694 100644 --- a/polyply/src/build_file_parser.py +++ b/polyply/src/build_file_parser.py @@ -234,8 +234,7 @@ def _tag_nodes(molecule, keyword, option, molname=""): elif molecule.nodes[node]["resid"] in resids and not\ molecule.nodes[node]["resname"] == resname: msg = "parsing build file: could not find resid {resid} with resname {resname} in molecule {molname}." - LOGGER.warning(msg, **{"resid": molecule.nodes[node]["resid"], "resname": resname, - "molname": molname}) + LOGGER.warning(msg, resid=molecule.nodes[node]["resid"], resname=resname, molname=molname) # broadcast warning if we find the resname but it doesn't match the resid elif molecule.nodes[node]["resname"] == resname and not\ From 9efef5936e940094b9dd9bfe3189ca520d62a206 Mon Sep 17 00:00:00 2001 From: Fabian Grunewald <32294573+fgrunewald@users.noreply.github.com> Date: Fri, 9 Sep 2022 13:28:21 +0200 Subject: [PATCH 04/18] Update polyply/src/build_file_parser.py Co-authored-by: Peter C Kroon --- polyply/src/build_file_parser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/polyply/src/build_file_parser.py b/polyply/src/build_file_parser.py index 542cb694..af634434 100644 --- a/polyply/src/build_file_parser.py +++ b/polyply/src/build_file_parser.py @@ -240,8 +240,7 @@ def _tag_nodes(molecule, keyword, option, molname=""): elif molecule.nodes[node]["resname"] == resname and not\ molecule.nodes[node]["resid"]: msg = "parsing build file: could not find residue {resname} with resid {resid} in molecule {molname}." - LOGGER.warning(msg, **{"resid": molecule.nodes[node]["resid"], "resname": resname, - "molname": molname}) + LOGGER.warning(msg, resid=molecule.nodes[node]["resid"], resname=resname, molname=molname) @staticmethod def _base_parser_geometry(tokens, _type): geometry_def = {} From d3614c8c48da2491ea5e03f5c365c5f58e2ca174 Mon Sep 17 00:00:00 2001 From: Fabian Grunewald <32294573+fgrunewald@users.noreply.github.com> Date: Fri, 9 Sep 2022 13:28:44 +0200 Subject: [PATCH 05/18] Update polyply/src/build_file_parser.py Co-authored-by: Peter C Kroon --- polyply/src/build_file_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polyply/src/build_file_parser.py b/polyply/src/build_file_parser.py index af634434..9f0a5345 100644 --- a/polyply/src/build_file_parser.py +++ b/polyply/src/build_file_parser.py @@ -274,7 +274,7 @@ def _check_geometry_def(geom_def, geom_type): one of sphere, cylinder, rectangle """ msg = ("Geometry restriction {geom_def} extends beyond definite " - "positive coordiantes relative to the center point " + "positive coordinates relative to the center point " "{x:.3f} {y:.3f} {z:.3f}. Be aware polyply only builds " "in positive coordinate space.") From 7c969256b036e3ce677faa9555968f82fc13815a Mon Sep 17 00:00:00 2001 From: "f.grunewald" Date: Sat, 10 Sep 2022 20:03:32 +0200 Subject: [PATCH 06/18] make check-function return bool instead of raising the warning --- polyply/src/build_file_parser.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/polyply/src/build_file_parser.py b/polyply/src/build_file_parser.py index 9f0a5345..152c24a5 100644 --- a/polyply/src/build_file_parser.py +++ b/polyply/src/build_file_parser.py @@ -241,6 +241,7 @@ def _tag_nodes(molecule, keyword, option, molname=""): molecule.nodes[node]["resid"]: msg = "parsing build file: could not find residue {resname} with resid {resid} in molecule {molname}." LOGGER.warning(msg, resid=molecule.nodes[node]["resid"], resname=resname, molname=molname) + @staticmethod def _base_parser_geometry(tokens, _type): geometry_def = {} @@ -257,7 +258,14 @@ def _base_parser_geometry(tokens, _type): parameters.append(_type) geometry_def["parameters"] = parameters - _check_geometry_def(geometry_def, _type) + is_good_geometry = _check_geometry_def(geometry_def, _type) + if not is_good_geometry: + msg = ("Geometry restriction {_type} {resname} {start} {stop} " + "extends beyond definite positive coordinates relative " + "to the center point {x:.3f} {y:.3f} {z:.3f}. Be aware " + "polyply only builds in positive coordinate space.") + LOGGER.warning(msg, _type=_type, x=point[0], y=point[1], z=point[2], + resname=tokens[0], start=tokens[1], stop=tokens[2]) return geometry_def def _check_geometry_def(geom_def, geom_type): @@ -282,17 +290,19 @@ def _check_geometry_def(geom_def, geom_type): if geom_type == "sphere": radius = geom_def['parameters'][2] if any( i < j for i, j in zip([radius, radius, radius], point)): - LOGGER.warning(msg, geom_def=geom_def, x=point[0], y=point[1], z=point[2]) + return False if geom_type == "rectangle": a, b, c = geom_def['parameters'][2:5] if any( i < j for i, j in zip([a, b, c], point)): - LOGGER.warning(msg, geom_def=geom_def, x=point[0], y=point[1], z=point[2]) + return False if geom_type == "cylinder": z, r = geom_def['parameters'][2:4] if z < point[2] or r < np.linalg.norm(point[:2]): - LOGGER.warning(msg, geom_def=geom_def, x=point[0], y=point[1], z=point[2]) + return False + + return True def read_build_file(lines, molecules, topology): """ From c35f4e922bf6c56ed39a4d3dce45de621dada31b Mon Sep 17 00:00:00 2001 From: "f.grunewald" Date: Sat, 10 Sep 2022 20:34:18 +0200 Subject: [PATCH 07/18] adjust warnings to also trigger when equal --- polyply/src/build_file_parser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/polyply/src/build_file_parser.py b/polyply/src/build_file_parser.py index 152c24a5..3d428991 100644 --- a/polyply/src/build_file_parser.py +++ b/polyply/src/build_file_parser.py @@ -289,17 +289,17 @@ def _check_geometry_def(geom_def, geom_type): point = geom_def['parameters'][1] if geom_type == "sphere": radius = geom_def['parameters'][2] - if any( i < j for i, j in zip([radius, radius, radius], point)): + if any( i >= j for i, j in zip([radius, radius, radius], point)): return False if geom_type == "rectangle": a, b, c = geom_def['parameters'][2:5] - if any( i < j for i, j in zip([a, b, c], point)): + if any( i >= j for i, j in zip([a, b, c], point)): return False if geom_type == "cylinder": z, r = geom_def['parameters'][2:4] - if z < point[2] or r < np.linalg.norm(point[:2]): + if z >= point[2] or any(r >= j for j in point[:2]): return False return True From 27b5e77a6eb0293782e5c285d2ed9c1c874a1c25 Mon Sep 17 00:00:00 2001 From: "f.grunewald" Date: Sat, 10 Sep 2022 20:42:45 +0200 Subject: [PATCH 08/18] adjust tests to also trigger when equal --- polyply/src/build_file_parser.py | 2 +- polyply/tests/test_build_file_parser.py | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/polyply/src/build_file_parser.py b/polyply/src/build_file_parser.py index 3d428991..02a94e68 100644 --- a/polyply/src/build_file_parser.py +++ b/polyply/src/build_file_parser.py @@ -298,7 +298,7 @@ def _check_geometry_def(geom_def, geom_type): return False if geom_type == "cylinder": - z, r = geom_def['parameters'][2:4] + r, z = geom_def['parameters'][2:4] if z >= point[2] or any(r >= j for j in point[:2]): return False diff --git a/polyply/tests/test_build_file_parser.py b/polyply/tests/test_build_file_parser.py index fccb9ce2..9e1836a3 100644 --- a/polyply/tests/test_build_file_parser.py +++ b/polyply/tests/test_build_file_parser.py @@ -53,18 +53,30 @@ def test_base_parser_geometry(tokens, _type, expected): assert result_param == expected_param @pytest.mark.parametrize('tokens, _type', ( + # for cylinder radius is not box + (["PEO", "63", "154", "in", "4", "8", "8", "6", "7"], + "cylinder",), + # for cylinder radius is equal + (["PEO", "63", "154", "in", "6", "8", "8", "6", "7"], + "cylinder",), # for cylinder z is not covered (["PEO", "63", "154", "in", "8", "8", "6", "6", "7"], "cylinder",), - # for cylinder z is not covered - (["PEO", "63", "154", "in", "2", "2", "8", "6", "7"], + # for cylinder z is equal + (["PEO", "63", "154", "in", "8", "8", "7", "6", "7"], "cylinder",), # for recangle one of the sides is out (["PEO", "0", "10", "out", "11", "0", "13", "1", "2", "3"], "rectangle",), + # for recangle one of the sides is equal + (["PEO", "0", "10", "out", "1", "10", "10", "1", "2", "3"], + "rectangle",), # for sphere radius is to large/small (["PEO", "0", "10", "in", "0", "12", "13", "5"], "sphere",), + # for sphere radius is to equal + (["PEO", "0", "10", "in", "5", "12", "13", "5"], + "sphere",), )) def test_base_parser_geometry_warning(caplog, tokens, _type): with caplog.at_level(logging.WARNING): From 1045c9988a1ea2c998a66a95508ef895395d5c9b Mon Sep 17 00:00:00 2001 From: "f.grunewald" Date: Sat, 10 Sep 2022 20:56:00 +0200 Subject: [PATCH 09/18] add tests for warnings when residues are not found --- polyply/tests/test_build_file_parser.py | 43 +++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/polyply/tests/test_build_file_parser.py b/polyply/tests/test_build_file_parser.py index 9e1836a3..7671720f 100644 --- a/polyply/tests/test_build_file_parser.py +++ b/polyply/tests/test_build_file_parser.py @@ -261,6 +261,49 @@ def test_parser(test_system, lines, tagged_mols, tagged_nodes): assert node in tagged_nodes assert idx in tagged_mols +@pytest.mark.parametrize('lines', ( + # resname not found + """ + [ molecule ] + ; name from to + AA 0 2 + ; + [ cylinder ] + ; resname start stop inside-out x y z r z + ALA 2 4 in 5 5 5 5 5 + PEG 1 2 in 5 5 5 5 5 + """, + # resids don't match all resnames + """ + [ molecule ] + BB 2 3 + [ rectangle ] + ; resname start stop inside-out x y z a b c + ALA 1 6 in 5 5 5 5 5 5 + """, + # test nothing is tagged based on the molname + """ + [ molecule ] + CC 1 6 + [ sphere ] + ; resname start stop inside-out x y z r + ALA 2 4 in 5 5 5 5 + """, + )) +def test_parser_warnings(caplog, test_system, lines): + lines = textwrap.dedent(lines).splitlines() + ff = vermouth.forcefield.ForceField(name='test_ff') + top = Topology(ff) + with caplog.at_level(logging.WARNING): + polyply.src.build_file_parser.read_build_file(lines, + test_system.molecules, + top) + for record in caplog.records: + assert record.levelname == "WARNING" + break + else: + assert False + @pytest.mark.parametrize('lines, expected', ( # basic test From bb9648eafd0627e37866e2a6d3a93900ab1c3ff6 Mon Sep 17 00:00:00 2001 From: "f.grunewald" Date: Sat, 10 Sep 2022 21:02:29 +0200 Subject: [PATCH 10/18] add tests for warnings when residues are not found --- polyply/tests/test_build_file_parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/polyply/tests/test_build_file_parser.py b/polyply/tests/test_build_file_parser.py index 7671720f..773de7be 100644 --- a/polyply/tests/test_build_file_parser.py +++ b/polyply/tests/test_build_file_parser.py @@ -273,13 +273,13 @@ def test_parser(test_system, lines, tagged_mols, tagged_nodes): ALA 2 4 in 5 5 5 5 5 PEG 1 2 in 5 5 5 5 5 """, - # resids don't match all resnames + # resids don't match resnames """ [ molecule ] BB 2 3 [ rectangle ] ; resname start stop inside-out x y z a b c - ALA 1 6 in 5 5 5 5 5 5 + ALA 1 2 in 5 5 5 5 5 5 """, # test nothing is tagged based on the molname """ From 904cd29745183a87f56aba707459ca835b10a8a8 Mon Sep 17 00:00:00 2001 From: "f.grunewald" Date: Sat, 10 Sep 2022 22:01:36 +0200 Subject: [PATCH 11/18] fix usage of test-system and warnings --- polyply/src/build_file_parser.py | 9 +++++- polyply/tests/test_build_file_parser.py | 37 +++++++++++-------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/polyply/src/build_file_parser.py b/polyply/src/build_file_parser.py index 02a94e68..f5ab20f8 100644 --- a/polyply/src/build_file_parser.py +++ b/polyply/src/build_file_parser.py @@ -51,6 +51,11 @@ def _molecule(self, line, lineno=0): tokens = line.split() self.current_molname = tokens[0] self.current_molidxs = np.arange(float(tokens[1]), float(tokens[2]), 1., dtype=int) + # raise error if molname not in system + if tokens[0] not in self.topology.mol_idx_by_name: + msg = "Molecule with name {name} is not part of the system." + raise IOError(msg.format(name=tokens[0])) + for idx in self.current_molidxs: if idx not in self.topology.mol_idx_by_name[tokens[0]]: msg = ("parsing build file: could not find molecule with " @@ -208,7 +213,8 @@ def finalize(self, lineno=0): if that molecule is mentioned in the build file by name. """ for mol_idx, molecule in enumerate(self.molecules): - + print(mol_idx, molecule.mol_name) + print(self.build_options) if (molecule.mol_name, mol_idx) in self.build_options: for option in self.build_options[(molecule.mol_name, mol_idx)]: self._tag_nodes(molecule, "restraints", option, molecule.mol_name) @@ -239,6 +245,7 @@ def _tag_nodes(molecule, keyword, option, molname=""): # broadcast warning if we find the resname but it doesn't match the resid elif molecule.nodes[node]["resname"] == resname and not\ molecule.nodes[node]["resid"]: + print("go here") msg = "parsing build file: could not find residue {resname} with resid {resid} in molecule {molname}." LOGGER.warning(msg, resid=molecule.nodes[node]["resid"], resname=resname, molname=molname) diff --git a/polyply/tests/test_build_file_parser.py b/polyply/tests/test_build_file_parser.py index 773de7be..a43fd17e 100644 --- a/polyply/tests/test_build_file_parser.py +++ b/polyply/tests/test_build_file_parser.py @@ -152,7 +152,7 @@ def test_system(): NA = MetaMolecule() NA.add_monomer(current=0, resname="NA", connections=[]) molecules = [meta_mol_A, meta_mol_A.copy(), - meta_mol_B.copy(), NA, NA.copy(), + meta_mol_B, NA, NA.copy(), NA.copy(), NA.copy()] top = Topology(force_field=force_field) top.molecules = molecules @@ -223,7 +223,7 @@ def test_distance_restraints_error(test_system, line): ; [ cylinder ] ; resname start stop inside-out x y z r z - ALA 2 4 in 5 5 5 5 5 + ALA 2 4 in 5 5 5 4 4 """, [0, 1], [0, 1, 2, 3]), @@ -233,28 +233,28 @@ def test_distance_restraints_error(test_system, line): BB 2 3 [ rectangle ] ; resname start stop inside-out x y z a b c - ALA 3 6 in 5 5 5 5 5 5 + ALA 3 6 in 5 5 5 4 4 4 """, [2], [2, 3, 4, 5]), # test nothing is tagged based on the molname + # combo with mol-idx; molname must be part of + # the system though (""" [ molecule ] - CC 1 6 + BB 0 1 [ sphere ] ; resname start stop inside-out x y z r - ALA 2 4 in 5 5 5 5 + ALA 2 4 in 5 5 5 4 """, [], []), )) def test_parser(test_system, lines, tagged_mols, tagged_nodes): lines = textwrap.dedent(lines).splitlines() - ff = vermouth.forcefield.ForceField(name='test_ff') - top = Topology(ff) polyply.src.build_file_parser.read_build_file(lines, test_system.molecules, - top) + test_system) for idx, mol in enumerate(test_system.molecules): for node in mol.nodes: if "restraints" in mol.nodes[node]: @@ -270,8 +270,8 @@ def test_parser(test_system, lines, tagged_mols, tagged_nodes): ; [ cylinder ] ; resname start stop inside-out x y z r z - ALA 2 4 in 5 5 5 5 5 - PEG 1 2 in 5 5 5 5 5 + ALA 2 4 in 5 5 5 4 4 + PEG 1 2 in 5 5 5 4 4 """, # resids don't match resnames """ @@ -279,32 +279,29 @@ def test_parser(test_system, lines, tagged_mols, tagged_nodes): BB 2 3 [ rectangle ] ; resname start stop inside-out x y z a b c - ALA 1 2 in 5 5 5 5 5 5 + GLU 3 6 in 5 5 5 4 4 4 """, # test nothing is tagged based on the molname """ [ molecule ] - CC 1 6 + BB 0 1 [ sphere ] ; resname start stop inside-out x y z r - ALA 2 4 in 5 5 5 5 + ALA 2 4 in 5 5 5 4 """, )) def test_parser_warnings(caplog, test_system, lines): lines = textwrap.dedent(lines).splitlines() - ff = vermouth.forcefield.ForceField(name='test_ff') - top = Topology(ff) with caplog.at_level(logging.WARNING): polyply.src.build_file_parser.read_build_file(lines, test_system.molecules, - top) + test_system) for record in caplog.records: assert record.levelname == "WARNING" break else: assert False - @pytest.mark.parametrize('lines, expected', ( # basic test (""" @@ -337,12 +334,10 @@ def test_parser_warnings(caplog, test_system, lines): ))) def test_persistence_parsers(test_system, lines, expected): lines = textwrap.dedent(lines).splitlines() - ff = vermouth.forcefield.ForceField(name='test_ff') - top = Topology(ff) polyply.src.build_file_parser.read_build_file(lines, test_system.molecules, - top) - for ref, new in zip(expected, top.persistences): + test_system) + for ref, new in zip(expected, test_system.persistences): print(ref, new) for info_ref, info_new in zip(ref[:-1], new[:-1]): assert info_ref == info_new From 66f5e5462ee46818028cce69f3cd60c9b6fdf31b Mon Sep 17 00:00:00 2001 From: "f.grunewald" Date: Sat, 10 Sep 2022 22:31:30 +0200 Subject: [PATCH 12/18] add proper copy method for meta-molecules --- polyply/src/meta_molecule.py | 18 ++++++++++++++++++ polyply/tests/test_meta_molecule.py | 21 +++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/polyply/src/meta_molecule.py b/polyply/src/meta_molecule.py index 31bbc2e4..0f59108a 100644 --- a/polyply/src/meta_molecule.py +++ b/polyply/src/meta_molecule.py @@ -203,6 +203,24 @@ def split_residue(self, split_strings): self.relabel_and_redo_res_graph(mapping) return mapping + def copy(self, as_view=False): + """ + Overwrites the networkx copy method. This is needed to + correcly set all attributes. + """ + if as_view is True: + return nx.graphviews.generic_graph_view(self) + G = self.__class__(force_field=self.force_field, + mol_name=self.mol_name) + G.molecule = self.molecule + G.graph.update(self.graph) + G.add_nodes_from((n, d.copy()) for n, d in self._node.items()) + G.add_edges_from( + (u, v, datadict.copy()) + for u, nbrs in self._adj.items() + for v, datadict in nbrs.items()) + return G + @property def search_tree(self): diff --git a/polyply/tests/test_meta_molecule.py b/polyply/tests/test_meta_molecule.py index 492cb69f..dfa787fb 100644 --- a/polyply/tests/test_meta_molecule.py +++ b/polyply/tests/test_meta_molecule.py @@ -243,3 +243,24 @@ def test_unkown_fromat_error(): MetaMolecule.from_sequence_file(force_field=ff, file_path=test_path, mol_name="test") + +def test_copy(): + """ + Test if a MetaMol copy has all attributes etc. + """ + file_name = TEST_DATA + "/itp/PEO.itp" + edges = [(0,1), (1,2)] + nodes = [0, 1, 2] + attrs = {0: 'PEO', 1: 'PEO', 2: 'PEO'} + + ff = vermouth.forcefield.ForceField(name='test_ff') + name = "PEO" + meta_mol = MetaMolecule.from_itp(ff, file_name, name) + + copy_mol = meta_mol.copy() + assert list(meta_mol.nodes) == list(copy_mol.nodes) + assert meta_mol.force_field == copy_mol.force_field + assert meta_mol.molecule == copy_mol.molecule + for node in meta_mol.nodes: + for key, attr in meta_mol.nodes[node].items(): + assert attr == copy_mol.nodes[node][key] From a9337a567c43c7aacacb3711a9a2d9ddc3039252 Mon Sep 17 00:00:00 2001 From: "f.grunewald" Date: Sat, 10 Sep 2022 22:39:51 +0200 Subject: [PATCH 13/18] stricter test for build-files --- polyply/tests/test_build_file_parser.py | 38 ++++++++++++++++--------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/polyply/tests/test_build_file_parser.py b/polyply/tests/test_build_file_parser.py index a43fd17e..cbbe83f7 100644 --- a/polyply/tests/test_build_file_parser.py +++ b/polyply/tests/test_build_file_parser.py @@ -151,6 +151,7 @@ def test_system(): "BB") NA = MetaMolecule() NA.add_monomer(current=0, resname="NA", connections=[]) + molecules = [meta_mol_A, meta_mol_A.copy(), meta_mol_B, NA, NA.copy(), NA.copy(), NA.copy()] @@ -223,10 +224,10 @@ def test_distance_restraints_error(test_system, line): ; [ cylinder ] ; resname start stop inside-out x y z r z - ALA 2 4 in 5 5 5 4 4 + ALA 1 4 in 5 5 5 4 4 """, [0, 1], - [0, 1, 2, 3]), + [0, 1, 2]), # test that a different molecule is tagged (""" [ molecule ] @@ -236,7 +237,7 @@ def test_distance_restraints_error(test_system, line): ALA 3 6 in 5 5 5 4 4 4 """, [2], - [2, 3, 4, 5]), + [2, 3, 4]), # test nothing is tagged based on the molname # combo with mol-idx; molname must be part of # the system though @@ -251,15 +252,14 @@ def test_distance_restraints_error(test_system, line): []), )) def test_parser(test_system, lines, tagged_mols, tagged_nodes): - lines = textwrap.dedent(lines).splitlines() - polyply.src.build_file_parser.read_build_file(lines, + lines = textwrap.dedent(lines).splitlines() + polyply.src.build_file_parser.read_build_file(lines, test_system.molecules, test_system) - for idx, mol in enumerate(test_system.molecules): - for node in mol.nodes: - if "restraints" in mol.nodes[node]: - assert node in tagged_nodes - assert idx in tagged_mols + for idx in tagged_mols: + mol = test_system.molecules[idx] + restr_nodes = nx.get_node_attributes(mol, "restraints") + assert list(restr_nodes.keys()) == tagged_nodes @pytest.mark.parametrize('lines', ( # resname not found @@ -284,10 +284,10 @@ def test_parser(test_system, lines, tagged_mols, tagged_nodes): # test nothing is tagged based on the molname """ [ molecule ] - BB 0 1 + BB 2 3 [ sphere ] ; resname start stop inside-out x y z r - ALA 2 4 in 5 5 5 4 + ALA 0 2 in 5 5 5 4 """, )) def test_parser_warnings(caplog, test_system, lines): @@ -302,6 +302,19 @@ def test_parser_warnings(caplog, test_system, lines): else: assert False +def test_molname_error(test_system): + lines = """ + [ molecule ] + CC 0 1 + [ sphere ] + ALA 0 2 in 5 5 5 4 + """ + lines = textwrap.dedent(lines).splitlines() + with pytest.raises(IOError): + polyply.src.build_file_parser.read_build_file(lines, + test_system.molecules, + test_system) + @pytest.mark.parametrize('lines, expected', ( # basic test (""" @@ -338,7 +351,6 @@ def test_persistence_parsers(test_system, lines, expected): test_system.molecules, test_system) for ref, new in zip(expected, test_system.persistences): - print(ref, new) for info_ref, info_new in zip(ref[:-1], new[:-1]): assert info_ref == info_new assert all(ref[-1] == new[-1]) From 9d50746e19db91c3ed73c4b54e8d68783045e8b3 Mon Sep 17 00:00:00 2001 From: "f.grunewald" Date: Sat, 10 Sep 2022 22:40:07 +0200 Subject: [PATCH 14/18] remove print --- polyply/src/build_file_parser.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/polyply/src/build_file_parser.py b/polyply/src/build_file_parser.py index f5ab20f8..2fb20e1f 100644 --- a/polyply/src/build_file_parser.py +++ b/polyply/src/build_file_parser.py @@ -213,8 +213,6 @@ def finalize(self, lineno=0): if that molecule is mentioned in the build file by name. """ for mol_idx, molecule in enumerate(self.molecules): - print(mol_idx, molecule.mol_name) - print(self.build_options) if (molecule.mol_name, mol_idx) in self.build_options: for option in self.build_options[(molecule.mol_name, mol_idx)]: self._tag_nodes(molecule, "restraints", option, molecule.mol_name) From 62847fc839f71a2689347abc9401f169e75cd8e8 Mon Sep 17 00:00:00 2001 From: "f.grunewald" Date: Sat, 10 Sep 2022 22:49:20 +0200 Subject: [PATCH 15/18] not implemented error for as_view copy method --- polyply/src/meta_molecule.py | 3 ++- polyply/tests/test_meta_molecule.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/polyply/src/meta_molecule.py b/polyply/src/meta_molecule.py index 0f59108a..da39c8d2 100644 --- a/polyply/src/meta_molecule.py +++ b/polyply/src/meta_molecule.py @@ -209,7 +209,8 @@ def copy(self, as_view=False): correcly set all attributes. """ if as_view is True: - return nx.graphviews.generic_graph_view(self) + msg = "Polyply currently does not implement copy as nx graph-views." + raise NotImplementedError(msg) G = self.__class__(force_field=self.force_field, mol_name=self.mol_name) G.molecule = self.molecule diff --git a/polyply/tests/test_meta_molecule.py b/polyply/tests/test_meta_molecule.py index dfa787fb..9d532053 100644 --- a/polyply/tests/test_meta_molecule.py +++ b/polyply/tests/test_meta_molecule.py @@ -264,3 +264,19 @@ def test_copy(): for node in meta_mol.nodes: for key, attr in meta_mol.nodes[node].items(): assert attr == copy_mol.nodes[node][key] + +def test_copy_as_view(): + """ + Test if a MetaMol copy as view raises Error. + """ + file_name = TEST_DATA + "/itp/PEO.itp" + edges = [(0,1), (1,2)] + nodes = [0, 1, 2] + attrs = {0: 'PEO', 1: 'PEO', 2: 'PEO'} + + ff = vermouth.forcefield.ForceField(name='test_ff') + name = "PEO" + meta_mol = MetaMolecule.from_itp(ff, file_name, name) + + with pytest.raises(NotImplementedError): + copy_mol = meta_mol.copy(as_view=True) From e977fc9aa826098d5919959f020e04230096f79f Mon Sep 17 00:00:00 2001 From: "f.grunewald" Date: Sat, 10 Sep 2022 22:52:22 +0200 Subject: [PATCH 16/18] remove print and fix warning --- polyply/src/build_file_parser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/polyply/src/build_file_parser.py b/polyply/src/build_file_parser.py index 2fb20e1f..084d2075 100644 --- a/polyply/src/build_file_parser.py +++ b/polyply/src/build_file_parser.py @@ -242,8 +242,7 @@ def _tag_nodes(molecule, keyword, option, molname=""): # broadcast warning if we find the resname but it doesn't match the resid elif molecule.nodes[node]["resname"] == resname and not\ - molecule.nodes[node]["resid"]: - print("go here") + molecule.nodes[node]["resid"] in resids: msg = "parsing build file: could not find residue {resname} with resid {resid} in molecule {molname}." LOGGER.warning(msg, resid=molecule.nodes[node]["resid"], resname=resname, molname=molname) From 505b0b493e0b311981decce6e20148e6fd4c9a7d Mon Sep 17 00:00:00 2001 From: "f.grunewald" Date: Sat, 10 Sep 2022 22:53:26 +0200 Subject: [PATCH 17/18] adjust warning --- polyply/tests/test_build_file_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polyply/tests/test_build_file_parser.py b/polyply/tests/test_build_file_parser.py index cbbe83f7..a0630c03 100644 --- a/polyply/tests/test_build_file_parser.py +++ b/polyply/tests/test_build_file_parser.py @@ -287,7 +287,7 @@ def test_parser(test_system, lines, tagged_mols, tagged_nodes): BB 2 3 [ sphere ] ; resname start stop inside-out x y z r - ALA 0 2 in 5 5 5 4 + GLU 5 6 in 5 5 5 4 """, )) def test_parser_warnings(caplog, test_system, lines): From 018c57e0f1bad3345b4bdbc1304147f7856daf93 Mon Sep 17 00:00:00 2001 From: "f.grunewald" Date: Sat, 10 Sep 2022 23:07:00 +0200 Subject: [PATCH 18/18] fix text for logging of resname vs resid second message --- polyply/src/build_file_parser.py | 4 ++-- polyply/tests/test_logging.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/polyply/src/build_file_parser.py b/polyply/src/build_file_parser.py index 084d2075..241ce501 100644 --- a/polyply/src/build_file_parser.py +++ b/polyply/src/build_file_parser.py @@ -243,8 +243,8 @@ def _tag_nodes(molecule, keyword, option, molname=""): # broadcast warning if we find the resname but it doesn't match the resid elif molecule.nodes[node]["resname"] == resname and not\ molecule.nodes[node]["resid"] in resids: - msg = "parsing build file: could not find residue {resname} with resid {resid} in molecule {molname}." - LOGGER.warning(msg, resid=molecule.nodes[node]["resid"], resname=resname, molname=molname) + msg = "parsing build file: could not find residue {resname} with resids {resids} in molecule {molname}." + LOGGER.warning(msg, resids=",".join(map(str, resids)), resname=resname, molname=molname) @staticmethod def _base_parser_geometry(tokens, _type): diff --git a/polyply/tests/test_logging.py b/polyply/tests/test_logging.py index ed657f96..911de5c1 100644 --- a/polyply/tests/test_logging.py +++ b/polyply/tests/test_logging.py @@ -22,7 +22,7 @@ ("sphere", {"resname": "ALA", "start": 9, "stop": 12, "parameters":["in", np.array([10.0, 10.0, 10.0]), 5.0]}, [], - "parsing build file: could not find resid {} with resname ALA in molecule AA.", + "parsing build file: could not find residue ALA with resids 9.0,10.0,11.0 in molecule AA.", range(9, 12)), )) def test_tag_nodes_logging(caplog, test_molecule, _type, option, expected, warning, idxs):