from builtins import object
__author__ = "giacomov"
import collections
import astropy.units as u
from astromodels.core.tree import Node
from astromodels.utils.pretty_list import dict_to_list
# This module keeps the configuration of the units used in astromodels
# Pre-defined values
_ENERGY = u.keV
_TIME = u.s
_ANGLE = u.deg
_AREA = u.cm**2
[docs]
class UnknownUnit(Exception):
pass
[docs]
class UnitMismatch(Exception):
pass
def _check_unit(new_unit, old_unit):
"""
Check that the new unit is compatible with the old unit for the quantity described by variable_name
:param new_unit: instance of astropy.units.Unit
:param old_unit: instance of astropy.units.Unit
:return: nothin
"""
try:
new_unit.physical_type
except AttributeError:
raise UnitMismatch(
"The provided unit (%s) has no physical type. Was expecting a unit for %s"
% (new_unit, old_unit.physical_type)
)
if new_unit.physical_type != old_unit.physical_type:
raise UnitMismatch(
"Physical type mismatch: you provided a unit for %s instead of a unit for %s"
% (new_unit.physical_type, old_unit.physical_type)
)
class _AstromodelsUnits(object):
"""
Store the fundamental units of time, energy, angle and area to be used in astromodels.
"""
def __init__(
self, energy_unit=None, time_unit=None, angle_unit=None, area_unit=None
):
if energy_unit is None:
energy_unit = _ENERGY
if time_unit is None:
time_unit = _TIME
if angle_unit is None:
angle_unit = _ANGLE
if area_unit is None:
area_unit = _AREA
self._units = collections.OrderedDict()
self._units["energy"] = energy_unit
self._units["time"] = time_unit
self._units["angle"] = angle_unit
self._units["area"] = area_unit
# This __new__ method add the properties to the class. We could have achieved the same with a metaclass,
# but this method is more clearer, for a tiny performance penalty. Consider also that under normal circumstances
# this class will be only created once per session
def __new__(cls, *args, **kwargs):
cls.energy = property(*(cls._create_property("energy")))
cls.time = property(*(cls._create_property("time")))
cls.angle = property(*(cls._create_property("angle")))
cls.area = property(*(cls._create_property("area")))
obj = super(_AstromodelsUnits, cls).__new__(cls)
return obj
def get(self, what):
return self._get_unit(what)
def _set_unit(self, what, new_unit):
try:
old_unit = self._units[what]
except KeyError:
raise UnknownUnit(
"You can only assign units for energy, time, angle and area. Don't know "
"anything about %s" % what
)
# This allows to use strings in place of Unit instances as new_unit
new_unit = u.Unit(new_unit)
# Check that old and new unit are for the appropriate quantity
_check_unit(new_unit, old_unit)
# set the new unit
self._units[what] = new_unit
def _get_unit(self, what):
try:
return self._units[what]
except KeyError:
raise UnknownUnit("%s is not a fundamental unit" % what)
# This is a function which generates the elements needed to make a property.photon
# Just a trick to avoid having to duplicate the same code for each unit.
# It is called in __new__
@staticmethod
def _create_property(what):
return (
lambda self: self._get_unit(what),
lambda self, new_unit: self._set_unit(what, new_unit),
"Sets or gets the unit for %s" % what,
)
# Add the == and != operators
def __eq__(self, other):
return other.to_dict() == self.to_dict()
def __ne__(self, other):
return other.to_dict() != self.to_dict()
def _repr__base(self, rich_output):
return dict_to_list(self._units, html=rich_output)
def to_dict(self, minimal=False):
return self._units
# This is a factory which will always return the same instance of the _AstromodelsUnits class
class _AstromodelsUnitsFactory(object):
_instance = None
def __call__(self, *args, **kwds):
if self._instance is None:
# Create and return a new instance
self._instance = _AstromodelsUnits(*args, **kwds)
return self._instance
else:
# Use the instance already created
return self._instance
# Create the factory to be used in the program
get_units = _AstromodelsUnitsFactory()