Introduction
Matrix Algebra
Number Theory
Collections Extensions
General Utilities
API Javadocs
License
SourceForge Project
|
Matrix Algebra
The us.rconner.math package contains interfaces,
default implementations, and some utilities for performing matrix and
vector math.
This package arose out of the frustration encountered when working
with other publicly available matrix packages, and has been designed
using a completely different set of goals and criteria. Whereas other
similar packages are commonly designed with a particular problem
and/or solution in mind, this one was designed to be as general as
possible, so that it can be applied to as many problems as possible.
This is an attempt at a general matrix algebra solution from an OO
perspective, rather than from that of a numerical analyst.
That being said, this package is not fully general in the following
ways:
- There are only specifications for 1- and 2-dimensional arrays.
- The specifications only allow for elements of type
double .
These restrictions will hopefully be rectified in the future, when it
is best determined how to best incorporate the changes while
minimizing interface bloat.
These are the design goals and reasoning which have guided the
implementation of this package:
Specify the interface, not the
implementation. This provides the following benefits:
- Users can define their own matrix implementations and have
them work with anyone else's algorithms.
- Users can define their own algorithms and have them work
with anyone else's matrix implementations.
- More efficient classes can be created for many
special-case matrices and shallow wrapper implementations.
See the source code for
IdentityMatrix and
the wrapper implementations in MatrixUtils
for good examples.
Because of this constraint, many properly coded algorithms
may not be as efficient as if they were written for a specific
matrix implementation. However, this time loss will be offset,
or possibly even reversed, if the algorithm uses any form or
sparse and/or wrapped matrix. This is because the special
matrix implementations will consume less memory, use less time
for operations, and even gain time by reducing the load on the
garbage collector. Don't underestimate the amount of time that
can be gained by avoiding the allocation of objects.
Minimize coupling. The interface
specifications do not reference any particular concrete
implementations, and so the provided implementations are free to
change without impacting the interfaces. There is also very
little coupling between the provided implementations.
Do not define any non-trivial algorithms.
How best to implement an algorithm or decompose a matrix largely
depends upon the particular matrix representation involved and
what qualities it has (positive definite, upper triangular,
etc.). Since this is a general package, that functionality is
not defined here. A possible future extension will be to
provide some general purpose algorithms in a separate package.
Any mutable matrix or vector class should have a
general copy constructor. This allows the user to
create a copy of any given matrix as a specific implementation
type.
The allocation of any new non-shallow matrix or
vector instance should be explicit. How best to use
memory should be decided by the user, not the library. All
mathematical operations defined on the Matrix and
Vector interfaces (addition, scaling, transpose,
etc.) which return a Matrix or Vector
result modify the object upon which they are called. New
instances are not created.
One problem this addresses, other than unnecessary memory
allocation, is that a given implementation would not necessarily
know what kind of thing to create. This is not a problem for a
standard double[][] implementation, but what would
a special tri-diagonal matrix class do? The solution is to
simply specify that methods do not create new instances.
If you need to create a new mutable instance, then simply use
the copy constructor for the specific implementation type, like
this:
Matrix result = new DefaultMatrix( a ).add( b );
or this:
Matrix result = new DefaultMatrix( MatrixUtils.add( a, b ) );
The methods on Matrix , Vector , and
MatrixUtils should never allocate more storage for
elements than that required to temporarily hold one row or
column, with the following exceptions:
- Methods whose purpose is to allocate storage, such as
Matrix.getMatrixElements() .
- Some implementations of
Matrix.square() , in
particular those of AbstractMatrix and
DefaultMatrix .
- Most implementations of
toString() .
The interface explicitly does not specify that
equals() and hashCode() should be
overridden. This is because conforming to the contracts of those
methods would require that equals() be an exact test.
Being one decimal place off in one element would result in two
otherwise equal matrices being unequal. This is not how a typical
user would prefer equals() to behave. Also, it would
make hashCode() an excessively expensive operation.
Instead, use the provided methods in MatrixUtils to test
equality within some tolerance.
One final note about matrix decompositions. I fully expect that
users will write their own decomposition classes, and there may be
some in a later separate package as well. A decomposition is, itself,
just a matrix with a very specific internal representation. As such,
any decomposition really should implement the Matrix
interface; extending AbstractMatrix is probably the
easiest way to do this. Some types of decompositions are effective
for certain operations, and those methods should be overridden when it
is more efficient to do so.
|