Skip to content

molforge.scoring

scoring

Unified scoring: score any structure, pose, or sequence with any scorer.

Every score in molforge is a bare float whose "good" direction you have to know out of band — Vina affinity is lower-is-better, a Gnina CNN score is higher-is-better, pLDDT is higher-is-better, ProteinMPNN's log-likelihood is lower-is-better. :mod:molforge.scoring makes the direction explicit so you can rank, compare, and threshold across sources uniformly:

from molforge.scoring import ConfidenceScorer, rank

best_first = rank(structures, ConfidenceScorer())
top = best_first[0][0]

Every scorer returns a :class:Score carrying its :class:Direction, and :attr:Score.ranking_key gives a "higher is always better" number. A :class:Scorer also plugs straight into :class:molforge.design.DesignLoop as an objective.

What's here (v1) — all dependency-free:

  • :class:ConfidenceScorer — a folded structure's mean pLDDT.
  • :class:DockingScorer — a pose / docking result's score, with the producing engine's direction (via :meth:DockingScorer.from_engine).
  • :class:FunctionScorer — wrap any item -> float callable + direction.
  • :func:rank / :func:best — direction-aware ordering.

Learned scorers that compute a value (ESM perplexity, ProteinMPNN log-likelihood, engine re-scoring) are follow-ups implementing the same :class:Scorer ABC.

Direction

Bases: Enum

Which way is better for a score.

Score dataclass

Score(
    value: float,
    direction: Direction,
    scorer: str = "",
    metadata: dict[str, Any] = dict(),
)

A scalar score plus the direction that makes it comparable.

Attributes:

Name Type Description
value float

The raw score in the scorer's native units.

direction Direction

Whether higher or lower value is better.

scorer str

Name of the scorer that produced it.

metadata dict[str, Any]

Optional extras (component scores, units, rank, ...).

ranking_key property

ranking_key: float

A "higher is always better" view of :attr:value.

Lower-is-better scores are negated, so ranking_key can be compared and sorted uniformly across scorers of either direction.

is_better_than

is_better_than(other: Score) -> bool

True if this score is strictly better than other.

A nan value is never better than a real one (and loses to everything), so unscoreable items sink in a ranking rather than surfacing spuriously.

Scorer

Bases: ABC

Abstract base for anything that assigns a :class:Score to an item.

Concrete scorers document what they take (a :class:~molforge.core.Protein, a Pose, a sequence string, ...) — the contract is deliberately loose, like the engine ABCs — and set :attr:direction.

Attributes:

Name Type Description
name str

Human-readable scorer name (set by subclasses).

direction Direction

Whether higher or lower is better for this scorer.

score abstractmethod

score(item: Any) -> Score

Score a single item.

score_many

score_many(
    items: Iterable[Any],
    *,
    workers: int | None = None,
    backend: Backend = "serial",
    on_error: OnError = "raise",
) -> list[Score]

Score many items, in input order.

Defaults to the "serial" backend — most scorers are cheap metadata reads, and a scorer built from a lambda isn't picklable for the process backend. See :func:molforge.parallel.map_parallel.

ConfidenceScorer

Bases: Scorer

Score a :class:~molforge.core.Protein by its mean pLDDT-style confidence.

Reads metadata["mean_confidence"] (falling back to the mean of metadata["confidence_per_residue"]) — the uniform key every folding engine writes. Higher is better. No dependencies.

DockingScorer

DockingScorer(*, direction: Direction)

Bases: Scorer

Score a Pose or DockingResult by its docked score.

Docking scores don't share a direction — Vina affinity is lower-is-better, a Gnina CNN score is higher-is-better — so the direction must be supplied. :meth:from_engine reads it from the engine that produced the result (its score_direction); otherwise pass direction explicitly. Reads the already-computed pose.score; it does not re-dock, so it needs no docking dependencies.

from_engine classmethod

from_engine(engine: Any) -> DockingScorer

Build a scorer whose direction matches engine's score_direction.

FunctionScorer

FunctionScorer(
    func: Callable[[Any], float],
    *,
    direction: Direction,
    name: str = "function",
)

Bases: Scorer

Wrap any item -> float callable as a :class:Scorer.

The escape hatch: plug an engine-rescoring function, an ESM perplexity call, or a bespoke composite into anything that consumes a Scorer — including :class:molforge.design.DesignLoop — without a dedicated class. You supply the :class:Direction.

best

best(items: Iterable[Any], scorer: Scorer) -> Any

Return the single best item under scorer.

Raises:

Type Description
ValueError

If items is empty.

rank

rank(
    items: Iterable[Any], scorer: Scorer
) -> list[tuple[Any, Score]]

Score every item and return (item, score) pairs best-first.

nan scores sort last, so unscoreable items never displace real ones.