Plugins
Five plugin kinds, each an abstract base class in rbfenetmap.core.meta:
Kind |
Responsibility |
Built-ins |
|---|---|---|
mapper |
Propose an atom correspondence |
|
scorer |
Reduce descriptors to a cost |
|
planner |
Select the final edge set |
|
exporter |
Serialize for a downstream program |
|
intermediate |
Invent a ligand bridging a gap |
|
The intermediate generator is the only kind whose output changes the ligand set rather
than the network over it. Its contract is correspondingly narrow: it proposes molecules
and nothing else. Posing them is
rbfenetmap.core.posing.pose_intermediate()’s job, deciding whether the resulting
edges are feasible is the repair’s, and pricing them is the scorer’s – each of those
already has an owner, and a generator that took one over would make an intermediate’s
quality depend on which generator happened to invent it. See
Intermediate ligands for what the two built-in generators actually do.
Lazy registration
A PluginSpec describes a plugin without
importing it. Registration is pure metadata; the implementation module is imported only
when create() is called.
That is what lets rbfenet plugins --all list every backend – including ones whose
dependencies are absent – without importing RDKit or kartograf, and what lets the whole
test suite run with no optional dependency installed.
The requires tuple is probed with importlib.util.find_spec(), so a missing
backend is reported as needs kartograf, gufe rather than as a raw
ModuleNotFoundError.
Writing a mapper
from typing import ClassVar
from rbfenetmap.core.meta.mappers import AbstractMapper
from rbfenetmap.core.models import AtomMapping
class MyMapper(AbstractMapper):
name: ClassVar[str] = "mine"
def map_pair(self, source, target, options) -> AtomMapping:
core = {...} # {index_in_source: index_in_target}
return AtomMapping.from_core_pairs(
core, n_atoms_1=source.n_atoms, n_atoms_2=target.n_atoms, method=self.name
)
A mapper does not need to produce a connected soft-core. Repairing fragmentation is
rbfenetmap.core.softcore.repair_softcore_connectivity()’s job, and it runs on every
mapper’s output. Duplicating that logic inside a mapper only makes mappers harder to write
and compare.
Registering it
from rbfenetmap.core.pluginregistry import PluginRegistry, PluginSpec
registry = PluginRegistry()
registry.register(PluginSpec(
name="mine",
kind="mapper",
target="my_package.mappers:MyMapper",
description="What it does.",
requires=("some_backend",),
))
An instance can also be passed straight to
build_network(); registering is only needed to make the
plugin selectable by name from the CLI.
Exporters: the hook into other programs
An exporter adapts a planned network to a downstream consumer without that consumer’s
concerns reaching back into the core. It may also implement
validate(), which checks
format-specific constraints the core does not enforce, early and without writing anything.
The amber exporter is the motivating case: Amber soft-core masks select atoms by
name, so a soft-core atom sharing a name with a common-core atom would silently widen
the mask and produce wrong free energies with no error at run time. validate catches
that before any expensive work happens.