diff --git a/gufe/__init__.py b/gufe/__init__.py index 10e12d57..37eb6e7b 100644 --- a/gufe/__init__.py +++ b/gufe/__init__.py @@ -18,7 +18,7 @@ from .mapping import ( ComponentMapping, # how individual Components relate - AtomMapping, AtomMapper, # more specific to atom based components + AtomMapping, AtomMapper, AtomMappingScorer, # more specific to atom based components LigandAtomMapping, ) diff --git a/gufe/ligand_network_planner.py b/gufe/ligand_network_planner.py new file mode 100644 index 00000000..e9f16310 --- /dev/null +++ b/gufe/ligand_network_planner.py @@ -0,0 +1,48 @@ +import abc +from typing import Iterable + +from . import SmallMoleculeComponent, LigandNetwork +from . import AtomMapper +from .mapping.atom_mapping_scorer import AtomMappingScorer + +class LigandNetworkPlanner(abc.ABC): + """A generic class for calculating :class:`.LigandNetworks`. + + 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 __init__(self, mapper: AtomMapper, scorer:AtomMappingScorer, *args, *kwargs): + """ Generate a Ligand Network Planner. This class in general needs a mapper and a scorer. + + Parameters + ---------- + mapper: AtomMapper + scorer: AtomMappingScorer + args + kwargs + """ + self.mapper = mapper + self.scorer = scorer + + + def __call__(self, ligands: Iterable[SmallMoleculeComponent])-> LigandNetwork: + return self.generate_ligand_network(*args, **kwargs) + + @abc.abstractmethod + def generate_ligand_network(self, ligands: Iterable[SmallMoleculeComponent])->LigandNetwork: + """Plan a Network which connects all ligands with minimal cost + + Parameters + ---------- + ligands : Iterable[SmallMoleculeComponent] + the ligands to include in the Network + + Returns + ------- + LigandNetwork + A Network, that connects all ligands with each other. + """ diff --git a/gufe/mapping/__init__.py b/gufe/mapping/__init__.py index bd6be51b..3a9ceddb 100644 --- a/gufe/mapping/__init__.py +++ b/gufe/mapping/__init__.py @@ -4,4 +4,5 @@ from .componentmapping import ComponentMapping from .atom_mapping import AtomMapping from .atom_mapper import AtomMapper +from .atom_mapping_scorer import AtomMappingScorer from .ligandatommapping import LigandAtomMapping diff --git a/gufe/mapping/atom_mapping_scorer.py b/gufe/mapping/atom_mapping_scorer.py new file mode 100644 index 00000000..cf9d0611 --- /dev/null +++ b/gufe/mapping/atom_mapping_scorer.py @@ -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 ..tokenization import GufeTokenizable + +from . import AtomMapping + + +class AtomMappingScorer(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: 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, 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. + + """ + pass diff --git a/gufe/tests/test_setup_interfaces.py b/gufe/tests/test_setup_interfaces.py new file mode 100644 index 00000000..ab8a8ba0 --- /dev/null +++ b/gufe/tests/test_setup_interfaces.py @@ -0,0 +1,15 @@ +# This code is part of OpenFE and is licensed under the MIT license. +# For details, see https://github.com/OpenFreeEnergy/gufe + +import pytest +from gufe import AtomMappingScorer, AtomMapper + + +def test_atom_mapping_scorer(): + with pytest.raises(TypeError, match="Can't instantiate abstract class AtomMappingScorer"): + scorer = AtomMappingScorer() + + +def test_atom_mapper(): + with pytest.raises(TypeError, match="Can't instantiate abstract class AtomMapper"): + mapper = AtomMapper()