kotlinsvdnd4j

Runtime Error From ND4J When Executing SVD


I'm playing with ND4J basics to come up to speed with its linear algebra capabilities.

I'm running on a Macbook Pro using nd4j-api and nd4j-native dependencies version 1.0.0-M2.1, Open JDK version 17, Kotlin 1.7.20, and IntelliJ 2022.2.2 Ultimate Edition.

I'm writing JUnit 5 tests to perform simple operations: add, subtract, multiply, and divide a 2x2 matrix and a scalar; matrix addition, subtraction, multiplication; LU and QR decompositions. All are successful and pass just fine.

I had a problem when I ran this JUnit test for SVD:

@Test
fun `singular value decomposition`() {
    // setup
    // https://stackoverflow.com/questions/19763698/solving-non-square-linear-system-with-r/19767525#19767525
    val a = Nd4j.create(doubleArrayOf(
       0.0, 1.0, -2.0, 3.0,
       5.0, -3.0, 1.0, -2.0,
       5.0, -2.0, -1.0, 1.0
    ), intArrayOf(3, 4))
    val b = Nd4j.create(doubleArrayOf(-17.0, 28.0, 11.0), intArrayOf(3, 1))
    val u = Nd4j.create(doubleArrayOf(
        -0.1295469, -0.8061540,  0.5773503,
        0.7629233,  0.2908861,  0.5773503,
        0.6333764, -0.5152679, -0.5773503
    ), intArrayOf(3, 3))
    val v = Nd4j.create(doubleArrayOf(
         0.87191556, -0.2515803, -0.1764323,
        -0.46022634, -0.1453716, -0.4694190,
         0.04853711,  0.5423235,  0.6394484,
        -0.15999723, -0.7883272,  0.5827720
    ), intArrayOf(3, 4))
    val d = Nd4j.create(doubleArrayOf(
        8.007081e+00, 4.459446e+00, 4.022656e-16
    ), intArrayOf(3))
    // exercise
    val actual = NDLinalg().svd(a, true, true)
    // assert
    // Temporary assertion; I'll replace this after I see what SVD returns to me.
    Assertions.assertTrue(true)
}

I was surprised to see this error:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

org.nd4j.linalg.exception.ND4JIllegalStateException: Op name svd - no output arrays were provided and calculateOutputShape failed to execute

    at org.nd4j.linalg.cpu.nativecpu.ops.NativeOpExecutioner.exec(NativeOpExecutioner.java:1513)
    at org.nd4j.linalg.factory.Nd4j.exec(Nd4j.java:6545)
    at org.nd4j.linalg.factory.ops.NDLinalg.svd(NDLinalg.java:309)
    at ie.duffymo.fea.MatrixTest.singular value decomposition(MatrixTest.kt:353)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)


More in the stack trace.    

Process finished with exit code 255

I don't know how to address the admonition that I have not provided output arrays. I've provided what the method signature calls for.

Any advice on how to fix this?

The sample solution I'm referring to was created using R for another SO question.


Solution

  • This turned out to be an initialization bug. You can find the fix here: https://github.com/deeplearning4j/deeplearning4j/pull/9823

    Our functions basically have 2 phases: calculating an output shape for the output result and the actual execution of the op.

    It was getting stuck in the first part with the arguments it was expecting. In this case it was an int argument being expected but not everything was being passed in underneath due to the way the constructor was initialized.

    In the future you can do:

            Nd4j.getExecutioner().enableDebugMode(true);
            Nd4j.getExecutioner().enableVerboseMode(true);
    
            var a = Nd4j.create(new double[]{
                    0.0, 1.0, -2.0, 3.0,
                    5.0, -3.0, 1.0, -2.0,
                    5.0, -2.0, -1.0, 1.0
            }, new int[]{3, 4});
            var b = Nd4j.create(new double[]{-17.0, 28.0, 11.0}, new int[]{3, 1});
            var u = Nd4j.create(new double[]{
                    -0.1295469, -0.8061540, 0.5773503,
                    0.7629233, 0.2908861, 0.5773503,
                    0.6333764, -0.5152679, -0.5773503
            }, new int[]{3, 3});
            var v = Nd4j.create(new double[]{
                    0.87191556, -0.2515803, -0.1764323,
                    -0.46022634, -0.1453716, -0.4694190,
                    0.04853711, 0.5423235, 0.6394484,
                    -0.15999723, -0.7883272, 0.5827720
            }, new int[]{3, 4});
            var d = Nd4j.create(new double[]{
                    8.007081e+00, 4.459446e+00, 4.022656e-16
            }, new int[]{3});
            // exercise
            var actual = DynamicCustomOp.builder("svd")
                    .addInputs(a)
                    .addIntegerArguments(1,1, Svd.DEFAULT_SWITCHNUM)
                    .build();
            Nd4j.linalg().svd(a,true,true);
    

    Note how I just used Nd4j.linalg() instead there.

    For a workaround do:

      var actual = DynamicCustomOp.builder("svd")
                    .addInputs(a)
                    .addIntegerArguments(1,1, Svd.DEFAULT_SWITCHNUM)
                    .build();
            Nd4j.getExecutioner().exec(actual);