Create Custom MatrixStore

Anders Peterson edited this page Oct 17, 2017 · 7 revisions

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.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.