Adding a Scorer¶
This guide explains how to integrate a new scoring function or external design
tool into REvoDesign's "Magician" gimmick system. Scorers are auto-discovered
by the PluginRegistry -- no manual registration step is needed.
Overview¶
A scorer plugin is a Python subpackage under magician/designers/ that
subclasses ExternalDesignerAbstract and implements at minimum initialize()
and scorer(). The canonical example is the OpenKinetics scorer plugin at
src/REvoDesign/magician/designers/openkinetics/.
Step 1: Create the subpackage¶
src/REvoDesign/magician/designers/
├── __init__.py # Existing package init
└── your_scorer/
├── __init__.py # Exports your classes
├── _scorers.py # Scorer class definition(s)
└── ... # Supporting modules (client, models, helpers)
The PluginRegistry in magician/__init__.py imports all modules under
REvoDesign.magician.designers and discovers non-abstract subclasses of
ExternalDesignerAbstract, so the subpackage is found automatically.
Step 2: Subclass ExternalDesignerAbstract¶
Define your scorer class in _scorers.py:
from REvoDesign.basic.designer import ExternalDesignerAbstract
class YourScorer(ExternalDesignerAbstract):
name = "Your-Scorer-Name"
installed = True # Set to True if dependencies are available
scorer_only = True # True if only scoring is implemented
prefer_lower = False # Whether lower scores are preferred
def initialize(self, *args, **kwargs):
# One-time setup: load config, validate dependencies, etc.
self.initialized = True
def scorer(self, mutant, **kwargs) -> float:
# Score a single mutant, return a float.
...
return score
Required class attributes:
| Attribute | Type | Description |
|---|---|---|
name |
str |
Unique plugin name used in registry lookups and UI selection. |
installed |
bool |
Signals availability to the registry (installed_names). |
scorer_only |
bool |
When True, only scorer() is implemented. |
prefer_lower |
bool |
If True, lower scores are treated as better. |
no_need_to_score_wt |
bool |
If True, the wild-type is not scored separately (default False). |
Required methods:
initialize(self, *args, **kwargs)-- Called once when the gimmick is "pre-heated" byMagician.setup(). Use it to load config, validate the environment, or initialize remote API clients.scorer(self, mutant, **kwargs) -> float-- Called for each mutant to be scored. Themutantparameter is aMutantorRosettaPyProteinSequenceobject.
Optional methods:
designer(self, *args, **kwargs)-- Only needed if your plugin can also generate new designs (not just score existing ones).parallel_scorer(self, mutants, nproc, **kwargs) -> list[Mutant]-- The base class provides ajoblib.Parallelimplementation. Override for custom batching logic (see OpenKinetics for an example that sends batch API requests).preffer_substitutions(self, aa)-- Optional amino-acid preference setup.
Step 3: Create a YAML config¶
Config files live under config/third_party/scorers/. Create a YAML file
that follows the OpenKinetics pattern:
# src/REvoDesign/config/third_party/scorers/your_scorer.yaml
# Loaded via reload_config_file("third_party/scorers/your_scorer")["third_party"]
scorers:
your_scorer:
enabled: false
# Add runtime knobs here...
some_option: "default_value"
The config is loaded in initialize() via:
from REvoDesign.bootstrap import reload_config_file
config = reload_config_file("third_party/scorers/your_scorer")["third_party"]
Step 4: Auto-discovery (it's automatic)¶
The PluginRegistry at module load time:
- Imports all modules under
REvoDesign.magician.designers. - Scans for non-abstract subclasses of
ExternalDesignerAbstract. - Indexes them by their
nameattribute. - Exposes them via
DESIGNER_REGISTRY.all_classes,IMPLEMENTED_DESIGNERS, andDESIGNER_REGISTRY.installed_names.
Your scorer will automatically appear in:
MagicianAssistant.installed_worker(ifinstalled = True)- The UI's external scorer dropdown
Magician.setup(name_cfg_term="ui.interact.use_external_scorer")
No manual registration or __init__.py export in magician/designers/ is
needed, as long as the subpackage is importable.
Example: OpenKinetics¶
The OpenKinetics scorer at src/REvoDesign/magician/designers/openkinetics/
is the reference implementation. Key design decisions:
Class hierarchy¶
class OpenKineticsScorerAbstract(ExternalDesignerAbstract, ABC):
"""Base class with shared client, caching, and scoring logic."""
installed = True
scorer_only = True
__bibtex__ = {"OpenKineticsPredictor": _OPENKINETICS_PREDICTOR_BIBTEX}
@classmethod
@abstractmethod
def built_in_defaults(cls) -> dict[str, str]:
"""Return {'method': ..., 'prediction_type': ...}."""
def initialize(self, *args, **kwargs):
# Resolve substrate SMILES from kwargs or PDB metadata
...
def scorer(self, mutant, **kwargs) -> float:
# Submit variant to the OpenKinetics API, return predicted value
...
Dynamic subclasses¶
Instead of writing one class per prediction method, _scorers.py defines a
_SCORER_SPECS tuple and creates subclasses dynamically with type():
_SCORER_SPECS = (
("CataProKcatScorer", "OpenKinetics-CataPro-kcat", "CataPro", "kcat", "CataPro"),
("CatPredKcatScorer", "OpenKinetics-CatPred-kcat", "CatPred", "kcat", "CatPred"),
# ... more specs ...
)
for class_name, scorer_name, method, prediction_type, citation_key in _SCORER_SPECS:
globals()[class_name] = type(
class_name,
(OpenKineticsScorerAbstract,),
{
"name": scorer_name,
"prefer_lower": prediction_type.lower() == "km",
"built_in_defaults": _built_in_defaults(method, prediction_type),
"__bibtex__": {citation_key: _PREDICTOR_BIBTEX[citation_key]},
},
)
Each subclass sets name, prefer_lower, built_in_defaults, and
__bibtex__. The OpenKinetics package's __init__.py re-exports these
dynamic classes by name.
Package structure¶
openkinetics/
├── __init__.py # Re-exports all public symbols, dynamic scorer classes
├── _scorers.py # OpenKineticsScorerAbstract + dynamic subclass creation
├── _client.py # OpenKinetics REST API client (submit, poll, get_result)
├── _models.py # Dataclasses, exception types, constants
└── _pdb.py # PDB/ligand helpers (SMILES extraction, metadata)
Step 5 (optional): Add citation support¶
Set __bibtex__ on your class to integrate with the citation system:
class YourScorer(ExternalDesignerAbstract):
__bibtex__ = {
"your_method": r"""@article{...,
title = {...},
...
}"""
}
Citations are collected when self.cite() is called and managed by
CitationManager (citations/citation_manager.py).
Testing¶
Create a test file under tests/ following the pattern in
tests/magician/test_openkinetics_scorer.py. Key things to test:
- Registry discovery: verify your class appears in
IMPLEMENTED_DESIGNERSandALL_DESIGNER_CLASSES. - Initialization: test
initialize()with valid and invalid config. - Scoring: test
scorer()with mock data or a known fixture. - Config loading: test that your YAML config is loaded correctly.
- Skip tests when the external dependency is not installed (check
your_class.installed).
Run with:
make kw-test PYTEST_KW='your_keyword'