]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
mjo/clan/clan_operator.py: implement composition and related methods
authorMichael Orlitzky <michael@orlitzky.com>
Fri, 20 Feb 2026 19:08:26 +0000 (14:08 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Fri, 20 Feb 2026 19:08:26 +0000 (14:08 -0500)
mjo/clan/clan_operator.py

index 33ff32384b830b08978ac6b68cee1d69b30bbeda..5f88d17107e629bce348b01a301cca8fc82974dc 100644 (file)
@@ -84,6 +84,158 @@ class ClanOperator(Map):
         """
         return self.codomain().from_vector(self.matrix()*x.to_vector())
 
+    def _composition_(self, other, homset):
+        """
+        Compose two clan operators to get another one (and NOT a formal
+        composite object) back.
+
+        SETUP::
+
+            sage: from mjo.clan.clan_operator import ClanOperator
+            sage: from mjo.clan.clan import HnClan, SnClan, VinbergClan
+
+        EXAMPLES::
+
+            sage: C1 = VinbergClan()
+            sage: C2 = HnClan(2)
+            sage: C3 = SnClan(2)
+            sage: mat1 = matrix(QQ, [[1,2,3,4,5],
+            ....:                    [6,7,8,9,0],
+            ....:                    [1,2,3,4,5],
+            ....:                    [6,7,8,9,0]])
+            sage: mat2 = matrix(QQ, [[1,2,3,4],
+            ....:                    [5,6,7,8],
+            ....:                    [9,0,1,2]])
+            sage: g = ClanOperator(C1, C2, mat1)
+            sage: f = ClanOperator(C2, C3, mat2)
+            sage: f*g
+            Clan operator represented by the matrix:
+            [ 40  50  60  70  20]
+            [ 96 122 148 174  60]
+            [ 22  34  46  58  50]
+            Domain: Vinberg clan over Rational Field
+            Codomain: Clan S^2 over Rational Field
+
+        """
+        return ClanOperator(
+          other.domain(),
+          self.codomain(),
+          self.matrix()*other.matrix())
+
+    def __mul__(self, other):
+        """
+        Compose two clan operators, or scale myself by an element of the
+        ambient vector space.
+
+        We need to override the real ``__mul__`` function to prevent the
+        coercion framework from throwing an error when it fails to convert
+        a base ring element into a morphism.
+
+        SETUP::
+
+            sage: from mjo.clan.clan import HnClan
+            sage: from mjo.clan.clan_operator import ClanOperator
+
+        EXAMPLES:
+
+        We can scale an operator on a rational algebra by a rational number::
+
+            sage: C = HnClan(2)
+            sage: b0,b1,b2,b3 = C.gens()
+            sage: x = 4*b0 + 8*b1 + 32*b2 + 64*b3
+            sage: x.operator()
+            Clan operator represented by the matrix:
+            [ 4  0  0  0]
+            [ 8 34  0  0]
+            [32  0 34  0]
+            [ 0 16 64 64]
+            Domain: Clan H^2 over Rational Field
+            Codomain: Clan H^2 over Rational Field
+            sage: x.operator()*(1/2)
+            Clan operator represented by the matrix:
+            [ 2  0  0  0]
+            [ 4 17  0  0]
+            [16  0 17  0]
+            [ 0  8 32 32]
+            Domain: Clan H^2 over Rational Field
+            Codomain: Clan H^2 over Rational Field
+
+        """
+        try:
+            if other in self.codomain().base_ring():
+                return ClanOperator(
+                    self.domain(),
+                    self.codomain(),
+                    self.matrix()*other)
+        except NotImplementedError:
+            # This can happen with certain arguments if the base_ring()
+            # is weird and doesn't know how to test membership.
+            pass
+
+        # This should eventually delegate to _composition_ after performing
+        # some sanity checks for us.
+        return super().__mul__(other)
+
+    def __pow__(self, n):
+        """
+        Raise this clan operator to the power ``n``.
+
+        SETUP::
+
+            sage: from mjo.clan.clan_operator import ClanOperator
+            sage: from mjo.clan.clan import HnClan, SnClan
+
+        EXAMPLES::
+
+            sage: C = SnClan(2)
+            sage: f = C.random_element().operator()
+            sage: f^0
+            Clan operator represented by the matrix:
+            [1 0 0]
+            [0 1 0]
+            [0 0 1]
+            Domain: Clan S^2 over Rational Field
+            Codomain: Clan S^2 over Rational Field
+            sage: id = C.one().operator()
+            sage: id^2
+            Clan operator represented by the matrix:
+            [1 0 0]
+            [0 1 0]
+            [0 0 1]
+            Domain: Clan S^2 over Rational Field
+            Codomain: Clan S^2 over Rational Field
+
+        TESTS:
+
+        Exponentiation doesn't work when the domain and codomain
+        differ, even if their dimensions are compatible::
+
+            sage: C1 = SnClan(1)
+            sage: C2 = HnClan(1)
+            sage: I  = C1.one().operator().matrix()
+            sage: L = ClanOperator(C1, C2, I)
+            sage: L^2
+            Traceback (most recent call last):
+            ...
+            TypeError: ...domain must equal right...
+
+        """
+        if (n == 1):
+            return self
+        elif (n == 0):
+            # Raising a vector space morphism to the zero power gives
+            # you back a special IdentityMorphism that is useless to us.
+            mat = self.matrix()**self.base_ring().zero()
+            return ClanOperator(self.domain(), self.codomain(), mat)
+
+        # Actually multiply them for n >= 2 so that the domain and
+        # codomain checks in __mul__ aren't skipped (as they would be
+        # if we simply exponentiated the matrix).
+        from functools import reduce
+        from itertools import repeat
+        from operator import mul
+        return reduce(mul, repeat(self, n))
+
     def _repr_(self):
         r"""