Choose a Hook, Stage, or Callback#
The prerequisite is wanting to add observation or post-processing to an existing execution path.
What you’ll be able to do
By the end of this page, you’ll be able to distinguish Hooks that change data, Stages that replace execution units, and Callbacks that observe events.
See the optimization components index; for a custom contract, continue to Custom components.
Compare the boundaries#
Mechanism |
Purpose |
Execution point |
Can change state? |
Suitable uses |
|---|---|---|---|---|
|
Post-process an existing Operator’s output |
After Crossover or Mutation |
Can change candidates through its return value |
Repair, rounding, and bounding |
|
Process after a Surrogate fit |
After |
Can update the model or external state in the Hook |
Fit records and model post-processing |
|
Implement or replace a compatibility execution unit |
At a Stage position in the Pipeline |
Can update state by returning an |
Add generation processing and swap a Stage |
|
Observe events |
When a registered Event fires |
|
Logging, history, and condition checks |
Change data with a Hook#
To change an Operator’s candidate array, use with_post() rather than CallbackManager.
with_post() returns a copy with the Hook added without modifying the original instance.
import numpy as np
from saealib.operators import MutationUniform
def snap_to_grid(offspring, parents, rng, ctx):
return np.round(offspring * 10.0) / 10.0
mutation = MutationUniform(0.3).with_post(snap_to_grid)
For post-processing after a Surrogate fit, pass fn(train_x, train_y, ctx) -> None to with_post_fit().
See the Surrogate concept for the contract used by this Hook.
Replace an execution unit with a Stage#
Stage is the compatibility boundary execute(state) -> state.
Return a new State with state.replace() when updating state.
It is not the boundary for implementing a structured Framework Component or Compiler directly; for that, see Framework extensions.
from saealib import Stage
class LogGenerationStage(Stage):
name = "log_generation"
def execute(self, state):
print(state.gen)
return state
See the Stage concept for Pipeline composition and the Stage contract.
Observe with a Callback#
CallbackManager.register() registers a function that receives an Event type and event.
The event.ctx read by the handler is the public read boundary for generation and evaluation counts.
import numpy as np
from saealib import GenerationEndEvent
from saealib import Optimizer, Problem
def objective(x):
return np.sum(x**2)
problem = Problem(
objective,
dim=3,
n_obj=1,
direction=np.array([-1.0]),
lb=[-5.0] * 3,
ub=[5.0] * 3,
)
optimizer = Optimizer(problem, seed=0)
history = []
def record(event):
history.append((event.ctx.gen, event.ctx.fe))
optimizer.cbmanager.register(GenerationEndEvent, record)
Replacing candidate arrays or rewriting state is outside a Callback’s responsibility. Use Callbacks for event observation, logging, and recording history.
Use Custom components when you need to implement a component contract, and see Framework extensions when changing the contract or Runtime itself.