Skip to content

AskTellAlgorithm and AlgoContext

The Python-owned ask/tell authoring surface, for algorithms more naturally expressed as a loop over an EvalSession than as an engine-hosted generate() hook. sezgi.algo.Algorithm is kept as a compat alias for AskTellAlgorithm under its pre-rename name.

algo

Subclassable algorithm authoring over the EvalSession ask/tell core.

M3-4 Task 2: AskTellAlgorithm (renamed from Algorithm, M4-1 Task 3 -- see the class's own docstring note) is the pure-Python ABC that later M3-4 tasks port 17 example algorithms onto (with bit-exact RNG parity against existing pure scripts). A subclass implements setup(ctx)/step(ctx); solve() drives the setup/step loop over an AlgoContext until the budget is exhausted and returns a SolveResult.

M4-1 Task 3: the top-level sezgi.Algorithm name was repurposed for the NEW engine-hosted class-first base (sezgi/algorithm.py) -- this module's own ask/tell base is renamed AskTellAlgorithm, with a module-level Algorithm = AskTellAlgorithm compat alias kept below (so sezgi.algo. Algorithm -- the old import path -- still resolves, just no longer the top-level sezgi.Algorithm binding).

M3-4 Task 3: bbob_records provides a multi-scenario sweep helper that records runs in the same shape as run_experiment, allowing custom Algorithm instances to feed sezgi's stats pipeline (results_matrix, per_budget_packages).

M3-8 Task 7: AlgoContext widens from Float-only to also cover permutation-typed problems (sezgi.problems.tsp(...)) -- the exact minimal surface of the approved scope ruling: ctx.kind ("float" or "permutation"), ctx.n (the dimension -- for a permutation problem, the number of cities/positions), ctx.random_permutation() (a uniformly random 0-based tour, drawn from the session's own seeded RNG stream -- NOT Python's random module, so a permutation-typed run is reproducible the same way a Float-typed one is via ctx.rng), and ctx.two_opt(tour, i, j) (the classic 2-opt reversal move; pure Python, no RNG -- see its own docstring for the exact inclusive/exclusive i/j semantics). Every Float-typed attribute/method (ctx.dim, ctx.bounds, ctx.random_point(), ...) is UNCHANGED -- this widening is purely additive.

AskTellAlgorithm

Bases: abc.ABC

Subclass, implement setup() and step(), call solve().

M4-1 Task 3 rename: this class was sezgi.Algorithm through M3-4/M3-8; the top-level sezgi.Algorithm name now binds the NEW engine-hosted class-first base (sezgi/algorithm.py, Algorithm.generate(pop, ctx) run INSIDE the Rust engine loop) instead. This class is unchanged in every other respect -- same setup()/step()/solve() contract, same AlgoContext, same SolveResult. See the module-level Algorithm = AskTellAlgorithm compat alias below for the old import path.

__abstractmethods__ class-attribute

__abstractmethods__ = frozenset({'step', 'setup'})

frozenset() -> empty frozenset object frozenset(iterable) -> frozenset object

Build an immutable unordered collection of unique elements.

__doc__ class-attribute

__doc__ = 'Subclass, implement setup() and step(), call solve().\n\n    M4-1 Task 3 rename: this class was `sezgi.Algorithm` through M3-4/M3-8;\n    the top-level `sezgi.Algorithm` name now binds the NEW engine-hosted\n    class-first base (`sezgi/algorithm.py`, `Algorithm.generate(pop, ctx)`\n    run INSIDE the Rust engine loop) instead. This class is unchanged in\n    every other respect -- same setup()/step()/solve() contract, same\n    `AlgoContext`, same `SolveResult`. See the module-level `Algorithm =\n    AskTellAlgorithm` compat alias below for the old import path.'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

__module__ class-attribute

__module__ = 'sezgi.algo'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

__weakref__ property

__weakref__

list of weak references to the object

setup

setup(ctx)

REQUIRED: called once, before the first step(), to perform any one-time initialization (e.g. seeding an initial population via ctx.evaluate(...)). ctx is this run's AlgoContext -- see the class docstring.

step

step(ctx)

REQUIRED: called repeatedly, once per iteration, until the budget is exhausted (ctx.remaining <= 0). Each call MUST evaluate at least one point (via ctx.evaluate(...)) or raise BudgetExhausted itself -- solve()'s own driver raises RuntimeError if a step() call consumes no budget at all (see solve()'s own docstring).

solve

solve(problem, budget, seed, log_dir=None)

problem: routed through sezgi.as_native_problem (final-review fix 5, matching sezgi.solve/Algorithm.run/the builtin wrapper classes): a native Problem handle passes through unchanged. Anything not a handle or a sezgi.Problem subclass instance raises as_native_problem's own friendly TypeError. A sezgi.Problem subclass instance itself converts cleanly, but is then rejected by EvalSession.for_problem below with its own honest ValueError (no ask/tell session type exists for a CallableSpaced problem -- a pre-existing Rust-side restriction this fix does not lift, see lib.rs:1753-1770) instead of pyo3's confusing raw conversion error a Problem subclass used to fail with here.

AlgoContext

AlgoContext(session, dim, bounds, seed, kind)

Everything a subclass touches during a run. Wraps the EvalSession (sole keeper of counting/best/logging) plus a seeded random.Random.

kind/n/bounds all come from the session's own problem: kind is "float" for every continuous problem, "permutation" for a permutation-typed one (sezgi.problems.tsp(...), M3-8 Task 7); bounds is None for a permutation-typed session (there is no uniform (lo, hi) domain to sample -- use random_permutation()/two_opt() instead of random_point()); n is the same dimension value as dim under a second, kind-neutral name (for a permutation problem, the number of cities/positions -- dim reads oddly for a tour, n doesn't).

Constructed internally by the AskTellAlgorithm run loop -- subclasses receive an already-built ctx, they never construct one themselves. session: the EvalSession this context wraps. dim/ bounds/seed/kind: see the class docstring for n/bounds/kind's exact semantics; seed seeds this context's own random.Random (self.rng).

__doc__ class-attribute

__doc__ = 'Everything a subclass touches during a run. Wraps the EvalSession\n    (sole keeper of counting/best/logging) plus a seeded random.Random.\n\n    `kind`/`n`/`bounds` all come from the session\'s own problem: `kind` is\n    `"float"` for every continuous problem, `"permutation"` for a\n    permutation-typed one (`sezgi.problems.tsp(...)`, M3-8 Task 7); `bounds`\n    is `None` for a permutation-typed session (there is no uniform (lo, hi)\n    domain to sample -- use `random_permutation()`/`two_opt()` instead of\n    `random_point()`); `n` is the same dimension value as `dim` under a\n    second, kind-neutral name (for a permutation problem, the number of\n    cities/positions -- `dim` reads oddly for a tour, `n` doesn\'t).'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

__module__ class-attribute

__module__ = 'sezgi.algo'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

__weakref__ property

__weakref__

list of weak references to the object

budget property

budget

The run's total evaluation budget, as given to solve().

A fixed value for the whole run -- setup()/step() never change it; compare against evals_used/remaining to know how much is left.

evals_used property

evals_used

Evaluations consumed so far.

Updated by every evaluate() call (each accepted batch adds len(points)); read this BEFORE calling step() again to size the next batch against remaining.

f_opt property

f_opt

The problem's known optimum (a float), or None if it has none -- see sezgi.Problem.optimum's own doc for what "known" means.

Fixed for the whole run; used by solve()'s own driver to compute the returned SolveResult.gap.

remaining property

remaining

Evaluations left before the budget is exhausted (budget - evals_used).

evaluate() itself already checks this and raises BudgetExhausted for an over-large batch; reading remaining directly lets step() size its own batch instead of guessing.

best

best()

Returns (best_x, best_f), the best point evaluated so far, or None if nothing has been evaluated yet.

best_x's shape matches evaluate()'s own points rows (a length-dim list of floats, or a tour, per self.kind).

random_point

random_point()

A uniformly random point in self.bounds (one coordinate per self.dim), drawn from self.rng (this context's own seeded random.Random -- NOT the underlying session's Rust-side stream; see random_permutation()'s own doc for that distinction).

Raises ValueError for a permutation-typed context (self.bounds is None) -- use random_permutation() instead.

random_permutation

random_permutation()

A uniformly random 0-based tour (a permutation of range(self.n)), drawn from the underlying session's own seeded RNG stream -- NOT self.rng (Python's random.Random, used only by the Float path): a permutation-typed run must be reproducible from the same seed the same way a Float-typed one is, so the draw comes from the session (the house RngStream, Rust-side), not from Python's random module. See EvalSession.random_permutation's own doc for the shuffle algorithm.

Raises ValueError if self.kind != "permutation".

two_opt

two_opt(tour, i, j)

Returns a NEW tour with the segment tour[i:j+1] -- positions i through j, 0-based, INCLUSIVE on both ends -- reversed in place: the classic 2-opt move, replacing edges (tour[i-1], tour[i]) and (tour[j], tour[j+1]) with (tour[i-1], tour[j]) and (tour[i], tour[j+1]) (the tour's own closing edge wraps at the ends, unaffected unless i == 0 or j == len(tour) - 1).

Requires 0 <= i <= j < len(tour); raises ValueError otherwise. i == j reverses a single-element segment (a no-op: the returned tour equals tour). i == 0, j == len(tour) - 1 reverses the WHOLE tour (still a no-op on tour length/validity, but exercises both boundaries at once).

Pure Python, no RNG, does not mutate tour or touch the evaluation budget -- call evaluate([...]) on the result to score it. Does not itself validate that tour is a permutation (a tour from random_permutation() or an earlier two_opt() call already is one); available regardless of self.kind.

evaluate

evaluate(points)

Evaluates a batch of points, all-or-nothing.

points is a list of rows: each a length-dim list of floats for a Float-typed context, or each a length-n 0-based tour (a permutation of range(n)) for a permutation-typed one.

Raises BudgetExhausted (this module) if points doesn't fit the remaining budget -- checked BEFORE calling the session, so nothing is charged on a rejected batch. Raises ValueError (from the underlying EvalSession, not BudgetExhausted) if any row is the wrong length, contains a non-finite coordinate (NaN/inf, Float path), or is not a valid tour (out-of-range/repeated city, permutation path) -- that check happens session-side, so it fires even for a batch that fits the budget.

SolveResult

SolveResult(algo: str, seed: int, budget: int, evals_used: int, best_x: list, best_f: float, f_opt: float | None, gap: float | None)

The result of any front door's run: returned by AskTellAlgorithm.solve, sezgi.Algorithm.run, and every sezgi.builtins wrapper class's run() alike -- one result shape everywhere, per this milestone's own design ruling (see _wrap_result's own docstring for the shared construction).

algo: the algorithm's name, as recorded in the result (see each caller's own name/algo_name resolution). seed: the run's master seed. budget: the run's evaluation budget. evals_used: evaluations actually consumed (<= budget). best_x: the best point evaluated -- a length-dim list of floats for a Float-typed session, a tour (list of ints) for a permutation-typed one, or the block-converted value sezgi.Problem.evaluate would receive (bare/tuple, see sezgi.problem's conversion table) for an engine-hosted sezgi.Algorithm run. best_f: the fitness/objective value at best_x. f_opt: the problem's known optimum, or None if it has none. gap: best_f - f_opt, or None when f_opt is None.

__annotations__ class-attribute

__annotations__ = {'algo': <class 'str'>, 'seed': <class 'int'>, 'budget': <class 'int'>, 'evals_used': <class 'int'>, 'best_x': <class 'list'>, 'best_f': <class 'float'>, 'f_opt': float | None, 'gap': float | None}

dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)

__dataclass_fields__ class-attribute

__dataclass_fields__ = {'algo': Field(name='algo',type=<class 'str'>,default=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,default_factory=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'seed': Field(name='seed',type=<class 'int'>,default=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,default_factory=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'budget': Field(name='budget',type=<class 'int'>,default=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,default_factory=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'evals_used': Field(name='evals_used',type=<class 'int'>,default=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,default_factory=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'best_x': Field(name='best_x',type=<class 'list'>,default=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,default_factory=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'best_f': Field(name='best_f',type=<class 'float'>,default=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,default_factory=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'f_opt': Field(name='f_opt',type=float | None,default=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,default_factory=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'gap': Field(name='gap',type=float | None,default=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,default_factory=<dataclasses._MISSING_TYPE object at 0x7fa5130c6de0>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD)}

dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)

__doc__ class-attribute

__doc__ = "The result of any front door's run: returned by\n    `AskTellAlgorithm.solve`, `sezgi.Algorithm.run`, and every\n    `sezgi.builtins` wrapper class's `run()` alike -- one result shape\n    everywhere, per this milestone's own design ruling (see\n    `_wrap_result`'s own docstring for the shared construction).\n\n    algo: the algorithm's name, as recorded in the result (see each\n        caller's own `name`/`algo_name` resolution).\n    seed: the run's master seed.\n    budget: the run's evaluation budget.\n    evals_used: evaluations actually consumed (`<= budget`).\n    best_x: the best point evaluated -- a length-`dim` list of floats for a\n        Float-typed session, a tour (list of ints) for a permutation-typed\n        one, or the block-converted value `sezgi.Problem.evaluate` would\n        receive (bare/tuple, see `sezgi.problem`'s conversion table) for an\n        engine-hosted `sezgi.Algorithm` run.\n    best_f: the fitness/objective value at `best_x`.\n    f_opt: the problem's known optimum, or `None` if it has none.\n    gap: `best_f - f_opt`, or `None` when `f_opt` is `None`."

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

__match_args__ class-attribute

__match_args__ = ('algo', 'seed', 'budget', 'evals_used', 'best_x', 'best_f', 'f_opt', 'gap')

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

__module__ class-attribute

__module__ = 'sezgi.algo'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

__weakref__ property

__weakref__

list of weak references to the object

BudgetExhausted

Bases: builtins.Exception

Raised by AlgoContext.evaluate when a batch does not fit the remaining budget. The driver catches it to end the run cleanly; user code may also catch it to trigger its own finalization.

__doc__ class-attribute

__doc__ = 'Raised by AlgoContext.evaluate when a batch does not fit the\n    remaining budget. The driver catches it to end the run cleanly; user\n    code may also catch it to trigger its own finalization.'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

__module__ class-attribute

__module__ = 'sezgi.algo'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

__weakref__ property

__weakref__

list of weak references to the object

bbob_records

bbob_records(factory, fids, dims, instances, seeds, budget, log_dir=None)

BBOB-scenario sweep helper that records runs in stats-pipeline shape.

Runs an AskTellAlgorithm across a multi-scenario sweep (combinations of BBOB functions, dimensions, instances, and seeds) and records results in the same dict shape as run_experiment: (algo, fid, dim, instance, seed, budget, best_f, f_opt, gap, evals_used, wall_secs).

The record-key contract matches run_experiment (see its own docstring in sezgi.__init__), allowing this helper's output to mix freely with Rust-side records in one call to results_matrix/per_budget_packages.

Piotrowski et al. (2025) show algorithm rankings on benchmark comparisons can flip depending on which evaluation budget is examined (documented in per_budget_packages), motivating multi-budget reporting as the default.

factory: zero-arg callable returning a FRESH AskTellAlgorithm instance per run (a bare AskTellAlgorithm subclass works). fids: list of BBOB function IDs (1..24). dims: list of dimensions. instances: list of BBOB instances (1..). seeds: list of random seeds. budget: fixed evaluation budget for all runs. log_dir: optional path to an IOH output directory. When given, each run calls algo.solve(..., log_dir=log_dir) on its own fresh session, which creates a fresh IohLogger and finish()es it once per run. IohLogger::finish merges that single run into any existing (algo, fid, dim) scenario BY RUN IDENTITY -- an (instance, seed, budget) not already present is appended, and a re-run of the exact same (instance, seed, budget) rewrites its own prior entry in place rather than duplicating it -- so sweeping multiple seeds through this helper accumulates every seed's run into one archive instead of each solve() call clobbering the last. Omitting log_dir keeps the behavior unchanged (no IOH logging). The resulting archive is readable via read_ioh_records, ecdf, and coco_export, mirroring run_experiment's contract exactly.

Returns: list[dict], one record per (fid, dim, instance, seed) run, with keys exactly {algo, fid, dim, instance, seed, budget, suite, best_f, f_opt, gap, evals_used, wall_secs}.