def product_on_basis(self, i, j):
return self._multiplication_table[i][j]
- def _a_regular_element(self):
- """
- Guess a regular element. Needed to compute the basis for our
- characteristic polynomial coefficients.
-
- SETUP::
-
- sage: from mjo.eja.eja_algebra import random_eja
-
- TESTS:
-
- Ensure that this hacky method succeeds for every algebra that we
- know how to construct::
-
- sage: set_random_seed()
- sage: J = random_eja()
- sage: J._a_regular_element().is_regular()
- True
-
- """
- gs = self.gens()
- z = self.sum( (i+1)*gs[i] for i in range(len(gs)) )
- if not z.is_regular():
- raise ValueError("don't know a regular element")
- return z
-
-
- @cached_method
- def _charpoly_basis_space(self):
- """
- Return the vector space spanned by the basis used in our
- characteristic polynomial coefficients. This is used not only to
- compute those coefficients, but also any time we need to
- evaluate the coefficients (like when we compute the trace or
- determinant).
- """
- z = self._a_regular_element()
- # Don't use the parent vector space directly here in case this
- # happens to be a subalgebra. In that case, we would be e.g.
- # two-dimensional but span_of_basis() would expect three
- # coordinates.
- V = VectorSpace(self.base_ring(), self.vector_space().dimension())
- basis = [ (z**k).to_vector() for k in range(self.rank()) ]
- V1 = V.span_of_basis( basis )
- b = (V1.basis() + V1.complement().basis())
- return V.span_of_basis(b)
-
-
- def _charpoly_coeff(self, i):
- """
- Return the coefficient polynomial "a_{i}" of this algebra's
- general characteristic polynomial.
-
- Having this be a separate cached method lets us compute and
- store the trace/determinant (a_{r-1} and a_{0} respectively)
- separate from the entire characteristic polynomial.
- """
- return self._charpoly_coefficients()[i]
-
-
@cached_method
def characteristic_polynomial(self):
"""
"""
Return the matrix space in which this algebra's natural basis
elements live.
+
+ Generally this will be an `n`-by-`1` column-vector space,
+ except when the algebra is trivial. There it's `n`-by-`n`
+ (where `n` is zero), to ensure that two elements of the
+ natural basis space (empty matrices) can be multiplied.
"""
- if self._natural_basis is None or len(self._natural_basis) == 0:
+ if self.is_trivial():
+ return MatrixSpace(self.base_ring(), 0)
+ elif self._natural_basis is None or len(self._natural_basis) == 0:
return MatrixSpace(self.base_ring(), self.dimension(), 1)
else:
return self._natural_basis[0].matrix_space()
# there's only one.
return cls(field)
- n = ZZ.random_element(cls._max_test_case_size()) + 1
+ n = ZZ.random_element(cls._max_test_case_size() + 1)
return cls(n, field, **kwargs)
@cached_method
for k in range(n) )
L_x = matrix(F, n, n, L_x_i_j)
+
+ r = None
+ if self.rank.is_in_cache():
+ r = self.rank()
+ # There's no need to pad the system with redundant
+ # columns if we *know* they'll be redundant.
+ n = r
+
# Compute an extra power in case the rank is equal to
# the dimension (otherwise, we would stop at x^(r-1)).
x_powers = [ (L_x**k)*self.one().to_vector()
AE = A.extended_echelon_form()
E = AE[:,n:]
A_rref = AE[:,:n]
- r = A_rref.rank()
+ if r is None:
+ r = A_rref.rank()
b = x_powers[r]
# The theory says that only the first "r" coefficients are
return x.to_vector().inner_product(y.to_vector())
-def random_eja(field=AA, nontrivial=False):
+def random_eja(field=AA):
"""
Return a "random" finite-dimensional Euclidean Jordan Algebra.
Euclidean Jordan algebra of dimension...
"""
- eja_classes = [HadamardEJA,
- JordanSpinEJA,
- RealSymmetricEJA,
- ComplexHermitianEJA,
- QuaternionHermitianEJA]
- if not nontrivial:
- eja_classes.append(TrivialEJA)
- classname = choice(eja_classes)
+ classname = choice([TrivialEJA,
+ HadamardEJA,
+ JordanSpinEJA,
+ RealSymmetricEJA,
+ ComplexHermitianEJA,
+ QuaternionHermitianEJA])
return classname.random_instance(field=field)
-
-
class MatrixEuclideanJordanAlgebra(FiniteDimensionalEuclideanJordanAlgebra):
@staticmethod
def _max_test_case_size():
J = MatrixEuclideanJordanAlgebra(QQ,
basis,
normalize_basis=False)
- return J._charpoly_coefficients()
+ 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
# is supposed to hold the entire long vector, and the subspace W
# of V will be spanned by the vectors that arise from symmetric
# matrices. Thus for S^2, dim(V) == 4 and dim(W) == 3.
+ if len(basis) == 0:
+ return []
+
field = basis[0].base_ring()
dimension = basis[0].nrows()
sage: x.operator().matrix().is_symmetric()
True
+ We can construct the (trivial) algebra of rank zero::
+
+ sage: RealSymmetricEJA(0)
+ Euclidean Jordan algebra of dimension 0 over Algebraic Real Field
+
"""
@classmethod
def _denormalized_basis(cls, n, field):
sage: x.operator().matrix().is_symmetric()
True
+ We can construct the (trivial) algebra of rank zero::
+
+ sage: ComplexHermitianEJA(0)
+ Euclidean Jordan algebra of dimension 0 over Algebraic Real Field
+
"""
@classmethod
sage: x.operator().matrix().is_symmetric()
True
+ We can construct the (trivial) algebra of rank zero::
+
+ sage: QuaternionHermitianEJA(0)
+ Euclidean Jordan algebra of dimension 0 over Algebraic Real Field
+
"""
@classmethod
def _denormalized_basis(cls, n, field):