From: Michael Orlitzky Date: Mon, 1 Mar 2021 20:06:34 +0000 (-0500) Subject: New module mjo.octonions implementing the algebra of Octonions(). X-Git-Url: http://gitweb.michael.orlitzky.com/?p=sage.d.git;a=commitdiff_plain;h=bccb4975e8bc21ddb5f726a68f2497477df97df9 New module mjo.octonions implementing the algebra of Octonions(). --- diff --git a/mjo/all.py b/mjo/all.py index de2212d..f802767 100644 --- a/mjo/all.py +++ b/mjo/all.py @@ -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 index 0000000..f6e222e --- /dev/null +++ b/mjo/octonions.py @@ -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)