Skip to content

Commit 2a02cac

Browse files
author
Darren Dale
committed
proper subclassing of ndarray
figured out how to handle constants as unit constants, with reference quantities that are UncertainQuantitys
1 parent 996a443 commit 2a02cac

8 files changed

Lines changed: 175 additions & 100 deletions

File tree

.ropeproject/objectdb

-8.34 KB
Binary file not shown.

quantities/constants/constants.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# -*- coding: utf-8 -*-
12
"""
23
"""
34
from __future__ import absolute_import
@@ -6,6 +7,7 @@
67
from .codata import physical_constants as _pc
78
from quantities.quantity import Quantity
89
from quantities.uncertainquantity import UncertainQuantity
10+
from quantities.unitquantity import UnitQuantity
911

1012
def _cd(name):
1113
entry = _pc[name]
@@ -52,8 +54,13 @@ def _cd(name):
5254
yobi = 2**80
5355

5456
#physical constants
55-
d_220 = a_Si_220 = silicon_220_lattice_spacing = \
56-
_cd('{220} lattice spacing of silicon')
57+
d_220 = a_Si_220 = silicon_220_lattice_spacing = UnitQuantity(
58+
'silicon_220_lattice_spacing',
59+
_cd('{220} lattice spacing of silicon'),
60+
symbol='d_220',
61+
u_symbol='d₂₂₀',
62+
aliases=['a_Si_220']
63+
)
5764
alpha_particle_electron_mass_ratio = \
5865
_cd('alpha particle-electron mass ratio')
5966
m_alpha = alpha_particle_mass = \

quantities/dimensionality.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class BaseDimensionality(object):
1919
def simplified(self):
2020
if len(self):
2121
rq = 1*unit_registry['dimensionless']
22+
print type(rq), type(rq.dimensionality)
2223
for u, d in self.iteritems():
2324
rq *= u.reference_quantity**d
2425
return rq.dimensionality
@@ -182,6 +183,9 @@ def __contains__(self, key):
182183

183184
class Dimensionality(BaseDimensionality, dict):
184185

186+
def copy(self):
187+
return Dimensionality(dict.copy(self))
188+
185189
def __iadd__(self, other):
186190
try:
187191
assert self == other

quantities/markup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ def format_units(udict):
3333
u = key.symbol
3434
if d>0:
3535
if d > 1:
36-
u = u + ('**%s'%d)
36+
u = u + ('**%s'%d).rstrip('0').rstrip('.')
3737
num.append(u)
3838
elif d<0:
3939
d = -d
4040
if d > 1:
41-
u = u + ('**%s'%d)
41+
u = u + ('**%s'%d).rstrip('0').rstrip('.')
4242
den.append(u)
4343
res = '*'.join(num)
4444
if len(den):

quantities/quantity.py

Lines changed: 83 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -38,28 +38,24 @@ class Quantity(numpy.ndarray):
3838
__array_priority__ = 21
3939

4040
def __new__(cls, data, units='', dtype='d', copy=True):
41-
if not isinstance(data, numpy.ndarray):
42-
data = numpy.array(data, dtype=dtype)
43-
44-
if copy == True:
45-
data = data.copy()
41+
if isinstance(data, Quantity):
42+
if units:
43+
# force a copy so we don't rescale a subset of the original
44+
copy = True
45+
46+
res = numpy.array(data, dtype=dtype, copy=copy).view(cls)
47+
if copy:
48+
res._dimensionality = data._dimensionality.copy()
49+
else:
50+
res._dimensionality = data._dimensionality
51+
if units:
52+
res.units = units
4653

47-
if isinstance(data, Quantity) and units:
48-
data = data.rescale(units)
54+
return res
4955

50-
# should this be a "cooperative super" call instead?
51-
ret = numpy.ndarray.__new__(
52-
cls,
53-
data.shape,
54-
data.dtype,
55-
buffer=data
56-
)
57-
return ret
56+
res = numpy.array(data, dtype=dtype, copy=copy).view(cls)
5857

59-
def __init__(self, data, units='', dtype='d', copy=True):
60-
if not units and isinstance(data, Quantity):
61-
dims = data.dimensionality
62-
elif isinstance(units, str):
58+
if isinstance(units, str):
6359
if units in ('', 'dimensionless'):
6460
dims = {}
6561
else:
@@ -73,7 +69,9 @@ def __init__(self, data, units='', dtype='d', copy=True):
7369
'units must be a quantity, string, or dimensionality, got %s'\
7470
%type(units)
7571
)
76-
self._dimensionality = Dimensionality(dims)
72+
res._dimensionality = Dimensionality(dims)
73+
74+
return res
7775

7876
@property
7977
def dimensionality(self):
@@ -116,30 +114,76 @@ def rescale(self, units):
116114
"""
117115
Return a copy of the quantity converted to the specified units
118116
"""
119-
copy = Quantity(self)
120-
copy.units = units
121-
return copy
117+
return Quantity(self, units)
122118

123119
@property
124120
def simplified(self):
125121
rq = 1*unit_registry['dimensionless']
126122
for u, d in self.dimensionality.iteritems():
127-
rq *= u.reference_quantity**d
123+
rq = rq * u.reference_quantity**d
128124
return rq * self.magnitude
129125

130126
def __array_finalize__(self, obj):
131-
self._dimensionality = getattr(
132-
obj, 'dimensionality', Dimensionality()
133-
)
127+
self._dimensionality = getattr(obj, '_dimensionality', Dimensionality())
128+
if self.base is None:
129+
self._dimensionality = self._dimensionality.copy()
130+
131+
# def __array_wrap__(self, obj, context=None):
132+
# """
133+
# Special hook for ufuncs.
134+
# Wraps the numpy array and sets the mask according to context.
135+
# """
136+
# result = obj.view(type(self))
137+
#
138+
# if context is not None:
139+
# result._dimensionality = result._dimensionality.copy()
140+
# (func, args, _) = context
141+
# m = reduce(mask_or, [getmaskarray(arg) for arg in args])
142+
# # Get the domain mask................
143+
# domain = ufunc_domain.get(func, None)
144+
# if domain is not None:
145+
# if len(args) > 2:
146+
# d = reduce(domain, args)
147+
# else:
148+
# d = domain(*args)
149+
# # Fill the result where the domain is wrong
150+
# try:
151+
# # Binary domain: take the last value
152+
# fill_value = ufunc_fills[func][-1]
153+
# except TypeError:
154+
# # Unary domain: just use this one
155+
# fill_value = ufunc_fills[func]
156+
# except KeyError:
157+
# # Domain not recognized, use fill_value instead
158+
# fill_value = self.fill_value
159+
# result = result.copy()
160+
# np.putmask(result, d, fill_value)
161+
# # Update the mask
162+
# if m is nomask:
163+
# if d is not nomask:
164+
# m = d
165+
# else:
166+
# m |= d
167+
# # Make sure the mask has the proper size
168+
# if result.shape == () and m:
169+
# return masked
170+
# else:
171+
# result._mask = m
172+
# result._sharedmask = False
173+
# #....
174+
# return result
134175

135176
def __add__(self, other):
136177
if not isinstance(other, Quantity):
137178
other = Quantity(other, copy=False)
138179

139180
dims = self.dimensionality + other.dimensionality
140-
magnitude = self.magnitude + other.magnitude
181+
ret = super(Quantity, self).__add__(other)
182+
ret._dimensionality = dims
141183

142-
return Quantity(magnitude, dims, magnitude.dtype)
184+
return ret
185+
186+
# TODO: in-place arithmetic should check for .base, and raise if not None
143187

144188
def __iadd__(self, other):
145189
if not isinstance(other, Quantity):
@@ -187,12 +231,13 @@ def __rsub__(self, other):
187231
def __mul__(self, other):
188232
try:
189233
dims = self.dimensionality * other.dimensionality
190-
magnitude = self.magnitude * other.magnitude
191234
except AttributeError:
192-
magnitude = self.magnitude * other
193-
dims = copy.copy(self.dimensionality)
235+
other = Quantity(other, copy=False)
236+
dims = Dimensionality(self.dimensionality)
194237

195-
return Quantity(magnitude, dims, magnitude.dtype)
238+
ret = super(Quantity, self).__mul__(other)
239+
ret._dimensionality = dims
240+
return ret
196241

197242
def __imul__(self, other):
198243
try:
@@ -211,12 +256,13 @@ def __rmul__(self, other):
211256
def __truediv__(self, other):
212257
try:
213258
dims = self.dimensionality / other.dimensionality
214-
magnitude = self.magnitude / other.magnitude
215259
except AttributeError:
216-
magnitude = self.magnitude / other
217-
dims = copy.copy(self.dimensionality)
260+
other = Quantity(other, copy=False)
261+
dims = Dimensionality(self.dimensionality)
218262

219-
return Quantity(magnitude, dims, magnitude.dtype)
263+
ret = super(Quantity, self).__truediv__(other)
264+
ret._dimensionality = dims
265+
return ret
220266

221267
def __div__(self, other):
222268
return self.__truediv__(other)

quantities/uncertainquantity.py

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,25 @@ class UncertainQuantity(Quantity):
1212
__array_priority__ = 22
1313

1414
def __new__(cls, data, units='', uncertainty=None, dtype='d', copy=True):
15-
return Quantity.__new__(cls, data, units, dtype, copy)
16-
17-
def __init__(self, data, units='', uncertainty=None, dtype='d', copy=True):
18-
Quantity.__init__(self, data, units, dtype, copy)
15+
ret = Quantity.__new__(cls, data, units, dtype, copy)
1916

2017
if uncertainty is None:
2118
if isinstance(data, UncertainQuantity):
22-
uncertainty = data.uncertainty
19+
if copy:
20+
uncertainty = data.uncertainty.copy()
21+
else:
22+
uncertainty = data.uncertainty
2323
else:
24-
uncertainty = numpy.zeros(self.shape, dtype)
24+
uncertainty = numpy.zeros(ret.shape, dtype)
2525
elif not isinstance(uncertainty, numpy.ndarray):
2626
uncertainty = numpy.array(uncertainty, dtype)
2727
try:
28-
assert uncertainty.shape == self.shape
28+
assert uncertainty.shape == ret.shape
2929
except AssertionError:
3030
raise ValueError('data and uncertainty must have identical shape')
31-
self.uncertainty = uncertainty
31+
ret.uncertainty = uncertainty
32+
33+
return ret
3234

3335
@property
3436
def simplified(self):
@@ -80,40 +82,59 @@ def __array_finalize__(self, obj):
8082
def __add__(self, other):
8183
res = Quantity.__add__(self, other)
8284
u = (self.uncertainty**2+other.uncertainty**2)**0.5
83-
# TODO: use .view:
8485
return UncertainQuantity(res, uncertainty=u, copy=False)
8586

87+
def __radd__(self, other):
88+
return self.__add__(other)
89+
8690
def __sub__(self, other):
8791
res = Quantity.__sub__(self, other)
8892
u = (self.uncertainty**2+other.uncertainty**2)**0.5
89-
# TODO: use .view:
9093
return UncertainQuantity(res, uncertainty=u, copy=False)
9194

95+
def __rsub__(self, other):
96+
if not isinstance(other, UncertainQuantity):
97+
other = UncertainQuantity(other, copy=False)
98+
99+
return UncertainQuantity.__sub__(other, self)
100+
92101
def __mul__(self, other):
93-
res = Quantity.__mul__(self, other)
102+
res = super(UncertainQuantity, self).__mul__(other)
94103
try:
95104
sru = self.relative_uncertainty
96105
oru = other.relative_uncertainty
97106
ru = (sru**2+oru**2)**0.5
98-
u = res * ru
107+
u = res.view(Quantity) * ru
99108
except AttributeError:
100109
other = numpy.array(other, copy=False)
101110
u = (self.uncertainty**2*other**2)**0.5
102-
# TODO: use .view:
103-
return UncertainQuantity(res, uncertainty=u, copy=False)
111+
112+
res._uncertainty = u
113+
return res
114+
115+
def __rmul__(self, other):
116+
return self.__mul__(other)
104117

105118
def __truediv__(self, other):
106-
res = Quantity.__truediv__(self, other)
119+
res = super(UncertainQuantity, self).__truediv__(other)
107120
try:
108121
sru = self.relative_uncertainty
109122
oru = other.relative_uncertainty
110123
ru = (sru**2+oru**2)**0.5
111-
u = res * ru
124+
u = res.view(Quantity) * ru
112125
except AttributeError:
113126
other = numpy.array(other, copy=False)
114127
u = (self.uncertainty**2/other**2)**0.5
115-
# TODO: use .view:
116-
return UncertainQuantity(res, uncertainty=u, copy=False)
128+
129+
res._uncertainty = u
130+
return res
131+
132+
def __rtruediv__(self, other):
133+
temp = UncertainQuantity(
134+
1/self.magnitude, self.dimensionality**-1,
135+
1/self.uncertainty.magnitude, copy=False
136+
)
137+
return other * temp
117138

118139
def __pow__(self, other):
119140
res = Quantity.__pow__(self, other)

0 commit comments

Comments
 (0)