How to create an array-like class compatible with NumPy ufuncs?

I am trying to implement Automatic Differentiation using a class that behaves like a NumPy array. This is not a subclass of numpy.ndarray , but contains two array attributes. One for value and one for Jacobi matrix. Each operation is overloaded for work, both by value and by the Jacobian. However, I am having problems with NumPy ufuncs (like np.log ) working with my custom "array".

I created the following minimal example to illustrate the problem. Two supposed to be a radiation-hardened version of the NumPy array, which computes everything twice and ensures that the results are equal.

This should be support for indexing, the logarithm of the elements and length. Like a regular ndarray . The elementary logarithm works fine when called using x.cos() , but does something unexpected when called using np.cos(x) .

 from __future__ import print_function import numpy as np class Two(object): def __init__(self, val1, val2): print("init with", val1, val2) assert np.array_equal(val1, val2) self.val1 = val1 self.val2 = val2 def __getitem__(self, s): print("getitem", s, "got", Two(self.val1[s], self.val2[s])) return Two(self.val1[s], self.val2[s]) def __repr__(self): return "<<{}, {}>>".format(self.val1, self.val2) def log(self): print("log", self) return Two(np.log(self.val1), np.log(self.val2)) def __len__(self): print("len", self, "=", self.val1.shape[0]) return self.val1.shape[0] x = Two(np.array([1,2]).T, np.array([1,2]).T) 

Indexing returns the corresponding elements from both attributes, as expected:

 >>> print("First element in x:", x[0], "\n") init with [1 2] [1 2] init with 1 1 getitem 0 got <<1, 1>> init with 1 1 First element in x: <<1, 1>> 

The elemental logarithm works fine when called with x.cos() :

 >>> print("--- x.log() ---", x.log(), "\n") log <<[1 2], [1 2]>> init with [ 0. 0.69314] [ 0. 0.69314] --- x.log() --- <<[ 0. 0.69314], [ 0. 0.69314]>> 

However, np.log(x) does not work as expected. He understands that the object has a length, so he extracts each element and takes the logarithm on each of them, and then returns an array of two objects (dtype = object).

 >>> print("--- np.log(x) with len ---", np.log(x), "\n") # WTF len <<[1 2], [1 2]>> = 2 len <<[1 2], [1 2]>> = 2 init with 1 1 getitem 0 got <<1, 1>> init with 1 1 init with 2 2 getitem 1 got <<2, 2>> init with 2 2 len <<[1 2], [1 2]>> = 2 len <<[1 2], [1 2]>> = 2 init with 1 1 getitem 0 got <<1, 1>> init with 1 1 init with 2 2 getitem 1 got <<2, 2>> init with 2 2 len <<[1 2], [1 2]>> = 2 len <<[1 2], [1 2]>> = 2 init with 1 1 getitem 0 got <<1, 1>> init with 1 1 init with 2 2 getitem 1 got <<2, 2>> init with 2 2 log <<1, 1>> init with 0.0 0.0 log <<2, 2>> init with 0.693147 0.693147 --- np.log(x) with len --- [<<0.0, 0.0>> <<0.693147, 0.693147>>] 

If Two doesn't have a length method, it works fine:

 >>> del Two.__len__ >>> print("--- np.log(x) without len ---", np.log(x), "\n") log <<[1 2], [1 2]>> init with [ 0. 0.69314718] [ 0. 0.693147] --- np.log(x) without len --- <<[ 0. 0.693147], [ 0. 0.693147]>> 

How to create a class that meets the requirements (getitem, log, len)? I explored the subclassification of ndarray , but it turned out to be more complicated than worth it.

Also, I could not find the location in the NumPy source code accessed by x.__len__ , so I am also interested in this.

Edit: I am using miniconda2 with Python 2.7.11 and NumPy 1.11.0.

+5
source share

All Articles