X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=mjo%2Feja%2Feja_algebra.py;h=8b37a83602ef9b00b5a14c55785c2163b44df32b;hb=0cf1bd4fb459733e559f2040089e1905fc6af9ca;hp=106a0cddec06355a952e71d75699677b25dd7da9;hpb=fc29add6cf1d9ff4e8a240b0f8f2ca6672d4ea57;p=sage.d.git diff --git a/mjo/eja/eja_algebra.py b/mjo/eja/eja_algebra.py index 106a0cd..8b37a83 100644 --- a/mjo/eja/eja_algebra.py +++ b/mjo/eja/eja_algebra.py @@ -52,6 +52,98 @@ the other algebras. Cartesian products of these are also supported using the usual ``cartesian_product()`` function; as a result, we support (up to isomorphism) all Euclidean Jordan algebras. +At a minimum, the following are required to construct a Euclidean +Jordan algebra: + + * A basis of matrices, column vectors, or MatrixAlgebra elements + * A Jordan product defined on the basis + * Its inner product defined on the basis + +The real numbers form a Euclidean Jordan algebra when both the Jordan +and inner products are the usual multiplication. We use this as our +example, and demonstrate a few ways to construct an EJA. + +First, we can use one-by-one SageMath matrices with algebraic real +entries to represent real numbers. We define the Jordan and inner +products to be essentially real-number multiplication, with the only +difference being that the Jordan product again returns a one-by-one +matrix, whereas the inner product must return a scalar. Our basis for +the one-by-one matrices is of course the set consisting of a single +matrix with its sole entry non-zero:: + + sage: from mjo.eja.eja_algebra import FiniteDimensionalEJA + sage: jp = lambda X,Y: X*Y + sage: ip = lambda X,Y: X[0,0]*Y[0,0] + sage: b1 = matrix(AA, [[1]]) + sage: J1 = FiniteDimensionalEJA((b1,), jp, ip) + sage: J1 + Euclidean Jordan algebra of dimension 1 over Algebraic Real Field + +In fact, any positive scalar multiple of that inner-product would work:: + + sage: ip2 = lambda X,Y: 16*ip(X,Y) + sage: J2 = FiniteDimensionalEJA((b1,), jp, ip2) + sage: J2 + Euclidean Jordan algebra of dimension 1 over Algebraic Real Field + +But beware that your basis will be orthonormalized _with respect to the +given inner-product_ unless you pass ``orthonormalize=False`` to the +constructor. For example:: + + sage: J3 = FiniteDimensionalEJA((b1,), jp, ip2, orthonormalize=False) + sage: J3 + Euclidean Jordan algebra of dimension 1 over Algebraic Real Field + +To see the difference, you can take the first and only basis element +of the resulting algebra, and ask for it to be converted back into +matrix form:: + + sage: J1.basis()[0].to_matrix() + [1] + sage: J2.basis()[0].to_matrix() + [1/4] + sage: J3.basis()[0].to_matrix() + [1] + +Since square roots are used in that process, the default scalar field +that we use is the field of algebraic real numbers, ``AA``. You can +also Use rational numbers, but only if you either pass +``orthonormalize=False`` or know that orthonormalizing your basis +won't stray beyond the rational numbers. The example above would +have worked only because ``sqrt(16) == 4`` is rational. + +Another option for your basis is to use elemebts of a +:class:`MatrixAlgebra`:: + + sage: from mjo.matrix_algebra import MatrixAlgebra + sage: A = MatrixAlgebra(1,AA,AA) + sage: J4 = FiniteDimensionalEJA(A.gens(), jp, ip) + sage: J4 + Euclidean Jordan algebra of dimension 1 over Algebraic Real Field + sage: J4.basis()[0].to_matrix() + +---+ + | 1 | + +---+ + +An easier way to view the entire EJA basis in its original (but +perhaps orthonormalized) matrix form is to use the ``matrix_basis`` +method:: + + sage: J4.matrix_basis() + (+---+ + | 1 | + +---+,) + +In particular, a :class:`MatrixAlgebra` is needed to work around the +fact that matrices in SageMath must have entries in the same +(commutative and associative) ring as its scalars. There are many +Euclidean Jordan algebras whose elements are matrices that violate +those assumptions. The complex, quaternion, and octonion Hermitian +matrices all have entries in a ring (the complex numbers, quaternions, +or octonions...) that differs from the algebra's scalar ring (the real +numbers). Quaternions are also non-commutative; the octonions are +neither commutative nor associative. + SETUP:: sage: from mjo.eja.eja_algebra import random_eja @@ -104,6 +196,11 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): product. This will be applied to ``basis`` to compute an inner-product table (basically a matrix) for this algebra. + - ``matrix_space`` -- the space that your matrix basis lives in, + or ``None`` (the default). So long as your basis does not have + length zero you can omit this. But in trivial algebras, it is + required. + - ``field`` -- a subfield of the reals (default: ``AA``); the scalar field for the algebra. @@ -128,7 +225,6 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): sage: basis = tuple(b.superalgebra_element() for b in A.basis()) sage: J.subalgebra(basis, orthonormalize=False).is_associative() True - """ Element = FiniteDimensionalEJAElement @@ -137,6 +233,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): jordan_product, inner_product, field=AA, + matrix_space=None, orthonormalize=True, associative=None, cartesian_product=False, @@ -236,8 +333,14 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): basis = tuple(gram_schmidt(basis, inner_product)) # Save the (possibly orthonormalized) matrix basis for - # later... + # later, as well as the space that its elements live in. + # In most cases we can deduce the matrix space, but when + # n == 0 (that is, there are no basis elements) we cannot. self._matrix_basis = basis + if matrix_space is None: + self._matrix_space = self._matrix_basis[0].parent() + else: + self._matrix_space = matrix_space # Now create the vector space for the algebra, which will have # its own set of non-ambient coordinates (in terms of the @@ -1021,7 +1124,8 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): sage: J = ComplexHermitianEJA(2,field=QQ,orthonormalize=False) sage: J.matrix_space() - Full MatrixSpace of 4 by 4 dense matrices over Rational Field + Module of 2 by 2 matrices with entries in Algebraic Field over + the scalar ring Rational Field sage: J = QuaternionHermitianEJA(1,field=QQ,orthonormalize=False) sage: J.matrix_space() Module of 1 by 1 matrices with entries in Quaternion @@ -1029,10 +1133,7 @@ class FiniteDimensionalEJA(CombinatorialFreeModule): the scalar ring Rational Field """ - if self.is_trivial(): - return MatrixSpace(self.base_ring(), 0) - else: - return self.matrix_basis()[0].parent() + return self._matrix_space @cached_method @@ -1609,6 +1710,7 @@ class RationalBasisEJA(FiniteDimensionalEJA): jordan_product, inner_product, field=QQ, + matrix_space=self.matrix_space(), associative=self.is_associative(), orthonormalize=False, check_field=False, @@ -1705,25 +1807,35 @@ class ConcreteEJA(FiniteDimensionalEJA): """ @staticmethod - def _max_random_instance_size(): + def _max_random_instance_dimension(): + r""" + The maximum dimension of any random instance. Ten dimensions seems + to be about the point where everything takes a turn for the + worse. And dimension ten (but not nine) allows the 4-by-4 real + Hermitian matrices, the 2-by-2 quaternion Hermitian matrices, + and the 2-by-2 octonion Hermitian matrices. + """ + return 10 + + @staticmethod + def _max_random_instance_size(max_dimension): """ 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 algebra when it is used in a random test case. This size + (which can be passed to the algebra's constructor) is itself + based on the ``max_dimension`` parameter. This method must be implemented in each subclass. """ raise NotImplementedError @classmethod - def random_instance(cls, *args, **kwargs): + def random_instance(cls, max_dimension=None, *args, **kwargs): """ - Return a random instance of this type of algebra. + Return a random instance of this type of algebra whose dimension + is less than or equal to ``max_dimension``. If the dimension bound + is omitted, then the ``_max_random_instance_dimension()`` is used + to get a suitable bound. This method should be implemented in each subclass. """ @@ -1736,7 +1848,7 @@ class ConcreteEJA(FiniteDimensionalEJA): return eja_class.random_instance(*args, **kwargs) -class MatrixEJA: +class MatrixEJA(FiniteDimensionalEJA): @staticmethod def _denormalized_basis(A): """ @@ -1877,8 +1989,23 @@ class MatrixEJA: return tr.real() + def __init__(self, matrix_space, **kwargs): + # We know this is a valid EJA, but will double-check + # if the user passes check_axioms=True. + if "check_axioms" not in kwargs: kwargs["check_axioms"] = False + + + super().__init__(self._denormalized_basis(matrix_space), + self.jordan_product, + self.trace_inner_product, + field=matrix_space.base_ring(), + matrix_space=matrix_space, + **kwargs) -class RealSymmetricEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): + self.rank.set_cache(matrix_space.nrows()) + self.one.set_cache( self(matrix_space.one()) ) + +class RealSymmetricEJA(MatrixEJA, RationalBasisEJA, ConcreteEJA): """ The rank-n simple EJA consisting of real symmetric n-by-n matrices, the usual symmetric Jordan product, and the trace inner @@ -1912,8 +2039,8 @@ class RealSymmetricEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): The dimension of this algebra is `(n^2 + n) / 2`:: sage: set_random_seed() - sage: n_max = RealSymmetricEJA._max_random_instance_size() - sage: n = ZZ.random_element(1, n_max) + sage: d = RealSymmetricEJA._max_random_instance_dimension() + sage: n = RealSymmetricEJA._max_random_instance_size(d) sage: J = RealSymmetricEJA(n) sage: J.dimension() == (n^2 + n)/2 True @@ -1944,15 +2071,20 @@ class RealSymmetricEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): """ @staticmethod - def _max_random_instance_size(): - return 4 # Dimension 10 + def _max_random_instance_size(max_dimension): + # Obtained by solving d = (n^2 + n)/2. + # The ZZ-int-ZZ thing is just "floor." + return ZZ(int(ZZ(8*max_dimension + 1).sqrt()/2 - 1/2)) @classmethod - def random_instance(cls, **kwargs): + def random_instance(cls, max_dimension=None, **kwargs): """ Return a random instance of this type of algebra. """ - n = ZZ.random_element(cls._max_random_instance_size() + 1) + if max_dimension is None: + max_dimension = cls._max_random_instance_dimension() + max_size = cls._max_random_instance_size(max_dimension) + 1 + n = ZZ.random_element(max_size) return cls(n, **kwargs) def __init__(self, n, field=AA, **kwargs): @@ -1961,21 +2093,19 @@ class RealSymmetricEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): if "check_axioms" not in kwargs: kwargs["check_axioms"] = False A = MatrixSpace(field, n) - super().__init__(self._denormalized_basis(A), - self.jordan_product, - self.trace_inner_product, - field=field, - **kwargs) + super().__init__(A, **kwargs) - # TODO: this could be factored out somehow, but is left here - # because the MatrixEJA is not presently a subclass of the - # FDEJA class that defines rank() and one(). - self.rank.set_cache(n) - self.one.set_cache(self(A.one())) + from mjo.eja.eja_cache import real_symmetric_eja_coeffs + a = real_symmetric_eja_coeffs(self) + if a is not None: + if self._rational_algebra is None: + self._charpoly_coefficients.set_cache(a) + else: + self._rational_algebra._charpoly_coefficients.set_cache(a) -class ComplexHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): +class ComplexHermitianEJA(MatrixEJA, RationalBasisEJA, ConcreteEJA): """ The rank-n simple EJA consisting of complex Hermitian n-by-n matrices over the real numbers, the usual symmetric Jordan product, @@ -1988,21 +2118,36 @@ class ComplexHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): EXAMPLES: - In theory, our "field" can be any subfield of the reals:: + In theory, our "field" can be any subfield of the reals, but we + can't use inexact real fields at the moment because SageMath + doesn't know how to convert their elements into complex numbers, + or even into algebraic reals:: - sage: ComplexHermitianEJA(2, field=RDF, check_axioms=True) - Euclidean Jordan algebra of dimension 4 over Real Double Field - sage: ComplexHermitianEJA(2, field=RR, check_axioms=True) - Euclidean Jordan algebra of dimension 4 over Real Field with - 53 bits of precision + sage: QQbar(RDF(1)) + Traceback (most recent call last): + ... + TypeError: Illegal initializer for algebraic number + sage: AA(RR(1)) + Traceback (most recent call last): + ... + TypeError: Illegal initializer for algebraic number + + This causes the following error when we try to scale a matrix of + complex numbers by an inexact real number:: + + sage: ComplexHermitianEJA(2,field=RR) + Traceback (most recent call last): + ... + TypeError: Unable to coerce entries (=(1.00000000000000, + -0.000000000000000)) to coefficients in Algebraic Real Field TESTS: The dimension of this algebra is `n^2`:: sage: set_random_seed() - sage: n_max = ComplexHermitianEJA._max_random_instance_size() - sage: n = ZZ.random_element(1, n_max) + sage: d = ComplexHermitianEJA._max_random_instance_dimension() + sage: n = ComplexHermitianEJA._max_random_instance_size(d) sage: J = ComplexHermitianEJA(n) sage: J.dimension() == n^2 True @@ -2039,32 +2184,34 @@ class ComplexHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): from mjo.hurwitz import ComplexMatrixAlgebra A = ComplexMatrixAlgebra(n, scalars=field) - super().__init__(self._denormalized_basis(A), - self.jordan_product, - self.trace_inner_product, - field=field, - **kwargs) + super().__init__(A, **kwargs) - # TODO: this could be factored out somehow, but is left here - # because the MatrixEJA is not presently a subclass of the - # FDEJA class that defines rank() and one(). - self.rank.set_cache(n) - self.one.set_cache(self(A.one())) + from mjo.eja.eja_cache import complex_hermitian_eja_coeffs + a = complex_hermitian_eja_coeffs(self) + if a is not None: + if self._rational_algebra is None: + self._charpoly_coefficients.set_cache(a) + else: + self._rational_algebra._charpoly_coefficients.set_cache(a) @staticmethod - def _max_random_instance_size(): - return 3 # Dimension 9 + def _max_random_instance_size(max_dimension): + # Obtained by solving d = n^2. + # The ZZ-int-ZZ thing is just "floor." + return ZZ(int(ZZ(max_dimension).sqrt())) @classmethod - def random_instance(cls, **kwargs): + def random_instance(cls, max_dimension=None, **kwargs): """ Return a random instance of this type of algebra. """ - n = ZZ.random_element(cls._max_random_instance_size() + 1) + if max_dimension is None: + max_dimension = cls._max_random_instance_dimension() + n = ZZ.random_element(cls._max_random_instance_size(max_dimension) + 1) return cls(n, **kwargs) -class QuaternionHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): +class QuaternionHermitianEJA(MatrixEJA, RationalBasisEJA, ConcreteEJA): r""" The rank-n simple EJA consisting of self-adjoint n-by-n quaternion matrices, the usual symmetric Jordan product, and the @@ -2090,8 +2237,8 @@ class QuaternionHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): The dimension of this algebra is `2*n^2 - n`:: sage: set_random_seed() - sage: n_max = QuaternionHermitianEJA._max_random_instance_size() - sage: n = ZZ.random_element(1, n_max) + sage: d = QuaternionHermitianEJA._max_random_instance_dimension() + sage: n = QuaternionHermitianEJA._max_random_instance_size(d) sage: J = QuaternionHermitianEJA(n) sage: J.dimension() == 2*(n^2) - n True @@ -2128,40 +2275,44 @@ class QuaternionHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): from mjo.hurwitz import QuaternionMatrixAlgebra A = QuaternionMatrixAlgebra(n, scalars=field) - super().__init__(self._denormalized_basis(A), - self.jordan_product, - self.trace_inner_product, - field=field, - **kwargs) + super().__init__(A, **kwargs) + + from mjo.eja.eja_cache import quaternion_hermitian_eja_coeffs + a = quaternion_hermitian_eja_coeffs(self) + if a is not None: + if self._rational_algebra is None: + self._charpoly_coefficients.set_cache(a) + else: + self._rational_algebra._charpoly_coefficients.set_cache(a) - # TODO: this could be factored out somehow, but is left here - # because the MatrixEJA is not presently a subclass of the - # FDEJA class that defines rank() and one(). - self.rank.set_cache(n) - self.one.set_cache(self(A.one())) @staticmethod - def _max_random_instance_size(): + def _max_random_instance_size(max_dimension): r""" The maximum rank of a random QuaternionHermitianEJA. """ - return 2 # Dimension 6 + # Obtained by solving d = 2n^2 - n. + # The ZZ-int-ZZ thing is just "floor." + return ZZ(int(ZZ(8*max_dimension + 1).sqrt()/4 + 1/4)) @classmethod - def random_instance(cls, **kwargs): + def random_instance(cls, max_dimension=None, **kwargs): """ Return a random instance of this type of algebra. """ - n = ZZ.random_element(cls._max_random_instance_size() + 1) + if max_dimension is None: + max_dimension = cls._max_random_instance_dimension() + n = ZZ.random_element(cls._max_random_instance_size(max_dimension) + 1) return cls(n, **kwargs) -class OctonionHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): +class OctonionHermitianEJA(MatrixEJA, RationalBasisEJA, ConcreteEJA): r""" SETUP:: sage: from mjo.eja.eja_algebra import (FiniteDimensionalEJA, ....: OctonionHermitianEJA) + sage: from mjo.hurwitz import Octonions, OctonionMatrixAlgebra EXAMPLES: @@ -2176,7 +2327,8 @@ class OctonionHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): After a change-of-basis, the 2-by-2 algebra has the same multiplication table as the ten-dimensional Jordan spin algebra:: - sage: b = OctonionHermitianEJA._denormalized_basis(2,QQ) + sage: A = OctonionMatrixAlgebra(2,Octonions(QQ),QQ) + sage: b = OctonionHermitianEJA._denormalized_basis(A) sage: basis = (b[0] + b[9],) + b[1:9] + (b[0] - b[9],) sage: jp = OctonionHermitianEJA.jordan_product sage: ip = OctonionHermitianEJA.trace_inner_product @@ -2242,18 +2394,29 @@ class OctonionHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): """ @staticmethod - def _max_random_instance_size(): + def _max_random_instance_size(max_dimension): r""" The maximum rank of a random QuaternionHermitianEJA. """ - return 1 # Dimension 1 + # There's certainly a formula for this, but with only four + # cases to worry about, I'm not that motivated to derive it. + if max_dimension >= 27: + return 3 + elif max_dimension >= 10: + return 2 + elif max_dimension >= 1: + return 1 + else: + return 0 @classmethod - def random_instance(cls, **kwargs): + def random_instance(cls, max_dimension=None, **kwargs): """ Return a random instance of this type of algebra. """ - n = ZZ.random_element(cls._max_random_instance_size() + 1) + if max_dimension is None: + max_dimension = cls._max_random_instance_dimension() + n = ZZ.random_element(cls._max_random_instance_size(max_dimension) + 1) return cls(n, **kwargs) def __init__(self, n, field=AA, **kwargs): @@ -2267,17 +2430,15 @@ class OctonionHermitianEJA(RationalBasisEJA, ConcreteEJA, MatrixEJA): from mjo.hurwitz import OctonionMatrixAlgebra A = OctonionMatrixAlgebra(n, scalars=field) - super().__init__(self._denormalized_basis(A), - self.jordan_product, - self.trace_inner_product, - field=field, - **kwargs) + super().__init__(A, **kwargs) - # TODO: this could be factored out somehow, but is left here - # because the MatrixEJA is not presently a subclass of the - # FDEJA class that defines rank() and one(). - self.rank.set_cache(n) - self.one.set_cache(self(A.one())) + from mjo.eja.eja_cache import octonion_hermitian_eja_coeffs + a = octonion_hermitian_eja_coeffs(self) + if a is not None: + if self._rational_algebra is None: + self._charpoly_coefficients.set_cache(a) + else: + self._rational_algebra._charpoly_coefficients.set_cache(a) class AlbertEJA(OctonionHermitianEJA): @@ -2342,13 +2503,14 @@ class HadamardEJA(RationalBasisEJA, ConcreteEJA): (r0, r1, r2) """ def __init__(self, n, field=AA, **kwargs): + MS = MatrixSpace(field, n, 1) + if n == 0: jordan_product = lambda x,y: x inner_product = lambda x,y: x else: def jordan_product(x,y): - P = x.parent() - return P( xi*yi for (xi,yi) in zip(x,y) ) + return MS( xi*yi for (xi,yi) in zip(x,y) ) def inner_product(x,y): return (x.T*y)[0,0] @@ -2362,34 +2524,41 @@ class HadamardEJA(RationalBasisEJA, ConcreteEJA): if "orthonormalize" not in kwargs: kwargs["orthonormalize"] = False if "check_axioms" not in kwargs: kwargs["check_axioms"] = False - column_basis = tuple( b.column() - for b in FreeModule(field, n).basis() ) + column_basis = tuple( MS(b) for b in FreeModule(field, n).basis() ) super().__init__(column_basis, jordan_product, inner_product, field=field, + matrix_space=MS, associative=True, **kwargs) self.rank.set_cache(n) - if n == 0: - self.one.set_cache( self.zero() ) - else: - self.one.set_cache( sum(self.gens()) ) + self.one.set_cache( self.sum(self.gens()) ) @staticmethod - def _max_random_instance_size(): + def _max_random_instance_dimension(): r""" - The maximum dimension of a random HadamardEJA. + There's no reason to go higher than five here. That's + enough to get the point across. """ return 5 + @staticmethod + def _max_random_instance_size(max_dimension): + r""" + The maximum size (=dimension) of a random HadamardEJA. + """ + return max_dimension + @classmethod - def random_instance(cls, **kwargs): + def random_instance(cls, max_dimension=None, **kwargs): """ Return a random instance of this type of algebra. """ - n = ZZ.random_element(cls._max_random_instance_size() + 1) + if max_dimension is None: + max_dimension = cls._max_random_instance_dimension() + n = ZZ.random_element(cls._max_random_instance_size(max_dimension) + 1) return cls(n, **kwargs) @@ -2489,22 +2658,22 @@ class BilinearFormEJA(RationalBasisEJA, ConcreteEJA): # verify things, we'll skip the rest of the checks. if "check_axioms" not in kwargs: kwargs["check_axioms"] = False + n = B.nrows() + MS = MatrixSpace(field, n, 1) + def inner_product(x,y): return (y.T*B*x)[0,0] def jordan_product(x,y): - P = x.parent() x0 = x[0,0] xbar = x[1:,0] y0 = y[0,0] ybar = y[1:,0] z0 = inner_product(y,x) zbar = y0*xbar + x0*ybar - return P([z0] + zbar.list()) + return MS([z0] + zbar.list()) - n = B.nrows() - column_basis = tuple( b.column() - for b in FreeModule(field, n).basis() ) + column_basis = tuple( MS(b) for b in FreeModule(field, n).basis() ) # TODO: I haven't actually checked this, but it seems legit. associative = False @@ -2515,6 +2684,7 @@ class BilinearFormEJA(RationalBasisEJA, ConcreteEJA): jordan_product, inner_product, field=field, + matrix_space=MS, associative=associative, **kwargs) @@ -2522,25 +2692,34 @@ class BilinearFormEJA(RationalBasisEJA, ConcreteEJA): # one-dimensional ambient space (because the rank is bounded # by the ambient dimension). self.rank.set_cache(min(n,2)) - if n == 0: self.one.set_cache( self.zero() ) else: self.one.set_cache( self.monomial(0) ) @staticmethod - def _max_random_instance_size(): + def _max_random_instance_dimension(): r""" - The maximum dimension of a random BilinearFormEJA. + There's no reason to go higher than five here. That's + enough to get the point across. """ return 5 + @staticmethod + def _max_random_instance_size(max_dimension): + r""" + The maximum size (=dimension) of a random BilinearFormEJA. + """ + return max_dimension + @classmethod - def random_instance(cls, **kwargs): + def random_instance(cls, max_dimension=None, **kwargs): """ Return a random instance of this algebra. """ - n = ZZ.random_element(cls._max_random_instance_size() + 1) + if max_dimension is None: + max_dimension = cls._max_random_instance_dimension() + n = ZZ.random_element(cls._max_random_instance_size(max_dimension) + 1) if n.is_zero(): B = matrix.identity(ZZ, n) return cls(B, **kwargs) @@ -2623,21 +2802,16 @@ class JordanSpinEJA(BilinearFormEJA): # can pass in a field! super().__init__(B, *args, **kwargs) - @staticmethod - def _max_random_instance_size(): - r""" - The maximum dimension of a random JordanSpinEJA. - """ - return 5 - @classmethod - def random_instance(cls, **kwargs): + def random_instance(cls, max_dimension=None, **kwargs): """ Return a random instance of this type of algebra. Needed here to override the implementation for ``BilinearFormEJA``. """ - n = ZZ.random_element(cls._max_random_instance_size() + 1) + if max_dimension is None: + max_dimension = cls._max_random_instance_dimension() + n = ZZ.random_element(cls._max_random_instance_size(max_dimension) + 1) return cls(n, **kwargs) @@ -2670,10 +2844,11 @@ class TrivialEJA(RationalBasisEJA, ConcreteEJA): 0 """ - def __init__(self, **kwargs): + def __init__(self, field=AA, **kwargs): jordan_product = lambda x,y: x - inner_product = lambda x,y: 0 + inner_product = lambda x,y: field.zero() basis = () + MS = MatrixSpace(field,0) # New defaults for keyword arguments if "orthonormalize" not in kwargs: kwargs["orthonormalize"] = False @@ -2683,6 +2858,8 @@ class TrivialEJA(RationalBasisEJA, ConcreteEJA): jordan_product, inner_product, associative=True, + field=field, + matrix_space=MS, **kwargs) # The rank is zero using my definition, namely the dimension of the @@ -2860,7 +3037,14 @@ class CartesianProductEJA(FiniteDimensionalEJA): associative = all( f.is_associative() for f in factors ) - MS = self.matrix_space() + # Compute my matrix space. This category isn't perfect, but + # is good enough for what we need to do. + MS_cat = MagmaticAlgebras(field).FiniteDimensional().WithBasis() + MS_cat = MS_cat.Unital().CartesianProducts() + MS_factors = tuple( J.matrix_space() for J in factors ) + from sage.sets.cartesian_product import CartesianProduct + MS = CartesianProduct(MS_factors, MS_cat) + basis = [] zero = MS.zero() for i in range(m): @@ -2895,15 +3079,16 @@ class CartesianProductEJA(FiniteDimensionalEJA): jordan_product, inner_product, field=field, + matrix_space=MS, orthonormalize=False, associative=associative, cartesian_product=True, check_field=False, check_axioms=False) + self.rank.set_cache(sum(J.rank() for J in factors)) ones = tuple(J.one().to_matrix() for J in factors) self.one.set_cache(self(ones)) - self.rank.set_cache(sum(J.rank() for J in factors)) def cartesian_factors(self): # Copy/pasted from CombinatorialFreeModule_CartesianProduct. @@ -2955,11 +3140,13 @@ class CartesianProductEJA(FiniteDimensionalEJA): sage: J2 = ComplexHermitianEJA(1) sage: J = cartesian_product([J1,J2]) sage: J.one().to_matrix()[0] - [1 0] - [0 1] + +---+ + | 1 | + +---+ sage: J.one().to_matrix()[1] - [1 0] - [0 1] + +---+ + | 1 | + +---+ :: @@ -2976,16 +3163,7 @@ class CartesianProductEJA(FiniteDimensionalEJA): +----+ """ - scalars = self.cartesian_factor(0).base_ring() - - # This category isn't perfect, but is good enough for what we - # need to do. - cat = MagmaticAlgebras(scalars).FiniteDimensional().WithBasis() - cat = cat.Unital().CartesianProducts() - factors = tuple( J.matrix_space() for J in self.cartesian_factors() ) - - from sage.sets.cartesian_product import CartesianProduct - return CartesianProduct(factors, cat) + return super().matrix_space() @cached_method @@ -3237,15 +3415,23 @@ class RationalBasisCartesianProductEJA(CartesianProductEJA, RationalBasisEJA.CartesianProduct = RationalBasisCartesianProductEJA -def random_eja(*args, **kwargs): - J1 = ConcreteEJA.random_instance(*args, **kwargs) +def random_eja(max_dimension=None, *args, **kwargs): + # Use the ConcreteEJA default as the total upper bound (regardless + # of any whether or not any individual factors set a lower limit). + if max_dimension is None: + max_dimension = ConcreteEJA._max_random_instance_dimension() + J1 = ConcreteEJA.random_instance(max_dimension, *args, **kwargs) - # This might make Cartesian products appear roughly as often as - # any other ConcreteEJA. - if ZZ.random_element(len(ConcreteEJA.__subclasses__()) + 1) == 0: - # Use random_eja() again so we can get more than two factors. - J2 = random_eja(*args, **kwargs) - J = cartesian_product([J1,J2]) - return J - else: + + # Roll the dice to see if we attempt a Cartesian product. + dice_roll = ZZ.random_element(len(ConcreteEJA.__subclasses__()) + 1) + new_max_dimension = max_dimension - J1.dimension() + if new_max_dimension == 0 or dice_roll != 0: + # If it's already as big as we're willing to tolerate, just + # return it and don't worry about Cartesian products. return J1 + else: + # Use random_eja() again so we can get more than two factors + # if the sub-call also Decides on a cartesian product. + J2 = random_eja(new_max_dimension, *args, **kwargs) + return cartesian_product([J1,J2])