import logging
from ForMoSA.parameter.prior import Prior
from ForMoSA.core.errors import ForMoSAError
from ForMoSA.core.loggings import setup_logging
from ForMoSA.core.enums import VsiniFunction, ParameterKind
[docs]
class Parameter(object):
'''
ForMoSA Parameter class. Handles a single parameter for the nested sampling algorithm.
Parameters
----------
name : str
Name of the parameter ('par1', 'par2', 'rv', 'd', ...)
prior : Prior
Prior object associated with the parameter (UniformPrior, GaussianPrior, ConstantPrior, LogUniformPrior)
kind : ParameterKind
Type of parameter used to identify the parameter
scope : str
'global' if this is a global parameter or 'local' if it is applied to specific observations
obs_index : int | None
Index of the obervation the parameter is applied to (None if scope is 'global')
title : str | None
Title of the parameter (used to connect grid parameters to their associated title)
vsini_function : VsiniFunction
Vsini function used for the prior (required if name starts with 'vsini')
logger : logging.Logger
Logger
log_level : str
Level of the Logger
Notes
-----
Authors: Allan Denis
'''
def __init__(self, name: str, prior: Prior, kind: ParameterKind, scope: str = 'global', obs_index: list[int] | None = None, title: str | None = None, vsini_function: VsiniFunction | None = None, logger: logging.Logger | None = None, log_level: str='INFO'):
self._name = name
self._prior = prior
self._kind = kind
self._scope = scope
self._obs_index = obs_index
self._vsini_function = vsini_function
self._title = title
self._logger = logger if logger is not None else setup_logging(log_level, name = 'Parameter')
self._validate()
# ======================
# Representation
# ======================
def __repr__(self):
return f"Parameter(name={self.name}, kind={self.kind}, prior={self.prior}, vsini_function={self.vsini_function}, scope={self.scope}, obs_index={self.obs_index})"
# ======================
# Properties
# ======================
@property
def logger(self) -> logging.Logger:
"""Logger."""
return self._logger
@property
def name(self) -> str:
"""Name of the parameter."""
return self._name
@property
def kind(self) -> ParameterKind:
"""Parameter type."""
return self._kind
@property
def scope(self) -> str:
"""Scope ('global' or 'local')."""
return self._scope
@property
def is_local(self) -> bool:
"""Whether the parameter is local."""
return self.scope == 'local'
@property
def title(self) -> str:
"""Title."""
return self._title
@property
def obs_index(self) -> list[int] | None:
"""Index of the observation the parameter is applied to."""
return self._obs_index
@property
def prior(self) -> Prior:
"""Prior object associated with the parameter."""
return self._prior
@property
def vsini_function(self) -> str:
"""Vsini function used for the prior."""
return self._vsini_function
@vsini_function.setter
def vsini_function(self, value: str):
"""Setter for vsini_function."""
self._vsini_function = value
@property
def is_fixed(self) -> bool:
"""Whether the parameter is fixed (constant prior) or free."""
return self._prior.is_fixed
@property
def to_dict(self) -> dict:
"""Dictionary representation of the parameter."""
return {
'name': self.name,
'prior': self.prior.to_dict,
'kind': self.kind.kind,
'scope': self.scope,
'obs_index': self.obs_index,
'title': self.title,
'vsini_function': self.vsini_function.value if isinstance(self.vsini_function, VsiniFunction) else self.vsini_function
}
# ======================
# Clas methods
# ======================
[docs]
@classmethod
def from_dict(cls, data: dict, logger: logging.Logger | None = None, log_level: str = 'INFO') -> "Parameter":
'''
Reconstruct a Parameter from a dictionary of Parameter.
Parameters
----------
data : dict
Dictionary containing parameter parameters
logger : logging.Logger
Logger
log_level : str
Level of the Logger
Returns
-------
Parameter
An instance of class Parameter
Notes
-----
Authors: Allan Denis
'''
logger = logger if logger is not None else setup_logging(level=log_level, name='Parameter')
logger.debug('Extract Parameter from dictionary')
name, prior, kind, scope, title, obs_index = data['name'], data['prior'], data['kind'], data['scope'], data['title'], data['obs_index']
vsini_function = VsiniFunction[data['vsini_function']] if data['vsini_function'] in [func.value for func in VsiniFunction] else None
logger.info(f' {kind} parameter detected with name {name}')
vsini_function = VsiniFunction(vsini_function) if vsini_function is not None else None
return cls(name=name, prior=Prior.from_dict(prior, logger=logger), kind=ParameterKind[kind], scope=scope, title=title, obs_index=obs_index, vsini_function=vsini_function, logger=logger)
# ======================
# Methods
# ======================
def _validate(self) -> None:
'''
Validation of the parameter.
Notes
-----
Authors: Allan Denis
'''
if not isinstance(self.prior, Prior):
raise ForMoSAError(f"Parameter {self.name} must be initialized with a Prior object", self._logger)
if not isinstance(self.kind, ParameterKind):
raise ForMoSAError(f"Parameter '{self.name}' must have a valid ParameterKind", self._logger)
if self.kind is ParameterKind.VSINI:
valid_vsini_functions = list(VsiniFunction)
if self.vsini_function is None or self.vsini_function not in valid_vsini_functions:
raise ForMoSAError(f"VSINI parameter '{self.name}' requires a vsini_function amongst {valid_vsini_functions}. Got {self.vsini_function} instead", self._logger)
if self.scope not in ('global', 'local'):
raise ForMoSAError(f"Parameter '{self.name}' has invalid scope '{self.scope}'. Must be 'global' or 'local'", self._logger)
if self.scope == 'global' and self.obs_index is not None:
raise ForMoSAError(f"Global parameter '{self.name}' must have obs_index = None", self._logger)
if self.scope == 'local' and self.obs_index is None:
raise ForMoSAError(f"Local parameter '{self.name}' must have a valid obs_index", self._logger)
if self.obs_index is not None:
if not isinstance(self.obs_index, list):
raise ForMoSAError(f"<Parameter {self.name} must have a list for obs_index. Got {type(self.obs_index)} instead>", self.logger)
if self._title is None:
self._title = self.name