X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feja_algebra.py;h=7f7b1f9f1c09703a2d8f48e295b203604fa5556c;hb=ef91dedb7fecddd4c0a5472e501188739b3d3d88;hp=4e66b2db251274d3091875a20056692f33849513;hpb=309fc28265693c9abd51780ea76f8beec39ce27f;p=sage.d.git diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index 4e66b2d..7f7b1f9 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -61,7 +61,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): """ SETUP:: - sage: from mjo.eja.eja_algebra import (JordanSpinEJA, random_eja) + sage: from mjo.eja.eja_algebra import ( + ....: FiniteDimensionalEuclideanJordanAlgebra, + ....: JordanSpinEJA, + ....: random_eja) EXAMPLES: @@ -75,13 +78,20 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): TESTS: - The ``field`` we're given must be real:: + The ``field`` we're given must be real with ``check=True``:: sage: JordanSpinEJA(2,QQbar) Traceback (most recent call last): ... ValueError: field is not real + The multiplication table must be square with ``check=True``:: + + sage: FiniteDimensionalEuclideanJordanAlgebra(QQ,((),())) + Traceback (most recent call last): + ... + ValueError: multiplication table is not square + """ if check: if not field.is_subring(RR): @@ -96,9 +106,15 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): category = MagmaticAlgebras(field).FiniteDimensional() category = category.WithBasis().Unital() + # The multiplication table had better be square + n = len(mult_table) + if check: + if not all( len(l) == n for l in mult_table ): + raise ValueError("multiplication table is not square") + fda = super(FiniteDimensionalEuclideanJordanAlgebra, self) fda.__init__(field, - range(len(mult_table)), + range(n), prefix=prefix, category=category) self.print_options(bracket='') @@ -114,6 +130,13 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): for ls in mult_table ] + if check: + if not self._is_commutative(): + raise ValueError("algebra is not commutative") + if not self._is_jordanian(): + raise ValueError("Jordan identity does not hold") + if not self._inner_product_is_associative(): + raise ValueError("inner product is not associative") def _element_constructor_(self, elt): """ @@ -235,11 +258,74 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): def product_on_basis(self, i, j): return self._multiplication_table[i][j] + def _is_commutative(self): + r""" + Whether or not this algebra's multiplication table is commutative. + + This method should of course always return ``True``, unless + this algebra was constructed with ``check=False`` and passed + an invalid multiplication table. + """ + return all( self.product_on_basis(i,j) == self.product_on_basis(i,j) + for i in range(self.dimension()) + for j in range(self.dimension()) ) + + def _is_jordanian(self): + r""" + Whether or not this algebra's multiplication table respects the + Jordan identity `(x^{2})(xy) = x(x^{2}y)`. + + We only check one arrangement of `x` and `y`, so for a + ``True`` result to be truly true, you should also check + :meth:`_is_commutative`. This method should of course always + return ``True``, unless this algebra was constructed with + ``check=False`` and passed an invalid multiplication table. + """ + return all( (self.monomial(i)**2)*(self.monomial(i)*self.monomial(j)) + == + (self.monomial(i))*((self.monomial(i)**2)*self.monomial(j)) + for i in range(self.dimension()) + for j in range(self.dimension()) ) + + def _inner_product_is_associative(self): + r""" + Return whether or not this algebra's inner product `B` is + associative; that is, whether or not `B(xy,z) = B(x,yz)`. + + This method should of course always return ``True``, unless + this algebra was constructed with ``check=False`` and passed + an invalid multiplication table. + """ + + # Used to check whether or not something is zero in an inexact + # ring. This number is sufficient to allow the construction of + # QuaternionHermitianEJA(2, RDF) with check=True. + epsilon = 1e-16 + + for i in range(self.dimension()): + for j in range(self.dimension()): + for k in range(self.dimension()): + x = self.monomial(i) + y = self.monomial(j) + z = self.monomial(k) + diff = (x*y).inner_product(z) - x.inner_product(y*z) + + if self.base_ring().is_exact(): + if diff != 0: + return False + else: + if diff.abs() > epsilon: + return False + + return True + @cached_method - def characteristic_polynomial(self): + def characteristic_polynomial_of(self): """ - Return a characteristic polynomial that works for all elements - of this algebra. + Return the algebra's "characteristic polynomial of" function, + which is itself a multivariate polynomial that, when evaluated + at the coordinates of some algebra element, returns that + element's characteristic polynomial. The resulting polynomial has `n+1` variables, where `n` is the dimension of this algebra. The first `n` variables correspond to @@ -259,7 +345,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): Alizadeh, Example 11.11:: sage: J = JordanSpinEJA(3) - sage: p = J.characteristic_polynomial(); p + sage: p = J.characteristic_polynomial_of(); p X1^2 - X2^2 - X3^2 + (-2*t)*X1 + t^2 sage: xvec = J.one().to_vector() sage: p(*xvec) @@ -272,7 +358,7 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): any argument:: sage: J = TrivialEJA() - sage: J.characteristic_polynomial() + sage: J.characteristic_polynomial_of() 1 """ @@ -579,6 +665,25 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): Vector space of degree 6 and dimension 2... sage: J1 Euclidean Jordan algebra of dimension 3... + sage: J0.one().natural_representation() + [0 0 0] + [0 0 0] + [0 0 1] + sage: orig_df = AA.options.display_format + sage: AA.options.display_format = 'radical' + sage: J.from_vector(J5.basis()[0]).natural_representation() + [ 0 0 1/2*sqrt(2)] + [ 0 0 0] + [1/2*sqrt(2) 0 0] + sage: J.from_vector(J5.basis()[1]).natural_representation() + [ 0 0 0] + [ 0 0 1/2*sqrt(2)] + [ 0 1/2*sqrt(2) 0] + sage: AA.options.display_format = orig_df + sage: J1.one().natural_representation() + [1 0 0] + [0 1 0] + [0 0 0] TESTS: @@ -593,9 +698,10 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): sage: J1.superalgebra() == J and J1.dimension() == J.dimension() True - The identity elements in the two subalgebras are the - projections onto their respective subspaces of the - superalgebra's identity element:: + The decomposition is into eigenspaces, and its components are + therefore necessarily orthogonal. Moreover, the identity + elements in the two subalgebras are the projections onto their + respective subspaces of the superalgebra's identity element:: sage: set_random_seed() sage: J = random_eja() @@ -605,6 +711,16 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): ....: x = J.random_element() sage: c = x.subalgebra_idempotent() sage: J0,J5,J1 = J.peirce_decomposition(c) + sage: ipsum = 0 + sage: for (w,y,z) in zip(J0.basis(), J5.basis(), J1.basis()): + ....: w = w.superalgebra_element() + ....: y = J.from_vector(y) + ....: z = z.superalgebra_element() + ....: ipsum += w.inner_product(y).abs() + ....: ipsum += w.inner_product(z).abs() + ....: ipsum += y.inner_product(z).abs() + sage: ipsum + 0 sage: J1(c) == J1.one() True sage: J0(J.one() - c) == J0.one() @@ -640,10 +756,61 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): return (J0, J5, J1) - def random_elements(self, count): + def random_element(self, thorough=False): + r""" + Return a random element of this algebra. + + Our algebra superclass method only returns a linear + combination of at most two basis elements. We instead + want the vector space "random element" method that + returns a more diverse selection. + + INPUT: + + - ``thorough`` -- (boolean; default False) whether or not we + should generate irrational coefficients for the random + element when our base ring is irrational; this slows the + algebra operations to a crawl, but any truly random method + should include them + + """ + # For a general base ring... maybe we can trust this to do the + # right thing? Unlikely, but. + V = self.vector_space() + v = V.random_element() + + if self.base_ring() is AA: + # The "random element" method of the algebraic reals is + # stupid at the moment, and only returns integers between + # -2 and 2, inclusive: + # + # https://trac.sagemath.org/ticket/30875 + # + # Instead, we implement our own "random vector" method, + # and then coerce that into the algebra. We use the vector + # space degree here instead of the dimension because a + # subalgebra could (for example) be spanned by only two + # vectors, each with five coordinates. We need to + # generate all five coordinates. + if thorough: + v *= QQbar.random_element().real() + else: + v *= QQ.random_element() + + return self.from_vector(V.coordinate_vector(v)) + + def random_elements(self, count, thorough=False): """ Return ``count`` random elements as a tuple. + INPUT: + + - ``thorough`` -- (boolean; default False) whether or not we + should generate irrational coefficients for the random + elements when our base ring is irrational; this slows the + algebra operations to a crawl, but any truly random method + should include them + SETUP:: sage: from mjo.eja.eja_algebra import JordanSpinEJA @@ -658,7 +825,8 @@ class FiniteDimensionalEuclideanJordanAlgebra(CombinatorialFreeModule): True """ - return tuple( self.random_element() for idx in range(count) ) + return tuple( self.random_element(thorough) + for idx in range(count) ) @classmethod def random_instance(cls, field=AA, **kwargs): @@ -2050,3 +2218,50 @@ class TrivialEJA(FiniteDimensionalEuclideanJordanAlgebra): # largest subalgebra generated by any element. fdeja.__init__(field, mult_table, **kwargs) self.rank.set_cache(0) + + +class DirectSumEJA(FiniteDimensionalEuclideanJordanAlgebra): + r""" + The external (orthogonal) direct sum of two other Euclidean Jordan + algebras. Essentially the Cartesian product of its two factors. + Every Euclidean Jordan algebra decomposes into an orthogonal + direct sum of simple Euclidean Jordan algebras, so no generality + is lost by providing only this construction. + + SETUP:: + + sage: from mjo.eja.eja_algebra import (HadamardEJA, + ....: RealSymmetricEJA, + ....: DirectSumEJA) + + EXAMPLES:: + + sage: J1 = HadamardEJA(2) + sage: J2 = RealSymmetricEJA(3) + sage: J = DirectSumEJA(J1,J2) + sage: J.dimension() + 8 + sage: J.rank() + 5 + + """ + def __init__(self, J1, J2, field=AA, **kwargs): + n1 = J1.dimension() + n2 = J2.dimension() + n = n1+n2 + V = VectorSpace(field, n) + mult_table = [ [ V.zero() for j in range(n) ] + for i in range(n) ] + for i in range(n1): + for j in range(n1): + p = (J1.monomial(i)*J1.monomial(j)).to_vector() + mult_table[i][j] = V(p.list() + [field.zero()]*n2) + + for i in range(n2): + for j in range(n2): + p = (J2.monomial(i)*J2.monomial(j)).to_vector() + mult_table[n1+i][n1+j] = V([field.zero()]*n1 + p.list()) + + fdeja = super(DirectSumEJA, self) + fdeja.__init__(field, mult_table, **kwargs) + self.rank.set_cache(J1.rank() + J2.rank())