asd
This commit is contained in:
@ -0,0 +1,138 @@
|
||||
"""Duration module."""
|
||||
|
||||
import functools
|
||||
import operator
|
||||
|
||||
from matplotlib import _api
|
||||
|
||||
|
||||
class Duration:
|
||||
"""Class Duration in development."""
|
||||
|
||||
allowed = ["ET", "UTC"]
|
||||
|
||||
def __init__(self, frame, seconds):
|
||||
"""
|
||||
Create a new Duration object.
|
||||
|
||||
= ERROR CONDITIONS
|
||||
- If the input frame is not in the allowed list, an error is thrown.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- frame The frame of the duration. Must be 'ET' or 'UTC'
|
||||
- seconds The number of seconds in the Duration.
|
||||
"""
|
||||
_api.check_in_list(self.allowed, frame=frame)
|
||||
self._frame = frame
|
||||
self._seconds = seconds
|
||||
|
||||
def frame(self):
|
||||
"""Return the frame the duration is in."""
|
||||
return self._frame
|
||||
|
||||
def __abs__(self):
|
||||
"""Return the absolute value of the duration."""
|
||||
return Duration(self._frame, abs(self._seconds))
|
||||
|
||||
def __neg__(self):
|
||||
"""Return the negative value of this Duration."""
|
||||
return Duration(self._frame, -self._seconds)
|
||||
|
||||
def seconds(self):
|
||||
"""Return the number of seconds in the Duration."""
|
||||
return self._seconds
|
||||
|
||||
def __bool__(self):
|
||||
return self._seconds != 0
|
||||
|
||||
def _cmp(self, op, rhs):
|
||||
"""
|
||||
Check that *self* and *rhs* share frames; compare them using *op*.
|
||||
"""
|
||||
self.checkSameFrame(rhs, "compare")
|
||||
return op(self._seconds, rhs._seconds)
|
||||
|
||||
__eq__ = functools.partialmethod(_cmp, operator.eq)
|
||||
__ne__ = functools.partialmethod(_cmp, operator.ne)
|
||||
__lt__ = functools.partialmethod(_cmp, operator.lt)
|
||||
__le__ = functools.partialmethod(_cmp, operator.le)
|
||||
__gt__ = functools.partialmethod(_cmp, operator.gt)
|
||||
__ge__ = functools.partialmethod(_cmp, operator.ge)
|
||||
|
||||
def __add__(self, rhs):
|
||||
"""
|
||||
Add two Durations.
|
||||
|
||||
= ERROR CONDITIONS
|
||||
- If the input rhs is not in the same frame, an error is thrown.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- rhs The Duration to add.
|
||||
|
||||
= RETURN VALUE
|
||||
- Returns the sum of ourselves and the input Duration.
|
||||
"""
|
||||
# Delay-load due to circular dependencies.
|
||||
import matplotlib.testing.jpl_units as U
|
||||
|
||||
if isinstance(rhs, U.Epoch):
|
||||
return rhs + self
|
||||
|
||||
self.checkSameFrame(rhs, "add")
|
||||
return Duration(self._frame, self._seconds + rhs._seconds)
|
||||
|
||||
def __sub__(self, rhs):
|
||||
"""
|
||||
Subtract two Durations.
|
||||
|
||||
= ERROR CONDITIONS
|
||||
- If the input rhs is not in the same frame, an error is thrown.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- rhs The Duration to subtract.
|
||||
|
||||
= RETURN VALUE
|
||||
- Returns the difference of ourselves and the input Duration.
|
||||
"""
|
||||
self.checkSameFrame(rhs, "sub")
|
||||
return Duration(self._frame, self._seconds - rhs._seconds)
|
||||
|
||||
def __mul__(self, rhs):
|
||||
"""
|
||||
Scale a UnitDbl by a value.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- rhs The scalar to multiply by.
|
||||
|
||||
= RETURN VALUE
|
||||
- Returns the scaled Duration.
|
||||
"""
|
||||
return Duration(self._frame, self._seconds * float(rhs))
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __str__(self):
|
||||
"""Print the Duration."""
|
||||
return f"{self._seconds:g} {self._frame}"
|
||||
|
||||
def __repr__(self):
|
||||
"""Print the Duration."""
|
||||
return f"Duration('{self._frame}', {self._seconds:g})"
|
||||
|
||||
def checkSameFrame(self, rhs, func):
|
||||
"""
|
||||
Check to see if frames are the same.
|
||||
|
||||
= ERROR CONDITIONS
|
||||
- If the frame of the rhs Duration is not the same as our frame,
|
||||
an error is thrown.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- rhs The Duration to check for the same frame
|
||||
- func The name of the function doing the check.
|
||||
"""
|
||||
if self._frame != rhs._frame:
|
||||
raise ValueError(
|
||||
f"Cannot {func} Durations with different frames.\n"
|
||||
f"LHS: {self._frame}\n"
|
||||
f"RHS: {rhs._frame}")
|
||||
@ -0,0 +1,211 @@
|
||||
"""Epoch module."""
|
||||
|
||||
import functools
|
||||
import operator
|
||||
import math
|
||||
import datetime as DT
|
||||
|
||||
from matplotlib import _api
|
||||
from matplotlib.dates import date2num
|
||||
|
||||
|
||||
class Epoch:
|
||||
# Frame conversion offsets in seconds
|
||||
# t(TO) = t(FROM) + allowed[ FROM ][ TO ]
|
||||
allowed = {
|
||||
"ET": {
|
||||
"UTC": +64.1839,
|
||||
},
|
||||
"UTC": {
|
||||
"ET": -64.1839,
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, frame, sec=None, jd=None, daynum=None, dt=None):
|
||||
"""
|
||||
Create a new Epoch object.
|
||||
|
||||
Build an epoch 1 of 2 ways:
|
||||
|
||||
Using seconds past a Julian date:
|
||||
# Epoch('ET', sec=1e8, jd=2451545)
|
||||
|
||||
or using a matplotlib day number
|
||||
# Epoch('ET', daynum=730119.5)
|
||||
|
||||
= ERROR CONDITIONS
|
||||
- If the input units are not in the allowed list, an error is thrown.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- frame The frame of the epoch. Must be 'ET' or 'UTC'
|
||||
- sec The number of seconds past the input JD.
|
||||
- jd The Julian date of the epoch.
|
||||
- daynum The matplotlib day number of the epoch.
|
||||
- dt A python datetime instance.
|
||||
"""
|
||||
if ((sec is None and jd is not None) or
|
||||
(sec is not None and jd is None) or
|
||||
(daynum is not None and
|
||||
(sec is not None or jd is not None)) or
|
||||
(daynum is None and dt is None and
|
||||
(sec is None or jd is None)) or
|
||||
(daynum is not None and dt is not None) or
|
||||
(dt is not None and (sec is not None or jd is not None)) or
|
||||
((dt is not None) and not isinstance(dt, DT.datetime))):
|
||||
raise ValueError(
|
||||
"Invalid inputs. Must enter sec and jd together, "
|
||||
"daynum by itself, or dt (must be a python datetime).\n"
|
||||
"Sec = %s\n"
|
||||
"JD = %s\n"
|
||||
"dnum= %s\n"
|
||||
"dt = %s" % (sec, jd, daynum, dt))
|
||||
|
||||
_api.check_in_list(self.allowed, frame=frame)
|
||||
self._frame = frame
|
||||
|
||||
if dt is not None:
|
||||
daynum = date2num(dt)
|
||||
|
||||
if daynum is not None:
|
||||
# 1-JAN-0001 in JD = 1721425.5
|
||||
jd = float(daynum) + 1721425.5
|
||||
self._jd = math.floor(jd)
|
||||
self._seconds = (jd - self._jd) * 86400.0
|
||||
|
||||
else:
|
||||
self._seconds = float(sec)
|
||||
self._jd = float(jd)
|
||||
|
||||
# Resolve seconds down to [ 0, 86400)
|
||||
deltaDays = math.floor(self._seconds / 86400)
|
||||
self._jd += deltaDays
|
||||
self._seconds -= deltaDays * 86400.0
|
||||
|
||||
def convert(self, frame):
|
||||
if self._frame == frame:
|
||||
return self
|
||||
|
||||
offset = self.allowed[self._frame][frame]
|
||||
|
||||
return Epoch(frame, self._seconds + offset, self._jd)
|
||||
|
||||
def frame(self):
|
||||
return self._frame
|
||||
|
||||
def julianDate(self, frame):
|
||||
t = self
|
||||
if frame != self._frame:
|
||||
t = self.convert(frame)
|
||||
|
||||
return t._jd + t._seconds / 86400.0
|
||||
|
||||
def secondsPast(self, frame, jd):
|
||||
t = self
|
||||
if frame != self._frame:
|
||||
t = self.convert(frame)
|
||||
|
||||
delta = t._jd - jd
|
||||
return t._seconds + delta * 86400
|
||||
|
||||
def _cmp(self, op, rhs):
|
||||
"""Compare Epochs *self* and *rhs* using operator *op*."""
|
||||
t = self
|
||||
if self._frame != rhs._frame:
|
||||
t = self.convert(rhs._frame)
|
||||
if t._jd != rhs._jd:
|
||||
return op(t._jd, rhs._jd)
|
||||
return op(t._seconds, rhs._seconds)
|
||||
|
||||
__eq__ = functools.partialmethod(_cmp, operator.eq)
|
||||
__ne__ = functools.partialmethod(_cmp, operator.ne)
|
||||
__lt__ = functools.partialmethod(_cmp, operator.lt)
|
||||
__le__ = functools.partialmethod(_cmp, operator.le)
|
||||
__gt__ = functools.partialmethod(_cmp, operator.gt)
|
||||
__ge__ = functools.partialmethod(_cmp, operator.ge)
|
||||
|
||||
def __add__(self, rhs):
|
||||
"""
|
||||
Add a duration to an Epoch.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- rhs The Epoch to subtract.
|
||||
|
||||
= RETURN VALUE
|
||||
- Returns the difference of ourselves and the input Epoch.
|
||||
"""
|
||||
t = self
|
||||
if self._frame != rhs.frame():
|
||||
t = self.convert(rhs._frame)
|
||||
|
||||
sec = t._seconds + rhs.seconds()
|
||||
|
||||
return Epoch(t._frame, sec, t._jd)
|
||||
|
||||
def __sub__(self, rhs):
|
||||
"""
|
||||
Subtract two Epoch's or a Duration from an Epoch.
|
||||
|
||||
Valid:
|
||||
Duration = Epoch - Epoch
|
||||
Epoch = Epoch - Duration
|
||||
|
||||
= INPUT VARIABLES
|
||||
- rhs The Epoch to subtract.
|
||||
|
||||
= RETURN VALUE
|
||||
- Returns either the duration between to Epoch's or the a new
|
||||
Epoch that is the result of subtracting a duration from an epoch.
|
||||
"""
|
||||
# Delay-load due to circular dependencies.
|
||||
import matplotlib.testing.jpl_units as U
|
||||
|
||||
# Handle Epoch - Duration
|
||||
if isinstance(rhs, U.Duration):
|
||||
return self + -rhs
|
||||
|
||||
t = self
|
||||
if self._frame != rhs._frame:
|
||||
t = self.convert(rhs._frame)
|
||||
|
||||
days = t._jd - rhs._jd
|
||||
sec = t._seconds - rhs._seconds
|
||||
|
||||
return U.Duration(rhs._frame, days*86400 + sec)
|
||||
|
||||
def __str__(self):
|
||||
"""Print the Epoch."""
|
||||
return f"{self.julianDate(self._frame):22.15e} {self._frame}"
|
||||
|
||||
def __repr__(self):
|
||||
"""Print the Epoch."""
|
||||
return str(self)
|
||||
|
||||
@staticmethod
|
||||
def range(start, stop, step):
|
||||
"""
|
||||
Generate a range of Epoch objects.
|
||||
|
||||
Similar to the Python range() method. Returns the range [
|
||||
start, stop) at the requested step. Each element will be a
|
||||
Epoch object.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- start The starting value of the range.
|
||||
- stop The stop value of the range.
|
||||
- step Step to use.
|
||||
|
||||
= RETURN VALUE
|
||||
- Returns a list containing the requested Epoch values.
|
||||
"""
|
||||
elems = []
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
d = start + i * step
|
||||
if d >= stop:
|
||||
break
|
||||
|
||||
elems.append(d)
|
||||
i += 1
|
||||
|
||||
return elems
|
||||
@ -0,0 +1,94 @@
|
||||
"""EpochConverter module containing class EpochConverter."""
|
||||
|
||||
from matplotlib import cbook, units
|
||||
import matplotlib.dates as date_ticker
|
||||
|
||||
__all__ = ['EpochConverter']
|
||||
|
||||
|
||||
class EpochConverter(units.ConversionInterface):
|
||||
"""
|
||||
Provides Matplotlib conversion functionality for Monte Epoch and Duration
|
||||
classes.
|
||||
"""
|
||||
|
||||
jdRef = 1721425.5
|
||||
|
||||
@staticmethod
|
||||
def axisinfo(unit, axis):
|
||||
# docstring inherited
|
||||
majloc = date_ticker.AutoDateLocator()
|
||||
majfmt = date_ticker.AutoDateFormatter(majloc)
|
||||
return units.AxisInfo(majloc=majloc, majfmt=majfmt, label=unit)
|
||||
|
||||
@staticmethod
|
||||
def float2epoch(value, unit):
|
||||
"""
|
||||
Convert a Matplotlib floating-point date into an Epoch of the specified
|
||||
units.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- value The Matplotlib floating-point date.
|
||||
- unit The unit system to use for the Epoch.
|
||||
|
||||
= RETURN VALUE
|
||||
- Returns the value converted to an Epoch in the specified time system.
|
||||
"""
|
||||
# Delay-load due to circular dependencies.
|
||||
import matplotlib.testing.jpl_units as U
|
||||
|
||||
secPastRef = value * 86400.0 * U.UnitDbl(1.0, 'sec')
|
||||
return U.Epoch(unit, secPastRef, EpochConverter.jdRef)
|
||||
|
||||
@staticmethod
|
||||
def epoch2float(value, unit):
|
||||
"""
|
||||
Convert an Epoch value to a float suitable for plotting as a python
|
||||
datetime object.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- value An Epoch or list of Epochs that need to be converted.
|
||||
- unit The units to use for an axis with Epoch data.
|
||||
|
||||
= RETURN VALUE
|
||||
- Returns the value parameter converted to floats.
|
||||
"""
|
||||
return value.julianDate(unit) - EpochConverter.jdRef
|
||||
|
||||
@staticmethod
|
||||
def duration2float(value):
|
||||
"""
|
||||
Convert a Duration value to a float suitable for plotting as a python
|
||||
datetime object.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- value A Duration or list of Durations that need to be converted.
|
||||
|
||||
= RETURN VALUE
|
||||
- Returns the value parameter converted to floats.
|
||||
"""
|
||||
return value.seconds() / 86400.0
|
||||
|
||||
@staticmethod
|
||||
def convert(value, unit, axis):
|
||||
# docstring inherited
|
||||
|
||||
# Delay-load due to circular dependencies.
|
||||
import matplotlib.testing.jpl_units as U
|
||||
|
||||
if not cbook.is_scalar_or_string(value):
|
||||
return [EpochConverter.convert(x, unit, axis) for x in value]
|
||||
if unit is None:
|
||||
unit = EpochConverter.default_units(value, axis)
|
||||
if isinstance(value, U.Duration):
|
||||
return EpochConverter.duration2float(value)
|
||||
else:
|
||||
return EpochConverter.epoch2float(value, unit)
|
||||
|
||||
@staticmethod
|
||||
def default_units(value, axis):
|
||||
# docstring inherited
|
||||
if cbook.is_scalar_or_string(value):
|
||||
return value.frame()
|
||||
else:
|
||||
return EpochConverter.default_units(value[0], axis)
|
||||
@ -0,0 +1,97 @@
|
||||
"""StrConverter module containing class StrConverter."""
|
||||
|
||||
import numpy as np
|
||||
|
||||
import matplotlib.units as units
|
||||
|
||||
__all__ = ['StrConverter']
|
||||
|
||||
|
||||
class StrConverter(units.ConversionInterface):
|
||||
"""
|
||||
A Matplotlib converter class for string data values.
|
||||
|
||||
Valid units for string are:
|
||||
- 'indexed' : Values are indexed as they are specified for plotting.
|
||||
- 'sorted' : Values are sorted alphanumerically.
|
||||
- 'inverted' : Values are inverted so that the first value is on top.
|
||||
- 'sorted-inverted' : A combination of 'sorted' and 'inverted'
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def axisinfo(unit, axis):
|
||||
# docstring inherited
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def convert(value, unit, axis):
|
||||
# docstring inherited
|
||||
|
||||
if value == []:
|
||||
return []
|
||||
|
||||
# we delay loading to make matplotlib happy
|
||||
ax = axis.axes
|
||||
if axis is ax.xaxis:
|
||||
isXAxis = True
|
||||
else:
|
||||
isXAxis = False
|
||||
|
||||
axis.get_major_ticks()
|
||||
ticks = axis.get_ticklocs()
|
||||
labels = axis.get_ticklabels()
|
||||
|
||||
labels = [l.get_text() for l in labels if l.get_text()]
|
||||
|
||||
if not labels:
|
||||
ticks = []
|
||||
labels = []
|
||||
|
||||
if not np.iterable(value):
|
||||
value = [value]
|
||||
|
||||
newValues = []
|
||||
for v in value:
|
||||
if v not in labels and v not in newValues:
|
||||
newValues.append(v)
|
||||
|
||||
labels.extend(newValues)
|
||||
|
||||
# DISABLED: This is disabled because matplotlib bar plots do not
|
||||
# DISABLED: recalculate the unit conversion of the data values
|
||||
# DISABLED: this is due to design and is not really a bug.
|
||||
# DISABLED: If this gets changed, then we can activate the following
|
||||
# DISABLED: block of code. Note that this works for line plots.
|
||||
# DISABLED if unit:
|
||||
# DISABLED if unit.find("sorted") > -1:
|
||||
# DISABLED labels.sort()
|
||||
# DISABLED if unit.find("inverted") > -1:
|
||||
# DISABLED labels = labels[::-1]
|
||||
|
||||
# add padding (so they do not appear on the axes themselves)
|
||||
labels = [''] + labels + ['']
|
||||
ticks = list(range(len(labels)))
|
||||
ticks[0] = 0.5
|
||||
ticks[-1] = ticks[-1] - 0.5
|
||||
|
||||
axis.set_ticks(ticks)
|
||||
axis.set_ticklabels(labels)
|
||||
# we have to do the following lines to make ax.autoscale_view work
|
||||
loc = axis.get_major_locator()
|
||||
loc.set_bounds(ticks[0], ticks[-1])
|
||||
|
||||
if isXAxis:
|
||||
ax.set_xlim(ticks[0], ticks[-1])
|
||||
else:
|
||||
ax.set_ylim(ticks[0], ticks[-1])
|
||||
|
||||
result = [ticks[labels.index(v)] for v in value]
|
||||
|
||||
ax.viewLim.ignore(-1)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def default_units(value, axis):
|
||||
# docstring inherited
|
||||
# The default behavior for string indexing.
|
||||
return "indexed"
|
||||
@ -0,0 +1,180 @@
|
||||
"""UnitDbl module."""
|
||||
|
||||
import functools
|
||||
import operator
|
||||
|
||||
from matplotlib import _api
|
||||
|
||||
|
||||
class UnitDbl:
|
||||
"""Class UnitDbl in development."""
|
||||
|
||||
# Unit conversion table. Small subset of the full one but enough
|
||||
# to test the required functions. First field is a scale factor to
|
||||
# convert the input units to the units of the second field. Only
|
||||
# units in this table are allowed.
|
||||
allowed = {
|
||||
"m": (0.001, "km"),
|
||||
"km": (1, "km"),
|
||||
"mile": (1.609344, "km"),
|
||||
|
||||
"rad": (1, "rad"),
|
||||
"deg": (1.745329251994330e-02, "rad"),
|
||||
|
||||
"sec": (1, "sec"),
|
||||
"min": (60.0, "sec"),
|
||||
"hour": (3600, "sec"),
|
||||
}
|
||||
|
||||
_types = {
|
||||
"km": "distance",
|
||||
"rad": "angle",
|
||||
"sec": "time",
|
||||
}
|
||||
|
||||
def __init__(self, value, units):
|
||||
"""
|
||||
Create a new UnitDbl object.
|
||||
|
||||
Units are internally converted to km, rad, and sec. The only
|
||||
valid inputs for units are [m, km, mile, rad, deg, sec, min, hour].
|
||||
|
||||
The field UnitDbl.value will contain the converted value. Use
|
||||
the convert() method to get a specific type of units back.
|
||||
|
||||
= ERROR CONDITIONS
|
||||
- If the input units are not in the allowed list, an error is thrown.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- value The numeric value of the UnitDbl.
|
||||
- units The string name of the units the value is in.
|
||||
"""
|
||||
data = _api.check_getitem(self.allowed, units=units)
|
||||
self._value = float(value * data[0])
|
||||
self._units = data[1]
|
||||
|
||||
def convert(self, units):
|
||||
"""
|
||||
Convert the UnitDbl to a specific set of units.
|
||||
|
||||
= ERROR CONDITIONS
|
||||
- If the input units are not in the allowed list, an error is thrown.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- units The string name of the units to convert to.
|
||||
|
||||
= RETURN VALUE
|
||||
- Returns the value of the UnitDbl in the requested units as a floating
|
||||
point number.
|
||||
"""
|
||||
if self._units == units:
|
||||
return self._value
|
||||
data = _api.check_getitem(self.allowed, units=units)
|
||||
if self._units != data[1]:
|
||||
raise ValueError(f"Error trying to convert to different units.\n"
|
||||
f" Invalid conversion requested.\n"
|
||||
f" UnitDbl: {self}\n"
|
||||
f" Units: {units}\n")
|
||||
return self._value / data[0]
|
||||
|
||||
def __abs__(self):
|
||||
"""Return the absolute value of this UnitDbl."""
|
||||
return UnitDbl(abs(self._value), self._units)
|
||||
|
||||
def __neg__(self):
|
||||
"""Return the negative value of this UnitDbl."""
|
||||
return UnitDbl(-self._value, self._units)
|
||||
|
||||
def __bool__(self):
|
||||
"""Return the truth value of a UnitDbl."""
|
||||
return bool(self._value)
|
||||
|
||||
def _cmp(self, op, rhs):
|
||||
"""Check that *self* and *rhs* share units; compare them using *op*."""
|
||||
self.checkSameUnits(rhs, "compare")
|
||||
return op(self._value, rhs._value)
|
||||
|
||||
__eq__ = functools.partialmethod(_cmp, operator.eq)
|
||||
__ne__ = functools.partialmethod(_cmp, operator.ne)
|
||||
__lt__ = functools.partialmethod(_cmp, operator.lt)
|
||||
__le__ = functools.partialmethod(_cmp, operator.le)
|
||||
__gt__ = functools.partialmethod(_cmp, operator.gt)
|
||||
__ge__ = functools.partialmethod(_cmp, operator.ge)
|
||||
|
||||
def _binop_unit_unit(self, op, rhs):
|
||||
"""Check that *self* and *rhs* share units; combine them using *op*."""
|
||||
self.checkSameUnits(rhs, op.__name__)
|
||||
return UnitDbl(op(self._value, rhs._value), self._units)
|
||||
|
||||
__add__ = functools.partialmethod(_binop_unit_unit, operator.add)
|
||||
__sub__ = functools.partialmethod(_binop_unit_unit, operator.sub)
|
||||
|
||||
def _binop_unit_scalar(self, op, scalar):
|
||||
"""Combine *self* and *scalar* using *op*."""
|
||||
return UnitDbl(op(self._value, scalar), self._units)
|
||||
|
||||
__mul__ = functools.partialmethod(_binop_unit_scalar, operator.mul)
|
||||
__rmul__ = functools.partialmethod(_binop_unit_scalar, operator.mul)
|
||||
|
||||
def __str__(self):
|
||||
"""Print the UnitDbl."""
|
||||
return f"{self._value:g} *{self._units}"
|
||||
|
||||
def __repr__(self):
|
||||
"""Print the UnitDbl."""
|
||||
return f"UnitDbl({self._value:g}, '{self._units}')"
|
||||
|
||||
def type(self):
|
||||
"""Return the type of UnitDbl data."""
|
||||
return self._types[self._units]
|
||||
|
||||
@staticmethod
|
||||
def range(start, stop, step=None):
|
||||
"""
|
||||
Generate a range of UnitDbl objects.
|
||||
|
||||
Similar to the Python range() method. Returns the range [
|
||||
start, stop) at the requested step. Each element will be a
|
||||
UnitDbl object.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- start The starting value of the range.
|
||||
- stop The stop value of the range.
|
||||
- step Optional step to use. If set to None, then a UnitDbl of
|
||||
value 1 w/ the units of the start is used.
|
||||
|
||||
= RETURN VALUE
|
||||
- Returns a list containing the requested UnitDbl values.
|
||||
"""
|
||||
if step is None:
|
||||
step = UnitDbl(1, start._units)
|
||||
|
||||
elems = []
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
d = start + i * step
|
||||
if d >= stop:
|
||||
break
|
||||
|
||||
elems.append(d)
|
||||
i += 1
|
||||
|
||||
return elems
|
||||
|
||||
def checkSameUnits(self, rhs, func):
|
||||
"""
|
||||
Check to see if units are the same.
|
||||
|
||||
= ERROR CONDITIONS
|
||||
- If the units of the rhs UnitDbl are not the same as our units,
|
||||
an error is thrown.
|
||||
|
||||
= INPUT VARIABLES
|
||||
- rhs The UnitDbl to check for the same units
|
||||
- func The name of the function doing the check.
|
||||
"""
|
||||
if self._units != rhs._units:
|
||||
raise ValueError(f"Cannot {func} units of different types.\n"
|
||||
f"LHS: {self._units}\n"
|
||||
f"RHS: {rhs._units}")
|
||||
@ -0,0 +1,85 @@
|
||||
"""UnitDblConverter module containing class UnitDblConverter."""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import cbook, units
|
||||
import matplotlib.projections.polar as polar
|
||||
|
||||
__all__ = ['UnitDblConverter']
|
||||
|
||||
|
||||
# A special function for use with the matplotlib FuncFormatter class
|
||||
# for formatting axes with radian units.
|
||||
# This was copied from matplotlib example code.
|
||||
def rad_fn(x, pos=None):
|
||||
"""Radian function formatter."""
|
||||
n = int((x / np.pi) * 2.0 + 0.25)
|
||||
if n == 0:
|
||||
return str(x)
|
||||
elif n == 1:
|
||||
return r'$\pi/2$'
|
||||
elif n == 2:
|
||||
return r'$\pi$'
|
||||
elif n % 2 == 0:
|
||||
return fr'${n//2}\pi$'
|
||||
else:
|
||||
return fr'${n}\pi/2$'
|
||||
|
||||
|
||||
class UnitDblConverter(units.ConversionInterface):
|
||||
"""
|
||||
Provides Matplotlib conversion functionality for the Monte UnitDbl class.
|
||||
"""
|
||||
# default for plotting
|
||||
defaults = {
|
||||
"distance": 'km',
|
||||
"angle": 'deg',
|
||||
"time": 'sec',
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def axisinfo(unit, axis):
|
||||
# docstring inherited
|
||||
|
||||
# Delay-load due to circular dependencies.
|
||||
import matplotlib.testing.jpl_units as U
|
||||
|
||||
# Check to see if the value used for units is a string unit value
|
||||
# or an actual instance of a UnitDbl so that we can use the unit
|
||||
# value for the default axis label value.
|
||||
if unit:
|
||||
label = unit if isinstance(unit, str) else unit.label()
|
||||
else:
|
||||
label = None
|
||||
|
||||
if label == "deg" and isinstance(axis.axes, polar.PolarAxes):
|
||||
# If we want degrees for a polar plot, use the PolarPlotFormatter
|
||||
majfmt = polar.PolarAxes.ThetaFormatter()
|
||||
else:
|
||||
majfmt = U.UnitDblFormatter(useOffset=False)
|
||||
|
||||
return units.AxisInfo(majfmt=majfmt, label=label)
|
||||
|
||||
@staticmethod
|
||||
def convert(value, unit, axis):
|
||||
# docstring inherited
|
||||
if not cbook.is_scalar_or_string(value):
|
||||
return [UnitDblConverter.convert(x, unit, axis) for x in value]
|
||||
# If no units were specified, then get the default units to use.
|
||||
if unit is None:
|
||||
unit = UnitDblConverter.default_units(value, axis)
|
||||
# Convert the incoming UnitDbl value/values to float/floats
|
||||
if isinstance(axis.axes, polar.PolarAxes) and value.type() == "angle":
|
||||
# Guarantee that units are radians for polar plots.
|
||||
return value.convert("rad")
|
||||
return value.convert(unit)
|
||||
|
||||
@staticmethod
|
||||
def default_units(value, axis):
|
||||
# docstring inherited
|
||||
# Determine the default units based on the user preferences set for
|
||||
# default units when printing a UnitDbl.
|
||||
if cbook.is_scalar_or_string(value):
|
||||
return UnitDblConverter.defaults[value.type()]
|
||||
else:
|
||||
return UnitDblConverter.default_units(value[0], axis)
|
||||
@ -0,0 +1,28 @@
|
||||
"""UnitDblFormatter module containing class UnitDblFormatter."""
|
||||
|
||||
import matplotlib.ticker as ticker
|
||||
|
||||
__all__ = ['UnitDblFormatter']
|
||||
|
||||
|
||||
class UnitDblFormatter(ticker.ScalarFormatter):
|
||||
"""
|
||||
The formatter for UnitDbl data types.
|
||||
|
||||
This allows for formatting with the unit string.
|
||||
"""
|
||||
|
||||
def __call__(self, x, pos=None):
|
||||
# docstring inherited
|
||||
if len(self.locs) == 0:
|
||||
return ''
|
||||
else:
|
||||
return f'{x:.12}'
|
||||
|
||||
def format_data_short(self, value):
|
||||
# docstring inherited
|
||||
return f'{value:.12}'
|
||||
|
||||
def format_data(self, value):
|
||||
# docstring inherited
|
||||
return f'{value:.12}'
|
||||
@ -0,0 +1,76 @@
|
||||
"""
|
||||
A sample set of units for use with testing unit conversion
|
||||
of Matplotlib routines. These are used because they use very strict
|
||||
enforcement of unitized data which will test the entire spectrum of how
|
||||
unitized data might be used (it is not always meaningful to convert to
|
||||
a float without specific units given).
|
||||
|
||||
UnitDbl is essentially a unitized floating point number. It has a
|
||||
minimal set of supported units (enough for testing purposes). All
|
||||
of the mathematical operation are provided to fully test any behaviour
|
||||
that might occur with unitized data. Remember that unitized data has
|
||||
rules as to how it can be applied to one another (a value of distance
|
||||
cannot be added to a value of time). Thus we need to guard against any
|
||||
accidental "default" conversion that will strip away the meaning of the
|
||||
data and render it neutered.
|
||||
|
||||
Epoch is different than a UnitDbl of time. Time is something that can be
|
||||
measured where an Epoch is a specific moment in time. Epochs are typically
|
||||
referenced as an offset from some predetermined epoch.
|
||||
|
||||
A difference of two epochs is a Duration. The distinction between a Duration
|
||||
and a UnitDbl of time is made because an Epoch can have different frames (or
|
||||
units). In the case of our test Epoch class the two allowed frames are 'UTC'
|
||||
and 'ET' (Note that these are rough estimates provided for testing purposes
|
||||
and should not be used in production code where accuracy of time frames is
|
||||
desired). As such a Duration also has a frame of reference and therefore needs
|
||||
to be called out as different that a simple measurement of time since a delta-t
|
||||
in one frame may not be the same in another.
|
||||
"""
|
||||
|
||||
from .Duration import Duration
|
||||
from .Epoch import Epoch
|
||||
from .UnitDbl import UnitDbl
|
||||
|
||||
from .StrConverter import StrConverter
|
||||
from .EpochConverter import EpochConverter
|
||||
from .UnitDblConverter import UnitDblConverter
|
||||
|
||||
from .UnitDblFormatter import UnitDblFormatter
|
||||
|
||||
|
||||
__version__ = "1.0"
|
||||
|
||||
__all__ = [
|
||||
'register',
|
||||
'Duration',
|
||||
'Epoch',
|
||||
'UnitDbl',
|
||||
'UnitDblFormatter',
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
"""Register the unit conversion classes with matplotlib."""
|
||||
import matplotlib.units as mplU
|
||||
|
||||
mplU.registry[str] = StrConverter()
|
||||
mplU.registry[Epoch] = EpochConverter()
|
||||
mplU.registry[Duration] = EpochConverter()
|
||||
mplU.registry[UnitDbl] = UnitDblConverter()
|
||||
|
||||
|
||||
# Some default unit instances
|
||||
# Distances
|
||||
m = UnitDbl(1.0, "m")
|
||||
km = UnitDbl(1.0, "km")
|
||||
mile = UnitDbl(1.0, "mile")
|
||||
# Angles
|
||||
deg = UnitDbl(1.0, "deg")
|
||||
rad = UnitDbl(1.0, "rad")
|
||||
# Time
|
||||
sec = UnitDbl(1.0, "sec")
|
||||
min = UnitDbl(1.0, "min")
|
||||
hr = UnitDbl(1.0, "hour")
|
||||
day = UnitDbl(24.0, "hour")
|
||||
sec = UnitDbl(1.0, "sec")
|
||||
Reference in New Issue
Block a user