]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
mjo/clan: start implementing clan operators
authorMichael Orlitzky <michael@orlitzky.com>
Mon, 16 Feb 2026 14:17:50 +0000 (09:17 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Mon, 16 Feb 2026 14:17:50 +0000 (09:17 -0500)
This is a moral prerequisite for the Vinberg inner product.

mjo/clan/clan_element.py
mjo/clan/clan_operator.py [new file with mode: 0644]

index eedf4815ea37522d878e761a7fbadb037f8bd166..6b08e53134f351b6bb1654209f681d6f8279d1e0 100644 (file)
@@ -45,6 +45,67 @@ class ClanElement(IndexedFreeModuleElement):
             for (bj, yj)  in other.items()
         )
 
+    def leftreg(self):
+        r"""
+        Return the left-regular representation of this element.
+
+        SETUP::
+
+            sage: from mjo.clan.unital_clan import VinbergClan
+
+        EXAMPLES::
+
+            sage: C = VinbergClan()
+            sage: C.one().leftreg()
+            [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]
+
+        """
+        P = self.parent()
+        left_mult_by_self = lambda y: self*y
+        L = P.module_morphism(function=left_mult_by_self, codomain=P)
+        return L.matrix()
+
+    def operator(self):
+        """
+        Return the left-multiplication-by-me operator on the
+        ambient clan.
+
+        SETUP::
+
+            sage: from mjo.clan.unital_clan import VinbergClan
+
+        EXAMPLES::
+
+            sage: C = VinbergClan()
+            sage: C.one().operator()
+            Clan operator represented by the matrix:
+            [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]
+            Domain: Vinberg clan over Rational Field
+            Codomain: Vinberg clan over Rational Field
+
+        TESTS::
+
+            sage: C = VinbergClan()
+            sage: x = C.random_element()
+            sage: y = C.random_element()
+            sage: x.operator()(y) == x*y
+            True
+            sage: y.operator()(x) == y*x
+            True
+
+        """
+        from mjo.clan.clan_operator import ClanOperator
+        P = self.parent()
+        return ClanOperator(P, P, self.leftreg())
+
 
 class NormalDecompositionElement(ClanElement):
     """
diff --git a/mjo/clan/clan_operator.py b/mjo/clan/clan_operator.py
new file mode 100644 (file)
index 0000000..6a058e5
--- /dev/null
@@ -0,0 +1,121 @@
+from sage.categories.all import FreeModules
+from sage.categories.map import Map
+
+class ClanOperator(Map):
+    r"""
+    An operator between two clans.
+
+    Defined for *unital* clans at the moment, because that's all we
+    have.
+
+    SETUP::
+
+        sage: from mjo.clan.unital_clan import SnClan
+        sage: from mjo.clan.clan_operator import ClanOperator
+
+    EXAMPLES:
+
+    The domain and codomain must be unital clans; if either is not,
+    then an error is raised::
+
+        sage: J = SnClan(2)
+        sage: V = VectorSpace(J.base_ring(), 3)
+        sage: M = matrix.identity(J.base_ring(), 3)
+        sage: ClanOperator(V,J,M)
+        Traceback (most recent call last):
+        ...
+        TypeError: domain must be a unital clan
+        sage: ClanOperator(J,V,M)
+        Traceback (most recent call last):
+        ...
+        TypeError: codomain must be a unital clan
+
+    """
+
+    def __init__(self, domain, codomain, mat):
+        from mjo.clan.unital_clan import UnitalClan
+
+        if not isinstance(domain, UnitalClan):
+            raise TypeError('domain must be a unital clan')
+        if not isinstance(codomain, UnitalClan):
+            raise TypeError('codomain must be a unital clan')
+
+        F = domain.base_ring()
+        if not (F == codomain.base_ring()):
+            raise ValueError("domain and codomain must have the same base ring")
+        if not (F == mat.base_ring()):
+            raise ValueError("domain and matrix must have the same base ring")
+
+        # We need to supply something here to avoid getting the
+        # default Homset of the parent FiniteDimensionalAlgebra class,
+        # which messes up e.g. equality testing. We use FreeModules(F)
+        # instead of VectorSpaces(F) because our characteristic polynomial
+        # algorithm will need to F to be a polynomial ring at some point.
+        # When F is a field, FreeModules(F) returns VectorSpaces(F) anyway.
+        parent = domain.Hom(codomain, FreeModules(F))
+
+        # The Map initializer will set our parent to a homset, which
+        # is explicitly NOT what we want, because these ain't algebra
+        # homomorphisms.
+        super().__init__(parent)
+
+        # Keep a matrix around to do all of the real work. It would
+        # be nice if we could use a VectorSpaceMorphism instead, but
+        # those use row vectors that we don't want to accidentally
+        # expose to our users.
+        self._matrix = mat
+
+
+    def _call_(self, x):
+        """
+        Allow this operator to be called only on elements of a clan.
+
+        SETUP::
+
+            sage: from mjo.clan.clan_operator import ClanOperator
+            sage: from mjo.clan.unital_clan import VinbergClan
+
+        EXAMPLES::
+
+            sage: C = VinbergClan()
+            sage: x = C.linear_combination(zip(C.gens(),range(len(C.gens()))))
+            sage: id = identity_matrix(C.base_ring(), C.dimension())
+            sage: f = ClanOperator(C,C,id)
+            sage: f(x) == x
+            True
+
+        """
+        return self.codomain().from_vector(self._matrix*x.to_vector())
+
+    def _repr_(self):
+        r"""
+
+        A text representation of this linear operator on a Euclidean
+        Jordan Algebra.
+
+        SETUP::
+
+            sage: from mjo.clan.clan_operator import ClanOperator
+            sage: from mjo.clan.unital_clan import HnClan
+
+        EXAMPLES::
+
+            sage: C = HnClan(2)
+            sage: id = identity_matrix(C.base_ring(), C.dimension())
+            sage: ClanOperator(C,C,id)
+            Clan operator represented by the matrix:
+            [1 0 0 0]
+            [0 1 0 0]
+            [0 0 1 0]
+            [0 0 0 1]
+            Domain: Clan H^2 over Rational Field
+            Codomain: Clan H^2 over Rational Field
+
+        """
+        msg = ("Clan operator represented by the matrix:\n",
+               "{!r}\n",
+               "Domain: {}\n",
+               "Codomain: {}")
+        return ''.join(msg).format(self._matrix,
+                                   self.domain(),
+                                   self.codomain())