"""
Unit Support: Classes and methods to allow Unum and Quantities unit
packages to interact with Topographica.
"""
import param
from param.parameterized import bothmethod
from topo import sheet, pattern, base, numbergen
import numpy as np
from unittest import SkipTest
got_unum = False; got_pq=False
try:
import quantities as pq
got_pq=True
except: pass
try:
import unum
import unum.units
unum.Unum.UNIT_FORMAT = '%s'
got_unum=True
except: pass
if True not in [got_unum, got_pq]: raise SkipTest('Could not find Quantities or Unum.')
class strip_pq_hook(param.ParameterizedFunction):
def __call__(self,obj,val):
"""
Hook to convert units provided by the quantities package to the base
unit as defined in the QuantityConversions class and return a float.
"""
if not hasattr(val,'units'):
return val
if hasattr(obj,'unit_conversions'):
return obj.unit_conversions.convert_to_base(val)
elif hasattr(obj,'src') and hasattr(obj.src,'unit_conversions'):
return obj.src.unit_conversions.convert_to_base(val)
class strip_unum_hook(param.ParameterizedFunction):
def __call__(self,obj,val):
"""
Hook to convert unum unit objects to the specified base unit and
return as a float.
"""
if not val.__class__.__name__ == 'Unum':
return val
if hasattr(obj,'unit_conversions'):
return obj.unit_conversions.convert_to_base(val)
elif hasattr(obj,'src') and hasattr(obj.src,'unit_conversions'):
return obj.src.unit_conversions.convert_to_base(val)
[docs]class Conversions(object):
"""
The UnumConversion class holds unit conversions and definitions,
setting and resetting the unum unit table allowing sheet specific
conversions to be performed.
"""
package = 'Quantities'
initialized = False
def __init__(self,units=None):
"""
Initialize this object first by storing the unit
specification, backing up the current unit table, creating the
specified unum unit objects and then restoring the backed up
unit table.
"""
if not hasattr(self,'_unit_objects'):
self._unit_objects = {}
if not hasattr(self,'_unit_specs'):
self._unit_specs = {}
if 'unit_conversions' not in sheet.Sheet.params():
sheet.Sheet._add_parameter("unit_conversions",param.Parameter(None))
if 'unit_conversions' not in pattern.PatternGenerator.params():
pattern.PatternGenerator._add_parameter("unit_conversions",param.Parameter(None))
if 'unit_conversions' not in numbergen.NumberGenerator.params():
numbergen.NumberGenerator._add_parameter("unit_conversions",param.Parameter(None))
self.initialize(units)
self.initialized = True
@bothmethod
[docs] def declare_unit(obj,unit_key,conversion,name,base=False):
"""
Create and return unit using specified package.
"""
if obj.package == 'Quantities':
unit_obj = pq.UnitQuantity(name, definition=conversion,symbol=unit_key)
if base:
unit = [(unit_obj,unit_key,conversion,name)]
obj._set_base_units_pq(unit)
else:
unit = [(unit_obj,conversion)]
obj.initialize_units(unit)
return unit_obj
if obj.package == 'Unum':
if conversion is None:
conversion = 0
obj.del_unit(unit_key)
unit_obj = unum.Unum.unit(unit_key,conversion,name)
if base:
unit = [(unit_obj,unit_key,conversion,name)]
obj._set_base_units_unum(unit)
else:
unit = [(unit_obj,conversion)]
obj.initialize_units(unit)
return unit_obj
@bothmethod
[docs] def get_unit(obj,unit_key,local=True,global_=True):
"""
Looks up and returns unit_key in local then global unit
dictionary, if not in either returns None.
"""
unit = None
if local and obj.initialized:
unit = obj._get_local_unit(unit_key)
if global_ and not unit:
unit = obj._get_global_unit(unit_key)
return unit
@classmethod
[docs] def set_package(obj,package):
"""
Set the public methods in accordance with the selected package.
"""
obj.package = package
public_methods = ['convert_to_base','initialize','initialize_units','set_local_units']
unum_methods = [obj._convert_to_base_unum,obj._initialize_unum,obj._initialize_units_unum,obj._set_local_units_unum]
pq_methods = [obj._convert_to_base_pq,obj._initialize_pq,obj._initialize_units_pq,obj._set_local_units_pq]
if obj.package == 'Unum' and not got_unum:
raise ImportError('Unum package not installed, call Conversions.set_package(\'Quantities\') or install Unum.')
if obj.package == 'Quantities' and not got_pq:
raise ImportError('Quantities package not installed, call Conversions.set_package = (\'Unum\') or install Quantities.')
if obj.package =='Unum': selected = unum_methods
else: selected = pq_methods
[setattr(obj,pub,sel) for (sel,pub) in zip(selected, public_methods)]
def _convert_to_base_pq(self,val):
"""
Set local unit definitions and detect unit type then convert
the value and return as a float.
"""
self._set_local_units_pq()
for idx,unit in enumerate(self._base_units):
try:
val.rescale(unit[1])
break
except: pass
if idx == len(self._base_units): print 'Cannot convert {0} to base unit.'.format(val)
return val.rescale(self._base_units[idx][1]).magnitude
def _convert_to_base_unum(self,val):
"""
Convert value to base unit using local unit
definitions and return a float.
"""
self._ut_bak = unum.Unum.getUnitTable()
self._set_local_units_unum()
try:
val.checkNoUnit()
val = float(val)
except unum.ShouldBeUnitlessError:
for idx,unit in enumerate(self._base_units):
try:
val.asUnit(unit[0])
break
except: pass
if idx == len(self._base_units): print 'Cannot convert {0} to base unit.'.format(val)
val = float(val.asUnit(self._base_units[idx][0])/self._base_units[idx][0])
unum.Unum.reset(self._ut_bak)
return val
@bothmethod
def _del_unit(obj,unit_key):
"""
Delete specified unit definition from global unum unit table.
"""
unit_table = unum.Unum.getUnitTable()
if unit_key in unit_table.keys():
del unit_table[unit_key]
unum.Unum.reset(unit_table)
@bothmethod
def _get_local_unit(self,unit_key):
"""
Returns local unit object or if nonexistent None.
"""
if unit_key in self._unit_objects.keys():
return self._unit_objects[unit_key]
else:
return None
@bothmethod
def _get_global_unit(obj,unit_key):
if obj.package == 'Unum': return getattr(unum.units,unit_key)
elif obj.package == 'Quantities': return pq.registry.unit_registry[unit_key]
else: return None
def _initialize_pq(self,units):
"""
Initialize param and Topographica to deal with Quantities unit
package by installing appropriate set hook on parameters and
initializing specified units.
"""
param.Number.set_hook = strip_pq_hook
base.boundingregion.BoundingRegionParameter.set_hook = strip_pq_hook
self.initialize_units(units)
def _initialize_unum(self,units):
"""
Initialize param and Topographica to deal with Unum unit
package by installing appropriate set hook on parameters and
initializing specified units.
"""
param.Number.set_hook = strip_unum_hook
base.boundingregion.BoundingRegionParameter.set_hook = strip_unum_hook
self.initialize_units(units)
@bothmethod
def _initialize_units_pq(obj,units):
"""
Initialize specified units using Quantities unit package.
"""
if not hasattr(obj,'_unit_objects'):
obj._unit_objects = {}
if not hasattr(obj,'_unit_specs'):
obj._unit_specs = {}
for unit in units:
unit_key = unit[0].symbol
obj._unit_objects[unit_key] = unit[0]
obj._unit_specs[unit_key] = (unit[1],unit[0].name)
@bothmethod
def _initialize_units_unum(obj,units):
"""
Initialize specified units using Unum unit package.
"""
if not hasattr(obj,'_unit_objects'):
obj._unit_objects = {}
if not hasattr(obj,'_unit_specs'):
obj._unit_specs = {}
for unit in units:
unit_key = unit[0].strUnit()
obj._unit_objects[unit_key] = unit[0]
obj._unit_specs[unit_key] = (unit[1],unit[0].getUnitTable()[unit_key][2])
@bothmethod
def _set_base_units_pq(obj,units):
"""
Set base unit, which is used to interface with Topographicas
coordinate system, using Quantities unit package.
"""
if not hasattr(obj,'_base_units'):
obj._base_units = []
for unit in units:
obj._base_units.append((unit[0],unit[1],unit[2],unit[3]))
@bothmethod
def _set_base_units_unum(obj,units):
"""
Set base units, which are used to interface with Topographicas
coordinate system, using Unum unit package.
"""
if not hasattr(obj,'_base_units'):
obj._base_units = []
for unit in units:
obj._base_units.append((unit[0],unit[1],unit[2],unit[3]))
def _set_local_units_pq(self):
"""
Set the local unit definitions according to stored unit definitions and using
the Quantities unit package.
"""
for unit in self._unit_specs.keys():
self._unit_objects[unit]._conv_ref = np.array(self._unit_specs[unit][0].magnitude) * self._unit_specs[unit][0].units.simplified
for idx,unit in enumerate(self._base_units):
self._base_units[idx][0]._conv_ref = np.array(unit[2].magnitude) * unit[2].units.simplified
def _set_local_units_unum(self):
"""
Set the local unit definitions according to stored unit definitions and using
the Quantities unit package.
"""
for unit_key in self._unit_specs.keys():
self.del_unit(unit_key)
unit_spec = self._unit_specs[unit_key]
self._unit_objects[unit_key] = unum.Unum.unit(unit_key,unit_spec[0],unit_spec[1])
for unit in self._base_units:
self.del_unit(unit[1])
unum.Unum.unit(unit[1],unit[2],unit[3])