Skip to content

parameterspace.parameterspace

ParameterSpace

Class representing a parameter space that allows to sampling, converting and checking configurations.

add(self, parameter, condition=None)

Add a parameter that is only active if condition returns true when called.

Parameters:

Name Type Description Default
parameter Union[BaseParameter, ParameterSpace]

A parameter or a subspace to be added to this space.

Every parameter needs to have a unique name! A ValueError is raised if a parameter with the same name already exists in the ParameterSpace.

required
condition Callable

Lambda function that returns whether this parameter is active based on values of other parameters in the ParameterSpace.

The signature is read using introspection to extract on which parameters the condition operates on.

None
Source code in parameterspace/parameterspace.py
def add(
    self,
    parameter: Union[BaseParameter, ParameterSpace],
    condition: Callable = None,
):
    """Add a parameter that is only active if condition returns true when called.

    Args:
        parameter: A parameter or a subspace to be added to this space.\n
            Every parameter needs to have a unique name! A `ValueError` is raised if
            a parameter with the same name already exists in the `ParameterSpace`.
        condition: Lambda function that returns whether this parameter is active
            based on values of other parameters in the `ParameterSpace`.\n
            The signature is read using introspection to extract on which parameters
            the condition operates on.
    """
    if not isinstance(condition, Condition):
        tmp_condition = Condition(condition)
        condition = Condition()
        for n in tmp_condition.all_varnames:
            condition.merge(self._parameters[n]["condition"])

        condition.merge(tmp_condition)

    if isinstance(parameter, ParameterSpace):
        for pn in parameter.get_parameter_names():
            p = parameter[pn]
            tmp_condition = copy.deepcopy(condition).merge(p["condition"])
            self.add(p["parameter"], tmp_condition)
    else:
        if parameter.name in self._parameters:
            raise ValueError("Parameter %s already exists!" % parameter.name)
        self._parameters[parameter.name] = {
            "parameter": parameter,
            "condition": condition,
        }

check_validity(self, configuration)

Test whether the provided configuration is complete and valid.

It checks wheter all parameters have a valid value or are not active. A warning is shown describing the issue.

Parameters:

Name Type Description Default
configuration dict

The configuration to check.

required

Returns:

Type Description
bool

True if configuration is valid, False if not.

Source code in parameterspace/parameterspace.py
def check_validity(self, configuration: dict) -> bool:
    """Test whether the provided configuration is complete and valid.

    It checks wheter all parameters have a valid value or are not active.
    A warning is shown describing the issue.

    Args:
      configuration: The configuration to check.

    Returns:
        `True` if configuration is valid, `False` if not.
    """
    for n, p in self._parameters.items():
        v = configuration.get(n, None)
        if v is None:
            if p["condition"](configuration):
                warnings.warn(
                    f"Parameter {n} should be active, "
                    + "but does not have a value assigned.\n"
                    + f"Full configuration: {configuration}",
                    RuntimeWarning,
                )
                return False
        else:
            if not p["condition"](configuration):
                warnings.warn(
                    f"Parameter {n} = {v} should not be active!\n"
                    + f"Full configuration: {configuration}",
                    RuntimeWarning,
                )
                return False
            if not p["parameter"].check_value(v):
                warnings.warn(
                    f"Parameter {n} = {v} is not a valid assignment!\n"
                    + f"Full configuration: {configuration}",
                    RuntimeWarning,
                )
                return False
    return True

copy(self)

Get a copy that behaves exactly like the original SearchSpace. Call seed() on the copy to get independent samples.

Source code in parameterspace/parameterspace.py
def copy(self) -> ParameterSpace:
    return ParameterSpace.from_dict(self.to_dict())

fix(self, **kwargs)

Remove all parameters from the parameters list and add treat them as constants.

The main use-case for this method is to narrow down a generic ParameterSpace by fixing certain variables. This could be because of special constraints, or to focus any optimization on a few parameters.

Note

This method will change the ParameterSpace object irreversibly.

Parameters:

Name Type Description Default
**kwargs Any

All arguments to this method have to be keyword arguments with valid parameter names and valid values.

The method will change the current ParameterSpace such that thenumerical representation does not contain any fixed parameters (or always inactive ones).

The dictionary representation will still contain the fixed parameters with their respective value.

{}

Exceptions:

Type Description
ValueError

In case of not existing parameters or invalid values.

Source code in parameterspace/parameterspace.py
def fix(self, **kwargs: Any):
    """Remove all parameters from the parameters list and add treat them as
    constants.

    The main use-case for this method is to narrow down a generic
    `ParameterSpace` by fixing certain variables. This could be because of
    special constraints, or to focus any optimization on a few parameters.

    Note:
        This method will change the ParameterSpace object irreversibly.

    Args:
        **kwargs: All arguments to this method have to be keyword arguments
            with valid parameter names and valid values.\n
            The method will change the current `ParameterSpace` such that
            thenumerical representation does not contain any fixed
            parameters (or always inactive ones).\n
            The dictionary representation will still contain the fixed
            parameters with their respective value.

    Raises:
        ValueError: In case of not existing parameters or invalid values.

    """

    for name, value in kwargs.items():
        if name not in self._parameters:
            raise ValueError(
                f"Parameter '{name}' is not part of this ParameterSpace!"
            )

        if not self._parameters[name]["parameter"].check_value(value):
            raise ValueError(f"Invalid value `{value}` for parameter '{name}'!")

    # remove any constants here that might not be active based on some parameter values,
    # but are still specified here.
    actual_constants = copy.copy(kwargs)
    for n in kwargs.keys():
        p = self._parameters[n]
        if p["condition"].all_varnames <= set(kwargs) and not p["condition"](
            kwargs
        ):
            actual_constants.pop(n, None)
    self._constants.update(**actual_constants)

    for p in list(self._parameters.keys()):

        if p in self._constants:
            del self._parameters[p]
            continue

        try:
            if self[p]["condition"](self._constants):
                self[p]["condition"] = Condition(None)
            else:
                del self._parameters[p]
        except ValueError:
            pass

from_dict(dict_representation) staticmethod

Create a new ParameterSpace instance with the state passed as dictionary.

Parameters:

Name Type Description Default
dict_representation dict

A dictionary representation of a ParameterSpace.

required

Returns:

Type Description
ParameterSpace

New class instance

Source code in parameterspace/parameterspace.py
@staticmethod
def from_dict(dict_representation: dict) -> ParameterSpace:
    """
    Create a new `ParameterSpace` instance with the state passed as
    dictionary.

    Args:
        dict_representation: A dictionary representation of a
            `ParameterSpace`.

    Returns:
        New class instance
    """
    return_ps = ParameterSpace()
    return_ps.seed(dict_representation.get("bit_generator_state"))

    for parameter_dict in dict_representation["parameters"].values():
        parameter = BaseParameter.from_dict(parameter_dict["parameter"])
        condition = Condition.from_dict(parameter_dict["condition"])
        return_ps._parameters[parameter.name] = {
            "parameter": parameter,
            "condition": condition,
        }
    return_ps._constants = copy.deepcopy(dict_representation["constants"])

    return return_ps

from_numerical(self, vector)

Convert a np.float64 type vector numerical representation of a configuration from this space to a dictionary representation.

Source code in parameterspace/parameterspace.py
def from_numerical(self, vector: np.ndarray) -> dict:
    conf = {}
    for i, (n, p) in enumerate(self._parameters.items()):
        if not np.isnan(vector[i]):
            conf[n] = p["parameter"].num2val(vector[i])
    conf.update(self._constants)
    return self.remove_inactive(conf)

get_continuous_bounds(self)

Return the ParamerSpace specific bounds if it is purely continuous.

Exceptions:

Type Description
ValueError

If ParameterSpace contains non-continuous parameters.

Returns:

Type Description
List[Tuple]

Continuous bounds for this benchmark to easily run GP

Source code in parameterspace/parameterspace.py
def get_continuous_bounds(self) -> List[Tuple]:
    """Return the ParamerSpace specific bounds if it is purely continuous.

    Raises:
        ValueError: If `ParameterSpace` contains non-continuous parameters.

    Returns:
        Continuous bounds for this benchmark to easily run GP
    """
    bounds = []
    for n in self.get_parameter_names():
        p = self[n]["parameter"]
        if not p.is_continuous:
            raise ValueError(
                "Parameterspace contains non-continuous parameter:\n{}".format(p)
            )
        bounds.append(tuple(p.get_numerical_bounds()))
    return bounds

get_parameter_names(self)

Get names of all parameters already in the current ParameterSpace.

Returns:

Type Description
List[str]

Parameter names.

Source code in parameterspace/parameterspace.py
def get_parameter_names(self) -> List[str]:
    """Get names of all parameters already in the current `ParameterSpace`.

    Returns:
        Parameter names.
    """
    return list(self._parameters.keys())

has_conditions(self)

Check if any of the parameters in the current ParameterSpace is conditioned on others.

Returns:

Type Description
bool

True of conditions exists, False if not.

Source code in parameterspace/parameterspace.py
def has_conditions(self) -> bool:
    """Check if any of the parameters in the current `ParameterSpace` is
    conditioned on others.

    Returns:
        `True` of conditions exists, `False` if not.
    """
    for p in self:
        if not p["condition"].empty():
            return True
    return False

log_likelihood(self, configuration)

Compute log-likelihood of a configuration under the given prior.

Parameters:

Name Type Description Default
configuration dict

A parameter configuration

required

Returns:

Type Description
float

Log-likelihood value or NaN if configuration is invalid.

Source code in parameterspace/parameterspace.py
def log_likelihood(self, configuration: dict) -> float:
    """Compute log-likelihood of a configuration under the given prior.

    Args:
        configuration: A parameter configuration

    Returns:
        Log-likelihood value or `NaN` if configuration is invalid.
    """
    if not self.check_validity(configuration):
        return np.nan
    self.remove_inactive(configuration)
    return self.log_likelihood_numerical(self.to_numerical(configuration))

log_likelihood_numerical(self, vector_configuration)

Compute log-likelihood for the numerical representation of a configuration under the given prior.

Note

This method assumes that the vector representation is valid! There are no sanity checks at this point. Inactive parameters must have NaN values.

Parameters:

Name Type Description Default
vector_configuration np.ndarray

Numerical vector representation of a configuration.

required

Returns:

Type Description
float

Log-likelihood value

Source code in parameterspace/parameterspace.py
def log_likelihood_numerical(self, vector_configuration: np.ndarray) -> float:
    """Compute log-likelihood for the numerical representation of a
    configuration under the given prior.

    Note:
        This method assumes that the vector representation is valid! There
        are no sanity checks at this point. Inactive parameters must have
        `NaN` values.

    Args:
        vector_configuration: Numerical vector representation of a
            configuration.

    Returns:
        Log-likelihood value
    """
    ll = 0.0
    for i, p in enumerate(self._parameters.values()):
        if np.isfinite(vector_configuration[i]):
            ll += p["parameter"].loglikelihood_numerical_value(
                vector_configuration[i]
            )
    return ll

num2val(self, vector)

Attention

Deprecated. Use [ParameterSpace.from_numerical] [parameterspace.parameterspace.ParameterSpace.from_numerical].

Parameters:

Name Type Description Default
vector np.ndarray

Numerical vector representation of a configuration.

required

Returns:

Type Description
dict

Dictionary representation of the input configuration.

Source code in parameterspace/parameterspace.py
def num2val(self, vector: np.ndarray) -> dict:
    """
    Attention:
        Deprecated. Use [ParameterSpace.from_numerical]\
        [parameterspace.parameterspace.ParameterSpace.from_numerical].

    Args:
        vector: Numerical vector representation of a configuration.

    Returns:
        Dictionary representation of the input configuration.

    """
    warnings.warn(
        "Method ParameterSpace.num2val is deprecated and will be removed in the "
        + "future! Please use ParameterSpace.from_numerical instead.",
        category=DeprecationWarning,
    )
    return self.from_numerical(vector)

remove(self, name)

Remove a parameter by name from this ParameterSpace instance.

Note

When removing a parameter from a ParameterSpace that has been added to another ParameterSpace, the including ParameterSpace is not affected.

Parameters:

Name Type Description Default
name str

Name of the parameter.

required

Exceptions:

Type Description
RuntimeError

In case other parameters condition on the parameter that should be removed.

KeyError

In case parameter doesn't exist.

Source code in parameterspace/parameterspace.py
def remove(self, name: str):
    """Remove a parameter by name from this `ParameterSpace` instance.

    Note:
        When removing a parameter from a `ParameterSpace` that has been
        added to another `ParameterSpace`, the including `ParameterSpace`
        is not affected.

    Args:
        name: Name of the parameter.

    Raises:
        RuntimeError: In case other parameters condition on the parameter
            that should be removed.
        KeyError: In case parameter doesn't exist.
    """
    conditioning_parameters = []
    for p_name, p in self._parameters.items():
        if name in p["condition"].all_varnames:
            conditioning_parameters.append(p_name)
    if conditioning_parameters:
        raise RuntimeError(
            f"Unable to remove parameter '{name}' "
            + f"because the parameters {conditioning_parameters} condition on it. "
            + f"Please remove the other parameters before removing '{name}'."
        )

    try:
        self._parameters.pop(name)
    except KeyError as e:
        raise KeyError(
            f"Parameter '{name}' is not part of the ParameterSpace."
        ) from e

remove_inactive(self, configuration)

Identify and remove parameters that should not have a value because their condition is unsatisfied.

Parameters:

Name Type Description Default
configuration dict

Configuration to check.

required

Returns:

Type Description
dict

Cleaned configuration.

Source code in parameterspace/parameterspace.py
def remove_inactive(self, configuration: dict) -> dict:
    """Identify and remove parameters that should not have a value because
    their condition is unsatisfied.

    Args:
        configuration: Configuration to check.

    Returns:
        Cleaned configuration.
    """
    for n, p in self._parameters.items():
        if not p["condition"](configuration):
            configuration.pop(n, None)
    return configuration

sample(self, partial_configuration=None, rng=None)

Sample a random configuration based on the priors.

Parameters:

Name Type Description Default
partial_configuration dict

Partial assignment of certain variables. If not None, only the remaining variables are set randomly. All conditions are honored.

Right now, there is no check if the partitial configuration violates any conditions.

None
rng np.random.Generator

A Numpy random number generator used to generate the sample. Overrides the internal random number generator that is set on initialization of the space.

None

Exceptions:

Type Description
ValueError

If a passed value is not valid for the corresponding parameter.

Returns:

Type Description
dict

Random parameter configuration.

Source code in parameterspace/parameterspace.py
def sample(
    self, partial_configuration: dict = None, rng: np.random.Generator = None
) -> dict:
    """Sample a random configuration based on the priors.

    Args:
        partial_configuration: Partial assignment of certain variables. If
            not `None`, only the remaining variables are set randomly. All
            conditions are honored.\n
            Right now, there is no check if the partitial configuration
            violates any conditions.
        rng: A Numpy random number generator used to generate the
            sample. Overrides the internal random number generator that is
            set on initialization of the space.

    Raises:
        ValueError: If a passed value is not valid for the corresponding
            parameter.

    Returns:
        Random parameter configuration.
    """
    if rng is None:
        rng = self._rng

    config = {} if partial_configuration is None else partial_configuration
    for n, v in config.items():
        if not self._parameters[n]["parameter"].check_value(v):
            raise ValueError("%s = %s is not valid for this space" % (n, v))

    config.update(self._constants)

    for n, p in self._parameters.items():
        if n in config:
            continue
        if p["condition"].empty() or p["condition"](config):
            config[n] = p["parameter"].sample_values(random_state=rng)

    return config

seed(self, seed=None)

Reinitialize the random number generator.

Parameters:

Name Type Description Default
seed Union[int, dict]

Either an integer seed or a numpy bit generator state dictionary. Defaults to None.

None
Source code in parameterspace/parameterspace.py
def seed(self, seed: Union[int, dict] = None) -> None:
    """Reinitialize the random number generator.

    Args:
        seed: Either an integer seed or a numpy bit generator state dictionary.
            Defaults to None.
    """
    if isinstance(seed, dict):
        self._rng = np.random.default_rng()
        self._rng.bit_generator.state = seed
    else:
        self._rng = np.random.default_rng(seed)

to_dict(self)

Transform current ParameterSpace into a dictionary representation.

Returns:

Type Description
dict

Contains "constants" and "parameters" (with "condition").

Source code in parameterspace/parameterspace.py
def to_dict(self) -> dict:
    """
    Transform current `ParameterSpace` into a dictionary representation.

    Returns:
        Contains "constants" and "parameters" (with "condition").
    """

    parameters = {}
    for p in self._parameters.values():
        parameters[p["parameter"].name] = {
            "parameter": p["parameter"].to_dict(),
            "condition": p["condition"].to_dict(),
        }
    return {
        "bit_generator_state": self._rng.bit_generator.state,
        "parameters": parameters,
        "constants": copy.deepcopy(self._constants),
    }

to_latex_table(self, name_dict=None)

Construct LaTeX string for a table that shows the ParameterSpace with names, bounds and transformations.

Parameters:

Name Type Description Default
name_dict Optional[dict]

Mapping of parameter name in ParameterSpace to name to be rendered in LaTeX table.

None

Returns:

Type Description
str

LaTeX table represenation

Source code in parameterspace/parameterspace.py
def to_latex_table(self, name_dict: Optional[dict] = None) -> str:
    """Construct LaTeX string for a table that shows the `ParameterSpace`
    with names, bounds and transformations.

    Args:
        name_dict: Mapping of parameter name in `ParameterSpace` to name
            to be rendered in LaTeX table.

    Returns:
        LaTeX table represenation
    """
    try:
        from num2tex import configure as num2tex_configure
        from num2tex import num2tex
    except ImportError as e:
        raise RuntimeError(
            "To use this functionality, please install num2tex."
        ) from e

    num2tex_configure(exp_format="cdot")

    name_dict = {} if name_dict is None else name_dict

    latex_strs = [
        "\\begin{tabular}{c c c c c}",
        "\\hline",
        "Parameter Name & Type & Values & Transformation & Prior \\\\",
        "\\hline",
    ]

    for parameter_name in self._parameters:
        parameter = self._parameters[parameter_name]["parameter"]

        name_str = name_dict.get(parameter_name, parameter.name)
        transformation_name = type(parameter._transformation).__name__
        prior_name = type(parameter._prior).__name__

        if isinstance(parameter, ps.IntegerParameter):
            type_str = "Integer"
            values_str = "$[{0[0]}, {0[1]}]$".format(parameter.bounds)
            transformation_str = "Log" if "Log" in transformation_name else ""
            prior_str = prior_name

        if isinstance(parameter, ps.ContinuousParameter):
            type_str = "Float"
            prior_str = prior_name

            if "Log" in transformation_name:
                transformation_str = "Log"
                values_str = "$[{}, {}]$".format(
                    num2tex(parameter.bounds[0], precision=2),
                    num2tex(parameter.bounds[1], precision=2),
                )
            else:
                transformation_str = " "
                values_str = "$[{0[0]}, {0[1]}]$".format(parameter.bounds)

        if isinstance(parameter, ps.CategoricalParameter):
            type_str = "Categorical"
            values_str = "[" + ", ".join(parameter.values) + "]"
            transformation_str = " "
            prior_probs = parameter._prior.probabilities
            prior_str = (
                "["
                + ",".join(map(lambda p: "{:3.2f}".format(p), prior_probs))
                + "]"
            )

        latex_strs.append(
            " & ".join(
                [name_str, type_str, values_str, transformation_str, prior_str]
            )
            + "\\\\"
        )

    latex_strs.extend(["\\hline", "\\end{tabular}"])
    return "\n".join(latex_strs)

to_numerical(self, configuration)

Given a configuration from this space, create a numerical vector representation. The transformed representation needs to be between 0 and 1 (uniform), including integers, ordinal and categoricals. Inactive parameters have to be represented with np.nan

Source code in parameterspace/parameterspace.py
def to_numerical(self, configuration: dict) -> np.ndarray:
    self.remove_inactive(configuration)

    for name, value in self._constants.items():
        if name not in configuration:
            raise ValueError(
                f"Configuration does not contain contant `{name} == {value}`!"
            )
        if configuration[name] != value:
            raise ValueError(
                f"Constant parameter {name} has value {configuration[name]}, "
                + f"but should be {value}!"
            )

    vec = np.zeros(len(self._parameters), dtype=float)
    for i, (n, p) in enumerate(self._parameters.items()):
        v = configuration.get(n, None)
        vec[i] = p["parameter"].val2num(v)
    return vec

val2num(self, configuration)

Attention

Deprecated. Use [ParameterSpace.to_numerical] [parameterspace.parameterspace.ParameterSpace.to_numerical].

Parameters:

Name Type Description Default
configuration dict

Configuration to convert.

required

Exceptions:

Type Description
ValueError

If configuration contains invalid names or values.

Returns:

Type Description
np.ndarray

Vector representation of the configuration.

Source code in parameterspace/parameterspace.py
def val2num(self, configuration: dict) -> np.ndarray:
    """
    Attention:
        Deprecated. Use [ParameterSpace.to_numerical]\
        [parameterspace.parameterspace.ParameterSpace.to_numerical].

    Args:
        configuration: Configuration to convert.

    Raises:
        ValueError:  If configuration contains invalid names or values.

    Returns:
        Vector representation of the configuration.

    """
    warnings.warn(
        "Method ParameterSpace.val2num is deprecated and will be removed in "
        + "the future! Please use ParameterSpace.to_numerical instead.",
        category=DeprecationWarning,
    )
    return self.to_numerical(configuration)