"""
Define the interface for a plugin class.
"""
import abc
from typing import Dict
from astromodels import IndependentVariable, Model
from astromodels.core.parameter import Parameter
from threeML.io.logging import invalid_plugin_name, setup_logger
log = setup_logger(__name__)
# def set_external_property(method):
# """
# Sets external property values if they exist
#
#
# :param method:
# :return:
# """
#
# @functools.wraps(method)
# def wrapper(instance, *args, **kwargs):
#
# if instance._external_properties:
#
# for property, value in instance._external_properties:
# property.value = value
#
# return method(instance, *args, **kwargs)
#
# return wrapper
[docs]
class PluginPrototype(metaclass=abc.ABCMeta):
def __init__(self, name: str, nuisance_parameters: Dict[str, Parameter]):
invalid_plugin_name(name, log)
# Make sure total is not used as a name (need to use it for other things, like the total value of the statistic)
if name.lower() == "total":
log.error("Sorry, you cannot use 'total' as name for a plugin.")
raise AssertionError(
"Sorry, you cannot use 'total' as name for a plugin."
)
self._name: str = name
# This is just to make sure that the plugin is legal
if not isinstance(nuisance_parameters, dict):
log.error("nuisance_parameters are not a dict and are invalid!")
raise AssertionError(
"nuisance_parameters are not a dict and are invalid!"
)
self._nuisance_parameters: Dict[str, Parameter] = nuisance_parameters
# These are the external properties (time, polarization, etc.)
# self._external_properties = []
self._tag = None
self._exclude_from_fit = False
[docs]
def get_name(self) -> str:
log.warning(
"Do not use get_name() for plugins, use the .name property",
)
return self.name
@property
def name(self) -> str:
"""
Returns the name of this instance
:return: a string (this is enforced to be a valid python identifier)
"""
return self._name
@property
def nuisance_parameters(self) -> Dict[str, Parameter]:
"""
Returns a dictionary containing the nuisance parameters for this dataset
:return: a dictionary
"""
return self._nuisance_parameters
[docs]
def update_nuisance_parameters(
self, new_nuisance_parameters: Dict[str, Parameter]
) -> None:
if not isinstance(new_nuisance_parameters, dict):
log.error("nuisance_parameters are not a dict and are invalid!")
raise AssertionError(
"nuisance_parameters are not a dict and are invalid!"
)
self._nuisance_parameters = new_nuisance_parameters
# def external_property(self, property, value):
# """
# Set external/auxiliary properties and their value
# :param property: an astromodels auxiliary variable
# :param value: the value of the auxiliary variable for this plugin
# :return:
# """
#
# self._external_properties.append((property, value))
[docs]
def get_number_of_data_points(self) -> int:
"""
This returns the number of data points that are used to evaluate the likelihood.
For binned measurements, this is the number of active bins used in the fit. For
unbinned measurements, this would be the number of photons/particles that are
evaluated on the likelihood
"""
log.warning(
"get_number_of_data_points not implemented, values for statistical measurements such as AIC or BIC are "
"unreliable",
)
return 1.0
def _get_tag(self):
return self._tag
def _set_tag(self, spec):
"""
Tag this plugin with the provided independent variable and a start and end value.
This can be used for example to fit a time-varying model. In this case the independent variable will be the
time and the start and end will be the start and stop time of the exposure for this plugin. These values will
be used to average the model over the provided time interval when fitting.
:param independent_variable: an IndependentVariable instance
:param start: start value for this plugin
:param end: end value for this plugin. If this is not provided, instead of integrating the model between
start and end, the model will be evaluate at start. Default: None (i.e., not provided)
:return: none
"""
if len(spec) == 2:
independent_variable, start = spec
end = None
elif len(spec) == 3:
independent_variable, start, end = spec
else:
raise ValueError(
"Tag specification should be (independent_variable, start[, end])"
)
# Let's do a lazy check
if not isinstance(independent_variable, IndependentVariable):
log.warning(
"When tagging a plugin, you should use an IndependentVariable instance. You used instead "
"an instance of a %s object. This might lead to crashes or "
"other problems." % type(independent_variable)
)
self._tag = (independent_variable, start, end)
tag = property(
_get_tag,
_set_tag,
doc="Gets/sets the tag for this instance, as (independent variable, start, "
"[end])",
)
[docs]
def exclude_from_fit(self,flag=False):
"""
This can be used to explude a plug in from the fit
:param flag: True or Fase (default)
:return:
"""
log.info("Plug in %s had beed expluded from the fit" % self.name)
self._exclude_from_fit = flag
######################################################################
# The following methods must be implemented by each plugin
######################################################################
[docs]
@abc.abstractmethod
def set_model(self, likelihood_model_instance: Model):
"""
Set the model to be used in the joint minimization. Must be a LikelihoodModel instance.
"""
pass
[docs]
@abc.abstractmethod
def get_log_like(self) -> float:
"""
Return the value of the log-likelihood with the current values for the
parameters
"""
pass
[docs]
@abc.abstractmethod
def inner_fit(self):
"""
This is used for the profile likelihood. Keeping fixed all parameters in the
LikelihoodModel, this method minimize the logLike over the remaining nuisance
parameters, i.e., the parameters belonging only to the model for this
particular detector. If there are no nuisance parameters, simply return the
logLike value.
"""
pass