-
-
Notifications
You must be signed in to change notification settings - Fork 209
Create Custom MatrixStore
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:
- 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.
- 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.
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.
}
}
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.