Library for arbitrarydimension hypercomplex numbers following the CayleyDickson construction.
Project description
Hypercomplex
A Python library for working with quaternions, octonions, sedenions, and beyond following the CayleyDickson construction of hypercomplex numbers.
The complex numbers may be viewed as an extension of the everyday real numbers. A complex number has two realnumber coefficients, one multiplied by 1, the other multiplied by i.
In a similar way, a quaternion, which has 4 components, can be constructed by combining two complex numbers. Likewise, two quaternions can construct an octonion (8 components), and two octonions can construct a sedenion (16 components).
The method for this construction is known as the CayleyDickson construction and the resulting classes of numbers are types of hypercomplex numbers. There is no limit to the number of times you can repeat the CayleyDickson construction to create new types of hypercomplex numbers, doubling the number of components each time.
This Python 3 package allows the creation of number classes at any repetition level of CayleyDickson constructions, and has builtins for the lower, named levels such as quaternion, octonion, and sedenion.
Installation
pip install hypercomplex
This package was built in Python 3.9.6 and has been tested to be compatible with python 3.6 through 3.10.
Basic Usage
from hypercomplex import Complex, Quaternion, Octonion, Voudon, cayley_dickson_construction
c = Complex(0, 7)
print(c) # > (0 7)
print(c == 7j) # > True
q = Quaternion(1.1, 2.2, 3.3, 4.4)
print(2 * q) # > (2.2 4.4 6.6 8.8)
print(Quaternion.e_matrix()) # > e0 e1 e2 e3
# e1 e0 e3 e2
# e2 e3 e0 e1
# e3 e2 e1 e0
o = Octonion(0, 0, 0, 0, 8, 8, 9, 9)
print(o + q) # > (1.1 2.2 3.3 4.4 8 8 9 9)
v = Voudon()
print(v == 0) # > True
print(len(v)) # > 256
BeyondVoudon = cayley_dickson_construction(Voudon)
print(len(BeyondVoudon())) # > 512
For more snippets see the Thorough Usage Examples section below.
Package Contents
Three functions form the core of the package:

reals(base)
 Given a base type (float
by default), generates a class that represents numbers with 1 hypercomplex dimension, i.e. real numbers. This class can then be extended into complex numbers and beyond withcayley_dickson_construction
.Any usual math operations on instances of the class returned by
reals
behave as instances ofbase
would but their type remains the reals class. By default they are printed with theg
formatspec and surrounded by parentheses, e.g.(1)
, to remain consistent with the format of higher dimension hypercomplex numbers.Python's
decimal.Decimal
might be another likely choice forbase
.# reals example: from hypercomplex import reals from decimal import Decimal D = reals(Decimal) print(D(10) / 4) # > (2.5) print(D(3) * D(9)) # > (27)

cayley_dickson_construction(basis)
(aliascd_construction
) generates a new class of hypercomplex numbers with twice the dimension of the givenbasis
, which must be another hypercomplex number class or class returned fromreals
. The new class of numbers is defined recursively on the basis according the CayleyDickson construction. Normal math operations may be done upon its instances and with instances of other numeric types.# cayley_dickson_construction example: from hypercomplex import * RealNum = reals() ComplexNum = cayley_dickson_construction(RealNum) QuaternionNum = cayley_dickson_construction(ComplexNum) q = QuaternionNum(1, 2, 3, 4) print(q) # > (1 2 3 4) print(1 / q) # > (0.0333333 0.0666667 0.1 0.133333) print(q + 1+2j) # > (2 4 3 4)

cayley_dickson_algebra(level, base)
(aliascd_algebra
) is a helper function that repeatedly appliescayley_dickson_construction
to the givenbase
type (float
by default)level
number of times. That is,cayley_dickson_algebra
returns the class for the CayleyDickson algebra of hypercomplex numbers with2**level
dimensions.# cayley_dickson_algebra example: from hypercomplex import * OctonionNum = cayley_dickson_algebra(3) o = OctonionNum(8, 7, 6, 5, 4, 3, 2, 1) print(o) # > (8 7 6 5 4 3 2 1) print(2 * o) # > (16 14 12 10 8 6 4 2) print(o.conjugate()) # > (8 7 6 5 4 3 2 1)
For convenience, nine internal number types are already defined, built off of each other:
Name  Aliases  Description 

Real 
R , CD1 , CD[0] 
Real numbers with 1 hypercomplex dimension based on float . 
Complex 
C , CD2 , CD[1] 
Complex numbers with 2 hypercomplex dimensions based on Real . 
Quaternion 
Q , CD4 , CD[2] 
Quaternion numbers with 4 hypercomplex dimensions based on Complex . 
Octonion 
O , CD8 , CD[3] 
Octonion numbers with 8 hypercomplex dimensions based on Quaternion . 
Sedenion 
S , CD16 , CD[4] 
Sedenion numbers with 16 hypercomplex dimensions based on Octonion . 
Pathion 
P , CD32 , CD[5] 
Pathion numbers with 32 hypercomplex dimensions based on Sedenion . 
Chingon 
X , CD64 , CD[6] 
Chingon numbers with 64 hypercomplex dimensions based on Pathion . 
Routon 
U , CD128 , CD[7] 
Routon numbers with 128 hypercomplex dimensions based on Chingon . 
Voudon 
V , CD256 , CD[8] 
Voudon numbers with 256 hypercomplex dimensions based on Routon . 
# builtin types example:
from hypercomplex import *
print(Real(4)) # > (4)
print(C(37j)) # > (3 7)
print(CD4(.1, 2.2, 3.3e3)) # > (0.1 2.2 3300 0)
print(CD[3](1, 0, 2, 0, 3)) # > (1 0 2 0 3 0 0 0)
The names and letterabbreviations were taken from this image (mirror) found in Micheal Carter's paper Visualization of the CayleyDickson Hypercomplex Numbers Up to the Chingons (64D), but they also may be known according to their Latin naming conventions.
Thorough Usage Examples
This list follows examples.py exactly and documents nearly all the things you can do with the hypercomplex numbers created by this package.
Every example assumes the appropriate imports are already done, e.g. from hypercomplex import *
.

Initialization can be done in various ways, including using Python's built in complex numbers. Unspecified coefficients become 0.
print(R(1.5)) # > (1.5) print(C(2, 3)) # > (2 3) print(C(2 + 3j)) # > (2 3) print(Q(4, 5, 6, 7)) # > (4 5 6 7) print(Q(4 + 5j, C(6, 7), pair=True)) # > (4 5 6 7) print(P()) # > (0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)

Numbers can be added and subtracted. The result will be the type with more dimensions.
print(Q(0, 1, 2, 2) + C(9, 1)) # > (9 0 2 2) print(100.1  O(0, 0, 0, 0, 1.1, 2.2, 3.3, 4.4)) # > (100.1 0 0 0 1.1 2.2 3.3 4.4)

Numbers can be multiplied. The result will be the type with more dimensions.
print(10 * S(1, 2, 3)) # > (10 20 30 0 0 0 0 0 0 0 0 0 0 0 0 0) print(Q(1.5, 2.0) * O(0, 1)) # > (2 1.5 0 0 0 0 0 0) # notice quaternions are noncommutative print(Q(1, 2, 3, 4) * Q(1, 0, 0, 1)) # > (3 5 1 5) print(Q(1, 0, 0, 1) * Q(1, 2, 3, 4)) # > (3 1 5 5)

Numbers can be divided and
inverse
gives the multiplicative inverse.print(100 / C(0, 2)) # > (0 50) print(C(2, 2) / Q(1, 2, 3, 4)) # > (0.2 0.0666667 0.0666667 0.466667) print(C(2, 2) * Q(1, 2, 3, 4).inverse()) # > (0.2 0.0666667 0.0666667 0.466667) print(R(2).inverse(), 1 / R(2)) # > (0.5) (0.5)

Numbers can be raised to integer powers, a shortcut for repeated multiplication or division.
q = Q(0, 3, 4, 0) print(q**5) # > (0 1875 2500 0) print(q * q * q * q * q) # > (0 1875 2500 0) print(q**1) # > (0 0.12 0.16 0) print(1 / q) # > (0 0.12 0.16 0) print(q**0) # > (1 0 0 0)

conjugate
gives the conjugate of the number.print(R(9).conjugate()) # > (9) print(C(9, 8).conjugate()) # > (9 8) print(Q(9, 8, 7, 6).conjugate()) # > (9 8 7 6)

norm
gives the absolute value as the base type (float
by default). There is alsonorm_squared
.print(O(3, 4).norm(), type(O(3, 4).norm())) # > 5.0 <class 'float'> print(abs(O(3, 4))) # > 5.0 print(O(3, 4).norm_squared()) # > 25.0

Numbers are considered equal if their coefficients all match. Nonexistent coefficients are 0.
print(R(999) == V(999)) # > True print(C(1, 2) == Q(1, 2)) # > True print(C(1, 2) == Q(1, 2, 0.1)) # > False

coefficients
gives a tuple of the components of the number in their base type (float
by default). The propertiesreal
andimag
are shortcuts for the first two components. Indexing can also be used (but is inefficient).print(R(100).coefficients()) # > (100.0,) q = Q(2, 3, 4, 5) print(q.coefficients()) # > (2.0, 3.0, 4.0, 5.0) print(q.real, q.imag) # > 2.0 3.0 print(q[0], q[1], q[2], q[3]) # > 2.0 3.0 4.0 5.0

e(index)
of a number class gives the unit hypercomplex number where the index coefficient is 1 and all others are 0.print(C.e(0)) # > (1 0) print(C.e(1)) # > (0 1) print(O.e(3)) # > (0 0 0 1 0 0 0 0)

e_matrix
of a number class gives the multiplication table ofe(i)*e(j)
. Setstring=False
to get a 2D list instead of a string. Setraw=True
to get the raw hypercomplex numbers.print(O.e_matrix()) # > e1 e2 e3 e4 e5 e6 e7 # e0 e3 e2 e5 e4 e7 e6 # e3 e0 e1 e6 e7 e4 e5 # e2 e1 e0 e7 e6 e5 e4 # e5 e6 e7 e0 e1 e2 e3 # e4 e7 e6 e1 e0 e3 e2 # e7 e4 e5 e2 e3 e0 e1 # e6 e5 e4 e3 e2 e1 e0 # print(C.e_matrix(string=False, raw=True)) # > [[(1 0), (0 1)], [(0 1), (1 0)]]

A number is considered truthy if it has has nonzero coefficients. Conversion to
int
,float
andcomplex
are only valid when the coefficients beyond the dimension of those types are all 0.print(bool(Q())) # > False print(bool(Q(0, 0, 0.01, 0))) # > True print(complex(Q(5, 5))) # > (5+5j) print(int(V(9.9))) # > 9 # print(float(C(1, 2))) < invalid

Any usual format spec for the base type can be given in an fstring.
o = O(0.001, 1, 2, 3.3333, 4e5) print(f"{o:.2f}") # > (0.00 1.00 2.00 3.33 400000.00 0.00 0.00 0.00) print(f"{R(23.9):04.0f}") # > (0024)

The
len
of a number is its hypercomplex dimension, i.e. the number of components or coefficients it has.print(len(R())) # > 1 print(len(C(7, 7))) # > 2 print(len(U())) # > 128

Using
in
behaves the same as if the number were a tuple of its coefficients.print(3 in Q(1, 2, 3, 4)) # > True print(5 in Q(1, 2, 3, 4)) # > False

copy
can be used to duplicate a number (but should generally never be needed as all operations create a new number).x = O(9, 8, 7) y = x.copy() print(x == y) # > True print(x is y) # > False

base
on a number class will return the base type the entire numbers are built upon.print(R.base()) # > <class 'float'> print(V.base()) # > <class 'float'> A = cayley_dickson_algebra(20, int) print(A.base()) # > <class 'int'>

Hypercomplex numbers are weird, so be careful! Here two nonzero sedenions multiply to give zero because sedenions and beyond have zero devisors.
s1 = S.e(5) + S.e(10) s2 = S.e(6) + S.e(9) print(s1) # > (0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0) print(s2) # > (0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0) print(s1 * s2) # > (0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0) print((1 / s1) * (1 / s2)) # > (0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0) # print(1/(s1 * s2)) < zero division error
About
I wrote this package for the novelty of it and as a math and programming exercise. The operations it can perform on hypercomplex numbers are not particularly efficient due to the recursive nature of the CayleyDickson construction.
I am not a mathematician, only a math hobbyist, and apologize if there are issues with the implementations or descriptions I have provided.
Project details
Release history Release notifications  RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for hypercomplex0.3.4py3noneany.whl
Algorithm  Hash digest  

SHA256  2a8c9211c9dba456dc13c99a654cce4b9ba01434ee3b03c04fa7613f9c540f88 

MD5  67f7f95c0beee63821abccd4a0b25d76 

BLAKE2b256  37b1f73d5b223ca6d6c5900388c61e14488ab8d38d8acaa3e2a3fd1ba1042cec 