Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds LigandNetwork.remove_edges #320

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion gufe/ligandnetwork.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from itertools import chain
import json
import networkx as nx
from typing import FrozenSet, Iterable, Optional
from typing import FrozenSet, Iterable, Optional, Union
import gufe

from gufe import SmallMoleculeComponent
Expand Down Expand Up @@ -184,6 +184,39 @@ def enlarge_graph(self, *, edges=None, nodes=None) -> LigandNetwork:

return LigandNetwork(self.edges | set(edges), self.nodes | set(nodes))

def remove_edges(self, edges: Union[LigandAtomMapping, list[LigandAtomMapping]]) -> LigandNetwork:
"""Create a new copy of this network with some edges removed

Note that this will not remove any nodes, potentially resulting in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How big of deal is this? Is it worth adding the complexity of checking to make sure we don't allow that? I can't remember what the rules are, if we allow creating a network that is disconnected, then I don't think we need a check, but if we require a connected network, then we want to make sure we don't let users put things into a inconsistent state.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good suggestion, I think :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was 50/50 on what is best here. Currently disconnected graphs are "allowed" in a data structure sense, but they're often undesirable. It wouldn't be impossible to find and remove orphan nodes, but then the method isn't strictly "remove edges". I think I'll chicken out and add a kwarg that allows either, default being remove orphans.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wouldn't be impossible to find and remove orphan nodes

Ah yes, I was suggesting a more gentle "throw an error if removing this edge creates an orphan" or something so it can stay focused and do what it says on the tin, so I would be happy with kwargs that control things like, raising an error or not if it would create an orphan, and/or remove orphan(s) (this will be tricking if removing a edge creates 2 disconnect graphs, which one gets remove? the one with more nodes? -- maybe we can support the case where if it creates a single orphan, the default is to raise an error, but that can be suppressed, and additionally with another kwarg the orphan can be removed)

disconnected networks

Parameters
----------
edges : list[LigandAtomMapping] or LigandAtomMapping
the edges to remove, these *must* be present in the network

Returns
-------
network : LigandNetwork
"""
if isinstance(edges, LigandAtomMapping):
edges = [edges]

to_remove = set(edges)
current = set(self.edges)

# check that all edges to remove are present
if extras := to_remove - current:
raise ValueError("Some edges weren't already present: "
f"{extras}")

new_edges = current - to_remove
new_net = LigandNetwork(new_edges, self.nodes)
if not new_net.graph().is_connected():
RiesBen marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError("The result is a disconnected Network!")
return new_net
return LigandNetwork(new_edges, self.nodes)

def _to_rfe_alchemical_network(
self,
components: dict[str, gufe.Component],
Expand Down
38 changes: 38 additions & 0 deletions gufe/tests/test_ligand_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,41 @@ def test_empty_ligand_network(mols):

assert len(n.edges) == 0
assert len(n.nodes) == 1


class TestLigandNetworkRemoveEdges:
def test_remove_edges_single(self, std_edges):
e1, e2, e3 = std_edges

n = LigandNetwork(edges=[e1, e2, e3])

n2 = n.remove_edges(e1)

assert len(n2.edges) == 2
assert len(n2.nodes) == 3
assert e1 not in n2.edges
assert e2 in n2.edges
assert e3 in n2.edges
assert n.nodes == n2.nodes

def test_remove_edges_pair(self, std_edges):
e1, e2, e3 = std_edges

n = LigandNetwork(edges=[e1, e2, e3])

n2 = n.remove_edges([e1, e2])

assert len(n2.edges) == 1
assert len(n2.nodes) == 3
assert e1 not in n2.edges
assert e2 not in n2.edges
assert e3 in n2.edges
assert n.nodes == n2.nodes

def test_remove_edges_fail(self, std_edges):
e1, e2, e3 = std_edges

n = LigandNetwork([e1, e2])

with pytest.raises(ValueError):
n.remove_edges(e3)
23 changes: 23 additions & 0 deletions news/added_LigandNetwork_delete_edges.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* added LigandNetwork.remove_edges
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to this getting into the change log


**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
Loading