-
-
Notifications
You must be signed in to change notification settings - Fork 218
Getting Started
A short guide on ojAlgo's various array, vector and matrix classes to help you get started.
The org.ojalgo.access package contains a number of interfaces widely used throughout ojAlgo, and the root of them all is org.ojalgo.access.Structure1D. This interface only declares one method – count() – returning the total number of elements/items in a data structure. It's equivalent to the size() method of java.util.Collection but returns long rather than int. Some data structures in ojAlgo actually support containg this many elements/items.
In your IDE - assuming you have a project setup with ojAlgo on the classpath - open a type hierarchy with Structure1D at the root. That type hierarchy is fairly deep/complex and encompasses a very large subset of everything in ojAlgo. Understanding at least the basics of how this hierchy is constructed is necessary for all ojAlgo users - please spend some time exploring it!
Studying that hierarchy you should notice 3 things:
- To complement Structure1D there are interfaces Structure2D and StructureAnyD, both of which extend Structure1D. Extending each of those three Structure- interfaces you'll find the Access1D, Access2D and AccessAnyD interfaces. Access2D extends Structure2D AND Access1D, similarly AccessAnyD extends StructureAnyD AND Access1D. This pattern is one of the core design decisions of ojAlgo. Anything "2D" or "AnyD" is also/simultaneously "1D" – there's always a 1D variant of the API. How the 1D and 2D/AnyD API variants correlate is strictly specified, and the AccessUtils class contain methods to help with the translations.
- A large portion of the classes/interfaces in ojAlgo use generic type parameters to specify what they contain/handle, and practicly always the generic type declaration is:
<N extends Number>
- The Access1D, Access2D and AccessAnyD interfaces declare methods to get/extract/access one specific elements of a data structure. They each have a get(...) method returning a generic "N" (a Number subclass) as well as a doubleValue(...) method returning primitive double. Whatever Number subclass you're working with you always have direct access to primitive double representations. That can be very handy; particularly when the internal data type actually is primitive double, and that's by far the most commonly used type for maths. This parallel type pattern is another of ojAlgo's core design decisions.
The ojAlgo data structures are not general purpose "collections". They're fixed size and structure, contain numbers only, and primitive double gets special treatment.
Assuming you need a general purpose array, vector or matrix implementation of some sort there are two main alternatives:
- Array1D, Array2D or ArrayAnyD from the org.ojalgo.array package
- Something from the org.ojalgo.matrix package or its sub packages
The classes in the org.ojalgo.array package have three clear advantage over the various matrix implementations:
- They can be any-dimensional (AnyD) - matrices are 2D.
- They support huge data structures. You really can make use of those long element indices.
- They support a larger set of Number subclasses (for instance rational numbers and quaternions).
The key thing not available in the org.ojalgo.array package is "Linear Algebra".
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 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).
- Don't use the constructors. Use factories and/or builders instead.