<NME> LongToNumberMapTest.java
<BEF> /*
 * Copyright 1997-2022 Optimatika
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.ojalgo.array;

import java.util.Map.Entry;
import java.util.Random;
import java.util.SortedMap;
import java.util.TreeMap;

import org.junit.jupiter.api.Test;
import org.ojalgo.TestUtils;
import org.ojalgo.function.constant.PrimitiveMath;
import org.ojalgo.function.special.PowerOf2;
import org.ojalgo.type.context.NumberContext;

 * LongToNumberMap
 *
 * @author apete
 */
public class LongToNumberMapTest extends ArrayTests {

    private static final Random RANDOM = new Random();

    @Test
    public void testAlignCapacity() {
        TestUtils.assertEquals(1, 1L << PowerOf2.powerOf2Larger(-1L));
        TestUtils.assertEquals(16, 1L << PowerOf2.powerOf2Larger(16L));
        TestUtils.assertEquals(512, 1L << PowerOf2.powerOf2Larger(365L));
        TestUtils.assertEquals(16_384, 1L << PowerOf2.powerOf2Larger(16_384L));
    }

    @Test
    public void testCompareWithTreeMap() {

        final LongToNumberMap<Double> primit64Map = LongToNumberMap.factory(ArrayR064.FACTORY).make();
        final LongToNumberMap<Double> direct64Map = LongToNumberMap.factory(BufferArray.DIRECT64).make();

        final SortedMap<Long, Double> expectedMap = new TreeMap<>();

        for (long index = 0L; index < 1_000; index++) {

            final Double value = RANDOM.nextDouble();

            primit64Map.put(index, value);
            direct64Map.put(index, value);
            expectedMap.put(index, value);
        }

        for (int c = 0; c < 1_000; c++) {

            final Long index = Long.valueOf(RANDOM.nextInt(1_000));
            final Double value = RANDOM.nextDouble();

            primit64Map.put(index, value);
            direct64Map.put(index, value);
            expectedMap.put(index, value);
        }

        for (int c = 0; c < 1_000; c++) {

            final Long index = Long.valueOf(RANDOM.nextInt(1_000));

            primit64Map.remove(index);
            direct64Map.remove(index);
            expectedMap.remove(index);
        }

        for (long index = 0L; index < 100; index++) {
            primit64Map.remove(index);
            direct64Map.remove(index);
            expectedMap.remove(index);
        }

        TestUtils.assertEquals(expectedMap.size(), primit64Map.size());
        TestUtils.assertEquals(expectedMap.size(), direct64Map.size());

        for (final Entry<Long, Double> entry : expectedMap.entrySet()) {
            final double expectedValue = entry.getValue().doubleValue();
            TestUtils.assertEquals(expectedValue, primit64Map.get(entry.getKey()).doubleValue());
            TestUtils.assertEquals(expectedValue, primit64Map.doubleValue(entry.getKey()));
            TestUtils.assertEquals(expectedValue, direct64Map.get(entry.getKey()).doubleValue());
            TestUtils.assertEquals(expectedValue, direct64Map.doubleValue(entry.getKey()));
        }

    }

    @Test
    public void testSubmap() {

        final LongToNumberMap<Double> tmpMap = LongToNumberMap.factory(ArrayR064.FACTORY).make();

        tmpMap.put(10, 1);
        tmpMap.put(20, 2);
        tmpMap.put(50, 5);
        tmpMap.put(100, 1);
        tmpMap.put(200, 2);
        tmpMap.put(500, 5);
        tmpMap.put(1000, 1);

        final LongToNumberMap<Double> tmpHeadMap = tmpMap.headMap(100L);
        TestUtils.assertEquals(3, tmpHeadMap.size());
        double tmpHeadSum = 0.0;
        for (final Entry<Long, Double> tmpEntry : tmpHeadMap.entrySet()) {
            tmpHeadSum += tmpEntry.getValue();
        }
        TestUtils.assertEquals(8.0, tmpHeadSum);

        final LongToNumberMap<Double> tmpTailMap = tmpMap.tailMap(100L);
        TestUtils.assertEquals(4, tmpTailMap.size());
        double tmpTailSum = 0.0;
        for (final Entry<Long, Double> tmpEntry : tmpTailMap.entrySet()) {
            tmpTailSum += tmpEntry.getValue();
        }
        TestUtils.assertEquals(9.0, tmpTailSum);

        final LongToNumberMap<Double> tmpSubMap = tmpMap.subMap(35, 350);
        TestUtils.assertEquals(3, tmpSubMap.size());
        double tmpSubSum = 0.0;
        for (final Entry<Long, Double> tmpEntry : tmpSubMap.entrySet()) {
            tmpSubSum += tmpEntry.getValue();
        }
        TestUtils.assertEquals(8.0, tmpSubSum);
        TestUtils.assertEquals(tmpSubMap.values(), tmpMap.values(35, 350));

        final LongToNumberMap<Double> tmpSubMap2 = tmpMap.subMap(50, 350);
        TestUtils.assertEquals(3, tmpSubMap2.size());
        double tmpSubSum2 = 0.0;
        for (final Entry<Long, Double> tmpEntry : tmpSubMap2.entrySet()) {
            tmpSubSum2 += tmpEntry.getValue();
        }
        TestUtils.assertEquals(8.0, tmpSubSum2);
        TestUtils.assertEquals(tmpSubMap.values(), tmpMap.values(50, 350));

        final LongToNumberMap<Double> tmpSubMap3 = tmpMap.subMap(35, 500);
        TestUtils.assertEquals(3, tmpSubMap3.size());
        double tmpSubSum3 = 0.0;
        for (final Entry<Long, Double> tmpEntry : tmpSubMap3.entrySet()) {
            tmpSubSum3 += tmpEntry.getValue();
        }
        TestUtils.assertEquals(8.0, tmpSubSum3);
        TestUtils.assertEquals(tmpSubMap.values(), tmpMap.values(35, 500));

        TestUtils.assertEquals(7, tmpMap.size());
        double tmpKeySum = 0.0;
        double tmpValSum = 0.0;
        for (final Entry<Long, Double> tmpEntry : tmpMap.entrySet()) {
            tmpKeySum += tmpEntry.getKey();
            tmpValSum += tmpEntry.getValue();
        }
        TestUtils.assertEquals(1880.0, tmpKeySum);
        TestUtils.assertEquals(17.0, tmpValSum);

        tmpKeySum = 0.0;
        for (final Long tmpKey : tmpMap.keySet()) {
            tmpKeySum += tmpKey;
        }
        TestUtils.assertEquals(1880.0, tmpKeySum);

        tmpValSum = 0.0;
        for (final Double tmpValue : tmpMap.values()) {
            tmpValSum += tmpValue;
        }
        TestUtils.assertEquals(17.0, tmpValSum);

    }

}

<MSG> ANN batches and other improvements (#359)


<DFF> @@ -28,7 +28,6 @@ import java.util.TreeMap;
 
 import org.junit.jupiter.api.Test;
 import org.ojalgo.TestUtils;
-import org.ojalgo.function.constant.PrimitiveMath;
 import org.ojalgo.function.special.PowerOf2;
 import org.ojalgo.type.context.NumberContext;
 

{'commit': 'diff --git a/CHANGELOG.md b/CHANGELOG.md\nindex 17a48657..2acd3166 100644\n--- a/CHANGELOG.md\n+++ b/CHANGELOG.md\n@@ -15,6 +15,10 @@ Added / Changed / Deprecated / Fixed / Removed / Security\n \n ### Added\n \n+#### org.ojalgo.ann\n+\n+- Now possible to train and invoke/evaluate neural networks in batches.\n+\n #### org.ojalgo.array\n \n - `Array2D` and `ArrayAnyD` are now reshapable\n@@ -24,6 +28,10 @@ Added / Changed / Deprecated / Fixed / Removed / Security\n - Additions to `DaemonPoolExecutor`: A `ThreadFactory` factory method, as well as a set of `ExecutorService` factory methods that makes use of that.\n - New utility `ProcessingService` standardise/simplify some `ExecutorService` usage.\n \n+#### org.ojalgo.data\n+\n+- New `DataBatch` class. It\'s a resuable component to help collect 1D data in a 2D structure. Can be used with neural networks (and other things) to batch data.\n+\n #### org.ojalgo.matrix\n \n - New interface `Matrix2D` common to both `BasicMatrix` (implements it) and `MatrixStore` (extends it).\n@@ -45,6 +53,7 @@ Added / Changed / Deprecated / Fixed / Removed / Security\n #### org.ojalgo.ann\n \n - Internal refactoring to `ArtificialNeuralNetwork` primarily to improve performance.\n+- The activator RECTIFIER has been deprecated/renamed RELU which is more in line with what users expect.\n \n #### org.ojalgo.concurrent\n \ndiff --git a/src/main/java/org/ojalgo/ann/ArtificialNeuralNetwork.java b/src/main/java/org/ojalgo/ann/ArtificialNeuralNetwork.java\nindex 161f6ccd..aa9d28a4 100644\n--- a/src/main/java/org/ojalgo/ann/ArtificialNeuralNetwork.java\n+++ b/src/main/java/org/ojalgo/ann/ArtificialNeuralNetwork.java\n@@ -32,6 +32,7 @@ import java.util.Arrays;\n import java.util.List;\n import java.util.function.Consumer;\n \n+import org.ojalgo.data.DataBatch;\n import org.ojalgo.function.BasicFunction;\n import org.ojalgo.function.PrimitiveFunction;\n import org.ojalgo.function.aggregator.Aggregator;\n@@ -59,8 +60,14 @@ public final class ArtificialNeuralNetwork implements BasicFunction.PlainUnary<A\n         IDENTITY(ArtificialNeuralNetwork::doIdentity, arg -> ONE, true),\n         /**\n          * ReLU: [0,+)\n+         *\n+         * @deprecated v49 Use {@link #RELU} instead\n          */\n         RECTIFIER(ArtificialNeuralNetwork::doReLU, arg -> arg > ZERO ? ONE : ZERO, true),\n+        /**\n+         * ReLU: [0,+)\n+         */\n+        RELU(ArtificialNeuralNetwork::doReLU, arg -> arg > ZERO ? ONE : ZERO, true),\n         /**\n          * [0,1]\n          */\n@@ -72,7 +79,6 @@ public final class ArtificialNeuralNetwork implements BasicFunction.PlainUnary<A\n          * training.\n          */\n         SOFTMAX(ArtificialNeuralNetwork::doSoftMax, arg -> ONE, false),\n-\n         /**\n          * [-1,1]\n          */\n@@ -243,8 +249,8 @@ public final class ArtificialNeuralNetwork implements BasicFunction.PlainUnary<A\n \n     static void doSoftMax(final PhysicalStore<Double> output) {\n         output.modifyAll(EXP);\n-        double total = output.aggregateAll(Aggregator.SUM).doubleValue();\n-        output.modifyAll(DIVIDE.by(total));\n+        Primitive64Store totals = output.reduceRows(Aggregator.SUM).collect(Primitive64Store.FACTORY);\n+        output.onRows(DIVIDE, totals).supplyTo(output);\n     }\n \n     static void doTanh(final PhysicalStore<Double> output) {\n@@ -334,16 +340,43 @@ public final class ArtificialNeuralNetwork implements BasicFunction.PlainUnary<A\n         return this.newInvoker().invoke(input);\n     }\n \n+    /**\n+     * With batch size 1\n+     *\n+     * @see #newInvoker(int)\n+     */\n+    public NetworkInvoker newInvoker() {\n+        return this.newInvoker(1);\n+    }\n+\n     /**\n      * If you create multiple invokers you can use them in different threads simutaneously - the invoker\n      * contains any/all invocation specific state.\n+     *\n+     * @param batchSize The batch size - the number of batched invocations\n+     * @return The invoker\n      */\n-    public NetworkInvoker newInvoker() {\n-        return new NetworkInvoker(this);\n+    public NetworkInvoker newInvoker(final int batchSize) {\n+        return new NetworkInvoker(this, batchSize);\n     }\n \n+    /**\n+     * With batch size 1\n+     *\n+     * @see #newTrainer(int)\n+     */\n     public NetworkTrainer newTrainer() {\n-        NetworkTrainer trainer = new NetworkTrainer(this);\n+        return this.newTrainer(1);\n+    }\n+\n+    /**\n+     * Only 1 trainer at the time.\n+     *\n+     * @param batchSize The batch size - the number of batched training examples\n+     * @return The trainer\n+     */\n+    public NetworkTrainer newTrainer(final int batchSize) {\n+        NetworkTrainer trainer = new NetworkTrainer(this, batchSize);\n         if (this.getOutputActivator() == Activator.SOFTMAX) {\n             trainer.error(Error.CROSS_ENTROPY);\n         } else {\n@@ -367,7 +400,11 @@ public final class ArtificialNeuralNetwork implements BasicFunction.PlainUnary<A\n     public String toString() {\n         StringBuilder tmpBuilder = new StringBuilder();\n         tmpBuilder.append("ArtificialNeuralNetwork [Layers=");\n-        tmpBuilder.append(Arrays.toString(myLayers));\n+        for (CalculationLayer calculationLayer : myLayers) {\n+            tmpBuilder.append("\\n");\n+            tmpBuilder.append(calculationLayer);\n+        }\n+        tmpBuilder.append("\\n");\n         tmpBuilder.append("]");\n         return tmpBuilder.toString();\n     }\n@@ -420,10 +457,18 @@ public final class ArtificialNeuralNetwork implements BasicFunction.PlainUnary<A\n                 myConfiguration.probabilityDidKeepInput(layer), myConfiguration.regularisation());\n     }\n \n+    int countInputNodes() {\n+        return myLayers[0].countInputNodes();\n+    }\n+\n     int countInputNodes(final int layer) {\n         return myLayers[layer].countInputNodes();\n     }\n \n+    int countOutputNodes() {\n+        return myLayers[myLayers.length - 1].countOutputNodes();\n+    }\n+\n     int countOutputNodes(final int layer) {\n         return myLayers[layer].countOutputNodes();\n     }\n@@ -447,6 +492,10 @@ public final class ArtificialNeuralNetwork implements BasicFunction.PlainUnary<A\n         return myLayers[layer].invoke(input, output);\n     }\n \n+    DataBatch newBatch(final int rows, final int columns) {\n+        return DataBatch.from(myFactory, rows, columns);\n+    }\n+\n     PhysicalStore<Double> newStore(final int rows, final int columns) {\n         return myFactory.make(rows, columns);\n     }\ndiff --git a/src/main/java/org/ojalgo/ann/CalculationLayer.java b/src/main/java/org/ojalgo/ann/CalculationLayer.java\nindex 7d03c815..e6bd2696 100644\n--- a/src/main/java/org/ojalgo/ann/CalculationLayer.java\n+++ b/src/main/java/org/ojalgo/ann/CalculationLayer.java\n@@ -25,6 +25,7 @@ import static org.ojalgo.function.constant.PrimitiveMath.*;\n \n import java.util.function.DoubleUnaryOperator;\n \n+import org.ojalgo.function.PrimitiveFunction;\n import org.ojalgo.matrix.store.MatrixStore;\n import org.ojalgo.matrix.store.PhysicalStore;\n import org.ojalgo.random.Uniform;\n@@ -102,7 +103,7 @@ final class CalculationLayer {\n     void adjust(final PhysicalStore<Double> input, final PhysicalStore<Double> output, final PhysicalStore<Double> upstreamGradient,\n             final PhysicalStore<Double> downstreamGradient, final double learningRate, final double dropoutsFactor, final DoubleUnaryOperator regularisation) {\n \n-        downstreamGradient.modifyMatching(MULTIPLY, output.onAll(myActivator.getDerivativeInTermsOfOutput()));\n+        downstreamGradient.modifyMatching(MULTIPLY, output.onAll(myActivator.getDerivativeInTermsOfOutput()).transpose());\n \n         if (upstreamGradient != null) {\n             // No need to do this multiplication for the input layer\n@@ -110,16 +111,22 @@ final class CalculationLayer {\n             myWeights.multiply(downstreamGradient, upstreamGradient);\n         }\n \n-        for (long j = 0L, numbOutput = myWeights.countColumns(); j < numbOutput; j++) {\n-            final double gradient = downstreamGradient.doubleValue(j);\n-            double ratedGradient = learningRate * gradient;\n-            for (long i = 0L, numbInput = myWeights.countRows(); i < numbInput; i++) {\n-                if (regularisation != null) {\n-                    myWeights.add(i, j, learningRate * regularisation.applyAsDouble(myWeights.doubleValue(i, j)));\n+        if (regularisation != null) {\n+            PrimitiveFunction.Unary modifier = arg -> arg + learningRate * regularisation.applyAsDouble(arg);\n+            myWeights.modifyAll(modifier);\n+        }\n+\n+        for (long j = 0L, nbOutput = myWeights.countColumns(); j < nbOutput; j++) {\n+            for (long b = 0L, batchSize = input.countRows(); b < batchSize; b++) {\n+\n+                double gradient = downstreamGradient.doubleValue(j, b);\n+                double ratedGradient = learningRate * gradient;\n+                myBias.add(j, ratedGradient);\n+\n+                for (long i = 0L, nbInput = myWeights.countRows(); i < nbInput; i++) {\n+                    myWeights.add(i, j, ratedGradient * (input.doubleValue(b, i) / dropoutsFactor));\n                 }\n-                myWeights.add(i, j, ratedGradient * (input.doubleValue(i) / dropoutsFactor));\n             }\n-            myBias.add(j, ratedGradient);\n         }\n     }\n \n@@ -152,13 +159,13 @@ final class CalculationLayer {\n     }\n \n     PhysicalStore<Double> invoke(final PhysicalStore<Double> input, final PhysicalStore<Double> output) {\n-        myWeights.premultiply(input).onMatching(ADD, myBias).supplyTo(output);\n+        myWeights.premultiply(input).onColumns(ADD, myBias).supplyTo(output);\n         myActivator.activate(output);\n         return output;\n     }\n \n     PhysicalStore<Double> invoke(final PhysicalStore<Double> input, final PhysicalStore<Double> output, final double probabilityToKeep) {\n-        myWeights.premultiply(input).onMatching(ADD, myBias).supplyTo(output);\n+        myWeights.premultiply(input).onColumns(ADD, myBias).supplyTo(output);\n         myActivator.activate(output, probabilityToKeep);\n         return output;\n     }\ndiff --git a/src/main/java/org/ojalgo/ann/NetworkInvoker.java b/src/main/java/org/ojalgo/ann/NetworkInvoker.java\nindex 164d6f62..9f52031f 100644\n--- a/src/main/java/org/ojalgo/ann/NetworkInvoker.java\n+++ b/src/main/java/org/ojalgo/ann/NetworkInvoker.java\n@@ -21,16 +21,26 @@\n  */\n package org.ojalgo.ann;\n \n+import org.ojalgo.data.DataBatch;\n import org.ojalgo.function.BasicFunction;\n import org.ojalgo.matrix.store.MatrixStore;\n import org.ojalgo.structure.Access1D;\n \n public class NetworkInvoker extends WrappedANN implements BasicFunction.PlainUnary<Access1D<Double>, MatrixStore<Double>> {\n \n-    NetworkInvoker(final ArtificialNeuralNetwork network) {\n-        super(network);\n+    NetworkInvoker(final ArtificialNeuralNetwork network, final int batchSize) {\n+        super(network, batchSize);\n     }\n \n+    /**\n+     * The input argument is typed as {@link Access1D} which essentially means it can be anything. If the\n+     * batch size is anything other than 1 this needs to be a 2D structure with the number of rows matching\n+     * the batch size. The return type is a {@link MatrixStore} where the number of rows match the batch size\n+     * and the number of columns match the number of output nodes. A {@link DataBatch} may help when creating\n+     * the batches or you simply create any 2D data structure and fill the rows.\n+     *\n+     * @see org.ojalgo.function.BasicFunction.PlainUnary#invoke(java.lang.Object)\n+     */\n     @Override\n     public MatrixStore<Double> invoke(final Access1D<Double> input) {\n         return super.invoke(input, null);\ndiff --git a/src/main/java/org/ojalgo/ann/NetworkTrainer.java b/src/main/java/org/ojalgo/ann/NetworkTrainer.java\nindex b27d557e..b1be5a53 100644\n--- a/src/main/java/org/ojalgo/ann/NetworkTrainer.java\n+++ b/src/main/java/org/ojalgo/ann/NetworkTrainer.java\n@@ -26,6 +26,8 @@ import java.util.Iterator;\n \n import org.ojalgo.ann.ArtificialNeuralNetwork.Activator;\n import org.ojalgo.ann.ArtificialNeuralNetwork.Error;\n+import org.ojalgo.data.DataBatch;\n+import org.ojalgo.matrix.store.MatrixStore;\n import org.ojalgo.matrix.store.PhysicalStore;\n import org.ojalgo.structure.Access1D;\n import org.ojalgo.structure.Access2D;\n@@ -41,15 +43,15 @@ public final class NetworkTrainer extends WrappedANN {\n     private final TrainingConfiguration myConfiguration = new TrainingConfiguration();\n     private final PhysicalStore<Double>[] myGradients;\n \n-    NetworkTrainer(final ArtificialNeuralNetwork network) {\n+    NetworkTrainer(final ArtificialNeuralNetwork network, final int batchSize) {\n \n-        super(network);\n+        super(network, batchSize);\n \n         int depth = network.depth();\n \n         myGradients = (PhysicalStore<Double>[]) new PhysicalStore<?>[depth];\n         for (int l = 0; l < depth; l++) {\n-            myGradients[l] = network.newStore(network.countOutputNodes(l), 1);\n+            myGradients[l] = network.newStore(network.countOutputNodes(l), batchSize);\n         }\n     }\n \n@@ -141,6 +143,14 @@ public final class NetworkTrainer extends WrappedANN {\n         return this;\n     }\n \n+    /**\n+     * @see NetworkTrainer#newInputBatch()\n+     */\n+    @Override\n+    public DataBatch newOutputBatch() {\n+        return super.newOutputBatch();\n+    }\n+\n     public NetworkTrainer rate(final double rate) {\n         myConfiguration.learningRate = rate;\n         return this;\n@@ -173,15 +183,23 @@ public final class NetworkTrainer extends WrappedANN {\n         return builder.toString();\n     }\n \n+    /**\n+     * The arguments are typed as {@link Access1D} but it\'s probably best to think of (create) them as\n+     * something 2D where the number of rows should match the batch size and the number of columns the number\n+     * of inputs and outputs respectively. When the batch size is 1 then the arguments can actually be 1D.\n+     *\n+     * @param givenInput One or more input examples, depending on the batch size\n+     * @param targetOutput One or more, matching, output targets\n+     */\n     public void train(final Access1D<Double> givenInput, final Access1D<Double> targetOutput) {\n \n-        Access1D<Double> current = this.invoke(givenInput, myConfiguration);\n+        MatrixStore<Double> current = this.invoke(givenInput, myConfiguration);\n \n-        myGradients[myGradients.length - 1].fillMatching(targetOutput, myConfiguration.error.getDerivative(), current);\n+        myGradients[myGradients.length - 1].regionByTransposing().fillMatching(targetOutput, myConfiguration.error.getDerivative(), current);\n \n         for (int l = this.depth() - 1; l >= 0; l--) {\n \n-            PhysicalStore<Double> input = l == 0 ? this.getInput() : this.getOutput(l - 1);\n+            PhysicalStore<Double> input = this.getInput(l);\n             PhysicalStore<Double> output = this.getOutput(l);\n \n             PhysicalStore<Double> upstreamGradient = l == 0 ? null : myGradients[l - 1];\n@@ -194,7 +212,10 @@ public final class NetworkTrainer extends WrappedANN {\n     /**\n      * Note that the required {@link Iterable}:s can be obtained from calling {@link Access2D#rows()} or\n      * {@link Access2D#columns()} on anything "2D".\n+     *\n+     * @deprecated Just use {@link #train(Access1D, Access1D)} instead\n      */\n+    @Deprecated\n     public void train(final Iterable<? extends Access1D<Double>> givenInputs, final Iterable<? extends Access1D<Double>> targetOutputs) {\n \n         Iterator<? extends Access1D<Double>> iterI = givenInputs.iterator();\ndiff --git a/src/main/java/org/ojalgo/ann/WrappedANN.java b/src/main/java/org/ojalgo/ann/WrappedANN.java\nindex d23de5c1..1545addf 100644\n--- a/src/main/java/org/ojalgo/ann/WrappedANN.java\n+++ b/src/main/java/org/ojalgo/ann/WrappedANN.java\n@@ -25,6 +25,7 @@ import java.util.List;\n import java.util.function.Supplier;\n \n import org.ojalgo.ann.ArtificialNeuralNetwork.Activator;\n+import org.ojalgo.data.DataBatch;\n import org.ojalgo.matrix.store.MatrixStore;\n import org.ojalgo.matrix.store.PhysicalStore;\n import org.ojalgo.structure.Access1D;\n@@ -32,20 +33,21 @@ import org.ojalgo.structure.Structure2D;\n \n abstract class WrappedANN implements Supplier<ArtificialNeuralNetwork> {\n \n-    private final PhysicalStore<Double> myInput;\n+    private final int myBatchSize;\n+    private PhysicalStore<Double> myInput;\n     private final ArtificialNeuralNetwork myNetwork;\n     private final PhysicalStore<Double>[] myOutputs;\n \n-    WrappedANN(final ArtificialNeuralNetwork network) {\n+    WrappedANN(final ArtificialNeuralNetwork network, final int batchSize) {\n \n         super();\n \n         myNetwork = network;\n+        myBatchSize = batchSize;\n \n-        myInput = network.newStore(1, network.countInputNodes(0));\n         myOutputs = (PhysicalStore<Double>[]) new PhysicalStore<?>[network.depth()];\n         for (int l = 0; l < myOutputs.length; l++) {\n-            myOutputs[l] = network.newStore(1, network.countOutputNodes(l));\n+            myOutputs[l] = network.newStore(batchSize, network.countOutputNodes(l));\n         }\n     }\n \n@@ -80,6 +82,25 @@ abstract class WrappedANN implements Supplier<ArtificialNeuralNetwork> {\n         return result;\n     }\n \n+    /**\n+     * When using {@link NetworkTrainer} or {@link NetworkInvoker} with a batch size larger than 1 this\n+     * utility may help with creating the batches.\n+     */\n+    public DataBatch newInputBatch() {\n+        return myNetwork.newBatch(myBatchSize, myNetwork.countInputNodes());\n+    }\n+\n+    private void setInput(final Access1D<Double> input) {\n+        if (input instanceof PhysicalStore && ((PhysicalStore<Double>) input).getRowDim() == myBatchSize) {\n+            myInput = (PhysicalStore<Double>) input;\n+        } else {\n+            if (myInput == null || myInput.getRowDim() != myBatchSize) {\n+                myInput = myNetwork.newStore(myBatchSize, myNetwork.countInputNodes());\n+            }\n+            myInput.fillMatching(input);\n+        }\n+    }\n+\n     void adjust(final int layer, final PhysicalStore<Double> input, final PhysicalStore<Double> output, final PhysicalStore<Double> upstreamGradient,\n             final PhysicalStore<Double> downstreamGradient) {\n         myNetwork.adjust(layer, input, output, upstreamGradient, downstreamGradient);\n@@ -93,6 +114,10 @@ abstract class WrappedANN implements Supplier<ArtificialNeuralNetwork> {\n         return myNetwork.getActivator(layer);\n     }\n \n+    int getBatchSize() {\n+        return myBatchSize;\n+    }\n+\n     double getBias(final int layer, final int output) {\n         return myNetwork.getBias(layer, output);\n     }\n@@ -101,6 +126,14 @@ abstract class WrappedANN implements Supplier<ArtificialNeuralNetwork> {\n         return myInput;\n     }\n \n+    PhysicalStore<Double> getInput(final int layer) {\n+        return layer <= 0 ? myInput : myOutputs[layer - 1];\n+    }\n+\n+    PhysicalStore<Double> getOutput() {\n+        return myOutputs[myOutputs.length - 1];\n+    }\n+\n     PhysicalStore<Double> getOutput(final int layer) {\n         return myOutputs[layer];\n     }\n@@ -119,7 +152,8 @@ abstract class WrappedANN implements Supplier<ArtificialNeuralNetwork> {\n \n     MatrixStore<Double> invoke(final Access1D<Double> input, final TrainingConfiguration configuration) {\n \n-        myInput.fillMatching(input);\n+        this.setInput(input);\n+\n         myNetwork.setConfiguration(configuration);\n \n         PhysicalStore<Double> retVal = myInput;\n@@ -129,6 +163,10 @@ abstract class WrappedANN implements Supplier<ArtificialNeuralNetwork> {\n         return retVal;\n     }\n \n+    DataBatch newOutputBatch() {\n+        return myNetwork.newBatch(myBatchSize, myNetwork.countOutputNodes());\n+    }\n+\n     void randomise() {\n         myNetwork.randomise();\n     }\ndiff --git a/src/main/java/org/ojalgo/array/Primitive32Array.java b/src/main/java/org/ojalgo/array/Primitive32Array.java\nindex 7938a5e8..15e97a4a 100644\n--- a/src/main/java/org/ojalgo/array/Primitive32Array.java\n+++ b/src/main/java/org/ojalgo/array/Primitive32Array.java\n@@ -142,7 +142,11 @@ public class Primitive32Array extends PrimitiveArray {\n \n     @Override\n     public void fillMatching(final Access1D<?> values) {\n-        FillAll.fill(data, values);\n+        if (values instanceof Primitive32Array) {\n+            FillMatchingSingle.fill(data, ((Primitive32Array) values).data);\n+        } else {\n+            FillMatchingSingle.fill(data, values);\n+        }\n     }\n \n     @Override\ndiff --git a/src/main/java/org/ojalgo/array/Primitive64Array.java b/src/main/java/org/ojalgo/array/Primitive64Array.java\nindex 8ec0bdb6..4ea902fc 100644\n--- a/src/main/java/org/ojalgo/array/Primitive64Array.java\n+++ b/src/main/java/org/ojalgo/array/Primitive64Array.java\n@@ -146,7 +146,11 @@ public class Primitive64Array extends PrimitiveArray {\n \n     @Override\n     public void fillMatching(final Access1D<?> values) {\n-        FillAll.fill(data, values);\n+        if (values instanceof Primitive64Array) {\n+            FillMatchingSingle.fill(data, ((Primitive64Array) values).data);\n+        } else {\n+            FillMatchingSingle.fill(data, values);\n+        }\n     }\n \n     @Override\ndiff --git a/src/main/java/org/ojalgo/array/ReferenceTypeArray.java b/src/main/java/org/ojalgo/array/ReferenceTypeArray.java\nindex 98153472..ffffd208 100644\n--- a/src/main/java/org/ojalgo/array/ReferenceTypeArray.java\n+++ b/src/main/java/org/ojalgo/array/ReferenceTypeArray.java\n@@ -28,6 +28,7 @@ import java.util.Spliterators;\n import org.ojalgo.array.operation.COPY;\n import org.ojalgo.array.operation.Exchange;\n import org.ojalgo.array.operation.FillAll;\n+import org.ojalgo.array.operation.FillMatchingSingle;\n import org.ojalgo.array.operation.OperationBinary;\n import org.ojalgo.array.operation.OperationParameter;\n import org.ojalgo.array.operation.OperationUnary;\n@@ -87,7 +88,7 @@ public abstract class ReferenceTypeArray<N extends Comparable<N>> extends PlainA\n \n     @Override\n     public void fillMatching(final Access1D<?> values) {\n-        FillAll.fill(data, values, this.factory().scalar());\n+        FillMatchingSingle.fill(data, values, this.factory().scalar());\n     }\n \n     @Override\ndiff --git a/src/main/java/org/ojalgo/array/operation/AMAX.java b/src/main/java/org/ojalgo/array/operation/AMAX.java\nindex 85404f13..6f941907 100644\n--- a/src/main/java/org/ojalgo/array/operation/AMAX.java\n+++ b/src/main/java/org/ojalgo/array/operation/AMAX.java\n@@ -23,10 +23,9 @@ package org.ojalgo.array.operation;\n \n import java.math.BigDecimal;\n \n-import org.ojalgo.array.BasicArray;\n import org.ojalgo.function.constant.BigMath;\n-import org.ojalgo.function.constant.PrimitiveMath;\n import org.ojalgo.scalar.Scalar;\n+import org.ojalgo.structure.Access1D;\n \n /**\n  * Given a vector x, the i?amax functions return the position of the vector element x[i] that has the largest\n@@ -40,16 +39,16 @@ public final class AMAX implements ArrayOperation {\n \n     public static int THRESHOLD = 128;\n \n-    public static <N extends Comparable<N>> long invoke(final BasicArray<N> data, final long first, final long limit, final long step) {\n+    public static long invoke(final Access1D<?> data, final long first, final long limit, final long step) {\n \n         long retVal = first;\n-        double tmpLargest = 0D;\n-        double tmpValue;\n+        double largest = 0D;\n+        double candidate;\n \n         for (long i = first; i < limit; i += step) {\n-            tmpValue = PrimitiveMath.ABS.invoke(data.doubleValue(i));\n-            if (tmpValue > tmpLargest) {\n-                tmpLargest = tmpValue;\n+            candidate = Math.abs(data.doubleValue(i));\n+            if (candidate > largest) {\n+                largest = candidate;\n                 retVal = i;\n             }\n         }\n@@ -59,13 +58,13 @@ public final class AMAX implements ArrayOperation {\n     public static int invoke(final BigDecimal[] data, final int first, final int limit, final int step) {\n \n         int retVal = first;\n-        BigDecimal tmpLargest = BigMath.ZERO;\n-        BigDecimal tmpValue;\n+        BigDecimal largest = BigMath.ZERO;\n+        BigDecimal candidate;\n \n         for (int i = first; i < limit; i += step) {\n-            tmpValue = BigMath.ABS.invoke(data[i]);\n-            if (tmpValue.compareTo(tmpLargest) > 0) {\n-                tmpLargest = tmpValue;\n+            candidate = data[i].abs();\n+            if (candidate.compareTo(largest) > 0) {\n+                largest = candidate;\n                 retVal = i;\n             }\n         }\n@@ -79,7 +78,7 @@ public final class AMAX implements ArrayOperation {\n         double candidate;\n \n         for (int i = first; i < limit; i += step) {\n-            candidate = PrimitiveMath.ABS.invoke(data[i]);\n+            candidate = Math.abs(data[i]);\n             if (candidate > largest) {\n                 largest = candidate;\n                 retVal = i;\n@@ -91,13 +90,13 @@ public final class AMAX implements ArrayOperation {\n     public static int invoke(final float[] data, final int first, final int limit, final int step) {\n \n         int retVal = first;\n-        float tmpLargest = 0F;\n-        float tmpValue;\n+        float largest = 0F;\n+        float candidate;\n \n         for (int i = first; i < limit; i += step) {\n-            tmpValue = Math.abs(data[i]);\n-            if (tmpValue > tmpLargest) {\n-                tmpLargest = tmpValue;\n+            candidate = Math.abs(data[i]);\n+            if (candidate > largest) {\n+                largest = candidate;\n                 retVal = i;\n             }\n         }\n@@ -107,13 +106,13 @@ public final class AMAX implements ArrayOperation {\n     public static <N extends Scalar<N>> int invoke(final N[] data, final int first, final int limit, final int step) {\n \n         int retVal = first;\n-        double tmpLargest = 0D;\n-        double tmpValue;\n+        double largest = 0D;\n+        double candidate;\n \n         for (int i = first; i < limit; i += step) {\n-            tmpValue = data[i].norm();\n-            if (tmpValue > tmpLargest) {\n-                tmpLargest = tmpValue;\n+            candidate = data[i].norm();\n+            if (candidate > largest) {\n+                largest = candidate;\n                 retVal = i;\n             }\n         }\ndiff --git a/src/main/java/org/ojalgo/array/operation/COPY.java b/src/main/java/org/ojalgo/array/operation/COPY.java\nindex e36f4647..8cbb1848 100644\n--- a/src/main/java/org/ojalgo/array/operation/COPY.java\n+++ b/src/main/java/org/ojalgo/array/operation/COPY.java\n@@ -76,13 +76,6 @@ public final class COPY implements ArrayOperation {\n         return retVal;\n     }\n \n-    public static void invoke(final double[] source, final int sourceOffset, final double[] destination, final int destinationOffset, final int first,\n-            final int limit) {\n-        for (int i = first; i < limit; i++) {\n-            destination[destinationOffset + i] = source[sourceOffset + i];\n-        }\n-    }\n-\n     public static void row(final Access2D<?> source, final long row, final double[] destination, final int first, final int limit) {\n         for (int j = first; j < limit; j++) {\n             destination[j] = source.doubleValue(row, j);\ndiff --git a/src/main/java/org/ojalgo/array/operation/FillAll.java b/src/main/java/org/ojalgo/array/operation/FillAll.java\nindex 8422d685..9e8ca20f 100644\n--- a/src/main/java/org/ojalgo/array/operation/FillAll.java\n+++ b/src/main/java/org/ojalgo/array/operation/FillAll.java\n@@ -23,19 +23,11 @@ package org.ojalgo.array.operation;\n \n import org.ojalgo.function.NullaryFunction;\n import org.ojalgo.scalar.Scalar;\n-import org.ojalgo.structure.Access1D;\n \n public final class FillAll implements ArrayOperation {\n \n     public static int THRESHOLD = 128;\n \n-    public static void fill(final double[] data, final Access1D<?> values) {\n-        final int limit = Math.min(data.length, values.size());\n-        for (int i = 0; i < limit; i++) {\n-            data[i] = values.doubleValue(i);\n-        }\n-    }\n-\n     public static void fill(final double[] data, final int first, final int limit, final int step, final double value) {\n         for (int i = first; i < limit; i += step) {\n             data[i] = value;\n@@ -48,13 +40,6 @@ public final class FillAll implements ArrayOperation {\n         }\n     }\n \n-    public static void fill(final float[] data, final Access1D<?> values) {\n-        final int limit = Math.min(data.length, values.size());\n-        for (int i = 0; i < limit; i++) {\n-            data[i] = values.floatValue(i);\n-        }\n-    }\n-\n     public static void fill(final float[] data, final int first, final int limit, final int step, final float value) {\n         for (int i = first; i < limit; i += step) {\n             data[i] = value;\n@@ -67,13 +52,6 @@ public final class FillAll implements ArrayOperation {\n         }\n     }\n \n-    public static <N extends Comparable<N>> void fill(final N[] data, final Access1D<?> values, final Scalar.Factory<N> scalar) {\n-        final int limit = Math.min(data.length, values.size());\n-        for (int i = 0; i < limit; i++) {\n-            data[i] = scalar.cast(values.get(i));\n-        }\n-    }\n-\n     public static <N extends Comparable<N>> void fill(final N[] data, final int first, final int limit, final int step, final N value) {\n         for (int i = first; i < limit; i += step) {\n             data[i] = value;\ndiff --git a/src/main/java/org/ojalgo/array/operation/FillMatchingSingle.java b/src/main/java/org/ojalgo/array/operation/FillMatchingSingle.java\nindex 46f361e0..3182f937 100644\n--- a/src/main/java/org/ojalgo/array/operation/FillMatchingSingle.java\n+++ b/src/main/java/org/ojalgo/array/operation/FillMatchingSingle.java\n@@ -24,6 +24,7 @@ package org.ojalgo.array.operation;\n import java.math.BigDecimal;\n \n import org.ojalgo.scalar.Scalar;\n+import org.ojalgo.structure.Access1D;\n import org.ojalgo.structure.Access2D;\n import org.ojalgo.type.TypeUtils;\n \n@@ -89,6 +90,48 @@ public final class FillMatchingSingle implements ArrayOperation {\n         }\n     }\n \n+    public static void fill(final double[] data, final Access1D<?> values) {\n+        int limit = Math.min(data.length, values.size());\n+        for (int i = 0; i < limit; i++) {\n+            data[i] = values.doubleValue(i);\n+        }\n+    }\n+\n+    public static void fill(final double[] data, final double[] values) {\n+        int limit = Math.min(data.length, values.length);\n+        for (int i = 0; i < limit; i++) {\n+            data[i] = values[i];\n+        }\n+    }\n+\n+    public static void fill(final float[] data, final Access1D<?> values) {\n+        int limit = Math.min(data.length, values.size());\n+        for (int i = 0; i < limit; i++) {\n+            data[i] = values.floatValue(i);\n+        }\n+    }\n+\n+    public static void fill(final float[] data, final float[] values) {\n+        int limit = Math.min(data.length, values.length);\n+        for (int i = 0; i < limit; i++) {\n+            data[i] = values[i];\n+        }\n+    }\n+\n+    public static <N extends Comparable<N>> void fill(final N[] data, final Access1D<?> values, final Scalar.Factory<N> scalar) {\n+        int limit = Math.min(data.length, values.size());\n+        for (int i = 0; i < limit; i++) {\n+            data[i] = scalar.cast(values.get(i));\n+        }\n+    }\n+\n+    public static void invoke(final double[] source, final int sourceOffset, final double[] destination, final int destinationOffset, final int first,\n+            final int limit) {\n+        for (int i = first; i < limit; i++) {\n+            destination[destinationOffset + i] = source[sourceOffset + i];\n+        }\n+    }\n+\n     public static void transpose(final BigDecimal[] data, final int structure, final int firstColumn, final int limitColumn, final Access2D<?> source) {\n         int index = structure * firstColumn;\n         for (int j = firstColumn; j < limitColumn; j++) {\ndiff --git a/src/main/java/org/ojalgo/array/operation/HouseholderHermitian.java b/src/main/java/org/ojalgo/array/operation/HouseholderHermitian.java\nindex c443db1a..1c850e23 100644\n--- a/src/main/java/org/ojalgo/array/operation/HouseholderHermitian.java\n+++ b/src/main/java/org/ojalgo/array/operation/HouseholderHermitian.java\n@@ -174,7 +174,7 @@ public final class HouseholderHermitian implements ArrayOperation {\n \n         // Copy the last column (same as the last row) of z to d\n         // The last row/column is the first to be worked on in the main loop\n-        COPY.invoke(data, tmpRowDim * tmpLast, d, 0, 0, n);\n+        FillMatchingSingle.invoke(data, tmpRowDim * tmpLast, d, 0, 0, n);\n \n         // Householder reduction to tridiagonal form.\n         for (int i = tmpLast; i > 0; i--) { // row index of target householder point\ndiff --git a/src/main/java/org/ojalgo/data/DataBatch.java b/src/main/java/org/ojalgo/data/DataBatch.java\nnew file mode 100644\nindex 00000000..20d2db3d\n--- /dev/null\n+++ b/src/main/java/org/ojalgo/data/DataBatch.java\n@@ -0,0 +1,115 @@\n+/*\n+ * Copyright 1997-2021 Optimatika\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the "Software"), to deal\n+ * in the Software without restriction, including without limitation the rights\n+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+ * copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n+ * SOFTWARE.\n+ */\n+package org.ojalgo.data;\n+\n+import static org.ojalgo.function.constant.PrimitiveMath.*;\n+\n+import java.util.Collection;\n+\n+import org.ojalgo.structure.Access1D;\n+import org.ojalgo.structure.Access2D;\n+import org.ojalgo.structure.Factory2D;\n+import org.ojalgo.structure.Mutate2D;\n+import org.ojalgo.structure.Mutate2D.Receiver;\n+\n+/**\n+ * A reusable data batch. Build 2D data structures by adding a set of 1D structures treating then as rows. Use\n+ * it, {@link #reset()} and do it again.\n+ *\n+ * @author apete\n+ */\n+public final class DataBatch implements Access2D<Double>, Access2D.Collectable<Double, Mutate2D.Receiver<Double>> {\n+\n+    /**\n+     * With the batch size/capacity specified. The returned instance will throw an exception if too many rows\n+     * are added to it.\n+     */\n+    public static DataBatch from(final Factory2D<? extends Mutate2D.ModifiableReceiver<Double>> factory, final int batchSize, final int dataNodes) {\n+        return new DataBatch(factory.make(batchSize, dataNodes));\n+    }\n+\n+    private int myCursor = 0;\n+    private final Mutate2D.ModifiableReceiver<Double> myData;\n+\n+    DataBatch(final Mutate2D.ModifiableReceiver<Double> data) {\n+\n+        super();\n+\n+        myData = data;\n+    }\n+\n+    public void addRow(final Access1D<Double> row) {\n+        myData.fillRow(myCursor, row);\n+        myCursor++;\n+    }\n+\n+    public void addRows(final Collection<? extends Access1D<Double>> rows) {\n+        rows.forEach(this::addRow);\n+    }\n+\n+    public void addRowWithSingleUnit(final int unitIndex) {\n+        myData.fillRow(myCursor, ZERO);\n+        myData.set(myCursor, unitIndex, ONE);\n+        myCursor++;\n+    }\n+\n+    /**\n+     * Assumes all rows are of equal length and will only check the first. Returns 0 if there are no rows.\n+     *\n+     * @see org.ojalgo.structure.Structure2D#countColumns()\n+     */\n+    public long countColumns() {\n+        return myData.countColumns();\n+    }\n+\n+    public long countRows() {\n+        return myData.countRows();\n+    }\n+\n+    public double doubleValue(final long row, final long col) {\n+        return myData.doubleValue(row, col);\n+    }\n+\n+    public Double get(final long row, final long col) {\n+        return Double.valueOf(this.doubleValue(row, col));\n+    }\n+\n+    public boolean isFull() {\n+        return this.remaining() == 0;\n+    }\n+\n+    /**\n+     * Remaining capacity / batch entries / number of rows.\n+     */\n+    public int remaining() {\n+        return this.getRowDim() - myCursor;\n+    }\n+\n+    public void reset() {\n+        myCursor = 0;\n+    }\n+\n+    public void supplyTo(final Receiver<Double> receiver) {\n+        receiver.fillMatching(myData);\n+    }\n+\n+}\ndiff --git a/src/main/java/org/ojalgo/function/aggregator/BigAggregator.java b/src/main/java/org/ojalgo/function/aggregator/BigAggregator.java\nindex 86016af4..11fbd7f8 100644\n--- a/src/main/java/org/ojalgo/function/aggregator/BigAggregator.java\n+++ b/src/main/java/org/ojalgo/function/aggregator/BigAggregator.java\n@@ -25,7 +25,6 @@ import static org.ojalgo.function.constant.BigMath.*;\n \n import java.math.BigDecimal;\n \n-import org.ojalgo.ProgrammingError;\n import org.ojalgo.function.constant.BigMath;\n import org.ojalgo.scalar.BigScalar;\n import org.ojalgo.scalar.Scalar;\n@@ -74,14 +73,6 @@ public final class BigAggregator extends AggregatorSet<BigDecimal> {\n                     myNumber = BigMath.ADD.invoke(myNumber, anArg);\n                 }\n \n-                public boolean isMergeable() {\n-                    return false;\n-                }\n-\n-                public void merge(final BigDecimal result) {\n-                    ProgrammingError.throwForIllegalInvocation();\n-                }\n-\n                 public AggregatorFunction<BigDecimal> reset() {\n                     myCount = 0;\n                     myNumber = ZERO;\n@@ -114,10 +105,6 @@ public final class BigAggregator extends AggregatorSet<BigDecimal> {\n                     }\n                 }\n \n-                public void merge(final BigDecimal result) {\n-                    myCount += result.intValue();\n-                }\n-\n                 public AggregatorFunction<BigDecimal> reset() {\n                     myCount = 0;\n                     return this;\n@@ -147,10 +134,6 @@ public final class BigAggregator extends AggregatorSet<BigDecimal> {\n                     myNumber = BigMath.MAX.invoke(myNumber, BigMath.ABS.invoke(anArg));\n                 }\n \n-                public void merge(final BigDecimal result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<BigDecimal> reset() {\n                     myNumber = ZERO;\n                     return this;\n@@ -180,10 +163,6 @@ public final class BigAggregator extends AggregatorSet<BigDecimal> {\n                     myNumber = BigMath.MAX.invoke(myNumber, anArg);\n                 }\n \n-                public void merge(final BigDecimal result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<BigDecimal> reset() {\n                     myNumber = VERY_NEGATIVE;\n                     return this;\n@@ -204,9 +183,8 @@ public final class BigAggregator extends AggregatorSet<BigDecimal> {\n                 public BigDecimal get() {\n                     if (myNumber.compareTo(VERY_POSITIVE) == 0) {\n                         return ZERO;\n-                    } else {\n-                        return myNumber;\n                     }\n+                    return myNumber;\n                 }\n \n                 public int intValue() {\n@@ -217,10 +195,6 @@ public final class BigAggregator extends AggregatorSet<BigDecimal> {\n                     myNumber = BigMath.MIN.invoke(myNumber, anArg);\n                 }\n \n-                public void merge(final BigDecimal result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<BigDecimal> reset() {\n                     myNumber = VERY_POSITIVE;\n                     return this;\n@@ -250,10 +224,6 @@ public final class BigAggregator extends AggregatorSet<BigDecimal> {\n                     myNumber = BigMath.ADD.invoke(myNumber, anArg.abs());\n                 }\n \n-                public void merge(final BigDecimal result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<BigDecimal> reset() {\n                     myNumber = ZERO;\n                     return this;\n@@ -283,10 +253,6 @@ public final class BigAggregator extends AggregatorSet<BigDecimal> {\n                     myNumber = BigMath.ADD.invoke(myNumber, BigMath.MULTIPLY.invoke(anArg, anArg));\n                 }\n \n-                public void merge(final BigDecimal result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<BigDecimal> reset() {\n                     myNumber = ZERO;\n                     return this;\n@@ -316,10 +282,6 @@ public final class BigAggregator extends AggregatorSet<BigDecimal> {\n                     myNumber = BigMath.MULTIPLY.invoke(myNumber, anArg);\n                 }\n \n-                public void merge(final BigDecimal result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<BigDecimal> reset() {\n                     myNumber = ONE;\n                     return this;\n@@ -349,10 +311,6 @@ public final class BigAggregator extends AggregatorSet<BigDecimal> {\n                     myNumber = BigMath.MULTIPLY.invoke(myNumber, BigMath.MULTIPLY.invoke(anArg, anArg));\n                 }\n \n-                public void merge(final BigDecimal result) {\n-                    myNumber = BigMath.MULTIPLY.invoke(myNumber, result);\n-                }\n-\n                 public AggregatorFunction<BigDecimal> reset() {\n                     myNumber = ONE;\n                     return this;\n@@ -375,9 +333,8 @@ public final class BigAggregator extends AggregatorSet<BigDecimal> {\n                 public BigDecimal get() {\n                     if (myNumber.compareTo(VERY_POSITIVE) == 0) {\n                         return ZERO;\n-                    } else {\n-                        return myNumber;\n                     }\n+                    return myNumber;\n                 }\n \n                 public int intValue() {\n@@ -390,10 +347,6 @@ public final class BigAggregator extends AggregatorSet<BigDecimal> {\n                     }\n                 }\n \n-                public void merge(final BigDecimal result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<BigDecimal> reset() {\n                     myNumber = VERY_POSITIVE;\n                     return this;\n@@ -423,10 +376,6 @@ public final class BigAggregator extends AggregatorSet<BigDecimal> {\n                     myNumber = BigMath.ADD.invoke(myNumber, anArg);\n                 }\n \n-                public void merge(final BigDecimal result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<BigDecimal> reset() {\n                     myNumber = ZERO;\n                     return this;\n@@ -456,10 +405,6 @@ public final class BigAggregator extends AggregatorSet<BigDecimal> {\n                     myNumber = BigMath.ADD.invoke(myNumber, BigMath.MULTIPLY.invoke(anArg, anArg));\n                 }\n \n-                public void merge(final BigDecimal result) {\n-                    myNumber = BigMath.ADD.invoke(myNumber, result);\n-                }\n-\n                 public AggregatorFunction<BigDecimal> reset() {\n                     myNumber = ZERO;\n                     return this;\ndiff --git a/src/main/java/org/ojalgo/function/aggregator/ComplexAggregator.java b/src/main/java/org/ojalgo/function/aggregator/ComplexAggregator.java\nindex 9b589e24..e35a1302 100644\n--- a/src/main/java/org/ojalgo/function/aggregator/ComplexAggregator.java\n+++ b/src/main/java/org/ojalgo/function/aggregator/ComplexAggregator.java\n@@ -21,7 +21,6 @@\n  */\n package org.ojalgo.function.aggregator;\n \n-import org.ojalgo.ProgrammingError;\n import org.ojalgo.function.constant.ComplexMath;\n import org.ojalgo.function.constant.PrimitiveMath;\n import org.ojalgo.scalar.ComplexNumber;\n@@ -72,14 +71,6 @@ public final class ComplexAggregator extends AggregatorSet<ComplexNumber> {\n                     myNumber = myNumber.add(anArg);\n                 }\n \n-                public boolean isMergeable() {\n-                    return false;\n-                }\n-\n-                public void merge(final ComplexNumber result) {\n-                    ProgrammingError.throwForIllegalInvocation();\n-                }\n-\n                 public AggregatorFunction<ComplexNumber> reset() {\n                     myCount = 0;\n                     myNumber = ComplexNumber.ZERO;\n@@ -112,10 +103,6 @@ public final class ComplexAggregator extends AggregatorSet<ComplexNumber> {\n                     }\n                 }\n \n-                public void merge(final ComplexNumber result) {\n-                    myCount += result.intValue();\n-                }\n-\n                 public AggregatorFunction<ComplexNumber> reset() {\n                     myCount = 0;\n                     return this;\n@@ -145,10 +132,6 @@ public final class ComplexAggregator extends AggregatorSet<ComplexNumber> {\n                     myNumber = ComplexMath.MAX.invoke(myNumber, ComplexMath.ABS.invoke(anArg));\n                 }\n \n-                public void merge(final ComplexNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<ComplexNumber> reset() {\n                     myNumber = ComplexNumber.ZERO;\n                     return this;\n@@ -178,10 +161,6 @@ public final class ComplexAggregator extends AggregatorSet<ComplexNumber> {\n                     myNumber = ComplexMath.MAX.invoke(myNumber, anArg);\n                 }\n \n-                public void merge(final ComplexNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<ComplexNumber> reset() {\n                     myNumber = ComplexNumber.ZERO;\n                     return this;\n@@ -202,9 +181,8 @@ public final class ComplexAggregator extends AggregatorSet<ComplexNumber> {\n                 public ComplexNumber get() {\n                     if (ComplexNumber.isInfinite(myNumber)) {\n                         return ComplexNumber.ZERO;\n-                    } else {\n-                        return myNumber;\n                     }\n+                    return myNumber;\n                 }\n \n                 public int intValue() {\n@@ -215,10 +193,6 @@ public final class ComplexAggregator extends AggregatorSet<ComplexNumber> {\n                     myNumber = ComplexMath.MIN.invoke(myNumber, anArg);\n                 }\n \n-                public void merge(final ComplexNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<ComplexNumber> reset() {\n                     myNumber = ComplexNumber.INFINITY;\n                     return this;\n@@ -248,10 +222,6 @@ public final class ComplexAggregator extends AggregatorSet<ComplexNumber> {\n                     myNumber = myNumber.add(anArg.norm());\n                 }\n \n-                public void merge(final ComplexNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<ComplexNumber> reset() {\n                     myNumber = ComplexNumber.ZERO;\n                     return this;\n@@ -282,10 +252,6 @@ public final class ComplexAggregator extends AggregatorSet<ComplexNumber> {\n                     myNumber = myNumber.add(tmpMod * tmpMod);\n                 }\n \n-                public void merge(final ComplexNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<ComplexNumber> reset() {\n                     myNumber = ComplexNumber.ZERO;\n                     return this;\n@@ -315,10 +281,6 @@ public final class ComplexAggregator extends AggregatorSet<ComplexNumber> {\n                     myNumber = myNumber.multiply(anArg);\n                 }\n \n-                public void merge(final ComplexNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<ComplexNumber> reset() {\n                     myNumber = ComplexNumber.ONE;\n                     return this;\n@@ -348,10 +310,6 @@ public final class ComplexAggregator extends AggregatorSet<ComplexNumber> {\n                     myNumber = myNumber.multiply(anArg.multiply(anArg));\n                 }\n \n-                public void merge(final ComplexNumber result) {\n-                    myNumber = myNumber.multiply(result);\n-                }\n-\n                 public AggregatorFunction<ComplexNumber> reset() {\n                     myNumber = ComplexNumber.ONE;\n                     return this;\n@@ -374,9 +332,8 @@ public final class ComplexAggregator extends AggregatorSet<ComplexNumber> {\n                 public ComplexNumber get() {\n                     if (ComplexNumber.isInfinite(myNumber)) {\n                         return ComplexNumber.ZERO;\n-                    } else {\n-                        return myNumber;\n                     }\n+                    return myNumber;\n                 }\n \n                 public int intValue() {\n@@ -389,10 +346,6 @@ public final class ComplexAggregator extends AggregatorSet<ComplexNumber> {\n                     }\n                 }\n \n-                public void merge(final ComplexNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<ComplexNumber> reset() {\n                     myNumber = ComplexNumber.INFINITY;\n                     return this;\n@@ -422,10 +375,6 @@ public final class ComplexAggregator extends AggregatorSet<ComplexNumber> {\n                     myNumber = myNumber.add(anArg);\n                 }\n \n-                public void merge(final ComplexNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<ComplexNumber> reset() {\n                     myNumber = ComplexNumber.ZERO;\n                     return this;\n@@ -455,10 +404,6 @@ public final class ComplexAggregator extends AggregatorSet<ComplexNumber> {\n                     myNumber = myNumber.add(anArg.multiply(anArg));\n                 }\n \n-                public void merge(final ComplexNumber result) {\n-                    myNumber = myNumber.add(result);\n-                }\n-\n                 public AggregatorFunction<ComplexNumber> reset() {\n                     myNumber = ComplexNumber.ZERO;\n                     return this;\ndiff --git a/src/main/java/org/ojalgo/function/aggregator/PrimitiveAggregator.java b/src/main/java/org/ojalgo/function/aggregator/PrimitiveAggregator.java\nindex 210743ac..849f74fe 100644\n--- a/src/main/java/org/ojalgo/function/aggregator/PrimitiveAggregator.java\n+++ b/src/main/java/org/ojalgo/function/aggregator/PrimitiveAggregator.java\n@@ -23,7 +23,6 @@ package org.ojalgo.function.aggregator;\n \n import static org.ojalgo.function.constant.PrimitiveMath.*;\n \n-import org.ojalgo.ProgrammingError;\n import org.ojalgo.function.constant.PrimitiveMath;\n import org.ojalgo.scalar.PrimitiveScalar;\n import org.ojalgo.scalar.Scalar;\n@@ -73,14 +72,6 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n                     myValue += anArg;\n                 }\n \n-                public boolean isMergeable() {\n-                    return false;\n-                }\n-\n-                public void merge(final Double result) {\n-                    ProgrammingError.throwForIllegalInvocation();\n-                }\n-\n                 public AggregatorFunction<Double> reset() {\n                     myCount = 0;\n                     myValue = ZERO;\n@@ -113,10 +104,6 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n                     }\n                 }\n \n-                public void merge(final Double result) {\n-                    myCount += result.intValue();\n-                }\n-\n                 public AggregatorFunction<Double> reset() {\n                     myCount = 0;\n                     return this;\n@@ -146,10 +133,6 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n                     myValue = PrimitiveMath.MAX.invoke(myValue, PrimitiveMath.ABS.invoke(anArg));\n                 }\n \n-                public void merge(final Double result) {\n-                    this.invoke(result.doubleValue());\n-                }\n-\n                 public AggregatorFunction<Double> reset() {\n                     myValue = ZERO;\n                     return this;\n@@ -179,10 +162,6 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n                     myValue = PrimitiveMath.MAX.invoke(myValue, anArg);\n                 }\n \n-                public void merge(final Double result) {\n-                    this.invoke(result.doubleValue());\n-                }\n-\n                 public AggregatorFunction<Double> reset() {\n                     myValue = NEGATIVE_INFINITY;\n                     return this;\n@@ -203,9 +182,8 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n                 public double doubleValue() {\n                     if (Double.isInfinite(myValue)) {\n                         return ZERO;\n-                    } else {\n-                        return myValue;\n                     }\n+                    return myValue;\n                 }\n \n                 public int intValue() {\n@@ -216,10 +194,6 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n                     myValue = PrimitiveMath.MIN.invoke(myValue, anArg);\n                 }\n \n-                public void merge(final Double result) {\n-                    this.invoke(result.doubleValue());\n-                }\n-\n                 public AggregatorFunction<Double> reset() {\n                     myValue = POSITIVE_INFINITY;\n                     return this;\n@@ -249,10 +223,6 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n                     myValue += PrimitiveMath.ABS.invoke(anArg);\n                 }\n \n-                public void merge(final Double result) {\n-                    this.invoke(result.doubleValue());\n-                }\n-\n                 public AggregatorFunction<Double> reset() {\n                     myValue = ZERO;\n                     return this;\n@@ -271,7 +241,6 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n                 private double myValue = ZERO;\n \n                 public double doubleValue() {\n-                    //return myValue; // more than 100x slower\n                     return PrimitiveMath.SQRT.invoke(myValue);\n                 }\n \n@@ -281,11 +250,6 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n \n                 public void invoke(final double anArg) {\n                     myValue += anArg * anArg;\n-                    //myValue = PrimitiveFunction.HYPOT.invoke(myValue, anArg); // more than 100x slower\n-                }\n-\n-                public void merge(final Double result) {\n-                    this.invoke(result.doubleValue());\n                 }\n \n                 public AggregatorFunction<Double> reset() {\n@@ -317,10 +281,6 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n                     myValue *= anArg;\n                 }\n \n-                public void merge(final Double result) {\n-                    this.invoke(result.doubleValue());\n-                }\n-\n                 public AggregatorFunction<Double> reset() {\n                     myValue = ONE;\n                     return this;\n@@ -350,10 +310,6 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n                     myValue *= anArg * anArg;\n                 }\n \n-                public void merge(final Double result) {\n-                    myValue *= result;\n-                }\n-\n                 public AggregatorFunction<Double> reset() {\n                     myValue = ONE;\n                     return this;\n@@ -376,9 +332,8 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n                 public double doubleValue() {\n                     if (Double.isInfinite(myValue)) {\n                         return ZERO;\n-                    } else {\n-                        return myValue;\n                     }\n+                    return myValue;\n                 }\n \n                 public int intValue() {\n@@ -392,10 +347,6 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n                     }\n                 }\n \n-                public void merge(final Double result) {\n-                    this.invoke(result.doubleValue());\n-                }\n-\n                 public AggregatorFunction<Double> reset() {\n                     myValue = POSITIVE_INFINITY;\n                     return this;\n@@ -425,10 +376,6 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n                     myValue += anArg;\n                 }\n \n-                public void merge(final Double result) {\n-                    this.invoke(result.doubleValue());\n-                }\n-\n                 public AggregatorFunction<Double> reset() {\n                     myValue = ZERO;\n                     return this;\n@@ -458,10 +405,6 @@ public final class PrimitiveAggregator extends AggregatorSet<Double> {\n                     myValue += anArg * anArg;\n                 }\n \n-                public void merge(final Double result) {\n-                    myValue += result;\n-                }\n-\n                 public AggregatorFunction<Double> reset() {\n                     myValue = ZERO;\n                     return this;\ndiff --git a/src/main/java/org/ojalgo/function/aggregator/QuaternionAggregator.java b/src/main/java/org/ojalgo/function/aggregator/QuaternionAggregator.java\nindex 09b8dc8f..3a9f3b77 100644\n--- a/src/main/java/org/ojalgo/function/aggregator/QuaternionAggregator.java\n+++ b/src/main/java/org/ojalgo/function/aggregator/QuaternionAggregator.java\n@@ -21,7 +21,6 @@\n  */\n package org.ojalgo.function.aggregator;\n \n-import org.ojalgo.ProgrammingError;\n import org.ojalgo.function.constant.PrimitiveMath;\n import org.ojalgo.function.constant.QuaternionMath;\n import org.ojalgo.scalar.PrimitiveScalar;\n@@ -73,14 +72,6 @@ public final class QuaternionAggregator extends AggregatorSet<Quaternion> {\n                     myNumber = myNumber.add(anArg);\n                 }\n \n-                public boolean isMergeable() {\n-                    return false;\n-                }\n-\n-                public void merge(final Quaternion result) {\n-                    ProgrammingError.throwForIllegalInvocation();\n-                }\n-\n                 public AggregatorFunction<Quaternion> reset() {\n                     myCount = 0;\n                     myNumber = Quaternion.ZERO;\n@@ -113,10 +104,6 @@ public final class QuaternionAggregator extends AggregatorSet<Quaternion> {\n                     }\n                 }\n \n-                public void merge(final Quaternion result) {\n-                    myCount += result.intValue();\n-                }\n-\n                 public AggregatorFunction<Quaternion> reset() {\n                     myCount = 0;\n                     return this;\n@@ -146,10 +133,6 @@ public final class QuaternionAggregator extends AggregatorSet<Quaternion> {\n                     myNumber = QuaternionMath.MAX.invoke(myNumber, QuaternionMath.ABS.invoke(anArg));\n                 }\n \n-                public void merge(final Quaternion result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<Quaternion> reset() {\n                     myNumber = Quaternion.ZERO;\n                     return this;\n@@ -179,10 +162,6 @@ public final class QuaternionAggregator extends AggregatorSet<Quaternion> {\n                     myNumber = QuaternionMath.MAX.invoke(myNumber, anArg);\n                 }\n \n-                public void merge(final Quaternion result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<Quaternion> reset() {\n                     myNumber = Quaternion.ZERO;\n                     return this;\n@@ -203,9 +182,8 @@ public final class QuaternionAggregator extends AggregatorSet<Quaternion> {\n                 public Quaternion get() {\n                     if (Quaternion.isInfinite(myNumber)) {\n                         return Quaternion.ZERO;\n-                    } else {\n-                        return myNumber;\n                     }\n+                    return myNumber;\n                 }\n \n                 public int intValue() {\n@@ -216,10 +194,6 @@ public final class QuaternionAggregator extends AggregatorSet<Quaternion> {\n                     myNumber = QuaternionMath.MIN.invoke(myNumber, anArg);\n                 }\n \n-                public void merge(final Quaternion result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<Quaternion> reset() {\n                     myNumber = Quaternion.INFINITY;\n                     return this;\n@@ -249,10 +223,6 @@ public final class QuaternionAggregator extends AggregatorSet<Quaternion> {\n                     myNumber = myNumber.add(anArg.norm());\n                 }\n \n-                public void merge(final Quaternion result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<Quaternion> reset() {\n                     myNumber = Quaternion.ZERO;\n                     return this;\n@@ -283,10 +253,6 @@ public final class QuaternionAggregator extends AggregatorSet<Quaternion> {\n                     myNumber = myNumber.add(tmpMod * tmpMod);\n                 }\n \n-                public void merge(final Quaternion result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<Quaternion> reset() {\n                     myNumber = Quaternion.ZERO;\n                     return this;\n@@ -316,10 +282,6 @@ public final class QuaternionAggregator extends AggregatorSet<Quaternion> {\n                     myNumber = myNumber.multiply(anArg);\n                 }\n \n-                public void merge(final Quaternion result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<Quaternion> reset() {\n                     myNumber = Quaternion.ONE;\n                     return this;\n@@ -349,10 +311,6 @@ public final class QuaternionAggregator extends AggregatorSet<Quaternion> {\n                     myNumber = myNumber.multiply(anArg.multiply(anArg));\n                 }\n \n-                public void merge(final Quaternion result) {\n-                    myNumber = myNumber.multiply(result);\n-                }\n-\n                 public AggregatorFunction<Quaternion> reset() {\n                     myNumber = Quaternion.ONE;\n                     return this;\n@@ -375,9 +333,8 @@ public final class QuaternionAggregator extends AggregatorSet<Quaternion> {\n                 public Quaternion get() {\n                     if (Quaternion.isInfinite(myNumber)) {\n                         return Quaternion.ZERO;\n-                    } else {\n-                        return myNumber;\n                     }\n+                    return myNumber;\n                 }\n \n                 public int intValue() {\n@@ -390,10 +347,6 @@ public final class QuaternionAggregator extends AggregatorSet<Quaternion> {\n                     }\n                 }\n \n-                public void merge(final Quaternion result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<Quaternion> reset() {\n                     myNumber = Quaternion.INFINITY;\n                     return this;\n@@ -423,10 +376,6 @@ public final class QuaternionAggregator extends AggregatorSet<Quaternion> {\n                     myNumber = myNumber.add(anArg);\n                 }\n \n-                public void merge(final Quaternion result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<Quaternion> reset() {\n                     myNumber = Quaternion.ZERO;\n                     return this;\n@@ -456,10 +405,6 @@ public final class QuaternionAggregator extends AggregatorSet<Quaternion> {\n                     myNumber = myNumber.add(anArg.multiply(anArg));\n                 }\n \n-                public void merge(final Quaternion result) {\n-                    myNumber = myNumber.add(result);\n-                }\n-\n                 public AggregatorFunction<Quaternion> reset() {\n                     myNumber = Quaternion.ZERO;\n                     return this;\ndiff --git a/src/main/java/org/ojalgo/function/aggregator/RationalAggregator.java b/src/main/java/org/ojalgo/function/aggregator/RationalAggregator.java\nindex b78ee9bd..e37b26a5 100644\n--- a/src/main/java/org/ojalgo/function/aggregator/RationalAggregator.java\n+++ b/src/main/java/org/ojalgo/function/aggregator/RationalAggregator.java\n@@ -21,7 +21,6 @@\n  */\n package org.ojalgo.function.aggregator;\n \n-import org.ojalgo.ProgrammingError;\n import org.ojalgo.function.constant.PrimitiveMath;\n import org.ojalgo.function.constant.RationalMath;\n import org.ojalgo.scalar.PrimitiveScalar;\n@@ -73,14 +72,6 @@ public final class RationalAggregator extends AggregatorSet<RationalNumber> {\n                     myNumber = myNumber.add(anArg);\n                 }\n \n-                public boolean isMergeable() {\n-                    return false;\n-                }\n-\n-                public void merge(final RationalNumber result) {\n-                    ProgrammingError.throwForIllegalInvocation();\n-                }\n-\n                 public AggregatorFunction<RationalNumber> reset() {\n                     myCount = 0;\n                     myNumber = RationalNumber.ZERO;\n@@ -113,10 +104,6 @@ public final class RationalAggregator extends AggregatorSet<RationalNumber> {\n                     }\n                 }\n \n-                public void merge(final RationalNumber result) {\n-                    myCount += result.intValue();\n-                }\n-\n                 public AggregatorFunction<RationalNumber> reset() {\n                     myCount = 0;\n                     return this;\n@@ -146,10 +133,6 @@ public final class RationalAggregator extends AggregatorSet<RationalNumber> {\n                     myNumber = RationalMath.MAX.invoke(myNumber, RationalMath.ABS.invoke(anArg));\n                 }\n \n-                public void merge(final RationalNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<RationalNumber> reset() {\n                     myNumber = RationalNumber.ZERO;\n                     return this;\n@@ -179,10 +162,6 @@ public final class RationalAggregator extends AggregatorSet<RationalNumber> {\n                     myNumber = RationalMath.MAX.invoke(myNumber, anArg);\n                 }\n \n-                public void merge(final RationalNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<RationalNumber> reset() {\n                     myNumber = RationalNumber.NEGATIVE_INFINITY;\n                     return this;\n@@ -203,9 +182,8 @@ public final class RationalAggregator extends AggregatorSet<RationalNumber> {\n                 public RationalNumber get() {\n                     if (RationalNumber.isInfinite(myNumber)) {\n                         return RationalNumber.ZERO;\n-                    } else {\n-                        return myNumber;\n                     }\n+                    return myNumber;\n                 }\n \n                 public int intValue() {\n@@ -216,10 +194,6 @@ public final class RationalAggregator extends AggregatorSet<RationalNumber> {\n                     myNumber = RationalMath.MIN.invoke(myNumber, anArg);\n                 }\n \n-                public void merge(final RationalNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<RationalNumber> reset() {\n                     myNumber = RationalNumber.POSITIVE_INFINITY;\n                     return this;\n@@ -249,10 +223,6 @@ public final class RationalAggregator extends AggregatorSet<RationalNumber> {\n                     myNumber = myNumber.add(PrimitiveMath.ABS.invoke(anArg.doubleValue()));\n                 }\n \n-                public void merge(final RationalNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<RationalNumber> reset() {\n                     myNumber = RationalNumber.ZERO;\n                     return this;\n@@ -283,10 +253,6 @@ public final class RationalAggregator extends AggregatorSet<RationalNumber> {\n                     myNumber = myNumber.add(tmpMod * tmpMod);\n                 }\n \n-                public void merge(final RationalNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<RationalNumber> reset() {\n                     myNumber = RationalNumber.ZERO;\n                     return this;\n@@ -316,10 +282,6 @@ public final class RationalAggregator extends AggregatorSet<RationalNumber> {\n                     myNumber = myNumber.multiply(anArg);\n                 }\n \n-                public void merge(final RationalNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<RationalNumber> reset() {\n                     myNumber = RationalNumber.ONE;\n                     return this;\n@@ -349,10 +311,6 @@ public final class RationalAggregator extends AggregatorSet<RationalNumber> {\n                     myNumber = myNumber.multiply(anArg.multiply(anArg));\n                 }\n \n-                public void merge(final RationalNumber result) {\n-                    myNumber = myNumber.multiply(result);\n-                }\n-\n                 public AggregatorFunction<RationalNumber> reset() {\n                     myNumber = RationalNumber.ONE;\n                     return this;\n@@ -375,9 +333,8 @@ public final class RationalAggregator extends AggregatorSet<RationalNumber> {\n                 public RationalNumber get() {\n                     if (RationalNumber.isInfinite(myNumber)) {\n                         return RationalNumber.ZERO;\n-                    } else {\n-                        return myNumber;\n                     }\n+                    return myNumber;\n                 }\n \n                 public int intValue() {\n@@ -390,10 +347,6 @@ public final class RationalAggregator extends AggregatorSet<RationalNumber> {\n                     }\n                 }\n \n-                public void merge(final RationalNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<RationalNumber> reset() {\n                     myNumber = RationalNumber.POSITIVE_INFINITY;\n                     return this;\n@@ -423,10 +376,6 @@ public final class RationalAggregator extends AggregatorSet<RationalNumber> {\n                     myNumber = myNumber.add(anArg);\n                 }\n \n-                public void merge(final RationalNumber result) {\n-                    this.invoke(result);\n-                }\n-\n                 public AggregatorFunction<RationalNumber> reset() {\n                     myNumber = RationalNumber.ZERO;\n                     return this;\n@@ -456,10 +405,6 @@ public final class RationalAggregator extends AggregatorSet<RationalNumber> {\n                     myNumber = myNumber.add(anArg.multiply(anArg));\n                 }\n \n-                public void merge(final RationalNumber result) {\n-                    myNumber = myNumber.add(result);\n-                }\n-\n                 public AggregatorFunction<RationalNumber> reset() {\n                     myNumber = RationalNumber.ZERO;\n                     return this;\ndiff --git a/src/main/java/org/ojalgo/matrix/decomposition/RawEigenvalue.java b/src/main/java/org/ojalgo/matrix/decomposition/RawEigenvalue.java\nindex 4ae10152..ba14f1c4 100644\n--- a/src/main/java/org/ojalgo/matrix/decomposition/RawEigenvalue.java\n+++ b/src/main/java/org/ojalgo/matrix/decomposition/RawEigenvalue.java\n@@ -30,8 +30,8 @@ import org.ojalgo.RecoverableCondition;\n import org.ojalgo.array.Array1D;\n import org.ojalgo.array.Primitive64Array;\n import org.ojalgo.array.operation.AXPY;\n-import org.ojalgo.array.operation.COPY;\n import org.ojalgo.array.operation.DOT;\n+import org.ojalgo.array.operation.FillMatchingSingle;\n import org.ojalgo.function.aggregator.AggregatorFunction;\n import org.ojalgo.function.aggregator.ComplexAggregator;\n import org.ojalgo.matrix.decomposition.function.ExchangeColumns;\n@@ -431,7 +431,7 @@ abstract class RawEigenvalue extends RawDecomposition implements Eigenvalue<Doub\n         // Copy the last column (same as the last row) of z to d\n         // The last row/column is the first to be worked on in the main loop\n \n-        COPY.invoke(data[last], 0, d, 0, 0, size);\n+        FillMatchingSingle.invoke(data[last], 0, d, 0, 0, size);\n \n         for (int m = last; m > 0; m--) { // row index of target householder point\n             // col index of target householder point\ndiff --git a/src/main/java/org/ojalgo/matrix/store/MatrixStore.java b/src/main/java/org/ojalgo/matrix/store/MatrixStore.java\nindex 948e68fa..bc0405e9 100644\n--- a/src/main/java/org/ojalgo/matrix/store/MatrixStore.java\n+++ b/src/main/java/org/ojalgo/matrix/store/MatrixStore.java\n@@ -22,6 +22,7 @@\n package org.ojalgo.matrix.store;\n \n import org.ojalgo.ProgrammingError;\n+import org.ojalgo.array.operation.AMAX;\n import org.ojalgo.function.UnaryFunction;\n import org.ojalgo.function.VoidFunction;\n import org.ojalgo.function.aggregator.Aggregator;\n@@ -63,7 +64,7 @@ import org.ojalgo.type.context.NumberContext;\n  * @author apete\n  */\n public interface MatrixStore<N extends Comparable<N>> extends Matrix2D<N, MatrixStore<N>>, ElementsSupplier<N>, Access2D.Visitable<N>, Access2D.Sliceable<N>,\n-        Access2D.Elements, Structure2D.ReducibleTo1D<ElementsSupplier<N>> {\n+        Access2D.Elements, Access2D.IndexOf, Structure2D.ReducibleTo1D<ElementsSupplier<N>> {\n \n     public interface Factory<N extends Comparable<N>> {\n \n@@ -427,11 +428,10 @@ public interface MatrixStore<N extends Comparable<N>> extends Matrix2D<N, Matrix\n         }\n \n         public void supplyTo(final TransformableRegion<N> receiver) {\n-            if (receiver.isAcceptable(this)) {\n-                receiver.accept(this.get());\n-            } else {\n+            if (!receiver.isAcceptable(this)) {\n                 throw new ProgrammingError("Not acceptable!");\n             }\n+            receiver.accept(this.get());\n         }\n \n         public LogicalBuilder<N> symmetric(final boolean upper) {\n@@ -768,6 +768,34 @@ public interface MatrixStore<N extends Comparable<N>> extends Matrix2D<N, Matrix\n         return this;\n     }\n \n+    default long indexOfLargestInColumn(final long row, final long col) {\n+        long structure = this.countRows();\n+        long first = Structure2D.index(structure, row, col);\n+        long limit = Structure2D.index(structure, 0L, col + 1L);\n+        long step = 1L;\n+        return AMAX.invoke(this, first, limit, step);\n+    }\n+\n+    default long indexOfLargestInRange(final long first, final long limit) {\n+        return AMAX.invoke(this, first, limit, 1L);\n+    }\n+\n+    default long indexOfLargestInRow(final long row, final long col) {\n+        long structure = this.countRows();\n+        long first = Structure2D.index(structure, row, col);\n+        long limit = Structure2D.index(structure, row, this.countColumns());\n+        long step = structure;\n+        return AMAX.invoke(this, first, limit, step);\n+    }\n+\n+    default long indexOfLargestOnDiagonal(final long start) {\n+        long structure = this.countRows();\n+        long first = Structure2D.index(structure, start, start);\n+        long limit = Structure2D.index(structure, structure, this.countColumns());\n+        long step = structure + 1L;\n+        return AMAX.invoke(this, first, limit, step);\n+    }\n+\n     default boolean isAbsolute(final long row, final long col) {\n         return this.toScalar(row, col).isAbsolute();\n     }\n@@ -786,9 +814,9 @@ public interface MatrixStore<N extends Comparable<N>> extends Matrix2D<N, Matrix\n             ComplexNumber lowerLeft;\n             ComplexNumber upperRight;\n \n-            for (int j = 0; retVal && (j < numberOfColumns); j++) {\n+            for (int j = 0; retVal && j < numberOfColumns; j++) {\n                 retVal &= PrimitiveScalar.isSmall(PrimitiveMath.ONE, ComplexNumber.valueOf(this.get(j, j)).i);\n-                for (int i = j + 1; retVal && (i < numberOfRows); i++) {\n+                for (int i = j + 1; retVal && i < numberOfRows; i++) {\n                     lowerLeft = ComplexNumber.valueOf(this.get(i, j)).conjugate();\n                     upperRight = ComplexNumber.valueOf(this.get(j, i));\n                     retVal &= PrimitiveScalar.isSmall(PrimitiveMath.ONE, lowerLeft.subtract(upperRight).norm());\n@@ -797,8 +825,8 @@ public interface MatrixStore<N extends Comparable<N>> extends Matrix2D<N, Matrix\n \n         } else {\n \n-            for (int j = 0; retVal && (j < numberOfColumns); j++) {\n-                for (int i = j + 1; retVal && (i < numberOfRows); i++) {\n+            for (int j = 0; retVal && j < numberOfColumns; j++) {\n+                for (int i = j + 1; retVal && i < numberOfRows; i++) {\n                     retVal &= PrimitiveScalar.isSmall(PrimitiveMath.ONE, this.doubleValue(i, j) - this.doubleValue(j, i));\n                 }\n             }\n@@ -900,11 +928,10 @@ public interface MatrixStore<N extends Comparable<N>> extends Matrix2D<N, Matrix\n \n         if (this.isVector()) {\n             return frobeniusNorm;\n-        } else {\n-            // Bringing it closer to what the operator norm would be\n-            // In case of representing a ComplexNumber or Quaternion as a matrix this will match their norms\n-            return frobeniusNorm / PrimitiveMath.SQRT.invoke((double) Math.min(this.countRows(), this.countColumns()));\n         }\n+        // Bringing it closer to what the operator norm would be\n+        // In case of representing a ComplexNumber or Quaternion as a matrix this will match their norms\n+        return frobeniusNorm / PrimitiveMath.SQRT.invoke((double) Math.min(this.countRows(), this.countColumns()));\n     }\n \n     default MatrixStore<N> onAll(final UnaryFunction<N> operator) {\n@@ -936,41 +963,42 @@ public interface MatrixStore<N extends Comparable<N>> extends Matrix2D<N, Matrix\n \n             return factory.builder().makeIdentity(this.countRows()).get();\n \n-        } else if (power == 1) {\n+        }\n+        if (power == 1) {\n \n             return this;\n \n-        } else if (power == 2) {\n+        }\n+        if (power == 2) {\n \n             return this.multiply(this);\n \n-        } else if ((power % 2) == 0) {\n+        }\n+        if (power % 2 == 0) {\n             // 4,6,8,10...\n \n             return this.power(2).power(power / 2);\n \n-        } else if (power > 8) {\n+        }\n+        if (power > 8) {\n             // 9,11,13,15...\n \n             return this.power(power - 1).multiply(this);\n \n-        } else {\n-            // 3,5,7\n-\n-            PhysicalStore<N> right = factory.make(this);\n-            PhysicalStore<N> product = factory.make(this);\n-            PhysicalStore<N> temp;\n-\n-            this.multiply(this, product);\n-            for (int i = 2; i < power; i++) {\n-                temp = right;\n-                right = product;\n-                product = temp;\n-                this.multiply(right, product);\n-            }\n+        }\n+        PhysicalStore<N> right = factory.make(this);\n+        PhysicalStore<N> product = factory.make(this);\n+        PhysicalStore<N> temp;\n \n-            return product;\n+        this.multiply(this, product);\n+        for (int i = 2; i < power; i++) {\n+            temp = right;\n+            right = product;\n+            product = temp;\n+            this.multiply(right, product);\n         }\n+\n+        return product;\n     }\n \n     /**\ndiff --git a/src/main/java/org/ojalgo/matrix/store/PhysicalStore.java b/src/main/java/org/ojalgo/matrix/store/PhysicalStore.java\nindex 8734fdf2..d9762b2d 100644\n--- a/src/main/java/org/ojalgo/matrix/store/PhysicalStore.java\n+++ b/src/main/java/org/ojalgo/matrix/store/PhysicalStore.java\n@@ -30,7 +30,6 @@ import org.ojalgo.function.FunctionSet;\n import org.ojalgo.function.aggregator.AggregatorSet;\n import org.ojalgo.matrix.transformation.Householder;\n import org.ojalgo.matrix.transformation.Rotation;\n-import org.ojalgo.netio.BasicLogger;\n import org.ojalgo.scalar.Scalar;\n import org.ojalgo.structure.Access1D;\n import org.ojalgo.structure.Access2D;\n@@ -156,8 +155,6 @@ public interface PhysicalStore<N extends Comparable<N>> extends MatrixStore<N>,\n     default void supplyTo(final TransformableRegion<N> receiver) {\n         if (this != receiver) {\n             receiver.fillMatching(this);\n-        } else {\n-            BasicLogger.error("Why do you do this!");\n         }\n     }\n \ndiff --git a/src/main/java/org/ojalgo/matrix/store/Primitive32Store.java b/src/main/java/org/ojalgo/matrix/store/Primitive32Store.java\nindex 267ee1b1..6ddc50b1 100644\n--- a/src/main/java/org/ojalgo/matrix/store/Primitive32Store.java\n+++ b/src/main/java/org/ojalgo/matrix/store/Primitive32Store.java\n@@ -882,6 +882,11 @@ public final class Primitive32Store extends Primitive32Array implements Physical\n         return myUtility.sliceRow(row, col);\n     }\n \n+    @Override\n+    public String toString() {\n+        return Access2D.toString(this);\n+    }\n+\n     public void substituteBackwards(final Access2D<Double> body, final boolean unitDiagonal, final boolean conjugated, final boolean hermitian) {\n \n         final int tmpRowDim = myRowDim;\ndiff --git a/src/main/java/org/ojalgo/netio/BasicLogger.java b/src/main/java/org/ojalgo/netio/BasicLogger.java\nindex 85e0fcd3..dc08a9fd 100644\n--- a/src/main/java/org/ojalgo/netio/BasicLogger.java\n+++ b/src/main/java/org/ojalgo/netio/BasicLogger.java\n@@ -21,6 +21,8 @@\n  */\n package org.ojalgo.netio;\n \n+import java.io.File;\n+import java.io.FileNotFoundException;\n import java.io.IOException;\n import java.io.PrintStream;\n import java.io.PrintWriter;\n@@ -97,7 +99,7 @@ public abstract class BasicLogger {\n         public Printer printf(final Locale locale, final String format, final Object... args) {\n \n             synchronized (myAppendable) {\n-                if ((myFormatter == null) || (myFormatter.locale() != locale)) {\n+                if (myFormatter == null || myFormatter.locale() != locale) {\n                     myFormatter = new Formatter(myAppendable, locale);\n                 }\n                 myFormatter.format(locale, format, args);\n@@ -109,7 +111,7 @@ public abstract class BasicLogger {\n         public Printer printf(final String format, final Object... args) {\n \n             synchronized (myAppendable) {\n-                if ((myFormatter == null) || (myFormatter.locale() != Locale.getDefault())) {\n+                if (myFormatter == null || myFormatter.locale() != Locale.getDefault()) {\n                     myFormatter = new Formatter(myAppendable);\n                 }\n                 myFormatter.format(Locale.getDefault(), format, args);\n@@ -778,6 +780,14 @@ public abstract class BasicLogger {\n         BasicLogger.println(DEBUG);\n     }\n \n+    public static void mkdirs(final File dir) {\n+        if (!dir.exists()) {\n+            if (!dir.mkdirs() && !dir.exists()) {\n+                throw new RuntimeException(new FileNotFoundException(dir.getAbsolutePath()));\n+            }\n+        }\n+    }\n+\n     public static void debug(final Object message) {\n         BasicLogger.println(DEBUG, message);\n     }\n@@ -862,12 +872,11 @@ public abstract class BasicLogger {\n             } else {\n                 return context.enforce(new BigDecimal(NumberDefinition.doubleValue(number))).toPlainString();\n             }\n+        }\n+        if (number instanceof Scalar<?>) {\n+            return ((Scalar<?>) number).toString(context);\n         } else {\n-            if (number instanceof Scalar<?>) {\n-                return ((Scalar<?>) number).toString(context);\n-            } else {\n-                return context.enforce(new BigDecimal(NumberDefinition.doubleValue(number))).toString();\n-            }\n+            return context.enforce(new BigDecimal(NumberDefinition.doubleValue(number))).toString();\n         }\n     }\n \n@@ -890,8 +899,8 @@ public abstract class BasicLogger {\n     }\n \n     static void printmtrx(final Printer appender, final Access2D<?> matrix, final NumberContext context) {\n-        if ((appender != null) && (matrix.count() > 0L)) {\n-            if ((matrix instanceof ComplexMatrix) || (matrix.get(0, 0) instanceof ComplexNumber)) {\n+        if (appender != null && matrix.count() > 0L) {\n+            if (matrix instanceof ComplexMatrix || matrix.get(0, 0) instanceof ComplexNumber) {\n                 BasicLogger.printmtrx(appender, matrix, context, false);\n             } else {\n                 BasicLogger.printmtrx(appender, matrix, context, true);\ndiff --git a/src/main/java/org/ojalgo/netio/IDX.java b/src/main/java/org/ojalgo/netio/IDX.java\nindex 0d5595e8..c5a9bf1c 100644\n--- a/src/main/java/org/ojalgo/netio/IDX.java\n+++ b/src/main/java/org/ojalgo/netio/IDX.java\n@@ -39,13 +39,13 @@ import org.ojalgo.structure.Access2D;\n  */\n public abstract class IDX {\n \n-    public static ArrayAnyD<Double> parse(final String filePath) {\n+    public static ArrayAnyD<Double> parse(final File filePath) {\n         return IDX.parse(filePath, Primitive32Array.FACTORY);\n     }\n \n-    public static ArrayAnyD<Double> parse(final String filePath, final DenseArray.Factory<Double> arrayFactory) {\n+    public static ArrayAnyD<Double> parse(final File filePath, final DenseArray.Factory<Double> arrayFactory) {\n \n-        try (DataInputStream input = new DataInputStream(new BufferedInputStream(new FileInputStream(new File(filePath))))) {\n+        try (DataInputStream input = new DataInputStream(new BufferedInputStream(new FileInputStream(filePath)))) {\n \n             input.read();\n             input.read();\n@@ -86,12 +86,19 @@ public abstract class IDX {\n \n             return data;\n \n-        } catch (IOException exception) {\n-\n-            return null;\n+        } catch (IOException cause) {\n+            throw new RuntimeException(cause);\n         }\n     }\n \n+    public static ArrayAnyD<Double> parse(final String filePath) {\n+        return IDX.parse(new File(filePath));\n+    }\n+\n+    public static ArrayAnyD<Double> parse(final String filePath, final DenseArray.Factory<Double> arrayFactory) {\n+        return IDX.parse(new File(filePath), arrayFactory);\n+    }\n+\n     public static void print(final Access2D<?> image, final BasicLogger.Printer printer) {\n         IDX.print(image, printer, true);\n     }\n@@ -109,7 +116,7 @@ public abstract class IDX {\n     public static void print(final Access2D<?> image, final BasicLogger.Printer printer, final boolean transpose, final double maxExpectedValue) {\n \n         double oneThird = maxExpectedValue / 3D;\n-        double twoThirds = (2D * maxExpectedValue) / 3D;\n+        double twoThirds = 2D * maxExpectedValue / 3D;\n \n         long numbRows = image.countRows();\n         long numbCols = image.countColumns();\ndiff --git a/src/main/java/org/ojalgo/optimisation/linear/SimplexTableau.java b/src/main/java/org/ojalgo/optimisation/linear/SimplexTableau.java\nindex d3e600ea..2a3810be 100644\n--- a/src/main/java/org/ojalgo/optimisation/linear/SimplexTableau.java\n+++ b/src/main/java/org/ojalgo/optimisation/linear/SimplexTableau.java\n@@ -302,17 +302,6 @@ abstract class SimplexTableau implements AlgorithmStore, Access2D<Double> {\n         Mutate2D newConstraintsBody() {\n             return new Mutate2D() {\n \n-                public void add(final long row, final long col, final Comparable<?> addend) {\n-                    this.add(row, col, NumberDefinition.doubleValue(addend));\n-                }\n-\n-                public void add(final long row, final long col, final double addend) {\n-                    //                    myRows[(int) row].add(col, addend);\n-                    //                    myPhase1Weights.add(col, -addend);\n-                    myTransposed.add(col, row, addend);\n-                    myTransposed.add(col, DenseTableau.this.countConstraints() + 1, -addend);\n-                }\n-\n                 public long countColumns() {\n                     return DenseTableau.this.countVariables();\n                 }\n@@ -345,19 +334,6 @@ abstract class SimplexTableau implements AlgorithmStore, Access2D<Double> {\n \n             return new Mutate1D() {\n \n-                public void add(final long index, final Comparable<?> addend) {\n-                    this.add(index, NumberDefinition.doubleValue(addend));\n-                }\n-\n-                public void add(final long index, final double addend) {\n-                    //                    myRows[(int) index].set(SparseTableau.this.countVariables() + index, ONE);\n-                    //                    myRHS.add(index, addend);\n-                    //                    myInfeasibility -= addend;\n-                    myTransposed.set(numbVar + index, index, ONE);\n-                    myTransposed.add(col, index, addend);\n-                    myTransposed.add(col, numbConstr + 1, -addend);\n-                }\n-\n                 public long count() {\n                     return DenseTableau.this.countConstraints();\n                 }\n@@ -385,15 +361,6 @@ abstract class SimplexTableau implements AlgorithmStore, Access2D<Double> {\n \n             return new Mutate1D() {\n \n-                public void add(final long index, final Comparable<?> addend) {\n-                    this.add(index, NumberDefinition.doubleValue(addend));\n-                }\n-\n-                public void add(final long index, final double addend) {\n-                    // myObjectiveWeights.add(index, addend);\n-                    myTransposed.add(index, row, addend);\n-                }\n-\n                 public long count() {\n                     return DenseTableau.this.countVariables();\n                 }\n@@ -403,7 +370,6 @@ abstract class SimplexTableau implements AlgorithmStore, Access2D<Double> {\n                 }\n \n                 public void set(final long index, final double value) {\n-                    // myObjectiveWeights.set(index, value);\n                     myTransposed.set(index, row, value);\n                 }\n \n@@ -527,24 +493,21 @@ abstract class SimplexTableau implements AlgorithmStore, Access2D<Double> {\n             final int myNumberOfVariables = this.countVariables();\n \n             if (row < myNumberOfConstraints) {\n-                if (col < (myNumberOfVariables + myNumberOfConstraints)) {\n+                if (col < myNumberOfVariables + myNumberOfConstraints) {\n                     return myRows[(int) row].doubleValue(col);\n-                } else {\n-                    return myRHS.doubleValue(row);\n                 }\n-            } else if (row == myNumberOfConstraints) {\n-                if (col < (myNumberOfVariables + myNumberOfConstraints)) {\n+                return myRHS.doubleValue(row);\n+            }\n+            if (row == myNumberOfConstraints) {\n+                if (col < myNumberOfVariables + myNumberOfConstraints) {\n                     return myObjectiveWeights.doubleValue(col);\n-                } else {\n-                    return myValue;\n-                }\n-            } else {\n-                if (col < (myNumberOfVariables + myNumberOfConstraints)) {\n-                    return myPhase1Weights.doubleValue(col);\n-                } else {\n-                    return myInfeasibility;\n                 }\n+                return myValue;\n+            }\n+            if (col < myNumberOfVariables + myNumberOfConstraints) {\n+                return myPhase1Weights.doubleValue(col);\n             }\n+            return myInfeasibility;\n         }\n \n         public Double get(final long row, final long col) {\n@@ -587,9 +550,8 @@ abstract class SimplexTableau implements AlgorithmStore, Access2D<Double> {\n                 final UnaryFunction<Double> modifier = DIVIDE.second(pivotElement);\n                 pivotBody.modifyAll(modifier);\n                 return modifier.invoke(pivotRHS);\n-            } else {\n-                return pivotRHS;\n             }\n+            return pivotRHS;\n \n             //            if (ABS.invoke(pivotElement) < ONE) {\n             //                final UnaryFunction<Double> tmpModifier = DIVIDE.second(pivotElement);\n@@ -731,41 +693,40 @@ abstract class SimplexTableau implements AlgorithmStore, Access2D<Double> {\n \n         @Override\n         protected Access1D<Double> sliceTableauColumn(final int col) {\n-            if (col < this.countVariablesTotally()) {\n-                return new Access1D<Double>() {\n+            if (col >= this.countVariablesTotally()) {\n+                return myRHS;\n+            }\n+            return new Access1D<Double>() {\n \n-                    public long count() {\n-                        return SparseTableau.this.countConstraints();\n-                    }\n+                public long count() {\n+                    return SparseTableau.this.countConstraints();\n+                }\n \n-                    public double doubleValue(final long index) {\n-                        return myRows[(int) index].doubleValue(col);\n-                    }\n+                public double doubleValue(final long index) {\n+                    return myRows[(int) index].doubleValue(col);\n+                }\n \n-                    public Double get(final long index) {\n-                        return myRows[(int) index].get(col);\n-                    }\n+                public Double get(final long index) {\n+                    return myRows[(int) index].get(col);\n+                }\n \n-                    @Override\n-                    public String toString() {\n-                        return Access1D.toString(this);\n-                    }\n+                @Override\n+                public String toString() {\n+                    return Access1D.toString(this);\n+                }\n \n-                };\n-            } else {\n-                return myRHS;\n-            }\n+            };\n         }\n \n         @Override\n         protected Access1D<Double> sliceTableauRow(final int row) {\n             if (row < this.countConstraints()) {\n                 return myRows[row];\n-            } else if (row == this.countConstraints()) {\n+            }\n+            if (row == this.countConstraints()) {\n                 return myObjectiveWeights;\n-            } else {\n-                return myPhase1Weights;\n             }\n+            return myPhase1Weights;\n         }\n \n         @Override\n@@ -784,15 +745,6 @@ abstract class SimplexTableau implements AlgorithmStore, Access2D<Double> {\n         Mutate2D newConstraintsBody() {\n             return new Mutate2D() {\n \n-                public void add(final long row, final long col, final Comparable<?> addend) {\n-                    this.add(row, col, NumberDefinition.doubleValue(addend));\n-                }\n-\n-                public void add(final long row, final long col, final double addend) {\n-                    myRows[(int) row].add(col, addend);\n-                    myPhase1Weights.add(col, -addend);\n-                }\n-\n                 public long countColumns() {\n                     return SparseTableau.this.countVariables();\n                 }\n@@ -817,16 +769,6 @@ abstract class SimplexTableau implements AlgorithmStore, Access2D<Double> {\n         Mutate1D newConstraintsRHS() {\n             return new Mutate1D() {\n \n-                public void add(final long index, final Comparable<?> addend) {\n-                    this.add(index, NumberDefinition.doubleValue(addend));\n-                }\n-\n-                public void add(final long index, final double addend) {\n-                    myRows[(int) index].set(SparseTableau.this.countVariables() + index, ONE);\n-                    myRHS.add(index, addend);\n-                    myInfeasibility -= addend;\n-                }\n-\n                 public long count() {\n                     return SparseTableau.this.countConstraints();\n                 }\n@@ -848,14 +790,6 @@ abstract class SimplexTableau implements AlgorithmStore, Access2D<Double> {\n         Mutate1D newObjective() {\n             return new Mutate1D() {\n \n-                public void add(final long index, final Comparable<?> addend) {\n-                    this.add(index, NumberDefinition.doubleValue(addend));\n-                }\n-\n-                public void add(final long index, final double addend) {\n-                    myObjectiveWeights.add(index, addend);\n-                }\n-\n                 public long count() {\n                     return SparseTableau.this.countVariables();\n                 }\n@@ -907,20 +841,18 @@ abstract class SimplexTableau implements AlgorithmStore, Access2D<Double> {\n             // Max number of elements in CPU cache\n             long maxCount = OjAlgoUtils.ENVIRONMENT.getCacheElements(JavaType.DOUBLE.memory());\n \n-            if ((totCount <= maxCount) || ((numberOfProblemVariables <= numberOfConstraints) && (totCount <= (2L * maxCount)))) {\n+            if (totCount <= maxCount || numberOfProblemVariables <= numberOfConstraints && totCount <= 2L * maxCount) {\n                 return new DenseTableau(numberOfConstraints, numberOfProblemVariables, numberOfSlackVariables);\n-            } else {\n-                return new SparseTableau(numberOfConstraints, numberOfProblemVariables, numberOfSlackVariables);\n             }\n+            return new SparseTableau(numberOfConstraints, numberOfProblemVariables, numberOfSlackVariables);\n \n-        } else if (options.sparse) {\n+        }\n+        if (options.sparse) {\n \n             return new SparseTableau(numberOfConstraints, numberOfProblemVariables, numberOfSlackVariables);\n \n-        } else {\n-\n-            return new DenseTableau(numberOfConstraints, numberOfProblemVariables, numberOfSlackVariables);\n         }\n+        return new DenseTableau(numberOfConstraints, numberOfProblemVariables, numberOfSlackVariables);\n     }\n \n     private final int[] myBasis;\n@@ -1012,7 +944,6 @@ abstract class SimplexTableau implements AlgorithmStore, Access2D<Double> {\n         int row = this.getBasisRowIndex(index);\n \n         if (row < 0) {\n-            return false;\n         }\n \n         return false;\ndiff --git a/src/main/java/org/ojalgo/structure/Access1D.java b/src/main/java/org/ojalgo/structure/Access1D.java\nindex 0739458b..a2911850 100644\n--- a/src/main/java/org/ojalgo/structure/Access1D.java\n+++ b/src/main/java/org/ojalgo/structure/Access1D.java\n@@ -91,7 +91,7 @@ public interface Access1D<N extends Comparable<N>> extends Structure1D {\n         @Deprecated\n         default boolean isAllSmall(final double comparedTo) {\n             boolean retVal = true;\n-            for (long i = 0L, limit = this.count(); retVal && (i < limit); i++) {\n+            for (long i = 0L, limit = this.count(); retVal && i < limit; i++) {\n                 retVal &= this.isSmall(i, comparedTo);\n             }\n             return retVal;\n@@ -175,7 +175,7 @@ public interface Access1D<N extends Comparable<N>> extends Structure1D {\n \n             if (remaining > 1L) {\n \n-                final long split = myCursor + (remaining / 2L);\n+                final long split = myCursor + remaining / 2L;\n \n                 final ElementView<N> retVal = new ElementView<>(myValues, myCursor, split);\n \n@@ -183,24 +183,14 @@ public interface Access1D<N extends Comparable<N>> extends Structure1D {\n \n                 return retVal;\n \n-            } else {\n-\n-                return null;\n             }\n+            return null;\n         }\n \n     }\n \n-    /**\n-     * @deprecated v48 Will be removed\n-     */\n-    @Deprecated\n     public interface IndexOf extends Structure1D {\n \n-        /**\n-         * @deprecated v48 Will be removed\n-         */\n-        @Deprecated\n         default long indexOfLargest() {\n             return this.indexOfLargestInRange(0L, this.count());\n         }\n@@ -266,7 +256,7 @@ public interface Access1D<N extends Comparable<N>> extends Structure1D {\n \n         boolean retVal = length == accessB.count();\n \n-        for (int i = 0; retVal && (i < length); i++) {\n+        for (int i = 0; retVal && i < length; i++) {\n             retVal &= !accuracy.isDifferent(accessA.doubleValue(i), accessB.doubleValue(i));\n         }\n \ndiff --git a/src/main/java/org/ojalgo/structure/Access2D.java b/src/main/java/org/ojalgo/structure/Access2D.java\nindex 715b2c96..ece4b596 100644\n--- a/src/main/java/org/ojalgo/structure/Access2D.java\n+++ b/src/main/java/org/ojalgo/structure/Access2D.java\n@@ -127,7 +127,7 @@ public interface Access2D<N extends Comparable<N>> extends Structure2D, Access1D\n         default boolean isColumnSmall(final long row, final long col, final double comparedTo) {\n             boolean retVal = true;\n             final long tmpLimit = this.countRows();\n-            for (long i = row; retVal && (i < tmpLimit); i++) {\n+            for (long i = row; retVal && i < tmpLimit; i++) {\n                 retVal &= this.isSmall(i, col, comparedTo);\n             }\n             return retVal;\n@@ -150,7 +150,7 @@ public interface Access2D<N extends Comparable<N>> extends Structure2D, Access1D\n         default boolean isRowSmall(final long row, final long col, final double comparedTo) {\n             boolean retVal = true;\n             final long tmpLimit = this.countColumns();\n-            for (long j = col; retVal && (j < tmpLimit); j++) {\n+            for (long j = col; retVal && j < tmpLimit; j++) {\n                 retVal &= this.isSmall(row, j, comparedTo);\n             }\n             return retVal;\n@@ -247,9 +247,8 @@ public interface Access2D<N extends Comparable<N>> extends Structure2D, Access1D\n \n             if (delegateSpliterator != null) {\n                 return new ElementView<>(delegateSpliterator, myStructure);\n-            } else {\n-                return null;\n             }\n+            return null;\n         }\n \n     }\n@@ -275,7 +274,10 @@ public interface Access2D<N extends Comparable<N>> extends Structure2D, Access1D\n          * @deprecated v48 Will be removed\n          */\n         @Deprecated\n-        long indexOfLargestInColumn(final long row, final long col);\n+        default long indexOfLargestInColumn(final long row, final long col) {\n+            long structure = this.countRows();\n+            return this.indexOfLargestInRange(Structure2D.index(structure, row, col), Structure2D.index(structure, 0, col + 1));\n+        }\n \n         /**\n          * @deprecated v48 Will be removed\n@@ -411,8 +413,7 @@ public interface Access2D<N extends Comparable<N>> extends Structure2D, Access1D\n     }\n \n     static boolean equals(final Access2D<?> accessA, final Access2D<?> accessB, final NumberContext accuracy) {\n-        return (accessA.countRows() == accessB.countRows()) && (accessA.countColumns() == accessB.countColumns())\n-                && Access1D.equals(accessA, accessB, accuracy);\n+        return accessA.countRows() == accessB.countRows() && accessA.countColumns() == accessB.countColumns() && Access1D.equals(accessA, accessB, accuracy);\n     }\n \n     /**\n@@ -430,13 +431,13 @@ public interface Access2D<N extends Comparable<N>> extends Structure2D, Access1D\n \n         if (anyElement instanceof ComplexNumber) {\n \n-            for (int j = 0; retVal && (j < cols); j++) {\n+            for (int j = 0; retVal && j < cols; j++) {\n \n                 double imagDiag = ComplexNumber.valueOf(matrix.get(j, j)).i;\n \n                 retVal &= PrimitiveScalar.isSmall(PrimitiveMath.ONE, imagDiag);\n \n-                for (int i = j + 1; retVal && (i < rows); i++) {\n+                for (int i = j + 1; retVal && i < rows; i++) {\n \n                     ComplexNumber lowerLeft = ComplexNumber.valueOf(matrix.get(i, j)).conjugate();\n                     ComplexNumber upperRight = ComplexNumber.valueOf(matrix.get(j, i));\n@@ -450,8 +451,8 @@ public interface Access2D<N extends Comparable<N>> extends Structure2D, Access1D\n \n         } else {\n \n-            for (int j = 0; retVal && (j < cols); j++) {\n-                for (int i = j + 1; retVal && (i < rows); i++) {\n+            for (int j = 0; retVal && j < cols; j++) {\n+                for (int i = j + 1; retVal && i < rows; i++) {\n \n                     double lowerLeft = matrix.doubleValue(i, j);\n                     double upperRight = matrix.doubleValue(j, i);\n@@ -515,7 +516,7 @@ public interface Access2D<N extends Comparable<N>> extends Structure2D, Access1D\n         builder.append(matrix.getClass().getName());\n         builder.append(\' \').append(\'<\').append(\' \').append(numbRows).append(\' \').append(\'x\').append(\' \').append(numbCols).append(\' \').append(\'>\');\n \n-        if ((numbRows > 0) && (numbCols > 0) && (numbRows <= 50) && (numbCols <= 50) && ((numbRows * numbCols) <= 200)) {\n+        if (numbRows > 0 && numbCols > 0 && numbRows <= 50 && numbCols <= 50 && numbRows * numbCols <= 200) {\n \n             // First element\n             builder.append("\\n{ { ").append(matrix.get(0, 0));\ndiff --git a/src/main/java/org/ojalgo/type/format/NumberStyle.java b/src/main/java/org/ojalgo/type/format/NumberStyle.java\nindex d0d08ce4..9b78c2c2 100644\n--- a/src/main/java/org/ojalgo/type/format/NumberStyle.java\n+++ b/src/main/java/org/ojalgo/type/format/NumberStyle.java\n@@ -23,11 +23,73 @@ package org.ojalgo.type.format;\n \n import java.text.NumberFormat;\n import java.util.Locale;\n+import java.util.function.LongFunction;\n \n public enum NumberStyle {\n \n     CURRENCY, GENERAL, INTEGER, PERCENT, SCIENTIFIC;\n \n+    static final class IntegerToUniformString {\n+\n+        private static final String PADDING = "00000000000000000000";\n+\n+        static int numberOfDigits(final long number) {\n+\n+            if (number == 0) {\n+                return 1;\n+            }\n+\n+            long nmbr = number;\n+            int count = 0;\n+            while (nmbr != 0) {\n+                nmbr /= 10;\n+                count++;\n+            }\n+            return count;\n+        }\n+\n+        static String toStringWithFixedNumberOfDigits(final long value, final int numberOfDigits) {\n+            String retVal = Long.toString(value);\n+            retVal = PADDING + retVal;\n+            return retVal.substring(retVal.length() - numberOfDigits);\n+        }\n+\n+        private final int myMaxNumberOfDigits;\n+\n+        IntegerToUniformString(final long rangeSize) {\n+            super();\n+            myMaxNumberOfDigits = IntegerToUniformString.numberOfDigits(rangeSize - 1);\n+        }\n+\n+        public String toString(final long value) {\n+            return IntegerToUniformString.toStringWithFixedNumberOfDigits(value, myMaxNumberOfDigits);\n+        }\n+\n+    }\n+\n+    /**\n+     * Similar to {@link NumberStyle#toUniformString(long, long)} but return a "formatter" with the specified\n+     * range size.\n+     *\n+     * @param rangeSize The max number of integers\n+     * @return An integer-to-string function\n+     */\n+    public static LongFunction<String> newUniformFormatter(final long rangeSize) {\n+        IntegerToUniformString formatter = new IntegerToUniformString(rangeSize);\n+        return formatter::toString;\n+    }\n+\n+    /**\n+     * Creates String:s like "0067" and "0004" rather than "67" and "4" given an integer range (max number of\n+     * integers).\n+     */\n+    public static String toUniformString(final long value, final long rangeSize) {\n+\n+        int maxNumberOfDigits = IntegerToUniformString.numberOfDigits(rangeSize - 1);\n+\n+        return IntegerToUniformString.toStringWithFixedNumberOfDigits(value, maxNumberOfDigits);\n+    }\n+\n     public NumberFormat getFormat() {\n         return this.getFormat(Locale.getDefault());\n     }\ndiff --git a/src/test/java/org/ojalgo/ann/BatchTest.java b/src/test/java/org/ojalgo/ann/BatchTest.java\nnew file mode 100644\nindex 00000000..30448ea1\n--- /dev/null\n+++ b/src/test/java/org/ojalgo/ann/BatchTest.java\n@@ -0,0 +1,128 @@\n+/*\n+ * Copyright 1997-2021 Optimatika\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the "Software"), to deal\n+ * in the Software without restriction, including without limitation the rights\n+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+ * copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n+ * SOFTWARE.\n+ */\n+package org.ojalgo.ann;\n+\n+import java.io.File;\n+\n+import org.junit.jupiter.api.Test;\n+import org.junit.jupiter.api.io.TempDir;\n+import org.ojalgo.TestUtils;\n+import org.ojalgo.ann.ArtificialNeuralNetwork.Activator;\n+import org.ojalgo.array.Primitive64Array;\n+import org.ojalgo.data.DataBatch;\n+import org.ojalgo.matrix.store.MatrixStore;\n+import org.ojalgo.matrix.store.Primitive64Store;\n+import org.ojalgo.random.Uniform;\n+\n+public class BatchTest extends ANNTest {\n+\n+    private static final int BATCH_SIZE = 6;\n+    private static final int NB_INPUTS = 4;\n+    private static final int NB_OUTPUTS = 2;\n+\n+    static ArtificialNeuralNetwork generate() {\n+        return ArtificialNeuralNetwork.builder(Primitive64Store.FACTORY, NB_INPUTS).layer(3, Activator.RECTIFIER).layer(5, Activator.SIGMOID)\n+                .layer(NB_OUTPUTS, Activator.SOFTMAX).get();\n+    }\n+\n+    @TempDir\n+    File dirBatchTest;\n+\n+    @Test\n+    public void testInvoke() {\n+\n+        ArtificialNeuralNetwork ann = BatchTest.generate();\n+\n+        NetworkInvoker invSngl = ann.newInvoker();\n+        NetworkInvoker invBtch = ann.newInvoker(BATCH_SIZE);\n+\n+        DataBatch batch = invBtch.newInputBatch();\n+\n+        Primitive64Array input;\n+        MatrixStore<?>[] expected = new MatrixStore<?>[BATCH_SIZE];\n+\n+        for (int i = 0; i < BATCH_SIZE; i++) {\n+            input = Primitive64Array.make(NB_INPUTS);\n+            input.fillAll(Uniform.standard());\n+            batch.addRow(input);\n+\n+            expected[i] = invSngl.invoke(input).copy();\n+        }\n+\n+        MatrixStore<Double> actual = invBtch.invoke(batch);\n+\n+        for (int i = 0; i < BATCH_SIZE; i++) {\n+            TestUtils.assertEquals(expected[i], actual.sliceRow(i));\n+        }\n+    }\n+\n+    /**\n+     * Create batches of identical training examples (equivalent to training multiple times using the same\n+     * examples) and adjust the learning rate to get the exact same results.\n+     */\n+    @Test\n+    public void testTrainBatchOfEquals() {\n+\n+        ArtificialNeuralNetwork ann1 = BatchTest.generate();\n+        NetworkTrainer trnSngl = ann1.newTrainer().rate(1.0);\n+        NetworkInvoker invSngl = ann1.newInvoker();\n+\n+        ArtificialNeuralNetwork ann2 = this.copy(ann1);\n+        NetworkTrainer trnBtch = ann2.newTrainer(BATCH_SIZE).rate(1.0 / BATCH_SIZE);\n+        NetworkInvoker invBtch = ann2.newInvoker();\n+\n+        DataBatch inputBatch = trnBtch.newInputBatch();\n+        DataBatch outputBatch = trnBtch.newOutputBatch();\n+\n+        for (int l = 0; l < 10; l++) {\n+\n+            inputBatch.reset();\n+            outputBatch.reset();\n+\n+            Primitive64Array input = Primitive64Array.make(NB_INPUTS);\n+            input.fillAll(Uniform.standard());\n+\n+            Primitive64Array output = Primitive64Array.make(NB_OUTPUTS);\n+            output.set(l % NB_OUTPUTS, 1.0);\n+\n+            for (int b = 0; b < BATCH_SIZE; b++) {\n+                inputBatch.addRow(input);\n+                outputBatch.addRow(output);\n+            }\n+\n+            trnSngl.train(input, output);\n+            trnBtch.train(inputBatch, outputBatch);\n+\n+            MatrixStore<Double> outSngl = invSngl.invoke(input);\n+            MatrixStore<Double> outBtch = invBtch.invoke(input);\n+\n+            TestUtils.assertEquals(outSngl, outBtch);\n+        }\n+    }\n+\n+    private ArtificialNeuralNetwork copy(final ArtificialNeuralNetwork source) {\n+        File copyFile = new File(dirBatchTest, "copy.ann");\n+        source.writeTo(copyFile);\n+        return ArtificialNeuralNetwork.from(copyFile);\n+    }\n+\n+}\ndiff --git a/src/test/java/org/ojalgo/array/LongToNumberMapTest.java b/src/test/java/org/ojalgo/array/LongToNumberMapTest.java\nindex 3fe5ccc1..7790f09a 100644\n--- a/src/test/java/org/ojalgo/array/LongToNumberMapTest.java\n+++ b/src/test/java/org/ojalgo/array/LongToNumberMapTest.java\n@@ -28,7 +28,6 @@ import java.util.TreeMap;\n \n import org.junit.jupiter.api.Test;\n import org.ojalgo.TestUtils;\n-import org.ojalgo.function.constant.PrimitiveMath;\n import org.ojalgo.function.special.PowerOf2;\n import org.ojalgo.type.context.NumberContext;\n \n', 'commit_subject': 'ANN batches and other improvements (#359)', 'repo_name': 'optimatika/ojAlgo', 'committer': {'name': 'GitHub', 'email': '1505422b2465e9a84f6fdfaa161078890c593f06@github.com', 'time_sec': '1628259496', 'tz_offset': '120', 'date': {'seconds': '1628259496'}}, 'commit_tree': '4d76d4edf84fe29b67f2835d6bbcecf804db35ee', 'commit_message': 'ANN batches and other improvements (#359)\n\n', 'language_name': 'Shell', 'license': 'mit', 'hunks': '@@ -28,7 +28,6 @@ import java.util.TreeMap;\n \n import org.junit.jupiter.api.Test;\n import org.ojalgo.TestUtils;\n-import org.ojalgo.function.constant.PrimitiveMath;\n import org.ojalgo.function.special.PowerOf2;\n import org.ojalgo.type.context.NumberContext;\n \n', 'addition_count': 0, 'deletion_count': 1, 'src_file': 'a/src/test/java/org/ojalgo/array/LongToNumberMapTest.java', 'tgt_file': 'b/src/test/java/org/ojalgo/array/LongToNumberMapTest.java', 'file_extension': '.java', 'before_file': '/*\n * Copyright 1997-2022 Optimatika\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the "Software"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\npackage org.ojalgo.array;\n\nimport java.util.Map.Entry;\nimport java.util.Random;\nimport java.util.SortedMap;\nimport java.util.TreeMap;\n\nimport org.junit.jupiter.api.Test;\nimport org.ojalgo.TestUtils;\nimport org.ojalgo.function.constant.PrimitiveMath;\nimport org.ojalgo.function.special.PowerOf2;\nimport org.ojalgo.type.context.NumberContext;\n\n * LongToNumberMap\n *\n * @author apete\n */\npublic class LongToNumberMapTest extends ArrayTests {\n\n    private static final Random RANDOM = new Random();\n\n    @Test\n    public void testAlignCapacity() {\n        TestUtils.assertEquals(1, 1L << PowerOf2.powerOf2Larger(-1L));\n        TestUtils.assertEquals(16, 1L << PowerOf2.powerOf2Larger(16L));\n        TestUtils.assertEquals(512, 1L << PowerOf2.powerOf2Larger(365L));\n        TestUtils.assertEquals(16_384, 1L << PowerOf2.powerOf2Larger(16_384L));\n    }\n\n    @Test\n    public void testCompareWithTreeMap() {\n\n        final LongToNumberMap<Double> primit64Map = LongToNumberMap.factory(ArrayR064.FACTORY).make();\n        final LongToNumberMap<Double> direct64Map = LongToNumberMap.factory(BufferArray.DIRECT64).make();\n\n        final SortedMap<Long, Double> expectedMap = new TreeMap<>();\n\n        for (long index = 0L; index < 1_000; index++) {\n\n            final Double value = RANDOM.nextDouble();\n\n            primit64Map.put(index, value);\n            direct64Map.put(index, value);\n            expectedMap.put(index, value);\n        }\n\n        for (int c = 0; c < 1_000; c++) {\n\n            final Long index = Long.valueOf(RANDOM.nextInt(1_000));\n            final Double value = RANDOM.nextDouble();\n\n            primit64Map.put(index, value);\n            direct64Map.put(index, value);\n            expectedMap.put(index, value);\n        }\n\n        for (int c = 0; c < 1_000; c++) {\n\n            final Long index = Long.valueOf(RANDOM.nextInt(1_000));\n\n            primit64Map.remove(index);\n            direct64Map.remove(index);\n            expectedMap.remove(index);\n        }\n\n        for (long index = 0L; index < 100; index++) {\n            primit64Map.remove(index);\n            direct64Map.remove(index);\n            expectedMap.remove(index);\n        }\n\n        TestUtils.assertEquals(expectedMap.size(), primit64Map.size());\n        TestUtils.assertEquals(expectedMap.size(), direct64Map.size());\n\n        for (final Entry<Long, Double> entry : expectedMap.entrySet()) {\n            final double expectedValue = entry.getValue().doubleValue();\n            TestUtils.assertEquals(expectedValue, primit64Map.get(entry.getKey()).doubleValue());\n            TestUtils.assertEquals(expectedValue, primit64Map.doubleValue(entry.getKey()));\n            TestUtils.assertEquals(expectedValue, direct64Map.get(entry.getKey()).doubleValue());\n            TestUtils.assertEquals(expectedValue, direct64Map.doubleValue(entry.getKey()));\n        }\n\n    }\n\n    @Test\n    public void testSubmap() {\n\n        final LongToNumberMap<Double> tmpMap = LongToNumberMap.factory(ArrayR064.FACTORY).make();\n\n        tmpMap.put(10, 1);\n        tmpMap.put(20, 2);\n        tmpMap.put(50, 5);\n        tmpMap.put(100, 1);\n        tmpMap.put(200, 2);\n        tmpMap.put(500, 5);\n        tmpMap.put(1000, 1);\n\n        final LongToNumberMap<Double> tmpHeadMap = tmpMap.headMap(100L);\n        TestUtils.assertEquals(3, tmpHeadMap.size());\n        double tmpHeadSum = 0.0;\n        for (final Entry<Long, Double> tmpEntry : tmpHeadMap.entrySet()) {\n            tmpHeadSum += tmpEntry.getValue();\n        }\n        TestUtils.assertEquals(8.0, tmpHeadSum);\n\n        final LongToNumberMap<Double> tmpTailMap = tmpMap.tailMap(100L);\n        TestUtils.assertEquals(4, tmpTailMap.size());\n        double tmpTailSum = 0.0;\n        for (final Entry<Long, Double> tmpEntry : tmpTailMap.entrySet()) {\n            tmpTailSum += tmpEntry.getValue();\n        }\n        TestUtils.assertEquals(9.0, tmpTailSum);\n\n        final LongToNumberMap<Double> tmpSubMap = tmpMap.subMap(35, 350);\n        TestUtils.assertEquals(3, tmpSubMap.size());\n        double tmpSubSum = 0.0;\n        for (final Entry<Long, Double> tmpEntry : tmpSubMap.entrySet()) {\n            tmpSubSum += tmpEntry.getValue();\n        }\n        TestUtils.assertEquals(8.0, tmpSubSum);\n        TestUtils.assertEquals(tmpSubMap.values(), tmpMap.values(35, 350));\n\n        final LongToNumberMap<Double> tmpSubMap2 = tmpMap.subMap(50, 350);\n        TestUtils.assertEquals(3, tmpSubMap2.size());\n        double tmpSubSum2 = 0.0;\n        for (final Entry<Long, Double> tmpEntry : tmpSubMap2.entrySet()) {\n            tmpSubSum2 += tmpEntry.getValue();\n        }\n        TestUtils.assertEquals(8.0, tmpSubSum2);\n        TestUtils.assertEquals(tmpSubMap.values(), tmpMap.values(50, 350));\n\n        final LongToNumberMap<Double> tmpSubMap3 = tmpMap.subMap(35, 500);\n        TestUtils.assertEquals(3, tmpSubMap3.size());\n        double tmpSubSum3 = 0.0;\n        for (final Entry<Long, Double> tmpEntry : tmpSubMap3.entrySet()) {\n            tmpSubSum3 += tmpEntry.getValue();\n        }\n        TestUtils.assertEquals(8.0, tmpSubSum3);\n        TestUtils.assertEquals(tmpSubMap.values(), tmpMap.values(35, 500));\n\n        TestUtils.assertEquals(7, tmpMap.size());\n        double tmpKeySum = 0.0;\n        double tmpValSum = 0.0;\n        for (final Entry<Long, Double> tmpEntry : tmpMap.entrySet()) {\n            tmpKeySum += tmpEntry.getKey();\n            tmpValSum += tmpEntry.getValue();\n        }\n        TestUtils.assertEquals(1880.0, tmpKeySum);\n        TestUtils.assertEquals(17.0, tmpValSum);\n\n        tmpKeySum = 0.0;\n        for (final Long tmpKey : tmpMap.keySet()) {\n            tmpKeySum += tmpKey;\n        }\n        TestUtils.assertEquals(1880.0, tmpKeySum);\n\n        tmpValSum = 0.0;\n        for (final Double tmpValue : tmpMap.values()) {\n            tmpValSum += tmpValue;\n        }\n        TestUtils.assertEquals(17.0, tmpValSum);\n\n    }\n\n}\n', 'message': 'ANN batches and other improvements (#359)\n\n', 'author_name': 'GitHub', 'lang': 'java'}