Skip to content

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)