Optimisers#

mergen.algorithms#

Registry of optimisation algorithms for space-filling design.

This subpackage holds Mergen’s optimisation engines (SA, SCE, ESE, …) and a lightweight registry that lets users discover, look up, and extend the set of algorithms without modifying Mergen’s core.

Public functions#

get_optimizer()

Look up an optimiser class by its registered name.

list_optimizers()

List the names of all registered optimisers.

register_optimizer()

Register a new optimiser (typically called by third-party packages or by Mergen’s own algorithm modules at import time).

Examples

>>> from mergen.algorithms import list_optimizers, get_optimizer
>>> list_optimizers()
[]   # algorithms are added in subsequent phases (SA, SCE, ESE)
>>> # Once SA is implemented:
>>> # SAOptimizer = get_optimizer('sa')
>>> # sa = SAOptimizer(n_restarts=5, max_iter=10000)

Registering a custom optimiser#

>>> from mergen.algorithms import BaseOptimizer, register_optimizer
>>> class MyOptimizer(BaseOptimizer):
...     name = 'my_algo'
...     @classmethod
...     def get_default_params(cls): return {'param': 1.0}
...     def optimize(self, *a, **kw): ...
>>> register_optimizer('my_algo', MyOptimizer)
>>> # Now usable via Sampler.set_optimizer('my_algo', ...)
class mergen.algorithms.BaseOptimizer(**kwargs)[source]#

Bases: ABC

Abstract base class for space-filling design optimisers.

Every optimisation algorithm in Mergen (SA, SCE, ESE, …) inherits from this class and implements two methods:

  1. get_default_params() (classmethod) — return default hyperparams

  2. optimize() — run the optimisation

Hyperparameters are stored as instance attributes; the constructor populates them from the class defaults and any keyword arguments supplied by the user. Validation of parameter values (range checks, type checks) happens lazily inside optimize(), not in __init__(), following the scikit-learn convention. This allows sklearn.base.clone()-style cloning and grid search over hyperparameters.

Subclass requirements#

  • Set the class attribute name to a short identifier ('sa', 'sce', 'ese', …). This is the key used in the registry and by Sampler.set_optimizer(name, **kwargs).

  • Implement get_default_params() to return a dict of hyperparameter names → default values. Every key here becomes a valid keyword argument to __init__() and set_params().

  • Implement optimize() to perform the actual optimisation.

Examples

>>> class DummyOptimizer(BaseOptimizer):
...     name = 'dummy'
...
...     @classmethod
...     def get_default_params(cls):
...         return {'max_iter': 1000, 'seed_offset': 0}
...
...     def optimize(self, initial_design, space, criterion,
...                  n_frozen=0, crit_start=0, seed=44, verbose=True):
...         # ... actual algorithm here ...
...         return OptimisationResult(
...             design=initial_design,
...             score=0.0,
...             metadata={'algorithm': self.name},
...         )

See also

mergen.algorithms.register_optimizer

Register a subclass.

mergen.algorithms.get_optimizer

Look up a registered algorithm.

name: str = ''#
abstractmethod classmethod get_default_params()[source]#

Return default hyperparameters for this optimiser.

Subclasses override this to declare their hyperparameter schema. The dict keys define the legal keyword arguments to __init__() and set_params(); the values are used when the user does not supply an override.

Returns:

dict – Mapping from parameter name to default value.

Return type:

Dict[str, Any]

set_params(**kwargs)[source]#

Set hyperparameters on this instance (scikit-learn convention).

Parameters:

**kwargs (Any) – Hyperparameter values to update. Unknown keys raise ValueError.

Returns:

self – Enables fluent chaining: opt.set_params(max_iter=1000).optimize(...).

Raises:

ValueError – If any key is not a valid hyperparameter for this optimiser.

Return type:

BaseOptimizer

get_params()[source]#

Return current hyperparameter values (scikit-learn convention).

Returns:

dict – Snapshot of current hyperparameters.

Return type:

Dict[str, Any]

abstractmethod optimize(initial_design, space, criterion, n_frozen=0, crit_start=0, seed=44, verbose=True, banned=None)[source]#

Run the optimisation.

banned is an optional set of grid indices that the swap operators must never select (used for user-supplied sets and externally loaded designs).

Parameters:
  • initial_design (np.ndarray, shape (n, d)) – Starting design in the original parameter space. Built by prepare_initial_design() (typically a balanced LHS plus the prescribed / focus anchor rows).

  • space (ParameterSpace) – The parameter space — provides grid, constraints, parameter names, and value ranges.

  • criterion (BaseCriterion) – The criterion to minimise. Algorithms call criterion.evaluate, criterion.incremental, criterion.begin_1d / criterion.try_1d as appropriate.

  • n_frozen (int, optional) – The first n_frozen rows of initial_design are fixed and must not be altered. These are typically prescribed points the user requires in the final design. Default: 0.

  • crit_start (int, optional) – The criterion is evaluated on initial_design[crit_start:]. Rows [0, crit_start) participate in optimisation moves (they may be swapped) but are excluded from the criterion. Used for prescribed-but- not-criterion-included points. Default: 0.

  • seed (int, optional) – Random seed for reproducibility. Default: 44.

  • verbose (bool, optional) – Print progress information. Default: True.

  • banned (Optional[set])

Returns:

OptimisationResult – The optimised design and metadata.

Return type:

OptimisationResult

Notes

Subclasses must:

  • Treat the first n_frozen rows as immutable.

  • Evaluate the criterion only on rows [crit_start:].

  • Respect any constraints in space._constraints.

  • Populate metadata with at least the algorithm name and hyperparameters used.

prepare_initial_design(anchors, budget, space, reserved, seed=44)[source]#

Build the starting design for this optimiser.

The default implementation returns a balanced Latin Hypercube seed on top of the supplied anchors. This matches the literature convention for SA (Morris & Mitchell 1995), SCE (Kang 2019), and ESE (Jin et al. 2005), which all expect an LHS as input.

Subclasses may override this method if their algorithm requires a different initial design (e.g. a greedy maximin seed, a Sobol sequence, or a user-supplied design).

Parameters:
  • anchors (np.ndarray, shape (k, d)) – Rows that must appear in the output unchanged (prescribed and focus points placed by the Sampler).

  • budget (int) – Number of additional rows to add on top of the anchors. The returned design has len(anchors) + budget rows.

  • space (ParameterSpace) – The parameter space.

  • reserved (set) – Grid indices already in use; updated in place.

  • seed (int, default 44) – RNG seed for the LHS shuffles.

Returns:

  • design (np.ndarray, shape (len(anchors) + budget, d))

  • reserved (set (updated))

Return type:

tuple[np.ndarray, set]

Parameters:

kwargs (Any)

class mergen.algorithms.OptimisationResult(design, score, converged=False, n_iter=0, elapsed=0.0, metadata=<factory>)[source]#

Bases: object

Result of a single optimisation run.

Modelled on scipy.optimize.OptimizeResult but implemented as a dataclass for type safety and IDE support. Algorithm-specific data (acceptance rates, temperatures, restart counts, etc.) are stored under metadata.

Variables:
  • design (np.ndarray, shape (n, d)) – Final optimised design in the original parameter space.

  • score (float) – Final criterion value at the returned design. Lower is better (all criteria are minimised).

  • converged (bool) – Whether the optimiser believes it converged to a (local) optimum. Algorithm-specific definition; see each algorithm’s documentation.

  • n_iter (int) – Total number of iterations performed (across all restarts if any).

  • elapsed (float) – Wall-clock time of the optimisation in seconds.

  • metadata (dict) –

    Algorithm-specific information. Standard keys (when applicable):

    • "n_accepted" : int — accepted moves (SA, SCE, ESE)

    • "n_rejected" : int — rejected moves

    • "n_restarts" : int — number of restarts performed

    • "score_history" : list[float] — best score per restart

    • "T_start" : float — initial temperature (SA, ESE)

    • "T_end" : float — final temperature

    • "acceptance_rate" : float — fraction of accepted moves

    • "algorithm" : str — algorithm name (e.g. ‘sa’, ‘sce’)

    • "params" : dict — hyperparameters used

Parameters:

Notes

Use result.x as an alias for result.design and result.fun for result.score to maintain partial compatibility with the scipy.optimize.OptimizeResult interface.

design: ndarray#
score: float#
converged: bool = False#
n_iter: int = 0#
elapsed: float = 0.0#
metadata: Dict[str, Any]#
property x: ndarray#

Alias for design (scipy.optimize compatibility).

property fun: float#

Alias for score (scipy.optimize compatibility).

property nit: int#

Alias for n_iter (scipy.optimize compatibility).

property success: bool#

Alias for converged (scipy.optimize compatibility).

mergen.algorithms.register_optimizer(name, cls)[source]#

Register an optimiser class under a short identifier.

Once registered, the class becomes accessible via get_optimizer() and selectable via Sampler.set_optimizer(name, **kwargs) and Sampler.run(algorithm=name, ...).

Parameters:
  • name (str) – Short identifier for the algorithm (e.g. 'sa', 'sce', 'ese'). Convention: lowercase, underscore-separated.

  • cls (type) – A subclass of BaseOptimizer.

Raises:
Return type:

None

Examples

>>> from mergen.algorithms import BaseOptimizer, register_optimizer
>>> class MyOpt(BaseOptimizer):
...     name = 'my_algo'
...     @classmethod
...     def get_default_params(cls): return {}
...     def optimize(self, *a, **kw): ...
>>> register_optimizer('my_algo', MyOpt)

Notes

Re-registering an existing name overwrites the previous entry. A warning is not issued because legitimate use cases include swapping implementations during testing.

mergen.algorithms.get_optimizer(name)[source]#

Look up a registered optimiser class by name.

Parameters:

name (str) – The identifier under which the optimiser was registered.

Returns:

type – The optimiser class. Instantiate it as get_optimizer('sa')(n_restarts=5).

Raises:

KeyError – If no optimiser is registered under name.

Return type:

Type[BaseOptimizer]

Examples

>>> SAOptimizer = get_optimizer('sa')   # after SA is implemented
>>> sa = SAOptimizer(n_restarts=5)
mergen.algorithms.list_optimizers()[source]#

Return the sorted list of registered optimiser names.

Returns:

list[str] – Sorted list of identifiers (e.g. ['ese', 'sa', 'sce']).

Return type:

List[str]

Examples

>>> list_optimizers()
[]   # empty until SA / SCE / ESE are added