Source code for alf_core.optimizer.optimizer
# Copyright 2026 InstaDeep Ltd. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import time
from alf_core.dataclasses import Candidate, State
from alf_core.optimizer.acquisition_function import AcquisitionFunction
from alf_core.optimizer.search import BaseSearch
[docs]
class Optimizer:
"""Implements the ask/tell interface for active learning.
Coordinates search and acquisition functions to propose candidates and update
the surrogate model iteratively.
"""
def __init__(
self,
acquisition_fn: AcquisitionFunction,
search_fn: BaseSearch,
) -> None:
"""Initialize the optimizer.
Args:
acquisition_fn: Acquisition function for scoring candidates.
search_fn: Search function for generating candidate pools.
"""
self.acquisition_fn = acquisition_fn
self.search_fn = search_fn
[docs]
def ask(
self,
state: State,
) -> tuple[list[Candidate], State]:
"""Propose the next batch of candidates to evaluate.
Uses the search function to generate a candidate pool, then the acquisition
function to score and select the top candidates. In the first round, uses
the training dataset if available.
Args:
state: Current task state.
Returns:
A tuple containing:
- The proposed candidates to evaluate
- Updated state with ask_time metric
"""
t0 = time.perf_counter()
search_candidates = self.search_fn(state)
acquisition_candidates = self.acquisition_fn(search_candidates, state)
acquired_candidates = acquisition_candidates.get_top_k(state.acq_batch_size).candidates
t1 = time.perf_counter()
state.round_metrics.metrics["ask_time"] = t1 - t0
return acquired_candidates, state
[docs]
def tell(
self,
state: State,
) -> State:
"""Update the surrogate model with newly acquired data.
Trains the surrogate model on the updated training and validation datasets,
then computes and updates metrics.
Args:
state: Current task state with updated dataset.
Returns:
Updated state with tell_time, training_history, and optimizer metrics.
"""
t0 = time.perf_counter()
epoch_metrics = state.surrogate.fit(
train_data=state.dataset.train_dataset,
val_data=state.dataset.validation_dataset,
)
t1 = time.perf_counter()
state.round_metrics.training_history = epoch_metrics # full replacement, not append
state.round_metrics.metrics["tell_time"] = t1 - t0
state.round_metrics.metrics.update(self.get_metrics(state))
return state
[docs]
def get_metrics(
self,
state: State,
) -> dict[str, float]:
"""Collect metrics from acquired candidates, surrogate, and search functions.
Args:
state: Current task state.
Returns:
Dictionary of metric names to values, including:
- Metrics on acquired candidates (mean, max, min)
- Surrogate training metrics (if available)
- Search function metrics
"""
acquired_candidates = state.history[-1]
metrics = {
# Metrics on the acquired candidates during the current round
"acquired_candidates/round_mean": acquired_candidates.labels.mean(),
"acquired_candidates/round_max": acquired_candidates.labels.max(),
"acquired_candidates/round_min": acquired_candidates.labels.min(),
}
if state.surrogate:
surrogate_metrics = state.surrogate.get_training_summary_metrics()
metrics.update({f"surrogate/{k}": v for k, v in surrogate_metrics.items()})
metrics.update(self.search_fn.get_metrics(state))
return metrics