Configuring Algorithms

The Algorithm constructor accepts every runtime setting in two forms:

  • direct constructor arguments : pass numeric limits, reporter type, history flags, etc. as keyword arguments. This is the quickest way to get started.

  • pre-built objects : provide an explicit StoppingCondition, Reporter, HistoryTracker, or Checkpointer instance. This gives you finer control, reusability across runs, and the ability to extend behaviour through custom subclasses.

Both styles can be mixed; parameters given as objects take precedence over raw values. The rest of this page details the object-based approach because it makes all settings visible in one place, but everything described also applies when you use the simpler keyword-argument style.

Quick examples:

from metaheuristic_designer import Algorithm

# Direct arguments : compact
algo = Algorithm(
    objfunc,
    strategy,
    stop_cond="max_iterations or real_time_limit",
    max_iterations=500,
    real_time_limit=30.0,
    reporter="tqdm",
    track_median=True,
)

# Object-based : explicit and reusable
from metaheuristic_designer.stopping_condition import StoppingCondition
from metaheuristic_designer.reporters.tqdm_reporter import TQDMReporter
from metaheuristic_designer.history_tracker import HistoryTracker

stop = StoppingCondition(
    condition_str   = "max_iterations or real_time_limit",
    max_iterations = 500,
    real_time_limit = 30.0,
)
algo = Algorithm(
    objfunc,
    strategy,
    stopping_condition = stop,
    reporter           = TQDMReporter(),
    history_tracker    = HistoryTracker(track_median=True),
)

Except for objfunc and strategy, every other component is optional; if omitted the Algorithm creates sensible defaults.

Stopping Condition

A stopping condition is defined by a logical expression that combines tokens with and, or and parentheses. Every token that appears in the expression must have its corresponding numeric limit provided; limits for unused tokens can be left as None.

In the following table we indicate the token used in the stopping_condition string as well as the parameter that configures when that condition in the StoppingCondition

Tokens and their meaning

Token

Parmeter

Meaning

max_evaluations

max_evaluations

Maximum number of objective function evaluations.

max_iterations

max_iterations

Maximum number of iterations (generations).

real_time_limit

real_time_limit

Wall-clock time limit in seconds.

cpu_time_limit

cpu_time_limit

CPU time limit in seconds.

objective_target

objective_target

Target value for the raw objective. For minimisation (mode="min"), the algorithm stops when best_objective <= objective_target; for maximisation (mode="max"), when best_objective >= objective_target.

convergence

max_patience

Stops when the best fitness has not improved for max_patience consecutive iterations. This token requires max_patience to be set.

How to combine tokens and, or, and parentheses work exactly as in a logical expression:

  • and : the algorithm stops only when both sides are satisfied. Example "max_iterations and convergence" → stop when the maximum number of iterations has been reached and the convergence criterion is met.

  • or : the algorithm stops as soon as any of the sides is satisfied. Example "max_iterations or real_time_limit" → stop when either the iteration limit is reached or the time runs out, whichever happens first.

  • Parentheses allow more complex combinations. Example "(max_iterations or real_time_limit) and convergence" → stop only if (iterations or time) have passed and convergence is reached.

You can chain as many tokens as you like; the expression will be evaluated as a boolean tree with and having higher precedence than or (like standard Boolean algebra), so "a and b or c" is equivalent to "(a and b) or c".

Note : objective_target is compared directly against the raw objective that your ObjectiveFunc returns. No internal conversion is applied. Supply the target in the same units and sign as your problem’s objective.

Create a StoppingCondition object:

from metaheuristic_designer.stopping_condition import StoppingCondition

stop = StoppingCondition(
    condition_str       = "max_iterations or real_time_limit",
    progress_metric_str = None,               # defaults to condition_str
    max_iterations      = 500,
    real_time_limit     = 30.0,
    max_evaluations     = None,
    cpu_time_limit      = None,
    objective_target    = None,
    optimization_mode   = objfunc.mode,
)

When using convergence, provide max_patience:

stop = StoppingCondition(
    condition_str    = "max_iterations and convergence",
    max_iterations   = 200,
    max_patience     = 50,
    optimization_mode= objfunc.mode,
)

Or a target-driven stop:

stop = StoppingCondition(
    condition_str    = "objective_target",
    objective_target = 1e-10,
    optimization_mode= objfunc.mode,
)

Caution

If a token appears in the condition string but its limit is not supplied, an error is raised immediately. For example, forgetting max_iterations when using "max_iterations" will produce a clear message telling you what is missing. Conversely, parameters that are not required by the expression are ignored.

Progress metric : The progress_metric_str controls how the algorithm computes its progress (a number between 0 and 1). This progress value is not merely for display; it is consumed internally by parameter schedules (e.g., linearly decaying mutation strength) and by annealing strategies. Therefore you should choose a monotonic metric that reflects the expected convergence path of your algorithm.

Tokens like max_iterations, real_time_limit, max_evaluations and even objective_target are monotonic (the best objective only improves), so they produce reliable progress curves. The only token to avoid in progress_metric_str is convergence, because the progress would reset each time the best fitness improves, making parameter schedules erratic.

It is technically allowed to use convergence in the progress expression, but it is discouraged. If you need convergence as a stopping condition, confine it to condition_str and rely on a monotonic token for progress_metric_str.

You can decouple the two expressions:

stop = StoppingCondition(
    condition_str       = "objective_target",
    progress_metric_str = "real_time_limit",
    objective_target    = 1e-5,
    real_time_limit     = 60.0,
)

Reporter

Reporters govern what the algorithm prints or displays. Three implementations exist:

from metaheuristic_designer.reporters import TQDMReporter, VerboseReporter, SilentReporter

# Progress bar (ideal for Jupyter / terminal)
reporter = TQDMReporter(resolution=1000)

# Periodic text output at a configurable interval
reporter = VerboseReporter(verbose_timer=0.5)   # seconds between prints

# No output
reporter = SilentReporter()

The VerboseReporter prints a summary block at most once per verbose_timer interval.

History Tracker

The history tracker records metrics across iterations. All flags default to False except track_best, which is always active.

from metaheuristic_designer.history_tracker import HistoryTracker

history = HistoryTracker(
    track_best            = True,    # always recorded
    track_median          = False,
    track_worst           = False,
    track_diversity       = False,
    track_parameters      = False,   # record scheduled parameter values
    track_full_objective  = False,   # store the full fitness vector per generation
    track_full_population = False,   # store entire population each generation
)
  • track_diversity adds a diversity column (e.g., average pairwise distance or genotype spread).

  • track_parameters appends a column for each scheduled parameter (mutation strength, crossover probability, etc.) to the DataFrame returned by to_pandas().

  • track_full_objective enables to_pandas_full_objective(), which returns a

    wide-format DataFrame (one column per individual) for boxplots or violin plots.

Warning

Activating track_full_population or track_full_objective can consume significant memory for long runs or large populations. Use these options only when you need the full evolution trace or detailed fitness distributions.

Be very careful to combine these flags with checkpoints, they can cause checkpoints to be multiple Gigabytes in size and storage might consume a long time.

When track_full_objective is enabled, you can retrieve the data as a DataFrame:

wide_df = algo.history_tracker.to_pandas_full_objective()
# wide‑format: iteration, Individual_0, Individual_1, ... (one column per individual)

Checkpointer

If you provide a Checkpointer, the algorithm will periodically dump its entire state to disk. This makes it possible to resume interrupted runs. The checkpointer also saves on SIGINT (Ctrl+C), so you can manually stop a run and later reload it.

from metaheuristic_designer.checkpointer import Checkpointer

check = Checkpointer(
    checkpoint_file         = "my_run.pkl",
    iteration_frequency     = 10,        # save every 10 iterations
    time_frequency          = 300.0,     # save every 5 minutes
)

Only one of iteration_frequency or time_frequency needs to be set, and both can be active at the same time.

To load a previous checkpoint and continue you must call resume() instead of optimize():

algo = check.load(
    file_name       = "my_run.pkl",
    history_tracker = HistoryTracker(),   # reuse or fresh
    reporter        = TQDMReporter(),     # re-attach a reporter
)

# ⚠️  ABSOLUTELY DO NOT call algo.optimize() here, that would RESTART from scratch!
algo = algo.resume()

Warning

DO NOT call optimize() on a loaded algorithm, it will silently discard the checkpoint and start a brand-new run!.

Always use resume() when you want to pick up where you left off.

After loading, the reporter is not saved inside the checkpoint. You must provide it again explicitly when calling load(). Running without a reporter is possible but not recommended for long runs.

Fitness vs Objective: a Quick Guide

The library internally converts every raw objective value to a fitness that is always maximised. This keeps the internal logic simple. Two methods help you retrieve results in the appropriate representation:

  • best_solution(): returns the best decoded solution (phenotype) and its raw objective value. Use this for reporting and for using the result in your application.

  • best_individual(): returns the best encoded genotype and its fitness value (the internal maximised metric). Use this when you need to inspect the internal representation or implement optimization subroutines behaviour.

These methods are available on both Population and Algorithm objects. After a run, you can call them directly on the algorithm instance.

# Access via the algorithm
solution, objective = algo.best_solution()
genotype, fitness   = algo.best_individual()

# Access via the final population
solution, objective = population.best_solution()
genotype, fitness   = population.best_individual()

When you provide a stopping target, use objective_target with StoppingCondition. The comparison is direct: for minimisation the algorithm stops when best_objective <= objective_target; for maximisation when best_objective >= objective_target. You do not need to think in fitness units at all.

Putting It All Together

Here is a complete example that minimises the Sphere function with a genetic algorithm, using object-based configuration.

import numpy as np
from metaheuristic_designer import Algorithm, check_random_state
from metaheuristic_designer.benchmarks import Sphere
from metaheuristic_designer.strategies import GA
from metaheuristic_designer.initializers import UniformInitializer
from metaheuristic_designer.operators import create_operator
from metaheuristic_designer.parent_selection import create_parent_selection
from metaheuristic_designer.survivor_selection import create_survivor_selection
from metaheuristic_designer.stopping_condition import StoppingCondition
from metaheuristic_designer.history_tracker import HistoryTracker
from metaheuristic_designer.reporters.tqdm_reporter import TQDMReporter
from metaheuristic_designer.checkpointer import Checkpointer

rng = check_random_state(42)
objfunc = Sphere(5, mode="min")

# Build the search strategy
strategy = GA(
    initializer   = UniformInitializer(objfunc.dimension, objfunc.lower_bound, objfunc.upper_bound, population_size=100, random_state=rng),
    mutation_op   = create_operator("mutation.gaussian_mutation", N=1, F=0.1, random_state=rng),
    crossover_op  = create_operator("crossover.uniform_crossover", random_state=rng),
    parent_sel    = create_parent_selection("tournament", amount=50, tournament_size=3, random_state=rng),
    survivor_sel  = create_survivor_selection("elitism", amount=25, random_state=rng),
    mutation_prob = 0.3,
    crossover_prob= 0.9,
    random_state  = rng,
)

# Configure runtime objects
stopping = StoppingCondition(
    condition_str    = "max_iterations or objective_target",
    max_iterations  = 100,
    objective_target = 1e-5,
    optimization_mode= objfunc.mode,
)

reporter = TQDMReporter()

history = HistoryTracker(track_median=True, track_parameters=True)

checkpointer = Checkpointer("ga_sphere.pkl", iteration_frequency=20)

# Create and run
algo = Algorithm(
    objfunc,
    strategy,
    stopping_condition = stopping,
    reporter           = reporter,
    history_tracker    = history,
    checkpointer       = checkpointer,
)

population = algo.optimize()
solution, obj = population.best_solution()
print(f"Best objective: {obj:.6g}")
print(algo.history_tracker.to_pandas().tail())

After execution, population holds the final generation, and you can use algo.history_tracker to produce convergence plots (see the plotting tutorial).

Parallelism Note

The parallel and threads parameters of Algorithm are currently placeholders and have no effect. They are accepted for future compatibility but should not be relied upon.