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

gufe: Network Planning Stage - make this part ready for new methods. #346

Open
wants to merge 14 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
13 changes: 6 additions & 7 deletions gufe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@

from .chemicalsystem import ChemicalSystem

from .mapping import (
RiesBen marked this conversation as resolved.
Show resolved Hide resolved
ComponentMapping, # how individual Components relate
AtomMapping, AtomMapper, # more specific to atom based components
LigandAtomMapping,
)
from .setup.network_planning import (AtomMapping, AtomMapper,
AtomMappingScorer,
LigandAtomMapping,
LigandNetwork)

from .network import AlchemicalNetwork

from .settings import Settings

Expand All @@ -36,7 +37,5 @@

from .transformations import Transformation, NonTransformation

from .network import AlchemicalNetwork
from .ligandnetwork import LigandNetwork

__version__ = version("gufe")
7 changes: 0 additions & 7 deletions gufe/mapping/__init__.py

This file was deleted.

2 changes: 1 addition & 1 deletion gufe/protocols/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from ..settings import Settings, SettingsBaseModel
from ..tokenization import GufeTokenizable, GufeKey
from ..chemicalsystem import ChemicalSystem
from ..mapping import ComponentMapping
from gufe.setup.network_planning import ComponentMapping

from .protocoldag import ProtocolDAG, ProtocolDAGResult
from .protocolunit import ProtocolUnit
Expand Down
Empty file added gufe/setup/__init__.py
Empty file.
18 changes: 18 additions & 0 deletions gufe/setup/network_planning/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

# Factory Classes
from .component_mapper import ComponentMapper
from .component_mapping_scorer import ComponentMappingScorer
from .network_planner import NetworkPlanner

# Result Types
from .component_mapping import ComponentMapping
from .network_plan import NetworkPlan


# RBFE Protocol:
from .atom_mapping_based.atom_mapping import AtomMapping
from .atom_mapping_based.atom_mapper import AtomMapper
from .atom_mapping_based.atom_mapping_scorer import AtomMappingScorer

from .atom_mapping_based.ligand_atom_mapping import LigandAtomMapping
from .atom_mapping_based.ligandnetwork import LigandNetwork
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
from collections.abc import Iterator
import gufe

from ..tokenization import GufeTokenizable
from ..component_mapper import ComponentMapper
from .atom_mapping import AtomMapping


class AtomMapper(GufeTokenizable):
class AtomMapper(ComponentMapper):
"""A class for manufacturing mappings

Implementations of this class can require an arbitrary and non-standardised
Expand All @@ -27,4 +27,5 @@ def suggest_mappings(self,
Suggests zero or more :class:`.AtomMapping` objects, which are possible
atom mappings between two :class:`.Component` objects.
"""
...
raise NotImplementedError("This function was not implemented.")
RiesBen marked this conversation as resolved.
Show resolved Hide resolved

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


import gufe
from .componentmapping import ComponentMapping
from gufe.setup.network_planning.component_mapping import ComponentMapping


class AtomMapping(ComponentMapping, abc.ABC):
Expand All @@ -15,16 +15,6 @@ class AtomMapping(ComponentMapping, abc.ABC):

"""A mapping between two different atom-based Components"""

@property
def componentA(self) -> gufe.Component:
"""A copy of the first Component in the mapping"""
return self._componentA

@property
def componentB(self) -> gufe.Component:
"""A copy of the second Component in the mapping"""
return self._componentB

@property
@abc.abstractmethod
def componentA_to_componentB(self) -> Mapping[int, int]:
Expand All @@ -37,22 +27,23 @@ def componentA_to_componentB(self) -> Mapping[int, int]:
entity in the other component (e.g. the atom disappears), therefore
resulting in a KeyError on query
"""
...
raise NotImplementedError("This function was not implemented.")
RiesBen marked this conversation as resolved.
Show resolved Hide resolved

@property
@abc.abstractmethod
def componentB_to_componentA(self) -> Mapping[int, int]:
"""Similar to A to B, but reversed."""
...
raise NotImplementedError("This function was not implemented.")

@property
@abc.abstractmethod
def componentA_unique(self) -> Iterable[int]:
"""Indices of atoms in component A that aren't mappable to B"""
...
raise NotImplementedError("This function was not implemented.")

@property
@abc.abstractmethod
def componentB_unique(self) -> Iterable[int]:
"""Indices of atoms in component B that aren't mappable to A"""
...
raise NotImplementedError("This function was not implemented.")

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# This code is part of kartograf and is licensed under the MIT license.
# For details, see https://github.com/OpenFreeEnergy/gufe

import abc

from ..component_mapping_scorer import ComponentMappingScorer
from .atom_mapping import AtomMapping


class AtomMappingScorer(ComponentMappingScorer):
"""A generic class for scoring Atom mappings.
this class can be used for example to build graph algorithm based networks.

Implementations of this class can require an arbitrary and non-standardised
number of input arguments to create.

Implementations of this class provide the :meth:`.get_score` method

"""

def __call__(self, mapping: AtomMapping) -> float:
return self.get_score(mapping)

@abc.abstractmethod
def get_score(self, mapping: AtomMapping) -> float:
""" calculate the score for an :class:`.AtomMapping`
the scoring function returns a value between 0 and 1.
a value close to 1.0 indicates a small change - good score, a score close to zero indicates a large cost/change - bad score.

Parameters
----------
mapping: AtomMapping
the mapping to be scored
args
kwargs

Returns
-------
float
a value between [0,1] where zero is a very bad score and one a very good one.

"""
raise NotImplementedError("This function was not implemented.")
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

from gufe.components import SmallMoleculeComponent
from gufe.visualization.mapping_visualization import draw_mapping
from . import AtomMapping
from ..tokenization import JSON_HANDLER
from gufe.setup.network_planning import AtomMapping
from gufe.tokenization import JSON_HANDLER


class LigandAtomMapping(AtomMapping):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
import gufe

from gufe import SmallMoleculeComponent
from .mapping import LigandAtomMapping
from .tokenization import GufeTokenizable

from gufe.setup.network_planning.network_plan import NetworkPlan
from gufe.setup.network_planning.atom_mapping_based.ligand_atom_mapping import LigandAtomMapping

class LigandNetwork(GufeTokenizable):

class LigandNetwork(NetworkPlan):
"""A directed graph connecting many ligands according to their atom mapping

Parameters
Expand Down Expand Up @@ -48,37 +49,6 @@ def _to_dict(self) -> dict:
def _from_dict(cls, dct: dict):
return cls.from_graphml(dct['graphml'])

@property
def graph(self) -> nx.MultiDiGraph:
"""NetworkX graph for this network

This graph will have :class:`.ChemicalSystem` objects as nodes and
:class:`.Transformation` objects as directed edges
"""
if self._graph is None:
graph = nx.MultiDiGraph()
# set iterator order depends on PYTHONHASHSEED, sorting ensures
# reproducibility
for node in sorted(self._nodes):
graph.add_node(node)
for edge in sorted(self._edges):
graph.add_edge(edge.componentA, edge.componentB, object=edge,
**edge.annotations)

self._graph = nx.freeze(graph)

return self._graph

@property
def edges(self) -> FrozenSet[LigandAtomMapping]:
"""A read-only view of the edges of the Network"""
return self._edges

@property
def nodes(self) -> FrozenSet[SmallMoleculeComponent]:
"""A read-only view of the nodes of the Network"""
return self._nodes

def _serializable_graph(self) -> nx.Graph:
"""
Create NetworkX graph with serializable attribute representations.
Expand Down Expand Up @@ -307,12 +277,4 @@ def to_rbfe_alchemical_network(
# protocol=protocol,
# autoname=autoname,
# autoname_prefix=autoname_prefix
# )

def is_connected(self) -> bool:
"""Are all ligands in the network (indirectly) connected to each other

A "False" value indicates that either some ligands have no edges or that
there are separate networks that do not link to each other.
"""
return nx.is_weakly_connected(self.graph)
# )
30 changes: 30 additions & 0 deletions gufe/setup/network_planning/component_mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This code is part of gufe and is licensed under the MIT license.
# For details, see https://github.com/OpenFreeEnergy/gufe
import abc
from collections.abc import Iterator
import gufe

from gufe.tokenization import GufeTokenizable
from .component_mapping import ComponentMapping


class ComponentMapper(GufeTokenizable):
RiesBen marked this conversation as resolved.
Show resolved Hide resolved
"""A class for manufacturing mappings

Implementations of this class can require an arbitrary and non-standardised
number of input arguments to create.

Implementations of this class provide the :meth:`.suggest_mappings` method
"""

@abc.abstractmethod
def suggest_mappings(self,
A: gufe.Component,
B: gufe.Component
) -> Iterator[ComponentMapping]:
"""Suggests possible mappings between two Components

Suggests zero or more :class:`.AtomMapping` objects, which are possible
atom mappings between two :class:`.Component` objects.
"""
raise NotImplementedError("This function was not implemented.")
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ class ComponentMapping(GufeTokenizable, abc.ABC):
def __init__(self, componentA: gufe.Component, componentB: gufe.Component):
self._componentA = componentA
self._componentB = componentB
# self.componentA_to_componentB # TODO: is that something we want here, thinking beyond AtomMappings?

def __contains__(self, item: gufe.Component):
return item == self._componentA or item == self._componentB

@property
def componentA(self) -> gufe.Component:
"""A copy of the first Component in the mapping"""
return self._componentA

@property
def componentB(self) -> gufe.Component:
"""A copy of the second Component in the mapping"""
return self._componentB
36 changes: 36 additions & 0 deletions gufe/setup/network_planning/component_mapping_scorer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# This code is part of kartograf and is licensed under the MIT license.
# For details, see https://github.com/OpenFreeEnergy/gufe

import abc
from gufe.tokenization import GufeTokenizable

from .component_mapping import ComponentMapping

class ComponentMappingScorer(GufeTokenizable):
"""A generic class for scoring Atom mappings.
this class can be used for example to build graph algorithm based networks.
Implementations of this class can require an arbitrary and non-standardised
number of input arguments to create.
Implementations of this class provide the :meth:`.get_score` method
"""

def __call__(self, mapping: ComponentMapping) -> float:
return self.get_score(mapping)

@abc.abstractmethod
def get_score(self, mapping: ComponentMapping) -> float:
""" calculate the score for an :class:`.AtomMapping`
the scoring function returns a value between 0 and 1.
a value close to 1.0 indicates a small change, a score close to zero indicates a large cost/change.
Parameters
----------
mapping: AtomMapping
the mapping to be scored
args
kwargs
Returns
-------
float
a value between [0,1] where zero is a very bad score and one a very good one.
"""
raise NotImplementedError("This function was not implemented.")
Loading
Loading