+def _mat2vec(m):
+ return vector(m.base_ring(), m.list())
+
+def basis_representation(M):
+ """
+ Return the forward (``MatrixSpace`` -> ``VectorSpace``) and
+ inverse isometries, as a pair, that take elements of the given
+ ``MatrixSpace`` `M` to their representations as "long vectors,"
+ and vice-versa.
+
+ The argument ``M`` can be either a ``MatrixSpace`` or a basis for
+ a space of matrices. This function is needed because SageMath does
+ not know that matrix spaces are vector spaces, and therefore
+ cannot perform common operations with them -- like computing the
+ basis representation of an element.
+
+ Moreover, the ability to pass in a basis (rather than a
+ ``MatrixSpace``) is needed because SageMath has no way to express
+ that e.g. a (sub)space of symmetric matrices is itself a
+ ``MatrixSpace``.
+
+ INPUT:
+
+ - ``M`` -- Either a ``MatrixSpace``, or a list of matrices that form
+ a basis for a matrix space.
+
+ OUTPUT:
+
+ A pair of isometries ``(phi, phi_inv)``.
+
+ If the matrix space associated with `M` has dimension `n`, then
+ ``phi`` will map its elements to vectors of length `n` over the
+ same base ring. The inverse map ``phi_inv`` reverses that
+ operation.
+
+ SETUP::
+
+ sage: from mjo.matrix_vector import basis_representation
+
+ EXAMPLES:
+
+ This function computes the correct coordinate representations (of
+ length 3) for a basis of the space of two-by-two symmetric
+ matrices, the the inverse does indeed invert the process::
+
+ sage: E11 = matrix(QQbar,[ [1,0],
+ ....: [0,0] ])
+ sage: E12 = matrix(QQbar,[ [0, 1/sqrt(2)],
+ ....: [1/sqrt(2), 0] ])
+ sage: E22 = matrix(QQbar,[ [0,0],
+ ....: [0,1] ])
+ sage: basis = [E11, E12, E22]
+ sage: phi, phi_inv = basis_representation(basis)
+ sage: phi(E11); phi(E12); phi(E22)
+ (1, 0, 0)
+ (0, 1, 0)
+ (0, 0, 1)
+ sage: phi_inv(phi(E11)) == E11
+ True
+ sage: phi_inv(phi(E12)) == E12
+ True
+ sage: phi_inv(phi(E22)) == E22
+ True
+
+ MatrixSpace arguments work too::
+
+ sage: M = MatrixSpace(QQ,2)
+ sage: phi, phi_inv = basis_representation(M)
+ sage: X = matrix(QQ, [ [1,2],
+ ....: [3,4] ])
+ sage: phi(X)
+ (1, 2, 3, 4)
+ sage: phi_inv(phi(X)) == X
+ True
+
+ TESTS:
+
+ The inverse is generally an inverse::
+
+ sage: set_random_seed()
+ sage: n = ZZ.random_element(10)
+ sage: M = MatrixSpace(QQ,n)
+ sage: X = M.random_element()
+ sage: (phi, phi_inv) = basis_representation(M)
+ sage: phi_inv(phi(X)) == X
+ True
+
+ """
+ if is_MatrixSpace(M):
+ basis_space = M
+ basis = list(M.basis())
+ else:
+ basis_space = M[0].matrix_space()
+ basis = M
+
+ def phi(X):
+ """
+ The isometry sending ``X`` to its representation as a long vector.
+ """
+ if X not in basis_space:
+ raise ValueError("X does not live in the domain of phi")
+
+ V = VectorSpace(basis_space.base_ring(), X.nrows()*X.ncols())
+ W = V.span_of_basis( _mat2vec(s) for s in basis )
+ return W.coordinate_vector(_mat2vec(X))
+
+ def phi_inv(Y):
+ """
+ The isometry sending the long vector `Y` to an element of either
+ `M` or the span of `M` (depending on whether or not ``M``
+ is a ``MatrixSpace`` or a basis).
+ """
+ return sum( Y[i]*basis[i] for i in range(len(Y)) )
+
+ return (phi, phi_inv)
+
+
+
+def matrix_of_transformation(T, V):