I'm working with particular classes of matrices which admit fast operations without resorting to dense linear algebra. To keep the heierarchy simple, I'm currently doing the following (greatly simplified):
class Matrix: def mvm() # matrix-vector multiplication def is_square() def is_psd() def is_symm() def eigenvalue_bound() class Diag(Matrix): # represents a diagonal matrix with main diagonal v # requires O(n) memory and time for all Matrix operations # where n is the size of v def __init__(self, v) class Circulant(Matrix): # represents a circulant matrix with top row v # requires O(n lg n) memory and time for all Matrix operations # where n is the size of v def __init__(self, v) class Kronecker(Matrix): # represents the Kronecker product of two matrices A and B # requires O(memory(A) + memory(B)) memory for sparse storage # instead of the dense version, of size O(memory(A) * memory(B)) # Similarly, a Matrix operation taking op(A) and op(B) time # can be done in O(size(A) * op(B) + size(B) * op(A)). def __init__(self, A, B) class Composition(Matrix): # represents the composition product of two matrices A and B, AB # requires O(memory(A) + memory(B)) memory # might save time if op(A) + op(B) < op(AB) def __init__(self, A, B)
As you can see, I care about a lot of properties of the matrices - positive semi-definiteness, symmetry, eigenvalue information. These properties are available only if the matrix meets certain mathematical properties, and they translate nontrivially between the matrices.
There are other matrices and properties I'm considering, but hopefully this gets the point across: the methods I'd like to call on an instance of each of these is heavily dependent on the context in which the matrices are created.
I've adopted the "simple" solution of having the base contain the union of all properties I might ever want, and then I perform various runtime sanity checks if a method is called in a certain context.
At the other end of the spectrum, this might be a solution for multiple inheritance and mixins, but this would result in a combinatorially large number of subclasses to handle! What's the best approach here?
For clarification: these matrices are immutable. For consistency, let's stick to python. Another important feature is that under different constraints, the in-memory representation differs between each subtype. In other words, I'm really just treating these matrices as linear operators.