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.signalnum.signal * qnum.signal * nq * num.signaln * num.signal # this doesn't workWhen 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