Melange

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.


This project is being hosted by SourceForge.net Logo

Copyright © 2003 Ray A. Conner
This file last modified 09/08/03