quantities.Quantity
is a subclass of numpy.ndarray
that handles arithmetic and conversions of physical quantities. How can I make use of it's arithmetics without subclassing it? The following approach uses an __array__
-method -- but just works 80%, as you can see in the end:
class Numeric(object):
def __init__(self, signal):
self.signal = signal
self._dimensionality = self.signal._dimensionality
self.dimensionality = self.signal.dimensionality
def __array__(self):
return self.signal
def __mul__(self, obj):
return self.signal.__mul__(obj)
def __rmul__(self, obj):
return self.signal.__rmul__(obj)
With this I can do:
import quantities as pq
import numpy as np
num = Numeric(pq.Quantity([1,2,3], 'mV'))
q = pq.Quantity([2,3,4], 'mV')
n = np.array([3,4,5])
All of the following operations return the correct unit -- except the last, there the unit is missing:
print num * num
# [1 4 9] mV**2
print num * q
# [ 2 6 12] mV**2
print num * n
# [ 3 8 15] mV
print q * num
# [ 2 6 12] mV**2
print n * num
# [ 3 8 15] <------- no unit!
Any idea, what to fix in order to keep the correct unit?
edit: The return type/value of an arithmentic opperation should be equivalent to:
num.signal * num.signal
num.signal * q
num.signal * n
q * num.signal
n * num.signal # this doesn't work
When Python sees x * y
here's what happens:
y
is a subclass of x
--> y.__rmul__(x)
is calledotherwise:
x.__mul__(y)
is calledIF x.__mul__(y)
returns NotImplemented
(which is different from raise NotImplementedError
y.__rmul__(x)
is calledSo, there are two ways that __rmul__
can be called -- subclass ndarray
, or have ndarray
not be able to multiply with Numeric
.
You are unable to subclass, and apparently ndarray
is happy to work with Numeric
, so . . .
Thankfully, the numpy
folks prepared for situations such as this -- the answer lies in the __array_wrap__
method:
def __array_wrap__(self, out_arr, context=None):
return type(self.signal)(out_arr, self.dimensionality)
We are using the original signal
class, along with the original dimensionality, to create a new signal for the new Numeric
object.
The entire bit looks like this:
import quantities as pq
import numpy as np
class Numeric(object):
def __init__(self, signal):
self.signal = signal
self.dimensionality = self.signal.dimensionality
self._dimensionality = self.signal._dimensionality
def __array__(self):
return self.signal
def __array_wrap__(self, out_arr, context=None):
return type(self.signal)(out_arr, self.dimensionality)
def __mul__(self, obj):
return self.signal.__mul__(obj)
def __rmul__(self, obj):
return self.signal.__rmul__(obj)
num = Numeric(pq.Quantity([1,2,3], 'mV'))
q = pq.Quantity([2,3,4], 'mV')
n = np.array([3,4,5])
t = num * num
print type(t), t
t = num * q
print type(t), t
t = num * n
print type(t), t
t = q * num
print type(t), t
t = n * num
print type(t), t
And when run:
<class 'quantities.quantity.Quantity'> [1 4 9] mV**2
<class 'quantities.quantity.Quantity'> [ 2 6 12] mV**2
<class 'quantities.quantity.Quantity'> [ 3 8 15] mV
<class 'quantities.quantity.Quantity'> [ 2 6 12] mV**2
<class 'quantities.quantity.Quantity'> [ 3 8 15] mV
You need to definine __array_wrap__
. See the documentation here.
As a quick example using your example (but not requiring quantities
):
class Numeric(object):
def __init__(self, signal):
self.signal = signal
def __array__(self):
return self.signal
def __mul__(self, obj):
return type(self)(self.signal.__mul__(obj))
def __rmul__(self, obj):
return type(self)(self.signal.__rmul__(obj))
import numpy as np
num = Numeric(np.arange(10))
n = np.arange(10)
print type(num * n)
print type(n * num)
This yields:
<class '__main__.Numeric'>
<type 'numpy.ndarray'>
If we include __array_wrap__
:
class Numeric(object):
def __init__(self, signal):
self.signal = signal
def __array__(self):
return self.signal
def __mul__(self, obj):
return type(self)(self.signal.__mul__(obj))
def __rmul__(self, obj):
return type(self)(self.signal.__rmul__(obj))
def __array_wrap__(self, out_arr, context=None):
return type(self)(out_arr)
import numpy as np
num = Numeric(np.arange(10))
n = np.arange(10)
print type(num * n)
print type(n * num)
It yields:
<class '__main__.Numeric'>
<class '__main__.Numeric'>
However, I'm still confused as to why you can't just subclass ndarray
in the first place... I suspect it would be a lot cleaner in the long run. If you can't, you can't, though.
To fully mimic an ndarray
without subclassing ndarray
, you're going to need to get very familiar with the details of subclassing them.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With