Hi,

> For those who've not read my latest blog, I'm working on implementing
> a new structure for the matrices module of sympy.
> I faced many decision problems while doing so, like what should be
> named what and what should be where.
> 
> Should matrix and linear algebra be separated ?

Probably yes in favour of a clean software architecture.

> I plan to put only essential functionalities in the main class. Other
> functionalities will be put in a separate file matrixtools.py or
> matrixutils.py.

That is probably good but the difficulties arise at the boundaries.
What is still a member function and what is not ...?

> so, once again the questions are,
> What do you expect from the matrix module ?

Hmm, matrices are a fairly general "structure" so there are many things
I would expect from a rather complete implementation. Maybe you should
also look at the Maxima manual, for example at chapter "49. diag" and
"67. linearalgebra"

> What should be the public API ?

I see two "parts" of a public API. Some operations are more like member
functions while others are more independent of a concrete matrix and
should be implemented as free functions. (This is also where we go
more into the direction of procedural code.)

> What functionality should be given special status to reside inside the
> class ?

Every matrix for example has a shape, so functions for retrieving the
shape definitely should go inside the class.

Then there are more things that I think of being intrinsic to
a given matrix instance. As long as we talk about square matrices
sucht things include trace, determinant, eigenvalues, eigenvectors,
characteristic and minimal polynomial and probably a few more.

Further we should be able to reason about the usual properties like
if the matrix is symmetric, hermitian, skew, diagonal ...
And maybe things like definiteness but I'm not sure if that should
better go into the assumtions system.

Also I would expect member functions for the usual transformations
like transpose (including hermitian transpose), invert etc, so I like

        M.T(), M.invert(), ...

instead of

        transpose(M), invert(M), ...

But this also depends a little on the question if the matrix M
is mutable here. For mutables, the member functions are better,
but for immutable objects maybe the independent functions are
more natural.

If we go to rectangular matrices, some of the above functions
are not defined, so the question is how we want to handle this?

And we also get "new" functions, f.e. singular values, pseudo inverses
and all that generalisations. These would then also be member functions
and reduce to the "usual" functions if the matrix is square.
(Or do we want to have also a method to compute the singular values
of a square matrix?)

To show a code snippet:

        def singularvalues(self, ...):
                if not self.is_square():
                        return singularvalues
                else:
                        # Two choices:
                        return eigenvalues
                        # or
                        return NotImplementedError

and

        def eigenvalues(self, ...):
                if self.is_square():
                        return eigenvalues
                else:
                        # Two choices:
                        return singularvalues
                        # or
                        return NotImplementedError


In contrast to the intrinsic properties, there are for example the
decompositions like LU, QR, Schur, Jordan, Polar, ... decomposition
which yield several matrices. So free functions are more appropriate
here:

        L, U, P = ludec(M)
        Q, R = qr(M)

but already here we hit a corner case, namely the eigendecomposition,
I said above that eigenvalues and vectors are more intrinsic properties.
But whats about:

        P, Lambda = eigendec(M)

where M = P^-1 * Lambda * P

As we get two matrices here there can (and maybe really should) as well
be a free function for this. For example we get the eigenvalues as a diagonal
matrix here contrary to M.eigenvalues() where we would get a list/dict with
values and multiplicities.


I talked about intrinsic operations above, and another example case to consider 
is
the computation of null spaces. This can be seen as important as f.e. the 
determinant
and maybe resides on the same level. But then the question is, how do we 
compute it?

Because solving linear systems is in my view not a member function, thus:

        y = lu_solve(A,x)

is "better" than:

        y = A.lu_solve(x)

And in this course, we would end at:

        N = nullspace(M)

So there are many examples where it's not that obvious I think.


A similar example are matrix norms. I said I consider determinants 
to be tightly coupled to a matrix instance. But whats about norms?

        M.frobenius()

And what happens when we want to add more norms? Do we really
have to extend the matrix class code for this? Is this good design?
Maybe not ...


By the way, some of the above is also dependent on the ground fiels,
so if we deal with real, complex, ... matrices ...


> For example, jacobian does not belong to the class, it belongs to
> specialmatrices.py, and should be in the linalg. namespace as
> linalg.jacobian

Things like the jacobian and hessian are not directly related to the
matrices. (If we, at least for the moment, ignore more theoretical
concepts like operators etc) They just happen to be matrices,
but this is also not always true! (Jacobian of f:R^n->R is a vector,
or a n times 1 matrix, Hessian of f:R^m->R^n is a tensor object.)
So these functions should rather seamlessly integrate with diff, Derivative
and indexed objects / tensors. And they should play along grad, div, curl
laplacian etc.

To make a last example, what's the jacobian of a matrix of integers?
I would argue that this makes no sense unless you implicitely reinterpret
integers as constant functions ...

An simple implementation of the jacobian could look like:

def jacobian(f, (x,y,z,...)):
        return Matrix( [[ diff(f_i, x_j) for i in ...] for j in ... ] )


Another important topic is iteration. I think that matrices really
should support iteration. And there are at least three cases which
are useful:

for row in matrix.iterrows():
        ...

for col in matrix.itercols():
        ...

for cell in matrix.iterentries():
        ...

Of course I'm aware that not all can be supported with
the same efficiency depending on the storage layout.
Maybe this is less important for symbolic matrices which
are not that big?

Or there is an efficient (maybe lazy) transpose operation,
and then if f.e. iterrows() is efficient, itercols() replaced
by M.T.iterrows() is somewhat efficient too? I don't know ...

For Spare matrices maybe we want to have:

for cell in M.iternonzeroentries():
        ...


In the same direction we have slicing. And I think that
slicing is *really* important, maybe even more important than
iteration. So any matrix should support slicing like:

        M[a:b, c:d]       yielding a continuous submatrix
        M[(a,b), (c,d)]   yielding a submatrix consisting of M[a,c], M[a,d], 
M[b,c] and M [b,d]

For block matrices with arbitrary (and potentially different) block sizes
this should work on block level of course.


So, this are some of my thoughts about the topic. I don't know
if everything makes sense, especially with your code. (I have not
looked into it!) But anyway I hope this gives some useful input.


-- Raoul

-- 
You received this message because you are subscribed to the Google Groups 
"sympy" group.
To post to this group, send email to sympy@googlegroups.com.
To unsubscribe from this group, send email to 
sympy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sympy?hl=en.

Reply via email to