From b3ff841f6e42047d21ce33bb2632e37f63be058f Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Wed, 20 Sep 2023 15:26:25 +0200 Subject: [PATCH 01/13] initial draft --- bin/polyply | 3 +++ polyply/src/gen_coords.py | 6 ++++- polyply/src/random_walk.py | 50 ++++++++++++++++++++++++++++++++++---- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/bin/polyply b/bin/polyply index 3776c9e9..dfb2f193 100755 --- a/bin/polyply +++ b/bin/polyply @@ -160,6 +160,9 @@ def main(): # pylint: disable=too-many-locals,too-many-statements random_walk_group.add_argument('-nr', dest='nrewind', type=int, default=5, help=('number of residues to trace back ' ' when RW fails in first attempt')) + random_walk_group.add_argument('-mb', dest='max_bend', type=float, default=0, + help=('persistence length determining max bending ' + 'the higher the more straight the chain.')) backmap_group = parser_gen_coords.add_argument_group('Options for backmapping') backmap_group.add_argument('-back_fudge', dest='bfudge', type=float, default=0.4, diff --git a/polyply/src/gen_coords.py b/polyply/src/gen_coords.py index 54516cf3..4a11100a 100644 --- a/polyply/src/gen_coords.py +++ b/polyply/src/gen_coords.py @@ -116,7 +116,8 @@ def gen_coords(toppath, max_force=5*10**4.0, nrewind=5, lib=None, - bfudge=0.4): + bfudge=0.4, + max_bend=0): """ Subprogram for coordinate generation which implements the default polyply workflow for structure generation. In general, a topology @@ -189,6 +190,8 @@ def gen_coords(toppath, Fudge factor by which to scale the coordinates of the residues during the backmapping step. 1 will result in to-scale coordinates but will likely generate overlaps. + max_bend: float + persistence legnth determine max bending """ # Read in the topology @@ -258,6 +261,7 @@ def gen_coords(toppath, ignore=ignore, grid=grid, cycles=cycles, + max_bend=max_bend, nrewind=nrewind).run_system(topology.molecules) ligand_annotator.split_ligands() LOGGER.info("backmapping to target resolution", type="step") diff --git a/polyply/src/random_walk.py b/polyply/src/random_walk.py index cb8acc3f..0fb8651b 100644 --- a/polyply/src/random_walk.py +++ b/polyply/src/random_walk.py @@ -17,7 +17,7 @@ from numpy.linalg import norm import networkx as nx from .processor import Processor -from .linalg_functions import _vector_angle_degrees, not_exceeds_max_dimensions, norm_sphere, pbc_complete +from .linalg_functions import angle, _vector_angle_degrees, not_exceeds_max_dimensions, norm_sphere, pbc_complete from .graph_utils import neighborhood from .meta_molecule import _find_starting_node """ @@ -232,7 +232,8 @@ def __init__(self, max_force=1e3, vector_sphere=norm_sphere(5000), start_node=None, - nrewind=5): + nrewind=5, + max_bend=0): self.mol_idx = mol_idx self.nonbond_matrix = nonbond_matrix @@ -246,6 +247,7 @@ def __init__(self, self.start_node = start_node self.nrewind = nrewind self.placed_nodes = [] + self.max_bend = max_bend def _rewind(self, current_step): nodes = [node for _, node in self.placed_nodes[-self.nrewind:-1]] @@ -279,6 +281,43 @@ def checks_milestones(self, current_node, current_position, fudge=0.7): return True + def __find_neighborpos(self, node): + npos = None + for neighbor in self.molecule.neighbors[node]: + try: + npos = self.nonbond_matrix.get_point(self.mol_idx, neighbor) + break + except KeyError: + continue + return npos + + def bendiness(self, point, node): + lp = self.max_bend + B_neigh = list(self.molecule.search_tree.predecessors(node)) + + if len(B_neigh) == 1: + B = B_neigh[0] + else: + return True + + C_neigh = list(self.molecule.search_tree.predecessors(B)) + if len(C_neigh) == 1: + C = C_neigh[0] + B_pos = self.nonbond_matrix.get_point(self.mol_idx, B) + C_pos = self.nonbond_matrix.get_point(self.mol_idx, C) + ang_val = angle(point, B_pos, C_pos) + prob = np.exp(lp*ang_val/180)/(180*(np.exp(lp)-1)/lp) + test_prob = random.uniform(np.exp(lp*1/180)/(180*(np.exp(lp)-1)/lp), + np.exp(lp*179/180)/(180*(np.exp(lp)-1)/lp)) + if self.prev_prob < prob: + self.prev_prob = prob + return True + + elif prob > test_prob: + return True + else: + return True + def update_positions(self, vector_bundle, current_node, prev_node): """ Take an array of unit vectors `vector_bundle` and generate the coordinates @@ -308,7 +347,8 @@ def update_positions(self, vector_bundle, current_node, prev_node): if fulfill_geometrical_constraints(new_point, self.molecule.nodes[current_node])\ and self.checks_milestones(current_node, new_point, step_length)\ and is_restricted(new_point, last_point, self.molecule.nodes[current_node])\ - and not self._is_overlap(new_point, current_node): + and not self._is_overlap(new_point, current_node)\ + and self.bendiness(new_point, current_node): self.nonbond_matrix.add_positions(new_point, self.mol_idx, @@ -356,10 +396,10 @@ def _random_walk(self, meta_molecule): count = 0 path = list(meta_molecule.search_tree.edges) step_count = 0 - + self.prev_prob = 1 while step_count < len(path): prev_node, current_node = path[step_count] - + print(step_count) if not meta_molecule.nodes[current_node]["build"]: step_count += 1 continue From 47703a6d660f93c267dbe4f9b1ee2c23c8fb6f98 Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Wed, 20 Sep 2023 16:44:38 +0200 Subject: [PATCH 02/13] allow sequence specific bending --- polyply/src/build_file_parser.py | 9 ++++++++ polyply/src/nonbond_engine.py | 36 ++++++++++++++++++++++++++++++-- polyply/src/random_walk.py | 34 ++++++++++-------------------- polyply/src/topology.py | 5 +++++ 4 files changed, 59 insertions(+), 25 deletions(-) diff --git a/polyply/src/build_file_parser.py b/polyply/src/build_file_parser.py index f3df6738..47e55e55 100644 --- a/polyply/src/build_file_parser.py +++ b/polyply/src/build_file_parser.py @@ -175,6 +175,15 @@ def _volume(self, line, lineno=0): resname, volume = line.split() self.topology.volumes[resname] = float(volume) + @SectionLineParser.section_parser('bending') + def _bending(self, line, lineno=0): + """ + Parses the lines in the '[bending]' + directive and stores it. + """ + resA, resB, resC, bending_const = line.split() + self.topology.bending[frozenset([resA, resB, resC])] = float(bending_const) + def finalize_section(self, previous_section, ended_section): """ Called once a section has finished. Here we perform all diff --git a/polyply/src/nonbond_engine.py b/polyply/src/nonbond_engine.py index a02ecf55..eddb51c8 100644 --- a/polyply/src/nonbond_engine.py +++ b/polyply/src/nonbond_engine.py @@ -11,12 +11,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import random import itertools import numpy as np import scipy.spatial from polyply import jit from .topology import lorentz_berthelot_rule -from .linalg_functions import not_exceeds_max_dimensions +from .linalg_functions import angle, not_exceeds_max_dimensions def _lennard_jones_force(dist, point, ref, params): """ @@ -77,6 +78,8 @@ def __init__(self, nodes_to_idx, atom_types, interaction_matrix, + bending_matrix, + torsion_matrix, cut_off, boxsize): """ @@ -93,6 +96,12 @@ def __init__(self, Dict mapping the atom_types to LJ type interaction parameters, that is sigma, epsilon or C6, C12 depending on the potential used. Currently only the sigma epsilon form is implemented. + bending_matrix: dict[frozenset(str, str), float] + Dict mapping the residue types to a bending constant for the + bending probability + torsion_matrix: dict[frozenset(str, str), float] + Dict mapping the residue types to a torsion constant for the + torsion probability cut_off: float cut-off for which to compute the interaction in nm boxsize: np.ndarray @@ -103,6 +112,8 @@ def __init__(self, self.nodes_to_gndx = nodes_to_idx self.atypes = np.asarray(atom_types, dtype=str) self.interaction_matrix = interaction_matrix + self.bending_matrix = bending_matrix + self.torsion_matrix = torsion_matrix self.cut_off = cut_off self.boxsize = boxsize @@ -292,6 +303,25 @@ def compute_force_point(self, point, mol_idx, node, exclude=[], potential="LJ"): force += POTENTIAL_FUNC[potential](dist, point, self.positions[gndx_pair], params) return force + def compute_bending_probability(self, point, mol_idx, node_a, node_b, node_c): + # get the atom types aka resnames + typea = self.atypes[self.nodes_to_gndx[(mol_idx, node_a)]] + typeb = self.atypes[self.nodes_to_gndx[(mol_idx, node_b)]] + typec = self.atypes[self.nodes_to_gndx[(mol_idx, node_c)]] + # get the bending constant + lp = self.bending_matrix[frozenset([typea, typeb, typec])] + # get the missing postions + B_pos = self.get_point(mol_idx, node_b) + C_pos = self.get_point(mol_idx, node_c) + # compute angle + ang_val = angle(point, B_pos, C_pos) + # compute probability + prob = np.exp(lp*ang_val/180)/(180*(np.exp(lp)-1)/lp) + # compute test_prob + test_prob = random.uniform(np.exp(lp*1/180)/(180*(np.exp(lp)-1)/lp), + np.exp(lp*179/180)/(180*(np.exp(lp)-1)/lp)) + return prob, test_prob + @classmethod def from_topology(cls, molecules, topology, box): """ @@ -360,5 +390,7 @@ def from_topology(cls, molecules, topology, box): # dynamically set the cut-off as twice the largest vdw-radius cut_off = max(list(inter_matrix.values()))[0] * 2. nonbond_matrix = cls(positions, nodes_to_gndx, - atom_types, inter_matrix, cut_off=cut_off, boxsize=box) + atom_types, inter_matrix, + cut_off=cut_off, boxsize=box, + torsion_matrix=None, bending_matrix=topology.bending) return nonbond_matrix diff --git a/polyply/src/random_walk.py b/polyply/src/random_walk.py index 0fb8651b..bf6e4602 100644 --- a/polyply/src/random_walk.py +++ b/polyply/src/random_walk.py @@ -281,34 +281,22 @@ def checks_milestones(self, current_node, current_position, fudge=0.7): return True - def __find_neighborpos(self, node): - npos = None - for neighbor in self.molecule.neighbors[node]: - try: - npos = self.nonbond_matrix.get_point(self.mol_idx, neighbor) - break - except KeyError: - continue - return npos - def bendiness(self, point, node): - lp = self.max_bend - B_neigh = list(self.molecule.search_tree.predecessors(node)) + """ + Perform Monte-Carlo like sampling of bending potential. + """ + b_nodes = list(self.molecule.search_tree.predecessors(node)) - if len(B_neigh) == 1: - B = B_neigh[0] + if len(b_nodes) == 1: + b_node = b_nodes[0] else: return True - C_neigh = list(self.molecule.search_tree.predecessors(B)) - if len(C_neigh) == 1: - C = C_neigh[0] - B_pos = self.nonbond_matrix.get_point(self.mol_idx, B) - C_pos = self.nonbond_matrix.get_point(self.mol_idx, C) - ang_val = angle(point, B_pos, C_pos) - prob = np.exp(lp*ang_val/180)/(180*(np.exp(lp)-1)/lp) - test_prob = random.uniform(np.exp(lp*1/180)/(180*(np.exp(lp)-1)/lp), - np.exp(lp*179/180)/(180*(np.exp(lp)-1)/lp)) + c_nodes = list(self.molecule.search_tree.predecessors(b_node)) + if len(c_nodes) == 1: + c_node = c_nodes[0] + prob, test_prob = self.nonbond_matrix.compute_bending_probability(point, self.mol_idx, node, b_node, c_node) + if self.prev_prob < prob: self.prev_prob = prob return True diff --git a/polyply/src/topology.py b/polyply/src/topology.py index 86cca836..78e5a030 100644 --- a/polyply/src/topology.py +++ b/polyply/src/topology.py @@ -213,6 +213,10 @@ class Topology(System): A dictionary of all typed parameter defines: list A list of everything that is defined + volumes: dict + Volume constants for meta-models + bending: dict + Sequence dependent bending constants for meta-model """ def __init__(self, force_field, name=None): @@ -228,6 +232,7 @@ def __init__(self, force_field, name=None): self.persistences = [] self.distance_restraints = defaultdict(dict) self.volumes = {} + self.bending = {} def preprocess(self): """ From 5c609b3ef3fa27fe6d68112f3a12678e9d2d4d32 Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Sat, 23 Sep 2023 17:15:53 +0200 Subject: [PATCH 03/13] test and implementation of bending potential --- polyply/src/nonbond_engine.py | 40 +++++++++++++++++++++++---------- polyply/tests/test_nb_engine.py | 25 +++++++++++++++++++++ 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/polyply/src/nonbond_engine.py b/polyply/src/nonbond_engine.py index eddb51c8..50410c17 100644 --- a/polyply/src/nonbond_engine.py +++ b/polyply/src/nonbond_engine.py @@ -55,7 +55,6 @@ def _lennard_jones_force(dist, point, ref, params): POTENTIAL_FUNC = {"LJ": lennard_jones_force} - def _n_particles(molecules): """ Count the number of meta_molecule nodes @@ -303,24 +302,41 @@ def compute_force_point(self, point, mol_idx, node, exclude=[], potential="LJ"): force += POTENTIAL_FUNC[potential](dist, point, self.positions[gndx_pair], params) return force - def compute_bending_probability(self, point, mol_idx, node_a, node_b, node_c): - # get the atom types aka resnames - typea = self.atypes[self.nodes_to_gndx[(mol_idx, node_a)]] - typeb = self.atypes[self.nodes_to_gndx[(mol_idx, node_b)]] - typec = self.atypes[self.nodes_to_gndx[(mol_idx, node_c)]] - # get the bending constant - lp = self.bending_matrix[frozenset([typea, typeb, typec])] + def compute_bending_probability(self, lp, point, mol_idx, node_b, node_c): + """ + Compute probability of an angle between three points (`point`, `node_b`, + `node_c`) according to a simple decay function modulated by a bending + constant `lp`. If lp is large the distribution increases with the highest + probability being at 180 degrees (i.e. more straight angles), whilst if lp + is low the angles occur with almost equal probability. + + Parameters + ---------- + lp: float + bending constant + point: np.ndarray(1, 3) + coordinates of the new point + mol_idx: int + index of the molecule at hand + node_b: abc.hashable + first predecessor of the node corresponding to `point` + node_c: abc.hashable + second predecessor of the node corresponding to `point` + + Returns + ------- + float + the probability + """ # get the missing postions B_pos = self.get_point(mol_idx, node_b) C_pos = self.get_point(mol_idx, node_c) # compute angle ang_val = angle(point, B_pos, C_pos) # compute probability + # denominator is normalization prob = np.exp(lp*ang_val/180)/(180*(np.exp(lp)-1)/lp) - # compute test_prob - test_prob = random.uniform(np.exp(lp*1/180)/(180*(np.exp(lp)-1)/lp), - np.exp(lp*179/180)/(180*(np.exp(lp)-1)/lp)) - return prob, test_prob + return prob @classmethod def from_topology(cls, molecules, topology, box): diff --git a/polyply/tests/test_nb_engine.py b/polyply/tests/test_nb_engine.py index bacd91a7..62888df7 100644 --- a/polyply/tests/test_nb_engine.py +++ b/polyply/tests/test_nb_engine.py @@ -172,6 +172,31 @@ def test_update_positions_in_molecules(topology): for node in mol.nodes: assert all(mol.nodes[node]["position"] == np.array([1., 1., 1.])) +@pytest.mark.parametrize('lp, point, ref_prob',( + # low lp, 180 degree, + (0.001, np.array([0., 0., 2.]), 0.00555833379629605), + # low lp, 90 degree, + (0.001, np.array([0., 1., 1.]), 0.005555555324073842), + # low lp, close to zero angle, + (0.001, np.array([0., 0., 0.1]), 0.00555833379629605), + # high lp, 180 degree, + (10, np.array([0., 0., 2.]), 0.05555807788838943), + # high lp, 90 degree, + (10, np.array([0., 1., 1.]), 0.00037434738418303014), + # high lp, close to 0 degree, + (10, np.array([0., 0., 0.1]), 3.3299656173078084e-06),)) +def test_compute_bending_probability(topology, lp, point, ref_prob): + # we add two fixed positions to the first molecule + positions = [np.array([0., 0., 0,]), np.array([0., 0., 1.])] + for node, position in zip([0, 1], positions): + topology.molecules[0].nodes[node]["position"] = position + + nb_engine = NonBondEngine.from_topology(topology.molecules, + topology, + box=np.array([10., 10., 10.])) + prob = nb_engine.compute_bending_probability(lp, point, 0, 1, 0) + assert np.isclose(prob, ref_prob, atol=10**-5) + @pytest.mark.parametrize('mol_idx_a, mol_idx_b, node_a, node_b, expected', ((0, 0, 0, 1, (0.53+0.67)/2.0), (0, 2, 0, 0, (0.43+0.53)/2.0), From e64c2df587ca92d3ddb5c52fdb09aba9da08638b Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Sun, 24 Sep 2023 14:47:25 +0200 Subject: [PATCH 04/13] test and implementation of bending potential in RW module --- polyply/src/random_walk.py | 44 ++++++++++++++--------- polyply/tests/test_random_walk.py | 58 +++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 17 deletions(-) diff --git a/polyply/src/random_walk.py b/polyply/src/random_walk.py index bf6e4602..d26464ba 100644 --- a/polyply/src/random_walk.py +++ b/polyply/src/random_walk.py @@ -248,6 +248,7 @@ def __init__(self, self.nrewind = nrewind self.placed_nodes = [] self.max_bend = max_bend + self.prev_prob = 1 def _rewind(self, current_step): nodes = [node for _, node in self.placed_nodes[-self.nrewind:-1]] @@ -283,28 +284,38 @@ def checks_milestones(self, current_node, current_position, fudge=0.7): def bendiness(self, point, node): """ - Perform Monte-Carlo like sampling of bending potential. + Perform Monte-Carlo like sampling of bending probability. """ - b_nodes = list(self.molecule.search_tree.predecessors(node)) - - if len(b_nodes) == 1: - b_node = b_nodes[0] - else: - return True - + b_node = list(self.molecule.search_tree.predecessors(node))[0] c_nodes = list(self.molecule.search_tree.predecessors(b_node)) if len(c_nodes) == 1: c_node = c_nodes[0] - prob, test_prob = self.nonbond_matrix.compute_bending_probability(point, self.mol_idx, node, b_node, c_node) + # get the atom types aka resnames + typea = self.molecule.nodes[node]['resname'] + typeb = self.molecule.nodes[b_node]['resname'] + typec = self.molecule.nodes[c_node]['resname'] + # get the bending constant + lp = self.nonbond_matrix.bending_matrix.get((typea, typeb, typec), None) + if lp: + prob = self.nonbond_matrix.compute_bending_probability(lp, + point, + self.mol_idx, + b_node, + c_node) + if self.prev_prob < prob: + self.prev_prob = prob + return True - if self.prev_prob < prob: - self.prev_prob = prob - return True + # compute test probability from uniform sampling of prob function + test_prob = random.uniform(np.exp(lp*1/180)/(180*(np.exp(lp)-1)/lp), + np.exp(lp*179/180)/(180*(np.exp(lp)-1)/lp)) + if prob > test_prob: + self.prev_prob = prob + return True - elif prob > test_prob: - return True - else: - return True + return False + + return True def update_positions(self, vector_bundle, current_node, prev_node): """ @@ -384,7 +395,6 @@ def _random_walk(self, meta_molecule): count = 0 path = list(meta_molecule.search_tree.edges) step_count = 0 - self.prev_prob = 1 while step_count < len(path): prev_node, current_node = path[step_count] print(step_count) diff --git a/polyply/tests/test_random_walk.py b/polyply/tests/test_random_walk.py index f5e36cc1..790ad919 100644 --- a/polyply/tests/test_random_walk.py +++ b/polyply/tests/test_random_walk.py @@ -16,6 +16,7 @@ """ import math import pytest +import random import numpy as np from numpy.linalg import norm import networkx as nx @@ -188,6 +189,63 @@ def test_is_overlap(nonbond_matrix, molecule, new_point, result): # node 4 is already placed and hence is skipped over assert proccessor._is_overlap(new_point, 7, nrexcl=1) == result +@pytest.mark.parametrize('n_coords, new_point, prev_prob, lp, result', ( + # only 1 previous node -> always True + (1, + None, + 1, + 10, + True, + ), + # no lp is given -> always True + (1, + None, + 1, + None, + True, + ), + # 180 degrees should be fine even with high prev prob + (2, + np.array([1., 1., 1.11]), + 1, + 10, + True + ), + # small angle and prev prob high + (2, + np.array([1., 1.47, 0.1]), + 1., + 10, + False + ), + # small angle and prev prob low + (2, + np.array([1., 1.47, 0.1]), + 0., + 10, + True + ), + # medium angle, prev prob high, unifrom prob low + (2, + np.array([1., 1.57, 1.74]), + 1., + 10, + True + ), +)) +def test_bendiness(nonbond_matrix, molecule, n_coords, new_point, prev_prob, lp, result): + # set random seed for reproducability + random.seed(1) + nb_matrix = add_positions(nonbond_matrix, n_coords) + # the bending constant for a series of PEO monomers + # the value is the same as in the high test case from + # test nb matrix + nb_matrix.bending_matrix = {("PEO", "PEO", "PEO"): lp} + processor = RandomWalk(mol_idx=0, nonbond_matrix=nb_matrix) + processor.molecule = molecule + processor.prev_prob = prev_prob + assert processor.bendiness(new_point, n_coords) == result + @pytest.mark.parametrize('new_point, restraint, result', ( # distance restraint true upper_bound # ref_node, upper_bound, lower_bound From 0cda66e89bc8321375dcec32e38169b9e175d9ec Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Sun, 24 Sep 2023 15:12:35 +0200 Subject: [PATCH 05/13] have bending as threefold tuple --- 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 47e55e55..0895f64e 100644 --- a/polyply/src/build_file_parser.py +++ b/polyply/src/build_file_parser.py @@ -182,7 +182,7 @@ def _bending(self, line, lineno=0): directive and stores it. """ resA, resB, resC, bending_const = line.split() - self.topology.bending[frozenset([resA, resB, resC])] = float(bending_const) + self.topology.bending[(resA, resB, resC)] = float(bending_const) def finalize_section(self, previous_section, ended_section): """ From 701a2a7b0d967d3c9ebacb4e2da96fe54cb6b885 Mon Sep 17 00:00:00 2001 From: "Dr. Fabian Grunewald" <32294573+fgrunewald@users.noreply.github.com> Date: Fri, 20 Oct 2023 20:55:25 +0200 Subject: [PATCH 06/13] fix spelling Co-authored-by: Peter C Kroon --- polyply/src/gen_coords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polyply/src/gen_coords.py b/polyply/src/gen_coords.py index 4a11100a..ae74915d 100644 --- a/polyply/src/gen_coords.py +++ b/polyply/src/gen_coords.py @@ -191,7 +191,7 @@ def gen_coords(toppath, during the backmapping step. 1 will result in to-scale coordinates but will likely generate overlaps. max_bend: float - persistence legnth determine max bending + persistence length determine max bending """ # Read in the topology From 113f9997206841dbc1ee19ecb10fda2fc5ffba5f Mon Sep 17 00:00:00 2001 From: "Dr. Fabian Grunewald" <32294573+fgrunewald@users.noreply.github.com> Date: Fri, 20 Oct 2023 20:56:51 +0200 Subject: [PATCH 07/13] fix spelling Co-authored-by: Peter C Kroon --- polyply/src/nonbond_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polyply/src/nonbond_engine.py b/polyply/src/nonbond_engine.py index 50410c17..5a38182b 100644 --- a/polyply/src/nonbond_engine.py +++ b/polyply/src/nonbond_engine.py @@ -328,7 +328,7 @@ def compute_bending_probability(self, lp, point, mol_idx, node_b, node_c): float the probability """ - # get the missing postions + # get the missing positions B_pos = self.get_point(mol_idx, node_b) C_pos = self.get_point(mol_idx, node_c) # compute angle From d721574cffa17dbf1c5402406f73952d8feaf542 Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Thu, 28 Sep 2023 11:28:04 +0200 Subject: [PATCH 08/13] remove print --- polyply/src/random_walk.py | 1 - 1 file changed, 1 deletion(-) diff --git a/polyply/src/random_walk.py b/polyply/src/random_walk.py index d26464ba..c1a6b703 100644 --- a/polyply/src/random_walk.py +++ b/polyply/src/random_walk.py @@ -397,7 +397,6 @@ def _random_walk(self, meta_molecule): step_count = 0 while step_count < len(path): prev_node, current_node = path[step_count] - print(step_count) if not meta_molecule.nodes[current_node]["build"]: step_count += 1 continue From bc80f26eb0c3fbcf779675d2ac40c52eb9de3aa9 Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Fri, 10 Nov 2023 11:57:48 +0100 Subject: [PATCH 09/13] address comments --- polyply/src/random_walk.py | 64 ++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/polyply/src/random_walk.py b/polyply/src/random_walk.py index c1a6b703..0852a81d 100644 --- a/polyply/src/random_walk.py +++ b/polyply/src/random_walk.py @@ -285,37 +285,49 @@ def checks_milestones(self, current_node, current_position, fudge=0.7): def bendiness(self, point, node): """ Perform Monte-Carlo like sampling of bending probability. + + Parameters + ---------- + point: np.ndarray + new coordinate + node: `abc.hashable` + the current node for which to get a new point """ b_node = list(self.molecule.search_tree.predecessors(node))[0] c_nodes = list(self.molecule.search_tree.predecessors(b_node)) - if len(c_nodes) == 1: - c_node = c_nodes[0] - # get the atom types aka resnames - typea = self.molecule.nodes[node]['resname'] - typeb = self.molecule.nodes[b_node]['resname'] - typec = self.molecule.nodes[c_node]['resname'] - # get the bending constant - lp = self.nonbond_matrix.bending_matrix.get((typea, typeb, typec), None) - if lp: - prob = self.nonbond_matrix.compute_bending_probability(lp, - point, - self.mol_idx, - b_node, - c_node) - if self.prev_prob < prob: - self.prev_prob = prob - return True - # compute test probability from uniform sampling of prob function - test_prob = random.uniform(np.exp(lp*1/180)/(180*(np.exp(lp)-1)/lp), - np.exp(lp*179/180)/(180*(np.exp(lp)-1)/lp)) - if prob > test_prob: - self.prev_prob = prob - return True + if len(c_nodes) != 1: + return True + + c_node = c_nodes[0] + # get the atom types aka resnames + typea = self.molecule.nodes[node]['resname'] + typeb = self.molecule.nodes[b_node]['resname'] + typec = self.molecule.nodes[c_node]['resname'] + # get the bending constant + lp = self.nonbond_matrix.bending_matrix.get((typea, typeb, typec), None) + if not lp: + return True + + prob = self.nonbond_matrix.compute_bending_probability(lp, + point, + self.mol_idx, + b_node, + c_node) + if self.prev_prob < prob: + self.prev_prob = prob + return True + + # compute test probability from uniform sampling of prob function + norm = 180/lp * (np.exp(lp)-1) + test_prob = random.uniform(np.exp(lp*1/180)/norm, + np.exp(lp*179/180)/norm) + + if test_prob < prob: + self.prev_prob = prob + return True - return False - - return True + return False def update_positions(self, vector_bundle, current_node, prev_node): """ From 0cbc742e64dbd0ad28b075c0b9da1600fd7ac60c Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Fri, 10 Nov 2023 12:01:07 +0100 Subject: [PATCH 10/13] address comments --- polyply/tests/test_random_walk.py | 42 +++++-------------------------- 1 file changed, 6 insertions(+), 36 deletions(-) diff --git a/polyply/tests/test_random_walk.py b/polyply/tests/test_random_walk.py index 790ad919..b4639a39 100644 --- a/polyply/tests/test_random_walk.py +++ b/polyply/tests/test_random_walk.py @@ -191,47 +191,17 @@ def test_is_overlap(nonbond_matrix, molecule, new_point, result): @pytest.mark.parametrize('n_coords, new_point, prev_prob, lp, result', ( # only 1 previous node -> always True - (1, - None, - 1, - 10, - True, - ), + (1, None, 1, 10, True,), # no lp is given -> always True - (1, - None, - 1, - None, - True, - ), + (1, None, 1, None, True,), # 180 degrees should be fine even with high prev prob - (2, - np.array([1., 1., 1.11]), - 1, - 10, - True - ), + (2, np.array([1., 1., 1.11]), 1, 10, True), # small angle and prev prob high - (2, - np.array([1., 1.47, 0.1]), - 1., - 10, - False - ), + (2, np.array([1., 1.47, 0.1]), 1., 10, False), # small angle and prev prob low - (2, - np.array([1., 1.47, 0.1]), - 0., - 10, - True - ), + (2, np.array([1., 1.47, 0.1]), 0., 10, True), # medium angle, prev prob high, unifrom prob low - (2, - np.array([1., 1.57, 1.74]), - 1., - 10, - True - ), + (2, np.array([1., 1.57, 1.74]), 1., 10, True), )) def test_bendiness(nonbond_matrix, molecule, n_coords, new_point, prev_prob, lp, result): # set random seed for reproducability From f7c1f543a2401028fae5d81a370b47a558a3e535 Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Fri, 10 Nov 2023 12:02:15 +0100 Subject: [PATCH 11/13] address comments --- polyply/tests/test_nb_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polyply/tests/test_nb_engine.py b/polyply/tests/test_nb_engine.py index 62888df7..5674adcf 100644 --- a/polyply/tests/test_nb_engine.py +++ b/polyply/tests/test_nb_engine.py @@ -195,7 +195,7 @@ def test_compute_bending_probability(topology, lp, point, ref_prob): topology, box=np.array([10., 10., 10.])) prob = nb_engine.compute_bending_probability(lp, point, 0, 1, 0) - assert np.isclose(prob, ref_prob, atol=10**-5) + assert prob == pytest.approx(ref_prob, abs=10**-5) @pytest.mark.parametrize('mol_idx_a, mol_idx_b, node_a, node_b, expected', ((0, 0, 0, 1, (0.53+0.67)/2.0), From d1493893e6579682f3dfa6520d641c6e1d14720f Mon Sep 17 00:00:00 2001 From: Fabian Grunewald Date: Mon, 20 Nov 2023 16:48:59 +0100 Subject: [PATCH 12/13] remove maxbend option --- bin/polyply | 3 --- 1 file changed, 3 deletions(-) diff --git a/bin/polyply b/bin/polyply index dfb2f193..3776c9e9 100755 --- a/bin/polyply +++ b/bin/polyply @@ -160,9 +160,6 @@ def main(): # pylint: disable=too-many-locals,too-many-statements random_walk_group.add_argument('-nr', dest='nrewind', type=int, default=5, help=('number of residues to trace back ' ' when RW fails in first attempt')) - random_walk_group.add_argument('-mb', dest='max_bend', type=float, default=0, - help=('persistence length determining max bending ' - 'the higher the more straight the chain.')) backmap_group = parser_gen_coords.add_argument_group('Options for backmapping') backmap_group.add_argument('-back_fudge', dest='bfudge', type=float, default=0.4, From 41944b59c1a943968c3600931ce645e87d0ff8f6 Mon Sep 17 00:00:00 2001 From: Fabian Grunewald Date: Mon, 20 Nov 2023 16:51:18 +0100 Subject: [PATCH 13/13] remove maxbend option --- polyply/src/gen_coords.py | 6 +----- polyply/src/random_walk.py | 4 +--- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/polyply/src/gen_coords.py b/polyply/src/gen_coords.py index ae74915d..54516cf3 100644 --- a/polyply/src/gen_coords.py +++ b/polyply/src/gen_coords.py @@ -116,8 +116,7 @@ def gen_coords(toppath, max_force=5*10**4.0, nrewind=5, lib=None, - bfudge=0.4, - max_bend=0): + bfudge=0.4): """ Subprogram for coordinate generation which implements the default polyply workflow for structure generation. In general, a topology @@ -190,8 +189,6 @@ def gen_coords(toppath, Fudge factor by which to scale the coordinates of the residues during the backmapping step. 1 will result in to-scale coordinates but will likely generate overlaps. - max_bend: float - persistence length determine max bending """ # Read in the topology @@ -261,7 +258,6 @@ def gen_coords(toppath, ignore=ignore, grid=grid, cycles=cycles, - max_bend=max_bend, nrewind=nrewind).run_system(topology.molecules) ligand_annotator.split_ligands() LOGGER.info("backmapping to target resolution", type="step") diff --git a/polyply/src/random_walk.py b/polyply/src/random_walk.py index d26464ba..30e87a05 100644 --- a/polyply/src/random_walk.py +++ b/polyply/src/random_walk.py @@ -232,8 +232,7 @@ def __init__(self, max_force=1e3, vector_sphere=norm_sphere(5000), start_node=None, - nrewind=5, - max_bend=0): + nrewind=5): self.mol_idx = mol_idx self.nonbond_matrix = nonbond_matrix @@ -247,7 +246,6 @@ def __init__(self, self.start_node = start_node self.nrewind = nrewind self.placed_nodes = [] - self.max_bend = max_bend self.prev_prob = 1 def _rewind(self, current_step):