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()