#!/usr/bin/env python
from __future__ import annotations
import json
from ConfigSpace import __version__
from ConfigSpace.conditions import (
AbstractCondition,
AndConjunction,
EqualsCondition,
GreaterThanCondition,
InCondition,
LessThanCondition,
NotEqualsCondition,
OrConjunction,
)
from ConfigSpace.configuration_space import ConfigurationSpace
from ConfigSpace.forbidden import (
AbstractForbiddenComponent,
ForbiddenAndConjunction,
ForbiddenEqualsClause,
ForbiddenEqualsRelation,
ForbiddenGreaterThanRelation,
ForbiddenInClause,
ForbiddenLessThanRelation,
ForbiddenRelation,
)
from ConfigSpace.hyperparameters import (
BetaFloatHyperparameter,
BetaIntegerHyperparameter,
CategoricalHyperparameter,
Constant,
Hyperparameter,
NormalFloatHyperparameter,
NormalIntegerHyperparameter,
OrdinalHyperparameter,
UniformFloatHyperparameter,
UniformIntegerHyperparameter,
UnParametrizedHyperparameter,
)
JSON_FORMAT_VERSION = 0.4
################################################################################
# Builder for hyperparameters
def _build_constant(param: Constant) -> dict:
return {
"name": param.name,
"type": "constant",
"value": param.value,
}
def _build_unparametrized_hyperparameter(param: UnParametrizedHyperparameter) -> dict:
return {
"name": param.name,
"type": "unparametrized",
"value": param.value,
}
def _build_uniform_float(param: UniformFloatHyperparameter) -> dict:
return {
"name": param.name,
"type": "uniform_float",
"log": param.log,
"lower": param.lower,
"upper": param.upper,
"default": param.default_value,
"q": param.q,
}
def _build_normal_float(param: NormalFloatHyperparameter) -> dict:
return {
"name": param.name,
"type": "normal_float",
"log": param.log,
"mu": param.mu,
"sigma": param.sigma,
"default": param.default_value,
"lower": param.lower,
"upper": param.upper,
"q": param.q,
}
def _build_beta_float(param: BetaFloatHyperparameter) -> dict:
return {
"name": param.name,
"type": "beta_float",
"log": param.log,
"alpha": param.alpha,
"beta": param.beta,
"lower": param.lower,
"upper": param.upper,
"default": param.default_value,
"q": param.q,
}
def _build_uniform_int(param: UniformIntegerHyperparameter) -> dict:
return {
"name": param.name,
"type": "uniform_int",
"log": param.log,
"lower": param.lower,
"upper": param.upper,
"default": param.default_value,
"q": param.q,
}
def _build_normal_int(param: NormalIntegerHyperparameter) -> dict:
return {
"name": param.name,
"type": "normal_int",
"log": param.log,
"mu": param.mu,
"sigma": param.sigma,
"lower": param.lower,
"upper": param.upper,
"default": param.default_value,
"q": param.q,
}
def _build_beta_int(param: BetaIntegerHyperparameter) -> dict:
return {
"name": param.name,
"type": "beta_int",
"log": param.log,
"alpha": param.alpha,
"beta": param.beta,
"lower": param.lower,
"upper": param.upper,
"default": param.default_value,
"q": param.q,
}
def _build_categorical(param: CategoricalHyperparameter) -> dict:
return {
"name": param.name,
"type": "categorical",
"choices": param.choices,
"default": param.default_value,
"weights": param.weights,
}
def _build_ordinal(param: OrdinalHyperparameter) -> dict:
return {
"name": param.name,
"type": "ordinal",
"sequence": param.sequence,
"default": param.default_value,
}
################################################################################
# Builder for Conditions
def _build_condition(condition: AbstractCondition) -> dict:
methods = {
AndConjunction: _build_and_conjunction,
OrConjunction: _build_or_conjunction,
InCondition: _build_in_condition,
EqualsCondition: _build_equals_condition,
NotEqualsCondition: _build_not_equals_condition,
GreaterThanCondition: _build_greater_than_condition,
LessThanCondition: _build_less_than_condition,
}
return methods[type(condition)](condition)
def _build_and_conjunction(conjunction: AndConjunction) -> dict:
child = conjunction.get_descendant_literal_conditions()[0].child.name
cond_list = []
for component in conjunction.components:
cond_list.append(_build_condition(component))
return {
"child": child,
"type": "AND",
"conditions": cond_list,
}
def _build_or_conjunction(conjunction: OrConjunction) -> dict:
child = conjunction.get_descendant_literal_conditions()[0].child.name
cond_list = []
for component in conjunction.components:
cond_list.append(_build_condition(component))
return {
"child": child,
"type": "OR",
"conditions": cond_list,
}
def _build_in_condition(condition: InCondition) -> dict:
child = condition.child.name
parent = condition.parent.name
values = list(condition.values)
return {
"child": child,
"parent": parent,
"type": "IN",
"values": values,
}
def _build_equals_condition(condition: EqualsCondition) -> dict:
child = condition.child.name
parent = condition.parent.name
value = condition.value
return {
"child": child,
"parent": parent,
"type": "EQ",
"value": value,
}
def _build_not_equals_condition(condition: NotEqualsCondition) -> dict:
child = condition.child.name
parent = condition.parent.name
value = condition.value
return {
"child": child,
"parent": parent,
"type": "NEQ",
"value": value,
}
def _build_greater_than_condition(condition: GreaterThanCondition) -> dict:
child = condition.child.name
parent = condition.parent.name
value = condition.value
return {
"child": child,
"parent": parent,
"type": "GT",
"value": value,
}
def _build_less_than_condition(condition: LessThanCondition) -> dict:
child = condition.child.name
parent = condition.parent.name
value = condition.value
return {
"child": child,
"parent": parent,
"type": "LT",
"value": value,
}
################################################################################
# Builder for forbidden
def _build_forbidden(clause: AbstractForbiddenComponent) -> dict:
methods = {
ForbiddenEqualsClause: _build_forbidden_equals_clause,
ForbiddenInClause: _build_forbidden_in_clause,
ForbiddenAndConjunction: _build_forbidden_and_conjunction,
ForbiddenEqualsRelation: _build_forbidden_relation,
ForbiddenLessThanRelation: _build_forbidden_relation,
ForbiddenGreaterThanRelation: _build_forbidden_relation,
}
return methods[type(clause)](clause)
def _build_forbidden_equals_clause(clause: ForbiddenEqualsClause) -> dict:
return {
"name": clause.hyperparameter.name,
"type": "EQUALS",
"value": clause.value,
}
def _build_forbidden_in_clause(clause: ForbiddenInClause) -> dict:
return {
"name": clause.hyperparameter.name,
"type": "IN",
# The values are a set, but a set cannot be serialized to json
"values": list(clause.values),
}
def _build_forbidden_and_conjunction(clause: ForbiddenAndConjunction) -> dict:
return {
"name": clause.get_descendant_literal_clauses()[0].hyperparameter.name,
"type": "AND",
"clauses": [_build_forbidden(component) for component in clause.components],
}
def _build_forbidden_relation(clause: ForbiddenRelation) -> dict:
if isinstance(clause, ForbiddenLessThanRelation):
lambda_ = "LESS"
elif isinstance(clause, ForbiddenEqualsRelation):
lambda_ = "EQUALS"
elif isinstance(clause, ForbiddenGreaterThanRelation):
lambda_ = "GREATER"
else:
raise ValueError("Unknown relation '%s'" % type(clause))
return {
"left": clause.left.name,
"right": clause.right.name,
"type": "RELATION",
"lambda": lambda_,
}
################################################################################
[docs]def write(configuration_space: ConfigurationSpace, indent: int = 2) -> str:
"""
Create a string representation of a
:class:`~ConfigSpace.configuration_space.ConfigurationSpace` in json format.
This string can be written to file.
.. code:: python
from ConfigSpace import ConfigurationSpace
from ConfigSpace.read_and_write import json as cs_json
cs = ConfigurationSpace({"a": [1, 2, 3]})
with open('configspace.json', 'w') as f:
f.write(cs_json.write(cs))
Parameters
----------
configuration_space : :class:`~ConfigSpace.configuration_space.ConfigurationSpace`
a configuration space, which should be written to file.
indent : int
number of whitespaces to use as indent
Returns
-------
str
String representation of the configuration space,
which will be written to file
"""
if not isinstance(configuration_space, ConfigurationSpace):
raise TypeError(
"pcs_parser.write expects an instance of {}, "
"you provided '{}'".format(ConfigurationSpace, type(configuration_space)),
)
hyperparameters = []
conditions = []
forbiddens = []
for hyperparameter in configuration_space.values():
if isinstance(hyperparameter, Constant):
hyperparameters.append(_build_constant(hyperparameter))
elif isinstance(hyperparameter, UnParametrizedHyperparameter):
hyperparameters.append(_build_unparametrized_hyperparameter(hyperparameter))
elif isinstance(hyperparameter, BetaFloatHyperparameter):
hyperparameters.append(_build_beta_float(hyperparameter))
elif isinstance(hyperparameter, UniformFloatHyperparameter):
hyperparameters.append(_build_uniform_float(hyperparameter))
elif isinstance(hyperparameter, NormalFloatHyperparameter):
hyperparameters.append(_build_normal_float(hyperparameter))
elif isinstance(hyperparameter, BetaIntegerHyperparameter):
hyperparameters.append(_build_beta_int(hyperparameter))
elif isinstance(hyperparameter, UniformIntegerHyperparameter):
hyperparameters.append(_build_uniform_int(hyperparameter))
elif isinstance(hyperparameter, NormalIntegerHyperparameter):
hyperparameters.append(_build_normal_int(hyperparameter))
elif isinstance(hyperparameter, CategoricalHyperparameter):
hyperparameters.append(_build_categorical(hyperparameter))
elif isinstance(hyperparameter, OrdinalHyperparameter):
hyperparameters.append(_build_ordinal(hyperparameter))
else:
raise TypeError(
"Unknown type: {} ({})".format(
type(hyperparameter),
hyperparameter,
),
)
for condition in configuration_space.get_conditions():
conditions.append(_build_condition(condition))
for forbidden_clause in configuration_space.get_forbiddens():
forbiddens.append(_build_forbidden(forbidden_clause))
rval: dict = {}
if configuration_space.name is not None:
rval["name"] = configuration_space.name
rval["hyperparameters"] = hyperparameters
rval["conditions"] = conditions
rval["forbiddens"] = forbiddens
rval["python_module_version"] = __version__
rval["json_format_version"] = JSON_FORMAT_VERSION
return json.dumps(rval, indent=indent)
################################################################################
[docs]def read(jason_string: str) -> ConfigurationSpace:
"""
Create a configuration space definition from a json string.
.. code:: python
from ConfigSpace import ConfigurationSpace
from ConfigSpace.read_and_write import json as cs_json
cs = ConfigurationSpace({"a": [1, 2, 3]})
cs_string = cs_json.write(cs)
with open('configspace.json', 'w') as f:
f.write(cs_string)
with open('configspace.json', 'r') as f:
json_string = f.read()
config = cs_json.read(json_string)
Parameters
----------
jason_string : str
A json string representing a configuration space definition
Returns
-------
:class:`~ConfigSpace.configuration_space.ConfigurationSpace`
The deserialized ConfigurationSpace object
"""
jason = json.loads(jason_string)
if "name" in jason:
configuration_space = ConfigurationSpace(name=jason["name"])
else:
configuration_space = ConfigurationSpace()
for hyperparameter in jason["hyperparameters"]:
configuration_space.add_hyperparameter(
_construct_hyperparameter(
hyperparameter,
),
)
for condition in jason["conditions"]:
configuration_space.add_condition(
_construct_condition(
condition,
configuration_space,
),
)
for forbidden in jason["forbiddens"]:
configuration_space.add_forbidden_clause(
_construct_forbidden(
forbidden,
configuration_space,
),
)
return configuration_space
def _construct_hyperparameter(hyperparameter: dict) -> Hyperparameter: # noqa: PLR0911
hp_type = hyperparameter["type"]
name = hyperparameter["name"]
if hp_type == "constant":
return Constant(
name=name,
value=hyperparameter["value"],
)
if hp_type == "unparametrized":
return UnParametrizedHyperparameter(
name=name,
value=hyperparameter["value"],
)
if hp_type == "uniform_float":
return UniformFloatHyperparameter(
name=name,
log=hyperparameter["log"],
lower=hyperparameter["lower"],
upper=hyperparameter["upper"],
default_value=hyperparameter["default"],
# Backwards compatibily issue
# https://github.com/automl/ConfigSpace/issues/325
q=hyperparameter.get("q", None),
)
if hp_type == "normal_float":
return NormalFloatHyperparameter(
name=name,
log=hyperparameter["log"],
mu=hyperparameter["mu"],
sigma=hyperparameter["sigma"],
lower=hyperparameter["lower"],
upper=hyperparameter["upper"],
default_value=hyperparameter["default"],
# Backwards compatibily issue
# https://github.com/automl/ConfigSpace/issues/325
q=hyperparameter.get("q", None),
)
if hp_type == "beta_float":
return BetaFloatHyperparameter(
name=name,
alpha=hyperparameter["alpha"],
beta=hyperparameter["beta"],
lower=hyperparameter["lower"],
upper=hyperparameter["upper"],
log=hyperparameter["log"],
# Backwards compatibily issue
# https://github.com/automl/ConfigSpace/issues/325
q=hyperparameter.get("q", None),
default_value=hyperparameter["default"],
)
if hp_type == "uniform_int":
return UniformIntegerHyperparameter(
name=name,
log=hyperparameter["log"],
lower=hyperparameter["lower"],
upper=hyperparameter["upper"],
default_value=hyperparameter["default"],
# Backwards compatibily issue
# https://github.com/automl/ConfigSpace/issues/325
q=hyperparameter.get("q", None),
)
if hp_type == "normal_int":
return NormalIntegerHyperparameter(
name=name,
mu=hyperparameter["mu"],
sigma=hyperparameter["sigma"],
log=hyperparameter["log"],
lower=hyperparameter["lower"],
upper=hyperparameter["upper"],
default_value=hyperparameter["default"],
# Backwards compatibily issue
# https://github.com/automl/ConfigSpace/issues/325
q=hyperparameter.get("q", None),
)
if hp_type == "beta_int":
return BetaIntegerHyperparameter(
name=name,
alpha=hyperparameter["alpha"],
beta=hyperparameter["beta"],
lower=hyperparameter["lower"],
upper=hyperparameter["upper"],
log=hyperparameter["log"],
# Backwards compatibily issue
# https://github.com/automl/ConfigSpace/issues/325
q=hyperparameter.get("q", None),
default_value=hyperparameter["default"],
)
if hp_type == "categorical":
return CategoricalHyperparameter(
name=name,
choices=hyperparameter["choices"],
default_value=hyperparameter["default"],
weights=hyperparameter.get("weights"),
)
if hp_type == "ordinal":
return OrdinalHyperparameter(
name=name,
sequence=hyperparameter["sequence"],
default_value=hyperparameter["default"],
)
raise ValueError(hp_type)
def _construct_condition(
condition: dict,
cs: ConfigurationSpace,
) -> AbstractCondition:
condition_type = condition["type"]
methods = {
"AND": _construct_and_condition,
"OR": _construct_or_condition,
"IN": _construct_in_condition,
"EQ": _construct_eq_condition,
"NEQ": _construct_neq_condition,
"GT": _construct_gt_condition,
"LT": _construct_lt_condition,
}
return methods[condition_type](condition, cs)
def _construct_and_condition(
condition: dict,
cs: ConfigurationSpace,
) -> AndConjunction:
conditions = [_construct_condition(cond, cs) for cond in condition["conditions"]]
return AndConjunction(*conditions)
def _construct_or_condition(
condition: dict,
cs: ConfigurationSpace,
) -> OrConjunction:
conditions = [_construct_condition(cond, cs) for cond in condition["conditions"]]
return OrConjunction(*conditions)
def _construct_in_condition(
condition: dict,
cs: ConfigurationSpace,
) -> InCondition:
return InCondition(
child=cs[condition["child"]],
parent=cs[condition["parent"]],
values=condition["values"],
)
def _construct_eq_condition(
condition: dict,
cs: ConfigurationSpace,
) -> EqualsCondition:
return EqualsCondition(
child=cs[condition["child"]],
parent=cs[condition["parent"]],
value=condition["value"],
)
def _construct_neq_condition(
condition: dict,
cs: ConfigurationSpace,
) -> NotEqualsCondition:
return NotEqualsCondition(
child=cs[condition["child"]],
parent=cs[condition["parent"]],
value=condition["value"],
)
def _construct_gt_condition(
condition: dict,
cs: ConfigurationSpace,
) -> GreaterThanCondition:
return GreaterThanCondition(
child=cs[condition["child"]],
parent=cs[condition["parent"]],
value=condition["value"],
)
def _construct_lt_condition(
condition: dict,
cs: ConfigurationSpace,
) -> LessThanCondition:
return LessThanCondition(
child=cs[condition["child"]],
parent=cs[condition["parent"]],
value=condition["value"],
)
def _construct_forbidden(
clause: dict,
cs: ConfigurationSpace,
) -> AbstractForbiddenComponent:
forbidden_type = clause["type"]
methods = {
"EQUALS": _construct_forbidden_equals,
"IN": _construct_forbidden_in,
"AND": _construct_forbidden_and,
"RELATION": _construct_forbidden_equals,
}
return methods[forbidden_type](clause, cs)
def _construct_forbidden_equals(
clause: dict,
cs: ConfigurationSpace,
) -> ForbiddenEqualsClause:
return ForbiddenEqualsClause(hyperparameter=cs[clause["name"]], value=clause["value"])
def _construct_forbidden_in(
clause: dict,
cs: ConfigurationSpace,
) -> ForbiddenEqualsClause:
return ForbiddenInClause(hyperparameter=cs[clause["name"]], values=clause["values"])
def _construct_forbidden_and(
clause: dict,
cs: ConfigurationSpace,
) -> ForbiddenAndConjunction:
clauses = [_construct_forbidden(cl, cs) for cl in clause["clauses"]]
return ForbiddenAndConjunction(*clauses)
def _construct_forbidden_relation( # pyright: ignore
clause: dict,
cs: ConfigurationSpace,
) -> ForbiddenRelation:
left = cs[clause["left"]]
right = cs[clause["right"]]
if clause["lambda"] == "LESS":
return ForbiddenLessThanRelation(left, right)
if clause["lambda"] == "EQUALS":
return ForbiddenEqualsRelation(left, right)
if clause["lambda"] == "GREATER":
return ForbiddenGreaterThanRelation(left, right)
raise ValueError("Unknown relation '%s'" % clause["lambda"])