]> gitweb.michael.orlitzky.com - sage.d.git/commitdiff
New module mjo.octonions implementing the algebra of Octonions().
authorMichael Orlitzky <michael@orlitzky.com>
Mon, 1 Mar 2021 20:06:34 +0000 (15:06 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Mon, 1 Mar 2021 20:06:34 +0000 (15:06 -0500)
mjo/all.py
mjo/octonions.py [new file with mode: 0644]

index de2212d488360d5ff38edb7a3da775285e4262c2..f80276732c33e8cf3efd2384d3b950d52cfb758a 100644 (file)
@@ -8,6 +8,7 @@ from mjo.cone.all import *
 from mjo.eja.all import *
 from mjo.interpolation import *
 from mjo.misc import *
+from mjo.octonions import Octonions
 from mjo.orthogonal_polynomials import *
 from mjo.polynomial import *
 from mjo.symbol_sequence import *
diff --git a/mjo/octonions.py b/mjo/octonions.py
new file mode 100644 (file)
index 0000000..f6e222e
--- /dev/null
@@ -0,0 +1,291 @@
+from sage.combinat.free_module import CombinatorialFreeModule
+from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement
+from sage.categories.magmatic_algebras import MagmaticAlgebras
+from sage.rings.all import AA, ZZ
+from sage.matrix.matrix_space import MatrixSpace
+from sage.misc.table import table
+
+class Octonion(IndexedFreeModuleElement):
+    def conjugate(self):
+        r"""
+        SETUP::
+
+            sage: from mjo.octonions import Octonions
+
+        EXAMPLES::
+
+            sage: O = Octonions()
+            sage: x = sum(O.gens())
+            sage: x.conjugate()
+            e0 - e1 - e2 - e3 - e4 - e5 - e6 - e7
+
+        TESTS::
+
+        Conjugating twice gets you the original element::
+
+            sage: set_random_seed()
+            sage: O = Octonions()
+            sage: x = O.random_element()
+            sage: x.conjugate().conjugate() == x
+            True
+
+        """
+        C = MatrixSpace(ZZ,8).diagonal_matrix((1,-1,-1,-1,-1,-1,-1,-1))
+        return self.parent().from_vector(C*self.to_vector())
+
+    def real(self):
+        r"""
+        Return the real part of this octonion.
+
+        The real part of an octonion is its projection onto the span
+        of the first generator. In other words, the "first dimension"
+        is real and the others are imaginary.
+
+        SETUP::
+
+            sage: from mjo.octonions import Octonions
+
+        EXAMPLES::
+
+            sage: O = Octonions()
+            sage: x = sum(O.gens())
+            sage: x.real()
+            e0
+
+        TESTS:
+
+        This method is idempotent::
+
+            sage: set_random_seed()
+            sage: O = Octonions()
+            sage: x = O.random_element()
+            sage: x.real().real() == x.real()
+            True
+
+        """
+        return (self + self.conjugate())/2
+
+    def imag(self):
+        r"""
+        Return the imaginary part of this octonion.
+
+        The imaginary part of an octonion is its projection onto the
+        orthogonal complement of the span of the first generator. In
+        other words, the "first dimension" is real and the others are
+        imaginary.
+
+        SETUP::
+
+            sage: from mjo.octonions import Octonions
+
+        EXAMPLES::
+
+            sage: O = Octonions()
+            sage: x = sum(O.gens())
+            sage: x.imag()
+            e1 + e2 + e3 + e4 + e5 + e6 + e7
+
+        TESTS:
+
+        This method is idempotent::
+
+            sage: set_random_seed()
+            sage: O = Octonions()
+            sage: x = O.random_element()
+            sage: x.imag().imag() == x.imag()
+            True
+
+        """
+        return (self - self.conjugate())/2
+
+    def _norm_squared(self):
+        return (self*self.conjugate()).coefficient(0)
+
+    def norm(self):
+        r"""
+        Return the norm of this octonion.
+
+        SETUP::
+
+            sage: from mjo.octonions import Octonions
+
+        EXAMPLES::
+
+            sage: O = Octonions()
+            sage: O.one().norm()
+            1
+
+        TESTS:
+
+        The norm is nonnegative and belongs to the base field::
+
+            sage: set_random_seed()
+            sage: O = Octonions()
+            sage: n = O.random_element().norm()
+            sage: n >= 0 and n in O.base_ring()
+            True
+
+        The norm is homogeneous::
+
+            sage: set_random_seed()
+            sage: O = Octonions()
+            sage: x = O.random_element()
+            sage: alpha = O.base_ring().random_element()
+            sage: (alpha*x).norm() == alpha.abs()*x.norm()
+            True
+
+        """
+        return self._norm_squared().sqrt()
+
+    def inverse(self):
+        r"""
+        Return the inverse of this element if it exists.
+
+        SETUP::
+
+            sage: from mjo.octonions import Octonions
+
+        EXAMPLES::
+
+            sage: O = Octonions()
+            sage: x = sum(O.gens())
+            sage: x*x.inverse() == O.one()
+            True
+
+        ::
+
+            sage: O = Octonions()
+            sage: O.one().inverse() == O.one()
+            True
+
+        TESTS::
+
+            sage: set_random_seed()
+            sage: O = Octonions()
+            sage: x = O.random_element()
+            sage: x.is_zero() or ( x*x.inverse() == O.one() )
+            True
+
+        """
+        if self.is_zero():
+            raise ValueError("zero is not invertible")
+        return self.conjugate()/self._norm_squared()
+
+class Octonions(CombinatorialFreeModule):
+    r"""
+    SETUP::
+
+        sage: from mjo.octonions import Octonions
+
+    EXAMPLES::
+
+        sage: Octonions()
+        Octonion algebra with base ring Algebraic Real Field
+        sage: Octonions(field=QQ)
+        Octonion algebra with base ring Rational Field
+
+    """
+    def __init__(self,
+                 field=AA,
+                 prefix="e"):
+
+        # Not associative, not commutative
+        category = MagmaticAlgebras(field).FiniteDimensional()
+        category = category.WithBasis().Unital()
+
+        super().__init__(field,
+                         range(8),
+                         element_class=Octonion,
+                         category=category,
+                         prefix=prefix,
+                         bracket=False)
+
+        # The product of each basis element is plus/minus another
+        # basis element that can simply be looked up on
+        # https://en.wikipedia.org/wiki/Octonion
+        e0, e1, e2, e3, e4, e5, e6, e7 = self.gens()
+        self._multiplication_table = (
+            (e0, e1, e2, e3, e4, e5, e6, e7),
+            (e1,-e0, e3,-e2, e5,-e4,-e7, e6),
+            (e2,-e3,-e0, e1, e6, e7,-e4,-e5),
+            (e3, e2,-e1,-e0, e7,-e6, e5,-e4),
+            (e4,-e5,-e6,-e7,-e0, e1, e2, e3),
+            (e5, e4,-e7, e6,-e1,-e0,-e3, e2),
+            (e6, e7, e4,-e5,-e2, e3,-e0,-e1),
+            (e7,-e6, e5, e4,-e3,-e2, e1,-e0),
+        )
+
+    def product_on_basis(self, i, j):
+        return self._multiplication_table[i][j]
+
+    def one_basis(self):
+        r"""
+        Return the monomial index (basis element) corresponding to the
+        octonion unit element.
+
+        SETUP::
+
+            sage: from mjo.octonions import Octonions
+
+        TESTS:
+
+        This gives the correct unit element::
+
+            sage: set_random_seed()
+            sage: O = Octonions()
+            sage: x = O.random_element()
+            sage: x*O.one() == x and O.one()*x == x
+            True
+
+        """
+        return 0
+
+    def _repr_(self):
+        return ("Octonion algebra with base ring %s" % self.base_ring())
+
+    def multiplication_table(self):
+        """
+        Return a visual representation of this algebra's multiplication
+        table (on basis elements).
+
+        SETUP::
+
+            sage: from mjo.octonions import Octonions
+
+        EXAMPLES:
+
+        The multiplication table is what Wikipedia says it is::
+
+            sage: Octonions().multiplication_table()
+            +----++----+-----+-----+-----+-----+-----+-----+-----+
+            | *  || e0 | e1  | e2  | e3  | e4  | e5  | e6  | e7  |
+            +====++====+=====+=====+=====+=====+=====+=====+=====+
+            | e0 || e0 | e1  | e2  | e3  | e4  | e5  | e6  | e7  |
+            +----++----+-----+-----+-----+-----+-----+-----+-----+
+            | e1 || e1 | -e0 | e3  | -e2 | e5  | -e4 | -e7 | e6  |
+            +----++----+-----+-----+-----+-----+-----+-----+-----+
+            | e2 || e2 | -e3 | -e0 | e1  | e6  | e7  | -e4 | -e5 |
+            +----++----+-----+-----+-----+-----+-----+-----+-----+
+            | e3 || e3 | e2  | -e1 | -e0 | e7  | -e6 | e5  | -e4 |
+            +----++----+-----+-----+-----+-----+-----+-----+-----+
+            | e4 || e4 | -e5 | -e6 | -e7 | -e0 | e1  | e2  | e3  |
+            +----++----+-----+-----+-----+-----+-----+-----+-----+
+            | e5 || e5 | e4  | -e7 | e6  | -e1 | -e0 | -e3 | e2  |
+            +----++----+-----+-----+-----+-----+-----+-----+-----+
+            | e6 || e6 | e7  | e4  | -e5 | -e2 | e3  | -e0 | -e1 |
+            +----++----+-----+-----+-----+-----+-----+-----+-----+
+            | e7 || e7 | -e6 | e5  | e4  | -e3 | -e2 | e1  | -e0 |
+            +----++----+-----+-----+-----+-----+-----+-----+-----+
+
+        """
+        n = self.dimension()
+        # Prepend the header row.
+        M = [["*"] + list(self.gens())]
+
+        # And to each subsequent row, prepend an entry that belongs to
+        # the left-side "header column."
+        M += [ [self.monomial(i)] + [ self.monomial(i)*self.monomial(j)
+                                    for j in range(n) ]
+               for i in range(n) ]
+
+        return table(M, header_row=True, header_column=True, frame=True)