SurrogateManager#

Layer 3: Extend Components

Where Surrogate handles only fit/predict, SurrogateManager coordinates model fitting and batched prediction. An acquisition configured on the optimizer scores the returned predictions.

Optimizer.set_surrogate_manager() is a separate top-level swap point from Optimizer.set_surrogate() (a simplified version that wraps a Surrogate in LocalSurrogateManager).

SurrogateManager’s role#

SurrogateManager’s abstract method is predict(); fitting and generation hooks have default implementations.

predict(candidates_x, archive, ctx=None, *, refit=True) -> SurrogatePrediction (abstract): Predicts the candidates. With refit=True (the default), the surrogate is retrained before prediction.

fit(archive, ctx=None) -> None: A no-op by default. A pre-fit hook meant to be called once before a series of predict(..., refit=False) calls, used when the archive does not change.

last_accuracy: SurrogateAccuracy | None (class attribute): The accuracy metric computed by the most recent fit. Covered in detail in Surrogate accuracy evaluation and dynamic switching.

on_generation_end(gen, archive, ctx=None) / with_on_generation_end(fn): An end-of-generation hook. Also extensible via the same copy-and-chain approach.

The values received and returned at the two boundaries are:

Boundary

Receives

Returns

Main users

Stage compatibility boundary

archive, candidates_x: np.ndarray, ctx, refit

SurrogatePrediction, fit completion state

compatibility Stages, sequential compatibility runtime

graph-native boundary

GenomeBatch, FeatureEncoder, FeatureBatch, declared services

Features the Surrogate can fit and predict, plus predictions

Components, Compiler, structured runtime

The current vector-form Manager API on the Stage compatibility path uses fit(archive, ctx=None) and predict(candidates_x: np.ndarray, archive, ctx=None, *, refit=True) -> SurrogatePrediction. The current API also includes constructors such as GlobalSurrogateManager(surrogate, training_set=None, accuracy_evaluator=None), LocalSurrogateManager(surrogate, training_set=None, accuracy_evaluator=None), and PairwiseSurrogateManager(surrogate, training_set=None, n_ref=10). In the structured runtime, the Surrogate input boundary is GenomeBatch → FeatureEncoder → FeatureBatch → Surrogate. FeatureEncoder performs semantic conversion and determines the features the surrogate can learn. It differs from a space capability such as SamplingService. In the current implementation, the SurrogateManager contract declares ServiceRequirement("FeatureEncoder"), and VectorSpace registers a default encoder as a service, so numeric vector spaces resolve without extra configuration. ObjectSpace, PermutationSpace, and SequenceSpace raise an error unless the user provides a FeatureEncoder. The user decides what to pass to the surrogate.

Built-in SurrogateManagers#

Class

Approach

GlobalSurrogateManager

Fits globally once over the entire archive and predicts every candidate at once

LocalSurrogateManager

Fits locally per candidate, using its k nearest neighbors

CompositeSurrogateManager

Combines named prediction channels

PairwiseSurrogateManager

Predicts win rates with a pairwise-comparison surrogate

GlobalSurrogateManager(surrogate, training_set=None, accuracy_evaluator=None) uses ArchiveObjectiveSet() when training_set is omitted.

LocalSurrogateManager(surrogate, training_set=None, accuracy_evaluator=None) uses KNNObjectiveSet(n_neighbors=50) when training_set is omitted. n_neighbors isn’t a constructor argument of LocalSurrogateManager itself — it’s a parameter of the default training_set. Because it reuses and refits the same surrogate instance across candidates, it isn’t thread-safe.

CompositeSurrogateManager(managers) calls each named manager’s predict() and returns a multi-channel SurrogatePrediction. Use CompositeAcquisition to evaluate one acquisition per channel and combine the resulting score arrays with product_combine or rank_weighted_combine(weights=None).

PairwiseSurrogateManager(surrogate, training_set=None, n_ref=10) uses PairwiseComparisonSet() when training_set is omitted.

See TrainingSet and Surrogate accuracy evaluation and dynamic switching, respectively, for details on each manager’s training_set/accuracy_evaluator arguments.

Archive-based acquisitions#

These acquisitions score candidate geometry directly against the archive and do not require a surrogate prediction.

Class

Parameters

Meaning of the score

NoveltyAcquisition

k=1

Higher when the average distance to the k nearest archive points is larger

InverseDensityAcquisition

eps=1.0

The reciprocal of the ε-neighborhood density

MaximinDistanceAcquisition

None

The minimum distance among candidates plus the minimum distance to the archive

Extending SurrogateManager and archive-based acquisitions#

If you need a custom prediction scheme, subclass SurrogateManager and implement predict(). For a custom archive-based criterion, subclass AcquisitionFunction and implement its evaluation contract.

import numpy as np
from saealib import AcquisitionFunction, AcquisitionResult


class ConstantAcquisition(AcquisitionFunction):
    """A minimal acquisition that assigns every candidate the same score."""

    def evaluate(self, candidates_x, prediction, archive, ctx=None, *, prepared=None):
        return AcquisitionResult(scores=np.ones(len(candidates_x)))

References#