""" Euclidean Jordan Algebras. These are formally-real Jordan Algebras; specifically those where u^2 + v^2 = 0 implies that u = v = 0. They are used in optimization, and have some additional nice methods beyond what can be supported in a general Jordan Algebra. """ from sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra import FiniteDimensionalAlgebra from sage.algebras.finite_dimensional_algebras.finite_dimensional_algebra_element import FiniteDimensionalAlgebraElement class FiniteDimensionalEuclideanJordanAlgebra(FiniteDimensionalAlgebra): @staticmethod def __classcall__(cls, field, mult_table, names='e', category=None): fda = super(FiniteDimensionalEuclideanJordanAlgebra, cls) return fda.__classcall_private__(cls, field, mult_table, names, category) def __init__(self, field, mult_table, names='e', category=None): fda = super(FiniteDimensionalEuclideanJordanAlgebra, self) fda.__init__(field, mult_table, names, category) def _repr_(self): """ Return a string representation of ``self``. """ return "Euclidean Jordan algebra of degree {} over {}".format(self.degree(), self.base_ring()) def rank(self): """ Return the rank of this EJA. """ raise NotImplementedError class Element(FiniteDimensionalAlgebraElement): """ An element of a Euclidean Jordan algebra. Since EJAs are commutative, the "right multiplication" matrix is also the left multiplication matrix and must be symmetric:: sage: set_random_seed() sage: J = eja_ln(5) sage: J.random_element().matrix().is_symmetric() True """ def __pow__(self, n): """ Return ``self`` raised to the power ``n``. Jordan algebras are always power-associative; see for example Faraut and Koranyi, Proposition II.1.2 (ii). """ A = self.parent() if n == 0: return A.one() elif n == 1: return self else: return A.element_class(A, self.vector()*(self.matrix()**(n-1))) def span_of_powers(self): """ Return the vector space spanned by successive powers of this element. """ # The dimension of the subalgebra can't be greater than # the big algebra, so just put everything into a list # and let span() get rid of the excess. V = self.vector().parent() return V.span( (self**d).vector() for d in xrange(V.dimension()) ) def degree(self): """ Compute the degree of this element the straightforward way according to the definition; by appending powers to a list and figuring out its dimension (that is, whether or not they're linearly dependent). EXAMPLES:: sage: J = eja_ln(4) sage: J.one().degree() 1 sage: e0,e1,e2,e3 = J.gens() sage: (e0 - e1).degree() 2 """ return self.span_of_powers().dimension() def minimal_polynomial(self): return self.matrix().minimal_polynomial() def characteristic_polynomial(self): return self.matrix().characteristic_polynomial() def eja_rn(dimension, field=QQ): """ Return the Euclidean Jordan Algebra corresponding to the set `R^n` under the Hadamard product. EXAMPLES: This multiplication table can be verified by hand:: sage: J = eja_rn(3) sage: e0,e1,e2 = J.gens() sage: e0*e0 e0 sage: e0*e1 0 sage: e0*e2 0 sage: e1*e1 e1 sage: e1*e2 0 sage: e2*e2 e2 """ # The FiniteDimensionalAlgebra constructor takes a list of # matrices, the ith representing right multiplication by the ith # basis element in the vector space. So if e_1 = (1,0,0), then # right (Hadamard) multiplication of x by e_1 picks out the first # component of x; and likewise for the ith basis element e_i. Qs = [ matrix(field, dimension, dimension, lambda k,j: 1*(k == j == i)) for i in xrange(dimension) ] return FiniteDimensionalEuclideanJordanAlgebra(field,Qs) def eja_ln(dimension, field=QQ): """ Return the Jordan algebra corresponding to the Lorentz "ice cream" cone of the given ``dimension``. EXAMPLES: This multiplication table can be verified by hand:: sage: J = eja_ln(4) sage: e0,e1,e2,e3 = J.gens() sage: e0*e0 e0 sage: e0*e1 e1 sage: e0*e2 e2 sage: e0*e3 e3 sage: e1*e2 0 sage: e1*e3 0 sage: e2*e3 0 In one dimension, this is the reals under multiplication:: sage: J1 = eja_ln(1) sage: J2 = eja_rn(1) sage: J1 == J2 True """ Qs = [] id_matrix = identity_matrix(field,dimension) for i in xrange(dimension): ei = id_matrix.column(i) Qi = zero_matrix(field,dimension) Qi.set_row(0, ei) Qi.set_column(0, ei) Qi += diagonal_matrix(dimension, [ei[0]]*dimension) # The addition of the diagonal matrix adds an extra ei[0] in the # upper-left corner of the matrix. Qi[0,0] = Qi[0,0] * ~field(2) Qs.append(Qi) return FiniteDimensionalEuclideanJordanAlgebra(field,Qs)