-
-
Notifications
You must be signed in to change notification settings - Fork 218
Getting Started
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". There are a few things you should know before you produce too much code using BasicMatrix:
- The interface is designed for immutable implementations, and the BigMatrix, ComplexMatrix and PrimitiveMatrix implementations are indeed immutable. This is unusual for mathematical matrix classes, and most likely not what you expected. The main thing users tend to get wrong is how to instantiate, and fully populate, an immutable matrix object.
- BasicMatrix is not the only alternative
The first thing you should know is that there are two implementation layers. There's a BasicMatrix interface as well as MatrixStore and PhysicalStore interfaces.
BasicMatrix is a higher/application/logic level interface, and MatrixStore/PhysicalStore are lower/algorithm/implementation level interfaces. If you're building an application where you need to manipulate matrices to implement business logic then BasicMatrix is for you. MatrixStore/PhysicalStore is for those who code (complex) algorithms and need full 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.
The two layers are to some extent interoperable, but most users should choose either or.
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.
Typically you do NOT instantiate matrices using constructors. Instead you use factories and/or builders.
Once you've decided which implementation layers to use, you need to know how to create instances to work with. This is another thing new users often get wrong. Apart from trying to use the constructors rather than the factories users fail to realize that BasicMatrix instances are immutable.
final Factory<PrimitiveMatrix> tmpFactory = PrimitiveMatrix.FACTORY;
// A MatrixFactory has 13 different methods that return BasicMatrix instances.
final BasicMatrix tmpA = tmpFactory.makeEye(5000, 300);
// Internally this creates an "eye-structure" - not a large array...
final BasicMatrix tmpB = tmpFactory.makeFilled(300, 2, new Weibull(5.0, 2.0));
// When you create a matrix with random elements you can specify their distribution.
final BasicMatrix tmpC = tmpB.multiplyLeft(tmpA);
final BasicMatrix tmpD = tmpA.multiply(tmpB);
// ojAlgo differentiates between multiplying from the left and from the right.
// The matrices C and D will be equal, but the code executed to calculate them are different.
// The second alternative, resulting in D, will be MUCH faster!
final BasicMatrix tmpE = tmpA.add(1000, 19, 3.14);
final BasicMatrix tmpF = tmpE.add(10, 270, 2.18);
// The BasicMatrix interface does not specify a set-method for matrix elements.
// BasicMatrix instances are immutable.
// The add(...) method should only be used to modify a small number of elements.
// Don't do this!!!
BasicMatrix tmpG = tmpFactory.makeZero(500, 500);
for (int j = 0; j < tmpG.countColumns(); j++) {
for (int i = 0; i < tmpG.countRows(); i++) {
tmpG = tmpG.add(i, j, 100.0 * Math.min(i, j));
// Note that add(..) actually adds the specified value to whatever is already there.
// In this case that, kind of, works since the base matrix is all zeros.
// Completely populating a matrix this way is a really bad idea!
}
}
// Don't do this!!!
final double[][] tmpData = new double[][] { { 1.0, 2.0, 3.0 }, { 4.0, 5.0, 6.0 }, { 7.0, 8.0, 9.0 } };
final BasicMatrix tmpH = tmpFactory.rows(tmpData);
// A, perhaps, natural way to create a small matrix, but the arrays are copied.
// You do not want to create that array just as an intermediate step towards populating your matrix.
// Doing it this way is clumsy for larger matrices.
final Builder<PrimitiveMatrix> tmpBuilder = tmpFactory.getBuilder(500, 500);
// If you want to individually set many/all elements of a larger matrix you should use the builder.
for (int j = 0; j < 500; j++) {
for (int i = 0; i < 500; i++) {
tmpBuilder.set(i, j, 100.0 * Math.min(i, j));
}
}
final BasicMatrix tmpI = tmpBuilder.build();
// Now you've seen 4 of the 13 MatrixFactory methods...
final BasicMatrix tmpJ = tmpA.mergeRows(tmpD);
final BasicMatrix tmpK = tmpJ.selectRows(1, 10, 100, 1000);
// Sometimes it's practical to only use the factory/builder to create parts of the final matrix.
In many ways working with MatrixStore/PhysicalStore is similar to working with BasicMatrix. Just look at these examples doing roughly the same things the above examples did with BasicMatrix.
final PhysicalStore.Factory<Double, PrimitiveDenseStore> tmpFactory = PrimitiveDenseStore.FACTORY;
final PrimitiveDenseStore tmpA = tmpFactory.makeEye(5000, 300);
// A PrimitiveDenseStore is always a "full array". No smart data structures here...
final PrimitiveDenseStore tmpB = tmpFactory.makeFilled(300, 2, new Weibull(5.0, 2.0));
// The BasicMatrix and PhysicalStore factories are very similar. They both inherit a common interface.
final MatrixStore<Double> tmpC = tmpB.multiplyLeft(tmpA);
final MatrixStore<Double> tmpD = tmpA.multiply(tmpB);
// When both matrices are PhysicalStore instances there is no major difference
// between multiplying from the left and from the right.
tmpA.set(1000, 19, 3.14);
tmpA.set(10, 270, 2.18);
// PhysicalStore instances are mutable - very mutable.
// None of the methods defined in the PhysicalStore interface return matrices (of any type).
// Most methods don't return anything at all... They just mutate the matrix.
final PrimitiveDenseStore tmpE = tmpA.copy();
final MatrixStore<Double> tmpF = tmpA.transpose();
// If you need a copy to work with, you have to explicitly make that copy.
final double[][] tmpData = new double[][] { { 1.0, 2.0, 3.0 }, { 4.0, 5.0, 6.0 }, { 7.0, 8.0, 9.0 } };
final PrimitiveDenseStore tmpH = tmpFactory.rows(tmpData);
// A, perhaps, natural way to create a small matrix, but the arrays are copied.
// Doing it this way is clumsy for larger matrices.
// If you don't have to create that array - then don't.
final PrimitiveDenseStore tmpI = tmpFactory.makeZero(500, 500);
for (int j = 0; j < tmpI.getColDim(); j++) {
for (int i = 0; i < tmpI.getRowDim(); i++) {
tmpI.set(i, j, 100.0 * Math.min(i, j));
}
}
// Doing this is, of course, no problem at all! In many cases it's what you should do.
final MatrixStore<Double> tmpJ = tmpA.builder().right(tmpD).build();
final MatrixStore<Double> tmpK = tmpJ.builder().row(1, 10, 100, 1000).build();
// Once you have a MatrixStore instance you can build on it, logically.
final MatrixStore<Double> tmpG = tmpA.builder().right(tmpD).row(1, 10, 100, 1000).build();
// And of course you can do it in one movement. The matrices K and G are equal.
- There are two implementation layers and you should (probably) choose to only work with one of them.
- If you choose to work with BasicMatrix you should bear in mind that BigMatrix, ComplexMatrix and PrimitiveMatrix are immutable. This is a very useful feature when implementing high level business logic (they're thread safe by design), but immutable matrices are NOT SUITABLE for many use cases. Think about what you need!
- Don't use the constructors. Use factories and/or builders instead.