Skip to content
tpietzsch edited this page Jul 5, 2019 · 16 revisions

There is an updated version of this info at: http://www.ojalgo.org/2019/03/linear-algebra-introduction/


The linear algebra part of ojAlgo is one of its main attractions as well as an essential component to the other parts. It’s not difficult to use at all, but to fully exploit its capabilities there are a few things you need to know.

The BasicMatrix interface and its 3 implementations, BigMatrix, ComplexMatrix and PrimitiveMatrix, is what most new users find when they’re looking for “ojAlgo’s matrix class”, but there are actually two matrix implementation layers in ojAlgo. There’s the BasicMatrix interface and its implementations as well as the MatrixStore/PhysicalStore family of interfaces and classes.

BasicMatrix is a higher/application/logic level interface with a fixed and limited feature set, and MatrixStore/PhysicalStore are lower/algorithm/implementation level interfaces offering greater flexibility and control. Initially BasicMatrix was “the” interface to use. Everything else was just implementation stuff preferably hidden to users. This has changed. The lower level stuff is since long open and available to use for anyone. It is also where most of the development has been lately.

The BasicMatrix interface is designed for immutable implementations, and the BigMatrix, ComplexMatrix and PrimitiveMatrix implementations are indeed immutable. This can be very practical, but is an unusual feature for mathematical matrix classes, and most likely not what you expected. One of the things new users tend to get wrong is how to instantiate, and fully populate, an immutable matrix.

Each of the two implementation layers support three element types: double, BigDecimal and ComplexNumber. Most people will just use the double implementations, but some need ComplexNumber. If the matrices are not too large and you need that extra precision you can use BigDecimal.

The two layers are to some extent interoperable, but most users should choose either or. Have a look at both PrimitiveMatrix and PrimitiveDenseStore (assuming you need primitive double elements) and try to get some idea about the differences before you write too much code.

Example code

To compile this example, you will need ojAlgo library. The Maven dependency snippet is shown on the main page.

Below is some code demonstrating how to do some basic stuff, as well as pointing out some differences between PrimitiveMatrix and PrimitiveDenseStore.

import org.ojalgo.OjAlgoUtils;
import org.ojalgo.RecoverableCondition;
import org.ojalgo.matrix.BasicMatrix;
import org.ojalgo.matrix.PrimitiveMatrix;
import org.ojalgo.matrix.decomposition.QR;
import org.ojalgo.matrix.store.ElementsSupplier;
import org.ojalgo.matrix.store.MatrixStore;
import org.ojalgo.matrix.store.PhysicalStore;
import org.ojalgo.matrix.store.PrimitiveDenseStore;
import org.ojalgo.matrix.task.InverterTask;
import org.ojalgo.matrix.task.SolverTask;
import org.ojalgo.netio.BasicLogger;
import org.ojalgo.random.Weibull;

public final class GettingStarted {

    public static void main(String[] args) {

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

        BasicMatrix.Factory<PrimitiveMatrix> matrixFactory =
                PrimitiveMatrix.FACTORY;
        PhysicalStore.Factory<Double, PrimitiveDenseStore> storeFactory =
                PrimitiveDenseStore.FACTORY;
        // BasicMatrix.Factory and PhysicalStore.Factory are very similar.
        // Every factory in ojAlgo that makes 2D-structures
        // extends/implements the same interface.

        PrimitiveMatrix matrixA = matrixFactory.makeEye(5, 5);
        // Internally this creates an "eye-structure" - not a large array.
        PrimitiveDenseStore storeA = storeFactory.makeEye(5, 5);
        // A PrimitiveDenseStore is always a "full array". No smart data
        // structures here.

        PrimitiveMatrix matrixB =
                matrixFactory.makeFilled(5, 3, new Weibull(5.0, 2.0));
        PrimitiveDenseStore storeB =
                storeFactory.makeFilled(5, 3, new Weibull(5.0, 2.0));
        // When you create a matrix with random elements you can specify
        // their distribution.

        /* Matrix multiplication */

        PrimitiveMatrix matrixC = matrixA.multiply(matrixB);
        // Multiplying two PrimitiveMatrix:s is trivial. There are no
        // alternatives, and the returned product is a PrimitiveMatrix
        // (same as the inputs).

        // Doing the same thing using PrimitiveDenseStore (MatrixStore) you
        // have options...

        BasicLogger.debug("Different ways to do matrix multiplication with " +
                "MatrixStore:s");
        BasicLogger.debug();

        MatrixStore<Double> storeC = storeA.multiply(storeB);
        // One option is to do exactly what you did with PrimitiveMatrix.
        // The only difference is that the return type is MatrixStore rather
        // than PhysicalStore, PrimitiveDenseStore or whatever else you input.
        BasicLogger.debug("MatrixStore MatrixStore#multiply(MatrixStore)",
                storeC);

        PrimitiveDenseStore storeCPreallocated = storeFactory.makeZero(5, 3);
        // Another option is to first create the matrix that should hold the
        // resulting product,
        storeA.multiply(storeB, storeCPreallocated);
        // and then perform the multiplication. This enables reusing memory
        // (the product matrix).
        BasicLogger.debug(
                "void MatrixStore#multiply(Access1D, ElementsConsumer)",
                storeCPreallocated);

        ElementsSupplier<Double> storeCSupplier = storeB.premultiply(storeA);
        // A third option is the premultiply method:
        // 1) The left and right argument matrices are interchanged.
        // 2) The return type is an ElementsSupplier rather than
        //    a MatrixStore.
        // This is because the multiplication is not yet performed.
        // It is possible to define additional operation on
        // an ElementsSupplier.
        MatrixStore<Double> storeCLater = storeCSupplier.get();
        // The multiplication, and whatever additional operations you defined,
        // is performed when you call #get().
        BasicLogger.debug(
                "ElementsSupplier MatrixStore#premultiply(Access1D)",
                storeCLater);

        // A couple of more alternatives that will do the same thing.
        storeCPreallocated.fillByMultiplying(storeA, storeB);
        BasicLogger.debug(
                "void ElementsConsumer#fillByMultiplying(Access1D, Access1D)",
                storeCPreallocated);
        storeCSupplier.supplyTo(storeCPreallocated);
        BasicLogger.debug("void ElementsSupplier#supplyTo(ElementsConsumer)",
                storeCPreallocated);

        matrixA.invert();

        // With MatrixStore:s you need to use an InverterTask
        InverterTask<Double> inverter = InverterTask.PRIMITIVE.make(storeA);
        // There are many implementations of that interface. This factory
        // method will return one that may be suitable, but most likely you
        // will want to choose implementation based on what you know about
        // the matrix.
        try {
            inverter.invert(storeA);
        } catch (RecoverableCondition e) {
            // Will throw and exception if inversion fails, rethrowing it.
            throw new RuntimeException(e);
        }

        matrixA.solve(matrixC);

        SolverTask<Double> solver = SolverTask.PRIMITIVE.make(storeA, storeC);
        try {
            solver.solve(storeA, storeC);
        } catch (RecoverableCondition e) {
            // Will throw and exception if solving fails, rethrowing it.
            throw new RuntimeException(e);
        }

        // Most likely you want to do is to instantiate some matrix
        // decomposition (there are several).

        QR<Double> qr = QR.PRIMITIVE.make(storeA);
        qr.decompose(storeA);
        if (qr.isSolvable()) {
            qr.getSolution(storeC);
        } else {
            // You should verify that the equation system is solvable,
            // and do something else if it is not.
            throw new RuntimeException("Cannot solve the equation system");
        }

        /* Setting individual elements */

        storeA.set(3, 1, 3.14);
        storeA.set(3, 0, 2.18);
        // PhysicalStore instances are naturally mutable. If you want to set
        // or modify something - just do it

        BasicMatrix.Builder<PrimitiveMatrix> matrixBuilder = matrixA.copy();
        // PrimitiveMatrix is immutable. To modify anything, you need to copy
        // it to Builder instance.

        matrixBuilder.add(3, 1, 3.14);
        matrixBuilder.add(3, 0, 2.18);

        matrixBuilder.build();

        /* Creating matrices by explicitly setting all elements */

        double[][] tmpData = {
            {1.0, 2.0, 3.0},
            {4.0, 5.0, 6.0},
            {7.0, 8.0, 9.0}
        };

        matrixFactory.rows(tmpData);
        storeFactory.rows(tmpData);

        // If you don't want/need to first create some (intermediate) array
        // for the elements, you can of course set them on the matrix
        // directly.
        PrimitiveDenseStore storeZ = storeFactory.makeEye(3, 3);

        // Since PrimitiveMatrix is immutable this has to be done via
        // a builder.
        BasicMatrix.Builder<PrimitiveMatrix> matrixZBuilder =
                matrixFactory.getBuilder(3, 3);

        for (int j = 0; j < 3; j++) {
            for (int i = 0; i < 3; i++) {
                matrixZBuilder.set(i, j, i * j);
                storeZ.set(i, j, i * j);
            }
        }

        matrixZBuilder.get();
    }
}

Console output


GettingStarted
ojAlgo
44.0.0

Different ways to do matrix multiplication with MatrixStore:s

MatrixStore MatrixStore#multiply(MatrixStore)
 0.155074 0.248780 0.152604
 0.083154 0.073558 0.175543
 0.040861 0.265677 0.115585
 0.251556 0.156845 0.158622
 0.084072 0.014765 0.138253
void MatrixStore#multiply(Access1D, ElementsConsumer)
 0.155074 0.248780 0.152604
 0.083154 0.073558 0.175543
 0.040861 0.265677 0.115585
 0.251556 0.156845 0.158622
 0.084072 0.014765 0.138253
ElementsSupplier MatrixStore#premultiply(Access1D)
 0.155074 0.248780 0.152604
 0.083154 0.073558 0.175543
 0.040861 0.265677 0.115585
 0.251556 0.156845 0.158622
 0.084072 0.014765 0.138253
void ElementsConsumer#fillByMultiplying(Access1D, Access1D)
 0.155074 0.248780 0.152604
 0.083154 0.073558 0.175543
 0.040861 0.265677 0.115585
 0.251556 0.156845 0.158622
 0.084072 0.014765 0.138253
void ElementsSupplier#supplyTo(ElementsConsumer)
 0.155074 0.248780 0.152604
 0.083154 0.073558 0.175543
 0.040861 0.265677 0.115585
 0.251556 0.156845 0.158622
 0.084072 0.014765 0.138253