From 2af7cc065060155ffc4be8f49fb78f25513928bb Mon Sep 17 00:00:00 2001 From: riesben Date: Tue, 3 Sep 2024 08:43:47 +0200 Subject: [PATCH 01/56] draft for Konnektor integration --- openfe/setup/ligand_network_planning.py | 192 +++++++------------- openfe/tests/setup/test_network_planning.py | 2 +- 2 files changed, 70 insertions(+), 124 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 99aa2ee87..112112255 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -17,6 +17,10 @@ from lomap import generate_lomap_network, LomapAtomMapper from lomap.dbmol import _find_common_core +from konnektor.network_planners import (StarNetworkGenerator, + MaximalNetworkGenerator, + RedundantMinimalSpanningTreeNetworkGenerator, + MinimalSpanningTreeNetworkGenerator) def _hasten_lomap(mapper, ligands): @@ -39,9 +43,10 @@ def _hasten_lomap(mapper, ligands): def generate_radial_network( ligands: Iterable[SmallMoleculeComponent], - central_ligand: Union[SmallMoleculeComponent, str, int], - mappers: Union[AtomMapper, Iterable[AtomMapper]], + mapper: AtomMapper, + central_ligand: Union[SmallMoleculeComponent, str, int] = None, scorer: Optional[Callable[[LigandAtomMapping], float]] = None, + n_processes: int = 1, ) -> LigandNetwork: """ Plan a radial network with all ligands connected to a central node. @@ -80,20 +85,25 @@ def generate_radial_network( If no scorer is supplied, the first mapping provided by the iterable of mappers will be used. """ - if isinstance(mappers, AtomMapper): - mappers = [mappers] - mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) - else m for m in mappers] + if isinstance(mapper, LomapAtomMapper): + mapper = _hasten_lomap(mapper, ligands) + nodes = list(ligands) + + # Construct network + network_planner = StarNetworkGenerator(mapper=mapper, + scorer=scorer, + progress = progress, + n_processes = n_processes, + ) - # handle central_ligand arg possibilities - # after this, central_ligand is resolved to a SmallMoleculeComponent if isinstance(central_ligand, int): ligands = list(ligands) try: central_ligand = ligands[central_ligand] except IndexError: - raise ValueError(f"index '{central_ligand}' out of bounds, there are " - f"{len(ligands)} ligands") + raise ValueError( + f"index '{central_ligand}' out of bounds, there are " + f"{len(ligands)} ligands") elif isinstance(central_ligand, str): ligands = list(ligands) possibles = [l for l in ligands if l.name == central_ligand] @@ -104,45 +114,17 @@ def generate_radial_network( raise ValueError(f"Multiple ligands called '{central_ligand}'") central_ligand = possibles[0] - edges = [] - - for ligand in ligands: - if ligand == central_ligand: - wmsg = (f"The central_ligand {ligand.name} was also found in " - "the list of ligands to arrange around the " - "central_ligand this will be ignored.") - warnings.warn(wmsg) - continue - best_score = 0.0 - best_mapping = None - - for mapping in itertools.chain.from_iterable( - mapper.suggest_mappings(central_ligand, ligand) - for mapper in mappers - ): - if not scorer: - best_mapping = mapping - break - - score = scorer(mapping) - mapping = mapping.with_annotations({"score": score}) + network = network_planner.generate_ligand_network(nodes, central_component=central_ligand) - if score > best_score: - best_mapping = mapping - best_score = score - - if best_mapping is None: - raise ValueError(f"No mapping found for {ligand}") - edges.append(best_mapping) - - return LigandNetwork(edges) + return network def generate_maximal_network( ligands: Iterable[SmallMoleculeComponent], - mappers: Union[AtomMapper, Iterable[AtomMapper]], + mapper: AtomMapper, scorer: Optional[Callable[[LigandAtomMapping], float]] = None, progress: Union[bool, Callable[[Iterable], Iterable]] = True, + n_processes: int = 1, ) -> LigandNetwork: """ Plan a network with all possible proposed mappings. @@ -160,7 +142,7 @@ def generate_maximal_network( ---------- ligands : Iterable[SmallMoleculeComponent] the ligands to include in the LigandNetwork - mappers : AtomMapper or Iterable[AtomMapper] + mapper : AtomMapper or Iterable[AtomMapper] the AtomMapper(s) to use to propose mappings. At least 1 required, but many can be given. scorer : Scoring function @@ -169,42 +151,31 @@ def generate_maximal_network( progress bar: if False, no progress bar will be shown. If True, use a tqdm progress bar that only appears after 1.5 seconds. You can also provide a custom progress bar wrapper as a callable. + n_processes: int + parallelization of network generation. """ - if isinstance(mappers, AtomMapper): - mappers = [mappers] - mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) - else m for m in mappers] - + if isinstance(mapper, LomapAtomMapper): + mapper = _hasten_lomap(mapper, ligands) nodes = list(ligands) - if progress is True: - # default is a tqdm progress bar - total = len(nodes) * (len(nodes) - 1) // 2 - progress = functools.partial(tqdm, total=total, delay=1.5) - elif progress is False: - def progress(x): return x - # otherwise, it should be a user-defined callable - - mapping_generator = itertools.chain.from_iterable( - mapper.suggest_mappings(molA, molB) - for molA, molB in progress(itertools.combinations(nodes, 2)) - for mapper in mappers + # Construct network + network_planner = MaximalNetworkGenerator(mapper=mapper, + scorer=scorer, + progress = progress, + n_processes = n_processes, ) - if scorer: - mappings = [mapping.with_annotations({'score': scorer(mapping)}) - for mapping in mapping_generator] - else: - mappings = list(mapping_generator) - network = LigandNetwork(mappings, nodes=nodes) + network = network_planner.generate_ligand_network(nodes) + return network def generate_minimal_spanning_network( ligands: Iterable[SmallMoleculeComponent], - mappers: Union[AtomMapper, Iterable[AtomMapper]], + mapper: AtomMapper, scorer: Callable[[LigandAtomMapping], float], progress: Union[bool, Callable[[Iterable], Iterable]] = True, + n_processes: int = 1, ) -> LigandNetwork: """ Plan a network with as few edges as possible with maximum total score @@ -224,39 +195,29 @@ def generate_minimal_spanning_network( tqdm progress bar that only appears after 1.5 seconds. You can also provide a custom progress bar wrapper as a callable. """ - if isinstance(mappers, AtomMapper): - mappers = [mappers] - mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) - else m for m in mappers] - - # First create a network with all the proposed mappings (scored) - network = generate_maximal_network(ligands, mappers, scorer, progress) + if isinstance(mapper, LomapAtomMapper): + mapper = _hasten_lomap(mapper, ligands) + nodes = list(ligands) - # Flip network scores so we can use minimal algorithm - g2 = nx.MultiGraph() - for e1, e2, d in network.graph.edges(data=True): - g2.add_edge(e1, e2, weight=-d['score'], object=d['object']) + # Construct network + network_planner = MinimalSpanningTreeNetworkGenerator(mapper=mapper, + scorer=scorer, + progress = progress, + n_processes = n_processes, + ) - # Next analyze that network to create minimal spanning network. Because - # we carry the original (directed) LigandAtomMapping, we don't lose - # direction information when converting to an undirected graph. - min_edges = nx.minimum_spanning_edges(g2) - min_mappings = [edge_data['object'] for _, _, _, edge_data in min_edges] - min_network = LigandNetwork(min_mappings) - missing_nodes = set(network.nodes) - set(min_network.nodes) - if missing_nodes: - raise RuntimeError("Unable to create edges to some nodes: " - f"{list(missing_nodes)}") + network = network_planner.generate_ligand_network(nodes) - return min_network + return network def generate_minimal_redundant_network( ligands: Iterable[SmallMoleculeComponent], - mappers: Union[AtomMapper, Iterable[AtomMapper]], + mapper: AtomMapper, scorer: Callable[[LigandAtomMapping], float], progress: Union[bool, Callable[[Iterable], Iterable]] = True, mst_num: int = 2, + n_processes: int = 1, ) -> LigandNetwork: """ Plan a network with a specified amount of redundancy for each node @@ -283,40 +244,25 @@ def generate_minimal_redundant_network( Minimum Spanning Tree number: the number of minimum spanning trees to generate. If two, the second-best edges are included in the returned network. If three, the third-best edges are also included, etc. + n_processes: int + """ - if isinstance(mappers, AtomMapper): - mappers = [mappers] - mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) - else m for m in mappers] - - # First create a network with all the proposed mappings (scored) - network = generate_maximal_network(ligands, mappers, scorer, progress) - - # Flip network scores so we can use minimal algorithm - g2 = nx.MultiGraph() - for e1, e2, d in network.graph.edges(data=True): - g2.add_edge(e1, e2, weight=-d['score'], object=d['object']) - - # As in .generate_minimal_spanning_network(), use nx to get the minimal - # network. But now also remove those edges from the fully-connected - # network, then get the minimal network again. Add mappings from all - # minimal networks together. - mappings = [] - for _ in range(mst_num): # can increase range here for more redundancy - # get list from generator so that we don't adjust network by calling it: - current_best_edges = list(nx.minimum_spanning_edges(g2)) - - g2.remove_edges_from(current_best_edges) - for _, _, _, edge_data in current_best_edges: - mappings.append(edge_data['object']) - - redund_network = LigandNetwork(mappings) - missing_nodes = set(network.nodes) - set(redund_network.nodes) - if missing_nodes: - raise RuntimeError("Unable to create edges to some nodes: " - f"{list(missing_nodes)}") - - return redund_network + + if isinstance(mapper, LomapAtomMapper): + mapper = _hasten_lomap(mapper, ligands) + nodes = list(ligands) + + # Construct network + network_planner = RedundantMinimalSpanningTreeNetworkGenerator(mapper=mapper, + scorer=scorer, + progress = progress, + n_redundancy=mst_num, + n_processes = n_processes, + ) + + network = network_planner.generate_ligand_network(nodes) + + return network def generate_network_from_names( diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 2e62c8be9..a04381935 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -220,7 +220,7 @@ def scoring_func(mapping): network = openfe.setup.ligand_network_planning.generate_maximal_network( ligands=others + [toluene], - mappers=mappers, + mapper=mappers, scorer=scorer, progress=with_progress, ) From 3c5c8a0ea5e37c89de822464d54fd6efd6821294 Mon Sep 17 00:00:00 2001 From: riesben Date: Tue, 3 Sep 2024 08:46:15 +0200 Subject: [PATCH 02/56] draft for Konnektor integration --- openfe/setup/ligand_network_planning.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 112112255..2f455727e 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -89,13 +89,7 @@ def generate_radial_network( mapper = _hasten_lomap(mapper, ligands) nodes = list(ligands) - # Construct network - network_planner = StarNetworkGenerator(mapper=mapper, - scorer=scorer, - progress = progress, - n_processes = n_processes, - ) - + # Get central Component if isinstance(central_ligand, int): ligands = list(ligands) try: @@ -114,6 +108,14 @@ def generate_radial_network( raise ValueError(f"Multiple ligands called '{central_ligand}'") central_ligand = possibles[0] + + # Construct network + network_planner = StarNetworkGenerator(mapper=mapper, + scorer=scorer, + progress = progress, + n_processes = n_processes, + ) + network = network_planner.generate_ligand_network(nodes, central_component=central_ligand) return network From 2eb3c848e7a8deb71eb691601ab55ea51d1e21f5 Mon Sep 17 00:00:00 2001 From: riesben Date: Wed, 4 Sep 2024 09:38:44 +0200 Subject: [PATCH 03/56] changing back to mappers iterable as parameter --- openfe/setup/ligand_network_planning.py | 97 +++++++++++-------------- 1 file changed, 41 insertions(+), 56 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 2f455727e..31948e2b4 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -20,7 +20,8 @@ from konnektor.network_planners import (StarNetworkGenerator, MaximalNetworkGenerator, RedundantMinimalSpanningTreeNetworkGenerator, - MinimalSpanningTreeNetworkGenerator) + MinimalSpanningTreeNetworkGenerator, + ExplicitNetworkGenerator,) def _hasten_lomap(mapper, ligands): @@ -43,10 +44,11 @@ def _hasten_lomap(mapper, ligands): def generate_radial_network( ligands: Iterable[SmallMoleculeComponent], - mapper: AtomMapper, + mappers: Union[AtomMapper, Iterable[AtomMapper]], central_ligand: Union[SmallMoleculeComponent, str, int] = None, scorer: Optional[Callable[[LigandAtomMapping], float]] = None, n_processes: int = 1, + progress: bool = False, ) -> LigandNetwork: """ Plan a radial network with all ligands connected to a central node. @@ -85,11 +87,13 @@ def generate_radial_network( If no scorer is supplied, the first mapping provided by the iterable of mappers will be used. """ - if isinstance(mapper, LomapAtomMapper): - mapper = _hasten_lomap(mapper, ligands) - nodes = list(ligands) + if isinstance(mappers, AtomMapper): + mappers = [mappers] + mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) + else m for m in mappers] - # Get central Component + # handle central_ligand arg possibilities + # after this, central_ligand is resolved to a SmallMoleculeComponent if isinstance(central_ligand, int): ligands = list(ligands) try: @@ -110,20 +114,20 @@ def generate_radial_network( # Construct network - network_planner = StarNetworkGenerator(mapper=mapper, + network_planner = StarNetworkGenerator(mappers=mappers, scorer=scorer, progress = progress, n_processes = n_processes, ) - network = network_planner.generate_ligand_network(nodes, central_component=central_ligand) + network = network_planner.generate_ligand_network(components=ligands, central_component=central_ligand) return network def generate_maximal_network( ligands: Iterable[SmallMoleculeComponent], - mapper: AtomMapper, + mappers: Union[AtomMapper, Iterable[AtomMapper]], scorer: Optional[Callable[[LigandAtomMapping], float]] = None, progress: Union[bool, Callable[[Iterable], Iterable]] = True, n_processes: int = 1, @@ -156,12 +160,14 @@ def generate_maximal_network( n_processes: int parallelization of network generation. """ - if isinstance(mapper, LomapAtomMapper): - mapper = _hasten_lomap(mapper, ligands) + if isinstance(mappers, AtomMapper): + mappers = [mappers] + mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) + else m for m in mappers] nodes = list(ligands) # Construct network - network_planner = MaximalNetworkGenerator(mapper=mapper, + network_planner = MaximalNetworkGenerator(mappers=mappers, scorer=scorer, progress = progress, n_processes = n_processes, @@ -174,7 +180,7 @@ def generate_maximal_network( def generate_minimal_spanning_network( ligands: Iterable[SmallMoleculeComponent], - mapper: AtomMapper, + mappers: Union[AtomMapper, Iterable[AtomMapper]], scorer: Callable[[LigandAtomMapping], float], progress: Union[bool, Callable[[Iterable], Iterable]] = True, n_processes: int = 1, @@ -197,12 +203,14 @@ def generate_minimal_spanning_network( tqdm progress bar that only appears after 1.5 seconds. You can also provide a custom progress bar wrapper as a callable. """ - if isinstance(mapper, LomapAtomMapper): - mapper = _hasten_lomap(mapper, ligands) + if isinstance(mappers, AtomMapper): + mappers = [mappers] + mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) + else m for m in mappers] nodes = list(ligands) # Construct network - network_planner = MinimalSpanningTreeNetworkGenerator(mapper=mapper, + network_planner = MinimalSpanningTreeNetworkGenerator(mappers=mappers, scorer=scorer, progress = progress, n_processes = n_processes, @@ -215,7 +223,7 @@ def generate_minimal_spanning_network( def generate_minimal_redundant_network( ligands: Iterable[SmallMoleculeComponent], - mapper: AtomMapper, + mappers: Union[AtomMapper, Iterable[AtomMapper]], scorer: Callable[[LigandAtomMapping], float], progress: Union[bool, Callable[[Iterable], Iterable]] = True, mst_num: int = 2, @@ -249,13 +257,14 @@ def generate_minimal_redundant_network( n_processes: int """ - - if isinstance(mapper, LomapAtomMapper): - mapper = _hasten_lomap(mapper, ligands) + if isinstance(mappers, AtomMapper): + mappers = [mappers] + mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) + else m for m in mappers] nodes = list(ligands) # Construct network - network_planner = RedundantMinimalSpanningTreeNetworkGenerator(mapper=mapper, + network_planner = RedundantMinimalSpanningTreeNetworkGenerator(mappers=mappers, scorer=scorer, progress = progress, n_redundancy=mst_num, @@ -269,7 +278,7 @@ def generate_minimal_redundant_network( def generate_network_from_names( ligands: list[SmallMoleculeComponent], - mapper: AtomMapper, + mappers: Union[AtomMapper, Iterable[AtomMapper]], names: list[tuple[str, str]], ) -> LigandNetwork: """ @@ -298,28 +307,18 @@ def generate_network_from_names( if multiple molecules have the same name (this would otherwise be problematic) """ - nm2idx = {l.name: i for i, l in enumerate(ligands)} + nodes = list(ligands) - if len(nm2idx) < len(ligands): - dupes = Counter((l.name for l in ligands)) - dupe_names = [k for k, v in dupes.items() if v > 1] - raise ValueError(f"Duplicate names: {dupe_names}") + network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) - try: - ids = [(nm2idx[nm1], nm2idx[nm2]) for nm1, nm2 in names] - except KeyError: - badnames = [nm for nm in itertools.chain.from_iterable(names) - if nm not in nm2idx] - available = [ligand.name for ligand in ligands] - raise KeyError(f"Invalid name(s) requested {badnames}. " - f"Available: {available}") + network = network_planner.generate_network_from_names(ligands=nodes, names=names) - return generate_network_from_indices(ligands, mapper, ids) + return network def generate_network_from_indices( ligands: list[SmallMoleculeComponent], - mapper: AtomMapper, + mappers: Union[AtomMapper, Iterable[AtomMapper]], indices: list[tuple[int, int]], ) -> LigandNetwork: """ @@ -345,30 +344,16 @@ def generate_network_from_indices( IndexError if an invalid ligand index is requested """ - edges = [] - - for i, j in indices: - try: - m1, m2 = ligands[i], ligands[j] - except IndexError: - raise IndexError(f"Invalid ligand id, requested {i} {j} " - f"with {len(ligands)} available") - - mapping = next(mapper.suggest_mappings(m1, m2)) - - edges.append(mapping) - - network = LigandNetwork(edges=edges, nodes=ligands) - - if not network.is_connected(): - warnings.warn("Generated network is not fully connected") + nodes = list(ligands) + network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) + network = network_planner.generate_network_from_indices(ligands=nodes, names=indices) return network def load_orion_network( ligands: list[SmallMoleculeComponent], - mapper: AtomMapper, + mappers: Union[AtomMapper, Iterable[AtomMapper]], network_file: Union[str, Path], ) -> LigandNetwork: """Load a :class:`.LigandNetwork` from an Orion NES network file. @@ -410,7 +395,7 @@ def load_orion_network( def load_fepplus_network( ligands: list[SmallMoleculeComponent], - mapper: AtomMapper, + mappers: Union[AtomMapper, Iterable[AtomMapper]], network_file: Union[str, Path], ) -> LigandNetwork: """Load a :class:`.LigandNetwork` from an FEP+ edges network file. From 0e9440dd7e1752f5d4d8b4bcc05054abeea99cde Mon Sep 17 00:00:00 2001 From: riesben Date: Wed, 4 Sep 2024 10:02:04 +0200 Subject: [PATCH 04/56] consistently using Konnektor also in loading fep or orion net --- openfe/setup/ligand_network_planning.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 31948e2b4..e1eabef8b 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -347,7 +347,7 @@ def generate_network_from_indices( nodes = list(ligands) network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) - network = network_planner.generate_network_from_indices(ligands=nodes, names=indices) + network = network_planner.generate_network_from_indices(ligands=nodes, indices=indices) return network @@ -390,7 +390,10 @@ def load_orion_network( names.append((entry[0], entry[2])) - return generate_network_from_names(ligands, mapper, names) + network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) + network = network_planner.generate_network_from_names(ligands=ligands, names=names) + + return network def load_fepplus_network( @@ -432,4 +435,7 @@ def load_fepplus_network( names.append((entry[2], entry[4])) - return generate_network_from_names(ligands, mapper, names) + network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) + network = network_planner.generate_network_from_names(ligands=ligands, + names=names) + return network From a55cfb89fa26bd4f3f4510965f306e2405eac183 Mon Sep 17 00:00:00 2001 From: riesben Date: Wed, 4 Sep 2024 10:35:28 +0200 Subject: [PATCH 05/56] adding handles for Konnektor functionality. --- openfe/setup/ligand_network_planning.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index e1eabef8b..fa5b7b5e0 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -23,6 +23,9 @@ MinimalSpanningTreeNetworkGenerator, ExplicitNetworkGenerator,) +#Konnektor handles: +from konnektor import network_analysis, network_planners, network_tools + def _hasten_lomap(mapper, ligands): """take a mapper and some ligands, put a common core arg into the mapper """ From bc9eb89db468d501814938b75c4fe1d7d6fef924 Mon Sep 17 00:00:00 2001 From: riesben Date: Wed, 4 Sep 2024 10:38:17 +0200 Subject: [PATCH 06/56] used black for formatting. --- openfe/setup/ligand_network_planning.py | 149 ++++++++++++++---------- 1 file changed, 86 insertions(+), 63 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index fa5b7b5e0..359741c9a 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -17,31 +17,37 @@ from lomap import generate_lomap_network, LomapAtomMapper from lomap.dbmol import _find_common_core -from konnektor.network_planners import (StarNetworkGenerator, - MaximalNetworkGenerator, - RedundantMinimalSpanningTreeNetworkGenerator, - MinimalSpanningTreeNetworkGenerator, - ExplicitNetworkGenerator,) - -#Konnektor handles: +from konnektor.network_planners import ( + StarNetworkGenerator, + MaximalNetworkGenerator, + RedundantMinimalSpanningTreeNetworkGenerator, + MinimalSpanningTreeNetworkGenerator, + ExplicitNetworkGenerator, +) + +# Konnektor handles: from konnektor import network_analysis, network_planners, network_tools def _hasten_lomap(mapper, ligands): - """take a mapper and some ligands, put a common core arg into the mapper """ + """take a mapper and some ligands, put a common core arg into the mapper""" if mapper.seed: return mapper try: - core = _find_common_core([m.to_rdkit() for m in ligands], - element_change=mapper.element_change) + core = _find_common_core( + [m.to_rdkit() for m in ligands], element_change=mapper.element_change + ) except RuntimeError: # in case MCS throws a hissy fit core = "" return LomapAtomMapper( - time=mapper.time, threed=mapper.threed, max3d=mapper.max3d, - element_change=mapper.element_change, seed=core, - shift=mapper.shift + time=mapper.time, + threed=mapper.threed, + max3d=mapper.max3d, + element_change=mapper.element_change, + seed=core, + shift=mapper.shift, ) @@ -92,8 +98,10 @@ def generate_radial_network( """ if isinstance(mappers, AtomMapper): mappers = [mappers] - mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) - else m for m in mappers] + mappers = [ + _hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) else m + for m in mappers + ] # handle central_ligand arg possibilities # after this, central_ligand is resolved to a SmallMoleculeComponent @@ -104,26 +112,31 @@ def generate_radial_network( except IndexError: raise ValueError( f"index '{central_ligand}' out of bounds, there are " - f"{len(ligands)} ligands") + f"{len(ligands)} ligands" + ) elif isinstance(central_ligand, str): ligands = list(ligands) possibles = [l for l in ligands if l.name == central_ligand] if not possibles: - raise ValueError(f"No ligand called '{central_ligand}' " - f"available: {', '.join(l.name for l in ligands)}") + raise ValueError( + f"No ligand called '{central_ligand}' " + f"available: {', '.join(l.name for l in ligands)}" + ) if len(possibles) > 1: raise ValueError(f"Multiple ligands called '{central_ligand}'") central_ligand = possibles[0] - # Construct network - network_planner = StarNetworkGenerator(mappers=mappers, + network_planner = StarNetworkGenerator( + mappers=mappers, scorer=scorer, - progress = progress, - n_processes = n_processes, + progress=progress, + n_processes=n_processes, ) - network = network_planner.generate_ligand_network(components=ligands, central_component=central_ligand) + network = network_planner.generate_ligand_network( + components=ligands, central_component=central_ligand + ) return network @@ -165,15 +178,18 @@ def generate_maximal_network( """ if isinstance(mappers, AtomMapper): mappers = [mappers] - mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) - else m for m in mappers] + mappers = [ + _hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) else m + for m in mappers + ] nodes = list(ligands) # Construct network - network_planner = MaximalNetworkGenerator(mappers=mappers, + network_planner = MaximalNetworkGenerator( + mappers=mappers, scorer=scorer, - progress = progress, - n_processes = n_processes, + progress=progress, + n_processes=n_processes, ) network = network_planner.generate_ligand_network(nodes) @@ -208,15 +224,18 @@ def generate_minimal_spanning_network( """ if isinstance(mappers, AtomMapper): mappers = [mappers] - mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) - else m for m in mappers] + mappers = [ + _hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) else m + for m in mappers + ] nodes = list(ligands) # Construct network - network_planner = MinimalSpanningTreeNetworkGenerator(mappers=mappers, + network_planner = MinimalSpanningTreeNetworkGenerator( + mappers=mappers, scorer=scorer, - progress = progress, - n_processes = n_processes, + progress=progress, + n_processes=n_processes, ) network = network_planner.generate_ligand_network(nodes) @@ -262,16 +281,19 @@ def generate_minimal_redundant_network( """ if isinstance(mappers, AtomMapper): mappers = [mappers] - mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) - else m for m in mappers] + mappers = [ + _hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) else m + for m in mappers + ] nodes = list(ligands) # Construct network - network_planner = RedundantMinimalSpanningTreeNetworkGenerator(mappers=mappers, + network_planner = RedundantMinimalSpanningTreeNetworkGenerator( + mappers=mappers, scorer=scorer, - progress = progress, + progress=progress, n_redundancy=mst_num, - n_processes = n_processes, + n_processes=n_processes, ) network = network_planner.generate_ligand_network(nodes) @@ -280,9 +302,9 @@ def generate_minimal_redundant_network( def generate_network_from_names( - ligands: list[SmallMoleculeComponent], - mappers: Union[AtomMapper, Iterable[AtomMapper]], - names: list[tuple[str, str]], + ligands: list[SmallMoleculeComponent], + mappers: Union[AtomMapper, Iterable[AtomMapper]], + names: list[tuple[str, str]], ) -> LigandNetwork: """ Generate a :class:`.LigandNetwork` by specifying edges as tuples of names. @@ -320,9 +342,9 @@ def generate_network_from_names( def generate_network_from_indices( - ligands: list[SmallMoleculeComponent], - mappers: Union[AtomMapper, Iterable[AtomMapper]], - indices: list[tuple[int, int]], + ligands: list[SmallMoleculeComponent], + mappers: Union[AtomMapper, Iterable[AtomMapper]], + indices: list[tuple[int, int]], ) -> LigandNetwork: """ Generate a :class:`.LigandNetwork` by specifying edges as tuples of indices. @@ -350,14 +372,16 @@ def generate_network_from_indices( nodes = list(ligands) network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) - network = network_planner.generate_network_from_indices(ligands=nodes, indices=indices) + network = network_planner.generate_network_from_indices( + ligands=nodes, indices=indices + ) return network def load_orion_network( - ligands: list[SmallMoleculeComponent], - mappers: Union[AtomMapper, Iterable[AtomMapper]], - network_file: Union[str, Path], + ligands: list[SmallMoleculeComponent], + mappers: Union[AtomMapper, Iterable[AtomMapper]], + network_file: Union[str, Path], ) -> LigandNetwork: """Load a :class:`.LigandNetwork` from an Orion NES network file. @@ -380,15 +404,13 @@ def load_orion_network( If an unexpected line format is encountered. """ - with open(network_file, 'r') as f: - network_lines = [l.strip().split(' ') for l in f - if not l.startswith('#')] + with open(network_file, "r") as f: + network_lines = [l.strip().split(" ") for l in f if not l.startswith("#")] names = [] for entry in network_lines: if len(entry) != 3 or entry[1] != ">>": - errmsg = ("line does not match expected name >> name format: " - f"{entry}") + errmsg = "line does not match expected name >> name format: " f"{entry}" raise KeyError(errmsg) names.append((entry[0], entry[2])) @@ -400,9 +422,9 @@ def load_orion_network( def load_fepplus_network( - ligands: list[SmallMoleculeComponent], - mappers: Union[AtomMapper, Iterable[AtomMapper]], - network_file: Union[str, Path], + ligands: list[SmallMoleculeComponent], + mappers: Union[AtomMapper, Iterable[AtomMapper]], + network_file: Union[str, Path], ) -> LigandNetwork: """Load a :class:`.LigandNetwork` from an FEP+ edges network file. @@ -425,20 +447,21 @@ def load_fepplus_network( If an unexpected line format is encountered. """ - with open(network_file, 'r') as f: + with open(network_file, "r") as f: network_lines = [l.split() for l in f.readlines()] names = [] for entry in network_lines: - if len(entry) != 5 or entry[1] != '#' or entry[3] != '->': - errmsg = ("line does not match expected format " - f"hash:hash # name -> name\n" - "line format: {entry}") + if len(entry) != 5 or entry[1] != "#" or entry[3] != "->": + errmsg = ( + "line does not match expected format " + f"hash:hash # name -> name\n" + "line format: {entry}" + ) raise KeyError(errmsg) names.append((entry[2], entry[4])) network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) - network = network_planner.generate_network_from_names(ligands=ligands, - names=names) + network = network_planner.generate_network_from_names(ligands=ligands, names=names) return network From 9a0a85bc2568cb0878cb29ba8bb228cd2368f73e Mon Sep 17 00:00:00 2001 From: riesben Date: Wed, 4 Sep 2024 10:41:23 +0200 Subject: [PATCH 07/56] used black for formatting. --- openfe/setup/ligand_network_planning.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 359741c9a..e9642a745 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -36,7 +36,8 @@ def _hasten_lomap(mapper, ligands): try: core = _find_common_core( - [m.to_rdkit() for m in ligands], element_change=mapper.element_change + [m.to_rdkit() for m in ligands], + element_change=mapper.element_change, ) except RuntimeError: # in case MCS throws a hissy fit core = "" @@ -336,7 +337,9 @@ def generate_network_from_names( network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) - network = network_planner.generate_network_from_names(ligands=nodes, names=names) + network = network_planner.generate_network_from_names( + ligands=nodes, names=names + ) return network @@ -405,18 +408,24 @@ def load_orion_network( """ with open(network_file, "r") as f: - network_lines = [l.strip().split(" ") for l in f if not l.startswith("#")] + network_lines = [ + line.strip().split(" ") for line in f if not line.startswith("#") + ] names = [] for entry in network_lines: if len(entry) != 3 or entry[1] != ">>": - errmsg = "line does not match expected name >> name format: " f"{entry}" + errmsg = ( + "line does not match expected name >> name format: " f"{entry}" + ) raise KeyError(errmsg) names.append((entry[0], entry[2])) network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) - network = network_planner.generate_network_from_names(ligands=ligands, names=names) + network = network_planner.generate_network_from_names( + ligands=ligands, names=names + ) return network @@ -463,5 +472,7 @@ def load_fepplus_network( names.append((entry[2], entry[4])) network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) - network = network_planner.generate_network_from_names(ligands=ligands, names=names) + network = network_planner.generate_network_from_names( + ligands=ligands, names=names + ) return network From e626408fb03cc7bb1d7438ed8c1836f0fa7ae8ec Mon Sep 17 00:00:00 2001 From: Benjamin Ries Date: Thu, 17 Oct 2024 13:50:44 +0200 Subject: [PATCH 08/56] Update test_network_planning.py fix for tests --- openfe/tests/setup/test_network_planning.py | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index a04381935..92e5a0f6c 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -220,7 +220,7 @@ def scoring_func(mapping): network = openfe.setup.ligand_network_planning.generate_maximal_network( ligands=others + [toluene], - mapper=mappers, + mappers=mappers, scorer=scorer, progress=with_progress, ) @@ -484,7 +484,7 @@ def test_network_from_names(atom_mapping_basic_test_files, lomap_old_mapper): network = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) assert len(network.nodes) == len(ligs) @@ -508,7 +508,7 @@ def test_network_from_names_bad_name( _ = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) @@ -527,7 +527,7 @@ def test_network_from_names_duplicate_name( _ = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) @@ -541,7 +541,7 @@ def test_network_from_indices( network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) assert len(network.nodes) == len(ligs) @@ -566,7 +566,7 @@ def test_network_from_indices_indexerror( network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) @@ -580,7 +580,7 @@ def test_network_from_indices_disconnected_warning( _ = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) @@ -597,7 +597,7 @@ def test_network_from_external(file_fixture, loader, request, network = loader( ligands=[l for l in benzene_modifications.values()], - mapper=openfe.LomapAtomMapper(), + mappers=openfe.LomapAtomMapper(), network_file=network_file, ) @@ -633,7 +633,7 @@ def test_network_from_external_unknown_edge(file_fixture, loader, request, with pytest.raises(KeyError, match="Invalid name"): network = loader( ligands=ligs, - mapper=openfe.LomapAtomMapper(), + mappers=openfe.LomapAtomMapper(), network_file=network_file, ) @@ -658,7 +658,7 @@ def test_bad_orion_network(benzene_modifications, tmpdir): with pytest.raises(KeyError, match="line does not match"): network = openfe.setup.ligand_network_planning.load_orion_network( ligands=[l for l in benzene_modifications.values()], - mapper=openfe.LomapAtomMapper(), + mappers=openfe.LomapAtomMapper(), network_file='bad_orion_net.dat', ) @@ -681,6 +681,6 @@ def test_bad_edges_network(benzene_modifications, tmpdir): with pytest.raises(KeyError, match="line does not match"): network = openfe.setup.ligand_network_planning.load_fepplus_network( ligands=[l for l in benzene_modifications.values()], - mapper=openfe.LomapAtomMapper(), + mappers=openfe.LomapAtomMapper(), network_file='bad_edges.edges', ) From e1c117028b8b53bcb938d82784c00619e59ab89e Mon Sep 17 00:00:00 2001 From: RiesBen Date: Tue, 22 Oct 2024 16:13:36 +0200 Subject: [PATCH 09/56] fixes for unittests: - fix removing the central ligand from list. --- openfe/setup/ligand_network_planning.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index e9642a745..fcb1f6f21 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -110,6 +110,7 @@ def generate_radial_network( ligands = list(ligands) try: central_ligand = ligands[central_ligand] + ligands.remove(central_ligand) except IndexError: raise ValueError( f"index '{central_ligand}' out of bounds, there are " @@ -126,6 +127,7 @@ def generate_radial_network( if len(possibles) > 1: raise ValueError(f"Multiple ligands called '{central_ligand}'") central_ligand = possibles[0] + ligands.remove(central_ligand) # Construct network network_planner = StarNetworkGenerator( From 730718ac0a3ac6fdc6df099adb4329fa1328780a Mon Sep 17 00:00:00 2001 From: Benjamin Ries Date: Tue, 29 Oct 2024 23:38:54 +0100 Subject: [PATCH 10/56] Update test_network_planning.py remove test for testing if a central component is part of the other component list. This can not happen anymore with the current Konnektor implementation, therefore I propose removing the test. --- openfe/tests/setup/test_network_planning.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 92e5a0f6c..a629f1696 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -130,21 +130,7 @@ def test_radial_network_index_error(toluene_vs_others, lomap_old_mapper): ligands=ligands, central_ligand=2077, mappers=lomap_old_mapper, scorer=None, ) - - -def test_radial_network_self_central(toluene_vs_others, lomap_old_mapper): - # issue #544, include the central ligand in "ligands", - # shouldn't get self edge - ligs = [toluene_vs_others[0]] + toluene_vs_others[1] - - with pytest.warns(UserWarning, match="The central_ligand"): - network = openfe.setup.ligand_network_planning.generate_radial_network( - ligands=ligs, central_ligand=ligs[0], - mappers=lomap_old_mapper, scorer=None - ) - - assert len(network.edges) == len(ligs) - 1 - + def test_radial_network_with_scorer(toluene_vs_others, lomap_old_mapper): toluene, others = toluene_vs_others From a338d1767aed2e8a30a233fd7e4a7c2b6ab7d023 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 6 Nov 2024 10:36:13 -0800 Subject: [PATCH 11/56] updating docstrings --- openfe/setup/ligand_network_planning.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index fcb1f6f21..c0b56c7ac 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -24,8 +24,6 @@ MinimalSpanningTreeNetworkGenerator, ExplicitNetworkGenerator, ) - -# Konnektor handles: from konnektor import network_analysis, network_planners, network_tools @@ -71,16 +69,22 @@ def generate_radial_network( ligands : iterable of SmallMoleculeComponents the ligands to arrange around the central ligand. If the central ligand is present it will be ignored (i.e. avoiding a self edge) + mappers : AtomMapper or iterable of AtomMappers + mapper(s) to use, at least 1 required central_ligand : SmallMoleculeComponent or str or int the ligand to use as the hub/central ligand. If this is a string, this should match to one and only one ligand name. If this is an integer, this refers to the index from within ligands - mappers : AtomMapper or iterable of AtomMappers - mapper(s) to use, at least 1 required scorer : scoring function, optional a callable which returns a float for any LigandAtomMapping. Used to assign scores to potential mappings; higher scores indicate better mappings. + progress : Union[bool, Callable[Iterable], Iterable] + progress bar: if False, no progress bar will be shown. If True, use a + tqdm progress bar that only appears after 1.5 seconds. You can also + provide a custom progress bar wrapper as a callable. + n_processes: int + number of threads to use if parallelizing network generation. Raises ------ @@ -177,7 +181,7 @@ def generate_maximal_network( tqdm progress bar that only appears after 1.5 seconds. You can also provide a custom progress bar wrapper as a callable. n_processes: int - parallelization of network generation. + number of threads to use if parallelizing network generation. """ if isinstance(mappers, AtomMapper): mappers = [mappers] @@ -224,6 +228,8 @@ def generate_minimal_spanning_network( progress bar: if False, no progress bar will be shown. If True, use a tqdm progress bar that only appears after 1.5 seconds. You can also provide a custom progress bar wrapper as a callable. + n_processes: int + number of threads to use if parallelizing network generation. """ if isinstance(mappers, AtomMapper): mappers = [mappers] @@ -275,11 +281,12 @@ def generate_minimal_redundant_network( progress bar: if False, no progress bar will be shown. If True, use a tqdm progress bar that only appears after 1.5 seconds. You can also provide a custom progress bar wrapper as a callable. - mst_num: int + mst_num : int Minimum Spanning Tree number: the number of minimum spanning trees to generate. If two, the second-best edges are included in the returned network. If three, the third-best edges are also included, etc. n_processes: int + number of threads to use if parallelizing network generation """ if isinstance(mappers, AtomMapper): From b334007db68729c83c4727ff542e4329c9936411 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 6 Nov 2024 10:37:59 -0800 Subject: [PATCH 12/56] adjusting n_processes docstring language --- openfe/setup/ligand_network_planning.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index c0b56c7ac..5689a38c7 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -84,7 +84,7 @@ def generate_radial_network( tqdm progress bar that only appears after 1.5 seconds. You can also provide a custom progress bar wrapper as a callable. n_processes: int - number of threads to use if parallelizing network generation. + number of cpu processes to use if parallelizing network generation. Raises ------ @@ -181,7 +181,7 @@ def generate_maximal_network( tqdm progress bar that only appears after 1.5 seconds. You can also provide a custom progress bar wrapper as a callable. n_processes: int - number of threads to use if parallelizing network generation. + number of cpu processes to use if parallelizing network generation. """ if isinstance(mappers, AtomMapper): mappers = [mappers] @@ -229,7 +229,7 @@ def generate_minimal_spanning_network( tqdm progress bar that only appears after 1.5 seconds. You can also provide a custom progress bar wrapper as a callable. n_processes: int - number of threads to use if parallelizing network generation. + number of cpu processes to use if parallelizing network generation. """ if isinstance(mappers, AtomMapper): mappers = [mappers] From 1de080941bf598524c78a8dc9a6c4a5409a4b2e6 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 6 Nov 2024 10:58:55 -0800 Subject: [PATCH 13/56] fixing central_ligand default --- openfe/setup/ligand_network_planning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 5689a38c7..8835c9735 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -53,7 +53,7 @@ def _hasten_lomap(mapper, ligands): def generate_radial_network( ligands: Iterable[SmallMoleculeComponent], mappers: Union[AtomMapper, Iterable[AtomMapper]], - central_ligand: Union[SmallMoleculeComponent, str, int] = None, + central_ligand: Union[SmallMoleculeComponent, str, int, None], scorer: Optional[Callable[[LigandAtomMapping], float]] = None, n_processes: int = 1, progress: bool = False, From b85b4f39cd6f458646f220ea69428b3968979f62 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 6 Nov 2024 10:59:28 -0800 Subject: [PATCH 14/56] rename mappers to mapper to avoid api break --- openfe/setup/ligand_network_planning.py | 23 +++++----- openfe/tests/setup/test_network_planning.py | 48 ++++++++++----------- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 8835c9735..18f7334f7 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -70,7 +70,7 @@ def generate_radial_network( the ligands to arrange around the central ligand. If the central ligand is present it will be ignored (i.e. avoiding a self edge) mappers : AtomMapper or iterable of AtomMappers - mapper(s) to use, at least 1 required + mapper(s) to use to construct edges, at least 1 required central_ligand : SmallMoleculeComponent or str or int the ligand to use as the hub/central ligand. If this is a string, this should match to one and only one ligand name. @@ -171,9 +171,8 @@ def generate_maximal_network( ---------- ligands : Iterable[SmallMoleculeComponent] the ligands to include in the LigandNetwork - mapper : AtomMapper or Iterable[AtomMapper] - the AtomMapper(s) to use to propose mappings. At least 1 required, - but many can be given. + mappers : AtomMapper or iterable of AtomMappers + mapper(s) to use to construct edges, at least 1 required scorer : Scoring function any callable which takes a LigandAtomMapping and returns a float progress : Union[bool, Callable[Iterable], Iterable] @@ -323,8 +322,8 @@ def generate_network_from_names( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mapper: AtomMapper - the atom mapper to use to construct edges + mappers : AtomMapper or iterable of AtomMappers + mapper(s) to use, at least 1 required names : list of tuples of names the edges to form where the values refer to names of the small molecules, eg `[('benzene', 'toluene'), ...]` will create an edge between the @@ -365,8 +364,8 @@ def generate_network_from_indices( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mapper: AtomMapper - the atom mapper to use to construct edges + mappers : AtomMapper or iterable of AtomMappers + mapper(s) to use to construct edges, at least 1 required indices : list of tuples of indices the edges to form where the values refer to names of the small molecules, eg `[(3, 4), ...]` will create an edge between the 3rd and 4th molecules @@ -401,8 +400,8 @@ def load_orion_network( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mapper: AtomMapper - the atom mapper to use to construct edges + mappers : AtomMapper or iterable of AtomMappers + mapper(s) to use to construct edges, at least 1 required network_file : str path to NES network file. @@ -450,8 +449,8 @@ def load_fepplus_network( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mapper: AtomMapper - the atom mapper to use to construct edges + mappers : AtomMapper or iterable of AtomMappers + mapper(s) to use to construct edges, at least 1 required network_file : str path to edges network file. diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index a629f1696..b305c1ff0 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -62,7 +62,7 @@ def test_radial_network( network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=others, central_ligand=toluene, - mappers=mapper, scorer=None, + mapper=mapper, scorer=None, ) # couple sanity checks assert len(network.nodes) == len(atom_mapping_basic_test_files) @@ -84,7 +84,7 @@ def test_radial_network_int_str(atom_mapping_basic_test_files, toluene_vs_others network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=ligands, central_ligand=central_ligand_arg, - mappers=lomap_old_mapper, scorer=None, + mapper=lomap_old_mapper, scorer=None, ) assert len(network.nodes) == len(ligands) assert len(network.edges) == len(others) @@ -104,7 +104,7 @@ def test_radial_network_bad_str(toluene_vs_others, lomap_old_mapper): with pytest.raises(ValueError, match='No ligand called'): network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=ligands, central_ligand='unobtainium', - mappers=lomap_old_mapper, scorer=None, + mapper=lomap_old_mapper, scorer=None, ) @@ -116,7 +116,7 @@ def test_radial_network_multiple_str(toluene_vs_others, lomap_old_mapper): with pytest.raises(ValueError, match='Multiple ligands called'): network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=ligands, central_ligand='toluene', - mappers=lomap_old_mapper, scorer=None, + mapper=lomap_old_mapper, scorer=None, ) @@ -128,7 +128,7 @@ def test_radial_network_index_error(toluene_vs_others, lomap_old_mapper): with pytest.raises(ValueError, match='out of bounds'): network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=ligands, central_ligand=2077, - mappers=lomap_old_mapper, scorer=None, + mapper=lomap_old_mapper, scorer=None, ) @@ -141,7 +141,7 @@ def scorer(mapping): network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=others, central_ligand=toluene, - mappers=[BadMapper(), lomap_old_mapper], + mapper=[BadMapper(), lomap_old_mapper], scorer=scorer ) assert len(network.edges) == len(others) @@ -163,7 +163,7 @@ def test_radial_network_multiple_mappers_no_scorer(toluene_vs_others, network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=others, central_ligand=toluene, - mappers=mappers, + mapper=mappers, ) assert len(network.edges) == len(others) @@ -178,7 +178,7 @@ def test_radial_network_failure(atom_mapping_basic_test_files, lomap_old_mapper) network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=[nigel], central_ligand=atom_mapping_basic_test_files['toluene'], - mappers=[lomap_old_mapper], + mapper=[lomap_old_mapper], scorer=None ) @@ -206,7 +206,7 @@ def scoring_func(mapping): network = openfe.setup.ligand_network_planning.generate_maximal_network( ligands=others + [toluene], - mappers=mappers, + mapper=mappers, scorer=scorer, progress=with_progress, ) @@ -247,7 +247,7 @@ def scorer(mapping): network = openfe.ligand_network_planning.generate_minimal_spanning_network( ligands=ligands, - mappers=mappers, + mapper=mappers, scorer=scorer, ) @@ -277,7 +277,7 @@ def scorer(mapping): network = openfe.setup.ligand_network_planning.generate_minimal_spanning_network( ligands=others + [toluene], - mappers=mappers, + mapper=mappers, scorer=scorer ) return network @@ -333,7 +333,7 @@ def scorer(mapping): with pytest.raises(RuntimeError, match="Unable to create edges"): network = openfe.setup.ligand_network_planning.generate_minimal_spanning_network( ligands=others + [toluene, nimrod], - mappers=[lomap_old_mapper], + mapper=[lomap_old_mapper], scorer=scorer ) @@ -368,7 +368,7 @@ def scorer(mapping): network = openfe.setup.ligand_network_planning.generate_minimal_redundant_network( ligands=others + [toluene], - mappers=mappers, + mapper=mappers, scorer=scorer, mst_num=2 ) @@ -454,7 +454,7 @@ def scorer(mapping): with pytest.raises(RuntimeError, match="Unable to create edges"): network = openfe.setup.ligand_network_planning.generate_minimal_redundant_network( ligands=others + [toluene, nimrod], - mappers=[lomap_old_mapper], + mapper=[lomap_old_mapper], scorer=scorer ) @@ -470,7 +470,7 @@ def test_network_from_names(atom_mapping_basic_test_files, lomap_old_mapper): network = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) assert len(network.nodes) == len(ligs) @@ -494,7 +494,7 @@ def test_network_from_names_bad_name( _ = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) @@ -513,7 +513,7 @@ def test_network_from_names_duplicate_name( _ = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) @@ -527,7 +527,7 @@ def test_network_from_indices( network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) assert len(network.nodes) == len(ligs) @@ -552,7 +552,7 @@ def test_network_from_indices_indexerror( network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) @@ -566,7 +566,7 @@ def test_network_from_indices_disconnected_warning( _ = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) @@ -583,7 +583,7 @@ def test_network_from_external(file_fixture, loader, request, network = loader( ligands=[l for l in benzene_modifications.values()], - mappers=openfe.LomapAtomMapper(), + mapper=openfe.LomapAtomMapper(), network_file=network_file, ) @@ -619,7 +619,7 @@ def test_network_from_external_unknown_edge(file_fixture, loader, request, with pytest.raises(KeyError, match="Invalid name"): network = loader( ligands=ligs, - mappers=openfe.LomapAtomMapper(), + mapper=openfe.LomapAtomMapper(), network_file=network_file, ) @@ -644,7 +644,7 @@ def test_bad_orion_network(benzene_modifications, tmpdir): with pytest.raises(KeyError, match="line does not match"): network = openfe.setup.ligand_network_planning.load_orion_network( ligands=[l for l in benzene_modifications.values()], - mappers=openfe.LomapAtomMapper(), + mapper=openfe.LomapAtomMapper(), network_file='bad_orion_net.dat', ) @@ -667,6 +667,6 @@ def test_bad_edges_network(benzene_modifications, tmpdir): with pytest.raises(KeyError, match="line does not match"): network = openfe.setup.ligand_network_planning.load_fepplus_network( ligands=[l for l in benzene_modifications.values()], - mappers=openfe.LomapAtomMapper(), + mapper=openfe.LomapAtomMapper(), network_file='bad_edges.edges', ) From 16781f8d3a50bbb2e01f1d49ad4fb56bedfbd35e Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 6 Nov 2024 11:04:44 -0800 Subject: [PATCH 15/56] Revert "rename mappers to mapper to avoid api break" This reverts commit bf82085d7a86be63d2eaca11e105261107229ecc. --- openfe/setup/ligand_network_planning.py | 23 +++++----- openfe/tests/setup/test_network_planning.py | 48 ++++++++++----------- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 18f7334f7..8835c9735 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -70,7 +70,7 @@ def generate_radial_network( the ligands to arrange around the central ligand. If the central ligand is present it will be ignored (i.e. avoiding a self edge) mappers : AtomMapper or iterable of AtomMappers - mapper(s) to use to construct edges, at least 1 required + mapper(s) to use, at least 1 required central_ligand : SmallMoleculeComponent or str or int the ligand to use as the hub/central ligand. If this is a string, this should match to one and only one ligand name. @@ -171,8 +171,9 @@ def generate_maximal_network( ---------- ligands : Iterable[SmallMoleculeComponent] the ligands to include in the LigandNetwork - mappers : AtomMapper or iterable of AtomMappers - mapper(s) to use to construct edges, at least 1 required + mapper : AtomMapper or Iterable[AtomMapper] + the AtomMapper(s) to use to propose mappings. At least 1 required, + but many can be given. scorer : Scoring function any callable which takes a LigandAtomMapping and returns a float progress : Union[bool, Callable[Iterable], Iterable] @@ -322,8 +323,8 @@ def generate_network_from_names( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mappers : AtomMapper or iterable of AtomMappers - mapper(s) to use, at least 1 required + mapper: AtomMapper + the atom mapper to use to construct edges names : list of tuples of names the edges to form where the values refer to names of the small molecules, eg `[('benzene', 'toluene'), ...]` will create an edge between the @@ -364,8 +365,8 @@ def generate_network_from_indices( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mappers : AtomMapper or iterable of AtomMappers - mapper(s) to use to construct edges, at least 1 required + mapper: AtomMapper + the atom mapper to use to construct edges indices : list of tuples of indices the edges to form where the values refer to names of the small molecules, eg `[(3, 4), ...]` will create an edge between the 3rd and 4th molecules @@ -400,8 +401,8 @@ def load_orion_network( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mappers : AtomMapper or iterable of AtomMappers - mapper(s) to use to construct edges, at least 1 required + mapper: AtomMapper + the atom mapper to use to construct edges network_file : str path to NES network file. @@ -449,8 +450,8 @@ def load_fepplus_network( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mappers : AtomMapper or iterable of AtomMappers - mapper(s) to use to construct edges, at least 1 required + mapper: AtomMapper + the atom mapper to use to construct edges network_file : str path to edges network file. diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index b305c1ff0..a629f1696 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -62,7 +62,7 @@ def test_radial_network( network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=others, central_ligand=toluene, - mapper=mapper, scorer=None, + mappers=mapper, scorer=None, ) # couple sanity checks assert len(network.nodes) == len(atom_mapping_basic_test_files) @@ -84,7 +84,7 @@ def test_radial_network_int_str(atom_mapping_basic_test_files, toluene_vs_others network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=ligands, central_ligand=central_ligand_arg, - mapper=lomap_old_mapper, scorer=None, + mappers=lomap_old_mapper, scorer=None, ) assert len(network.nodes) == len(ligands) assert len(network.edges) == len(others) @@ -104,7 +104,7 @@ def test_radial_network_bad_str(toluene_vs_others, lomap_old_mapper): with pytest.raises(ValueError, match='No ligand called'): network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=ligands, central_ligand='unobtainium', - mapper=lomap_old_mapper, scorer=None, + mappers=lomap_old_mapper, scorer=None, ) @@ -116,7 +116,7 @@ def test_radial_network_multiple_str(toluene_vs_others, lomap_old_mapper): with pytest.raises(ValueError, match='Multiple ligands called'): network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=ligands, central_ligand='toluene', - mapper=lomap_old_mapper, scorer=None, + mappers=lomap_old_mapper, scorer=None, ) @@ -128,7 +128,7 @@ def test_radial_network_index_error(toluene_vs_others, lomap_old_mapper): with pytest.raises(ValueError, match='out of bounds'): network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=ligands, central_ligand=2077, - mapper=lomap_old_mapper, scorer=None, + mappers=lomap_old_mapper, scorer=None, ) @@ -141,7 +141,7 @@ def scorer(mapping): network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=others, central_ligand=toluene, - mapper=[BadMapper(), lomap_old_mapper], + mappers=[BadMapper(), lomap_old_mapper], scorer=scorer ) assert len(network.edges) == len(others) @@ -163,7 +163,7 @@ def test_radial_network_multiple_mappers_no_scorer(toluene_vs_others, network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=others, central_ligand=toluene, - mapper=mappers, + mappers=mappers, ) assert len(network.edges) == len(others) @@ -178,7 +178,7 @@ def test_radial_network_failure(atom_mapping_basic_test_files, lomap_old_mapper) network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=[nigel], central_ligand=atom_mapping_basic_test_files['toluene'], - mapper=[lomap_old_mapper], + mappers=[lomap_old_mapper], scorer=None ) @@ -206,7 +206,7 @@ def scoring_func(mapping): network = openfe.setup.ligand_network_planning.generate_maximal_network( ligands=others + [toluene], - mapper=mappers, + mappers=mappers, scorer=scorer, progress=with_progress, ) @@ -247,7 +247,7 @@ def scorer(mapping): network = openfe.ligand_network_planning.generate_minimal_spanning_network( ligands=ligands, - mapper=mappers, + mappers=mappers, scorer=scorer, ) @@ -277,7 +277,7 @@ def scorer(mapping): network = openfe.setup.ligand_network_planning.generate_minimal_spanning_network( ligands=others + [toluene], - mapper=mappers, + mappers=mappers, scorer=scorer ) return network @@ -333,7 +333,7 @@ def scorer(mapping): with pytest.raises(RuntimeError, match="Unable to create edges"): network = openfe.setup.ligand_network_planning.generate_minimal_spanning_network( ligands=others + [toluene, nimrod], - mapper=[lomap_old_mapper], + mappers=[lomap_old_mapper], scorer=scorer ) @@ -368,7 +368,7 @@ def scorer(mapping): network = openfe.setup.ligand_network_planning.generate_minimal_redundant_network( ligands=others + [toluene], - mapper=mappers, + mappers=mappers, scorer=scorer, mst_num=2 ) @@ -454,7 +454,7 @@ def scorer(mapping): with pytest.raises(RuntimeError, match="Unable to create edges"): network = openfe.setup.ligand_network_planning.generate_minimal_redundant_network( ligands=others + [toluene, nimrod], - mapper=[lomap_old_mapper], + mappers=[lomap_old_mapper], scorer=scorer ) @@ -470,7 +470,7 @@ def test_network_from_names(atom_mapping_basic_test_files, lomap_old_mapper): network = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) assert len(network.nodes) == len(ligs) @@ -494,7 +494,7 @@ def test_network_from_names_bad_name( _ = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) @@ -513,7 +513,7 @@ def test_network_from_names_duplicate_name( _ = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) @@ -527,7 +527,7 @@ def test_network_from_indices( network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) assert len(network.nodes) == len(ligs) @@ -552,7 +552,7 @@ def test_network_from_indices_indexerror( network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) @@ -566,7 +566,7 @@ def test_network_from_indices_disconnected_warning( _ = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) @@ -583,7 +583,7 @@ def test_network_from_external(file_fixture, loader, request, network = loader( ligands=[l for l in benzene_modifications.values()], - mapper=openfe.LomapAtomMapper(), + mappers=openfe.LomapAtomMapper(), network_file=network_file, ) @@ -619,7 +619,7 @@ def test_network_from_external_unknown_edge(file_fixture, loader, request, with pytest.raises(KeyError, match="Invalid name"): network = loader( ligands=ligs, - mapper=openfe.LomapAtomMapper(), + mappers=openfe.LomapAtomMapper(), network_file=network_file, ) @@ -644,7 +644,7 @@ def test_bad_orion_network(benzene_modifications, tmpdir): with pytest.raises(KeyError, match="line does not match"): network = openfe.setup.ligand_network_planning.load_orion_network( ligands=[l for l in benzene_modifications.values()], - mapper=openfe.LomapAtomMapper(), + mappers=openfe.LomapAtomMapper(), network_file='bad_orion_net.dat', ) @@ -667,6 +667,6 @@ def test_bad_edges_network(benzene_modifications, tmpdir): with pytest.raises(KeyError, match="line does not match"): network = openfe.setup.ligand_network_planning.load_fepplus_network( ligands=[l for l in benzene_modifications.values()], - mapper=openfe.LomapAtomMapper(), + mappers=openfe.LomapAtomMapper(), network_file='bad_edges.edges', ) From 28e70e2c234ca44249ae8006f0049508c7910931 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 6 Nov 2024 11:16:29 -0800 Subject: [PATCH 16/56] fixing mapper/s to avoid api break --- openfe/setup/ligand_network_planning.py | 16 ++++++++-------- openfe/tests/setup/test_network_planning.py | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 8835c9735..8cd120314 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -313,7 +313,7 @@ def generate_minimal_redundant_network( def generate_network_from_names( ligands: list[SmallMoleculeComponent], - mappers: Union[AtomMapper, Iterable[AtomMapper]], + mapper: AtomMapper, names: list[tuple[str, str]], ) -> LigandNetwork: """ @@ -344,7 +344,7 @@ def generate_network_from_names( """ nodes = list(ligands) - network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) + network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) network = network_planner.generate_network_from_names( ligands=nodes, names=names @@ -355,7 +355,7 @@ def generate_network_from_names( def generate_network_from_indices( ligands: list[SmallMoleculeComponent], - mappers: Union[AtomMapper, Iterable[AtomMapper]], + mapper: AtomMapper, indices: list[tuple[int, int]], ) -> LigandNetwork: """ @@ -383,7 +383,7 @@ def generate_network_from_indices( """ nodes = list(ligands) - network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) + network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) network = network_planner.generate_network_from_indices( ligands=nodes, indices=indices ) @@ -392,7 +392,7 @@ def generate_network_from_indices( def load_orion_network( ligands: list[SmallMoleculeComponent], - mappers: Union[AtomMapper, Iterable[AtomMapper]], + mapper: AtomMapper, network_file: Union[str, Path], ) -> LigandNetwork: """Load a :class:`.LigandNetwork` from an Orion NES network file. @@ -431,7 +431,7 @@ def load_orion_network( names.append((entry[0], entry[2])) - network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) + network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) network = network_planner.generate_network_from_names( ligands=ligands, names=names ) @@ -441,7 +441,7 @@ def load_orion_network( def load_fepplus_network( ligands: list[SmallMoleculeComponent], - mappers: Union[AtomMapper, Iterable[AtomMapper]], + mapper: AtomMapper, network_file: Union[str, Path], ) -> LigandNetwork: """Load a :class:`.LigandNetwork` from an FEP+ edges network file. @@ -480,7 +480,7 @@ def load_fepplus_network( names.append((entry[2], entry[4])) - network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) + network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) network = network_planner.generate_network_from_names( ligands=ligands, names=names ) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index a629f1696..a40f83911 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -470,7 +470,7 @@ def test_network_from_names(atom_mapping_basic_test_files, lomap_old_mapper): network = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) assert len(network.nodes) == len(ligs) @@ -494,7 +494,7 @@ def test_network_from_names_bad_name( _ = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) @@ -513,7 +513,7 @@ def test_network_from_names_duplicate_name( _ = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) @@ -527,7 +527,7 @@ def test_network_from_indices( network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) assert len(network.nodes) == len(ligs) @@ -552,7 +552,7 @@ def test_network_from_indices_indexerror( network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) @@ -566,7 +566,7 @@ def test_network_from_indices_disconnected_warning( _ = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) @@ -583,7 +583,7 @@ def test_network_from_external(file_fixture, loader, request, network = loader( ligands=[l for l in benzene_modifications.values()], - mappers=openfe.LomapAtomMapper(), + mapper=openfe.LomapAtomMapper(), network_file=network_file, ) @@ -619,7 +619,7 @@ def test_network_from_external_unknown_edge(file_fixture, loader, request, with pytest.raises(KeyError, match="Invalid name"): network = loader( ligands=ligs, - mappers=openfe.LomapAtomMapper(), + mapper=openfe.LomapAtomMapper(), network_file=network_file, ) @@ -644,7 +644,7 @@ def test_bad_orion_network(benzene_modifications, tmpdir): with pytest.raises(KeyError, match="line does not match"): network = openfe.setup.ligand_network_planning.load_orion_network( ligands=[l for l in benzene_modifications.values()], - mappers=openfe.LomapAtomMapper(), + mapper=openfe.LomapAtomMapper(), network_file='bad_orion_net.dat', ) @@ -667,6 +667,6 @@ def test_bad_edges_network(benzene_modifications, tmpdir): with pytest.raises(KeyError, match="line does not match"): network = openfe.setup.ligand_network_planning.load_fepplus_network( ligands=[l for l in benzene_modifications.values()], - mappers=openfe.LomapAtomMapper(), + mapper=openfe.LomapAtomMapper(), network_file='bad_edges.edges', ) From c94de80d8211d61f4ebe965eaa9fae8d0c44fcb4 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 6 Nov 2024 12:01:43 -0800 Subject: [PATCH 17/56] switching arg names from 'ligands' to new 'components' keyword to match konnektor --- openfe/setup/ligand_network_planning.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 8cd120314..c6ec47e3c 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -347,7 +347,7 @@ def generate_network_from_names( network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) network = network_planner.generate_network_from_names( - ligands=nodes, names=names + components=nodes, names=names ) return network @@ -433,7 +433,7 @@ def load_orion_network( network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) network = network_planner.generate_network_from_names( - ligands=ligands, names=names + components=ligands, names=names ) return network @@ -482,6 +482,6 @@ def load_fepplus_network( network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) network = network_planner.generate_network_from_names( - ligands=ligands, names=names + components=ligands, names=names ) return network From b8ca43c44f225b2c9d470925fb941dd1945063df Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 8 Jan 2025 15:03:52 -0800 Subject: [PATCH 18/56] reverting ordering change --- openfe/setup/ligand_network_planning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index c6ec47e3c..c231df905 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -52,8 +52,8 @@ def _hasten_lomap(mapper, ligands): def generate_radial_network( ligands: Iterable[SmallMoleculeComponent], + central_ligand: Union[SmallMoleculeComponent, str, int], mappers: Union[AtomMapper, Iterable[AtomMapper]], - central_ligand: Union[SmallMoleculeComponent, str, int, None], scorer: Optional[Callable[[LigandAtomMapping], float]] = None, n_processes: int = 1, progress: bool = False, From 68c1ff145c20d0545080eb8deecccf591c12426f Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 8 Jan 2025 15:20:49 -0800 Subject: [PATCH 19/56] fixing docstring --- openfe/setup/ligand_network_planning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index c231df905..1132d4a90 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -171,7 +171,7 @@ def generate_maximal_network( ---------- ligands : Iterable[SmallMoleculeComponent] the ligands to include in the LigandNetwork - mapper : AtomMapper or Iterable[AtomMapper] + mappers : AtomMapper or Iterable[AtomMapper] the AtomMapper(s) to use to propose mappings. At least 1 required, but many can be given. scorer : Scoring function From ef9fde29fc257a4b365a1dee00b319e7e9ee1641 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 14 May 2025 13:56:28 -0700 Subject: [PATCH 20/56] pull konnektor from main for development only --- environment.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/environment.yml b/environment.yml index 44f3d687a..71bd3f8c2 100644 --- a/environment.yml +++ b/environment.yml @@ -7,6 +7,7 @@ dependencies: - coverage - duecredit<0.10 - kartograf>=1.0.0 + # - konnektor - lomap2>=3.2.1 - networkx - numpy<2.0.0 @@ -52,3 +53,4 @@ dependencies: - threadpoolctl - pip: - git+https://github.com/OpenFreeEnergy/gufe@main + - git+https://github.com/OpenFreeEnergy/konnektor@main From 915e71a5b5a2d7d4b5992b0f5d8288a5a22ff66e Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Thu, 15 May 2025 07:24:59 -0700 Subject: [PATCH 21/56] mapper back to mappers to avoid api break --- openfe/setup/ligand_network_planning.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 1132d4a90..4dfc60926 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -69,12 +69,12 @@ def generate_radial_network( ligands : iterable of SmallMoleculeComponents the ligands to arrange around the central ligand. If the central ligand is present it will be ignored (i.e. avoiding a self edge) - mappers : AtomMapper or iterable of AtomMappers - mapper(s) to use, at least 1 required central_ligand : SmallMoleculeComponent or str or int the ligand to use as the hub/central ligand. If this is a string, this should match to one and only one ligand name. If this is an integer, this refers to the index from within ligands + mappers : AtomMapper or iterable of AtomMappers + mapper(s) to use, at least 1 required scorer : scoring function, optional a callable which returns a float for any LigandAtomMapping. Used to assign scores to potential mappings; higher scores indicate better @@ -313,7 +313,7 @@ def generate_minimal_redundant_network( def generate_network_from_names( ligands: list[SmallMoleculeComponent], - mapper: AtomMapper, + mappers: AtomMapper, names: list[tuple[str, str]], ) -> LigandNetwork: """ @@ -323,7 +323,7 @@ def generate_network_from_names( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mapper: AtomMapper + mappers: AtomMapper the atom mapper to use to construct edges names : list of tuples of names the edges to form where the values refer to names of the small molecules, @@ -355,7 +355,7 @@ def generate_network_from_names( def generate_network_from_indices( ligands: list[SmallMoleculeComponent], - mapper: AtomMapper, + mappers: AtomMapper, indices: list[tuple[int, int]], ) -> LigandNetwork: """ @@ -365,8 +365,8 @@ def generate_network_from_indices( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mapper: AtomMapper - the atom mapper to use to construct edges + mappers: AtomMapper + the atom mappers to use to construct edges indices : list of tuples of indices the edges to form where the values refer to names of the small molecules, eg `[(3, 4), ...]` will create an edge between the 3rd and 4th molecules @@ -392,7 +392,7 @@ def generate_network_from_indices( def load_orion_network( ligands: list[SmallMoleculeComponent], - mapper: AtomMapper, + mappers: AtomMapper, network_file: Union[str, Path], ) -> LigandNetwork: """Load a :class:`.LigandNetwork` from an Orion NES network file. @@ -401,7 +401,7 @@ def load_orion_network( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mapper: AtomMapper + mappers: AtomMapper the atom mapper to use to construct edges network_file : str path to NES network file. @@ -441,7 +441,7 @@ def load_orion_network( def load_fepplus_network( ligands: list[SmallMoleculeComponent], - mapper: AtomMapper, + mappers: AtomMapper, network_file: Union[str, Path], ) -> LigandNetwork: """Load a :class:`.LigandNetwork` from an FEP+ edges network file. @@ -450,7 +450,7 @@ def load_fepplus_network( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mapper: AtomMapper + mappers: AtomMapper the atom mapper to use to construct edges network_file : str path to edges network file. From a3e4f18b36ac0735d3c6515c29139abbb26ef483 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Thu, 15 May 2025 07:28:09 -0700 Subject: [PATCH 22/56] clean up --- openfe/setup/ligand_network_planning.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 4dfc60926..0c6b07ffb 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -55,8 +55,8 @@ def generate_radial_network( central_ligand: Union[SmallMoleculeComponent, str, int], mappers: Union[AtomMapper, Iterable[AtomMapper]], scorer: Optional[Callable[[LigandAtomMapping], float]] = None, - n_processes: int = 1, progress: bool = False, + n_processes: int = 1, ) -> LigandNetwork: """ Plan a radial network with all ligands connected to a central node. @@ -103,10 +103,8 @@ def generate_radial_network( """ if isinstance(mappers, AtomMapper): mappers = [mappers] - mappers = [ - _hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) else m - for m in mappers - ] + mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) + else m for m in mappers] # handle central_ligand arg possibilities # after this, central_ligand is resolved to a SmallMoleculeComponent From c800bea591093c861325f8e5f101bc0f7f2cb47d Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Thu, 15 May 2025 07:30:31 -0700 Subject: [PATCH 23/56] formatting --- openfe/setup/ligand_network_planning.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 0c6b07ffb..ef0d7c433 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -33,10 +33,9 @@ def _hasten_lomap(mapper, ligands): return mapper try: - core = _find_common_core( - [m.to_rdkit() for m in ligands], - element_change=mapper.element_change, - ) + core = _find_common_core([m.to_rdkit() for m in ligands], + element_change=mapper.element_change) + except RuntimeError: # in case MCS throws a hissy fit core = "" From 8d2b88b192ffd3487b8cbb0cdd78e5bafd1cc784 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Thu, 15 May 2025 08:29:25 -0700 Subject: [PATCH 24/56] mapper->mappers --- openfe/setup/ligand_network_planning.py | 8 ++++---- openfe/tests/setup/test_network_planning.py | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index ef0d7c433..441b58784 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -341,7 +341,7 @@ def generate_network_from_names( """ nodes = list(ligands) - network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) + network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) network = network_planner.generate_network_from_names( components=nodes, names=names @@ -380,7 +380,7 @@ def generate_network_from_indices( """ nodes = list(ligands) - network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) + network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) network = network_planner.generate_network_from_indices( ligands=nodes, indices=indices ) @@ -428,7 +428,7 @@ def load_orion_network( names.append((entry[0], entry[2])) - network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) + network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) network = network_planner.generate_network_from_names( components=ligands, names=names ) @@ -477,7 +477,7 @@ def load_fepplus_network( names.append((entry[2], entry[4])) - network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) + network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) network = network_planner.generate_network_from_names( components=ligands, names=names ) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index a40f83911..a629f1696 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -470,7 +470,7 @@ def test_network_from_names(atom_mapping_basic_test_files, lomap_old_mapper): network = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) assert len(network.nodes) == len(ligs) @@ -494,7 +494,7 @@ def test_network_from_names_bad_name( _ = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) @@ -513,7 +513,7 @@ def test_network_from_names_duplicate_name( _ = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) @@ -527,7 +527,7 @@ def test_network_from_indices( network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) assert len(network.nodes) == len(ligs) @@ -552,7 +552,7 @@ def test_network_from_indices_indexerror( network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) @@ -566,7 +566,7 @@ def test_network_from_indices_disconnected_warning( _ = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mapper=lomap_old_mapper, + mappers=lomap_old_mapper, ) @@ -583,7 +583,7 @@ def test_network_from_external(file_fixture, loader, request, network = loader( ligands=[l for l in benzene_modifications.values()], - mapper=openfe.LomapAtomMapper(), + mappers=openfe.LomapAtomMapper(), network_file=network_file, ) @@ -619,7 +619,7 @@ def test_network_from_external_unknown_edge(file_fixture, loader, request, with pytest.raises(KeyError, match="Invalid name"): network = loader( ligands=ligs, - mapper=openfe.LomapAtomMapper(), + mappers=openfe.LomapAtomMapper(), network_file=network_file, ) @@ -644,7 +644,7 @@ def test_bad_orion_network(benzene_modifications, tmpdir): with pytest.raises(KeyError, match="line does not match"): network = openfe.setup.ligand_network_planning.load_orion_network( ligands=[l for l in benzene_modifications.values()], - mapper=openfe.LomapAtomMapper(), + mappers=openfe.LomapAtomMapper(), network_file='bad_orion_net.dat', ) @@ -667,6 +667,6 @@ def test_bad_edges_network(benzene_modifications, tmpdir): with pytest.raises(KeyError, match="line does not match"): network = openfe.setup.ligand_network_planning.load_fepplus_network( ligands=[l for l in benzene_modifications.values()], - mapper=openfe.LomapAtomMapper(), + mappers=openfe.LomapAtomMapper(), network_file='bad_edges.edges', ) From 0cc0c242d20d4f872be0600ef86108bc0210c67f Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Thu, 15 May 2025 08:42:19 -0700 Subject: [PATCH 25/56] fixing call to network_from_indices --- openfe/setup/ligand_network_planning.py | 10 +++++----- openfe/tests/setup/test_network_planning.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 441b58784..389aaebb6 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -352,7 +352,7 @@ def generate_network_from_names( def generate_network_from_indices( ligands: list[SmallMoleculeComponent], - mappers: AtomMapper, + mapper: AtomMapper, indices: list[tuple[int, int]], ) -> LigandNetwork: """ @@ -362,8 +362,8 @@ def generate_network_from_indices( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mappers: AtomMapper - the atom mappers to use to construct edges + mapper: AtomMapper + the atom mapper to use to construct edges indices : list of tuples of indices the edges to form where the values refer to names of the small molecules, eg `[(3, 4), ...]` will create an edge between the 3rd and 4th molecules @@ -380,9 +380,9 @@ def generate_network_from_indices( """ nodes = list(ligands) - network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) + network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) network = network_planner.generate_network_from_indices( - ligands=nodes, indices=indices + components=nodes, indices=indices ) return network diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index a629f1696..77370f6a8 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -527,7 +527,7 @@ def test_network_from_indices( network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) assert len(network.nodes) == len(ligs) @@ -552,7 +552,7 @@ def test_network_from_indices_indexerror( network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) @@ -566,7 +566,7 @@ def test_network_from_indices_disconnected_warning( _ = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) From 3368bd40d96f80121ef85b1d3aaf7d6fc61a8263 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 20 May 2025 09:36:09 -0700 Subject: [PATCH 26/56] update radial network test scorer to return between 0 and 1 --- openfe/tests/setup/test_network_planning.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 77370f6a8..3176e3b74 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -136,7 +136,7 @@ def test_radial_network_with_scorer(toluene_vs_others, lomap_old_mapper): toluene, others = toluene_vs_others def scorer(mapping): - return len(mapping.componentA_to_componentB) + return 1-1/len(mapping.componentA_to_componentB) network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=others, @@ -150,7 +150,7 @@ def scorer(mapping): # we didn't take the bad mapper assert len(edge.componentA_to_componentB) > 1 assert 'score' in edge.annotations - assert edge.annotations['score'] == len(edge.componentA_to_componentB) + assert edge.annotations['score'] == 1-1/len(edge.componentA_to_componentB) def test_radial_network_multiple_mappers_no_scorer(toluene_vs_others, From 1e976929f073439f3094155887ab4cf02d568fe0 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 20 May 2025 10:46:19 -0700 Subject: [PATCH 27/56] organizing tests into classes --- openfe/tests/setup/test_network_planning.py | 680 ++++++++++---------- 1 file changed, 336 insertions(+), 344 deletions(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 3176e3b74..6b52ad06e 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -1,10 +1,10 @@ # This code is part of OpenFE and is licensed under the MIT license. # For details, see https://github.com/OpenFreeEnergy/openfe -from rdkit import Chem +from gufe import AtomMapper import pytest import networkx as nx -import openfe.setup +import openfe from ..conftest import mol_from_smiles @@ -34,8 +34,8 @@ def toluene_vs_others(atom_mapping_basic_test_files): return toluene, others -@pytest.fixture(scope='session') -def lomap_old_mapper(): +@pytest.fixture(scope="session") +def lomap_old_mapper() -> AtomMapper: """ LomapAtomMapper with the old default settings. @@ -48,139 +48,139 @@ def lomap_old_mapper(): element_change=True, seed='', shift=True ) +class TestRadialNetworkGenerator: + @pytest.mark.parametrize('as_list', [False, True]) + def test_radial_network( + self, atom_mapping_basic_test_files, toluene_vs_others, + as_list, lomap_old_mapper, + ): + toluene, others = toluene_vs_others + central_ligand_name = 'toluene' + mapper = lomap_old_mapper + if as_list: + mapper = [mapper] -@pytest.mark.parametrize('as_list', [False, True]) -def test_radial_network( - atom_mapping_basic_test_files, toluene_vs_others, - as_list, lomap_old_mapper, -): - toluene, others = toluene_vs_others - central_ligand_name = 'toluene' - mapper = lomap_old_mapper - if as_list: - mapper = [mapper] - - network = openfe.setup.ligand_network_planning.generate_radial_network( - ligands=others, central_ligand=toluene, - mappers=mapper, scorer=None, - ) - # couple sanity checks - assert len(network.nodes) == len(atom_mapping_basic_test_files) - assert len(network.edges) == len(others) - # check that all ligands are present, i.e. we included everyone - ligands_in_network = {mol.name for mol in network.nodes} - assert ligands_in_network == set(atom_mapping_basic_test_files.keys()) - # check that every edge has the central ligand within - assert all((central_ligand_name in {mapping.componentA.name, mapping.componentB.name}) - for mapping in network.edges) - - -@pytest.mark.parametrize('central_ligand_arg', [0, 'toluene']) -def test_radial_network_int_str(atom_mapping_basic_test_files, toluene_vs_others, - central_ligand_arg, lomap_old_mapper): - # check that passing either an integer or string to radial network still works - toluene, others = toluene_vs_others - ligands = [toluene] + others - - network = openfe.setup.ligand_network_planning.generate_radial_network( - ligands=ligands, central_ligand=central_ligand_arg, - mappers=lomap_old_mapper, scorer=None, - ) - assert len(network.nodes) == len(ligands) - assert len(network.edges) == len(others) - # check that all ligands are present, i.e. we included everyone - ligands_in_network = {mol.name for mol in network.nodes} - assert ligands_in_network == set(atom_mapping_basic_test_files.keys()) - # check that every edge has the central ligand within - assert all(('toluene' in {mapping.componentA.name, mapping.componentB.name}) - for mapping in network.edges) - - -def test_radial_network_bad_str(toluene_vs_others, lomap_old_mapper): - # check failure on missing name - toluene, others = toluene_vs_others - ligands = [toluene] + others - - with pytest.raises(ValueError, match='No ligand called'): network = openfe.setup.ligand_network_planning.generate_radial_network( - ligands=ligands, central_ligand='unobtainium', - mappers=lomap_old_mapper, scorer=None, + ligands=others, central_ligand=toluene, + mappers=mapper, scorer=None, ) + # couple sanity checks + assert len(network.nodes) == len(atom_mapping_basic_test_files) + assert len(network.edges) == len(others) + # check that all ligands are present, i.e. we included everyone + ligands_in_network = {mol.name for mol in network.nodes} + assert ligands_in_network == set(atom_mapping_basic_test_files.keys()) + # check that every edge has the central ligand within + assert all((central_ligand_name in {mapping.componentA.name, mapping.componentB.name}) + for mapping in network.edges) + + + @pytest.mark.parametrize('central_ligand_arg', [0, 'toluene']) + def test_radial_network_int_str(self, atom_mapping_basic_test_files, toluene_vs_others, + central_ligand_arg, lomap_old_mapper): + # check that passing either an integer or string to radial network still works + toluene, others = toluene_vs_others + ligands = [toluene] + others - -def test_radial_network_multiple_str(toluene_vs_others, lomap_old_mapper): - # check failure on multiple of specified name, it's ambiguous - toluene, others = toluene_vs_others - ligands = [toluene, toluene] + others - - with pytest.raises(ValueError, match='Multiple ligands called'): network = openfe.setup.ligand_network_planning.generate_radial_network( - ligands=ligands, central_ligand='toluene', + ligands=ligands, central_ligand=central_ligand_arg, mappers=lomap_old_mapper, scorer=None, ) + assert len(network.nodes) == len(ligands) + assert len(network.edges) == len(others) + # check that all ligands are present, i.e. we included everyone + ligands_in_network = {mol.name for mol in network.nodes} + assert ligands_in_network == set(atom_mapping_basic_test_files.keys()) + # check that every edge has the central ligand within + assert all(('toluene' in {mapping.componentA.name, mapping.componentB.name}) + for mapping in network.edges) + + + def test_radial_network_bad_str(self, toluene_vs_others, lomap_old_mapper): + # check failure on missing name + toluene, others = toluene_vs_others + ligands = [toluene] + others + + with pytest.raises(ValueError, match='No ligand called'): + network = openfe.setup.ligand_network_planning.generate_radial_network( + ligands=ligands, central_ligand='unobtainium', + mappers=lomap_old_mapper, scorer=None, + ) -def test_radial_network_index_error(toluene_vs_others, lomap_old_mapper): - # check if we ask for a out of bounds ligand we get a meaningful failure - toluene, others = toluene_vs_others - ligands = [toluene] + others + def test_radial_network_multiple_str(self, toluene_vs_others, lomap_old_mapper): + # check failure on multiple of specified name, it's ambiguous + toluene, others = toluene_vs_others + ligands = [toluene, toluene] + others - with pytest.raises(ValueError, match='out of bounds'): - network = openfe.setup.ligand_network_planning.generate_radial_network( - ligands=ligands, central_ligand=2077, - mappers=lomap_old_mapper, scorer=None, - ) - - -def test_radial_network_with_scorer(toluene_vs_others, lomap_old_mapper): - toluene, others = toluene_vs_others + with pytest.raises(ValueError, match='Multiple ligands called'): + network = openfe.setup.ligand_network_planning.generate_radial_network( + ligands=ligands, central_ligand='toluene', + mappers=lomap_old_mapper, scorer=None, + ) - def scorer(mapping): - return 1-1/len(mapping.componentA_to_componentB) - network = openfe.setup.ligand_network_planning.generate_radial_network( - ligands=others, - central_ligand=toluene, - mappers=[BadMapper(), lomap_old_mapper], - scorer=scorer - ) - assert len(network.edges) == len(others) + def test_radial_network_index_error(self, toluene_vs_others, lomap_old_mapper): + # check if we ask for a out of bounds ligand we get a meaningful failure + toluene, others = toluene_vs_others + ligands = [toluene] + others - for edge in network.edges: - # we didn't take the bad mapper - assert len(edge.componentA_to_componentB) > 1 - assert 'score' in edge.annotations - assert edge.annotations['score'] == 1-1/len(edge.componentA_to_componentB) + with pytest.raises(ValueError, match='out of bounds'): + network = openfe.setup.ligand_network_planning.generate_radial_network( + ligands=ligands, central_ligand=2077, + mappers=lomap_old_mapper, scorer=None, + ) + + def test_radial_network_with_scorer(self, toluene_vs_others, lomap_old_mapper): + toluene, others = toluene_vs_others -def test_radial_network_multiple_mappers_no_scorer(toluene_vs_others, - lomap_old_mapper): - toluene, others = toluene_vs_others + def scorer(mapping): + return 1-1/len(mapping.componentA_to_componentB) - mappers = [BadMapper(), lomap_old_mapper] + network = openfe.setup.ligand_network_planning.generate_radial_network( + ligands=others, + central_ligand=toluene, + mappers=[BadMapper(), lomap_old_mapper], + scorer=scorer + ) + assert len(network.edges) == len(others) - # in this one, we should always take the bad mapper - network = openfe.setup.ligand_network_planning.generate_radial_network( - ligands=others, - central_ligand=toluene, - mappers=mappers, - ) - assert len(network.edges) == len(others) + for edge in network.edges: + # make sure we didn't take BadMapper + assert len(edge.componentA_to_componentB) > 1 + assert 'score' in edge.annotations + assert edge.annotations['score'] == 1-1/len(edge.componentA_to_componentB) - for edge in network.edges: - assert edge.componentA_to_componentB == {0: 0} + def test_radial_network_multiple_mappers_no_scorer(self, toluene_vs_others, + lomap_old_mapper): + toluene, others = toluene_vs_others -def test_radial_network_failure(atom_mapping_basic_test_files, lomap_old_mapper): - nigel = openfe.SmallMoleculeComponent(mol_from_smiles('N')) + mappers = [BadMapper(), lomap_old_mapper] - with pytest.raises(ValueError, match='No mapping found for'): + # in this one, we should always take the bad mapper network = openfe.setup.ligand_network_planning.generate_radial_network( - ligands=[nigel], - central_ligand=atom_mapping_basic_test_files['toluene'], - mappers=[lomap_old_mapper], - scorer=None + ligands=others, + central_ligand=toluene, + mappers=mappers, ) + assert len(network.edges) == len(others) + + for edge in network.edges: + assert edge.componentA_to_componentB == {0: 0} + + + def test_radial_network_failure(self, atom_mapping_basic_test_files, lomap_old_mapper): + nigel = openfe.SmallMoleculeComponent(mol_from_smiles('N')) + + with pytest.raises(ValueError, match='No mapping found for'): + network = openfe.setup.ligand_network_planning.generate_radial_network( + ligands=[nigel], + central_ligand=atom_mapping_basic_test_files['toluene'], + mappers=[lomap_old_mapper], + scorer=None + ) @pytest.mark.parametrize('with_progress', [True, False]) @@ -228,34 +228,7 @@ def scoring_func(mapping): for edge in network.edges: assert 'score' not in edge.annotations - -@pytest.mark.parametrize('multi_mappers', [False, True]) -def test_minimal_spanning_network_mappers( - atom_mapping_basic_test_files, multi_mappers, lomap_old_mapper -): - ligands = [atom_mapping_basic_test_files['toluene'], - atom_mapping_basic_test_files['2-naftanol'], - ] - - if multi_mappers: - mappers = [BadMapper(), lomap_old_mapper] - else: - mappers = lomap_old_mapper - - def scorer(mapping): - return len(mapping.componentA_to_componentB) - - network = openfe.ligand_network_planning.generate_minimal_spanning_network( - ligands=ligands, - mappers=mappers, - scorer=scorer, - ) - - assert isinstance(network, openfe.LigandNetwork) - assert list(network.edges) - - -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def minimal_spanning_network(toluene_vs_others, lomap_old_mapper): toluene, others = toluene_vs_others @@ -281,62 +254,86 @@ def scorer(mapping): scorer=scorer ) return network +class TestMinimalSpanningNetworkGenerator: + @pytest.mark.parametrize("multi_mappers", [False, True]) + def test_minimal_spanning_network_mappers( + self, atom_mapping_basic_test_files, multi_mappers, lomap_old_mapper + ): + ligands = [ + atom_mapping_basic_test_files["toluene"], + atom_mapping_basic_test_files["2-naftanol"], + ] + if multi_mappers: + mappers = [BadMapper(), lomap_old_mapper] + else: + mappers = lomap_old_mapper -def test_minimal_spanning_network(minimal_spanning_network, toluene_vs_others): - tol, others = toluene_vs_others - assert len(minimal_spanning_network.nodes) == len(others) + 1 - for edge in minimal_spanning_network.edges: - assert edge.componentA_to_componentB != { - 0: 0} # lomap should find something + def scorer(mapping): + return len(mapping.componentA_to_componentB) + network = openfe.ligand_network_planning.generate_minimal_spanning_network( + ligands=ligands, + mappers=mappers, + scorer=scorer, + ) -def test_minimal_spanning_network_connectedness(minimal_spanning_network): - found_pairs = set() - for edge in minimal_spanning_network.edges: - pair = frozenset([edge.componentA, edge.componentB]) - assert pair not in found_pairs - found_pairs.add(pair) + assert isinstance(network, openfe.LigandNetwork) + assert list(network.edges) - assert nx.is_connected(nx.MultiGraph(minimal_spanning_network.graph)) + def test_minimal_spanning_network(self, minimal_spanning_network, toluene_vs_others): + tol, others = toluene_vs_others + assert len(minimal_spanning_network.nodes) == len(others) + 1 + for edge in minimal_spanning_network.edges: + assert edge.componentA_to_componentB != { + 0: 0} # lomap should find something -def test_minimal_spanning_network_regression(minimal_spanning_network): - # issue #244, this was previously giving non-reproducible (yet valid) - # networks when scores were tied. - edge_ids = sorted( - (edge.componentA.name, edge.componentB.name) - for edge in minimal_spanning_network.edges - ) - ref = sorted([ - ('1,3,7-trimethylnaphthalene', '2,6-dimethylnaphthalene'), - ('1-butyl-4-methylbenzene', '2-methyl-6-propylnaphthalene'), - ('2,6-dimethylnaphthalene', '2-methyl-6-propylnaphthalene'), - ('2,6-dimethylnaphthalene', '2-methylnaphthalene'), - ('2,6-dimethylnaphthalene', '2-naftanol'), - ('2,6-dimethylnaphthalene', 'methylcyclohexane'), - ('2,6-dimethylnaphthalene', 'toluene'), - ]) - - assert len(edge_ids) == len(ref) - assert edge_ids == ref - - -def test_minimal_spanning_network_unreachable(toluene_vs_others, - lomap_old_mapper): - toluene, others = toluene_vs_others - nimrod = openfe.SmallMoleculeComponent(mol_from_smiles("N")) + def test_minimal_spanning_network_connectedness(self, minimal_spanning_network): + found_pairs = set() + for edge in minimal_spanning_network.edges: + pair = frozenset([edge.componentA, edge.componentB]) + assert pair not in found_pairs + found_pairs.add(pair) - def scorer(mapping): - return len(mapping.componentA_to_componentB) + assert nx.is_connected(nx.MultiGraph(minimal_spanning_network.graph)) - with pytest.raises(RuntimeError, match="Unable to create edges"): - network = openfe.setup.ligand_network_planning.generate_minimal_spanning_network( - ligands=others + [toluene, nimrod], - mappers=[lomap_old_mapper], - scorer=scorer - ) + def test_minimal_spanning_network_regression(self, minimal_spanning_network): + # issue #244, this was previously giving non-reproducible (yet valid) + # networks when scores were tied. + edge_ids = sorted( + (edge.componentA.name, edge.componentB.name) + for edge in minimal_spanning_network.edges + ) + ref = sorted([ + ('1,3,7-trimethylnaphthalene', '2,6-dimethylnaphthalene'), + ('1-butyl-4-methylbenzene', '2-methyl-6-propylnaphthalene'), + ('2,6-dimethylnaphthalene', '2-methyl-6-propylnaphthalene'), + ('2,6-dimethylnaphthalene', '2-methylnaphthalene'), + ('2,6-dimethylnaphthalene', '2-naftanol'), + ('2,6-dimethylnaphthalene', 'methylcyclohexane'), + ('2,6-dimethylnaphthalene', 'toluene'), + ]) + + assert len(edge_ids) == len(ref) + assert edge_ids == ref + + + def test_minimal_spanning_network_unreachable(self, toluene_vs_others, + lomap_old_mapper): + toluene, others = toluene_vs_others + nimrod = openfe.SmallMoleculeComponent(mol_from_smiles("N")) + + def scorer(mapping): + return len(mapping.componentA_to_componentB) + + with pytest.raises(RuntimeError, match="Unable to create edges"): + network = openfe.setup.ligand_network_planning.generate_minimal_spanning_network( + ligands=others + [toluene, nimrod], + mappers=[lomap_old_mapper], + scorer=scorer + ) @pytest.fixture(scope='session') def minimal_redundant_network(toluene_vs_others, lomap_old_mapper): @@ -374,200 +371,195 @@ def scorer(mapping): ) return network +class TestMinimalRedundantNetworkGenerator: + def test_minimal_redundant_network(self, minimal_redundant_network, toluene_vs_others): + tol, others = toluene_vs_others -def test_minimal_redundant_network(minimal_redundant_network, toluene_vs_others): - tol, others = toluene_vs_others - - # test for correct number of nodes - assert len(minimal_redundant_network.nodes) == len(others) + 1 - - # test for correct number of edges - assert len(minimal_redundant_network.edges) == 2 * \ - (len(minimal_redundant_network.nodes) - 1) - - for edge in minimal_redundant_network.edges: - assert edge.componentA_to_componentB != { - 0: 0} # lomap should find something + # test for correct number of nodes + assert len(minimal_redundant_network.nodes) == len(others) + 1 + # test for correct number of edges + assert len(minimal_redundant_network.edges) == 2 * \ + (len(minimal_redundant_network.nodes) - 1) -def test_minimal_redundant_network_connectedness(minimal_redundant_network): - found_pairs = set() - for edge in minimal_redundant_network.edges: - pair = frozenset([edge.componentA, edge.componentB]) - assert pair not in found_pairs - found_pairs.add(pair) + for edge in minimal_redundant_network.edges: + assert edge.componentA_to_componentB != { + 0: 0} # lomap should find something - assert nx.is_connected(nx.MultiGraph(minimal_redundant_network.graph)) + def test_minimal_redundant_network_connectedness(self, minimal_redundant_network): + found_pairs = set() + for edge in minimal_redundant_network.edges: + pair = frozenset([edge.componentA, edge.componentB]) + assert pair not in found_pairs + found_pairs.add(pair) -def test_redundant_vs_spanning_network(minimal_redundant_network, minimal_spanning_network): - # when setting minimal redundant network to only take one MST, it should have as many - # edges as the regular minimum spanning network - assert 2 * len(minimal_spanning_network.edges) == len( - minimal_redundant_network.edges) + assert nx.is_connected(nx.MultiGraph(minimal_redundant_network.graph)) -def test_minimal_redundant_network_edges(minimal_redundant_network): - # issue #244, this was previously giving non-reproducible (yet valid) - # networks when scores were tied. - edge_ids = sorted( - (edge.componentA.name, edge.componentB.name) - for edge in minimal_redundant_network.edges - ) - ref = sorted([ - ('1,3,7-trimethylnaphthalene', '2,6-dimethylnaphthalene'), - ('1,3,7-trimethylnaphthalene', '2-methyl-6-propylnaphthalene'), - ('1-butyl-4-methylbenzene', '2,6-dimethylnaphthalene'), - ('1-butyl-4-methylbenzene', '2-methyl-6-propylnaphthalene'), - ('1-butyl-4-methylbenzene', 'toluene'), - ('2,6-dimethylnaphthalene', '2-methyl-6-propylnaphthalene'), - ('2,6-dimethylnaphthalene', '2-methylnaphthalene'), - ('2,6-dimethylnaphthalene', '2-naftanol'), - ('2,6-dimethylnaphthalene', 'methylcyclohexane'), - ('2,6-dimethylnaphthalene', 'toluene'), - ('2-methyl-6-propylnaphthalene', '2-methylnaphthalene'), - ('2-methylnaphthalene', '2-naftanol'), - ('2-methylnaphthalene', 'methylcyclohexane'), - ('2-methylnaphthalene', 'toluene') - ]) - - assert len(edge_ids) == len(ref) - assert edge_ids == ref - - -def test_minimal_redundant_network_redundant(minimal_redundant_network): - # test that each node is connected to 2 edges. - network = minimal_redundant_network - for node in network.nodes: - assert len(network.graph.in_edges(node)) + \ - len(network.graph.out_edges(node)) >= 2 - - -def test_minimal_redundant_network_unreachable(toluene_vs_others, - lomap_old_mapper): - toluene, others = toluene_vs_others - nimrod = openfe.SmallMoleculeComponent(mol_from_smiles("N")) + def test_redundant_vs_spanning_network(self, minimal_redundant_network, minimal_spanning_network): + # when setting minimal redundant network to only take one MST, it should have as many + # edges as the regular minimum spanning network + assert 2 * len(minimal_spanning_network.edges) == len( + minimal_redundant_network.edges) - def scorer(mapping): - return len(mapping.componentA_to_componentB) - with pytest.raises(RuntimeError, match="Unable to create edges"): - network = openfe.setup.ligand_network_planning.generate_minimal_redundant_network( - ligands=others + [toluene, nimrod], - mappers=[lomap_old_mapper], - scorer=scorer + def test_minimal_redundant_network_edges(self, minimal_redundant_network): + # issue #244, this was previously giving non-reproducible (yet valid) + # networks when scores were tied. + edge_ids = sorted( + (edge.componentA.name, edge.componentB.name) + for edge in minimal_redundant_network.edges ) + ref = sorted([ + ('1,3,7-trimethylnaphthalene', '2,6-dimethylnaphthalene'), + ('1,3,7-trimethylnaphthalene', '2-methyl-6-propylnaphthalene'), + ('1-butyl-4-methylbenzene', '2,6-dimethylnaphthalene'), + ('1-butyl-4-methylbenzene', '2-methyl-6-propylnaphthalene'), + ('1-butyl-4-methylbenzene', 'toluene'), + ('2,6-dimethylnaphthalene', '2-methyl-6-propylnaphthalene'), + ('2,6-dimethylnaphthalene', '2-methylnaphthalene'), + ('2,6-dimethylnaphthalene', '2-naftanol'), + ('2,6-dimethylnaphthalene', 'methylcyclohexane'), + ('2,6-dimethylnaphthalene', 'toluene'), + ('2-methyl-6-propylnaphthalene', '2-methylnaphthalene'), + ('2-methylnaphthalene', '2-naftanol'), + ('2-methylnaphthalene', 'methylcyclohexane'), + ('2-methylnaphthalene', 'toluene') + ]) + + assert len(edge_ids) == len(ref) + assert edge_ids == ref + + + def test_minimal_redundant_network_redundant(self, minimal_redundant_network): + # test that each node is connected to 2 edges. + network = minimal_redundant_network + for node in network.nodes: + assert len(network.graph.in_edges(node)) + \ + len(network.graph.out_edges(node)) >= 2 + + + def test_minimal_redundant_network_unreachable(self, toluene_vs_others, lomap_old_mapper): + toluene, others = toluene_vs_others + nimrod = openfe.SmallMoleculeComponent(mol_from_smiles("N")) + + def scorer(mapping): + return len(mapping.componentA_to_componentB) + + with pytest.raises(RuntimeError, match="Unable to create edges"): + network = openfe.setup.ligand_network_planning.generate_minimal_redundant_network( + ligands=others + [toluene, nimrod], + mappers=[lomap_old_mapper], + scorer=scorer + ) +class TestNetworkFromNames: + def test_network_from_names(self, atom_mapping_basic_test_files, lomap_old_mapper): + ligs = list(atom_mapping_basic_test_files.values()) -def test_network_from_names(atom_mapping_basic_test_files, lomap_old_mapper): - ligs = list(atom_mapping_basic_test_files.values()) - - requested = [ - ('toluene', '2-naftanol'), - ('2-methylnaphthalene', '2-naftanol'), - ] - - network = openfe.setup.ligand_network_planning.generate_network_from_names( - ligands=ligs, - names=requested, - mappers=lomap_old_mapper, - ) - - assert len(network.nodes) == len(ligs) - assert len(network.edges) == 2 - actual_edges = [(e.componentA.name, e.componentB.name) - for e in network.edges] - assert set(requested) == set(actual_edges) - - -def test_network_from_names_bad_name( - atom_mapping_basic_test_files, lomap_old_mapper -): - ligs = list(atom_mapping_basic_test_files.values()) - - requested = [ - ('hank', '2-naftanol'), - ('2-methylnaphthalene', '2-naftanol'), - ] + requested = [ + ('toluene', '2-naftanol'), + ('2-methylnaphthalene', '2-naftanol'), + ] - with pytest.raises(KeyError, match="Invalid name"): - _ = openfe.setup.ligand_network_planning.generate_network_from_names( + network = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, mappers=lomap_old_mapper, ) - -def test_network_from_names_duplicate_name( - atom_mapping_basic_test_files, lomap_old_mapper -): - ligs = list(atom_mapping_basic_test_files.values()) - ligs = ligs + [ligs[0]] - - requested = [ - ('toluene', '2-naftanol'), - ('2-methylnaphthalene', '2-naftanol'), - ] - - with pytest.raises(ValueError, match="Duplicate names"): - _ = openfe.setup.ligand_network_planning.generate_network_from_names( - ligands=ligs, - names=requested, - mappers=lomap_old_mapper, - ) + assert len(network.nodes) == len(ligs) + assert len(network.edges) == 2 + actual_edges = [(e.componentA.name, e.componentB.name) + for e in network.edges] + assert set(requested) == set(actual_edges) -def test_network_from_indices( - atom_mapping_basic_test_files, lomap_old_mapper -): - ligs = list(atom_mapping_basic_test_files.values()) + def test_network_from_names_bad_name(self, atom_mapping_basic_test_files, lomap_old_mapper): + ligs = list(atom_mapping_basic_test_files.values()) - requested = [(0, 1), (2, 3)] + requested = [ + ('hank', '2-naftanol'), + ('2-methylnaphthalene', '2-naftanol'), + ] - network = openfe.setup.ligand_network_planning.generate_network_from_indices( - ligands=ligs, - indices=requested, - mapper=lomap_old_mapper, - ) + with pytest.raises(KeyError, match="Invalid name"): + _ = openfe.setup.ligand_network_planning.generate_network_from_names( + ligands=ligs, + names=requested, + mappers=lomap_old_mapper, + ) - assert len(network.nodes) == len(ligs) - assert len(network.edges) == 2 - edges = list(network.edges) - expected_edges = {(ligs[0], ligs[1]), (ligs[2], ligs[3])} - actual_edges = {(edges[0].componentA, edges[0].componentB), - (edges[1].componentA, edges[1].componentB)} + def test_network_from_names_duplicate_name(self, atom_mapping_basic_test_files, lomap_old_mapper): + ligs = list(atom_mapping_basic_test_files.values()) + ligs = ligs + [ligs[0]] - assert actual_edges == expected_edges + requested = [ + ('toluene', '2-naftanol'), + ('2-methylnaphthalene', '2-naftanol'), + ] + with pytest.raises(ValueError, match="Duplicate names"): + _ = openfe.setup.ligand_network_planning.generate_network_from_names( + ligands=ligs, + names=requested, + mappers=lomap_old_mapper, + ) -def test_network_from_indices_indexerror( - atom_mapping_basic_test_files, lomap_old_mapper, -): - ligs = list(atom_mapping_basic_test_files.values()) +class TestNetworkFromIndices: + def test_network_from_indices( + self, atom_mapping_basic_test_files, lomap_old_mapper + ): + ligs = list(atom_mapping_basic_test_files.values()) - requested = [(20, 1), (2, 3)] + requested = [(0, 1), (2, 3)] - with pytest.raises(IndexError, match="Invalid ligand id"): network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, mapper=lomap_old_mapper, ) + assert len(network.nodes) == len(ligs) + assert len(network.edges) == 2 -def test_network_from_indices_disconnected_warning( - atom_mapping_basic_test_files, lomap_old_mapper, -): - ligs = list(atom_mapping_basic_test_files.values()) - requested = [(0, 1), (1, 2)] + edges = list(network.edges) + expected_edges = {(ligs[0], ligs[1]), (ligs[2], ligs[3])} + actual_edges = {(edges[0].componentA, edges[0].componentB), + (edges[1].componentA, edges[1].componentB)} - with pytest.warns(UserWarning): - _ = openfe.setup.ligand_network_planning.generate_network_from_indices( - ligands=ligs, - indices=requested, - mapper=lomap_old_mapper, - ) + assert actual_edges == expected_edges + + + def test_network_from_indices_indexerror( + self, atom_mapping_basic_test_files, lomap_old_mapper, + ): + ligs = list(atom_mapping_basic_test_files.values()) + + requested = [(20, 1), (2, 3)] + + with pytest.raises(IndexError, match="Invalid ligand id"): + network = openfe.setup.ligand_network_planning.generate_network_from_indices( + ligands=ligs, + indices=requested, + mapper=lomap_old_mapper, + ) + + + def test_network_from_indices_disconnected_warning( + self, atom_mapping_basic_test_files, lomap_old_mapper, + ): + ligs = list(atom_mapping_basic_test_files.values()) + requested = [(0, 1), (1, 2)] + + with pytest.warns(UserWarning): + _ = openfe.setup.ligand_network_planning.generate_network_from_indices( + ligands=ligs, + indices=requested, + mapper=lomap_old_mapper, + ) @pytest.mark.parametrize('file_fixture, loader', [ From d6526c6d26eae4244e6808bf6497121d06255c81 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 20 May 2025 13:40:12 -0700 Subject: [PATCH 28/56] expect only one mapping per edge for maximal networks --- openfe/tests/setup/test_network_planning.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 6b52ad06e..98fb011f0 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -188,6 +188,7 @@ def test_radial_network_failure(self, atom_mapping_basic_test_files, lomap_old_m @pytest.mark.parametrize('extra_mapper', [True, False]) def test_generate_maximal_network(toluene_vs_others, with_progress, with_scorer, extra_mapper, lomap_old_mapper): + """TODO: add test for throwing an error if there is no scorer but multiple mappings""" toluene, others = toluene_vs_others @@ -213,10 +214,7 @@ def scoring_func(mapping): assert len(network.nodes) == len(others) + 1 - if extra_mapper: - edge_count = len(others) * (len(others) + 1) - else: - edge_count = len(others) * (len(others) + 1) / 2 + edge_count = len(others) * (len(others) + 1) / 2 assert len(network.edges) == edge_count From a044a715d589f380dbfb2decd4d144bab9f3aec6 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 20 May 2025 13:48:42 -0700 Subject: [PATCH 29/56] adding TODOs --- openfe/tests/setup/test_network_planning.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 98fb011f0..4d30d3560 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -466,7 +466,7 @@ def test_network_from_names(self, atom_mapping_basic_test_files, lomap_old_mappe names=requested, mappers=lomap_old_mapper, ) - + # TODO: konnektor only constructs based on edges, doesn't allow for disconnected networks assert len(network.nodes) == len(ligs) assert len(network.edges) == 2 actual_edges = [(e.componentA.name, e.componentB.name) @@ -514,6 +514,7 @@ def test_network_from_indices( requested = [(0, 1), (2, 3)] + # TODO: konnektor only constructs based on edges, doesn't allow for disconnected networks network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, From 92fbffb2c3379b258377006a307c95dc67ab5273 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 21 May 2025 11:57:16 -0700 Subject: [PATCH 30/56] adding TODOs for later --- openfe/setup/ligand_network_planning.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 389aaebb6..dd0c16532 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -310,8 +310,8 @@ def generate_minimal_redundant_network( def generate_network_from_names( ligands: list[SmallMoleculeComponent], - mappers: AtomMapper, - names: list[tuple[str, str]], + mappers: AtomMapper, # TODO: rename this to 'mapper' and only accept one + names: list[tuple[str, str]], # TODO: rename to 'edges' or `edge_names` ) -> LigandNetwork: """ Generate a :class:`.LigandNetwork` by specifying edges as tuples of names. From 7655a1f6eafb644e1979247c4940730c211504e9 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 21 May 2025 16:32:36 -0700 Subject: [PATCH 31/56] update index error string to match konnektor's clarity improvement --- openfe/tests/setup/test_network_planning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 4d30d3560..03cd370d2 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -539,7 +539,7 @@ def test_network_from_indices_indexerror( requested = [(20, 1), (2, 3)] - with pytest.raises(IndexError, match="Invalid ligand id"): + with pytest.raises(IndexError, match="Invalid ligand index"): network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, From 14a191b78141152139db4fdd1d7fa498b5ce696d Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 21 May 2025 16:34:00 -0700 Subject: [PATCH 32/56] updating pytest.raises to look for RuntimeError instead of ValueError --- openfe/tests/setup/test_network_planning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 03cd370d2..dc1cfd1db 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -174,7 +174,7 @@ def test_radial_network_multiple_mappers_no_scorer(self, toluene_vs_others, def test_radial_network_failure(self, atom_mapping_basic_test_files, lomap_old_mapper): nigel = openfe.SmallMoleculeComponent(mol_from_smiles('N')) - with pytest.raises(ValueError, match='No mapping found for'): + with pytest.raises(RuntimeError, match='Could not generate any mapping!'): network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=[nigel], central_ligand=atom_mapping_basic_test_files['toluene'], From 9dcfa4f976f5940a4fb11d96cbe919ecbd2aac5d Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Fri, 23 May 2025 09:23:31 -0700 Subject: [PATCH 33/56] adding TODO --- .../test_relative_alchemical_network_planner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py b/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py index 484da6099..ab3a74163 100644 --- a/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py +++ b/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py @@ -38,8 +38,9 @@ def test_rbfe_alchemical_network_planner_call(atom_mapping_basic_test_files, T4_ protein=T4_protein_component, ) + # TODO: methylcyclohexane isn't included in this network, possibly because of a score tie in _map_scoring, which means it doesn't get included in maximal_network_generator's "mappings" assert isinstance(alchem_network, AlchemicalNetwork) - + edges = alchem_network.edges assert len(edges) == 14 # we build 2envs*8ligands-2startLigands = 14 relative edges. From 649845d8eb6f073a071e299f81d405bec0a6dd2d Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Fri, 23 May 2025 12:39:58 -0700 Subject: [PATCH 34/56] making unreachable test clearer --- openfe/tests/setup/test_network_planning.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index dc1cfd1db..0364e9f4d 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -438,15 +438,16 @@ def test_minimal_redundant_network_redundant(self, minimal_redundant_network): len(network.graph.out_edges(node)) >= 2 - def test_minimal_redundant_network_unreachable(self, toluene_vs_others, lomap_old_mapper): + def test_minimal_spanning_network_unreachable(self, toluene_vs_others, lomap_old_mapper): toluene, others = toluene_vs_others - nimrod = openfe.SmallMoleculeComponent(mol_from_smiles("N")) + # lomap_old_mapper won't return anything for nimrod, so it won't have any edges + nimrod = openfe.SmallMoleculeComponent(mol_from_smiles("N"), name='nimrod') def scorer(mapping): return len(mapping.componentA_to_componentB) - with pytest.raises(RuntimeError, match="Unable to create edges"): - network = openfe.setup.ligand_network_planning.generate_minimal_redundant_network( + with pytest.raises(RuntimeError, match="Unable to create edges to some nodes: \[SmallMoleculeComponent\(name=nimrod\)\]"): + openfe.setup.ligand_network_planning.generate_minimal_spanning_network( ligands=others + [toluene, nimrod], mappers=[lomap_old_mapper], scorer=scorer From f3200f73f2d9438d9c997664be89a10d3034e63f Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Fri, 23 May 2025 15:40:43 -0700 Subject: [PATCH 35/56] fixing regex AGAIN --- openfe/tests/setup/test_network_planning.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 0364e9f4d..dbd28f506 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -130,7 +130,7 @@ def test_radial_network_index_error(self, toluene_vs_others, lomap_old_mapper): ligands=ligands, central_ligand=2077, mappers=lomap_old_mapper, scorer=None, ) - + def test_radial_network_with_scorer(self, toluene_vs_others, lomap_old_mapper): toluene, others = toluene_vs_others @@ -446,7 +446,8 @@ def test_minimal_spanning_network_unreachable(self, toluene_vs_others, lomap_old def scorer(mapping): return len(mapping.componentA_to_componentB) - with pytest.raises(RuntimeError, match="Unable to create edges to some nodes: \[SmallMoleculeComponent\(name=nimrod\)\]"): + err_str = r"ERROR: Unable to create edges for the following nodes: \[SmallMoleculeComponent\(name=nimrod\)\]" + with pytest.raises(RuntimeError, match=err_str): openfe.setup.ligand_network_planning.generate_minimal_spanning_network( ligands=others + [toluene, nimrod], mappers=[lomap_old_mapper], From aeb5f8d3353c309637ef150dad26b93d93f2f000 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 27 May 2025 16:17:25 -0700 Subject: [PATCH 36/56] some light formatting --- openfe/tests/setup/test_network_planning.py | 212 +++++++++++--------- 1 file changed, 116 insertions(+), 96 deletions(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index dbd28f506..f3be14388 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -48,11 +48,15 @@ def lomap_old_mapper() -> AtomMapper: element_change=True, seed='', shift=True ) + class TestRadialNetworkGenerator: - @pytest.mark.parametrize('as_list', [False, True]) + @pytest.mark.parametrize("as_list", [False, True]) def test_radial_network( - self, atom_mapping_basic_test_files, toluene_vs_others, - as_list, lomap_old_mapper, + self, + atom_mapping_basic_test_files, + toluene_vs_others, + as_list, + lomap_old_mapper, ): toluene, others = toluene_vs_others central_ligand_name = 'toluene' @@ -61,36 +65,47 @@ def test_radial_network( mapper = [mapper] network = openfe.setup.ligand_network_planning.generate_radial_network( - ligands=others, central_ligand=toluene, - mappers=mapper, scorer=None, + ligands=others, + central_ligand=toluene, + mappers=mapper, + scorer=None, ) # couple sanity checks assert len(network.nodes) == len(atom_mapping_basic_test_files) assert len(network.edges) == len(others) + # check that all ligands are present, i.e. we included everyone ligands_in_network = {mol.name for mol in network.nodes} assert ligands_in_network == set(atom_mapping_basic_test_files.keys()) - # check that every edge has the central ligand within - assert all((central_ligand_name in {mapping.componentA.name, mapping.componentB.name}) - for mapping in network.edges) - - @pytest.mark.parametrize('central_ligand_arg', [0, 'toluene']) - def test_radial_network_int_str(self, atom_mapping_basic_test_files, toluene_vs_others, - central_ligand_arg, lomap_old_mapper): + # check that every edge has the central ligand within + assert all((central_ligand_name in {mapping.componentA.name, mapping.componentB.name}) for mapping in network.edges) + + @pytest.mark.parametrize("central_ligand_arg", [0, "toluene"]) + def test_radial_network_int_str( + self, + atom_mapping_basic_test_files, + toluene_vs_others, + central_ligand_arg, + lomap_old_mapper, + ): # check that passing either an integer or string to radial network still works toluene, others = toluene_vs_others ligands = [toluene] + others network = openfe.setup.ligand_network_planning.generate_radial_network( - ligands=ligands, central_ligand=central_ligand_arg, - mappers=lomap_old_mapper, scorer=None, + ligands=ligands, + central_ligand=central_ligand_arg, + mappers=lomap_old_mapper, + scorer=None, ) assert len(network.nodes) == len(ligands) assert len(network.edges) == len(others) + # check that all ligands are present, i.e. we included everyone ligands_in_network = {mol.name for mol in network.nodes} assert ligands_in_network == set(atom_mapping_basic_test_files.keys()) + # check that every edge has the central ligand within assert all(('toluene' in {mapping.componentA.name, mapping.componentB.name}) for mapping in network.edges) @@ -101,10 +116,12 @@ def test_radial_network_bad_str(self, toluene_vs_others, lomap_old_mapper): toluene, others = toluene_vs_others ligands = [toluene] + others - with pytest.raises(ValueError, match='No ligand called'): - network = openfe.setup.ligand_network_planning.generate_radial_network( - ligands=ligands, central_ligand='unobtainium', - mappers=lomap_old_mapper, scorer=None, + with pytest.raises(ValueError, match="No ligand called"): + _ = openfe.setup.ligand_network_planning.generate_radial_network( + ligands=ligands, + central_ligand="unobtainium", + mappers=lomap_old_mapper, + scorer=None, ) @@ -113,10 +130,12 @@ def test_radial_network_multiple_str(self, toluene_vs_others, lomap_old_mapper): toluene, others = toluene_vs_others ligands = [toluene, toluene] + others - with pytest.raises(ValueError, match='Multiple ligands called'): - network = openfe.setup.ligand_network_planning.generate_radial_network( - ligands=ligands, central_ligand='toluene', - mappers=lomap_old_mapper, scorer=None, + with pytest.raises(ValueError, match="Multiple ligands called"): + _ = openfe.setup.ligand_network_planning.generate_radial_network( + ligands=ligands, + central_ligand="toluene", + mappers=lomap_old_mapper, + scorer=None, ) @@ -125,10 +144,12 @@ def test_radial_network_index_error(self, toluene_vs_others, lomap_old_mapper): toluene, others = toluene_vs_others ligands = [toluene] + others - with pytest.raises(ValueError, match='out of bounds'): - network = openfe.setup.ligand_network_planning.generate_radial_network( - ligands=ligands, central_ligand=2077, - mappers=lomap_old_mapper, scorer=None, + with pytest.raises(ValueError, match="out of bounds"): + _ = openfe.setup.ligand_network_planning.generate_radial_network( + ligands=ligands, + central_ligand=2077, + mappers=lomap_old_mapper, + scorer=None, ) @@ -175,7 +196,7 @@ def test_radial_network_failure(self, atom_mapping_basic_test_files, lomap_old_m nigel = openfe.SmallMoleculeComponent(mol_from_smiles('N')) with pytest.raises(RuntimeError, match='Could not generate any mapping!'): - network = openfe.setup.ligand_network_planning.generate_radial_network( + _ = openfe.setup.ligand_network_planning.generate_radial_network( ligands=[nigel], central_ligand=atom_mapping_basic_test_files['toluene'], mappers=[lomap_old_mapper], @@ -183,20 +204,17 @@ def test_radial_network_failure(self, atom_mapping_basic_test_files, lomap_old_m ) -@pytest.mark.parametrize('with_progress', [True, False]) -@pytest.mark.parametrize('with_scorer', [True, False]) -@pytest.mark.parametrize('extra_mapper', [True, False]) -def test_generate_maximal_network(toluene_vs_others, with_progress, - with_scorer, extra_mapper, lomap_old_mapper): +@pytest.mark.parametrize("with_progress", [True, False]) +@pytest.mark.parametrize("with_scorer", [True, False]) +@pytest.mark.parametrize("extra_mapper", [True, False]) +def test_generate_maximal_network( + toluene_vs_others, with_progress, with_scorer, extra_mapper, lomap_old_mapper +): """TODO: add test for throwing an error if there is no scorer but multiple mappings""" toluene, others = toluene_vs_others - if extra_mapper: - mappers = [ - lomap_old_mapper, - BadMapper() - ] + mappers = [lomap_old_mapper, BadMapper()] else: mappers = lomap_old_mapper @@ -236,13 +254,13 @@ def scorer(mapping): """Scores are designed to give the same mst everytime""" scores = { # MST edges - ('1,3,7-trimethylnaphthalene', '2,6-dimethylnaphthalene'): 3, - ('1-butyl-4-methylbenzene', '2-methyl-6-propylnaphthalene'): 3, - ('2,6-dimethylnaphthalene', '2-methyl-6-propylnaphthalene'): 3, - ('2,6-dimethylnaphthalene', '2-methylnaphthalene'): 3, - ('2,6-dimethylnaphthalene', '2-naftanol'): 3, - ('2,6-dimethylnaphthalene', 'methylcyclohexane'): 3, - ('2,6-dimethylnaphthalene', 'toluene'): 3, + ("1,3,7-trimethylnaphthalene", "2,6-dimethylnaphthalene"): 3, + ("1-butyl-4-methylbenzene", "2-methyl-6-propylnaphthalene"): 3, + ("2,6-dimethylnaphthalene", "2-methyl-6-propylnaphthalene"): 3, + ("2,6-dimethylnaphthalene", "2-methylnaphthalene"): 3, + ("2,6-dimethylnaphthalene", "2-naftanol"): 3, + ("2,6-dimethylnaphthalene", "methylcyclohexane"): 3, + ("2,6-dimethylnaphthalene", "toluene"): 3, } return scores.get((mapping.componentA.name, mapping.componentB.name), 1) @@ -283,8 +301,8 @@ def test_minimal_spanning_network(self, minimal_spanning_network, toluene_vs_oth tol, others = toluene_vs_others assert len(minimal_spanning_network.nodes) == len(others) + 1 for edge in minimal_spanning_network.edges: - assert edge.componentA_to_componentB != { - 0: 0} # lomap should find something + # lomap should find something + assert edge.componentA_to_componentB != {0: 0} def test_minimal_spanning_network_connectedness(self, minimal_spanning_network): @@ -304,22 +322,23 @@ def test_minimal_spanning_network_regression(self, minimal_spanning_network): (edge.componentA.name, edge.componentB.name) for edge in minimal_spanning_network.edges ) - ref = sorted([ - ('1,3,7-trimethylnaphthalene', '2,6-dimethylnaphthalene'), - ('1-butyl-4-methylbenzene', '2-methyl-6-propylnaphthalene'), - ('2,6-dimethylnaphthalene', '2-methyl-6-propylnaphthalene'), - ('2,6-dimethylnaphthalene', '2-methylnaphthalene'), - ('2,6-dimethylnaphthalene', '2-naftanol'), - ('2,6-dimethylnaphthalene', 'methylcyclohexane'), - ('2,6-dimethylnaphthalene', 'toluene'), - ]) + ref = sorted( + [ + ("1,3,7-trimethylnaphthalene", "2,6-dimethylnaphthalene"), + ("1-butyl-4-methylbenzene", "2-methyl-6-propylnaphthalene"), + ("2,6-dimethylnaphthalene", "2-methyl-6-propylnaphthalene"), + ("2,6-dimethylnaphthalene", "2-methylnaphthalene"), + ("2,6-dimethylnaphthalene", "2-naftanol"), + ("2,6-dimethylnaphthalene", "methylcyclohexane"), + ("2,6-dimethylnaphthalene", "toluene"), + ] + ) assert len(edge_ids) == len(ref) assert edge_ids == ref - def test_minimal_spanning_network_unreachable(self, toluene_vs_others, - lomap_old_mapper): + def test_minimal_spanning_network_unreachable(self, toluene_vs_others, lomap_old_mapper): toluene, others = toluene_vs_others nimrod = openfe.SmallMoleculeComponent(mol_from_smiles("N")) @@ -327,7 +346,7 @@ def scorer(mapping): return len(mapping.componentA_to_componentB) with pytest.raises(RuntimeError, match="Unable to create edges"): - network = openfe.setup.ligand_network_planning.generate_minimal_spanning_network( + _ = openfe.setup.ligand_network_planning.generate_minimal_spanning_network( ligands=others + [toluene, nimrod], mappers=[lomap_old_mapper], scorer=scorer @@ -343,21 +362,21 @@ def scorer(mapping): """Scores are designed to give the same mst everytime""" scores = { # MST edges - ('1,3,7-trimethylnaphthalene', '2,6-dimethylnaphthalene'): 3, - ('1-butyl-4-methylbenzene', '2-methyl-6-propylnaphthalene'): 3, - ('2,6-dimethylnaphthalene', '2-methyl-6-propylnaphthalene'): 3, - ('2,6-dimethylnaphthalene', '2-methylnaphthalene'): 3, - ('2,6-dimethylnaphthalene', '2-naftanol'): 3, - ('2,6-dimethylnaphthalene', 'methylcyclohexane'): 3, - ('2,6-dimethylnaphthalene', 'toluene'): 3, + ("1,3,7-trimethylnaphthalene", "2,6-dimethylnaphthalene"): 3, + ("1-butyl-4-methylbenzene", "2-methyl-6-propylnaphthalene"): 3, + ("2,6-dimethylnaphthalene", "2-methyl-6-propylnaphthalene"): 3, + ("2,6-dimethylnaphthalene", "2-methylnaphthalene"): 3, + ("2,6-dimethylnaphthalene", "2-naftanol"): 3, + ("2,6-dimethylnaphthalene", "methylcyclohexane"): 3, + ("2,6-dimethylnaphthalene", "toluene"): 3, # MST redundant edges - ('1,3,7-trimethylnaphthalene', '2-methyl-6-propylnaphthalene'): 2, - ('1-butyl-4-methylbenzene', '2,6-dimethylnaphthalene'): 2, - ('1-butyl-4-methylbenzene', 'toluene'): 2, - ('2-methyl-6-propylnaphthalene', '2-methylnaphthalene'): 2, - ('2-methylnaphthalene', '2-naftanol'): 2, - ('2-methylnaphthalene', 'methylcyclohexane'): 2, - ('2-methylnaphthalene', 'toluene'): 2, + ("1,3,7-trimethylnaphthalene", "2-methyl-6-propylnaphthalene"): 2, + ("1-butyl-4-methylbenzene", "2,6-dimethylnaphthalene"): 2, + ("1-butyl-4-methylbenzene", "toluene"): 2, + ("2-methyl-6-propylnaphthalene", "2-methylnaphthalene"): 2, + ("2-methylnaphthalene", "2-naftanol"): 2, + ("2-methylnaphthalene", "methylcyclohexane"): 2, + ("2-methylnaphthalene", "toluene"): 2, } return scores.get((mapping.componentA.name, mapping.componentB.name), 1) @@ -381,8 +400,8 @@ def test_minimal_redundant_network(self, minimal_redundant_network, toluene_vs_o (len(minimal_redundant_network.nodes) - 1) for edge in minimal_redundant_network.edges: - assert edge.componentA_to_componentB != { - 0: 0} # lomap should find something + # lomap should find something + assert edge.componentA_to_componentB != {0: 0} def test_minimal_redundant_network_connectedness(self, minimal_redundant_network): @@ -398,8 +417,7 @@ def test_minimal_redundant_network_connectedness(self, minimal_redundant_network def test_redundant_vs_spanning_network(self, minimal_redundant_network, minimal_spanning_network): # when setting minimal redundant network to only take one MST, it should have as many # edges as the regular minimum spanning network - assert 2 * len(minimal_spanning_network.edges) == len( - minimal_redundant_network.edges) + assert 2 * len(minimal_spanning_network.edges) == len(minimal_redundant_network.edges) def test_minimal_redundant_network_edges(self, minimal_redundant_network): @@ -409,22 +427,24 @@ def test_minimal_redundant_network_edges(self, minimal_redundant_network): (edge.componentA.name, edge.componentB.name) for edge in minimal_redundant_network.edges ) - ref = sorted([ - ('1,3,7-trimethylnaphthalene', '2,6-dimethylnaphthalene'), - ('1,3,7-trimethylnaphthalene', '2-methyl-6-propylnaphthalene'), - ('1-butyl-4-methylbenzene', '2,6-dimethylnaphthalene'), - ('1-butyl-4-methylbenzene', '2-methyl-6-propylnaphthalene'), - ('1-butyl-4-methylbenzene', 'toluene'), - ('2,6-dimethylnaphthalene', '2-methyl-6-propylnaphthalene'), - ('2,6-dimethylnaphthalene', '2-methylnaphthalene'), - ('2,6-dimethylnaphthalene', '2-naftanol'), - ('2,6-dimethylnaphthalene', 'methylcyclohexane'), - ('2,6-dimethylnaphthalene', 'toluene'), - ('2-methyl-6-propylnaphthalene', '2-methylnaphthalene'), - ('2-methylnaphthalene', '2-naftanol'), - ('2-methylnaphthalene', 'methylcyclohexane'), - ('2-methylnaphthalene', 'toluene') - ]) + ref = sorted( + [ + ("1,3,7-trimethylnaphthalene", "2,6-dimethylnaphthalene"), + ("1,3,7-trimethylnaphthalene", "2-methyl-6-propylnaphthalene"), + ("1-butyl-4-methylbenzene", "2,6-dimethylnaphthalene"), + ("1-butyl-4-methylbenzene", "2-methyl-6-propylnaphthalene"), + ("1-butyl-4-methylbenzene", "toluene"), + ("2,6-dimethylnaphthalene", "2-methyl-6-propylnaphthalene"), + ("2,6-dimethylnaphthalene", "2-methylnaphthalene"), + ("2,6-dimethylnaphthalene", "2-naftanol"), + ("2,6-dimethylnaphthalene", "methylcyclohexane"), + ("2,6-dimethylnaphthalene", "toluene"), + ("2-methyl-6-propylnaphthalene", "2-methylnaphthalene"), + ("2-methylnaphthalene", "2-naftanol"), + ("2-methylnaphthalene", "methylcyclohexane"), + ("2-methylnaphthalene", "toluene"), + ] + ) assert len(edge_ids) == len(ref) assert edge_ids == ref @@ -542,7 +562,7 @@ def test_network_from_indices_indexerror( requested = [(20, 1), (2, 3)] with pytest.raises(IndexError, match="Invalid ligand index"): - network = openfe.setup.ligand_network_planning.generate_network_from_indices( + _ = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligs, indices=requested, mapper=lomap_old_mapper, @@ -610,7 +630,7 @@ def test_network_from_external_unknown_edge(file_fixture, loader, request, ligs = [l for l in benzene_modifications.values() if l.name != 'phenol'] with pytest.raises(KeyError, match="Invalid name"): - network = loader( + _ = loader( ligands=ligs, mappers=openfe.LomapAtomMapper(), network_file=network_file, @@ -635,7 +655,7 @@ def test_bad_orion_network(benzene_modifications, tmpdir): f.write(BAD_ORION_NETWORK) with pytest.raises(KeyError, match="line does not match"): - network = openfe.setup.ligand_network_planning.load_orion_network( + _ = openfe.setup.ligand_network_planning.load_orion_network( ligands=[l for l in benzene_modifications.values()], mappers=openfe.LomapAtomMapper(), network_file='bad_orion_net.dat', @@ -658,7 +678,7 @@ def test_bad_edges_network(benzene_modifications, tmpdir): f.write(BAD_EDGES) with pytest.raises(KeyError, match="line does not match"): - network = openfe.setup.ligand_network_planning.load_fepplus_network( + _ = openfe.setup.ligand_network_planning.load_fepplus_network( ligands=[l for l in benzene_modifications.values()], mappers=openfe.LomapAtomMapper(), network_file='bad_edges.edges', From b7478bfa4022da7b565c2e80bbeaac7f66a7ac8f Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 27 May 2025 16:42:06 -0700 Subject: [PATCH 37/56] cleaning up diff --- openfe/setup/ligand_network_planning.py | 56 ++++++++----------- ...est_relative_alchemical_network_planner.py | 1 + openfe/tests/setup/test_network_planning.py | 45 +++++++-------- 3 files changed, 48 insertions(+), 54 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index dd0c16532..2b3695a5d 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -28,14 +28,13 @@ def _hasten_lomap(mapper, ligands): - """take a mapper and some ligands, put a common core arg into the mapper""" + """take a mapper and some ligands, put a common core arg into the mapper """ if mapper.seed: return mapper try: core = _find_common_core([m.to_rdkit() for m in ligands], element_change=mapper.element_change) - except RuntimeError: # in case MCS throws a hissy fit core = "" @@ -310,8 +309,8 @@ def generate_minimal_redundant_network( def generate_network_from_names( ligands: list[SmallMoleculeComponent], - mappers: AtomMapper, # TODO: rename this to 'mapper' and only accept one - names: list[tuple[str, str]], # TODO: rename to 'edges' or `edge_names` + mapper: AtomMapper, + names: list[tuple[str, str]], ) -> LigandNetwork: """ Generate a :class:`.LigandNetwork` by specifying edges as tuples of names. @@ -320,7 +319,7 @@ def generate_network_from_names( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mappers: AtomMapper + mapper: AtomMapper the atom mapper to use to construct edges names : list of tuples of names the edges to form where the values refer to names of the small molecules, @@ -341,7 +340,7 @@ def generate_network_from_names( """ nodes = list(ligands) - network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) + network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) network = network_planner.generate_network_from_names( components=nodes, names=names @@ -389,7 +388,7 @@ def generate_network_from_indices( def load_orion_network( ligands: list[SmallMoleculeComponent], - mappers: AtomMapper, + mapper: AtomMapper, network_file: Union[str, Path], ) -> LigandNetwork: """Load a :class:`.LigandNetwork` from an Orion NES network file. @@ -398,7 +397,7 @@ def load_orion_network( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mappers: AtomMapper + mapper: AtomMapper the atom mapper to use to construct edges network_file : str path to NES network file. @@ -413,32 +412,28 @@ def load_orion_network( If an unexpected line format is encountered. """ - with open(network_file, "r") as f: - network_lines = [ - line.strip().split(" ") for line in f if not line.startswith("#") - ] + with open(network_file, 'r') as f: + network_lines = [l.strip().split(' ') for l in f + if not l.startswith('#')] names = [] for entry in network_lines: if len(entry) != 3 or entry[1] != ">>": - errmsg = ( - "line does not match expected name >> name format: " f"{entry}" - ) + errmsg = ("line does not match expected name >> name format: " + f"{entry}") raise KeyError(errmsg) names.append((entry[0], entry[2])) - network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) - network = network_planner.generate_network_from_names( - components=ligands, names=names - ) + network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) + network = network_planner.generate_network_from_names(components=ligands, names=names) return network def load_fepplus_network( ligands: list[SmallMoleculeComponent], - mappers: AtomMapper, + mapper: AtomMapper, network_file: Union[str, Path], ) -> LigandNetwork: """Load a :class:`.LigandNetwork` from an FEP+ edges network file. @@ -447,7 +442,7 @@ def load_fepplus_network( ---------- ligands : list of SmallMoleculeComponent the small molecules to place into the network - mappers: AtomMapper + mapper: AtomMapper the atom mapper to use to construct edges network_file : str path to edges network file. @@ -462,23 +457,20 @@ def load_fepplus_network( If an unexpected line format is encountered. """ - with open(network_file, "r") as f: + with open(network_file, 'r') as f: network_lines = [l.split() for l in f.readlines()] names = [] for entry in network_lines: - if len(entry) != 5 or entry[1] != "#" or entry[3] != "->": - errmsg = ( - "line does not match expected format " - f"hash:hash # name -> name\n" - "line format: {entry}" - ) + if len(entry) != 5 or entry[1] != '#' or entry[3] != '->': + errmsg = ("line does not match expected format " + "hash:hash # name -> name\n" + f"line format: {entry}") raise KeyError(errmsg) names.append((entry[2], entry[4])) - network_planner = ExplicitNetworkGenerator(mappers=mappers, scorer=None) - network = network_planner.generate_network_from_names( - components=ligands, names=names - ) + network_planner = ExplicitNetworkGenerator(mappers=mapper, scorer=None) + network = network_planner.generate_network_from_names(components=ligands, names=names) + return network diff --git a/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py b/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py index ab3a74163..80351a3da 100644 --- a/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py +++ b/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py @@ -39,6 +39,7 @@ def test_rbfe_alchemical_network_planner_call(atom_mapping_basic_test_files, T4_ ) # TODO: methylcyclohexane isn't included in this network, possibly because of a score tie in _map_scoring, which means it doesn't get included in maximal_network_generator's "mappings" + assert isinstance(alchem_network, AlchemicalNetwork) edges = alchem_network.edges diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index ecfad2aa6..5628d7239 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -216,8 +216,8 @@ def test_radial_network_failure(self, atom_mapping_basic_test_files, lomap_old_m ) -@pytest.mark.parametrize("with_progress", [True, False]) -@pytest.mark.parametrize("with_scorer", [True, False]) +@pytest.mark.parametrize('with_progress', [True, False]) +@pytest.mark.parametrize('with_scorer', [True, False]) @pytest.mark.parametrize("extra_mapper", [True, False]) def test_generate_maximal_network( toluene_vs_others, with_progress, with_scorer, extra_mapper, lomap_old_mapper @@ -383,21 +383,21 @@ def scorer(mapping): """Scores are designed to give the same mst every time""" scores = { # MST edges - ("1,3,7-trimethylnaphthalene", "2,6-dimethylnaphthalene"): 3, - ("1-butyl-4-methylbenzene", "2-methyl-6-propylnaphthalene"): 3, - ("2,6-dimethylnaphthalene", "2-methyl-6-propylnaphthalene"): 3, - ("2,6-dimethylnaphthalene", "2-methylnaphthalene"): 3, - ("2,6-dimethylnaphthalene", "2-naftanol"): 3, - ("2,6-dimethylnaphthalene", "methylcyclohexane"): 3, - ("2,6-dimethylnaphthalene", "toluene"): 3, + ('1,3,7-trimethylnaphthalene', '2,6-dimethylnaphthalene'): 3, + ('1-butyl-4-methylbenzene', '2-methyl-6-propylnaphthalene'): 3, + ('2,6-dimethylnaphthalene', '2-methyl-6-propylnaphthalene'): 3, + ('2,6-dimethylnaphthalene', '2-methylnaphthalene'): 3, + ('2,6-dimethylnaphthalene', '2-naftanol'): 3, + ('2,6-dimethylnaphthalene', 'methylcyclohexane'): 3, + ('2,6-dimethylnaphthalene', 'toluene'): 3, # MST redundant edges - ("1,3,7-trimethylnaphthalene", "2-methyl-6-propylnaphthalene"): 2, - ("1-butyl-4-methylbenzene", "2,6-dimethylnaphthalene"): 2, - ("1-butyl-4-methylbenzene", "toluene"): 2, - ("2-methyl-6-propylnaphthalene", "2-methylnaphthalene"): 2, - ("2-methylnaphthalene", "2-naftanol"): 2, - ("2-methylnaphthalene", "methylcyclohexane"): 2, - ("2-methylnaphthalene", "toluene"): 2, + ('1,3,7-trimethylnaphthalene', '2-methyl-6-propylnaphthalene'): 2, + ('1-butyl-4-methylbenzene', '2,6-dimethylnaphthalene'): 2, + ('1-butyl-4-methylbenzene', 'toluene'): 2, + ('2-methyl-6-propylnaphthalene', '2-methylnaphthalene'): 2, + ('2-methylnaphthalene', '2-naftanol'): 2, + ('2-methylnaphthalene', 'methylcyclohexane'): 2, + ('2-methylnaphthalene', 'toluene'): 2, } return scores.get((mapping.componentA.name, mapping.componentB.name), 1) @@ -480,6 +480,7 @@ def test_minimal_redundant_network_redundant(self, minimal_redundant_network): >= 2 ) + def test_minimal_spanning_network_unreachable(self, toluene_vs_others, lomap_old_mapper): toluene, others = toluene_vs_others # lomap_old_mapper won't return anything for nimrod, so it won't have any edges @@ -490,7 +491,7 @@ def scorer(mapping): err_str = r"ERROR: Unable to create edges for the following nodes: \[SmallMoleculeComponent\(name=nimrod\)\]" with pytest.raises(RuntimeError, match=err_str): - openfe.setup.ligand_network_planning.generate_minimal_spanning_network( + _ = openfe.setup.ligand_network_planning.generate_minimal_spanning_network( ligands=others + [toluene, nimrod], mappers=[lomap_old_mapper], scorer=scorer @@ -508,7 +509,7 @@ def test_network_from_names(self, atom_mapping_basic_test_files, lomap_old_mappe network = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligs, names=requested, - mappers=lomap_old_mapper, + mapper=lomap_old_mapper, ) # TODO: konnektor only constructs based on edges, doesn't allow for disconnected networks assert len(network.nodes) == len(ligs) @@ -618,7 +619,7 @@ def test_network_from_external(file_fixture, loader, request, benzene_modificati network = loader( ligands=[l for l in benzene_modifications.values()], - mappers=openfe.LomapAtomMapper(), + mapper=openfe.LomapAtomMapper(), network_file=network_file, ) @@ -653,7 +654,7 @@ def test_network_from_external_unknown_edge(file_fixture, loader, request, with pytest.raises(KeyError, match="Invalid name"): _ = loader( ligands=ligs, - mappers=openfe.LomapAtomMapper(), + mapper=openfe.LomapAtomMapper(), network_file=network_file, ) @@ -678,7 +679,7 @@ def test_bad_orion_network(benzene_modifications, tmpdir): with pytest.raises(KeyError, match="line does not match"): _ = openfe.setup.ligand_network_planning.load_orion_network( ligands=[l for l in benzene_modifications.values()], - mappers=openfe.LomapAtomMapper(), + mapper=openfe.LomapAtomMapper(), network_file='bad_orion_net.dat', ) @@ -701,6 +702,6 @@ def test_bad_edges_network(benzene_modifications, tmpdir): with pytest.raises(KeyError, match="line does not match"): _ = openfe.setup.ligand_network_planning.load_fepplus_network( ligands=[l for l in benzene_modifications.values()], - mappers=openfe.LomapAtomMapper(), + mapper=openfe.LomapAtomMapper(), network_file='bad_edges.edges', ) From b4d97177ce42b64823608a7d3f7ca446acf9ed0d Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Thu, 29 May 2025 14:24:19 -0700 Subject: [PATCH 38/56] central_ligand -> central component for msg regex --- openfe/tests/setup/test_network_planning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 5628d7239..1fc455635 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -156,7 +156,7 @@ def test_radial_network_self_central(self, toluene_vs_others, lomap_old_mapper): """issue #544, include the central ligand in "ligands", shouldn't get a self-edge""" ligs = [toluene_vs_others[0]] + toluene_vs_others[1] - with pytest.warns(UserWarning, match="The central_ligand"): + with pytest.warns(UserWarning, match="The central component 'toluene' was"): network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=ligs, central_ligand=ligs[0], From 39fe15928e780670dafc43208a98db748426238a Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Thu, 29 May 2025 16:11:30 -0700 Subject: [PATCH 39/56] regex --- openfe/tests/setup/test_network_planning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 1fc455635..c50b2c786 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -156,7 +156,7 @@ def test_radial_network_self_central(self, toluene_vs_others, lomap_old_mapper): """issue #544, include the central ligand in "ligands", shouldn't get a self-edge""" ligs = [toluene_vs_others[0]] + toluene_vs_others[1] - with pytest.warns(UserWarning, match="The central component 'toluene' was"): + with pytest.warns(UserWarning, match="The central component 'toluene'"): network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=ligs, central_ligand=ligs[0], From 5030386f883259e861c76fc4c11a9d5897355d60 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 3 Jun 2025 08:56:01 -0700 Subject: [PATCH 40/56] updating regexes to match new konnektor behavior --- openfe/tests/setup/test_network_planning.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 1016f9f7b..992849e9a 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -214,7 +214,7 @@ def test_radial_network_self_central(self, toluene_vs_others, lomap_old_mapper): toluene, others = toluene_vs_others ligands = [toluene] + others - with pytest.warns(UserWarning, match="The central_ligand toluene was also found in the list of ligands"): + with pytest.warns(UserWarning, match="The central component 'toluene' is present in the list of components"): network = openfe.setup.ligand_network_planning.generate_radial_network( ligands=ligands, central_ligand=toluene, @@ -471,7 +471,7 @@ def test_minimal_spanning_network_unreachable(self, toluene_vs_others, lomap_old scorer = simple_scorer - with pytest.raises(RuntimeError, match=r"Unable to create edges to some nodes: \[SmallMoleculeComponent\(name=nimrod\)\]"): + with pytest.raises(RuntimeError, match=r"Unable to create edges for the following nodes: \[SmallMoleculeComponent\(name=nimrod\)\]"): _ = openfe.setup.ligand_network_planning.generate_minimal_spanning_network( ligands=others + [toluene, nimrod], mappers=[lomap_old_mapper], @@ -579,32 +579,27 @@ class TestGenerateNetworkFromNames: def test_generate_network_from_names(self, atom_mapping_basic_test_files, lomap_old_mapper): ligands = list(atom_mapping_basic_test_files.values()) - requested = [ + requested_names = [ ('toluene', '2-naftanol'), ('2-methylnaphthalene', '2-naftanol'), ] network = openfe.setup.ligand_network_planning.generate_network_from_names( ligands=ligands, - names=requested, + names=requested_names, mapper=lomap_old_mapper, ) # TODO: konnektor only constructs based on edges, doesn't allow for disconnected networks - assert len(network.nodes) == len(ligs) + assert len(network.nodes) == len(ligands) assert len(network.edges) == 2 - actual_edges = [(e.componentA.name, e.componentB.name) - for e in network.edges] - assert set(requested) == set(actual_edges) expected_node_names = {c.name for c in ligands} actual_node_names = {n.name for n in network.nodes} - assert len(network.nodes) == len(ligands) assert actual_node_names == expected_node_names - assert len(network.edges) == 2 actual_edges = {(e.componentA.name, e.componentB.name) for e in network.edges} - assert set(requested) == actual_edges + assert set(requested_names) == actual_edges def test_generate_network_from_names_bad_name_error(self, atom_mapping_basic_test_files, lomap_old_mapper): ligands = list(atom_mapping_basic_test_files.values()) From 107b15b897f369537e6c865a7620f298edc6e85a Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 3 Jun 2025 09:14:37 -0700 Subject: [PATCH 41/56] formatting :( --- openfe/setup/ligand_network_planning.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index e316db33d..da113e02c 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -122,10 +122,8 @@ def generate_radial_network( ligands = list(ligands) possibles = [l for l in ligands if l.name == central_ligand] if not possibles: - raise ValueError( - f"No ligand called '{central_ligand}' " - f"available: {', '.join(l.name for l in ligands)}" - ) + raise ValueError(f"No ligand called '{central_ligand}' " + f"available: {', '.join(l.name for l in ligands)}") if len(possibles) > 1: raise ValueError(f"Multiple ligands called '{central_ligand}'") central_ligand = possibles[0] @@ -183,10 +181,8 @@ def generate_maximal_network( """ if isinstance(mappers, AtomMapper): mappers = [mappers] - mappers = [ - _hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) else m - for m in mappers - ] + mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) + else m for m in mappers] nodes = list(ligands) # Construct network @@ -232,10 +228,8 @@ def generate_minimal_spanning_network( """ if isinstance(mappers, AtomMapper): mappers = [mappers] - mappers = [ - _hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) else m - for m in mappers - ] + mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) + else m for m in mappers] nodes = list(ligands) # Construct network @@ -290,10 +284,8 @@ def generate_minimal_redundant_network( """ if isinstance(mappers, AtomMapper): mappers = [mappers] - mappers = [ - _hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) else m - for m in mappers - ] + mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) + else m for m in mappers] nodes = list(ligands) # Construct network From 93f045987201874eb03bb31fe90789517066e191 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 3 Jun 2025 09:16:13 -0700 Subject: [PATCH 42/56] formatting :( --- openfe/setup/ligand_network_planning.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index da113e02c..ef11779ac 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -114,10 +114,8 @@ def generate_radial_network( central_ligand = ligands[central_ligand] ligands.remove(central_ligand) except IndexError: - raise ValueError( - f"index '{central_ligand}' out of bounds, there are " - f"{len(ligands)} ligands" - ) + raise ValueError(f"index '{central_ligand}' out of bounds, there are " + f"{len(ligands)} ligands") elif isinstance(central_ligand, str): ligands = list(ligands) possibles = [l for l in ligands if l.name == central_ligand] From 43cbc1d6681db57ef0403a22b450c25a877fc762 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 3 Jun 2025 13:17:18 -0700 Subject: [PATCH 43/56] add connectedness check --- openfe/setup/ligand_network_planning.py | 13 +++++++++++++ openfe/tests/setup/test_network_planning.py | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index ef11779ac..48101a9e3 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -139,6 +139,19 @@ def generate_radial_network( components=ligands, central_component=central_ligand ) + if network.is_connected(): + connected_nodes = network.nodes + else: + connected_nodes = max(nx.weakly_connected_components(network.graph), key=len) + + # check for disconnected nodes + missing_nodes = set(ligands + [central_ligand]) - set(connected_nodes) + missing_node_names = [node.name for node in missing_nodes] + if missing_nodes: + raise RuntimeError( + f"ERROR: No mapping found between the central ligand ('{central_ligand.name}') and the following node(s): {missing_node_names}" + ) + return network diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 992849e9a..8ead81686 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -293,7 +293,8 @@ def test_radial_network_no_mapping_failure(self, toluene_vs_others, lomap_old_ma # lomap cannot make a mapping to nimrod, and will return nothing for the (toluene, nimrod) pair nimrod = openfe.SmallMoleculeComponent(mol_from_smiles('N'), name='nimrod') - with pytest.raises(ValueError, match=r'No mapping found for SmallMoleculeComponent\(name=nimrod\)'): + err_str = r"No mapping found between the central ligand \('toluene'\) and the following node\(s\): \['nimrod'\]" + with pytest.raises(RuntimeError, match=err_str): _ = openfe.setup.ligand_network_planning.generate_radial_network( ligands=others + [nimrod], central_ligand=toluene, From 385dbf171e0b293c862aa01a6f18128dc0108952 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 3 Jun 2025 13:47:47 -0700 Subject: [PATCH 44/56] updating TODO --- .../test_relative_alchemical_network_planner.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py b/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py index 083282230..58f02502f 100644 --- a/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py +++ b/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py @@ -33,7 +33,9 @@ def test_rbfe_alchemical_network_planner_call(atom_mapping_basic_test_files, T4_ solvent=SolventComponent(), protein=T4_protein_component, ) - # TODO: methylcyclohexane isn't included in this network, possibly because of a score tie in _map_scoring, which means it doesn't get included in maximal_network_generator's "mappings" + # TODO:this network is slightly different when using konnektor, + # possibly because of a score tie in _map_scoring, + # which means it doesn't get included in maximal_network_generator's "mappings" assert isinstance(alchem_network, AlchemicalNetwork) edges = alchem_network.edges From 890da29f1e679eaaeec8a8df6f52e5c8632094f4 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Thu, 5 Jun 2025 12:24:14 -0700 Subject: [PATCH 45/56] fix comment --- .../test_relative_alchemical_network_planner.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py b/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py index 58f02502f..cecfa585a 100644 --- a/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py +++ b/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py @@ -35,9 +35,7 @@ def test_rbfe_alchemical_network_planner_call(atom_mapping_basic_test_files, T4_ ) # TODO:this network is slightly different when using konnektor, # possibly because of a score tie in _map_scoring, - # which means it doesn't get included in maximal_network_generator's "mappings" assert isinstance(alchem_network, AlchemicalNetwork) - edges = alchem_network.edges assert len(edges) == 14 # we build 2envs * (8 ligands - 1) = 14 relative edges. From 9d58bbafe2f42b6031f692d267c2c610ea1cd4d8 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 10 Jun 2025 15:11:36 -0700 Subject: [PATCH 46/56] update progress bar docstring --- openfe/setup/ligand_network_planning.py | 32 ++++++++++--------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 48101a9e3..075fb9ad1 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -79,10 +79,8 @@ def generate_radial_network( a callable which returns a float for any LigandAtomMapping. Used to assign scores to potential mappings; higher scores indicate better mappings. - progress : Union[bool, Callable[Iterable], Iterable] - progress bar: if False, no progress bar will be shown. If True, use a - tqdm progress bar that only appears after 1.5 seconds. You can also - provide a custom progress bar wrapper as a callable. + progress : bool + If True, show a tqdm progress bar. (default=True) n_processes: int number of cpu processes to use if parallelizing network generation. @@ -159,7 +157,7 @@ def generate_maximal_network( ligands: Iterable[SmallMoleculeComponent], mappers: Union[AtomMapper, Iterable[AtomMapper]], scorer: Optional[Callable[[LigandAtomMapping], float]] = None, - progress: Union[bool, Callable[[Iterable], Iterable]] = True, + progress: bool = True, n_processes: int = 1, ) -> LigandNetwork: """ @@ -183,12 +181,10 @@ def generate_maximal_network( but many can be given. scorer : Scoring function any callable which takes a LigandAtomMapping and returns a float - progress : Union[bool, Callable[Iterable], Iterable] - progress bar: if False, no progress bar will be shown. If True, use a - tqdm progress bar that only appears after 1.5 seconds. You can also - provide a custom progress bar wrapper as a callable. + progress : bool + If True, show a tqdm progress bar. (default=True) n_processes: int - number of cpu processes to use if parallelizing network generation. + number of cpu processes to use if parallelizing network generation. """ if isinstance(mappers, AtomMapper): mappers = [mappers] @@ -214,7 +210,7 @@ def generate_minimal_spanning_network( mappers: Union[AtomMapper, Iterable[AtomMapper]], # TODO: scorer is currently required, but not actually necessary. scorer: Callable[[LigandAtomMapping], float], - progress: Union[bool, Callable[[Iterable], Iterable]] = True, + progress: bool = True, n_processes: int = 1, ) -> LigandNetwork: """ @@ -230,10 +226,8 @@ def generate_minimal_spanning_network( highest score edges scorer : Scoring function any callable which takes a LigandAtomMapping and returns a float - progress : Union[bool, Callable[Iterable], Iterable] - progress bar: if False, no progress bar will be shown. If True, use a - tqdm progress bar that only appears after 1.5 seconds. You can also - provide a custom progress bar wrapper as a callable. + progress : bool + If True, show a tqdm progress bar. (default=True) n_processes: int number of cpu processes to use if parallelizing network generation. """ @@ -260,7 +254,7 @@ def generate_minimal_redundant_network( ligands: Iterable[SmallMoleculeComponent], mappers: Union[AtomMapper, Iterable[AtomMapper]], scorer: Callable[[LigandAtomMapping], float], - progress: Union[bool, Callable[[Iterable], Iterable]] = True, + progress: bool = True, mst_num: int = 2, n_processes: int = 1, ) -> LigandNetwork: @@ -281,10 +275,8 @@ def generate_minimal_redundant_network( highest score edges scorer : Scoring function any callable which takes a LigandAtomMapping and returns a float - progress : Union[bool, Callable[Iterable], Iterable] - progress bar: if False, no progress bar will be shown. If True, use a - tqdm progress bar that only appears after 1.5 seconds. You can also - provide a custom progress bar wrapper as a callable. + progress : bool + If True, show a tqdm progress bar. (default=True) mst_num : int Minimum Spanning Tree number: the number of minimum spanning trees to generate. If two, the second-best edges are included in the returned From 14619e5ccf334c9e197e0a91afa162603ef95d7d Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 10 Jun 2025 15:13:22 -0700 Subject: [PATCH 47/56] fix ambiguous var name --- openfe/setup/ligand_network_planning.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 075fb9ad1..56d275e45 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -116,10 +116,10 @@ def generate_radial_network( f"{len(ligands)} ligands") elif isinstance(central_ligand, str): ligands = list(ligands) - possibles = [l for l in ligands if l.name == central_ligand] + possibles = [lig for lig in ligands if lig.name == central_ligand] if not possibles: raise ValueError(f"No ligand called '{central_ligand}' " - f"available: {', '.join(l.name for l in ligands)}") + f"available: {', '.join(lig.name for lig in ligands)}") if len(possibles) > 1: raise ValueError(f"Multiple ligands called '{central_ligand}'") central_ligand = possibles[0] From ea5c868361390fdf48d96351cacdb02837746083 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 10 Jun 2025 15:23:53 -0700 Subject: [PATCH 48/56] remove unused imports --- openfe/setup/ligand_network_planning.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 56d275e45..9dbfb8065 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -3,13 +3,8 @@ from pathlib import Path from typing import Iterable, Callable, Optional, Union -import itertools -from collections import Counter -import functools -import warnings import networkx as nx -from tqdm.auto import tqdm from gufe import SmallMoleculeComponent, AtomMapper from openfe.setup import LigandNetwork From 8d37c6ea8ea3e931ac9bb4cf764559cf62d5d0d7 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 10 Jun 2025 16:01:36 -0700 Subject: [PATCH 49/56] fix mypy checks --- openfe/setup/ligand_network_planning.py | 4 ++-- openfe/tests/setup/test_network_planning.py | 14 -------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/openfe/setup/ligand_network_planning.py b/openfe/setup/ligand_network_planning.py index 9dbfb8065..97bdeed09 100644 --- a/openfe/setup/ligand_network_planning.py +++ b/openfe/setup/ligand_network_planning.py @@ -99,10 +99,11 @@ def generate_radial_network( mappers = [_hasten_lomap(m, ligands) if isinstance(m, LomapAtomMapper) else m for m in mappers] + ligands = list(ligands) + # handle central_ligand arg possibilities # after this, central_ligand is resolved to a SmallMoleculeComponent if isinstance(central_ligand, int): - ligands = list(ligands) try: central_ligand = ligands[central_ligand] ligands.remove(central_ligand) @@ -110,7 +111,6 @@ def generate_radial_network( raise ValueError(f"index '{central_ligand}' out of bounds, there are " f"{len(ligands)} ligands") elif isinstance(central_ligand, str): - ligands = list(ligands) possibles = [lig for lig in ligands if lig.name == central_ligand] if not possibles: raise ValueError(f"No ligand called '{central_ligand}' " diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 8ead81686..467b7d0f7 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -376,20 +376,6 @@ def scorer(mapping): ligands=others + [toluene], mappers=mappers, scorer=scorer ) return network -class TestMinimalSpanningNetworkGenerator: - @pytest.mark.parametrize("multi_mappers", [False, True]) - def test_minimal_spanning_network_mappers( - self, atom_mapping_basic_test_files, multi_mappers, lomap_old_mapper - ): - ligands = [ - atom_mapping_basic_test_files["toluene"], - atom_mapping_basic_test_files["2-naftanol"], - ] - - if multi_mappers: - mappers = [BadMapper(), lomap_old_mapper] - else: - mappers = lomap_old_mapper class TestMinimalSpanningNetworkGenerator: @pytest.mark.parametrize("multi_mappers", [False, True]) From 311f133a670cb8414f336905b0d27b863bd3d293 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Wed, 11 Jun 2025 09:40:13 -0700 Subject: [PATCH 50/56] remove unused testing code --- openfe/tests/setup/test_network_planning.py | 27 --------------------- 1 file changed, 27 deletions(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 467b7d0f7..a0a933c09 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -234,8 +234,6 @@ def test_radial_network_with_scorer(self, toluene_vs_others, lomap_old_mapper, s """Test that the scorer chooses the mapper with the best score (in this case, the LOMAP mapper).""" toluene, others = toluene_vs_others - def scorer(mapping): - return 1-1/len(mapping.componentA_to_componentB) mappers = [BadMapper(), lomap_old_mapper] scorer = simple_scorer @@ -352,31 +350,6 @@ def test_generate_maximal_network( assert "score" not in edge.annotations assert 'score' not in edge.annotations -@pytest.fixture() -def minimal_spanning_network(toluene_vs_others, lomap_old_mapper): - toluene, others = toluene_vs_others - - mappers = [lomap_old_mapper] - - def scorer(mapping): - """Scores are designed to give the same mst everytime""" - scores = { - # MST edges - ("1,3,7-trimethylnaphthalene", "2,6-dimethylnaphthalene"): 3, - ("1-butyl-4-methylbenzene", "2-methyl-6-propylnaphthalene"): 3, - ("2,6-dimethylnaphthalene", "2-methyl-6-propylnaphthalene"): 3, - ("2,6-dimethylnaphthalene", "2-methylnaphthalene"): 3, - ("2,6-dimethylnaphthalene", "2-naftanol"): 3, - ("2,6-dimethylnaphthalene", "methylcyclohexane"): 3, - ("2,6-dimethylnaphthalene", "toluene"): 3, - } - return scores.get((mapping.componentA.name, mapping.componentB.name), 1) - - network = openfe.setup.ligand_network_planning.generate_minimal_spanning_network( - ligands=others + [toluene], mappers=mappers, scorer=scorer - ) - return network - class TestMinimalSpanningNetworkGenerator: @pytest.mark.parametrize("multi_mappers", [False, True]) def test_minimal_spanning_network(self, toluene_vs_others, multi_mappers, lomap_old_mapper, simple_scorer): From 8aba9b3efea5011880c18950903710db79febf74 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 17 Jun 2025 08:56:58 -0700 Subject: [PATCH 51/56] pin konnektor to 0.2 --- environment.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/environment.yml b/environment.yml index b5a07aab7..1e3e9948d 100644 --- a/environment.yml +++ b/environment.yml @@ -7,7 +7,7 @@ dependencies: - coverage - duecredit<0.10 - kartograf>=1.0.0 - # - konnektor + - konnektor~=0.2.0 - lomap2>=3.2.1 - networkx - numpy<2.0.0 @@ -53,4 +53,3 @@ dependencies: - threadpoolctl - pip: - git+https://github.com/OpenFreeEnergy/gufe@main - - git+https://github.com/OpenFreeEnergy/konnektor@main From b9fefa873498d2daac67a0ce82b6218c800a8a09 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 17 Jun 2025 10:41:17 -0700 Subject: [PATCH 52/56] remove completed TODOs --- .../test_relative_alchemical_network_planner.py | 2 -- openfe/tests/setup/test_network_planning.py | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py b/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py index cecfa585a..20eb89c62 100644 --- a/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py +++ b/openfe/tests/setup/alchemical_network_planner/test_relative_alchemical_network_planner.py @@ -33,8 +33,6 @@ def test_rbfe_alchemical_network_planner_call(atom_mapping_basic_test_files, T4_ solvent=SolventComponent(), protein=T4_protein_component, ) - # TODO:this network is slightly different when using konnektor, - # possibly because of a score tie in _map_scoring, assert isinstance(alchem_network, AlchemicalNetwork) edges = alchem_network.edges diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index a0a933c09..0388aa1f1 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -312,7 +312,6 @@ def test_generate_maximal_network( lomap_old_mapper, simple_scorer, ): - """TODO: add test for throwing an error if there is no scorer but multiple mappings""" toluene, others = toluene_vs_others if extra_mapper: @@ -549,7 +548,7 @@ def test_generate_network_from_names(self, atom_mapping_basic_test_files, lomap_ names=requested_names, mapper=lomap_old_mapper, ) - # TODO: konnektor only constructs based on edges, doesn't allow for disconnected networks + assert len(network.nodes) == len(ligands) assert len(network.edges) == 2 From f298c0ffedd2152227b9f39d47b76b662497deec Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 17 Jun 2025 10:59:43 -0700 Subject: [PATCH 53/56] add konnektor to docs env --- docs/environment.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/environment.yaml b/docs/environment.yaml index 35dc9b74c..32ec3f6b3 100644 --- a/docs/environment.yaml +++ b/docs/environment.yaml @@ -6,6 +6,7 @@ dependencies: - autodoc-pydantic <2.0 - gitpython - kartograf >=1.0.0 +- konnektor >=0.2.0 - libsass - lomap2 >=3.0.0 - myst-parser From 71c1669237ffab106c23d93af0ddec3063c148fe Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 17 Jun 2025 12:50:58 -0700 Subject: [PATCH 54/56] remove completed TODO --- openfe/tests/setup/test_network_planning.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openfe/tests/setup/test_network_planning.py b/openfe/tests/setup/test_network_planning.py index 0388aa1f1..747d08ec6 100644 --- a/openfe/tests/setup/test_network_planning.py +++ b/openfe/tests/setup/test_network_planning.py @@ -598,7 +598,6 @@ def test_network_from_indices(self, atom_mapping_basic_test_files, lomap_old_map requested = [(0, 1), (2, 3)] - # TODO: konnektor only constructs based on edges, doesn't allow for disconnected networks network = openfe.setup.ligand_network_planning.generate_network_from_indices( ligands=ligands, indices=requested, From 062bcb480c9dd09a98548c9622105a01bd0ac9c1 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 17 Jun 2025 12:52:39 -0700 Subject: [PATCH 55/56] updating news --- news/konnektor_changes.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 news/konnektor_changes.rst diff --git a/news/konnektor_changes.rst b/news/konnektor_changes.rst new file mode 100644 index 000000000..a80120dcd --- /dev/null +++ b/news/konnektor_changes.rst @@ -0,0 +1,24 @@ +**Added:** + +* Added optional ``progress`` (whether to show a progress bar) and ``n_processes`` (number of parallel processes to use when generating the network) arguments for network planners. + +**Changed:** + +* `konnektor _` is now used as the backend for all network generation. +* ``openfe.setup.ligand_network_planning.generate_maximal_network`` now returns the *best* mapping for each edge, rather than *all possible* mappings for each edge. If multiple mappers are passed but no scorer, the first mapper passed will be used, and a warning will be raised. + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From a3a5888785fb72f8c566227f415dcbb7d8a8fcb4 Mon Sep 17 00:00:00 2001 From: Alyssa Travitz Date: Tue, 17 Jun 2025 13:16:25 -0700 Subject: [PATCH 56/56] clarifying process and progress additions in news --- news/konnektor_changes.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/news/konnektor_changes.rst b/news/konnektor_changes.rst index a80120dcd..4ff5dcd05 100644 --- a/news/konnektor_changes.rst +++ b/news/konnektor_changes.rst @@ -1,7 +1,8 @@ **Added:** -* Added optional ``progress`` (whether to show a progress bar) and ``n_processes`` (number of parallel processes to use when generating the network) arguments for network planners. - +* Added optional ``n_processes`` (number of parallel processes to use when generating the network) arguments for network planners. +* Added optional ``progress`` (whether to show progress bar) for ``openfe.setup.ligand_network_planning.generate_radial_network`` (default=``False``, such that there is no default behavior change). + **Changed:** * `konnektor _` is now used as the backend for all network generation.