Source code for pysip.params.parameter
from typing import Literal, Optional, Tuple
from pydantic import ConfigDict, confloat, validator
from pydantic.dataclasses import dataclass
from pysip.params.prior import BasePrior
from .transforms import FixedTransform, ParameterTransform, Transforms, auto_transform
[docs]@dataclass(config=ConfigDict(arbitrary_types_allowed=True))
class Parameter:
"""A Parameter has a value in the constrained space θ and in the
unconstrained space η. See the documentation of the class for more details on the
different transformations.
Parameters
----------
name : str
Parameter name
value : float
Parameter value
loc : float
Location value
scale : float
Scaling value
transform : str | ParameterTransform
Bijections transform θ -> η and untransform η -> θ
bounds : tuple
Parameters bounds
prior : rv_continuous
Prior distribution, using a scipy.stats distribution object.
Attributes
----------
theta : float
Parameter value in the constrained space θ
theta_sd : float
Parameter value in the standardized constrained space θ_sd
eta : float
Parameter value in the unconstrained space η
eta_sd : float
Parameter value in the standardized unconstrained space η_sd
free : bool
False if the parameter is fixed
"""
name: str
value: float = None
loc: float = 0.0
scale: confloat(gt=0) = 1.0
bounds: Tuple[Optional[float], Optional[float]] = (None, None)
transform: ParameterTransform | Literal[
"auto", "fixed", "none", "log", "lower", "upper", "logit"
] = "auto"
prior: BasePrior = None
@validator("bounds")
def _validate_bounds(cls, bounds):
lb, ub = bounds
if lb is not None and ub is not None and lb >= ub:
raise ValueError("`lb` must be lower than `ub`")
return bounds
@validator("transform")
def _validate_transform(cls, transform, values):
if isinstance(transform, ParameterTransform):
return transform
bounds = values.get("bounds")
if transform == "auto":
return auto_transform(bounds)
return Transforms.get(transform)(bounds)
def __post_init_post_parse__(self):
if self.value is None:
self.eta = 0.0
else:
self.theta = self.loc + self.scale * self.value
if not self.transform.in_bounds(self.value):
raise ValueError(
f"Initial value {self.value} is out of bounds {self.bounds}"
)
@property
def theta(self) -> float:
"""Returns constrained parameter value θ"""
return self.loc + self.scale * self.value
@theta.setter
def theta(self, x):
"""Set constrained parameter value θ"""
self.value = (x - self.loc) / self.scale
self._eta = self.transform.transform(self.value)
@property
def theta_sd(self) -> float:
return self.value
@theta_sd.setter
def theta_sd(self, x):
self.value = x
self.transform.transform(self.value)
@property
def eta(self) -> float:
return self._eta
@eta.setter
def eta(self, x):
self._eta = x
self.value = self.transform.untransform(self._eta)
@property
def free(self) -> bool:
return not isinstance(self.transform, FixedTransform)
[docs] def get_penalty(self) -> float:
"""Penalty function"""
return self.transform.penalty(self.value)
[docs] def get_grad_penalty(self) -> float:
"""Penalty function"""
return self.transform.grad_penalty(self.value)