# we see in things like x = 1*e1 + 2*e2.
vector_basis = basis
- from sage.structure.element import is_Matrix
- basis_is_matrices = False
-
degree = 0
if n > 0:
- if is_Matrix(basis[0]):
- if basis[0].is_square():
- # TODO: this ugly is_square() hack works around the problem
- # of passing to_matrix()ed vectors in as the basis from a
- # subalgebra. They aren't REALLY matrices, at least not of
- # the type that we assume here... Ugh.
- basis_is_matrices = True
- from mjo.eja.eja_utils import _vec2mat
- vector_basis = tuple( map(_mat2vec,basis) )
- degree = basis[0].nrows()**2
- else:
- # convert from column matrices to vectors, yuck
- basis = tuple( map(_mat2vec,basis) )
- vector_basis = basis
- degree = basis[0].degree()
- else:
- degree = basis[0].degree()
+ # Works on both column and square matrices...
+ degree = len(basis[0].list())
- # Build an ambient space that fits...
+ # Build an ambient space that fits our matrix basis when
+ # written out as "long vectors."
V = VectorSpace(field, degree)
- # We overwrite the name "vector_basis" in a second, but never modify it
- # in place, to this effectively makes a copy of it.
- deortho_vector_basis = vector_basis
+ # The matrix that will hole the orthonormal -> unorthonormal
+ # coordinate transformation.
self._deortho_matrix = None
if orthonormalize:
- from mjo.eja.eja_utils import gram_schmidt
- if basis_is_matrices:
- vector_ip = lambda x,y: inner_product(_vec2mat(x), _vec2mat(y))
- vector_basis = gram_schmidt(vector_basis, vector_ip)
- else:
- vector_basis = gram_schmidt(vector_basis, inner_product)
+ # Save a copy of the un-orthonormalized basis for later.
+ # Convert it to ambient V (vector) coordinates while we're
+ # at it, because we'd have to do it later anyway.
+ deortho_vector_basis = tuple( V(b.list()) for b in basis )
- # Normalize the "matrix" basis, too!
- basis = vector_basis
+ from mjo.eja.eja_utils import gram_schmidt
+ basis = gram_schmidt(basis, inner_product)
- if basis_is_matrices:
- basis = tuple( map(_vec2mat,basis) )
+ # Save the (possibly orthonormalized) matrix basis for
+ # later...
+ self._matrix_basis = basis
- # Save the matrix "basis" for later... this is the last time we'll
- # reference it in this constructor.
- if basis_is_matrices:
- self._matrix_basis = basis
- else:
- MS = MatrixSpace(self.base_ring(), degree, 1)
- self._matrix_basis = tuple( MS(b) for b in basis )
-
- # Now create the vector space for the algebra...
+ # Now create the vector space for the algebra, which will have
+ # its own set of non-ambient coordinates (in terms of the
+ # supplied basis).
+ vector_basis = tuple( V(b.list()) for b in basis )
W = V.span_of_basis( vector_basis, check=check_axioms)
if orthonormalize:
# Now we actually compute the multiplication and inner-product
# tables/matrices using the possibly-orthonormalized basis.
self._inner_product_matrix = matrix.zero(field, n)
- self._multiplication_table = [ [0 for j in range(i+1)] for i in range(n) ]
+ self._multiplication_table = [ [0 for j in range(i+1)]
+ for i in range(n) ]
- print("vector_basis:")
- print(vector_basis)
# Note: the Jordan and inner-products are defined in terms
# of the ambient basis. It's important that their arguments
# are in ambient coordinates as well.
for i in range(n):
for j in range(i+1):
# ortho basis w.r.t. ambient coords
- q_i = vector_basis[i]
- q_j = vector_basis[j]
-
- if basis_is_matrices:
- q_i = _vec2mat(q_i)
- q_j = _vec2mat(q_j)
+ q_i = basis[i]
+ q_j = basis[j]
elt = jordan_product(q_i, q_j)
ip = inner_product(q_i, q_j)
- if basis_is_matrices:
- # do another mat2vec because the multiplication
- # table is in terms of vectors
- elt = _mat2vec(elt)
-
- # TODO: the jordan product turns things back into
- # matrices here even if they're supposed to be
- # vectors. ugh. Can we get rid of vectors all together
- # please?
- elt = W.coordinate_vector(elt)
+ # The jordan product returns a matrixy answer, so we
+ # have to convert it to the algebra coordinates.
+ elt = W.coordinate_vector(V(elt.list()))
self._multiplication_table[i][j] = self.from_vector(elt)
self._inner_product_matrix[i,j] = ip
self._inner_product_matrix[j,i] = ip
"""
def __init__(self, n, **kwargs):
- def jordan_product(x,y):
- P = x.parent()
- return P(tuple( xi*yi for (xi,yi) in zip(x,y) ))
- def inner_product(x,y):
- return x.inner_product(y)
+ 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) )
+
+ def inner_product(x,y):
+ return (x.T*y)[0,0]
# New defaults for keyword arguments. Don't orthonormalize
# because our basis is already orthonormal with respect to our
if "orthonormalize" not in kwargs: kwargs["orthonormalize"] = False
if "check_axioms" not in kwargs: kwargs["check_axioms"] = False
-
- standard_basis = FreeModule(ZZ, n).basis()
- super(HadamardEJA, self).__init__(standard_basis,
- jordan_product,
- inner_product,
- **kwargs)
+ column_basis = tuple( b.column() for b in FreeModule(ZZ, n).basis() )
+ super().__init__(column_basis, jordan_product, inner_product, **kwargs)
self.rank.set_cache(n)
if n == 0:
# inappropriate for us.
return cls(**kwargs)
-class DirectSumEJA(ConcreteEJA):
- 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 (random_eja,
- ....: 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
-
- TESTS:
-
- The external direct sum construction is only valid when the two factors
- have the same base ring; an error is raised otherwise::
-
- sage: set_random_seed()
- sage: J1 = random_eja(field=AA)
- sage: J2 = random_eja(field=QQ,orthonormalize=False)
- sage: J = DirectSumEJA(J1,J2)
- Traceback (most recent call last):
- ...
- ValueError: algebras must share the same base field
-
- """
- def __init__(self, J1, J2, **kwargs):
- if J1.base_ring() != J2.base_ring():
- raise ValueError("algebras must share the same base field")
- field = J1.base_ring()
-
- self._factors = (J1, J2)
- n1 = J1.dimension()
- n2 = J2.dimension()
- n = n1+n2
- V = VectorSpace(field, n)
- mult_table = [ [ V.zero() for j in range(i+1) ]
- for i in range(n) ]
- for i in range(n1):
- for j in range(i+1):
- 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(i+1):
- p = (J2.monomial(i)*J2.monomial(j)).to_vector()
- mult_table[n1+i][n1+j] = V([field.zero()]*n1 + p.list())
-
- # TODO: build the IP table here from the two constituent IP
- # matrices (it'll be block diagonal, I think).
- ip_table = [ [ field.zero() for j in range(i+1) ]
- for i in range(n) ]
- super(DirectSumEJA, self).__init__(field,
- mult_table,
- ip_table,
- check_axioms=False,
- **kwargs)
- self.rank.set_cache(J1.rank() + J2.rank())
-
-
- def factors(self):
- r"""
- Return the pair of this algebra's factors.
-
- SETUP::
-
- sage: from mjo.eja.eja_algebra import (HadamardEJA,
- ....: JordanSpinEJA,
- ....: DirectSumEJA)
-
- EXAMPLES::
-
- sage: J1 = HadamardEJA(2, field=QQ)
- sage: J2 = JordanSpinEJA(3, field=QQ)
- sage: J = DirectSumEJA(J1,J2)
- sage: J.factors()
- (Euclidean Jordan algebra of dimension 2 over Rational Field,
- Euclidean Jordan algebra of dimension 3 over Rational Field)
-
- """
- return self._factors
-
- def projections(self):
- r"""
- Return a pair of projections onto this algebra's factors.
-
- SETUP::
-
- sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
- ....: ComplexHermitianEJA,
- ....: DirectSumEJA)
-
- EXAMPLES::
-
- sage: J1 = JordanSpinEJA(2)
- sage: J2 = ComplexHermitianEJA(2)
- sage: J = DirectSumEJA(J1,J2)
- sage: (pi_left, pi_right) = J.projections()
- sage: J.one().to_vector()
- (1, 0, 1, 0, 0, 1)
- sage: pi_left(J.one()).to_vector()
- (1, 0)
- sage: pi_right(J.one()).to_vector()
- (1, 0, 0, 1)
-
- """
- (J1,J2) = self.factors()
- m = J1.dimension()
- n = J2.dimension()
- V_basis = self.vector_space().basis()
- # Need to specify the dimensions explicitly so that we don't
- # wind up with a zero-by-zero matrix when we want e.g. a
- # zero-by-two matrix (important for composing things).
- P1 = matrix(self.base_ring(), m, m+n, V_basis[:m])
- P2 = matrix(self.base_ring(), n, m+n, V_basis[m:])
- pi_left = FiniteDimensionalEJAOperator(self,J1,P1)
- pi_right = FiniteDimensionalEJAOperator(self,J2,P2)
- return (pi_left, pi_right)
-
- def inclusions(self):
- r"""
- Return the pair of inclusion maps from our factors into us.
-
- SETUP::
-
- sage: from mjo.eja.eja_algebra import (random_eja,
- ....: JordanSpinEJA,
- ....: RealSymmetricEJA,
- ....: DirectSumEJA)
-
- EXAMPLES::
-
- sage: J1 = JordanSpinEJA(3)
- sage: J2 = RealSymmetricEJA(2)
- sage: J = DirectSumEJA(J1,J2)
- sage: (iota_left, iota_right) = J.inclusions()
- sage: iota_left(J1.zero()) == J.zero()
- True
- sage: iota_right(J2.zero()) == J.zero()
- True
- sage: J1.one().to_vector()
- (1, 0, 0)
- sage: iota_left(J1.one()).to_vector()
- (1, 0, 0, 0, 0, 0)
- sage: J2.one().to_vector()
- (1, 0, 1)
- sage: iota_right(J2.one()).to_vector()
- (0, 0, 0, 1, 0, 1)
- sage: J.one().to_vector()
- (1, 0, 0, 1, 0, 1)
-
- TESTS:
-
- Composing a projection with the corresponding inclusion should
- produce the identity map, and mismatching them should produce
- the zero map::
-
- sage: set_random_seed()
- sage: J1 = random_eja()
- sage: J2 = random_eja()
- sage: J = DirectSumEJA(J1,J2)
- sage: (iota_left, iota_right) = J.inclusions()
- sage: (pi_left, pi_right) = J.projections()
- sage: pi_left*iota_left == J1.one().operator()
- True
- sage: pi_right*iota_right == J2.one().operator()
- True
- sage: (pi_left*iota_right).is_zero()
- True
- sage: (pi_right*iota_left).is_zero()
- True
-
- """
- (J1,J2) = self.factors()
- m = J1.dimension()
- n = J2.dimension()
- V_basis = self.vector_space().basis()
- # Need to specify the dimensions explicitly so that we don't
- # wind up with a zero-by-zero matrix when we want e.g. a
- # two-by-zero matrix (important for composing things).
- I1 = matrix.column(self.base_ring(), m, m+n, V_basis[:m])
- I2 = matrix.column(self.base_ring(), n, m+n, V_basis[m:])
- iota_left = FiniteDimensionalEJAOperator(J1,self,I1)
- iota_right = FiniteDimensionalEJAOperator(J2,self,I2)
- return (iota_left, iota_right)
-
- def inner_product(self, x, y):
- r"""
- The standard Cartesian inner-product.
-
- We project ``x`` and ``y`` onto our factors, and add up the
- inner-products from the subalgebras.
-
- SETUP::
-
-
- sage: from mjo.eja.eja_algebra import (HadamardEJA,
- ....: QuaternionHermitianEJA,
- ....: DirectSumEJA)
-
- EXAMPLE::
-
- sage: J1 = HadamardEJA(3,field=QQ)
- sage: J2 = QuaternionHermitianEJA(2,field=QQ,orthonormalize=False)
- sage: J = DirectSumEJA(J1,J2)
- sage: x1 = J1.one()
- sage: x2 = x1
- sage: y1 = J2.one()
- sage: y2 = y1
- sage: x1.inner_product(x2)
- 3
- sage: y1.inner_product(y2)
- 2
- sage: J.one().inner_product(J.one())
- 5
-
- """
- (pi_left, pi_right) = self.projections()
- x1 = pi_left(x)
- x2 = pi_right(x)
- y1 = pi_left(y)
- y2 = pi_right(y)
-
- return (x1.inner_product(y1) + x2.inner_product(y2))
+# class DirectSumEJA(ConcreteEJA):
+# 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 (random_eja,
+# ....: 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
+
+# TESTS:
+
+# The external direct sum construction is only valid when the two factors
+# have the same base ring; an error is raised otherwise::
+
+# sage: set_random_seed()
+# sage: J1 = random_eja(field=AA)
+# sage: J2 = random_eja(field=QQ,orthonormalize=False)
+# sage: J = DirectSumEJA(J1,J2)
+# Traceback (most recent call last):
+# ...
+# ValueError: algebras must share the same base field
+
+# """
+# def __init__(self, J1, J2, **kwargs):
+# if J1.base_ring() != J2.base_ring():
+# raise ValueError("algebras must share the same base field")
+# field = J1.base_ring()
+
+# self._factors = (J1, J2)
+# n1 = J1.dimension()
+# n2 = J2.dimension()
+# n = n1+n2
+# V = VectorSpace(field, n)
+# mult_table = [ [ V.zero() for j in range(i+1) ]
+# for i in range(n) ]
+# for i in range(n1):
+# for j in range(i+1):
+# 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(i+1):
+# p = (J2.monomial(i)*J2.monomial(j)).to_vector()
+# mult_table[n1+i][n1+j] = V([field.zero()]*n1 + p.list())
+
+# # TODO: build the IP table here from the two constituent IP
+# # matrices (it'll be block diagonal, I think).
+# ip_table = [ [ field.zero() for j in range(i+1) ]
+# for i in range(n) ]
+# super(DirectSumEJA, self).__init__(field,
+# mult_table,
+# ip_table,
+# check_axioms=False,
+# **kwargs)
+# self.rank.set_cache(J1.rank() + J2.rank())
+
+
+# def factors(self):
+# r"""
+# Return the pair of this algebra's factors.
+
+# SETUP::
+
+# sage: from mjo.eja.eja_algebra import (HadamardEJA,
+# ....: JordanSpinEJA,
+# ....: DirectSumEJA)
+
+# EXAMPLES::
+
+# sage: J1 = HadamardEJA(2, field=QQ)
+# sage: J2 = JordanSpinEJA(3, field=QQ)
+# sage: J = DirectSumEJA(J1,J2)
+# sage: J.factors()
+# (Euclidean Jordan algebra of dimension 2 over Rational Field,
+# Euclidean Jordan algebra of dimension 3 over Rational Field)
+
+# """
+# return self._factors
+
+# def projections(self):
+# r"""
+# Return a pair of projections onto this algebra's factors.
+
+# SETUP::
+
+# sage: from mjo.eja.eja_algebra import (JordanSpinEJA,
+# ....: ComplexHermitianEJA,
+# ....: DirectSumEJA)
+
+# EXAMPLES::
+
+# sage: J1 = JordanSpinEJA(2)
+# sage: J2 = ComplexHermitianEJA(2)
+# sage: J = DirectSumEJA(J1,J2)
+# sage: (pi_left, pi_right) = J.projections()
+# sage: J.one().to_vector()
+# (1, 0, 1, 0, 0, 1)
+# sage: pi_left(J.one()).to_vector()
+# (1, 0)
+# sage: pi_right(J.one()).to_vector()
+# (1, 0, 0, 1)
+
+# """
+# (J1,J2) = self.factors()
+# m = J1.dimension()
+# n = J2.dimension()
+# V_basis = self.vector_space().basis()
+# # Need to specify the dimensions explicitly so that we don't
+# # wind up with a zero-by-zero matrix when we want e.g. a
+# # zero-by-two matrix (important for composing things).
+# P1 = matrix(self.base_ring(), m, m+n, V_basis[:m])
+# P2 = matrix(self.base_ring(), n, m+n, V_basis[m:])
+# pi_left = FiniteDimensionalEJAOperator(self,J1,P1)
+# pi_right = FiniteDimensionalEJAOperator(self,J2,P2)
+# return (pi_left, pi_right)
+
+# def inclusions(self):
+# r"""
+# Return the pair of inclusion maps from our factors into us.
+
+# SETUP::
+
+# sage: from mjo.eja.eja_algebra import (random_eja,
+# ....: JordanSpinEJA,
+# ....: RealSymmetricEJA,
+# ....: DirectSumEJA)
+
+# EXAMPLES::
+
+# sage: J1 = JordanSpinEJA(3)
+# sage: J2 = RealSymmetricEJA(2)
+# sage: J = DirectSumEJA(J1,J2)
+# sage: (iota_left, iota_right) = J.inclusions()
+# sage: iota_left(J1.zero()) == J.zero()
+# True
+# sage: iota_right(J2.zero()) == J.zero()
+# True
+# sage: J1.one().to_vector()
+# (1, 0, 0)
+# sage: iota_left(J1.one()).to_vector()
+# (1, 0, 0, 0, 0, 0)
+# sage: J2.one().to_vector()
+# (1, 0, 1)
+# sage: iota_right(J2.one()).to_vector()
+# (0, 0, 0, 1, 0, 1)
+# sage: J.one().to_vector()
+# (1, 0, 0, 1, 0, 1)
+
+# TESTS:
+
+# Composing a projection with the corresponding inclusion should
+# produce the identity map, and mismatching them should produce
+# the zero map::
+
+# sage: set_random_seed()
+# sage: J1 = random_eja()
+# sage: J2 = random_eja()
+# sage: J = DirectSumEJA(J1,J2)
+# sage: (iota_left, iota_right) = J.inclusions()
+# sage: (pi_left, pi_right) = J.projections()
+# sage: pi_left*iota_left == J1.one().operator()
+# True
+# sage: pi_right*iota_right == J2.one().operator()
+# True
+# sage: (pi_left*iota_right).is_zero()
+# True
+# sage: (pi_right*iota_left).is_zero()
+# True
+
+# """
+# (J1,J2) = self.factors()
+# m = J1.dimension()
+# n = J2.dimension()
+# V_basis = self.vector_space().basis()
+# # Need to specify the dimensions explicitly so that we don't
+# # wind up with a zero-by-zero matrix when we want e.g. a
+# # two-by-zero matrix (important for composing things).
+# I1 = matrix.column(self.base_ring(), m, m+n, V_basis[:m])
+# I2 = matrix.column(self.base_ring(), n, m+n, V_basis[m:])
+# iota_left = FiniteDimensionalEJAOperator(J1,self,I1)
+# iota_right = FiniteDimensionalEJAOperator(J2,self,I2)
+# return (iota_left, iota_right)
+
+# def inner_product(self, x, y):
+# r"""
+# The standard Cartesian inner-product.
+
+# We project ``x`` and ``y`` onto our factors, and add up the
+# inner-products from the subalgebras.
+
+# SETUP::
+
+
+# sage: from mjo.eja.eja_algebra import (HadamardEJA,
+# ....: QuaternionHermitianEJA,
+# ....: DirectSumEJA)
+
+# EXAMPLE::
+
+# sage: J1 = HadamardEJA(3,field=QQ)
+# sage: J2 = QuaternionHermitianEJA(2,field=QQ,orthonormalize=False)
+# sage: J = DirectSumEJA(J1,J2)
+# sage: x1 = J1.one()
+# sage: x2 = x1
+# sage: y1 = J2.one()
+# sage: y2 = y1
+# sage: x1.inner_product(x2)
+# 3
+# sage: y1.inner_product(y2)
+# 2
+# sage: J.one().inner_product(J.one())
+# 5
+
+# """
+# (pi_left, pi_right) = self.projections()
+# x1 = pi_left(x)
+# x2 = pi_right(x)
+# y1 = pi_left(y)
+# y2 = pi_right(y)
+
+# return (x1.inner_product(y1) + x2.inner_product(y2))