parameterspace.parameters.base
BaseParameter (ABC)
Base parameter class defining the API.
Source code in parameterspace/parameters/base.py
class BaseParameter(abc.ABC):
"""Base parameter class defining the API."""
def __init__(
self,
name: str,
prior: BasePrior,
transformation: BaseTransformation,
is_continuous: bool,
is_ordered: bool,
num_values: Union[int, float],
inactive_numerical_value: Optional[float] = np.nan,
):
"""
Initialize with options common to all parameters.
Args:
name: Name of the parameter.
prior: Defines the pdf from which random samples are drawn. Default (`None`)
corresponds to a uniform prior. Note that the prior's bounds must match
the transformation's output bounds.
transformation: Defines the transformation from the values to their
numerical representation. Note that the transformation's output bounds
must match the prior's bounds.
is_continuous: Indicates whether this parameter varies continuously, i.e.
is a `float`.
is_ordered: Indicates whether this parameter has a natural ordering of it's
values.
num_values: Number of possible values. For ordinal, categorical, and integer
parameters, this equals the number of unique values. For continuous
parameters, it equals np.inf (even though technically there is only a
finite number of float values).
inactive_numerical_value: Placeholder value for this parameter in case it
is not active
"""
if not is_valid_python_variable_name(name):
raise ValueError(f"{name} needs to be a valid Python variable name.")
self.name = name
self._prior = prior
self._transformation = transformation
self._inactive_numerical_value = inactive_numerical_value
self.transformed_bounds = self._transformation.output_bounds
self._init_args: Tuple
self._init_kwargs: Dict
assert np.allclose(self._prior.bounds, self.transformed_bounds, 1e-6), (
f"Missmatch between the prior bounds ({self._prior.bounds})"
+ f"and the transformation's bounds ({self.transformed_bounds})!"
)
self.is_continuous = is_continuous
self.is_ordered = is_ordered
self.num_values = num_values
def __repr__(self):
"""Basic info about a parameter common to all types."""
string = f"Name: {self.name}\n"
string += f"Type: {self.__class__}\n"
string += f"Prior: {self._prior.__repr__()}\n"
string += f"is continuous: {self.is_continuous}\n"
string += f"is ordered: {self.is_ordered}\n"
string += f"num_values: {self.num_values}\n"
return string
def sample_values(self, num_samples=None, random_state=np.random):
"""Generate randomly sampled values based on the prior."""
numerical_samples = self.sample_numerical_values(num_samples, random_state)
if num_samples is None:
return self._transformation.inverse(numerical_samples)
return [
self._transformation.inverse(num_value) for num_value in numerical_samples
]
def sample_numerical_values(self, num_samples=None, random_state=np.random):
"""Generate random values based on the prior, but in the transformed space."""
return self._prior.sample(num_samples, random_state=random_state)
def val2num(self, value):
"""Translate a value into its numerical representation (incl. normalization)."""
if value is None:
return self._inactive_numerical_value
return self._transformation(value)
def num2val(self, numerical_value):
"""Translate the numerical representation into the actual value."""
return self._transformation.inverse(numerical_value)
def check_numerical_value(self, numerical_value):
"""Check if the numerical representation of the value is valid."""
return (
self.transformed_bounds[0] <= numerical_value <= self.transformed_bounds[1]
)
def pdf_numerical_value(self, numerical_value):
"""Compute the PDF based on the prior."""
return self._prior.pdf(numerical_value) * self._transformation.jacobian_factor(
numerical_value
)
def loglikelihood_numerical_value(self, numerical_value):
"""Compute the loglikelihood based on the prior."""
return self._prior.loglikelihood(numerical_value) + np.log(
self._transformation.jacobian_factor(numerical_value)
)
def loglikelihood(self, value):
"""Compute the loglikelihood based on the prior."""
return self.loglikelihood_numerical_value(self._transformation(value))
def get_numerical_bounds(self):
"""Translate the provided bounds into the numerical representation."""
return self.transformed_bounds
@abc.abstractmethod
def check_value(self, value):
"""Checks if value is valid."""
@staticmethod
def from_dict(json_dict):
parameter_class = json_dict["class_name"]
module_str, class_str = parameter_class.rsplit(".", 1)
module = importlib.import_module(module_str)
parameter_class = getattr(module, class_str)
transformation = BaseTransformation.from_dict(json_dict["transformation"])
prior = BasePrior.from_dict(json_dict["prior"])
return parameter_class(
*json_dict["init_args"],
**json_dict["init_kwargs"],
transformation=transformation,
prior=prior,
)
def to_dict(self):
json_dict = {
"class_name": type(self).__module__ + "." + type(self).__qualname__,
"init_args": self._init_args,
"init_kwargs": copy.deepcopy(self._init_kwargs),
"transformation": self._transformation.to_dict(),
"prior": self._prior.to_dict(),
}
for key in ["transformation", "prior"]:
if key in json_dict["init_kwargs"]:
del json_dict["init_kwargs"][key]
return json_dict
def __eq__(self, other):
if not isinstance(other, type(self)):
return False
if self.name != other.name:
return False
return (self._prior == other._prior) and (
self._transformation == other._transformation
)
check_numerical_value(self, numerical_value)
Check if the numerical representation of the value is valid.
Source code in parameterspace/parameters/base.py
def check_numerical_value(self, numerical_value):
"""Check if the numerical representation of the value is valid."""
return (
self.transformed_bounds[0] <= numerical_value <= self.transformed_bounds[1]
)
check_value(self, value)
Checks if value is valid.
Source code in parameterspace/parameters/base.py
@abc.abstractmethod
def check_value(self, value):
"""Checks if value is valid."""
get_numerical_bounds(self)
Translate the provided bounds into the numerical representation.
Source code in parameterspace/parameters/base.py
def get_numerical_bounds(self):
"""Translate the provided bounds into the numerical representation."""
return self.transformed_bounds
loglikelihood(self, value)
Compute the loglikelihood based on the prior.
Source code in parameterspace/parameters/base.py
def loglikelihood(self, value):
"""Compute the loglikelihood based on the prior."""
return self.loglikelihood_numerical_value(self._transformation(value))
loglikelihood_numerical_value(self, numerical_value)
Compute the loglikelihood based on the prior.
Source code in parameterspace/parameters/base.py
def loglikelihood_numerical_value(self, numerical_value):
"""Compute the loglikelihood based on the prior."""
return self._prior.loglikelihood(numerical_value) + np.log(
self._transformation.jacobian_factor(numerical_value)
)
num2val(self, numerical_value)
Translate the numerical representation into the actual value.
Source code in parameterspace/parameters/base.py
def num2val(self, numerical_value):
"""Translate the numerical representation into the actual value."""
return self._transformation.inverse(numerical_value)
pdf_numerical_value(self, numerical_value)
Compute the PDF based on the prior.
Source code in parameterspace/parameters/base.py
def pdf_numerical_value(self, numerical_value):
"""Compute the PDF based on the prior."""
return self._prior.pdf(numerical_value) * self._transformation.jacobian_factor(
numerical_value
)
sample_numerical_values(self, num_samples=None, random_state=<module 'numpy.random' from '/home/runner/.cache/pypoetry/virtualenvs/parameterspace-9AYrJA9h-py3.8/lib/python3.8/site-packages/numpy/random/__init__.py'>)
Generate random values based on the prior, but in the transformed space.
Source code in parameterspace/parameters/base.py
def sample_numerical_values(self, num_samples=None, random_state=np.random):
"""Generate random values based on the prior, but in the transformed space."""
return self._prior.sample(num_samples, random_state=random_state)
sample_values(self, num_samples=None, random_state=<module 'numpy.random' from '/home/runner/.cache/pypoetry/virtualenvs/parameterspace-9AYrJA9h-py3.8/lib/python3.8/site-packages/numpy/random/__init__.py'>)
Generate randomly sampled values based on the prior.
Source code in parameterspace/parameters/base.py
def sample_values(self, num_samples=None, random_state=np.random):
"""Generate randomly sampled values based on the prior."""
numerical_samples = self.sample_numerical_values(num_samples, random_state)
if num_samples is None:
return self._transformation.inverse(numerical_samples)
return [
self._transformation.inverse(num_value) for num_value in numerical_samples
]
val2num(self, value)
Translate a value into its numerical representation (incl. normalization).
Source code in parameterspace/parameters/base.py
def val2num(self, value):
"""Translate a value into its numerical representation (incl. normalization)."""
if value is None:
return self._inactive_numerical_value
return self._transformation(value)