- Algebraic Real Field
-
- """
- if self.base_ring() is QQ:
- # There's no need to construct *another* algebra over the
- # rationals if this one is already over the rationals.
- superclass = super(RationalBasisEuclideanJordanAlgebra, self)
- return superclass._charpoly_coefficients()
-
- mult_table = tuple(
- tuple(map(lambda x: x.to_vector(), ls))
- for ls in self._multiplication_table
- )
-
- # Do the computation over the rationals. The answer will be
- # the same, because our basis coordinates are (essentially)
- # rational.
- J = FiniteDimensionalEuclideanJordanAlgebra(QQ,
- mult_table,
- check_field=False,
- check_axioms=False)
- a = J._charpoly_coefficients()
- return tuple(map(lambda x: x.change_ring(self.base_ring()), a))
-
-
-class ConcreteEuclideanJordanAlgebra:
- r"""
- A class for the Euclidean Jordan algebras that we know by name.
-
- These are the Jordan algebras whose basis, multiplication table,
- rank, and so on are known a priori. More to the point, they are
- the Euclidean Jordan algebras for which we are able to conjure up
- a "random instance."
-
- SETUP::
-
- sage: from mjo.eja.eja_algebra import ConcreteEuclideanJordanAlgebra
-
- TESTS:
-
- Our basis is normalized with respect to the algebra's inner
- product, unless we specify otherwise::
-
- sage: set_random_seed()
- sage: J = ConcreteEuclideanJordanAlgebra.random_instance()
- sage: all( b.norm() == 1 for b in J.gens() )
- True
-
- Since our basis is orthonormal with respect to the algebra's inner
- product, and since we know that this algebra is an EJA, any
- left-multiplication operator's matrix will be symmetric because
- natural->EJA basis representation is an isometry and within the
- EJA the operator is self-adjoint by the Jordan axiom::
-
- sage: set_random_seed()
- sage: J = ConcreteEuclideanJordanAlgebra.random_instance()
- sage: x = J.random_element()
- sage: x.operator().is_self_adjoint()
- True
- """
-
- @staticmethod
- def _max_random_instance_size():
- """
- Return an integer "size" that is an upper bound on the size of
- this algebra when it is used in a random test
- case. Unfortunately, the term "size" is ambiguous -- when
- dealing with `R^n` under either the Hadamard or Jordan spin
- product, the "size" refers to the dimension `n`. When dealing
- with a matrix algebra (real symmetric or complex/quaternion
- Hermitian), it refers to the size of the matrix, which is far
- less than the dimension of the underlying vector space.
-
- This method must be implemented in each subclass.
- """
- raise NotImplementedError
-
- @classmethod
- def random_instance(cls, field=AA, **kwargs):
- """
- Return a random instance of this type of algebra.
-
- This method should be implemented in each subclass.
- """
- from sage.misc.prandom import choice
- eja_class = choice(cls.__subclasses__())
- return eja_class.random_instance(field)
-
-
-class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
-
- def __init__(self, field, basis, normalize_basis=True, **kwargs):
- """
- Compared to the superclass constructor, we take a basis instead of
- a multiplication table because the latter can be computed in terms
- of the former when the product is known (like it is here).
- """
- # Used in this class's fast _charpoly_coefficients() override.
- self._basis_normalizers = None
-
- # We're going to loop through this a few times, so now's a good
- # time to ensure that it isn't a generator expression.
- basis = tuple(basis)
-
- algebra_dim = len(basis)
- degree = 0 # size of the matrices
- if algebra_dim > 0:
- degree = basis[0].nrows()
-
- if algebra_dim > 1 and normalize_basis:
- # We'll need sqrt(2) to normalize the basis, and this
- # winds up in the multiplication table, so the whole
- # algebra needs to be over the field extension.
- R = PolynomialRing(field, 'z')
- z = R.gen()
- p = z**2 - 2
- if p.is_irreducible():
- field = field.extension(p, 'sqrt2', embedding=RLF(2).sqrt())
- basis = tuple( s.change_ring(field) for s in basis )
- self._basis_normalizers = tuple(
- ~(self.matrix_inner_product(s,s).sqrt()) for s in basis )
- basis = tuple(s*c for (s,c) in zip(basis,self._basis_normalizers))
-
- # Now compute the multiplication and inner product tables.
- # We have to do this *after* normalizing the basis, because
- # scaling affects the answers.
- V = VectorSpace(field, degree**2)
- W = V.span_of_basis( _mat2vec(s) for s in basis )
- mult_table = [[W.zero() for j in range(algebra_dim)]
- for i in range(algebra_dim)]
- ip_table = [[W.zero() for j in range(algebra_dim)]
- for i in range(algebra_dim)]
- for i in range(algebra_dim):
- for j in range(algebra_dim):
- mat_entry = (basis[i]*basis[j] + basis[j]*basis[i])/2
- mult_table[i][j] = W.coordinate_vector(_mat2vec(mat_entry))
-
- try:
- # HACK: ignore the error here if we don't need the
- # inner product (as is the case when we construct
- # a dummy QQ-algebra for fast charpoly coefficients.
- ip_table[i][j] = self.matrix_inner_product(basis[i],
- basis[j])
- except:
- pass
-
- try:
- # HACK PART DEUX
- self._inner_product_matrix = matrix(field,ip_table)
- except:
- pass
-
- super(MatrixEuclideanJordanAlgebra, self).__init__(field,
- mult_table,
- matrix_basis=basis,
- **kwargs)
-
- if algebra_dim == 0:
- self.one.set_cache(self.zero())
- else:
- n = basis[0].nrows()
- # The identity wrt (A,B) -> (AB + BA)/2 is independent of the
- # details of this algebra.
- self.one.set_cache(self(matrix.identity(field,n)))
-
-
- @cached_method
- def _charpoly_coefficients(self):
- r"""
- Override the parent method with something that tries to compute
- over a faster (non-extension) field.
- """
- if self._basis_normalizers is None or self.base_ring() is QQ:
- # We didn't normalize, or the basis we started with had
- # entries in a nice field already. Just compute the thing.
- return super(MatrixEuclideanJordanAlgebra, self)._charpoly_coefficients()
-
- basis = ( (b/n) for (b,n) in zip(self.matrix_basis(),
- self._basis_normalizers) )
-
- # Do this over the rationals and convert back at the end.
- # Only works because we know the entries of the basis are
- # integers. The argument ``check_axioms=False`` is required
- # because the trace inner-product method for this
- # class is a stub and can't actually be checked.
- J = MatrixEuclideanJordanAlgebra(QQ,
- basis,
- normalize_basis=False,
- check_field=False,
- check_axioms=False)
- a = J._charpoly_coefficients()
-
- # Unfortunately, changing the basis does change the
- # coefficients of the characteristic polynomial, but since
- # these are really the coefficients of the "characteristic
- # polynomial of" function, everything is still nice and
- # unevaluated. It's therefore "obvious" how scaling the
- # basis affects the coordinate variables X1, X2, et
- # cetera. Scaling the first basis vector up by "n" adds a
- # factor of 1/n into every "X1" term, for example. So here
- # we simply undo the basis_normalizer scaling that we
- # performed earlier.
- #
- # The a[0] access here is safe because trivial algebras
- # won't have any basis normalizers and therefore won't
- # make it to this "else" branch.
- XS = a[0].parent().gens()
- subs_dict = { XS[i]: self._basis_normalizers[i]*XS[i]
- for i in range(len(XS)) }
- return tuple( a_i.subs(subs_dict) for a_i in a )
-
-
- @staticmethod
- def real_embed(M):
- """
- Embed the matrix ``M`` into a space of real matrices.
-
- The matrix ``M`` can have entries in any field at the moment:
- the real numbers, complex numbers, or quaternions. And although
- they are not a field, we can probably support octonions at some
- point, too. This function returns a real matrix that "acts like"
- the original with respect to matrix multiplication; i.e.
-
- real_embed(M*N) = real_embed(M)*real_embed(N)
-
- """
- raise NotImplementedError
-
-
- @staticmethod
- def real_unembed(M):
- """
- The inverse of :meth:`real_embed`.
- """
- raise NotImplementedError
-
- @classmethod
- def matrix_inner_product(cls,X,Y):
- Xu = cls.real_unembed(X)
- Yu = cls.real_unembed(Y)
- tr = (Xu*Yu).trace()
-
- try:
- # Works in QQ, AA, RDF, et cetera.
- return tr.real()
- except AttributeError:
- # A quaternion doesn't have a real() method, but does
- # have coefficient_tuple() method that returns the
- # coefficients of 1, i, j, and k -- in that order.
- return tr.coefficient_tuple()[0]
-
-
-class RealMatrixEuclideanJordanAlgebra(MatrixEuclideanJordanAlgebra):
- @staticmethod
- def real_embed(M):
- """
- The identity function, for embedding real matrices into real
- matrices.
- """
- return M