Source code for mergen.algorithms
"""
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
----------------
:func:`get_optimizer`
Look up an optimiser class by its registered name.
:func:`list_optimizers`
List the names of all registered optimisers.
:func:`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', ...)
"""
from __future__ import annotations
from typing import Dict, List, Type
from .base import BaseOptimizer, OptimisationResult
__all__ = [
"BaseOptimizer",
"OptimisationResult",
"register_optimizer",
"get_optimizer",
"list_optimizers",
]
# ── Internal registry ────────────────────────────────────────────────────
_OPTIMIZER_REGISTRY: Dict[str, Type[BaseOptimizer]] = {}
# ── Public API ────────────────────────────────────────────────────────────
[docs]
def register_optimizer(name: str, cls: Type[BaseOptimizer]) -> None:
"""
Register an optimiser class under a short identifier.
Once registered, the class becomes accessible via :func:`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 :class:`BaseOptimizer`.
Raises
------
TypeError
If ``cls`` is not a subclass of :class:`BaseOptimizer`.
ValueError
If ``name`` is empty.
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.
"""
if not isinstance(name, str) or not name:
raise ValueError(f"Optimizer name must be a non-empty string, "
f"got {name!r}.")
if not isinstance(cls, type) or not issubclass(cls, BaseOptimizer):
raise TypeError(
f"Optimizer class must inherit from BaseOptimizer; "
f"got {cls!r}."
)
_OPTIMIZER_REGISTRY[name] = cls
[docs]
def get_optimizer(name: str) -> Type[BaseOptimizer]:
"""
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``.
Examples
--------
>>> SAOptimizer = get_optimizer('sa') # after SA is implemented
>>> sa = SAOptimizer(n_restarts=5)
"""
if name not in _OPTIMIZER_REGISTRY:
available = list_optimizers()
if available:
raise KeyError(
f"Unknown optimiser {name!r}. "
f"Available optimisers: {available}."
)
raise KeyError(
f"Unknown optimiser {name!r}. "
f"No optimisers are registered yet "
f"(they will be added in later phases: SA, SCE, ESE)."
)
return _OPTIMIZER_REGISTRY[name]
[docs]
def list_optimizers() -> List[str]:
"""
Return the sorted list of registered optimiser names.
Returns
-------
list[str]
Sorted list of identifiers (e.g. ``['ese', 'sa', 'sce']``).
Examples
--------
>>> list_optimizers()
[] # empty until SA / SCE / ESE are added
"""
return sorted(_OPTIMIZER_REGISTRY.keys())
# ── Auto-register built-in optimisers ────────────────────────────────────
# Algorithms register themselves as their modules are imported here.
from .sa import SAOptimizer # noqa: E402
from .sce import SCEOptimizer # noqa: E402
from .ese import ESEOptimizer # noqa: E402
register_optimizer('sa', SAOptimizer)
register_optimizer('sce', SCEOptimizer)
register_optimizer('ese', ESEOptimizer)