OptimizationStrategy#
saealib delegates the selection of candidates sent for expensive true evaluation to OptimizationStrategy.
The Strategy describes candidate generation, prediction, evaluation planning, Feedback, and Population updates as one graph.
OptimizationStrategy’s role#
The graph-native extension point is build_graph(provider) -> ComponentGraph.
Before execution, Optimizer passes this graph to the Compiler to create an ExecutablePlan.
build_pipeline(provider) -> Pipeline is the Stage compatibility path and an optional compatibility representation for Strategies that use the existing Pipeline DSL.
Even in this form, a Stage inserted into a structured Pipeline must be wrapped explicitly with stage_component(stage).
step(ctx, provider) is the remaining boundary on the Stage compatibility path.
A new graph-native component reads a StateView and returns a StatePatch or NodeResult.
The class attribute requires_surrogate: bool indicates whether this strategy needs a SurrogateManager.
Optimizer.validate() checks this attribute to confirm you aren’t trying to use a strategy with requires_surrogate=True while surrogate_manager is unset.
Built-in Strategies#
Class |
Parameters |
Approach |
|---|---|---|
|
None |
Uses no surrogate; truly evaluates every generated candidate |
|
None |
Generates one candidate per step and supports asynchronous refill |
|
|
Scores every candidate with the surrogate, and truly evaluates only the top |
|
|
Generates |
|
|
Advances |
IndividualBasedStrategy selects by fraction of individuals, while PreSelectionStrategy selects by count of individuals.
GenerationBasedStrategy switches between surrogate and true evaluation by generation, not by individual.
DirectStrategy is the comparison baseline that uses no surrogate at all, with requires_surrogate=False.
For steady-state execution, combine DirectStrategy with an
AsyncEvaluationScheduler. The scheduler fills available worker slots, polls without
blocking, and commits each completed update in the lifecycle order.
Each Strategy’s pipeline structure#
Class |
Pipeline |
|---|---|
|
CountGeneration → Ask → TrueEvaluation → ArchiveUpdate → Tell |
|
CountGeneration → Ask → SurrogatePredictStage → AcquisitionStage → SortByScore → TrueEvaluation (ratio-based) → ArchiveUpdate → Tell |
|
CountGeneration → Ask (n_candidates) → SurrogatePredictStage → AcquisitionStage → TopKSelection(k=n_select) → TrueEvaluation → ArchiveUpdate → Tell |
|
SurrogateOnlyLoop (gen_ctrl times) → CountGeneration → Ask → TrueEvaluation → ArchiveUpdate → Tell |
Each Strategy’s components include the EvaluationPlan, asynchronous evaluation submission and collection, Feedback, and Tell.
A Stage is a compatibility execution unit for part of that work, not the same execution contract as the entire structured Pipeline.
See Stage and Framework for details.
Which Strategy to choose#
For problems where evaluation cost is extremely high, IndividualBasedStrategy or PreSelectionStrategy, which use the surrogate to filter out most candidates, are effective.
Early in the search, when the surrogate’s reliability is still low, or when you don’t want to pay the surrogate’s own training cost frequently, GenerationBasedStrategy, which advances multiple generations at once using only the surrogate, is suited.
For problems where the surrogate’s approximation error itself is unacceptable, or evaluation cost is sufficiently low, relying solely on true evaluation via DirectStrategy is reasonable.
Behavior of runtime swapping#
When you change configuration such as provider.algorithm or provider.surrogate_manager at a step boundary in iterate() or run(), the execution environment detects the change, recompiles the plan, and applies it from the next generation.
This procedure works on both the Stage compatibility path and the graph-native path.
When a Component requests recompilation, the plan node returns RuntimeCommand or RequestRecompile through NodeResult.commands.
The request is accepted when the Runtime environment provides recompile(), and recompilation happens between steps. Rejection is also a normal result.
Implementing a custom Strategy#
When implementing a custom candidate-selection method, choose separately between the graph-native path and the Stage compatibility path.
Graph-native path: Implement build_graph() and return a ComponentGraph.
For a gradual migration using the Pipeline DSL, wrap each Stage with stage_component() before passing it to lower_pipeline().
from saealib import OptimizationStrategy, Pipeline
from saealib.core import lower_pipeline
from saealib.stages import stage_component
class CustomStrategy(OptimizationStrategy):
"""Skeleton for building a Strategy ComponentGraph from the Pipeline DSL."""
requires_surrogate = False
def build_graph(self, provider):
pipeline = Pipeline(
steps=[
stage_component(first_stage),
stage_component(second_stage),
]
)
return lower_pipeline(pipeline)
first_stage and second_stage stand for the Stage instances to combine.
This example shows only the boundary for building a ComponentGraph from a Pipeline; add evaluation-plan and Feedback contracts according to the Stage composition.
When adjusting only part of an existing Strategy, changing the compatibility Pipeline with Pipeline.replace/find is preferable to creating a new Strategy.
References#
saealib.AsyncEvaluationScheduler