Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
378 changes: 268 additions & 110 deletions README.md

Large diffs are not rendered by default.

221 changes: 209 additions & 12 deletions modcma/c_maes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,216 @@
import warnings
from enum import Enum
from typing import Union

import numpy as np
from ConfigSpace import (
ConfigurationSpace,
Configuration,
Categorical,
CategoricalHyperparameter,
ForbiddenGreaterThanRelation,
UniformIntegerHyperparameter,
NormalFloatHyperparameter,
EqualsCondition,
)

from .cmaescpp import (
constants,
utils,
sampling,
mutation,
selection,
parameters,
bounds,
restart,
options,
repelling,
utils, # pyright: ignore[reportMissingModuleSource]
sampling, # pyright: ignore[reportMissingModuleSource]
mutation, # pyright: ignore[reportMissingModuleSource]
selection, # pyright: ignore[reportMissingModuleSource]
parameters, # pyright: ignore[reportMissingModuleSource]
bounds, # pyright: ignore[reportMissingModuleSource]
restart, # pyright: ignore[reportMissingModuleSource]
options, # pyright: ignore[reportMissingModuleSource]
repelling, # pyright: ignore[reportMissingModuleSource]
Population,
Parameters,
ModularCMAES,
center,
es
center, # pyright: ignore[reportMissingModuleSource]
es, # pyright: ignore[reportMissingModuleSource]
)

from .cmaescpp.parameters import Settings
from .cmaescpp.parameters import ( # pyright: ignore[reportMissingModuleSource]
Settings,
Modules,
) # pyright: ignore[reportMissingModuleSource]


def _get_module_options(name: str) -> tuple:
if not hasattr(Modules, name):
raise NameError(f"Modules has no member {name}")

default_value = getattr(Modules(), name)
if isinstance(default_value, bool):
return (default_value, not default_value)

module_class = default_value.__class__
if issubclass(module_class, Enum):
other_values = [
x.name for x in module_class.__members__.values()
if x is not default_value
]
return tuple([default_value.name] + other_values)
raise TypeError(f"{name} has a unparsable type {type(default_value)}")


def get_all_module_options() -> dict:
return {
name: _get_module_options(name)
for name in dir(Modules)
if not name.startswith("_")
}


def _make_numeric_parameter(
name: str, dim: int, lb: float, ub: float
) -> Union[UniformIntegerHyperparameter, NormalFloatHyperparameter]:

settings = Parameters(Settings(dim))
default = getattr(settings.weights, name, None)
if default is None:
default = getattr(settings.settings, name)

if isinstance(default, int):
return UniformIntegerHyperparameter(name, lb, ub, default, log=True)

elif isinstance(default, float):
db = min(default - lb, ub - default)
return NormalFloatHyperparameter(name, default, 0.3 * db, lb, ub)

raise TypeError(
f"default value for {name} ({default}) "
f"has an unparsable type {type(default)}"
)


def get_configspace(
dim: int = None,
add_popsize: bool = True,
add_sigma: bool = True,
add_learning_rates: bool = True
) -> ConfigurationSpace:
cspace = ConfigurationSpace()
for name, options in get_all_module_options().items():
cspace.add(Categorical(name, options, default=options[0]))

if dim is None and (add_popsize or add_sigma or add_learning_rates):
warnings.warn(
"Filling configspace with default numeric values for dim=2, "
"since no dim was provided and only_modules was set to False"
)
dim = 2

if add_popsize:
cspace.add(_make_numeric_parameter("lambda0", dim, 1, 50 * dim))
cspace.add(_make_numeric_parameter("mu0", dim, 1, 50 * dim))
cspace.add(ForbiddenGreaterThanRelation(cspace["mu0"], cspace["lambda0"]))

if add_sigma:
cspace.add(_make_numeric_parameter("sigma0", dim, 1e-15, 1e15))

if add_learning_rates:
cspace.add(_make_numeric_parameter("cs", dim, 0, 1.0))
cspace.add(_make_numeric_parameter("cc", dim, 0, 1.0))
cspace.add(_make_numeric_parameter("cmu", dim, 0, 1.0))
cspace.add(_make_numeric_parameter("c1", dim, 0, 1.0))
cspace.add(_make_numeric_parameter("damps", dim, 0, 10.0))

return cspace


def set_module(modules: Modules, name: str, value: Enum) -> bool:
if hasattr(modules, name):
attr_class = type(getattr(modules, name))
if issubclass(attr_class, Enum):
value = getattr(attr_class, value)
setattr(modules, name, value)
return True
return False


def settings_from_dict(dim: int, **config: dict) -> Settings:
modules = Modules()
via_settings = {}
for name, value in dict(config).items():
if set_module(modules, name, value):
continue
via_settings[name] = value
settings = Settings(dim, modules, **via_settings)
return settings


def settings_from_config(
dim: int,
config: Configuration,
**kwargs
) -> Settings:
via_settings = kwargs
default_config = get_configspace(dim).get_default_configuration()
modules = Modules()
for name, value in dict(config).items():
if set_module(modules, name, value):
continue
if default_config[name] != value:
via_settings[name] = value

settings = Settings(dim, modules, **via_settings)
return settings

def fmin(func: callable, x0: np.ndarray, sigma0: float, budget: int, **kwargs):
"""Minimize a function using the modular CMA-ES.

Parameters
----------
func: callable
The objective function to be minimized.
x0 np.ndarray:
The first solution estimate
sigma0: float
The estimate of the stepsize (rule of thumb: 0.3 * (ub - lb))
budget: int
Maximum number of function evaluations to make.
**kwargs
These are directly passed into the instance of ModularCMAES,
in this manner parameters can be specified for the optimizer.
Returns
-------
xopt
The variables which minimize the function during this run
fopt
The value of function at found xopt
evals
The number of evaluations performed
es
The ModularCMAES instance
"""
settings = settings_from_dict(len(x0), sigma0=sigma0, budget=budget, **kwargs)
es = ModularCMAES(settings)
es(func)

return es.p.stats.global_best.x, es.p.stats.global_best.y, es.p.stats.evaluations, es

__all__ = (
"settings_from_config",
"get_configspace",
"get_all_module_options",
"Settings",
"Modules",
"constants",
"utils",
"sampling",
"mutation",
"selection",
"parameters",
"bounds",
"restart",
"options",
"repelling",
"Population",
"Parameters",
"ModularCMAES",
"center",
"es",
)
1 change: 1 addition & 0 deletions modcma/c_maes/cmaescpp/options.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class SampleTranformerType:
class CenterPlacement:
__members__: ClassVar[dict] = ... # read-only
UNIFORM: ClassVar[CenterPlacement] = ...
CENTER: ClassVar[CenterPlacement] = ...
X0: ClassVar[CenterPlacement] = ...
ZERO: ClassVar[CenterPlacement] = ...
__entries: ClassVar[dict] = ...
Expand Down
20 changes: 10 additions & 10 deletions modcma/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,16 +265,16 @@ class Parameters(AnnotatedStruct):
sequential: bool = False
threshold_convergence: bool = False
bound_correction: (
None, "saturate", "unif_resample", "COTN", "toroidal", "mirror") = None
None, "saturate", "unif_resample", "COTN", "toroidal", "mirror") = None # pyright: ignore[reportInvalidTypeForm]
orthogonal: bool = False
local_restart: (None, "restart", "IPOP", "BIPOP", "STOP") = None
base_sampler: ("gaussian", "sobol", "halton") = "gaussian"
mirrored: (None, "mirrored", "mirrored pairwise") = None
weights_option: ("default", "equal", "1/2^lambda") = "default"
local_restart: (None, "restart", "IPOP", "BIPOP", "STOP") = None # pyright: ignore[reportInvalidTypeForm]
base_sampler: ("gaussian", "sobol", "halton") = "gaussian" # pyright: ignore[reportInvalidTypeForm]
mirrored: (None, "mirrored", "mirrored pairwise") = None # pyright: ignore[reportInvalidTypeForm]
weights_option: ("default", "equal", "1/2^lambda") = "default" # pyright: ignore[reportInvalidTypeForm]
step_size_adaptation: (
"csa", "tpa", "msr", "xnes", "m-xnes", "lp-xnes", "psr") = "csa"
population: TypeVar("Population") = None
old_population: TypeVar("Population") = None
"csa", "tpa", "msr", "xnes", "m-xnes", "lp-xnes", "psr") = "csa" # pyright: ignore[reportInvalidTypeForm]
population: TypeVar("Population") = None # pyright: ignore[reportInvalidTypeForm]
old_population: TypeVar("Population") = None # pyright: ignore[reportInvalidTypeForm]
termination_criteria: dict = {}

ipop_factor: int = 2
Expand All @@ -286,8 +286,8 @@ class Parameters(AnnotatedStruct):
compute_termination_criteria: bool = False
sample_sigma: bool = False # TODO make this a module
vectorized_fitness: bool = False
sobol: TypeVar("Sobol") = None
halton: TypeVar("Halton") = None
sobol: TypeVar("Sobol") = None # pyright: ignore[reportInvalidTypeForm]
halton: TypeVar("Halton") = None # pyright: ignore[reportInvalidTypeForm]

__modules__ = (
"active",
Expand Down
Binary file removed perf.data
Binary file not shown.
12 changes: 6 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[build-system]
requires = [
"configspace",
"ioh",
"numpy",
"mypy",
"pybind11",
"setuptools>=42",
"scipy",
"wheel",
"pybind11>=2.6.0",
"ioh>=0.3.14",
"numpy>=1.18.5, <2.0.0",
"mypy",
"pybind11>=2.6.0",
"scipy>=1.5.1",
]

build-backend = "setuptools.build_meta"
Loading