Source code for rbfenetmap.core.meta.planners

"""The planner contract: select the final edge set from scored candidates."""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import ClassVar, Mapping, Sequence

from rbfenetmap.core.exceptions import NetworkPlanError
from rbfenetmap.core.models import Ligand, Network, Transformation
from rbfenetmap.core.options import NetworkOptions

__all__ = ("AbstractNetworkPlanner",)


[docs] class AbstractNetworkPlanner(ABC): """Choose which candidate transformations make up the network. Notes ----- A planner selects; it does not judge feasibility. Candidates arrive already scored, with infeasible ones marked. An implementation must filter on :attr:`~rbfenetmap.core.models.Transformation.feasible` and must still place every candidate -- feasible or not -- on :attr:`~rbfenetmap.core.models.Network.candidates`. Retaining the infeasible ones is what makes a disconnected result explicable. When the planner has to report that two groups of ligands cannot be joined, the rejected candidates that span the gap, and their reasons, are the actionable part of the message; without them the user gets "disconnected" and no idea what to loosen. """ name: ClassVar[str] = "abstract" #: Whether this planner knows how to place counterpoised (CBFE) edges. Both of the #: modes that need planner cooperation -- ``bridge`` and ``cycles`` -- are expressed as #: decisions about *where* an edge goes, which only a planner that reasons about #: components and cycles can make. ``all`` needs nothing from the planner, because the #: pipeline hands it a pool that is already entirely CBFE. supports_cbfe: ClassVar[bool] = False #: Whether this planner optimises a statistical design criterion. ``design`` names an #: *objective*, not a filter, so there is nothing a planner can do with it halfway: a #: planner that does not optimise the criterion would ignore the flag entirely, and a #: knob that is silently ignored is worse than one that is absent. supports_design: ClassVar[bool] = False
[docs] def check_design_support(self, options: NetworkOptions) -> None: """Raise if *options* names a design criterion this planner cannot optimise. Parameters ---------- options : NetworkOptions Raises ------ rbfenetmap.core.exceptions.NetworkPlanError Notes ----- The counterpart to :meth:`check_cbfe_support`, and refused for the same reason. ``--design a_optimal`` under the ``mst`` planner would produce a perfectly ordinary minimum-spanning-tree network, with nothing anywhere to connect the result to the flag the user set -- the ``--consistency graph`` failure mode this package already has one instance of and does not want a second. """ if self.supports_design or options.design == "none": return raise NetworkPlanError( f"Planner {self.name!r} does not optimise a design criterion, but design={options.design!r} " "asks for one. Use the 'optimal' planner, or drop --design." )
[docs] def check_cbfe_support(self, options: NetworkOptions) -> None: """Raise if *options* asks for CBFE placement this planner cannot do. Parameters ---------- options : NetworkOptions Raises ------ rbfenetmap.core.exceptions.NetworkPlanError Notes ----- Called rather than silently ignored. A user who passes ``--cbfe bridge`` and a planner that cannot honour it would otherwise get a disconnected network, or a connectivity error, with nothing to connect either outcome to the flag they set -- the same failure mode the scorers refuse for an unknown weight name. """ if self.supports_cbfe or not options.cbfe_bridges_components: return raise NetworkPlanError( f"Planner {self.name!r} cannot place CBFE edges, but cbfe_mode={options.cbfe_mode!r} requires it. " "Use the 'mst' planner, or cbfe_mode='all' (which needs no planner support), or 'off'." )
[docs] @abstractmethod def plan( self, ligands: Mapping[str, Ligand], candidates: Sequence[Transformation], options: NetworkOptions ) -> Network: """Select edges and return the planned network. Parameters ---------- ligands : Mapping[str, Ligand] Every vertex, including any the planner ends up unable to connect. candidates : Sequence[Transformation] Scored candidates, feasible and infeasible. options : NetworkOptions The user's selection knobs. Returns ------- Network Raises ------ rbfenetmap.core.exceptions.NetworkPlanError If the constraints are unsatisfiable: a forced edge that is infeasible, an ``n_edges`` too small to span, or a disconnected candidate pool while ``require_connected`` is set. Constraints that are merely tight -- an ``edges_per_ligand`` the pool cannot support -- are recorded on :attr:`~rbfenetmap.core.models.Network.unmet_constraints` instead. """
def __repr__(self) -> str: # pragma: no cover - trivial return f"{type(self).__name__}(name={self.name!r})"