from mjo.clan.clan import (
Clans,
ComplexHermitianClan,
- RealSymmetricClan,
- VinbergClan
+ RealSymmetricClan
)
+
+from mjo.clan.vinberg_clan import VinbergClan
"""
return f"Clan H^{self.rank()} over {self.base_ring()}"
-
-
-class VinbergClan(NormalDecomposition):
- r"""
- The clan corresponding to the Vinberg cone (as defined by Ishi).
-
- The Vinberg cone lives in a space whose points are pairs of real
- 2x2 symmetric matrices that agree in the first coordinate. The
- cone itself is the subset where both elements of the pair are
- positive-semidefinite.
-
- Ishi describes this space as x = (a,b) where
-
- a = [ x11 x21 ]
- [ x21 x22 ],
-
- b = [ x11 x31 ]
- [ x31 x33 ]
-
- As there is no obvious way to express this in Sage, we instead
- simply write out the x = (x11, x21, x22, x31, x33) as a vector of
- real numbers, and provide lifts to the matrix representation. The
- clan product is in terms of the up-hat and down-hat that is
- defined on `S^{2}.
-
- Following Gikdikin (p. 91), the basis for this clan is
-
- e[(0,0,1)] = [1,0], [1,0]
- [0,0] [0,0]
-
- e[(1,0,1)] = [0,1], [0,0]
- [1,0] [0,0]
-
- e[(1,1,1)] = [0,0], [0,0]
- [0,1] [0,0]
-
- e[(2,0,1)] = [0,0], [0,1]
- [0,0] [1,0]
-
- e[(2,1,1)] = nonexistent, component is trivial
-
- e[(2,2,1)] = [0,0], [0,0]
- [0,0] [0,1]
-
- SETUP::
-
- sage: from mjo.clan.clan import VinbergClan
-
- EXAMPLES:
-
- Check the unit element::
-
- sage: C = VinbergClan()
- sage: x = C.an_element()
- sage: x*C.one() == x
- True
- sage: C.one()*x == x
- True
-
- Verifying the axioms::
-
- sage: C = VinbergClan()
- sage: e = C.basis()
- sage: all( e[i,i,1]*e[i,i,1] == e[i,i,1] for i in range(3) )
- True
- sage: all( e[i,i,1]*e[j,i,1] == e[j,i,1]/2
- ....: for i in range(3)
- ....: for j in range(i+1,3)
- ....: if e.has_key((j,i,1)))
- True
- sage: all( e[i,i,1]*e[i,k,1] == e[i,k,1]/2
- ....: for i in range(2)
- ....: for k in range(i))
- True
- sage: all( (e[i,i,1]*e[j,k,1]).is_zero()
- ....: for i in range(2)
- ....: for j in range(2)
- ....: for k in range(j+1)
- ....: if not i in [j,k] )
- True
- sage: all( (e[i,k,1]*e[i,i,1]).is_zero()
- ....: for i in range(2)
- ....: for k in range(i) )
- True
- sage: all( (e[j,k,1]*e[i,i,1]).is_zero()
- ....: for i in range(2)
- ....: for j in range(2)
- ....: for k in range(j)
- ....: if not i in [j,k] )
- True
-
- The multiplication in this clan (verified by hand) is as
- follows)::
-
- sage: C = VinbergClan()
- sage: x = C.random_element()
- sage: y = C.random_element()
- sage: z = x*y
- sage: x = x.to_vector()
- sage: y = y.to_vector()
- sage: z = z.to_vector()
- sage: z[0] == x[0]*y[0]
- True
- sage: z[1] == y[1]*(x[0] + x[2])/QQ(2) + y[0]*x[1]
- True
- sage: z[2] == 2*x[1]*y[1] + x[2]*y[2]
- True
- sage: z[3] == y[3]*(x[0] + x[4])/QQ(2) + y[0]*x[3]
- True
- sage: z[4] == 2*x[3]*y[3] + x[4]*y[4]
- True
-
- The Ishi inner product in this clan (verified by hand) is as
- follows::
-
- sage: C = VinbergClan()
- sage: x = C.random_element()
- sage: y = C.random_element()
- sage: actual = x.inner_product(y)
- sage: x = x.to_vector()
- sage: y = y.to_vector()
- sage: expected = ( x[0]*y[0] + x[2]*y[2] + x[4]*y[4] +
- ....: 2*x[1]*y[1] + 2*x[3]*y[3] ) / QQ(2)
- sage: actual == expected
- True
-
- """
- def _unlift(self, pair):
- A,B = pair
- R5 = self._vector_space
- return ( A[(0,0,1)]*R5((0,0,1)) +
- A[(1,0,1)]*R5((1,0,1)) +
- A[(1,1,1)]*R5((1,1,1)) +
- B[(1,0,1)]*R5((2,0,1)) +
- B[(1,1,1)]*R5((2,2,1)) )
-
- def _lift(self, v):
- M1 = ( self._S2((0,0,1))*v[(0,0,1)] +
- self._S2((1,0,1))*v[(1,0,1)] +
- self._S2((1,1,1))*v[(1,1,1)] )
- M2 = ( self._S2((0,0,1))*v[(0,0,1)] +
- self._S2((1,0,1))*v[(2,0,1)] +
- self._S2((1,1,1))*v[(2,2,1)] )
- return (M1,M2)
-
- def __init__(self, scalar_field=QQ, **kwargs):
- from sage.matrix.matrix_space import MatrixSpace
- from sage.modules.free_module import VectorSpace
-
- M2 = MatrixSpace(scalar_field, 2)
- b = M2.basis()
-
- from sage.sets.family import Family
- S2_basis = Family({ (i,j,1) :
- a*(b[(i,j)] + b[(j,i)])
- for i in range(2)
- for j in range(i+1)
- if (a := 1 - (i == j)/scalar_field(2))
- })
-
- # M2.submodule() destroys our basis keys, so use
- # SubmoduleWithBasis directly.
- self._S2 = SubmoduleWithBasis(S2_basis,
- support_order=b.keys(),
- ambient=M2)
-
- # We need an Ishi basis (i,j,k) for R^5 if we want to use
- # NormalDecomposition. We imagine the (2,1,1) component
- # being trivial to keep the "triangle" intact. We are
- # example (f) on page 91 of Gindikin where we can cheat
- # and see the normal decomposition.
- R5 = VectorSpace(scalar_field, [
- (0,0,1),
- (1,0,1),
- (1,1,1),
- (2,0,1),
- (2,2,1)
- ])
-
- # The Clan __init__ does this, but if we do it now then we can
- # use the self.lift() and self.unlift() methods to define the
- # clan product rather than duplicating them locally.
- self._vector_space = R5
-
- def cp(x,y):
- # up_hat and down_hat need MatrixSpace elements, not
- # submodules with custom bases, so we need to lift
- # everything twice.
- X1,X2 = self._lift(x)
- Y1,Y2 = self._lift(y)
- X1 = X1.lift()
- X2 = X2.lift()
- Y1 = Y1.lift()
- Y2 = Y2.lift()
- Z1 = MatrixClan._down_hat(X1)*Y1 + Y1*MatrixClan._up_hat(X1)
- Z2 = MatrixClan._down_hat(X2)*Y2 + Y2*MatrixClan._up_hat(X2)
- return self._unlift((self._S2(Z1), self._S2(Z2)))
-
- def ip(x,y):
- two = x.base_ring()(2)
- p = cp(x,y) / two
- return sum( p[idx]
- for idx in p.monomial_coefficients()
- if idx[0] == idx[1] )
-
- super().__init__(R5, cp, ip, **kwargs)
-
- def __repr__(self) -> str:
- r"""
- The string representation of this clan.
-
- SETUP::
-
- sage: from mjo.clan.clan import VinbergClan
-
- EXAMPLES::
-
- sage: VinbergClan()
- Vinberg clan over Rational Field
-
- """
- return f"Vinberg clan over {self.base_ring()}"
-
- def random_triangular_cone_automorphism(self):
- r"""
- Generate a random triangular automorphism of the Vinberg cone.
-
- Elliot Herrington in his thesis "Highly symmetric homogeneous
- Kobayashi-hyperbolic manifolds" gives a formula for the
- connected component of the identity in the group of triangular
- automorphisms. This won't generate the whole group, but it's
- a good start.
- """
- from sage.matrix.matrix_space import MatrixSpace
- R = self.base_ring()
- MS = MatrixSpace(R, 5)
-
- # Herrington's notation for triangular automorphisms
- a = R._random_nonzero_element().abs()
- b = R.random_element()
- c = R.random_element()
- e = R._random_nonzero_element()
- i = R._random_nonzero_element()
-
- T = MS([ [a**2, b**2, c**2, 2*a*b, 2*a*c],
- [ 0, e**2, 0, 0, 0],
- [ 0, 0, i**2, 0, 0],
- [ 0, b*e, 0, a*e, 0],
- [ 0, 0, c*i, 0, a*i] ])
-
- from mjo.clan.clan_operator import ClanOperator
- return ClanOperator(self, self, T)
-
-
- def random_isotropy_cone_automorphism(self):
- r"""
- Generate a random automorphism of the Vinberg cone that
- fixes the unit element.
-
- This is effectively a guess, based on the work done by Ishi
- and Koufany for the **dual** Vinberg cone.
-
- SETUP::
-
- sage: from mjo.clan.clan import VinbergClan
-
- TESTS:
-
- Evidence for the conjecture that these preserve the two trace
- inner products::
-
- sage: C = VinbergClan()
- sage: A = C.random_isotropy_cone_automorphism()
- sage: A(C.one()) == C.one()
- True
- sage: x = C.random_element()
- sage: y = C.random_element()
- sage: x.inner_product(y) == A(x).inner_product(A(y))
- True
- sage: expected = x.inner_product_vinberg(y)
- sage: actual = A(x).inner_product_vinberg(A(y))
- sage: actual == expected
- True
-
- """
- from sage.matrix.matrix_space import MatrixSpace
- MS = MatrixSpace(self.base_ring(), 5)
- # Now the generators of the isotropy subgroup (inspired by
- # Ishi/Kounfany, but basically just guessing and checking).
- #
- # x21 -> -x21
- g1 = MS([ [1, 0,0,0,0],
- [0,-1,0,0,0],
- [0, 0,1,0,0],
- [0, 0,0,1,0],
- [0, 0,0,0,1] ])
- # x31 -> -x31
- g2 = MS([ [1,0,0, 0,0],
- [0,1,0, 0,0],
- [0,0,1, 0,0],
- [0,0,0,-1,0],
- [0,0,0, 0,1] ])
- # x32 <-> x33, x21 <-> x31
- g3 = MS([ [1,0,0,0,0],
- [0,0,0,1,0],
- [0,0,0,0,1],
- [0,1,0,0,0],
- [0,0,1,0,0] ])
-
- # Group is order eight?
- gs = [MS.one(), g1, g2, g3, g1*g2, g1*g3, g3*g1, g3*g2]
-
- from random import choice
- from mjo.clan.clan_operator import ClanOperator
- return ClanOperator(self, self, choice(gs))
-
- def random_cone_automorphism(self):
- r"""
- Generate a random automorphism of the Vinberg cone.
- """
- T = self.random_triangular_cone_automorphism()
- K = self.random_isotropy_cone_automorphism()
- return T*K
-
- def from_matrices(self, A, B):
- r"""
- Construct an element of this clan from a pair of
- symmetric matrices.
-
- SETUP::
-
- sage: from mjo.clan.clan import VinbergClan
-
- EXAMPLES::
-
- sage: C = VinbergClan()
- sage: A = matrix(QQ, [ [2, 1],
- ....: [1, 4] ])
- sage: B = matrix(QQ, [ [2, 2],
- ....: [2,-1] ])
- sage: C.from_matrices(A,B).to_vector()
- (2, 1, 4, 2, -1)
-
- """
- if not A.base_ring() == self.base_ring():
- raise ValueError(f"base ring of matrix A ({A.base_ring()})"
- " does not match clan ({self.base_ring()})")
- if not B.base_ring() == self.base_ring():
- raise ValueError(f"base ring of matrix B ({A.base_ring()})"
- " does not match clan ({self.base_ring()})")
- if not A == A.transpose():
- raise ValueError("matrix A is not symmetric")
- if not B == B.transpose():
- raise ValueError("matrix B is not symmetric")
- if not A.nrows() == 2:
- raise ValueError(f"matrix A must be 2x2")
- if not B.nrows() == 2:
- raise ValueError(f"matrix B must be 2x2")
- if not A[0,0] == B[0,0]:
- raise ValueError(f"A and B must agree in the (0,0) position")
-
- return ( A[0,0]*self((0,0,1)) +
- A[1,0]*self((1,0,1)) +
- A[1,1]*self((1,1,1)) +
- B[1,0]*self((2,0,1)) +
- B[1,1]*self((2,2,1)) )
-
- def from_list(self, l):
- r"""
- Construct an element of this clan from a list.
-
- This is a bit different from the other ``from_list`` methods
- on matrix clans, because the underlying vector space for the
- Vinberg clan is basically `R^5`. So this method just takes a
- list of five numbers and returns a clan element. It is a
- trivial wrapper around :meth:`from_vector`.
-
- SETUP::
-
- sage: from mjo.clan.clan import VinbergClan
-
- EXAMPLES::
-
- sage: C = VinbergClan()
- sage: x = C.from_list([2,1,4,2,-1])
- sage: x.to_vector()
- (2, 1, 4, 2, -1)
-
- """
- return self.sum_of_terms(zip(self.basis().keys(), l))
-
- def from_lists(self, l1, l2):
- r
- """
- Construct an element of this clan from a pair of lists.
-
- This is a shortcut for :meth:`from_matrix`, as the clan knows
- what the ambient matrix space was.
-
- SETUP::
-
- sage: from mjo.clan.clan import VinbergClan
-
- EXAMPLES::
-
- sage: C = VinbergClan()
- sage: x = C.from_lists([[2,1],[1,4]], [[2,2],[2,-1]])
- sage: x.to_vector()
- (2, 1, 4, 2, -1)
-
- This relies on the ambient vector space to convert a list to a
- matrix, so we can use one long list (as opposed to a list of
- lists) in each component::
-
- sage: C = VinbergClan()
- sage: x = C.from_lists([2,1,1,4],[2,2,2,-1])
- sage: x.to_vector()
- (2, 1, 4, 2, -1)
-
- """
- A = self._S2.ambient()(l1)
- B = self._S2.ambient()(l2)
- return self.from_matrices(A,B)
SETUP::
- sage: from mjo.clan.clan import VinbergClan
+ sage: from mjo.clan.vinberg_clan import VinbergClan
EXAMPLES::
SETUP::
- sage: from mjo.clan.clan import VinbergClan
+ sage: from mjo.clan.vinberg_clan import VinbergClan
EXAMPLES::
SETUP::
- sage: from mjo.clan.clan import RealSymmetricClan, VinbergClan
+ sage: from mjo.clan.clan import RealSymmetricClan
+ sage: from mjo.clan.vinberg_clan import VinbergClan
EXAMPLES:
SETUP::
sage: from mjo.clan.clan_operator import ClanOperator
- sage: from mjo.clan.clan import VinbergClan
+ sage: from mjo.clan.vinberg_clan import VinbergClan
EXAMPLES::
sage: from mjo.clan.clan_operator import ClanOperator
sage: from mjo.clan.clan import ( ComplexHermitianClan,
- ....: RealSymmetricClan,
- ....: VinbergClan )
+ ....: RealSymmetricClan )
+ sage: from mjo.clan.vinberg_clan import VinbergClan
EXAMPLES::
--- /dev/null
+from sage.rings.rational_field import QQ
+from mjo.clan.clan import MatrixClan, NormalDecomposition
+
+class VinbergClan(NormalDecomposition):
+ r"""
+ The clan corresponding to the Vinberg cone (as defined by Ishi).
+
+ The Vinberg cone lives in a space whose points are pairs of real
+ 2x2 symmetric matrices that agree in the first coordinate. The
+ cone itself is the subset where both elements of the pair are
+ positive-semidefinite.
+
+ Ishi describes this space as x = (a,b) where
+
+ a = [ x11 x21 ]
+ [ x21 x22 ],
+
+ b = [ x11 x31 ]
+ [ x31 x33 ]
+
+ As there is no obvious way to express this in Sage, we instead
+ simply write out the x = (x11, x21, x22, x31, x33) as a vector of
+ real numbers, and provide lifts to the matrix representation. The
+ clan product is in terms of the up-hat and down-hat that is
+ defined on `S^{2}.
+
+ Following Gikdikin (p. 91), the basis for this clan is
+
+ e[(0,0,1)] = [1,0], [1,0]
+ [0,0] [0,0]
+
+ e[(1,0,1)] = [0,1], [0,0]
+ [1,0] [0,0]
+
+ e[(1,1,1)] = [0,0], [0,0]
+ [0,1] [0,0]
+
+ e[(2,0,1)] = [0,0], [0,1]
+ [0,0] [1,0]
+
+ e[(2,1,1)] = nonexistent, component is trivial
+
+ e[(2,2,1)] = [0,0], [0,0]
+ [0,0] [0,1]
+
+ SETUP::
+
+ sage: from mjo.clan.vinberg_clan import VinbergClan
+
+ EXAMPLES:
+
+ Check the unit element::
+
+ sage: C = VinbergClan()
+ sage: x = C.an_element()
+ sage: x*C.one() == x
+ True
+ sage: C.one()*x == x
+ True
+
+ Verifying the axioms::
+
+ sage: C = VinbergClan()
+ sage: e = C.basis()
+ sage: all( e[i,i,1]*e[i,i,1] == e[i,i,1] for i in range(3) )
+ True
+ sage: all( e[i,i,1]*e[j,i,1] == e[j,i,1]/2
+ ....: for i in range(3)
+ ....: for j in range(i+1,3)
+ ....: if e.has_key((j,i,1)))
+ True
+ sage: all( e[i,i,1]*e[i,k,1] == e[i,k,1]/2
+ ....: for i in range(2)
+ ....: for k in range(i))
+ True
+ sage: all( (e[i,i,1]*e[j,k,1]).is_zero()
+ ....: for i in range(2)
+ ....: for j in range(2)
+ ....: for k in range(j+1)
+ ....: if not i in [j,k] )
+ True
+ sage: all( (e[i,k,1]*e[i,i,1]).is_zero()
+ ....: for i in range(2)
+ ....: for k in range(i) )
+ True
+ sage: all( (e[j,k,1]*e[i,i,1]).is_zero()
+ ....: for i in range(2)
+ ....: for j in range(2)
+ ....: for k in range(j)
+ ....: if not i in [j,k] )
+ True
+
+ The multiplication in this clan (verified by hand) is as
+ follows)::
+
+ sage: C = VinbergClan()
+ sage: x = C.random_element()
+ sage: y = C.random_element()
+ sage: z = x*y
+ sage: x = x.to_vector()
+ sage: y = y.to_vector()
+ sage: z = z.to_vector()
+ sage: z[0] == x[0]*y[0]
+ True
+ sage: z[1] == y[1]*(x[0] + x[2])/QQ(2) + y[0]*x[1]
+ True
+ sage: z[2] == 2*x[1]*y[1] + x[2]*y[2]
+ True
+ sage: z[3] == y[3]*(x[0] + x[4])/QQ(2) + y[0]*x[3]
+ True
+ sage: z[4] == 2*x[3]*y[3] + x[4]*y[4]
+ True
+
+ The Ishi inner product in this clan (verified by hand) is as
+ follows::
+
+ sage: C = VinbergClan()
+ sage: x = C.random_element()
+ sage: y = C.random_element()
+ sage: actual = x.inner_product(y)
+ sage: x = x.to_vector()
+ sage: y = y.to_vector()
+ sage: expected = ( x[0]*y[0] + x[2]*y[2] + x[4]*y[4] +
+ ....: 2*x[1]*y[1] + 2*x[3]*y[3] ) / QQ(2)
+ sage: actual == expected
+ True
+
+ """
+ def _unlift(self, pair):
+ A,B = pair
+ R5 = self._vector_space
+ return ( A[(0,0,1)]*R5((0,0,1)) +
+ A[(1,0,1)]*R5((1,0,1)) +
+ A[(1,1,1)]*R5((1,1,1)) +
+ B[(1,0,1)]*R5((2,0,1)) +
+ B[(1,1,1)]*R5((2,2,1)) )
+
+ def _lift(self, v):
+ M1 = ( self._S2((0,0,1))*v[(0,0,1)] +
+ self._S2((1,0,1))*v[(1,0,1)] +
+ self._S2((1,1,1))*v[(1,1,1)] )
+ M2 = ( self._S2((0,0,1))*v[(0,0,1)] +
+ self._S2((1,0,1))*v[(2,0,1)] +
+ self._S2((1,1,1))*v[(2,2,1)] )
+ return (M1,M2)
+
+ def __init__(self, scalar_field=QQ, **kwargs):
+ from sage.matrix.matrix_space import MatrixSpace
+ from sage.modules.free_module import VectorSpace
+
+ M2 = MatrixSpace(scalar_field, 2)
+ b = M2.basis()
+
+ from sage.sets.family import Family
+ S2_basis = Family({ (i,j,1) :
+ a*(b[(i,j)] + b[(j,i)])
+ for i in range(2)
+ for j in range(i+1)
+ if (a := 1 - (i == j)/scalar_field(2))
+ })
+
+ # M2.submodule() destroys our basis keys, so use
+ # SubmoduleWithBasis directly.
+ from sage.modules.with_basis.subquotient import (
+ SubmoduleWithBasis
+ )
+ self._S2 = SubmoduleWithBasis(S2_basis,
+ support_order=b.keys(),
+ ambient=M2)
+
+ # We need an Ishi basis (i,j,k) for R^5 if we want to use
+ # NormalDecomposition. We imagine the (2,1,1) component
+ # being trivial to keep the "triangle" intact. We are
+ # example (f) on page 91 of Gindikin where we can cheat
+ # and see the normal decomposition.
+ R5 = VectorSpace(scalar_field, [
+ (0,0,1),
+ (1,0,1),
+ (1,1,1),
+ (2,0,1),
+ (2,2,1)
+ ])
+
+ # The Clan __init__ does this, but if we do it now then we can
+ # use the self.lift() and self.unlift() methods to define the
+ # clan product rather than duplicating them locally.
+ self._vector_space = R5
+
+ def cp(x,y):
+ # up_hat and down_hat need MatrixSpace elements, not
+ # submodules with custom bases, so we need to lift
+ # everything twice.
+ X1,X2 = self._lift(x)
+ Y1,Y2 = self._lift(y)
+ X1 = X1.lift()
+ X2 = X2.lift()
+ Y1 = Y1.lift()
+ Y2 = Y2.lift()
+ Z1 = MatrixClan._down_hat(X1)*Y1 + Y1*MatrixClan._up_hat(X1)
+ Z2 = MatrixClan._down_hat(X2)*Y2 + Y2*MatrixClan._up_hat(X2)
+ return self._unlift((self._S2(Z1), self._S2(Z2)))
+
+ def ip(x,y):
+ two = x.base_ring()(2)
+ p = cp(x,y) / two
+ return sum( p[idx]
+ for idx in p.monomial_coefficients()
+ if idx[0] == idx[1] )
+
+ super().__init__(R5, cp, ip, **kwargs)
+
+ def __repr__(self) -> str:
+ r"""
+ The string representation of this clan.
+
+ SETUP::
+
+ sage: from mjo.clan.vinberg_clan import VinbergClan
+
+ EXAMPLES::
+
+ sage: VinbergClan()
+ Vinberg clan over Rational Field
+
+ """
+ return f"Vinberg clan over {self.base_ring()}"
+
+ def random_triangular_cone_automorphism(self):
+ r"""
+ Generate a random triangular automorphism of the Vinberg cone.
+
+ Elliot Herrington in his thesis "Highly symmetric homogeneous
+ Kobayashi-hyperbolic manifolds" gives a formula for the
+ connected component of the identity in the group of triangular
+ automorphisms. This won't generate the whole group, but it's
+ a good start.
+ """
+ from sage.matrix.matrix_space import MatrixSpace
+ R = self.base_ring()
+ MS = MatrixSpace(R, 5)
+
+ # Herrington's notation for triangular automorphisms
+ a = R._random_nonzero_element().abs()
+ b = R.random_element()
+ c = R.random_element()
+ e = R._random_nonzero_element()
+ i = R._random_nonzero_element()
+
+ T = MS([ [a**2, b**2, c**2, 2*a*b, 2*a*c],
+ [ 0, e**2, 0, 0, 0],
+ [ 0, 0, i**2, 0, 0],
+ [ 0, b*e, 0, a*e, 0],
+ [ 0, 0, c*i, 0, a*i] ])
+
+ from mjo.clan.clan_operator import ClanOperator
+ return ClanOperator(self, self, T)
+
+
+ def random_isotropy_cone_automorphism(self):
+ r"""
+ Generate a random automorphism of the Vinberg cone that
+ fixes the unit element.
+
+ This is effectively a guess, based on the work done by Ishi
+ and Koufany for the **dual** Vinberg cone.
+
+ SETUP::
+
+ sage: from mjo.clan.vinberg_clan import VinbergClan
+
+ TESTS:
+
+ Evidence for the conjecture that these preserve the two trace
+ inner products::
+
+ sage: C = VinbergClan()
+ sage: A = C.random_isotropy_cone_automorphism()
+ sage: A(C.one()) == C.one()
+ True
+ sage: x = C.random_element()
+ sage: y = C.random_element()
+ sage: x.inner_product(y) == A(x).inner_product(A(y))
+ True
+ sage: expected = x.inner_product_vinberg(y)
+ sage: actual = A(x).inner_product_vinberg(A(y))
+ sage: actual == expected
+ True
+
+ """
+ from sage.matrix.matrix_space import MatrixSpace
+ MS = MatrixSpace(self.base_ring(), 5)
+ # Now the generators of the isotropy subgroup (inspired by
+ # Ishi/Kounfany, but basically just guessing and checking).
+ #
+ # x21 -> -x21
+ g1 = MS([ [1, 0,0,0,0],
+ [0,-1,0,0,0],
+ [0, 0,1,0,0],
+ [0, 0,0,1,0],
+ [0, 0,0,0,1] ])
+ # x31 -> -x31
+ g2 = MS([ [1,0,0, 0,0],
+ [0,1,0, 0,0],
+ [0,0,1, 0,0],
+ [0,0,0,-1,0],
+ [0,0,0, 0,1] ])
+ # x32 <-> x33, x21 <-> x31
+ g3 = MS([ [1,0,0,0,0],
+ [0,0,0,1,0],
+ [0,0,0,0,1],
+ [0,1,0,0,0],
+ [0,0,1,0,0] ])
+
+ # Group is order eight?
+ gs = [MS.one(), g1, g2, g3, g1*g2, g1*g3, g3*g1, g3*g2]
+
+ from random import choice
+ from mjo.clan.clan_operator import ClanOperator
+ return ClanOperator(self, self, choice(gs))
+
+ def random_cone_automorphism(self):
+ r"""
+ Generate a random automorphism of the Vinberg cone.
+ """
+ T = self.random_triangular_cone_automorphism()
+ K = self.random_isotropy_cone_automorphism()
+ return T*K
+
+ def from_matrices(self, A, B):
+ r"""
+ Construct an element of this clan from a pair of
+ symmetric matrices.
+
+ SETUP::
+
+ sage: from mjo.clan.vinberg_clan import VinbergClan
+
+ EXAMPLES::
+
+ sage: C = VinbergClan()
+ sage: A = matrix(QQ, [ [2, 1],
+ ....: [1, 4] ])
+ sage: B = matrix(QQ, [ [2, 2],
+ ....: [2,-1] ])
+ sage: C.from_matrices(A,B).to_vector()
+ (2, 1, 4, 2, -1)
+
+ """
+ if not A.base_ring() == self.base_ring():
+ raise ValueError(f"base ring of matrix A ({A.base_ring()})"
+ " does not match clan ({self.base_ring()})")
+ if not B.base_ring() == self.base_ring():
+ raise ValueError(f"base ring of matrix B ({A.base_ring()})"
+ " does not match clan ({self.base_ring()})")
+ if not A == A.transpose():
+ raise ValueError("matrix A is not symmetric")
+ if not B == B.transpose():
+ raise ValueError("matrix B is not symmetric")
+ if not A.nrows() == 2:
+ raise ValueError(f"matrix A must be 2x2")
+ if not B.nrows() == 2:
+ raise ValueError(f"matrix B must be 2x2")
+ if not A[0,0] == B[0,0]:
+ raise ValueError(f"A and B must agree in the (0,0) position")
+
+ return ( A[0,0]*self((0,0,1)) +
+ A[1,0]*self((1,0,1)) +
+ A[1,1]*self((1,1,1)) +
+ B[1,0]*self((2,0,1)) +
+ B[1,1]*self((2,2,1)) )
+
+ def from_list(self, l):
+ r"""
+ Construct an element of this clan from a list.
+
+ This is a bit different from the other ``from_list`` methods
+ on matrix clans, because the underlying vector space for the
+ Vinberg clan is basically `R^5`. So this method just takes a
+ list of five numbers and returns a clan element. It is a
+ trivial wrapper around :meth:`from_vector`.
+
+ SETUP::
+
+ sage: from mjo.clan.vinberg_clan import VinbergClan
+
+ EXAMPLES::
+
+ sage: C = VinbergClan()
+ sage: x = C.from_list([2,1,4,2,-1])
+ sage: x.to_vector()
+ (2, 1, 4, 2, -1)
+
+ """
+ return self.sum_of_terms(zip(self.basis().keys(), l))
+
+ def from_lists(self, l1, l2):
+ r
+ """
+ Construct an element of this clan from a pair of lists.
+
+ This is a shortcut for :meth:`from_matrix`, as the clan knows
+ what the ambient matrix space was.
+
+ SETUP::
+
+ sage: from mjo.clan.vinberg_clan import VinbergClan
+
+ EXAMPLES::
+
+ sage: C = VinbergClan()
+ sage: x = C.from_lists([[2,1],[1,4]], [[2,2],[2,-1]])
+ sage: x.to_vector()
+ (2, 1, 4, 2, -1)
+
+ This relies on the ambient vector space to convert a list to a
+ matrix, so we can use one long list (as opposed to a list of
+ lists) in each component::
+
+ sage: C = VinbergClan()
+ sage: x = C.from_lists([2,1,1,4],[2,2,2,-1])
+ sage: x.to_vector()
+ (2, 1, 4, 2, -1)
+
+ """
+ A = self._S2.ambient()(l1)
+ B = self._S2.ambient()(l2)
+ return self.from_matrices(A,B)