From 40aa43e224760ec3da469916e080e5b08a984682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Komorowicz?= Date: Sat, 20 Apr 2019 10:04:41 +0200 Subject: [PATCH 1/2] Quick fix tensordot based on https://github.com/mattloper/chumpy/pull/25 This bug arose in numpy ~1.14 --- chumpy/ch.py | 10 +- chumpy/np_tensordot.py | 205 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 chumpy/np_tensordot.py diff --git a/chumpy/ch.py b/chumpy/ch.py index d073b3d..0163c41 100755 --- a/chumpy/ch.py +++ b/chumpy/ch.py @@ -2544,10 +2544,12 @@ def nonzero(a): a = a.r return np.nonzero(a) -try: - import inspect - exec(''.join(inspect.getsourcelines(np.tensordot)[0])) -except: pass +# Pull the code for tensordot in from numpy and reinterpret it using chumpy ops +import os +source_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'np_tensordot.py') +with open(source_path, 'r') as f: + source_lines = f.readlines() +exec(''.join(source_lines)) class tst(Ch): diff --git a/chumpy/np_tensordot.py b/chumpy/np_tensordot.py new file mode 100644 index 0000000..6254236 --- /dev/null +++ b/chumpy/np_tensordot.py @@ -0,0 +1,205 @@ +# Up to numpy 1.13, the numpy implementation of tensordot could be +# reinterpreted using chumpy. With numpy 1.14 the implementation started using +# ufunc.multiply.reduce which can't be understood by chumpy. This is the +# chumpy-compatible implementation of tensodrot from numpy 1.13.3. +# +# i.e. +# +# import inspect +# with open('np_tensordot.py', 'w') as f: +# f.write(''.join(inspect.getsourcelines(np.tensordot)[0])) + +""" +Copyright (c) 2005-2017, NumPy Developers. +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of the NumPy Developers nor the names of any + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + + +def tensordot(a, b, axes=2): + """ + Compute tensor dot product along specified axes for arrays >= 1-D. + Given two tensors (arrays of dimension greater than or equal to one), + `a` and `b`, and an array_like object containing two array_like + objects, ``(a_axes, b_axes)``, sum the products of `a`'s and `b`'s + elements (components) over the axes specified by ``a_axes`` and + ``b_axes``. The third argument can be a single non-negative + integer_like scalar, ``N``; if it is such, then the last ``N`` + dimensions of `a` and the first ``N`` dimensions of `b` are summed + over. + Parameters + ---------- + a, b : array_like, len(shape) >= 1 + Tensors to "dot". + axes : int or (2,) array_like + * integer_like + If an int N, sum over the last N axes of `a` and the first N axes + of `b` in order. The sizes of the corresponding axes must match. + * (2,) array_like + Or, a list of axes to be summed over, first sequence applying to `a`, + second to `b`. Both elements array_like must be of the same length. + See Also + -------- + dot, einsum + Notes + ----- + Three common use cases are: + * ``axes = 0`` : tensor product :math:`a\\otimes b` + * ``axes = 1`` : tensor dot product :math:`a\\cdot b` + * ``axes = 2`` : (default) tensor double contraction :math:`a:b` + When `axes` is integer_like, the sequence for evaluation will be: first + the -Nth axis in `a` and 0th axis in `b`, and the -1th axis in `a` and + Nth axis in `b` last. + When there is more than one axis to sum over - and they are not the last + (first) axes of `a` (`b`) - the argument `axes` should consist of + two sequences of the same length, with the first axis to sum over given + first in both sequences, the second axis second, and so forth. + Examples + -------- + A "traditional" example: + >>> a = np.arange(60.).reshape(3,4,5) + >>> b = np.arange(24.).reshape(4,3,2) + >>> c = np.tensordot(a,b, axes=([1,0],[0,1])) + >>> c.shape + (5, 2) + >>> c + array([[ 4400., 4730.], + [ 4532., 4874.], + [ 4664., 5018.], + [ 4796., 5162.], + [ 4928., 5306.]]) + >>> # A slower but equivalent way of computing the same... + >>> d = np.zeros((5,2)) + >>> for i in range(5): + ... for j in range(2): + ... for k in range(3): + ... for n in range(4): + ... d[i,j] += a[k,n,i] * b[n,k,j] + >>> c == d + array([[ True, True], + [ True, True], + [ True, True], + [ True, True], + [ True, True]], dtype=bool) + An extended example taking advantage of the overloading of + and \\*: + >>> a = np.array(range(1, 9)) + >>> a.shape = (2, 2, 2) + >>> A = np.array(('a', 'b', 'c', 'd'), dtype=object) + >>> A.shape = (2, 2) + >>> a; A + array([[[1, 2], + [3, 4]], + [[5, 6], + [7, 8]]]) + array([[a, b], + [c, d]], dtype=object) + >>> np.tensordot(a, A) # third argument default is 2 for double-contraction + array([abbcccdddd, aaaaabbbbbbcccccccdddddddd], dtype=object) + >>> np.tensordot(a, A, 1) + array([[[acc, bdd], + [aaacccc, bbbdddd]], + [[aaaaacccccc, bbbbbdddddd], + [aaaaaaacccccccc, bbbbbbbdddddddd]]], dtype=object) + >>> np.tensordot(a, A, 0) # tensor product (result too long to incl.) + array([[[[[a, b], + [c, d]], + ... + >>> np.tensordot(a, A, (0, 1)) + array([[[abbbbb, cddddd], + [aabbbbbb, ccdddddd]], + [[aaabbbbbbb, cccddddddd], + [aaaabbbbbbbb, ccccdddddddd]]], dtype=object) + >>> np.tensordot(a, A, (2, 1)) + array([[[abb, cdd], + [aaabbbb, cccdddd]], + [[aaaaabbbbbb, cccccdddddd], + [aaaaaaabbbbbbbb, cccccccdddddddd]]], dtype=object) + >>> np.tensordot(a, A, ((0, 1), (0, 1))) + array([abbbcccccddddddd, aabbbbccccccdddddddd], dtype=object) + >>> np.tensordot(a, A, ((2, 1), (1, 0))) + array([acccbbdddd, aaaaacccccccbbbbbbdddddddd], dtype=object) + """ + try: + iter(axes) + except: + axes_a = list(range(-axes, 0)) + axes_b = list(range(0, axes)) + else: + axes_a, axes_b = axes + try: + na = len(axes_a) + axes_a = list(axes_a) + except TypeError: + axes_a = [axes_a] + na = 1 + try: + nb = len(axes_b) + axes_b = list(axes_b) + except TypeError: + axes_b = [axes_b] + nb = 1 + + a, b = asarray(a), asarray(b) + as_ = a.shape + nda = a.ndim + bs = b.shape + ndb = b.ndim + equal = True + if na != nb: + equal = False + else: + for k in range(na): + if as_[axes_a[k]] != bs[axes_b[k]]: + equal = False + break + if axes_a[k] < 0: + axes_a[k] += nda + if axes_b[k] < 0: + axes_b[k] += ndb + if not equal: + raise ValueError("shape-mismatch for sum") + + # Move the axes to sum over to the end of "a" + # and to the front of "b" + notin = [k for k in range(nda) if k not in axes_a] + newaxes_a = notin + axes_a + N2 = 1 + for axis in axes_a: + N2 *= as_[axis] + newshape_a = (-1, N2) + olda = [as_[axis] for axis in notin] + + notin = [k for k in range(ndb) if k not in axes_b] + newaxes_b = axes_b + notin + N2 = 1 + for axis in axes_b: + N2 *= bs[axis] + newshape_b = (N2, -1) + oldb = [bs[axis] for axis in notin] + + at = a.transpose(newaxes_a).reshape(newshape_a) + bt = b.transpose(newaxes_b).reshape(newshape_b) + res = dot(at, bt) + return res.reshape(olda + oldb) From 3a490f6f49f51deadc0de65ccf1b1993b831e5fe Mon Sep 17 00:00:00 2001 From: dawars Date: Sat, 11 May 2019 22:31:27 +0200 Subject: [PATCH 2/2] Fix support for updated NumPy operator --- chumpy/ch_ops.py | 11 +++++++---- chumpy/np_tensordot.py | 6 ++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/chumpy/ch_ops.py b/chumpy/ch_ops.py index 762e3eb..1f7c149 100755 --- a/chumpy/ch_ops.py +++ b/chumpy/ch_ops.py @@ -797,10 +797,13 @@ def nonzero(a): a = a.r return np.nonzero(a) -try: - import inspect - exec(''.join(inspect.getsourcelines(np.tensordot)[0])) -except: pass +# Pull the code for tensordot in from numpy and reinterpret it using chumpy ops +import os +source_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'np_tensordot.py') +with open(source_path, 'r') as f: + source_lines = f.readlines() +exec(''.join(source_lines)) +__all__ += ['tensordot'] diff --git a/chumpy/np_tensordot.py b/chumpy/np_tensordot.py index 6254236..c9047a5 100644 --- a/chumpy/np_tensordot.py +++ b/chumpy/np_tensordot.py @@ -141,6 +141,8 @@ def tensordot(a, b, axes=2): >>> np.tensordot(a, A, ((2, 1), (1, 0))) array([acccbbdddd, aaaaacccccccbbbbbbdddddddd], dtype=object) """ + + import numpy as np try: iter(axes) except: @@ -161,7 +163,7 @@ def tensordot(a, b, axes=2): axes_b = [axes_b] nb = 1 - a, b = asarray(a), asarray(b) + a, b = np.asarray(a), np.asarray(b) as_ = a.shape nda = a.ndim bs = b.shape @@ -201,5 +203,5 @@ def tensordot(a, b, axes=2): at = a.transpose(newaxes_a).reshape(newshape_a) bt = b.transpose(newaxes_b).reshape(newshape_b) - res = dot(at, bt) + res = np.dot(at, bt) return res.reshape(olda + oldb)