Quansight-Labs / numpy.net

A port of NumPy to .Net
BSD 3-Clause "New" or "Revised" License
131 stars 14 forks source link

Tensordot - shape-mismatch for sum #9

Closed AsiaMartini closed 3 years ago

AsiaMartini commented 3 years ago

I'm trying to convert the python function einsum (commented line below) but I'm getting this error:

image

Am I passing the axis in the wrong way? In SciSharp/Numpy.NET I can do this without problems:

image

Could you please help me? Thanks.

KevinBaselinesw commented 3 years ago

Below is the tensordot function implementation. You can see the error being thrown. This algorithm was ported right from the real numpy code. Assuming I didn't make a mistake in porting, the logic is what python/numpy uses.

I suspect what is happening is that the code does not like your axes definition. I wonder if you can drop this code into your app and then step through it? It may lead to an idea of what is wrong.

If you want to send me a small code sample that I can plug into a unit test, I can try to debug it and provide some more information.

Also, have you tried to get your code to work in python/numpy?

  public static ndarray tensordot(object a, object b, (npy_intp[], npy_intp[]) axes)
    {

        npy_intp[] axes_a = axes.Item1;
        npy_intp[] axes_b = axes.Item2;

        int na = axes_a.Length;
        int nb = axes_b.Length;

        var aa = asanyarray(a);
        var bb = asanyarray(b);

        var as_ = aa.shape;
        var nda = aa.ndim;
        var bs = bb.shape;
        var ndb = bb.ndim;
        bool equal = true;

        if (na != nb)
        {
            equal = false;
        }
        else
        {
            for (int k = 0; k < na; k++)
            {
                long asindex = axes_a[k];
                asindex = asindex >= 0 ? asindex : asindex += as_.iDims.Length;

                long bsindex = axes_b[k];
                bsindex = bsindex >= 0 ? bsindex : bsindex += bs.iDims.Length;

                if (as_.iDims[asindex] != bs.iDims[bsindex])
                {
                    equal = false;
                    break;
                }
                if (axes_a[k] < 0)
                {
                    axes_a[k] += nda;
                }
                if (axes_b[k] < 0)
                {
                    axes_b[k] += ndb;
                }
            }
        }

        if (!equal)
        {
            throw new ValueError("shape-mismatch for sum");
        }

        /**********************************/

        List<int> notin = new List<int>();
        for (int k = 0; k < nda; k++)
        {
            if (!axes_a.Contains(k))
            {
                notin.Add(k);
            }
        }

        List<npy_intp> newaxes_a = new List<npy_intp>();
        foreach (var k in notin)
            newaxes_a.Add(k);
        foreach (var k in axes_a)
            newaxes_a.Add(k);

        npy_intp N2 = 1;
        foreach (var axis in axes_a)
        {
            N2 *= as_.iDims[axis];
        }

        List<npy_intp> asax = new List<npy_intp>();
        foreach (var ax in notin)
        {
            asax.Add(as_.iDims[ax]);
        }

        var multreduce = ufunc.reduce(UFuncOperation.multiply, asanyarray(asax.ToArray()));
        var newshape_a = new shape((npy_intp)multreduce.GetItem(0), N2);

        List<npy_intp> olda = new List<npy_intp>();
        foreach (var axis in notin)
        {
            olda.Add(as_.iDims[axis]);
        }

        /**********************************/

        notin = new List<int>();
        for (int k = 0; k < ndb; k++)
        {
            if (!axes_b.Contains(k))
            {
                notin.Add(k);
            }
        }
        List<npy_intp> newaxes_b = new List<npy_intp>();
        foreach (var k in axes_b)
            newaxes_b.Add(k);
        foreach (var k in notin)
            newaxes_b.Add(k);

        N2 = 1;
        foreach (var axis in axes_b)
        {
            N2 *= bs.iDims[axis];
        }

        List<npy_intp> bsax = new List<npy_intp>();
        foreach (var ax in notin)
        {
            bsax.Add(bs.iDims[ax]);
        }

        multreduce = ufunc.reduce(UFuncOperation.multiply, asanyarray(bsax.ToArray()));
        var newshape_b = new shape(N2, (npy_intp)multreduce.GetItem(0));

        List<npy_intp> oldb = new List<npy_intp>();
        foreach (var axis in notin)
        {
            oldb.Add(bs.iDims[axis]);
        }

        var at = aa.Transpose(newaxes_a.ToArray()).reshape(newshape_a);
        var bt = bb.Transpose(newaxes_b.ToArray()).reshape(newshape_b);
        var res = np.dot(at, bt);

        olda.AddRange(oldb);
        return res.reshape(olda);
    }
AsiaMartini commented 3 years ago

This is the library I'm working on:

https://github.com/gabriel-de-luca/simil

I already successfully converted it to C# using SciSharp/Numpy.NET, and I'm sure both are working. I need to use this library cause I cannot install python on my UWP app.

(The function where I get this error is "_get_abc_matrices")

KevinBaselinesw commented 3 years ago

1) Do you have a python version of _get_abc_matrices that uses np.tensordot? Then I can walk through the code and see what the axes should look like to match the python code.

2) Can you provide a more full code sample that shows how to create alpha_a, m1 and m2 that will reproduce the issue?

AsiaMartini commented 3 years ago

You can simply try this (variables values are correct, I compared them with those outputted by SciSharp/Numpy.NET):

var matrix = np.tensordot(alpha_0, temp, axes: (new long[] { 0 }, new long[] { 0 }));

alpha_0: {DOUBLE { 1.0, 1.0, 1.0 }}

temp: {DOUBLE { { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 } }, { { -86205769.32795963, -29995300.47464245, -82754041.41039754, -13001825.495725047 }, { -29995300.47464245, 82578955.42768276, -12272658.390253287, 86480121.86571775 }, { -82754041.41039754, -12272658.390253287, 91287177.6285136, -4029049.375114035 }, { -13001825.495725047, 86480121.86571775, -4029049.375114035, -87660363.72823673 } } }}

Just in case it's useful, this is the result given by the other library: {[[-1.31752684e+08 7.99501240e+07 -1.05062192e+08 9.83811340e+07] [ 7.99501240e+07 1.27901832e+08 9.91553420e+07 1.09018440e+08] [-1.05062192e+08 9.91553420e+07 9.20378248e+07 -1.22990888e+08] [ 9.83811340e+07 1.09018440e+08 -1.22990888e+08 -8.81869712e+07]]}

KevinBaselinesw commented 3 years ago

Here is a python and .net unit test. They both throw the same error.

The root cause is: alpha_0 is a single dim array of 3 (shape = (3,)) temp has a shape of (2,4,4).

The algorithm requires the axes to multiply have the same size. Your samples do not have the same size. "temp" has no axes of length 3.

If numpy.net "works", that might actually be the bug because it shouldn't.

Have you verified the output of the numpy.net against the python calls to np.einsum?

def test_tensordot_asiamartini(self):

    alpha_0 = np.arange(0,3,  dtype=np.float64)
    temp = np.arange(0, 32, dtype=np.float64).reshape(2,4,4)

    matrix = np.tensordot(alpha_0, temp, axes=([0],[0]))
    print(matrix)
    return

[TestMethod] public void test_tensordot_asiamartini() { var alpha_0 = np.array(new double[] { 1.0, 1.0, 1.0 }); var temp = np.array(new double[,,] { { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 } }, { { -86205769.32795963, -29995300.47464245, -82754041.41039754, -13001825.495725047 }, { -29995300.47464245, 82578955.42768276, -12272658.390253287, 86480121.86571775 }, { -82754041.41039754, -12272658.390253287, 91287177.6285136, -4029049.375114035 }, { -13001825.495725047, 86480121.86571775, -4029049.375114035, -87660363.72823673 } } } );

        var matrix = np.tensordot(alpha_0, temp, axes: (new long[] { 0 }, new long[] { 0 }));

        print(matrix);
    }
AsiaMartini commented 3 years ago

Have you verified the output of the numpy.net against the python calls to np.einsum? Yes, they both return the same values!

Could you please help me translate this in another (right) way? Thank you so much for your time.

KevinBaselinesw commented 3 years ago

This is a unit test from Numpy.net. It throws the same exception.

Please double check the input alpha_0 and temp arrays. There must be some difference between what you sent me and what is really happening.

[TestMethod] public void test_tensordot_asiamartini() { NDarray alpha_0 = np.array(new double[] { 1.0, 1.0, 1.0 }); NDarray temp = np.array(new double[,,] { { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 } }, { { -86205769.32795963, -29995300.47464245, -82754041.41039754, -13001825.495725047 }, { -29995300.47464245, 82578955.42768276, -12272658.390253287, 86480121.86571775 }, { -82754041.41039754, -12272658.390253287, 91287177.6285136, -4029049.375114035 }, { -13001825.495725047, 86480121.86571775, -4029049.375114035, -87660363.72823673 } } });

        var matrix = np.tensordot(alpha_0, temp, axes: (new [] { 0,0 }));

        //print(matrix);
    }
KevinBaselinesw commented 3 years ago

Please breakdown the working numpy.dot code into simpler form and print the arrays so we can see each step.

AsiaMartini commented 3 years ago

Sorry, you're right. The value I wrote of "temp" was wrong. This is the real one (just tested with numpy.net):

{DOUBLE { { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 } }, { { -18.0, 12.0, 12.0, -10.0 }, { 12.0, 10.0, 18.0, 12.0 }, { 12.0, 18.0, -10.0, -12.0 }, { -10.0, 12.0, -12.0, 18.0 } }, { { -3.5, 21.5, 5.5, -2.5 }, { 21.5, 2.5, 5.5, 3.5 }, { 5.5, 5.5, -20.5, -5.5 }, { -2.5, 3.5, -5.5, 21.5 } } }}

the error is now different: image


This is the function executed with Numpy.NET:

image

AsiaMartini commented 3 years ago

Here you can find the working code with Numpy.NET.

https://github.com/AsiaMartini/simil-dotnet

See the test() function in Simil.cs. Output values are listed in the README file.

KevinBaselinesw commented 3 years ago

You discovered an edge case bug in my library. Please get the latest code 9.71 for the bug fix. This unit test now produces the correct results.

    [TestMethod]
    public void test_tensordot_asiamartini_bugreport()
    {
        var alpha_0 = np.array(new double[] { 1.0, 1.0, 1.0 });
        var temp = np.array(new double[,,] { { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0 } }, 
                                             { { -18.0, 12.0, 12.0, -10.0 }, { 12.0, 10.0, 18.0, 12.0 }, { 12.0, 18.0, -10.0, -12.0 }, { -10.0, 12.0, -12.0, 18.0 } }, 
                                             { { -3.5, 21.5, 5.5, -2.5 }, { 21.5, 2.5, 5.5, 3.5 }, { 5.5, 5.5, -20.5, -5.5 }, { -2.5, 3.5, -5.5, 21.5 } }
                                           });

        var matrix = np.tensordot(alpha_0, temp, axes: (new long[] { 0 }, new long[] { 0 }));

        AssertArray(matrix, new double[,] { { -21.5, 33.5, 17.5, -12.5 }, { 33.5, 12.5, 23.5, 15.5 }, { 17.5, 23.5, -30.5, -17.5 }, { -12.5, 15.5, -17.5, 39.5 } });

        print(matrix);
    }
AsiaMartini commented 3 years ago

Thanks! It works perfectly! (I need you help with another function but I'll open another issue :D)