Skip to content

Create Custom MatrixStore

apete edited this page Sep 14, 2020 · 8 revisions

An updated version of this page can be found at https://www.ojalgo.org/2020/09/sparse-and-special-structure-matrices/


ojAlgo's MatrixStore interface largely defines what you can do with matrices, and the various implementations store the elements differently. There's a whole collection of implementations already in ojAlgo, but maybe you want to implement another one exploiting some special structure.

Here's an example how to implement a simple eye matrix. From this it should be possible to deduce how to implement any other special structered matrix.

There are 2 implementations described in the example:

  1. MostBasicEye just implements the 4 methods that all MatrixStore implementations have to implement. As you can see these are trivial to implement, and this class is already fully functional.
  2. BetterPerformingEye extends MostBasicEye and adds a few methods that make it perform better. It's just a handfull additional methods that would be easy to implement in most cases (in this case it's trivial) but doing so makes a big difference.

To furher improve things you may override any/all additional methods. Which is particular to the specific matrix structure and use case.

Example code

import static org.ojalgo.constant.PrimitiveMath.*;

import org.ojalgo.OjAlgoUtils;
import org.ojalgo.matrix.decomposition.QR;
import org.ojalgo.matrix.store.ElementsConsumer;
import org.ojalgo.matrix.store.MatrixStore;
import org.ojalgo.matrix.store.PhysicalStore;
import org.ojalgo.matrix.store.PrimitiveDenseStore;
import org.ojalgo.netio.BasicLogger;
import org.ojalgo.random.Normal;
import org.ojalgo.type.Stopwatch;

public class CreateCustomMatrixStore {

    /**
     * In addition to the required 4 methods, you probably should also implement these.
     */
    static class BetterPerformingEye extends MostBasicEye {

        public BetterPerformingEye(final long rowsCount, final long columnsCount) {
            super(rowsCount, columnsCount);
        }

        /**
         * For a primitive valued implementation, at least, you should implement this method
         */
        public double doubleValue(final long row, final long col) {
            return row == col ? ONE : ZERO;
        }

        /**
         * May allow the standard matrix multiplication code to exploit structure, certainly possible here.
         */
        public int firstInColumn(final int col) {
            return Math.min(col, (int) this.countRows());
        }

        /**
         * @see #firstInColumn(int)
         */
        public int firstInRow(final int row) {
            return Math.min(row, (int) this.countColumns());
        }

        /**
         * @see #firstInColumn(int)
         */
        public int limitOfColumn(final int col) {
            return Math.min(col + 1, (int) this.countRows());
        }

        /**
         * @see #firstInColumn(int)
         */
        public int limitOfRow(final int row) {
            return Math.min(row + 1, (int) this.countColumns());
        }

        /**
         * Custom/optimised copy functionality.
         */
        public void supplyTo(final ElementsConsumer<Double> receiver) {
            receiver.reset();
            receiver.fillDiagonal(ONE);
        }

    }

    /**
     * There are only 4 methods you have to implement in a custom MatrixStore, but there's a whole lot you can
     * to with that implementation. It's fully functional!
     */
    static class MostBasicEye implements MatrixStore<Double> {

        private final long myColumnsCount;
        private final long myRowsCount;

        public MostBasicEye(final long rowsCount, final long columnsCount) {
            super();
            myRowsCount = rowsCount;
            myColumnsCount = columnsCount;
        }

        public long countColumns() {
            return myColumnsCount;
        }

        public long countRows() {
            return myRowsCount;
        }

        public Double get(final long row, final long col) {
            return row == col ? ONE : ZERO;
        }

        public PhysicalStore.Factory<Double, PrimitiveDenseStore> physical() {
            return PrimitiveDenseStore.FACTORY;
        }

    }

    private static final int DIM = 2000;

    public static void main(final String[] args) {

        BasicLogger.debug();
        BasicLogger.debug(CreateCustomMatrixStore.class.getSimpleName());
        BasicLogger.debug(OjAlgoUtils.getTitle());
        BasicLogger.debug(OjAlgoUtils.getDate());
        BasicLogger.debug();

        final MatrixStore<Double> mostBasicEye = new MostBasicEye(DIM + DIM, DIM);
        final MatrixStore<Double> identityAndZero = MatrixStore.PRIMITIVE.makeIdentity(DIM).below(DIM).get();
        final MatrixStore<Double> betterPerformingEye = new BetterPerformingEye(DIM + DIM, DIM);

        final Stopwatch stopwatch = new Stopwatch();

        BasicLogger.debug();
        BasicLogger.debug("Compare multiplication");

        final PhysicalStore<Double> right = PrimitiveDenseStore.FACTORY.makeFilled(DIM, DIM, new Normal());
        final PhysicalStore<Double> product = PrimitiveDenseStore.FACTORY.makeZero(DIM + DIM, DIM);

        stopwatch.reset();
        mostBasicEye.multiply(right, product);
        BasicLogger.debug("MostBasicEye multiplied in {}", stopwatch.stop());

        stopwatch.reset();
        identityAndZero.multiply(right, product);
        BasicLogger.debug("Built-in ojAlgo multiplied in {}", stopwatch.stop());

        stopwatch.reset();
        betterPerformingEye.multiply(right, product);
        BasicLogger.debug("BetterPerformingEye multiplied in {}", stopwatch.stop());

        BasicLogger.debug();
        BasicLogger.debug("Compare QR decomposition");

        final QR<Double> decompQR = QR.PRIMITIVE.make(mostBasicEye);

        stopwatch.reset();
        decompQR.compute(mostBasicEye);
        BasicLogger.debug("MostBasicEye decomposed in {}", stopwatch.stop());

        stopwatch.reset();
        decompQR.compute(identityAndZero);
        BasicLogger.debug("Built-in ojAlgo decomposed in {}", stopwatch.stop());

        stopwatch.reset();
        decompQR.compute(betterPerformingEye);
        BasicLogger.debug("BetterPerformingEye decomposed in {}", stopwatch.stop());

        // Actually decomposing should take exactly the same time for the 3 alternatives.
        // ...and the input is already in the decomposed form.
        // The time difference is the time it takes to copy the input matrix
        // to the decomposer's internal storage.

    }

}

Console output


CreateCustomMatrixStore
ojAlgo
2017-09-21


Compare multiplication
MostBasicEye multiplied in 10199.886744ms
Built-in ojAlgo multiplied in 139.950113ms
BetterPerformingEye multiplied in 112.976904ms

Compare QR decomposition
MostBasicEye decomposed in 287.584171ms
Built-in ojAlgo decomposed in 202.867971ms
BetterPerformingEye decomposed in 57.981169ms

Another example is the Apache Commons Math adaptor from the ojAlgo-commons-math3 extensions module.