In this notebook, tools for working with quaternions for physics issues are developed. The class QH treat quaternions as Hamilton would have done: as a 4-vector over the real numbers.
In physics, group theory plays a central role in the fundamental forces of Nature via the standard model. The gauge symmetry U(1) a unit circle in the complex plane leads to electric charge conservation. The unit quaternions $SU(2)$ is the symmetry needed for the weak force which leads to beta decay. The group $SU(3)$ is the symmetry of the strong force that keeps a nucleus together.
The class Q8 was written in the hope that group theory would be written in first, not added as needed later. I call these "space-time numbers". The problem with such an approach is that one does not use the mathematical field of real numbers. Instead one relies on the set of positive reals. In some ways, this is like reverse engineering some basic computer science. Libraries written in C have a notion of a signed versus unsigned integer. The signed integer behaves like the familiar integers. The unsigned integer is like the positive integers. The difference between the two is whether there is a placeholder for the sign or not. All floats are signed. The modulo operations that work for unsigned integers does not work for floats.
This set of tools is done 4x:
Test driven development was used. The same tests were used for QH, QHa, Q8, and Q8a. Either class can be used to study quaternions in physics.
In [1]:
import IPython
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import math
import numpy as np
import random
import sympy as sp
import os
import unittest
from IPython.display import display
from os.path import basename
from glob import glob
%matplotlib inline
Define the stretch factor $\gamma$ and the $\gamma \beta$ used in special relativity.
In [2]:
def sr_gamma(beta_x=0, beta_y=0, beta_z=0):
"""The gamma used in special relativity using 3 velocites, some may be zero."""
return 1 / (1 - beta_x ** 2 - beta_y ** 2 - beta_z ** 2) ** (1/2)
def sr_gamma_betas(beta_x=0, beta_y=0, beta_z=0):
"""gamma and the three gamma * betas used in special relativity."""
g = sr_gamma(beta_x, beta_y, beta_z)
return [g, g * beta_x, g * beta_y, g * beta_z]
Define a class QH to manipulate quaternions as Hamilton would have done it so many years ago. The "qtype" is a little bit of text to leave a trail of breadcrumbs about how a particular quaternion was generated.
Write tests the QH class.
In [4]:
class TestQH(unittest.TestCase):
"""Class to make sure all the functions work as expected."""
Q = QH([1, -2, -3, -4], qtype="Q")
P = QH([0, 4, -3, 0], qtype="P")
R = QH([3, 0, 0, 0], qtype="R")
C = QH([2, 4, 0, 0], qtype="C")
def test_qt(self):
self.assertTrue(self.Q.t == 1)
def test_q_0(self):
q_z = self.Q.q_0()
print("q_0: ", q_z)
self.assertTrue(q_z.t == 0)
self.assertTrue(q_z.x == 0)
self.assertTrue(q_z.y == 0)
self.assertTrue(q_z.z == 0)
def test_q_1(self):
q_z = self.Q.q_1()
print("q_1: ", q_z)
self.assertTrue(q_z.t == 1)
self.assertTrue(q_z.x == 0)
self.assertTrue(q_z.y == 0)
self.assertTrue(q_z.z == 0)
def test_q_i(self):
q_z = self.Q.q_i()
print("q_i: ", q_z)
self.assertTrue(q_z.t == 0)
self.assertTrue(q_z.x == 1)
self.assertTrue(q_z.y == 0)
self.assertTrue(q_z.z == 0)
def test_q_j(self):
q_z = self.Q.q_j()
print("q_j: ", q_z)
self.assertTrue(q_z.t == 0)
self.assertTrue(q_z.x == 0)
self.assertTrue(q_z.y == 1)
self.assertTrue(q_z.z == 0)
def test_q_k(self):
q_z = self.Q.q_k()
print("q_k: ", q_z)
self.assertTrue(q_z.t == 0)
self.assertTrue(q_z.x == 0)
self.assertTrue(q_z.y == 0)
self.assertTrue(q_z.z == 1)
def test_q_random(self):
q_z = QH().q_random()
print("q_random():", q_z)
self.assertTrue(q_z.t >= 0 and q_z.t <= 1)
self.assertTrue(q_z.x >= 0 and q_z.x <= 1)
self.assertTrue(q_z.y >= 0 and q_z.y <= 1)
self.assertTrue(q_z.z >= 0 and q_z.z <= 1)
def test_equals(self):
self.assertTrue(self.Q.equals(self.Q))
self.assertFalse(self.Q.equals(self.P))
def test_conj_0(self):
q_z = self.Q.conj()
print("q_conj 0: ", q_z)
self.assertTrue(q_z.t == 1)
self.assertTrue(q_z.x == 2)
self.assertTrue(q_z.y == 3)
self.assertTrue(q_z.z == 4)
def test_conj_1(self):
q_z = self.Q.conj(1)
print("q_conj 1: ", q_z)
self.assertTrue(q_z.t == -1)
self.assertTrue(q_z.x == -2)
self.assertTrue(q_z.y == 3)
self.assertTrue(q_z.z == 4)
def test_conj_2(self):
q_z = self.Q.conj(2)
print("q_conj 2: ", q_z)
self.assertTrue(q_z.t == -1)
self.assertTrue(q_z.x == 2)
self.assertTrue(q_z.y == -3)
self.assertTrue(q_z.z == 4)
def sign_flips(self):
q_z = self.Q.sign_flips()
print("sign_flips: ", q_z)
self.assertTrue(q_z.t == -1)
self.assertTrue(q_z.x == 2)
self.assertTrue(q_z.y == 3)
self.assertTrue(q_z.z == 4)
def test_vahlen_conj_minus(self):
q_z = self.Q.vahlen_conj()
print("q_vahlen_conj -: ", q_z)
self.assertTrue(q_z.t == 1)
self.assertTrue(q_z.x == 2)
self.assertTrue(q_z.y == 3)
self.assertTrue(q_z.z == 4)
def test_vahlen_conj_star(self):
q_z = self.Q.vahlen_conj('*')
print("q_vahlen_conj *: ", q_z)
self.assertTrue(q_z.t == 1)
self.assertTrue(q_z.x == -2)
self.assertTrue(q_z.y == -3)
self.assertTrue(q_z.z == 4)
def test_vahlen_conj_prime(self):
q_z = self.Q.vahlen_conj("'")
print("q_vahlen_conj ': ", q_z)
self.assertTrue(q_z.t == 1)
self.assertTrue(q_z.x == 2)
self.assertTrue(q_z.y == 3)
self.assertTrue(q_z.z == -4)
def test_square(self):
q_z = self.Q.square()
print("square: ", q_z)
self.assertTrue(q_z.t == -28)
self.assertTrue(q_z.x == -4)
self.assertTrue(q_z.y == -6)
self.assertTrue(q_z.z == -8)
def test_norm_squared(self):
q_z = self.Q.norm_squared()
print("norm_squared: ", q_z)
self.assertTrue(q_z.t == 30)
self.assertTrue(q_z.x == 0)
self.assertTrue(q_z.y == 0)
self.assertTrue(q_z.z == 0)
def test_norm_squared_of_vector(self):
q_z = self.Q.norm_squared_of_vector()
print("norm_squared_of_vector: ", q_z)
self.assertTrue(q_z.t == 29)
self.assertTrue(q_z.x == 0)
self.assertTrue(q_z.y == 0)
self.assertTrue(q_z.z == 0)
def test_abs_of_q(self):
q_z = self.P.abs_of_q()
print("abs_of_q: ", q_z)
self.assertTrue(q_z.t == 5)
self.assertTrue(q_z.x == 0)
self.assertTrue(q_z.y == 0)
self.assertTrue(q_z.z == 0)
def test_normalize(self):
q_z = self.P.normalize()
print("q_normalized: ", q_z)
self.assertTrue(q_z.t == 0)
self.assertTrue(q_z.x == 0.8)
self.assertAlmostEqual(q_z.y, -0.6)
self.assertTrue(q_z.z == 0)
def test_abs_of_vector(self):
q_z = self.P.abs_of_vector()
print("abs_of_vector: ", q_z)
self.assertTrue(q_z.t == 5)
self.assertTrue(q_z.x == 0)
self.assertTrue(q_z.y == 0)
self.assertTrue(q_z.z == 0)
def test_add(self):
q_z = self.Q.add(self.P)
print("add: ", q_z)
self.assertTrue(q_z.t == 1)
self.assertTrue(q_z.x == 2)
self.assertTrue(q_z.y == -6)
self.assertTrue(q_z.z == -4)
def test_dif(self):
q_z = self.Q.dif(self.P)
print("dif: ", q_z)
self.assertTrue(q_z.t == 1)
self.assertTrue(q_z.x == -6)
self.assertTrue(q_z.y == 0)
self.assertTrue(q_z.z == -4)
def test_product(self):
q_z = self.Q.product(self.P)
print("product: ", q_z)
self.assertTrue(q_z.t == -1)
self.assertTrue(q_z.x == -8)
self.assertTrue(q_z.y == -19)
self.assertTrue(q_z.z == 18)
def test_product_even(self):
q_z = self.Q.product(self.P, kind="even")
print("product, kind even: ", q_z)
self.assertTrue(q_z.t == -1)
self.assertTrue(q_z.x == 4)
self.assertTrue(q_z.y == -3)
self.assertTrue(q_z.z == 0)
def test_product_odd(self):
q_z = self.Q.product(self.P, kind="odd")
print("product, kind odd: ", q_z)
self.assertTrue(q_z.t == 0)
self.assertTrue(q_z.x == -12)
self.assertTrue(q_z.y == -16)
self.assertTrue(q_z.z == 18)
def test_product_even_minus_odd(self):
q_z = self.Q.product(self.P, kind="even_minus_odd")
print("product, kind even_minus_odd: ", q_z)
self.assertTrue(q_z.t == -1)
self.assertTrue(q_z.x == 16)
self.assertTrue(q_z.y == 13)
self.assertTrue(q_z.z == -18)
def test_product_reverse(self):
q1q2_rev = self.Q.product(self.P, reverse=True)
q2q1 = self.P.product(self.Q)
self.assertTrue(q1q2_rev.equals(q2q1))
def test_Euclidean_product(self):
q_z = self.Q.Euclidean_product(self.P)
print("Euclidean product: ", q_z)
self.assertTrue(q_z.t == 1)
self.assertTrue(q_z.x == 16)
self.assertTrue(q_z.y == 13)
self.assertTrue(q_z.z == -18)
def test_invert(self):
q_z = self.P.invert()
print("invert: ", q_z)
self.assertTrue(q_z.t == 0)
self.assertTrue(q_z.x == -0.16)
self.assertTrue(q_z.y == 0.12)
self.assertTrue(q_z.z == 0)
def test_divide_by(self):
q_z = self.Q.divide_by(self.Q)
print("divide_by: ", q_z)
self.assertTrue(q_z.t == 1)
self.assertTrue(q_z.x == 0)
self.assertTrue(q_z.y == 0)
self.assertTrue(q_z.z == 0)
def test_triple_product(self):
q_z = self.Q.triple_product(self.P, self.Q)
print("triple product: ", q_z)
self.assertTrue(q_z.t == -2)
self.assertTrue(q_z.x == 124)
self.assertTrue(q_z.y == -84)
self.assertTrue(q_z.z == 8)
def test_rotate(self):
q_z = self.Q.rotate(1)
print("rotate: ", q_z)
self.assertTrue(q_z.t == 1)
self.assertTrue(q_z.x == -2)
self.assertTrue(q_z.y == 3)
self.assertTrue(q_z.z == 4)
def test_boost(self):
q1_sq = self.Q.square()
q_z = self.Q.boost(0.003)
q_z2 = q_z.square()
print("q1_sq: ", q1_sq)
print("boosted: ", q_z)
print("boosted squared: ", q_z2)
self.assertTrue(round(q_z2.t, 5) == round(q1_sq.t, 5))
def test_g_shift(self):
q1_sq = self.Q.square()
q_z = self.Q.g_shift(0.003)
q_z2 = q_z.square()
q_z_minimal = self.Q.g_shift(0.003, g_form="minimal")
q_z2_minimal = q_z_minimal.square()
print("q1_sq: ", q1_sq)
print("g_shift: ", q_z)
print("g squared: ", q_z2)
self.assertTrue(q_z2.t != q1_sq.t)
self.assertTrue(q_z2.x == q1_sq.x)
self.assertTrue(q_z2.y == q1_sq.y)
self.assertTrue(q_z2.z == q1_sq.z)
self.assertTrue(q_z2_minimal.t != q1_sq.t)
self.assertTrue(q_z2_minimal.x == q1_sq.x)
self.assertTrue(q_z2_minimal.y == q1_sq.y)
self.assertTrue(q_z2_minimal.z == q1_sq.z)
def test_sin(self):
self.assertTrue(QH([0, 0, 0, 0]).sin().equals(QH().q_0()))
self.assertTrue(self.Q.sin().equals(QH([91.7837157840346691, -21.8864868530291758, -32.8297302795437673, -43.7729737060583517])))
self.assertTrue(self.P.sin().equals(QH([0, 59.3625684622310033, -44.5219263466732542, 0])))
self.assertTrue(self.R.sin().equals(QH([0.1411200080598672, 0, 0, 0])))
self.assertTrue(self.C.sin().equals(QH([24.8313058489463785, -11.3566127112181743, 0, 0])))
def test_cos(self):
self.assertTrue(QH([0, 0, 0, 0]).cos().equals(QH().q_1()))
self.assertTrue(self.Q.cos().equals(QH([58.9336461679439481, 34.0861836904655959, 51.1292755356983974, 68.1723673809311919])))
self.assertTrue(self.P.cos().equals(QH([74.2099485247878476, 0, 0, 0])))
self.assertTrue(self.R.cos().equals(QH([-0.9899924966004454, 0, 0, 0])))
self.assertTrue(self.C.cos().equals(QH([-11.3642347064010600, -24.8146514856341867, 0, 0])))
def test_tan(self):
self.assertTrue(QH([0, 0, 0, 0]).tan().equals(QH().q_0()))
self.assertTrue(self.Q.tan().equals(QH([0.0000382163172501, -0.3713971716439372, -0.5570957574659058, -0.7427943432878743])))
self.assertTrue(self.P.tan().equals(QH([0, 0.7999273634100760, -0.5999455225575570, 0])))
self.assertTrue(self.R.tan().equals(QH([-0.1425465430742778, 0, 0, 0])))
self.assertTrue(self.C.tan().equals(QH([-0.0005079806234700, 1.0004385132020521, 0, 0])))
def test_sinh(self):
self.assertTrue(QH([0, 0, 0, 0]).sinh().equals(QH().q_0()))
self.assertTrue(self.Q.sinh().equals(QH([0.7323376060463428, 0.4482074499805421, 0.6723111749708131, 0.8964148999610841])))
self.assertTrue(self.P.sinh().equals(QH([0, -0.7671394197305108, 0.5753545647978831, 0])))
self.assertTrue(self.R.sinh().equals(QH([10.0178749274099026, 0, 0, 0])))
self.assertTrue(self.C.sinh().equals(QH([-2.3706741693520015, -2.8472390868488278, 0, 0])))
def test_cosh(self):
self.assertTrue(QH([0, 0, 0, 0]).cosh().equals(QH().q_1()))
self.assertTrue(self.Q.cosh().equals(QH([0.9615851176369565, 0.3413521745610167, 0.5120282618415251, 0.6827043491220334])))
self.assertTrue(self.P.cosh().equals(QH([0.2836621854632263, 0, 0, 0])))
self.assertTrue(self.R.cosh().equals(QH([10.0676619957777653, 0, 0, 0])))
self.assertTrue(self.C.cosh().equals(QH([-2.4591352139173837, -2.7448170067921538, 0, 0])))
def test_tanh(self):
self.assertTrue(QH([0, 0, 0, 0]).tanh().equals(QH().q_0()))
self.assertTrue(self.Q.tanh().equals(QH([1.0248695360556623, 0.1022956817887642, 0.1534435226831462, 0.2045913635775283])))
self.assertTrue(self.P.tanh().equals(QH([0, -2.7044120049972684, 2.0283090037479505, 0])))
self.assertTrue(self.R.tanh().equals(QH([0.9950547536867305, 0, 0, 0])))
self.assertTrue(self.C.tanh().equals(QH([1.0046823121902353, 0.0364233692474038, 0, 0])))
def test_exp(self):
self.assertTrue(QH([0, 0, 0, 0]).exp().equals(QH().q_1()))
self.assertTrue(self.Q.exp().equals(QH([1.6939227236832994, 0.7895596245415588, 1.1843394368123383, 1.5791192490831176])))
self.assertTrue(self.P.exp().equals(QH([0.2836621854632263, -0.7671394197305108, 0.5753545647978831, 0])))
self.assertTrue(self.R.exp().equals(QH([20.0855369231876679, 0, 0, 0])))
self.assertTrue(self.C.exp().equals(QH([-4.8298093832693851, -5.5920560936409816, 0, 0])))
def test_ln(self):
self.assertTrue(self.Q.ln().exp().equals(self.Q))
self.assertTrue(self.Q.ln().equals(QH([1.7005986908310777, -0.5151902926640850, -0.7727854389961275, -1.0303805853281700])))
self.assertTrue(self.P.ln().equals(QH([1.6094379124341003, 1.2566370614359172, -0.9424777960769379, 0])))
self.assertTrue(self.R.ln().equals(QH([1.0986122886681098, 0, 0, 0])))
self.assertTrue(self.C.ln().equals(QH([1.4978661367769954, 1.1071487177940904, 0, 0])))
def test_q_2_q(self):
self.assertTrue(self.Q.q_2_q(self.P).equals(QH([-0.0197219653530713, -0.2613955437374326, 0.6496281248064009, -0.3265786562423951])))
suite = unittest.TestLoader().loadTestsFromModule(TestQH())
unittest.TextTestRunner().run(suite);
In [5]:
class TestQHRep(unittest.TestCase):
Q12 = QH([1, 2, 0, 0])
Q1123 = QH([1, 1, 2, 3])
Q11p = QH([1, 1, 0, 0], representation="polar")
Q12p = QH([1, 2, 0, 0], representation="polar")
Q12np = QH([1, -2, 0, 0], representation="polar")
Q21p = QH([2, 1, 0, 0], representation="polar")
Q23p = QH([2, 3, 0, 0], representation="polar")
Q13p = QH([1, 3, 0, 0], representation="polar")
Q5p = QH([5, 0, 0, 0], representation="polar")
def test_txyz_2_representation(self):
qr = QH(self.Q12.txyz_2_representation(""))
self.assertTrue(qr.equals(self.Q12))
qr = QH(self.Q12.txyz_2_representation("polar"))
self.assertTrue(qr.equals(QH([2.23606797749979, 1.10714871779409, 0, 0])))
qr = QH(self.Q1123.txyz_2_representation("spherical"))
self.assertTrue(qr.equals(QH([1.0, 3.7416573867739413, 0.640522312679424, 1.10714871779409])))
def test_representation_2_txyz(self):
qr = QH(self.Q12.representation_2_txyz(""))
self.assertTrue(qr.equals(self.Q12))
qr = QH(self.Q12.representation_2_txyz("polar"))
self.assertTrue(qr.equals(QH([-0.4161468365471424, 0.9092974268256817, 0, 0])))
qr = QH(self.Q1123.representation_2_txyz("spherical"))
self.assertTrue(qr.equals(QH([1.0, -0.9001976297355174, 0.12832006020245673, -0.4161468365471424])))
def test_polar_products(self):
qr = self.Q11p.product(self.Q12p)
print("polar 1 1 0 0 * 1 2 0 0: ", qr)
self.assertTrue(qr.equals(self.Q13p))
qr = self.Q12p.product(self.Q21p)
print("polar 1 2 0 0 * 2 1 0 0: ", qr)
self.assertTrue(qr.equals(self.Q23p))
def test_polar_conj(self):
qr = self.Q12p.conj()
print("polar conj of 1 2 0 0: ", qr)
self.assertTrue(qr.equals(self.Q12np))
suite = unittest.TestLoader().loadTestsFromModule(TestQHRep())
unittest.TextTestRunner().run(suite);
A separate class is needed for numpy array due to technical issues I have getting sympy and numpy to play nicely with each other...
In [6]:
class QHa(object):
"""Quaternions as nparrays."""
def __init__(self, values=None, qtype="Q", representation=""):
if values is None:
self.a = np.array([0.0, 0.0, 0.0, 0.0])
elif len(values) == 4:
self.a = np.array([values[0], values[1], values[2], values[3]])
elif len(values) == 8:
self.a = np.array([values[0] - values[1], values[2] - values[3], values[4] - values[5], values[6] - values[7]])
self.representation = representation
if representation != "":
txyz = self.representation_2_txyz(representation)
self.a = np.array(txyz)
self.qtype = qtype
def __str__(self):
"""Customize the output."""
if self.representation == "":
string = "({t}, {x}, {y}, {z}) {qt}".format(t=self.a[0], x=self.a[1], y=self.a[2], z=self.a[3], qt=self.qtype)
elif self.representation == "polar":
rep = self.txyz_2_representation("polar")
string = "({A} A, {thetaX} 𝜈x, {thetaY} 𝜈y, {thetaZ} 𝜈z) {qt}".format(
A=rep[0], thetaX=rep[1], thetaY=rep[2], thetaZ=rep[3], qt=self.qtype)
elif self.representation == "spherical":
rep = self.txyz_2_representation("spherical")
string = "({t} t, {R} R, {theta} θ, {phi} φ) {qt}".format(t=rep[0], R=rep[1], theta=rep[2], phi=rep[3],
qt=self.qtype)
return string
def is_symbolic(self):
"""Figures out if an expression is symbolic."""
symbolic = False
if hasattr(self.a[0], "free_symbols") or hasattr(self.a[1], "free_symbols") or \
hasattr(self.a[2], "free_symbols") or hasattr(self.a[3], "free_symbols"):
symbolic = True
return symbolic
def txyz_2_representation(self, representation):
"""Converts Cartesian txyz into an array of 4 values in a different representation."""
symbolic = self.is_symbolic()
if representation == "":
rep = [self.a[0], self.a[1], self.a[2], self.a[3]]
elif representation == "polar":
amplitude = (self.a[0] ** 2 + self.a[1] ** 2 + self.a[2] ** 2 + self.a[3] ** 2) ** (1/2)
abs_v = self.abs_of_vector().a[0]
if symbolic:
theta = sp.atan2(abs_v, self.a[0])
else:
theta = math.atan2(abs_v, self.a[0])
if abs_v == 0:
thetaX, thetaY, thetaZ = 0, 0, 0
else:
thetaX = theta * self.a[1] / abs_v
thetaY = theta * self.a[2] / abs_v
thetaZ = theta * self.a[3] / abs_v
rep = [amplitude, thetaX, thetaY, thetaZ]
elif representation == "spherical":
t = self.a[0]
R = (self.a[1] ** 2 + self.a[2] **2 + self.a[3] **2) ** (1/2)
if R == 0:
theta = 0
else:
if symbolic:
theta = sp.acos(self.a[3] / R)
else:
theta = math.acos(self.a[3] / R)
if symbolic:
phi = sp.atan2(self.a[2], self.a[1])
else:
phi = math.atan2(self.a[2], self.a[1])
rep = [t, R, theta, phi]
else:
print("Oops, don't know representation: ", representation)
return rep
def representation_2_txyz(self, representation):
"""Convert from a representation to Cartesian txyz."""
symbolic = False
if hasattr(self.a[0], "free_symbols") or hasattr(self.a[1], "free_symbols") or \
hasattr(self.a[2], "free_symbols") or hasattr(self.a[3], "free_symbols"):
symbolic = True
if representation == "":
t, x, y, z = self.a[0], self.a[1], self.a[2], self.a[3]
elif representation == "polar":
amplitude, thetaX, thetaY, thetaZ = self.a[0], self.a[1], self.a[2], self.a[3]
theta = (thetaX ** 2 + thetaY ** 2 + thetaZ ** 2) ** (1/2)
if theta == 0:
t = amplitude
x, y, z = 0, 0, 0
else:
if symbolic:
t = amplitude * sp.cos(theta)
x = self.a[1] / theta * amplitude * sp.sin(theta)
y = self.a[2] / theta * amplitude * sp.sin(theta)
z = self.a[3] / theta * amplitude * sp.sin(theta)
else:
t = amplitude * math.cos(theta)
x = self.a[1] / theta * amplitude * math.sin(theta)
y = self.a[2] / theta * amplitude * math.sin(theta)
z = self.a[3] / theta * amplitude * math.sin(theta)
elif representation == "spherical":
t, R, theta, phi = self.a[0], self.a[1], self.a[2], self.a[3]
if symbolic:
x = R * sp.sin(theta) * sp.cos(phi)
y = R * sp.sin(theta) * sp.sin(phi)
z = R * sp.cos(theta)
else:
x = R * math.sin(theta) * math.cos(phi)
y = R * math.sin(theta) * math.sin(phi)
z = R * math.cos(theta)
else:
print("Oops, don't know representation: ", representation)
txyz = [t, x, y, z]
return txyz
def check_representations(self, q1):
"""If they are the same, report true. If not, kick out an exception. Don't add apples to oranges."""
if self.representation == q1.representation:
return True
else:
raise Exception("Oops, 2 quaternions have different representations: {}, {}".format(self.representation, q1.representation))
return False
def display_q(self):
"""display each terms in a pretty way."""
display(self.a[0])
display(self.a[1])
display(self.a[2])
display(self.a[3])
return
def simple_q(self):
"""display each terms in a pretty way."""
self.a[0] = sp.simplify(self.a[0])
self.a[1] = sp.simplify(self.a[1])
self.a[2] = sp.simplify(self.a[2])
self.a[3] = sp.simplify(self.a[3])
return
def q_0(self, qtype="0"):
"""Return a zero quaternion."""
q0 = QHa(qtype=qtype, representation=self.representation)
return q0
def q_1(self, qtype="1"):
"""Return a multiplicative identity quaternion."""
q1 = QHa([1.0, 0.0, 0.0, 0.0], qtype=qtype, representation=self.representation)
return q1
def q_i(self, qtype="i"):
"""Return i."""
qi = QHa([0.0, 1.0, 0.0, 0.0], qtype=qtype, representation=self.representation)
return qi
def q_j(self, qtype="j"):
"""Return j."""
qj = QHa([0.0, 0.0, 1.0, 0.0], qtype=qtype, representation=self.representation)
return qj
def q_k(self, qtype="k"):
"""Return k."""
qk = QHa([0.0, 0.0, 0.0, 1.0], qtype=qtype, representation=self.representation)
return qk
def q_random(self, qtype="?"):
"""Return a random-valued quaternion."""
qr = QHa([random.random(), random.random(), random.random(), random.random()], qtype=qtype, representation=self.representation)
return qr
def dupe(self, qtype=""):
"""Return a duplicate copy, good for testing since qtypes persist"""
return QHa([self.a[0], self.a[1], self.a[2], self.a[3]], qtype=self.qtype, representation=self.representation)
def equals(self, q1):
"""Tests if two quaternions are equal."""
self.check_representations(q1)
self_t, self_x, self_y, self_z = sp.expand(self.a[0]), sp.expand(self.a[1]), sp.expand(self.a[2]), sp.expand(self.a[3])
q1_t, q1_x, q1_y, q1_z = sp.expand(q1.a[0]), sp.expand(q1.a[1]), sp.expand(q1.a[2]), sp.expand(q1.a[3])
if math.isclose(self_t, q1_t) and math.isclose(self_x, q1_x) and math.isclose(self_y, q1_y) and math.isclose(self_z, q1_z):
return True
else:
return False
def conj(self, conj_type=0, qtype="*"):
"""Three types of conjugates."""
t, x, y, z = self.a[0], self.a[1], self.a[2], self.a[3]
conj_q = QHa()
if conj_type == 0:
conj_q.a[0] = 1.0 * t
if x != 0:
conj_q.a[1] = -1.0 * x
if y != 0:
conj_q.a[2] = -1.0 * y
if z != 0:
conj_q.a[3] = -1.0 * z
if conj_type == 1:
if t != 0:
conj_q.a[0] = -1.0 * t
conj_q.a[1] = 1.0 * x
if y != 0:
conj_q.a[2] = -1.0 * y
if z != 0:
conj_q.a[3] = -1.0 * z
qtype += "1"
if conj_type == 2:
if t != 0:
conj_q.a[0] = -1 * t
if x != 0:
conj_q.a[1] = -1 * x
conj_q.a[2] = 1.0 * y
if z != 0:
conj_q.a[3] = -1 * z
qtype += "2"
conj_q.qtype = self.qtype + qtype
conj_q.representation = self.representation
return conj_q
def flip_signs(self, conj_type=0, qtype="-"):
"""Flip all the signs, just like multipying by -1."""
end_qtype = "-{}".format(self.qtype)
t, x, y, z = self.a[0], self.a[1], self.a[2], self.a[3]
flip_q = QHa(qtype=end_qtype)
if t != 0:
flip_q.a[0] = -1.0 * t
if x != 0:
flip_q.a[1] = -1.0 * x
if y != 0:
flip_q.a[2] = -1.0 * y
if z != 0:
flip_q.a[3] = -1.0 * z
flip_q.qtype = end_qtype
flip_q.representation = self.representation
return flip_q
def vahlen_conj(self, conj_type="-", qtype="vc"):
"""Three types of conjugates -'* done by Vahlen in 1901."""
t, x, y, z = self.a[0], self.a[1], self.a[2], self.a[3]
conj_q = QHa()
if conj_type == '-':
conj_q.a[0] = 1.0 * t
if x != 0:
conj_q.a[1] = -1.0 * x
if y != 0:
conj_q.a[2] = -1.0 * y
if z != 0:
conj_q.a[3] = -1.0 * z
qtype += "*-"
if conj_type == "'":
conj_q.a[0] = 1.0 * t
if x != 0:
conj_q.a[1] = -1.0 * x
if y != 0:
conj_q.a[2] = -1.0 * y
conj_q.a[3] = 1.0 * z
qtype += "*'"
if conj_type == '*':
conj_q.a[0] = 1.0 * t
conj_q.a[1] = 1.0 * x
conj_q.a[2] = 1.0 * y
if z != 0:
conj_q.a[3] = -1.0 * z
qtype += "*"
conj_q.qtype = self.qtype + qtype
conj_q.representation = self.representation
return conj_q
def _commuting_products(self, q1):
"""Returns a dictionary with the commuting products."""
s_t, s_x, s_y, s_z = self.a[0], self.a[1], self.a[2], self.a[3]
q1_t, q1_x, q1_y, q1_z = q1.a[0], q1.a[1], q1.a[2], q1.a[3]
products = {'tt': s_t * q1_t,
'xx+yy+zz': s_x * q1_x + s_y * q1_y + s_z * q1_z,
'tx+xt': s_t * q1_x + s_x * q1_t,
'ty+yt': s_t * q1_y + s_y * q1_t,
'tz+zt': s_t * q1_z + s_z * q1_t}
return products
def _anti_commuting_products(self, q1):
"""Returns a dictionary with the three anti-commuting products."""
s_x, s_y, s_z = self.a[1], self.a[2], self.a[3]
q1_x, q1_y, q1_z = q1.a[1], q1.a[2], q1.a[3]
products = {'yz-zy': s_y * q1_z - s_z * q1_y,
'zx-xz': s_z * q1_x - s_x * q1_z,
'xy-yx': s_x * q1_y - s_y * q1_x,
'zy-yz': - s_y * q1_z + s_z * q1_y,
'xz-zx': - s_z * q1_x + s_x * q1_z,
'yx-xy': - s_x * q1_y + s_y * q1_x,
}
return products
def _all_products(self, q1):
"""Returns a dictionary with all possible products."""
products = self._commuting_products(q1)
products.update(self._anti_commuting_products(q1))
return products
def square(self, qtype="^2"):
"""Square a quaternion."""
end_qtype = "{}{}".format(self.qtype, qtype)
qxq = self._commuting_products(self)
sq_q = QHa(qtype=end_qtype, representation=self.representation)
sq_q.a[0] = qxq['tt'] - qxq['xx+yy+zz']
sq_q.a[1] = qxq['tx+xt']
sq_q.a[2] = qxq['ty+yt']
sq_q.a[3] = qxq['tz+zt']
sq_q.qtype = end_qtype
return sq_q
def norm_squared(self, qtype="|| ||^2"):
"""The norm_squared of a quaternion."""
end_qtype = "||{}||^2".format(self.qtype, qtype)
qxq = self._commuting_products(self)
n_q = QHa(qtype=end_qtype)
n_q.a[0] = qxq['tt'] + qxq['xx+yy+zz']
n_q.qtype = end_qtype
n_q.representation = self.representation
return n_q
def norm_squared_of_vector(self, qtype="norm_squaredV"):
"""The norm_squared of the vector of a quaternion."""
end_qtype = "||V({})||".format(self.qtype)
qxq = self._commuting_products(self)
nv_q = QHa(qtype=end_qtype)
nv_q.a[0] = qxq['xx+yy+zz']
nv_q.qtype = end_qtype
nv_q.representation = self.representation
return nv_q
def abs_of_q(self, qtype="| |"):
"""The absolute value, the square root of the norm_squared."""
end_qtype = "|{}|".format(self.qtype)
ns = self.norm_squared()
sqrt_t = ns.a[0] ** (1/2)
ns.a[0] = sqrt_t
ns.qtype = end_qtype
ns.representation = self.representation
return ns
def abs_of_vector(self, qtype="|V()|"):
"""The absolute value of the vector, the square root of the norm_squared of the vector."""
end_qtype = "|V({})|".format(self.qtype)
av = self.norm_squared_of_vector()
sqrt_t = av.a[0] ** (1/2)
av.a[0] = sqrt_t
av.qtype = end_qtype
av.representation = self.representation
return av
def normalize(self, qtype="U"):
"""Normalize a quaternion"""
end_qtype = "{}{}".format(self.qtype, qtype)
abs_q_inv = self.abs_of_q().invert()
n_q = self.product(abs_q_inv)
n_q.qtype = end_qtype
n_q.representation = self.representation
return n_q
def add(self, q1, qtype=""):
"""Form a add given 2 quaternions."""
self.check_representations(q1)
t_1, x_1, y_1, z_1 = self.a[0], self.a[1], self.a[2], self.a[3]
t_2, x_2, y_2, z_2 = q1.a[0], q1.a[1], q1.a[2], q1.a[3]
add_q = QHa()
add_q.a[0] = t_1 + t_2
add_q.a[1] = x_1 + x_2
add_q.a[2] = y_1 + y_2
add_q.a[3] = z_1 + z_2
add_q.qtype = "{f}+{s}".format(f=self.qtype, s=q1.qtype)
add_q.representation = self.representation
return add_q
def dif(self, q1, qtype=""):
"""Form a add given 2 quaternions."""
self.check_representations(q1)
t_1, x_1, y_1, z_1 = self.a[0], self.a[1], self.a[2], self.a[3]
t_2, x_2, y_2, z_2 = q1.a[0], q1.a[1], q1.a[2], q1.a[3]
dif_q = QHa()
dif_q.a[0] = t_1 - t_2
dif_q.a[1] = x_1 - x_2
dif_q.a[2] = y_1 - y_2
dif_q.a[3] = z_1 - z_2
dif_q.qtype = "{f}-{s}".format(f=self.qtype, s=q1.qtype)
dif_q.representation = self.representation
return dif_q
def product(self, q1, kind="", reverse=False, qtype=""):
"""Form a product given 2 quaternions: standard, even, odd, and even_minus_odd. If reverse=True, reverses the order."""
self.check_representations(q1)
commuting = self._commuting_products(q1)
q_even = QHa()
q_even.a[0] = commuting['tt'] - commuting['xx+yy+zz']
q_even.a[1] = commuting['tx+xt']
q_even.a[2] = commuting['ty+yt']
q_even.a[3] = commuting['tz+zt']
qxq = self._all_products(q1)
anti_commuting = self._anti_commuting_products(q1)
q_odd = QHa()
if reverse:
q_odd.a[1] = anti_commuting['zy-yz']
q_odd.a[2] = anti_commuting['xz-zx']
q_odd.a[3] = anti_commuting['yx-xy']
else:
q_odd.a[1] = anti_commuting['yz-zy']
q_odd.a[2] = anti_commuting['zx-xz']
q_odd.a[3] = anti_commuting['xy-yx']
result = QHa()
if kind == "":
result = q_even.add(q_odd)
times_symbol = "x"
elif kind.lower() == "even":
result = q_even
times_symbol = "xE"
elif kind.lower() == "odd":
result = q_odd
times_symbol = "xO"
elif kind.lower() == "even_minus_odd":
result = q_even.dif(q_odd)
times_symbol = "xE-O"
else:
raise Exception("Four 'kind' values are known: '', 'even', 'odd', and 'even_minus_odd'.")
if reverse:
times_symbol = times_symbol.replace('x', 'xR')
result.qtype = "{f}{ts}{s}".format(f=self.qtype, ts=times_symbol, s=q1.qtype)
result.representation = self.representation
return result
def Euclidean_product(self, q1, kind="", reverse=False, qtype=""):
"""Form a product p* q given 2 quaternions, not associative."""
self.check_representations(q1)
pq = QHa()
pq = self.conj().product(q1, kind, reverse, qtype)
return pq
def invert(self, qtype="^-1"):
"""The inverse of a quaternion."""
end_qtype = "{}{}".format(self.qtype, qtype)
q_conj = self.conj()
q_norm_squared = self.norm_squared()
if q_norm_squared.a[0] == 0:
print("oops, zero on the norm_squared.")
return self.q0()
q_norm_squared_inv = QHa([1.0 / q_norm_squared.a[0], 0, 0, 0])
q_inv = q_conj.product(q_norm_squared_inv, qtype=self.qtype)
q_inv.qtype = end_qtype
q_inv.representation = self.representation
return q_inv
def divide_by(self, q1, qtype=""):
"""Divide one quaternion by another. The order matters unless one is using a norm_squared (real number)."""
self.check_representations(q1)
q1_inv = q1.invert()
q_div = self.product(q1.invert())
q_div.qtype = "{f}/{s}".format(f=self.qtype, s=q1.qtype)
q_div.representation = self.representation
return q_div
def triple_product(self, q1, q2):
"""Form a triple product given 3 quaternions."""
self.check_representations(q1)
triple = self.product(q1).product(q2)
triple.representation = self.representation
return triple
# Quaternion rotation involves a triple product: UQU∗
# where the U is a unitary quaternion (having a norm_squared of one).
def rotate(self, a_1=0, a_2=0, a_3=0, qtype="rot"):
"""Do a rotation given up to three angles."""
end_qtype = "{}{}".format(self.qtype, qtype)
u = QHa([0, a_1, a_2, a_3])
u_abs = u.abs_of_q()
u_norm_squaredalized = u.divide_by(u_abs)
q_rot = u_norm_squaredalized.triple_product(self, u_norm_squaredalized.conj())
q_rot.qtype = end_qtype
q_rot.representation = self.representation
return q_rot
# A boost also uses triple products like a rotation, but more of them.
# This is not a well-known result, but does work.
# b -> b' = h b h* + 1/2 ((hhb)* -(h*h*b)*)
# where h is of the form (cosh(a), sinh(a))
def boost(self, beta_x=0.0, beta_y=0.0, beta_z=0.0, qtype="boost"):
"""A boost along the x, y, and/or z axis."""
end_qtype = "{}{}".format(self.qtype, qtype)
boost = QHa(sr_gamma_betas(beta_x, beta_y, beta_z))
b_conj = boost.conj()
triple_1 = boost.triple_product(self, b_conj)
triple_2 = boost.triple_product(boost, self).conj()
triple_3 = b_conj.triple_product(b_conj, self).conj()
triple_23 = triple_2.dif(triple_3)
half_23 = triple_23.product(QHa([0.5, 0.0, 0.0, 0.0]))
triple_123 = triple_1.add(half_23, qtype=self.qtype)
triple_123.qtype = end_qtype
triple_123.representation = self.representation
return triple_123
# g_shift is a function based on the space-times-time invariance proposal for gravity,
# which proposes that if one changes the distance from a gravitational source, then
# squares a measurement, the observers at two different hieghts agree to their
# space-times-time values, but not the intervals.
# g_form is the form of the function, either minimal or exponential
# Minimal is what is needed to pass all weak field tests of gravity
def g_shift(self, dimensionless_g, g_form="exp", qtype="g_shift"):
"""Shift an observation based on a dimensionless GM/c^2 dR."""
end_qtype = "{}{}".format(self.qtype, qtype)
if g_form == "exp":
g_factor = sp.exp(dimensionless_g)
elif g_form == "minimal":
g_factor = 1 + 2 * dimensionless_g + 2 * dimensionless_g ** 2
else:
print("g_form not defined, should be 'exp' or 'minimal': {}".format(g_form))
return self
g_q = QHa(qtype=end_qtype)
g_q.a[0] = self.a[0] / g_factor
g_q.a[1] = self.a[1] * g_factor
g_q.a[2] = self.a[2] * g_factor
g_q.a[3] = self.a[3] * g_factor
g_q.qtype = end_qtype
g_q.representation = self.representation
return g_q
def sin(self, qtype="sin"):
"""Take the sine of a quaternion, (sin(t) cosh(|R|), cos(t) sinh(|R|) R/|R|)"""
end_qtype = "sin({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
if abs_v.a[0] == 0:
return QHa([math.sin(self.a[0]), 0, 0, 0], qtype=end_qtype)
sint = math.sin(self.a[0])
cost = math.cos(self.a[0])
sinhR = math.sinh(abs_v.a[0])
coshR = math.cosh(abs_v.a[0])
k = cost * sinhR / abs_v.a[0]
q_out = QHa(qtype=end_qtype, representation=self.representation)
q_out.a[0] = sint * coshR
q_out.a[1] = k * self.a[1]
q_out.a[2] = k * self.a[2]
q_out.a[3] = k * self.a[3]
return q_out
def cos(self, qtype="sin"):
"""Take the cosine of a quaternion, (cos(t) cosh(|R|), sin(t) sinh(|R|) R/|R|)"""
end_qtype = "cos({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
if abs_v.a[0] == 0:
return QHa([math.cos(self.a[0]), 0, 0, 0], qtype=end_qtype)
sint = math.sin(self.a[0])
cost = math.cos(self.a[0])
sinhR = math.sinh(abs_v.a[0])
coshR = math.cosh(abs_v.a[0])
k = -1 * sint * sinhR / abs_v.a[0]
q_out = QHa(qtype=end_qtype, representation=self.representation)
q_out.a[0] = cost * coshR
q_out.a[1] = k * self.a[1]
q_out.a[2] = k * self.a[2]
q_out.a[3] = k * self.a[3]
return q_out
def tan(self, qtype="tan"):
"""Take the tan of a quaternion, sin/cos"""
end_qtype = "tan({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
if abs_v.a[0] == 0:
return QHa([math.tan(self.a[0]), 0, 0, 0], qtype=end_qtype)
sinq = self.sin()
cosq = self.cos()
q_out = sinq.divide_by(cosq)
q_out.qtype = end_qtype
q_out.representation = self.representation
return q_out
def sinh(self, qtype="sinh"):
"""Take the sinh of a quaternion, (sinh(t) cos(|R|), cosh(t) sin(|R|) R/|R|)"""
end_qtype = "sinh({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
if abs_v.a[0] == 0:
return QHa([math.sinh(self.a[0]), 0, 0, 0], qtype=end_qtype)
sinht = math.sinh(self.a[0])
cosht = math.cosh(self.a[0])
sinR = math.sin(abs_v.a[0])
cosR = math.cos(abs_v.a[0])
k = cosht * sinR / abs_v.a[0]
q_out = QHa(qtype=end_qtype, representation=self.representation)
q_out.a[0] = sinht * cosR
q_out.a[1] = k * self.a[1]
q_out.a[2] = k * self.a[2]
q_out.a[3] = k * self.a[3]
return q_out
def cosh(self, qtype="cosh"):
"""Take the cosh of a quaternion, (cosh(t) cos(|R|), sinh(t) sin(|R|) R/|R|)"""
end_qtype = "cosh({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
if abs_v.a[0] == 0:
return QHa([math.cosh(self.a[0]), 0, 0, 0], qtype=end_qtype)
sinht = math.sinh(self.a[0])
cosht = math.cosh(self.a[0])
sinR = math.sin(abs_v.a[0])
cosR = math.cos(abs_v.a[0])
k = sinht * sinR / abs_v.a[0]
q_out = QHa(qtype=end_qtype, representation=self.representation)
q_out.a[0] = cosht * cosR
q_out.a[1] = k * self.a[1]
q_out.a[2] = k * self.a[2]
q_out.a[3] = k * self.a[3]
return q_out
def tanh(self, qtype="tanh"):
"""Take the tanh of a quaternion, sin/cos"""
end_qtype = "tanh({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
if abs_v.a[0] == 0:
return QHa([math.tanh(self.a[0]), 0, 0, 0], qtype=end_qtype)
sinhq = self.sinh()
coshq = self.cosh()
q_out = sinhq.divide_by(coshq)
q_out.qtype = end_qtype
q_out.representation = self.representation
return q_out
def exp(self, qtype="exp"):
"""Take the exponential of a quaternion."""
# exp(q) = (exp(t) cos(|R|, exp(t) sin(|R|) R/|R|)
end_qtype = "exp({st})".format(st=self.qtype)
abs_v = self.abs_of_vector()
et = math.exp(self.a[0])
if (abs_v.a[0] == 0):
return QHa([et, 0, 0, 0], qtype=end_qtype, representation=self.representation)
cosR = math.cos(abs_v.a[0])
sinR = math.sin(abs_v.a[0])
k = et * sinR / abs_v.a[0]
expq = QHa([et * cosR, k * self.a[1], k * self.a[2], k * self.a[3]], qtype=end_qtype, representation=self.representation)
return expq
def ln(self, qtype="ln"):
"""Take the natural log of a quaternion."""
# ln(q) = (0.5 ln t^2 + R.R, atan2(|R|, t) R/|R|)
end_qtype = "ln({st})".format(st=self.qtype)
abs_v = self.abs_of_vector()
if (abs_v.a[0] == 0):
if self.a[0] > 0:
return(QHa([math.log(self.a[0]), 0, 0, 0], qtype=end_qtype, representation=self.representation))
else:
# I don't understant this, but mathematica does the same thing.
return(QHa([math.log(-self.a[0]), math.pi, 0, 0], qtype=end_qtype, representation=self.representation))
return QHa([lt, 0, 0, 0])
t_value = 0.5 * math.log(self.a[0] * self.a[0] + abs_v.a[0] * abs_v.a[0])
k = math.atan2(abs_v.a[0], self.a[0]) / abs_v.a[0]
expq = QHa([t_value, k * self.a[1], k * self.a[2], k * self.a[3]], qtype=end_qtype)
return expq
def q_2_q(self, q1, qtype="^P"):
"""Take the natural log of a quaternion."""
# q^p = exp(ln(q) * p)
self.check_representations(q1)
end_qtype = "{st}^P".format(st=self.qtype)
q2q = self.ln().product(q1).exp()
q2q.qtype = end_qtype
q2q.representation = self.representation
return q2q
def trunc(self):
"""Truncates values."""
self.a[0] = math.trunc(self.a[0])
self.a[1] = math.trunc(self.a[1])
self.a[2] = math.trunc(self.a[2])
self.a[3] = math.trunc(self.a[3])
return self
In [7]:
class TestQHa(unittest.TestCase):
"""Class to make sure all the functions work as expected."""
Q = QHa([1.0, -2.0, -3.0, -4.0], qtype="Q")
P = QHa([0.0, 4.0, -3.0, 0.0], qtype="P")
R = QHa([3.0, 0, 0, 0])
C = QHa([2.0, 4.0, 0, 0])
def test_qt(self):
self.assertTrue(self.Q.a[0] == 1)
def test_q_0(self):
q_z = self.Q.q_0()
print("q_0: ", q_z)
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 0)
def test_q_1(self):
q_z = self.Q.q_1()
print("q_1: ", q_z)
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 0)
def test_q_i(self):
q_z = self.Q.q_i()
print("q_i: ", q_z)
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[1] == 1)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 0)
def test_q_j(self):
q_z = self.Q.q_j()
print("q_1: ", q_z)
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 1)
self.assertTrue(q_z.a[3] == 0)
def test_q_k(self):
q_z = self.Q.q_k()
print("q_k: ", q_z)
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 1)
def test_q_random(self):
q_z = self.Q.q_random()
print("q_random(): ", q_z)
self.assertTrue(q_z.a[0] >= 0 and q_z.a[0] <= 1)
self.assertTrue(q_z.a[1] >= 0 and q_z.a[1] <= 1)
self.assertTrue(q_z.a[2] >= 0 and q_z.a[2] <= 1)
self.assertTrue(q_z.a[3] >= 0 and q_z.a[3] <= 1)
def test_equals(self):
self.assertTrue(self.Q.equals(self.Q))
self.assertFalse(self.Q.equals(self.P))
def test_conj_0(self):
q_z = self.Q.conj()
print("q_conj 0: ", q_z)
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == 2)
self.assertTrue(q_z.a[2] == 3)
self.assertTrue(q_z.a[3] == 4)
def test_conj_1(self):
q_z = self.Q.conj(1)
print("q_conj 1: ", q_z)
self.assertTrue(q_z.a[0] == -1)
self.assertTrue(q_z.a[1] == -2)
self.assertTrue(q_z.a[2] == 3)
self.assertTrue(q_z.a[3] == 4)
def test_conj_2(self):
q_z = self.Q.conj(2)
print("q_conj 2: ", q_z)
self.assertTrue(q_z.a[0] == -1)
self.assertTrue(q_z.a[1] == 2)
self.assertTrue(q_z.a[2] == -3)
self.assertTrue(q_z.a[3] == 4)
def test_vahlen_conj_minus(self):
q_z = self.Q.vahlen_conj()
print("q_vahlen_conj -: ", q_z)
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == 2)
self.assertTrue(q_z.a[2] == 3)
self.assertTrue(q_z.a[3] == 4)
def test_vahlen_conj_star(self):
q_z = self.Q.vahlen_conj('*')
print("q_vahlen_conj *: ", q_z)
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == -2)
self.assertTrue(q_z.a[2] == -3)
self.assertTrue(q_z.a[3] == 4)
def test_vahlen_conj_prime(self):
q_z = self.Q.vahlen_conj("'")
print("q_vahlen_conj ': ", q_z)
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == 2)
self.assertTrue(q_z.a[2] == 3)
self.assertTrue(q_z.a[3] == -4)
def test_square(self):
q_z = self.Q.square()
print("square: ", q_z)
self.assertTrue(q_z.a[0] == -28)
self.assertTrue(q_z.a[1] == -4)
self.assertTrue(q_z.a[2] == -6)
self.assertTrue(q_z.a[3] == -8)
def test_norm_squared(self):
q_z = self.Q.norm_squared()
print("norm_squared: ", q_z)
self.assertTrue(q_z.a[0] == 30)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 0)
def test_norm_squared_of_vector(self):
q_z = self.Q.norm_squared_of_vector()
print("norm_squared_of_vector: ", q_z)
self.assertTrue(q_z.a[0] == 29)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 0)
def test_abs_of_q(self):
q_z = self.P.abs_of_q()
print("abs_of_q: ", q_z)
self.assertTrue(q_z.a[0] == 5.0)
self.assertTrue(q_z.a[1] == 0.0)
self.assertTrue(q_z.a[2] == 0.0)
self.assertTrue(q_z.a[3] == 0.0)
def test_abs_of_vector(self):
q_z = self.P.abs_of_vector()
print("abs_of_vector: ", q_z)
self.assertTrue(q_z.a[0] == 5)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 0)
def test_normalize(self):
q_z = self.P.normalize()
print("q_normalized: ", q_z)
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[1] == 0.8)
self.assertAlmostEqual(q_z.a[2], -0.6)
self.assertTrue(q_z.a[3] == 0)
def test_add(self):
q_z = self.Q.add(self.P)
print("add: ", q_z)
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == 2)
self.assertTrue(q_z.a[2] == -6)
self.assertTrue(q_z.a[3] == -4)
def test_dif(self):
q_z = self.Q.dif(self.P)
print("dif: ", q_z)
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == -6)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == -4)
def test_product(self):
q_z = self.Q.product(self.P)
print("product: ", q_z)
self.assertTrue(q_z.a[0] == -1)
self.assertTrue(q_z.a[1] == -8)
self.assertTrue(q_z.a[2] == -19)
self.assertTrue(q_z.a[3] == 18)
def test_product_even(self):
q_z = self.Q.product(self.P, kind="even")
print("product even: ", q_z)
self.assertTrue(q_z.a[0] == -1)
self.assertTrue(q_z.a[1] == 4)
self.assertTrue(q_z.a[2] == -3)
self.assertTrue(q_z.a[3] == 0)
def test_product_odd(self):
q_z = self.Q.product(self.P, kind="odd")
print("product odd: ", q_z)
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[1] == -12)
self.assertTrue(q_z.a[2] == -16)
self.assertTrue(q_z.a[3] == 18)
def test_product_reverse(self):
q1q2_rev = self.Q.product(self.P, reverse=True)
q2q1 = self.P.product(self.Q)
self.assertTrue(q1q2_rev.equals(q2q1))
def test_product_even_minus_odd(self):
q_z = self.Q.product(self.P, kind="even_minus_odd")
print("product even_minus_odd: ", q_z)
self.assertTrue(q_z.a[0] == -1)
self.assertTrue(q_z.a[1] == 16)
self.assertTrue(q_z.a[2] == 13)
self.assertTrue(q_z.a[3] == -18)
def test_Euclidean_product(self):
q_z = self.Q.Euclidean_product(self.P)
print("Euclidean product: ", q_z)
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == 16)
self.assertTrue(q_z.a[2] == 13)
self.assertTrue(q_z.a[3] == -18)
def test_invert(self):
q_z = self.P.invert()
print("invert: ", q_z)
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[1] == -0.16)
self.assertTrue(q_z.a[2] == 0.12)
self.assertTrue(q_z.a[3] == 0)
def test_divide_by(self):
q_z = self.Q.divide_by(self.Q)
print("divide_by: ", q_z)
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 0)
def test_triple_product(self):
q_z = self.Q.triple_product(self.P, self.Q)
print("triple product: ", q_z)
self.assertTrue(q_z.a[0] == -2)
self.assertTrue(q_z.a[1] == 124)
self.assertTrue(q_z.a[2] == -84)
self.assertTrue(q_z.a[3] == 8)
def test_rotate(self):
q_z = self.Q.rotate(1)
print("rotate: ", q_z)
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == -2)
self.assertTrue(q_z.a[2] == 3)
self.assertTrue(q_z.a[3] == 4)
def test_boost(self):
q1_sq = self.Q.square()
q_z = self.Q.boost(0.003)
q_z2 = q_z.square()
print("q1_sq: ".format(q1_sq))
print("boosted: ", q_z)
print("boosted squared: ".format(q_z2))
print("{}")
self.assertTrue(round(q_z2.a[0], 5) == round(q1_sq.a[0], 5))
def test_g_shift(self):
q1_sq = self.Q.square()
q_z = self.Q.g_shift(0.003)
q_z2 = q_z.square()
q_z_minimal = self.Q.g_shift(0.003, g_form="minimal")
q_z2_minimal = q_z_minimal.square()
print("q1_sq: ".format(q1_sq))
print("g_shift: ", q_z)
print("g squared: ".format(q_z2))
self.assertTrue(q_z2.a[0] != q1_sq.a[0])
self.assertTrue(q_z2.a[1] == q1_sq.a[1])
self.assertTrue(q_z2.a[2] == q1_sq.a[2])
self.assertTrue(q_z2.a[3] == q1_sq.a[3])
self.assertTrue(q_z2_minimal.a[0] != q1_sq.a[0])
self.assertTrue(q_z2_minimal.a[1] == q1_sq.a[1])
self.assertTrue(q_z2_minimal.a[2] == q1_sq.a[2])
self.assertTrue(q_z2_minimal.a[3] == q1_sq.a[3])
def test_sin(self):
self.assertTrue(QHa([0, 0, 0, 0]).sin().equals(QHa().q_0()))
self.assertTrue(self.Q.sin().equals(QHa([91.7837157840346691, -21.8864868530291758, -32.8297302795437673, -43.7729737060583517])))
self.assertTrue(self.P.sin().equals(QHa([0, 59.3625684622310033, -44.5219263466732542, 0])))
self.assertTrue(self.R.sin().equals(QHa([0.1411200080598672, 0, 0, 0])))
self.assertTrue(self.C.sin().equals(QHa([24.8313058489463785, -11.3566127112181743, 0, 0])))
def test_cos(self):
self.assertTrue(QHa([0, 0, 0, 0]).cos().equals(QHa().q_1()))
self.assertTrue(self.Q.cos().equals(QHa([58.9336461679439481, 34.0861836904655959, 51.1292755356983974, 68.1723673809311919])))
self.assertTrue(self.P.cos().equals(QHa([74.2099485247878476, 0, 0, 0])))
self.assertTrue(self.R.cos().equals(QHa([-0.9899924966004454, 0, 0, 0])))
self.assertTrue(self.C.cos().equals(QHa([-11.3642347064010600, -24.8146514856341867, 0, 0])))
def test_tan(self):
self.assertTrue(QHa([0, 0, 0, 0]).tan().equals(QHa().q_0()))
self.assertTrue(self.Q.tan().equals(QHa([0.0000382163172501, -0.3713971716439372, -0.5570957574659058, -0.7427943432878743])))
self.assertTrue(self.P.tan().equals(QHa([0, 0.7999273634100760, -0.5999455225575570, 0])))
self.assertTrue(self.R.tan().equals(QHa([-0.1425465430742778, 0, 0, 0])))
self.assertTrue(self.C.tan().equals(QHa([-0.0005079806234700, 1.0004385132020521, 0, 0])))
def test_sinh(self):
self.assertTrue(QHa([0, 0, 0, 0]).sinh().equals(QHa().q_0()))
self.assertTrue(self.Q.sinh().equals(QHa([0.7323376060463428, 0.4482074499805421, 0.6723111749708131, 0.8964148999610841])))
self.assertTrue(self.P.sinh().equals(QHa([0, -0.7671394197305108, 0.5753545647978831, 0])))
self.assertTrue(self.R.sinh().equals(QHa([10.0178749274099026, 0, 0, 0])))
self.assertTrue(self.C.sinh().equals(QHa([-2.3706741693520015, -2.8472390868488278, 0, 0])))
def test_cosh(self):
self.assertTrue(QHa([0, 0, 0, 0]).cosh().equals(QHa().q_1()))
self.assertTrue(self.Q.cosh().equals(QHa([0.9615851176369565, 0.3413521745610167, 0.5120282618415251, 0.6827043491220334])))
self.assertTrue(self.P.cosh().equals(QHa([0.2836621854632263, 0, 0, 0])))
self.assertTrue(self.R.cosh().equals(QHa([10.0676619957777653, 0, 0, 0])))
self.assertTrue(self.C.cosh().equals(QHa([-2.4591352139173837, -2.7448170067921538, 0, 0])))
def test_tanh(self):
self.assertTrue(QHa([0, 0, 0, 0]).tanh().equals(QHa().q_0()))
self.assertTrue(self.Q.tanh().equals(QHa([1.0248695360556623, 0.1022956817887642, 0.1534435226831462, 0.2045913635775283])))
self.assertTrue(self.P.tanh().equals(QHa([0, -2.7044120049972684, 2.0283090037479505, 0])))
self.assertTrue(self.R.tanh().equals(QHa([0.9950547536867305, 0, 0, 0])))
self.assertTrue(self.C.tanh().equals(QHa([1.0046823121902353, 0.0364233692474038, 0, 0])))
def test_exp(self):
self.assertTrue(QHa([0, 0, 0, 0]).exp().equals(QHa().q_1()))
self.assertTrue(self.Q.exp().equals(QHa([1.6939227236832994, 0.7895596245415588, 1.1843394368123383, 1.5791192490831176])))
self.assertTrue(self.P.exp().equals(QHa([0.2836621854632263, -0.7671394197305108, 0.5753545647978831, 0])))
self.assertTrue(self.R.exp().equals(QHa([20.0855369231876679, 0, 0, 0])))
self.assertTrue(self.C.exp().equals(QHa([-4.8298093832693851, -5.5920560936409816, 0, 0])))
def test_ln(self):
self.assertTrue(self.Q.ln().exp().equals(self.Q))
self.assertTrue(self.Q.ln().equals(QHa([1.7005986908310777, -0.5151902926640850, -0.7727854389961275, -1.0303805853281700])))
self.assertTrue(self.P.ln().equals(QHa([1.6094379124341003, 1.2566370614359172, -0.9424777960769379, 0])))
self.assertTrue(self.R.ln().equals(QHa([1.0986122886681098, 0, 0, 0])))
self.assertTrue(self.C.ln().equals(QHa([1.4978661367769954, 1.1071487177940904, 0, 0])))
def test_q_2_q(self):
self.assertTrue(self.Q.q_2_q(self.P).equals(QHa([-0.0197219653530713, -0.2613955437374326, 0.6496281248064009, -0.3265786562423951])))
suite = unittest.TestLoader().loadTestsFromModule(TestQHa())
unittest.TextTestRunner().run(suite);
In [8]:
class TestQHaRep(unittest.TestCase):
Q12 = QHa([1, 2, 0, 0])
Q1123 = QHa([1, 1, 2, 3])
Q11p = QHa([1, 1, 0, 0], representation="polar")
Q12p = QHa([1, 2, 0, 0], representation="polar")
Q12np = QHa([1, -2, 0, 0], representation="polar")
Q21p = QHa([2, 1, 0, 0], representation="polar")
Q23p = QHa([2, 3, 0, 0], representation="polar")
Q13p = QHa([1, 3, 0, 0], representation="polar")
Q5p = QHa([5, 0, 0, 0], representation="polar")
def test_txyz_2_representation(self):
qr = QHa(self.Q12.txyz_2_representation(""))
self.assertTrue(qr.equals(self.Q12))
qr = QHa(self.Q12.txyz_2_representation("polar"))
self.assertTrue(qr.equals(QHa([2.23606797749979, 1.10714871779409, 0, 0])))
qr = QHa(self.Q1123.txyz_2_representation("spherical"))
self.assertTrue(qr.equals(QHa([1.0, 3.7416573867739413, 0.640522312679424, 1.10714871779409])))
def test_representation_2_txyz(self):
qr = QHa(self.Q12.representation_2_txyz(""))
self.assertTrue(qr.equals(self.Q12))
qr = QHa(self.Q12.representation_2_txyz("polar"))
self.assertTrue(qr.equals(QHa([-0.4161468365471424, 0.9092974268256817, 0, 0])))
qr = QHa(self.Q1123.representation_2_txyz("spherical"))
self.assertTrue(qr.equals(QHa([1.0, -0.9001976297355174, 0.12832006020245673, -0.4161468365471424])))
def test_polar_products(self):
qr = self.Q11p.product(self.Q12p)
print("polar 1 1 0 0 * 1 2 0 0: ", qr)
self.assertTrue(qr.equals(self.Q13p))
qr = self.Q12p.product(self.Q21p)
print("polar 1 2 0 0 * 2 1 0 0: ", qr)
self.assertTrue(qr.equals(self.Q23p))
def test_polar_conj(self):
qr = self.Q12p.conj()
print("polar conj of {}: {}", self.Q12p, qr)
print("Q12np: ", self.Q12np)
self.assertTrue(qr.equals(self.Q12np))
suite = unittest.TestLoader().loadTestsFromModule(TestQHaRep())
unittest.TextTestRunner().run(suite);
My long term goal is to deal with quaternions on a quaternion manifold. This will have 4 pairs of doublets. Each doublet is paired with its additive inverse. Instead of using real numbers, one uses (3, 0) and (0, 2) to represent +3 and -2 respectively. Numbers such as (5, 6) are allowed. That can be "reduced" to (0, 1). My sense is that somewhere deep in the depths of relativistic quantum field theory, this will be a "good thing". For now, it is a minor pain to program.
In [9]:
class Doublet(object):
"""A pair of number that are additive inverses. It can take
ints, floats, Symbols, or strings."""
def __init__(self, numbers=None):
if numbers is None:
self.p = 0
self.n = 0
elif isinstance(numbers, (int, float)):
if numbers < 0:
self.n = -1 * numbers
self.p = 0
else:
self.p = numbers
self.n = 0
elif isinstance(numbers, sp.Symbol):
self.p = numbers
self.n = 0
elif isinstance(numbers, list):
if len(numbers) == 2:
self.p, self.n = numbers[0], numbers[1]
elif isinstance(numbers, str):
n_list = numbers.split()
if (len(n_list) == 1):
if n_list.isnumeric():
n_value = float(numbers)
if n_value < 0:
self.n = -1 * n_list[0]
self.p = 0
else:
self.p = n_list[0]
self.n = 0
else:
self.p = sp.Symbol(n_list[0])
self.n = 0
if (len(n_list) == 2):
if n_list[0].isnumeric():
self.p = float(n_list[0])
else:
self.p = sp.Symbol(n_list[0])
if n_list[1].isnumeric():
self.n = float(n_list[1])
else:
self.n = sp.Symbol(n_list[1])
else:
print ("unable to parse this Double.")
def __str__(self):
"""Customize the output."""
return "{p}p {n}n".format(p=self.p, n=self.n)
def d_add(self, d1):
"""Add a doublet to another."""
pa0, n0 = self.p, self.n
p1, n1 = d1.p, d1.n
return Doublet([pa0 + p1, n0 + n1])
def d_reduce(self):
"""If p and n are not zero, subtract """
if self.p == 0 or self.n == 0:
return Doublet([self.p, self.n])
elif self.p > self.n:
return Doublet([self.p - self.n, 0])
elif self.p < self.n:
return Doublet([0, self.n - self.p])
else:
return Doublet()
def d_additive_inverse_up_to_an_automorphism(self, n=0):
"""Creates one additive inverses up to an arbitrary positive n."""
if n == 0:
return Doublet([self.n + n, self.p + n])
else:
red = self.d_reduce()
return Doublet([red.n + n, red.p + n])
def d_dif(self, d1, n=0):
"""Take the difference by flipping and adding."""
d2 = d1.d_additive_inverse_up_to_an_automorphism(n)
return self.d_add(d2)
def d_equals(self, d1):
"""Figure out if two doublets are equal up to an equivalence relation."""
self_red = self.d_reduce()
d1_red = d1.d_reduce()
if math.isclose(self_red.p, d1_red.p) and math.isclose(self_red.n, d1_red.n):
return True
else:
return False
def Z2_product(self, d1):
"""Uset the Abelian cyclic group Z2 to form the product of 2 doublets."""
p1 = self.p * d1.p + self.n * d1.n
n1 = self.p * d1.n + self.n * d1.p
return Doublet([p1, n1])
In [10]:
class TestDoublet(unittest.TestCase):
"""Class to make sure all the functions work as expected."""
d1 = Doublet()
d2 = Doublet(2)
d3 = Doublet(-3)
d4 = Doublet([5, 3])
dstr12 = Doublet("1 2")
dstr13 = Doublet("3 2")
def test_null(self):
self.assertTrue(self.d1.p == 0)
self.assertTrue(self.d1.n == 0)
def test_2(self):
self.assertTrue(self.d2.p == 2)
self.assertTrue(self.d2.n == 0)
def test_3(self):
self.assertTrue(self.d3.p == 0)
self.assertTrue(self.d3.n == 3)
def test_str12(self):
self.assertTrue(self.dstr12.p == 1)
self.assertTrue(self.dstr12.n == 2)
def test_add(self):
d_add = self.d2.d_add(self.d3)
self.assertTrue(d_add.p == 2)
self.assertTrue(d_add.n == 3)
def test_d_additive_inverse_up_to_an_automorphism(self):
d_f = self.d2.d_additive_inverse_up_to_an_automorphism()
self.assertTrue(d_f.p == 0)
self.assertTrue(d_f.n == 2)
def test_dif(self):
d_d = self.d2.d_dif(self.d3)
self.assertTrue(d_d.p == 5)
self.assertTrue(d_d.n == 0)
def test_reduce(self):
d_add = self.d2.d_add(self.d3)
d_r = d_add.d_reduce()
self.assertTrue(d_r.p == 0)
self.assertTrue(d_r.n == 1)
def test_Z2_product(self):
Z2p = self.dstr12.Z2_product(self.dstr13)
self.assertTrue(Z2p.p == 7)
self.assertTrue(Z2p.n == 8)
def test_d_equals(self):
self.assertTrue(self.d2.d_equals(self.d4))
self.assertFalse(self.d2.d_equals(self.d1))
def test_reduced_product(self):
"""Reduce before or after, should make no difference."""
Z2p_1 = self.dstr12.Z2_product(self.dstr13)
Z2p_red = Z2p_1.d_reduce()
d_r_1 = self.dstr12.d_reduce()
d_r_2 = self.dstr13.d_reduce()
Z2p_2 = d_r_1.Z2_product(d_r_2)
self.assertTrue(Z2p_red.p == Z2p_2.p)
self.assertTrue(Z2p_red.n == Z2p_2.n)
suite = unittest.TestLoader().loadTestsFromModule(TestDoublet())
unittest.TextTestRunner().run(suite);
Repeat the exercise for arrays.
In [11]:
class Doubleta(object):
"""A pair of number that are additive inverses. It can take
ints, floats, Symbols, or strings."""
def __init__(self, numbers=None):
if numbers is None:
self.d = np.array([0.0, 0.0])
elif isinstance(numbers, (int, float)):
if numbers < 0:
self.d = np.array([0, -1 * numbers])
else:
self.d = np.array([numbers, 0])
elif isinstance(numbers, sp.Symbol):
self.d = np.array([numbers, 0])
elif isinstance(numbers, list):
if len(numbers) == 2:
self.d = np.array([numbers[0], numbers[1]])
elif isinstance(numbers, str):
n_list = numbers.split()
if (len(n_list) == 1):
if n_list.isnumeric():
n_value = float(numbers)
if n_value < 0:
self.d = np.array([0, -1 * n_list[0]])
else:
self.d = np.array([n_list[0], 0])
else:
self.d = np.array([sp.Symbol(n_list[0]), 0])
if (len(n_list) == 2):
if n_list[0].isnumeric():
self.d = np.array([float(n_list[0]), float(n_list[1])])
else:
self.d = np.array([sp.Symbol(n_list[0]), sp.Symbol(n_list[1])])
else:
print ("unable to parse this Double.")
def __str__(self):
"""Customize the output."""
return "{p}p {n}n".format(p=self.d[0], n=self.d[1])
def d_add(self, d1):
"""Add a doublet to another."""
pa0, n0 = self.d[0], self.d[1]
p1, n1 = d1.d[0], d1.d[1]
return Doubleta([pa0 + p1, n0 + n1])
def d_reduce(self):
"""If p and n are not zero, subtract """
if self.d[0] == 0 or self.d[1] == 0:
return Doubleta([self.d[0], self.d[1]])
elif self.d[0] > self.d[1]:
return Doubleta([self.d[0] - self.d[1], 0])
elif self.d[0] < self.d[1]:
return Doubleta([0, self.d[1] - self.d[0]])
else:
return Doubleta()
def d_additive_inverse_up_to_an_automorphism(self, n=0):
"""Creates one additive inverses up to an arbitrary positive n."""
if n == 0:
return Doubleta([self.d[1], self.d[0]])
else:
red = self.d_reduce()
return Doubleta([red.d[1] + n, red.d[0] + n])
def d_dif(self, d1, n=0):
"""Take the difference by flipping and adding."""
d2 = d1.d_additive_inverse_up_to_an_automorphism(n)
return self.d_add(d2)
def d_equals(self, d1):
"""See if two are equals up to an constant value."""
self_red = self.d_reduce()
d1_red = d1.d_reduce()
if math.isclose(self_red.d[0], d1_red.d[0]) and math.isclose(self_red.d[1], d1_red.d[1]):
return True
else:
return False
def Z2_product(self, d1):
"""Uset the Abelian cyclic group Z2 to form the product of 2 doublets."""
p1 = self.d[0] * d1.d[0] + self.d[1] * d1.d[1]
n1 = self.d[0] * d1.d[1] + self.d[1] * d1.d[0]
return Doubleta([p1, n1])
In [12]:
class TestDoubleta(unittest.TestCase):
"""Class to make sure all the functions work as expected."""
d1 = Doubleta()
d2 = Doubleta(2)
d3 = Doubleta(-3)
d4 = Doubleta([5, 3])
dstr12 = Doubleta("1 2")
dstr13 = Doubleta("3 2")
def test_null(self):
self.assertTrue(self.d1.d[0] == 0)
self.assertTrue(self.d1.d[1] == 0)
def test_2(self):
self.assertTrue(self.d2.d[0] == 2)
self.assertTrue(self.d2.d[1] == 0)
def test_3(self):
self.assertTrue(self.d3.d[0] == 0)
self.assertTrue(self.d3.d[1] == 3)
def test_str12(self):
self.assertTrue(self.dstr12.d[0] == 1)
self.assertTrue(self.dstr12.d[1] == 2)
def test_add(self):
d_add = self.d2.d_add(self.d3)
self.assertTrue(d_add.d[0] == 2)
self.assertTrue(d_add.d[1] == 3)
def test_d_additive_inverse_up_to_an_automorphism(self):
d_f = self.d2.d_additive_inverse_up_to_an_automorphism()
self.assertTrue(d_f.d[0] == 0)
self.assertTrue(d_f.d[1] == 2)
def test_dif(self):
d_d = self.d2.d_dif(self.d3)
self.assertTrue(d_d.d[0] == 5)
self.assertTrue(d_d.d[1] == 0)
def test_reduce(self):
d_add = self.d2.d_add(self.d3)
d_r = d_add.d_reduce()
self.assertTrue(d_r.d[0] == 0)
self.assertTrue(d_r.d[1] == 1)
def test_Z2_product(self):
Z2p = self.dstr12.Z2_product(self.dstr13)
self.assertTrue(Z2p.d[0] == 7)
self.assertTrue(Z2p.d[1] == 8)
def test_d_equals(self):
self.assertTrue(self.d2.d_equals(self.d4))
self.assertFalse(self.d2.d_equals(self.d1))
def test_reduced_product(self):
"""Reduce before or after, should make no difference."""
Z2p_1 = self.dstr12.Z2_product(self.dstr13)
Z2p_red = Z2p_1.d_reduce()
d_r_1 = self.dstr12.d_reduce()
d_r_2 = self.dstr13.d_reduce()
Z2p_2 = d_r_1.Z2_product(d_r_2)
self.assertTrue(Z2p_red.d[0] == Z2p_2.d[0])
self.assertTrue(Z2p_red.d[1] == Z2p_2.d[1])
In [13]:
suite = unittest.TestLoader().loadTestsFromModule(TestDoubleta())
unittest.TextTestRunner().run(suite);
Write a class to handle quaternions given 8 numbers.
In [14]:
class Q8(object):
"""Quaternions on a quaternion manifold or space-time numbers."""
def __init__(self, values=None, qtype="Q", representation=""):
if values is None:
self.dt, self.dx, self.dy, self.dz = Doublet(), Doublet(),Doublet(), Doublet()
elif isinstance(values, list):
if len(values) == 4:
self.dt = Doublet(values[0])
self.dx = Doublet(values[1])
self.dy = Doublet(values[2])
self.dz = Doublet(values[3])
if len(values) == 8:
self.dt = Doublet([values[0], values[1]])
self.dx = Doublet([values[2], values[3]])
self.dy = Doublet([values[4], values[5]])
self.dz = Doublet([values[6], values[7]])
self.representation = representation
if representation != "":
self.dt.p, self.dt.n, self.dx.p, self.dx.n, self.dy.p, self.dy.n, self.dz.p, self.dz.n = self.representation_2_txyz(representation)
self.qtype=qtype
def __str__(self):
"""Customize the output."""
if self.representation == "":
string = "(({tp}, {tn}), ({xp}, {xn}), ({yp}, {yn}), ({zp}, {zn})) {qt}".format(tp=self.dt.p, tn=self.dt.n,
xp=self.dx.p, xn=self.dx.n,
yp=self.dy.p, yn=self.dy.n,
zp=self.dz.p, zn=self.dz.n,
qt=self.qtype)
elif self.representation == "polar":
rep = self.txyz_2_representation("polar")
string = "(({Ap}, {An}) A, ({thetaXp}, {thetaXn}) 𝜈x, ({thetaYp}, {thetaYn}) 𝜈y, ({thetaZp}, {thetaZn}) 𝜈z) {qt}".format(
Ap=rep[0], An=rep[1],
thetaXp=rep[2], thetaXn=rep[3],
thetaYp=rep[4], thetaYn=rep[5],
thetaZp=rep[6], thetaZn=rep[7], qt=self.qtype)
elif self.representation == "spherical":
rep = self.txyz_2_representation("spherical")
string = "(({tp}, {tn}) t, ({Rp}, {Rn}) R, ({thetap}, {thetan}) θ , ({phip}, {phin}) φ) {qt}".format(
tp=rep[0], tn=rep[1],
Rp=rep[2], Rn=rep[3],
thetap=rep[4], thetan=rep[5],
phip=rep[6], phin=rep[7], qt=self.qtype)
return string
def is_symbolic(self):
"""Looks to see if a symbol is inside one of the terms."""
symbolic = False
if hasattr(self.dt.p, "free_symbols") or hasattr(self.dt.n, "free_symbols") or \
hasattr(self.dx.p, "free_symbols") or hasattr(self.dx.n, "free_symbols") or \
hasattr(self.dy.p, "free_symbols") or hasattr(self.dy.n, "free_symbols") or \
hasattr(self.dz.p, "free_symbols") or hasattr(self.dz.n, "free_symbols"):
symbolic = True
return symbolic
def txyz_2_representation(self, representation):
"""Converts Cartesian txyz into an array of 4 values in a different representation."""
symbolic = self.is_symbolic()
if representation == "":
rep = [self.dt.p, self.dt.n, self.dx.p, self.dx.n, self.dy.p, self.dy.n, self.dz.p, self.dz.n]
return rep
elif representation == "polar":
dtr = self.dt.p - self.dt.n
dxr = self.dx.p - self.dx.n
dyr = self.dy.p - self.dy.n
dzr = self.dz.p - self.dz.n
amplitude = (dtr ** 2 + dxr ** 2 + dyr **2 + dzr **2) ** (1/2)
damp = Doublet(amplitude)
abs_v = self.abs_of_vector().dt.p
if symbolic:
theta = sp.atan2(abs_v, dtr)
else:
theta = math.atan2(abs_v, dtr)
if abs_v == 0:
dthetaX, dthetaY, dthetaZ = Doublet(), Doublet(), Doublet()
else:
thetaX = dxr / abs_v * theta
thetaY = dyr / abs_v * theta
thetaZ = dzr / abs_v * theta
dthetaX = Doublet(thetaX)
dthetaY = Doublet(thetaY)
dthetaZ = Doublet(thetaZ)
rep = [damp.p, damp.n, dthetaX.p, dthetaX.n, dthetaY.p, dthetaY.n, dthetaZ.p, dthetaZ.n]
return rep
elif representation == "spherical":
dtr = self.dt.p - self.dt.n
dxr = self.dx.p - self.dx.n
dyr = self.dy.p - self.dy.n
dzr = self.dz.p - self.dz.n
dt = self.dt
R =(dxr ** 2 + dyr **2 + dzr**2) ** (1/2)
if symbolic:
theta = sp.acos(dzr / R)
phi = sp.atan2(dyr, dxr)
else:
theta = math.acos(dzr / R)
phi = math.atan2(dyr, dxr)
dR = Doublet(R)
dtheta = Doublet(theta)
dphi = Doublet(phi)
rep = [dt.p, dt.n, dR.p, dR.n, dtheta.p, dtheta.n, dphi.p, dphi.n]
return rep
else:
print("Oops, don't know representation: ", representation)
def representation_2_txyz(self, representation):
"""Convert from a representation to Cartesian txyz."""
symbolic = self.is_symbolic()
if representation == "":
dt, dx, dy, dz = self.dt, self.dx, self.dy, self.dz
elif representation == "polar":
amplitude, thetaX, thetaY, thetaZ = self.dt, self.dx, self.dy, self.dz
amp = amplitude.p - amplitude.n
thetaXr = thetaX.p - thetaX.n
thetaYr = thetaY.p - thetaY.n
thetaZr = thetaZ.p - thetaZ.n
theta = (thetaXr ** 2 + thetaYr ** 2 + thetaZr ** 2) ** (1/2)
if theta == 0:
dt = amplitude
dx, dy, dz = Doublet(), Doublet(), Doublet()
else:
if symbolic:
t = amp * sp.cos(theta)
x = thetaXr / theta * amp * sp.sin(theta)
y = thetaYr / theta * amp * sp.sin(theta)
z = thetaZr / theta * amp * sp.sin(theta)
else:
t = amp * math.cos(theta)
x = thetaXr / theta * amp * math.sin(theta)
y = thetaYr / theta * amp * math.sin(theta)
z = thetaZr / theta * amp * math.sin(theta)
dt = Doublet(t)
dx = Doublet(x)
dy = Doublet(y)
dz = Doublet(z)
elif representation == "spherical":
dt, R, theta, phi = self.dt, self.dx, self.dy, self.dz
Rr = R.p - R.n
thetar = theta.p - theta.n
phir = phi.p - phi.n
if symbolic:
x = Rr * sp.sin(thetar) * sp.cos(phir)
y = Rr * sp.sin(thetar) * sp.sin(phir)
z = Rr * sp.cos(thetar)
else:
x = Rr * math.sin(thetar) * math.cos(phir)
y = Rr * math.sin(thetar) * math.sin(phir)
z = Rr * math.cos(thetar)
dx = Doublet(x)
dy = Doublet(y)
dz = Doublet(z)
else:
print("Oops, don't know representation: ", representation)
txyz = [dt.p, dt.n, dx.p, dx.n, dy.p, dy.n, dz.p, dz.n]
return txyz
def check_representations(self, q1):
"""If they are the same, report true. If not, kick out an exception. Don't add apples to oranges."""
if self.representation == q1.representation:
return True
else:
raise Exception("Oops, 2 quaternions have different representations: {}, {}".format(self.representation, q1.representation))
return False
def q4(self):
"""Return a 4 element array."""
return [self.dt.p - self.dt.n, self.dx.p - self.dx.n, self.dy.p - self.dy.n, self.dz.p - self.dz.n]
def q_0(self, qtype="0"):
"""Return a zero quaternion."""
return Q8(qtype=qtype, representation=self.representation)
def q_1(self, qtype="1"):
"""Return a multiplicative identity quaternion."""
return Q8([1, 0, 0, 0], qtype=qtype, representation=self.representation)
def q_i(self, qtype="i"):
"""Return i."""
return Q8([0, 1, 0, 0], qtype=qtype, representation=self.representation)
def q_j(self, qtype="j"):
"""Return j."""
return Q8([0, 0, 1, 0], qtype=qtype, representation=self.representation)
def q_k(self, qtype="k"):
"""Return k."""
return Q8([0, 0, 0, 1], qtype=qtype, representation=self.representation)
def q_random(self, qtype="?"):
"""Return a random-valued quaternion."""
return Q8([random.random(), random.random(), random.random(), random.random()], qtype=qtype, representation=self.representation)
def equals(self, q1):
"""Tests if two quaternions are equal."""
if self.dt.d_equals(q1.dt) and self.dx.d_equals(q1.dx) and \
self.dy.d_equals(q1.dy) and self.dz.d_equals(q1.dz):
return True
else:
return False
def conj(self, conj_type=0, qtype="*"):
"""Three types of conjugates."""
end_qtype = "{st}{qt}".format(st=self.qtype, qt=qtype)
conj_q = Q8()
if conj_type == 0:
conj_q.dt = self.dt
conj_q.dx = self.dx.d_additive_inverse_up_to_an_automorphism()
conj_q.dy = self.dy.d_additive_inverse_up_to_an_automorphism()
conj_q.dz = self.dz.d_additive_inverse_up_to_an_automorphism()
if conj_type == 1:
conj_q.dt = self.dt.d_additive_inverse_up_to_an_automorphism()
conj_q.dx = self.dx
conj_q.dy = self.dy.d_additive_inverse_up_to_an_automorphism()
conj_q.dz = self.dz.d_additive_inverse_up_to_an_automorphism()
end_qtype += "1"
if conj_type == 2:
conj_q.dt = self.dt.d_additive_inverse_up_to_an_automorphism()
conj_q.dx = self.dx.d_additive_inverse_up_to_an_automorphism()
conj_q.dy = self.dy
conj_q.dz = self.dz.d_additive_inverse_up_to_an_automorphism()
end_qtype += "2"
conj_q.qtype = end_qtype
conj_q.representation = self.representation
return conj_q
def vahlen_conj(self, conj_type="-", qtype="vc"):
"""Three types of conjugates -'* done by Vahlen in 1901."""
end_qtype = "{st}{qt}".format(st=self.qtype, qt=qtype)
conj_q = Q8()
if conj_type == "-":
conj_q.dt = self.dt
conj_q.dx = self.dx.d_additive_inverse_up_to_an_automorphism()
conj_q.dy = self.dy.d_additive_inverse_up_to_an_automorphism()
conj_q.dz = self.dz.d_additive_inverse_up_to_an_automorphism()
end_qtype += "-"
if conj_type == "'":
conj_q.dt = self.dt
conj_q.dx = self.dx.d_additive_inverse_up_to_an_automorphism()
conj_q.dy = self.dy.d_additive_inverse_up_to_an_automorphism()
conj_q.dz = self.dz
end_qtype += "'"
if conj_type == "*":
conj_q.dt = self.dt
conj_q.dx = self.dx
conj_q.dy = self.dy
conj_q.dz = self.dz.d_additive_inverse_up_to_an_automorphism()
end_qtype += "*"
conj_q.qtype = end_qtype
conj_q.representation = self.representation
return conj_q
def flip_signs(self, qtype=""):
"""Flip all the signs, just like multipying by -1."""
end_qtype = "-{}".format(self.qtype)
dt, dx, dy, dz = self.dt, self.dx, self.dy, self.dz
flip_q = Q8(qtype=end_qtype)
flip_q.dt.p = dt.n
flip_q.dt.n = dt.p
flip_q.dx.p = dx.n
flip_q.dx.n = dx.p
flip_q.dy.p = dy.n
flip_q.dy.n = dy.p
flip_q.dz.p = dz.n
flip_q.dz.n = dz.p
flip_q.qtype = end_qtype
flip_q.representation = self.representation
return flip_q
def _commuting_products(self, q1):
"""Returns a dictionary with the commuting products."""
products = {'tt': self.dt.Z2_product(q1.dt),
'xx+yy+zz': self.dx.Z2_product(q1.dx).d_add(self.dy.Z2_product(q1.dy)).d_add(self.dz.Z2_product(q1.dz)),
'tx+xt': self.dt.Z2_product(q1.dx).d_add(self.dx.Z2_product(q1.dt)),
'ty+yt': self.dt.Z2_product(q1.dy).d_add(self.dy.Z2_product(q1.dt)),
'tz+zt': self.dt.Z2_product(q1.dz).d_add(self.dz.Z2_product(q1.dt))}
return products
def _anti_commuting_products(self, q1):
"""Returns a dictionary with the three anti-commuting products."""
products = {'yz-zy': self.dy.Z2_product(q1.dz).d_dif(self.dz.Z2_product(q1.dy)),
'zx-xz': self.dz.Z2_product(q1.dx).d_dif(self.dx.Z2_product(q1.dz)),
'xy-yx': self.dx.Z2_product(q1.dy).d_dif(self.dy.Z2_product(q1.dx)),
'zy-yz': self.dz.Z2_product(q1.dy).d_dif(self.dy.Z2_product(q1.dz)),
'xz-zx': self.dx.Z2_product(q1.dz).d_dif(self.dz.Z2_product(q1.dx)),
'yx-xy': self.dy.Z2_product(q1.dx).d_dif(self.dx.Z2_product(q1.dy))
}
return products
def _all_products(self, q1):
"""Returns a dictionary with all possible products."""
products = self._commuting_products(q1)
products.update(self.anti_commuting_products(q1))
return products
def square(self, qtype="^2"):
"""Square a quaternion."""
end_qtype = "{st}{qt}".format(st=self.qtype, qt=qtype)
qxq = self._commuting_products(self)
sq = Q8(qtype=end_qtype, representation=self.representation)
sq.dt = qxq['tt'].d_dif(qxq['xx+yy+zz'])
sq.dx = qxq['tx+xt']
sq.dy = qxq['ty+yt']
sq.dz = qxq['tz+zt']
return sq
def reduce(self, qtype="reduced"):
"""Put all doublets into the reduced form so one of each pair is zero."""
end_qtype = "{st}-{qt}".format(st=self.qtype, qt=qtype)
q_red = Q8(qtype=end_qtype)
q_red.dt = self.dt.d_reduce()
q_red.dx = self.dx.d_reduce()
q_red.dy = self.dy.d_reduce()
q_red.dz = self.dz.d_reduce()
q_red.representation = self.representation
return q_red
def norm_squared(self, qtype="|| ||^2"):
"""The norm_squared of a quaternion."""
end_qtype = "||{st}||^2".format(st=self.qtype)
qxq = self._commuting_products(self)
n_q = Q8()
n_q.dt = qxq['tt'].d_add(qxq['xx+yy+zz'])
result = n_q.reduce()
result.qtype = end_qtype
result.representation = self.representation
return result
def norm_squared_of_vector(self, qtype="V(|| ||)^2"):
"""The norm_squared of the vector of a quaternion."""
end_qtype = "||{st}||^2".format(st=self.qtype)
qxq = self._commuting_products(self)
nv_q = Q8()
nv_q.dt = qxq['xx+yy+zz']
result = nv_q.reduce()
result.qtype = end_qtype
result.representation = self.representation
return result
def abs_of_q(self, qtype="| |"):
"""The absolute value, the square root of the norm_squared."""
end_qtype = "|{st}|".format(st=self.qtype, qt=qtype)
a = self.norm_squared()
sqrt_t = a.dt.p ** (1/2)
a.dt.p = sqrt_t
a.qtype = end_qtype
a.representation = self.representation
return a
def abs_of_vector(self, qtype="|V|"):
"""The absolute value of the vector, the square root of the norm_squared of the vector."""
end_qtype = "|{st}|".format(st=self.qtype, qt=qtype)
av = self.norm_squared_of_vector()
sqrt_t = av.dt.p ** (1/2)
av.dt.p = sqrt_t
av.qtype = end_qtype
av.representation = self.representation
return av
def normalize(self, qtype="U"):
"""Normalize a quaternion"""
end_qtype = "{st}U".format(st=self.qtype)
abs_q_inv = self.abs_of_q().invert()
n_q = self.product(abs_q_inv)
n_q.qtype = end_qtype
n_q.representation = self.representation
return n_q
def add(self, q1, qtype=""):
"""Form a add given 2 quaternions."""
self.check_representations(q1)
end_qtype = "{f}+{s}".format(f=self.qtype, s=q1.qtype)
add_q = Q8(qtype=end_qtype, representation=self.representation)
add_q.dt = self.dt.d_add(q1.dt)
add_q.dx = self.dx.d_add(q1.dx)
add_q.dy = self.dy.d_add(q1.dy)
add_q.dz = self.dz.d_add(q1.dz)
return add_q
def dif(self, q1, qtype=""):
"""Form a add given 2 quaternions."""
self.check_representations(q1)
end_qtype = "{f}-{s}".format(f=self.qtype, s=q1.qtype)
dif_q = Q8(qtype=end_qtype, representation=self.representation)
dif_q.dt = self.dt.d_dif(q1.dt)
dif_q.dx = self.dx.d_dif(q1.dx)
dif_q.dy = self.dy.d_dif(q1.dy)
dif_q.dz = self.dz.d_dif(q1.dz)
return dif_q
def product(self, q1, kind="", reverse=False, qtype=""):
"""Form a product given 2 quaternions: standard, even, odd, and even_minus_odd."""
self.check_representations(q1)
commuting = self._commuting_products(q1)
q_even = Q8()
q_even.dt = commuting['tt'].d_dif(commuting['xx+yy+zz'])
q_even.dx = commuting['tx+xt']
q_even.dy = commuting['ty+yt']
q_even.dz = commuting['tz+zt']
anti_commuting = self._anti_commuting_products(q1)
q_odd = Q8()
if reverse:
q_odd.dx = anti_commuting['zy-yz']
q_odd.dy = anti_commuting['xz-zx']
q_odd.dz = anti_commuting['yx-xy']
else:
q_odd.dx = anti_commuting['yz-zy']
q_odd.dy = anti_commuting['zx-xz']
q_odd.dz = anti_commuting['xy-yx']
result = Q8(representation=self.representation)
if kind == "":
result = q_even.add(q_odd)
times_symbol = "x"
elif kind.lower() == "even":
result = q_even
times_symbol = "xE"
elif kind.lower() == "odd":
result = q_odd
times_symbol = "xO"
elif kind.lower() == "even_minus_odd":
result = q_even.dif(q_odd)
times_symbol = "xE-O"
else:
raise Exception("Fouf 'kind' values are known: '', 'even', 'odd', and 'even_minus_odd'")
if reverse:
times_symbol = times_symbol.replace('x', 'xR')
if qtype:
result.qtype = qtype
else:
result.qtype = "{f}{ts}{s}".format(f=self.qtype, ts=times_symbol, s=q1.qtype)
return result
def Euclidean_product(self, q1, kind="", reverse=False, qtype=""):
"""Form a product p* q given 2 quaternions, not associative."""
self.check_representations(q1)
pq = Q8(representation=self.representation)
pq = self.conj().product(q1, kind, reverse, qtype)
return pq
def invert(self, qtype="^-1"):
"""Invert a quaternion."""
end_qtype = "{st}{qt}".format(st=self.qtype, qt=qtype)
q_conj = self.conj()
q_norm_squared = self.norm_squared().reduce()
if q_norm_squared.dt.p == 0:
return self.q0()
q_norm_squared_inv = Q8([1.0 / q_norm_squared.dt.p, 0, 0, 0, 0, 0, 0, 0])
q_inv = q_conj.product(q_norm_squared_inv, qtype=self.qtype)
q_inv.qtype = end_qtype
q_inv.representation = self.representation
return q_inv
def divide_by(self, q1, qtype=""):
"""Divide one quaternion by another. The order matters unless one is using a norm_squared (real number)."""
self.check_representations(q1)
end_qtype = "{f}/{s}".format(f=self.qtype, s=q1.qtype)
q_inv = q1.invert()
q_div = self.product(q_inv)
q_div.qtype = end_qtype
q_div.representation = self.representation
return q_div
def triple_product(self, q1, q2):
"""Form a triple product given 3 quaternions."""
self.check_representations(q1)
self.check_representations(q2)
triple = self.product(q1).product(q2)
triple.representation = self.representation
return triple
# Quaternion rotation involves a triple product: UQU∗
# where the U is a unitary quaternion (having a norm_squared of one).
def rotate(self, a_1p=0, a_1n=0, a_2p=0, a_2n=0, a_3p=0, a_3n=0):
"""Do a rotation given up to three angles."""
u = Q8([0, 0, a_1p, a_1n, a_2p, a_2n, a_3p, a_3n])
u_abs = u.abs_of_q()
u_norm_squaredalized = u.divide_by(u_abs)
q_rot = u_norm_squaredalized.triple_product(self, u_norm_squaredalized.conj())
q_rot.representation = self.representation
return q_rot
# A boost also uses triple products like a rotation, but more of them.
# This is not a well-known result, but does work.
def boost(self, beta_x=0, beta_y=0, beta_z=0, qtype="Boost!"):
"""A boost along the x, y, and/or z axis."""
end_qtype = "{st}{qt}".format(st=self.qtype, qt=qtype)
boost = Q8(sr_gamma_betas(beta_x, beta_y, beta_z))
b_conj = boost.conj()
triple_1 = boost.triple_product(self, b_conj)
triple_2 = boost.triple_product(boost, self).conj()
triple_3 = b_conj.triple_product(b_conj, self).conj()
triple_23 = triple_2.dif(triple_3)
half_23 = triple_23.product(Q8([0.5, 0, 0, 0, 0, 0, 0, 0]))
triple_123 = triple_1.add(half_23, qtype=self.qtype)
triple_123.qtype = end_qtype
triple_123.representation = self.representation
return triple_123
# g_shift is a function based on the space-times-time invariance proposal for gravity,
# which proposes that if one changes the distance from a gravitational source, then
# squares a measurement, the observers at two different hieghts agree to their
# space-times-time values, but not the intervals.
def g_shift(self, dimensionless_g, g_form="exp", qtype="G_shift"):
"""Shift an observation based on a dimensionless GM/c^2 dR."""
end_qtype = "{st}{qt}".format(st=self.qtype, qt=qtype)
if g_form == "exp":
g_factor = sp.exp(dimensionless_g)
if qtype == "g_shift":
qtype = "g_exp"
elif g_form == "minimal":
g_factor = 1 + 2 * dimensionless_g + 2 * dimensionless_g ** 2
if qtype == "g_shift":
qtype = "g_minimal"
else:
print("g_form not defined, should be 'exp' or 'minimal': {}".format(g_form))
return self
exp_g = sp.exp(dimensionless_g)
g_q = Q8(qtype=end_qtype, representation=self.representation)
g_q.dt = Doublet([self.dt.p / exp_g, self.dt.n / exp_g])
g_q.dx = Doublet([self.dx.p * exp_g, self.dx.n * exp_g])
g_q.dy = Doublet([self.dy.p * exp_g, self.dy.n * exp_g])
g_q.dz = Doublet([self.dz.p * exp_g, self.dz.n * exp_g])
return g_q
def sin(self, qtype="sin"):
"""Take the sine of a quaternion, (sin(t) cosh(|R|), cos(t) sinh(|R|) R/|R|)"""
end_qtype = "sin({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
red_t = self.dt.d_reduce()
if red_t.p == 0 and red_t.n != 0:
if abs_v.dt.p == 0:
return Q8([-1 * math.sin(red_t.n), 0, 0, 0], qtype=end_qtype)
sint = math.sin(-1 * red_t.n)
cost = math.cos(-1 * red_t.n)
else:
if abs_v.dt.p == 0:
return Q8([math.sin(red_t.p), 0, 0, 0], qtype=end_qtype)
sint = math.sin(red_t.p)
cost = math.cos(red_t.p)
sinhR = math.sinh(abs_v.dt.p)
coshR = math.cosh(abs_v.dt.p)
k = cost * sinhR / abs_v.dt.p
q_out = Q8(qtype=end_qtype, representation=self.representation)
q_out.dt = Doublet(sint * coshR)
q_out.dx = Doublet(k * (self.dx.p - self.dx.n))
q_out.dy = Doublet(k * (self.dy.p - self.dy.n))
q_out.dz = Doublet(k * (self.dz.p - self.dz.n))
return q_out
def cos(self, qtype="cos"):
"""Take the cosine of a quaternion, (cos(t) cosh(|R|), sin(t) sinh(|R|) R/|R|)"""
end_qtype = "cos({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
red_t = self.dt.d_reduce()
if red_t.p == 0 and red_t.n != 0:
if abs_v.dt.p == 0:
return Q8([math.cos(-1 * red_t.n), 0, 0, 0], qtype=end_qtype)
sint = math.sin(-1 * red_t.n)
cost = math.cos(-1 * red_t.n)
else:
if abs_v.dt.p == 0:
return Q8([math.cos(red_t.p), 0, 0, 0], qtype=end_qtype)
sint = math.sin(red_t.p)
cost = math.cos(red_t.p)
sinhR = math.sinh(abs_v.dt.p)
coshR = math.cosh(abs_v.dt.p)
k = -1 * sint * sinhR / abs_v.dt.p
q_out = Q8(qtype=end_qtype, representation=self.representation)
q_out.dt = Doublet(cost * coshR)
q_out.dx = Doublet(k * (self.dx.p - self.dx.n))
q_out.dy = Doublet(k * (self.dy.p - self.dy.n))
q_out.dz = Doublet(k * (self.dz.p - self.dz.n))
return q_out
def tan(self, qtype="sin"):
"""Take the tan of a quaternion, sin/cos"""
end_qtype = "tan({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
red_t = self.dt.d_reduce()
if red_t.p == 0 and red_t.n != 0:
if abs_v.dt == 0:
return Q8([math.tan(-1 * red_t.n), 0, 0, 0], qtype=end_qtype)
else:
if abs_v.dt.p == 0:
return Q8([math.tan(red_t.p), 0, 0, 0], qtype=end_qtype)
sinq = self.sin()
cosq = self.cos()
q_out = sinq.divide_by(cosq)
q_out.qtype = end_qtype
q_out.representation = self.representation
return q_out
def sinh(self, qtype="sin"):
"""Take the sinh of a quaternion, (sinh(t) cos(|R|), cosh(t) sin(|R|) R/|R|)"""
end_qtype = "sinh({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
red_t = self.dt.d_reduce()
if red_t.p == 0 and red_t.n != 0:
if abs_v.dt.p == 0:
return Q8([math.sinh(-1 * red_t.n), 0, 0, 0], qtype=end_qtype)
sinht = math.sinh(-1 * red_t.n)
cosht = math.cosh(-1 * red_t.n)
else:
if abs_v.dt.p == 0:
return Q8([math.sinh(red_t.p), 0, 0, 0], qtype=end_qtype)
sinht = math.sinh(red_t.p)
cosht = math.cosh(red_t.p)
sinR = math.sin(abs_v.dt.p)
cosR = math.cos(abs_v.dt.p)
k = cosht * sinR / abs_v.dt.p
q_out = Q8(qtype=end_qtype, representation=self.representation)
q_out.dt = Doublet(sinht * cosR)
q_out.dx = Doublet(k * (self.dx.p - self.dx.n))
q_out.dy = Doublet(k * (self.dy.p - self.dy.n))
q_out.dz = Doublet(k * (self.dz.p - self.dz.n))
return q_out
def cosh(self, qtype="cosh"):
"""Take the cosh of a quaternion, (cosh(t) cos(|R|), sinh(t) sin(|R|) R/|R|)"""
end_qtype = "cosh({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
red_t = self.dt.d_reduce()
if red_t.p == 0 and red_t.n != 0:
if abs_v.dt.p == 0:
return Q8([math.cosh(-1 * red_t.n), 0, 0, 0], qtype=end_qtype)
sinht = math.sinh(-1 * red_t.n)
cosht = math.cosh(-1 * red_t.n)
else:
if abs_v.dt.p == 0:
return Q8([math.cosh(red_t.p), 0, 0, 0], qtype=end_qtype)
sinht = math.sinh(red_t.p)
cosht = math.cosh(red_t.p)
sinR = math.sin(abs_v.dt.p)
cosR = math.cos(abs_v.dt.p)
k = sinht * sinR / abs_v.dt.p
q_out = Q8(qtype=end_qtype, representation=self.representation)
q_out.dt = Doublet(cosht * cosR)
q_out.dx = Doublet(k * (self.dx.p - self.dx.n))
q_out.dy = Doublet(k * (self.dy.p - self.dy.n))
q_out.dz = Doublet(k * (self.dz.p - self.dz.n))
q_out.qtype = end_qtype
return q_out
def tanh(self, qtype="sin"):
"""Take the tanh of a quaternion, sin/cos"""
end_qtype = "tanh({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
red_t = self.dt.d_reduce()
if abs_v.dt.p == 0:
if red_t.p == 0 and red_t.n != 0:
return Q8([-1 * math.tanh(self.dt.n), 0, 0, 0], qtype=end_qtype)
else:
return Q8([math.tanh(self.dt.p), 0, 0, 0], qtype=end_qtype)
sinhq = self.sinh()
coshq = self.cosh()
q_out = sinhq.divide_by(coshq)
q_out.qtype = end_qtype
q_out.representation = self.representation
return q_out
def exp(self, qtype="exp"):
"""Take the exponential of a quaternion."""
# exp(q) = (exp(t) cos(|R|, exp(t) sin(|R|) R/|R|)
end_qtype = "exp({st})".format(st=self.qtype)
abs_v = self.abs_of_vector()
red_t = self.dt.d_reduce()
if red_t.p == 0 and red_t.n != 0:
et = math.exp(-1 * red_t.n)
if (abs_v.dt.p == 0):
return Q8([et, 0, 0, 0], qtype=end_qtype)
cosR = math.cos(abs_v.dt.p)
sinR = math.sin(abs_v.dt.p)
else:
et = math.exp(red_t.p)
if (abs_v.dt.p == 0):
return Q8([et, 0, 0, 0], qtype=end_qtype)
cosR = math.cos(abs_v.dt.p)
sinR = math.sin(abs_v.dt.p)
k = et * sinR / abs_v.dt.p
expq = Q8(qtype=end_qtype, representation=self.representation)
expq.dt = Doublet(et * cosR)
expq.dx = Doublet(k * (self.dx.p - self.dx.n))
expq.dy = Doublet(k * (self.dy.p - self.dy.n))
expq.dz = Doublet(k * (self.dz.p - self.dz.n))
return expq
def ln(self, qtype="ln"):
"""Take the natural log of a quaternion."""
# ln(q) = (0.5 ln t^2 + R.R, atan2(|R|, t) R/|R|)
end_qtype = "ln({st})".format(st=self.qtype)
abs_v = self.abs_of_vector()
red_t = self.dt.d_reduce()
if red_t.p == 0 and red_t.n != 0:
if (abs_v.dt.p == 0):
# I don't understant this, but mathematica does the same thing, but it looks wrong to me.
return(Q8([math.log(-self.dt.n), math.pi, 0, 0], qtype=end_qtype))
t_value = 0.5 * math.log(red_t.n * red_t.n + abs_v.dt.p * abs_v.dt.p)
k = math.atan2(abs_v.dt.p, red_t.n) / abs_v.dt.p
else:
if (abs_v.dt.p == 0):
return(Q8([math.log(self.dt.p), 0, 0, 0], qtype=end_qtype))
t_value = 0.5 * math.log(red_t.p * red_t.p + abs_v.dt.p * abs_v.dt.p)
k = math.atan2(abs_v.dt.p, red_t.p) / abs_v.dt.p
lnq = Q8(qtype=end_qtype, representation=self.representation)
lnq.dt = Doublet(t_value)
lnq.dx = Doublet(k * (self.dx.p - self.dx.n))
lnq.dy = Doublet(k * (self.dy.p - self.dy.n))
lnq.dz = Doublet(k * (self.dz.p - self.dz.n))
return lnq
def q_2_q(self, q1, qtype="P"):
"""Take the natural log of a quaternion."""
# q^p = exp(ln(q) * p)
self.check_representations(q1)
end_qtype = "{st}^P".format(st=self.qtype)
q2q = self.ln().product(q1).reduce().exp()
q2q.qtype = end_qtype
q2q.representation = self.representation
return q2q
def trunc(self):
"""Truncates values."""
self.dt = math.trunc(self.dt)
self.dx = math.trunc(self.dx)
self.dy = math.trunc(self.dy)
self.dz = math.trunc(self.dz)
return self
In [15]:
class TestQ8(unittest.TestCase):
"""Class to make sure all the functions work as expected."""
Q = Q8([1, 0, 0, 2, 0, 3, 0, 4])
P = Q8([0, 0, 4, 0, 0, 3, 0, 0])
R = Q8([3, 0, 0, 0, 0, 0, 0, 0])
C = Q8([2, 0, 4, 0, 0, 0, 0, 0])
q_big = Q8([1, 2, 3, 4, 5, 6, 7, 8])
def test_qt(self):
self.assertTrue(self.Q.dt.p == 1)
def test_q_0(self):
q_z = self.Q.q_0()
print("q_0: {}", q_z)
self.assertTrue(q_z.dt.p == 0)
self.assertTrue(q_z.dx.p == 0)
self.assertTrue(q_z.dy.n == 0)
self.assertTrue(q_z.dz.p == 0)
def test_q_1(self):
q_z = self.Q.q_1()
print("q_1: {}", q_z)
self.assertTrue(q_z.dt.p == 1)
self.assertTrue(q_z.dx.p == 0)
self.assertTrue(q_z.dy.p == 0)
self.assertTrue(q_z.dz.p == 0)
def test_q_i(self):
q_z = self.Q.q_i()
print("q_i: {}", q_z)
self.assertTrue(q_z.dt.p == 0)
self.assertTrue(q_z.dx.p == 1)
self.assertTrue(q_z.dy.p == 0)
self.assertTrue(q_z.dz.p == 0)
def test_q_j(self):
q_z = self.Q.q_j()
print("q_j: {}", q_z)
self.assertTrue(q_z.dt.p == 0)
self.assertTrue(q_z.dx.p == 0)
self.assertTrue(q_z.dy.p == 1)
self.assertTrue(q_z.dz.p == 0)
def test_q_k(self):
q_z = self.Q.q_k()
print("q_k: {}", q_z)
self.assertTrue(q_z.dt.p == 0)
self.assertTrue(q_z.dx.p == 0)
self.assertTrue(q_z.dy.p == 0)
self.assertTrue(q_z.dz.p == 1)
def test_q_random(self):
q_z = self.Q.q_random()
print("q_random():", q_z)
self.assertTrue(q_z.dt.p >= 0 and q_z.dt.p <= 1)
self.assertTrue(q_z.dx.p >= 0 and q_z.dx.p <= 1)
self.assertTrue(q_z.dy.p >= 0 and q_z.dy.p <= 1)
self.assertTrue(q_z.dz.p >= 0 and q_z.dz.p <= 1)
def test_equals(self):
self.assertTrue(self.Q.equals(self.Q))
self.assertFalse(self.Q.equals(self.P))
def test_conj_0(self):
q_z = self.Q.conj()
print("conj 0: {}", q_z)
self.assertTrue(q_z.dt.p == 1)
self.assertTrue(q_z.dx.p == 2)
self.assertTrue(q_z.dy.p == 3)
self.assertTrue(q_z.dz.p == 4)
def test_conj_1(self):
q_z = self.Q.conj(1)
print("conj 1: {}", q_z)
self.assertTrue(q_z.dt.n == 1)
self.assertTrue(q_z.dx.n == 2)
self.assertTrue(q_z.dy.p == 3)
self.assertTrue(q_z.dz.p == 4)
def test_conj_2(self):
q_z = self.Q.conj(2)
print("conj 2: {}", q_z)
self.assertTrue(q_z.dt.n == 1)
self.assertTrue(q_z.dx.p == 2)
self.assertTrue(q_z.dy.n == 3)
self.assertTrue(q_z.dz.p == 4)
def test_vahlen_conj_0(self):
q_z = self.Q.vahlen_conj()
print("vahlen conj -: {}", q_z)
self.assertTrue(q_z.dt.p == 1)
self.assertTrue(q_z.dx.p == 2)
self.assertTrue(q_z.dy.p == 3)
self.assertTrue(q_z.dz.p == 4)
def test_vahlen_conj_1(self):
q_z = self.Q.vahlen_conj("'")
print("vahlen conj ': {}", q_z)
self.assertTrue(q_z.dt.p == 1)
self.assertTrue(q_z.dx.p == 2)
self.assertTrue(q_z.dy.p == 3)
self.assertTrue(q_z.dz.n == 4)
def test_vahlen_conj_2(self):
q_z = self.Q.vahlen_conj('*')
print("vahlen conj *: {}", q_z)
self.assertTrue(q_z.dt.p == 1)
self.assertTrue(q_z.dx.n == 2)
self.assertTrue(q_z.dy.n == 3)
self.assertTrue(q_z.dz.p == 4)
def test_square(self):
q_sq = self.Q.square()
q_sq_red = q_sq.reduce()
print("square: {}".format(q_sq))
print("square reduced: {}".format(q_sq_red))
self.assertTrue(q_sq.dt.p == 1)
self.assertTrue(q_sq.dt.n == 29)
self.assertTrue(q_sq.dx.n == 4)
self.assertTrue(q_sq.dy.n == 6)
self.assertTrue(q_sq.dz.n == 8)
self.assertTrue(q_sq_red.dt.p == 0)
self.assertTrue(q_sq_red.dt.n == 28)
def test_reduce(self):
q_red = self.q_big.reduce()
print("q_big reduced: {}".format(q_red))
self.assertTrue(q_red.dt.p == 0)
self.assertTrue(q_red.dt.n == 1)
self.assertTrue(q_red.dx.p == 0)
self.assertTrue(q_red.dx.n == 1)
self.assertTrue(q_red.dy.p == 0)
self.assertTrue(q_red.dy.n == 1)
self.assertTrue(q_red.dz.p == 0)
self.assertTrue(q_red.dz.n == 1)
def test_norm_squared(self):
q_z = self.Q.norm_squared()
print("norm_squared: {}", q_z)
self.assertTrue(q_z.dt.p == 30)
self.assertTrue(q_z.dt.n == 0)
self.assertTrue(q_z.dx.p == 0)
self.assertTrue(q_z.dx.n == 0)
self.assertTrue(q_z.dy.p == 0)
self.assertTrue(q_z.dy.n == 0)
self.assertTrue(q_z.dz.p == 0)
self.assertTrue(q_z.dz.n == 0)
def test_norm_squared_of_vector(self):
q_z = self.Q.norm_squared_of_vector()
print("norm_squared_of_vector: {}", q_z)
self.assertTrue(q_z.dt.p == 29)
self.assertTrue(q_z.dt.n == 0)
self.assertTrue(q_z.dx.p == 0)
self.assertTrue(q_z.dx.n == 0)
self.assertTrue(q_z.dy.p == 0)
self.assertTrue(q_z.dy.n == 0)
self.assertTrue(q_z.dz.p == 0)
self.assertTrue(q_z.dz.n == 0)
def test_abs_of_q(self):
q_z = self.P.abs_of_q()
print("abs_of_q: {}", q_z)
self.assertTrue(q_z.dt.p == 5)
self.assertTrue(q_z.dx.p == 0)
self.assertTrue(q_z.dy.p == 0)
self.assertTrue(q_z.dz.p == 0)
self.assertTrue(q_z.dt.n == 0)
self.assertTrue(q_z.dx.n == 0)
self.assertTrue(q_z.dy.n == 0)
self.assertTrue(q_z.dz.n == 0)
def test_abs_of_vector(self):
q_z = self.P.abs_of_vector()
print("abs_of_vector: {}", q_z)
self.assertTrue(q_z.dt.p == 5)
self.assertTrue(q_z.dx.p == 0)
self.assertTrue(q_z.dy.p == 0)
self.assertTrue(q_z.dz.p == 0)
self.assertTrue(q_z.dt.n == 0)
self.assertTrue(q_z.dx.n == 0)
self.assertTrue(q_z.dy.n == 0)
self.assertTrue(q_z.dz.n == 0)
def test_normalize(self):
q_z = self.P.normalize()
print("q_normalized: {}", q_z)
self.assertTrue(q_z.dt.p == 0)
self.assertTrue(q_z.dt.n == 0)
self.assertTrue(q_z.dx.p == 0.8)
self.assertTrue(q_z.dx.n == 0)
self.assertTrue(q_z.dy.p == 0)
self.assertAlmostEqual(q_z.dy.n, 0.6)
self.assertTrue(q_z.dz.p == 0)
self.assertTrue(q_z.dz.n == 0)
def test_add(self):
q_z = self.Q.add(self.P)
print("add: {}", q_z)
self.assertTrue(q_z.dt.p == 1)
self.assertTrue(q_z.dt.n == 0)
self.assertTrue(q_z.dx.p == 4)
self.assertTrue(q_z.dx.n == 2)
self.assertTrue(q_z.dy.p == 0)
self.assertTrue(q_z.dy.n == 6)
self.assertTrue(q_z.dz.p == 0)
self.assertTrue(q_z.dz.n == 4)
def test_add_reduce(self):
q_z_red = self.Q.add(self.P).reduce()
print("add reduce: {}".format(q_z_red))
self.assertTrue(q_z_red.dt.p == 1)
self.assertTrue(q_z_red.dt.n == 0)
self.assertTrue(q_z_red.dx.p == 2)
self.assertTrue(q_z_red.dx.n == 0)
self.assertTrue(q_z_red.dy.p == 0)
self.assertTrue(q_z_red.dy.n == 6)
self.assertTrue(q_z_red.dz.p == 0)
self.assertTrue(q_z_red.dz.n == 4)
def test_dif(self):
q_z = self.Q.dif(self.P)
print("dif: {}", q_z)
self.assertTrue(q_z.dt.p == 1)
self.assertTrue(q_z.dt.n == 0)
self.assertTrue(q_z.dx.p == 0)
self.assertTrue(q_z.dx.n == 6)
self.assertTrue(q_z.dy.p == 3)
self.assertTrue(q_z.dy.n == 3)
self.assertTrue(q_z.dz.p == 0)
self.assertTrue(q_z.dz.n == 4)
def test_product(self):
q_z = self.Q.product(self.P).reduce()
print("product: {}", q_z)
self.assertTrue(q_z.dt.p == 0)
self.assertTrue(q_z.dt.n == 1)
self.assertTrue(q_z.dx.p == 0)
self.assertTrue(q_z.dx.n == 8)
self.assertTrue(q_z.dy.p == 0)
self.assertTrue(q_z.dy.n == 19)
self.assertTrue(q_z.dz.p == 18)
self.assertTrue(q_z.dz.n == 0)
def test_product_even(self):
q_z = self.Q.product(self.P, kind="even").reduce()
print("product, kind even: {}", q_z)
self.assertTrue(q_z.dt.p == 0)
self.assertTrue(q_z.dt.n == 1)
self.assertTrue(q_z.dx.p == 4)
self.assertTrue(q_z.dx.n == 0)
self.assertTrue(q_z.dy.p == 0)
self.assertTrue(q_z.dy.n == 3)
self.assertTrue(q_z.dz.p == 0)
self.assertTrue(q_z.dz.n == 0)
def test_product_odd(self):
q_z = self.Q.product(self.P, kind="odd").reduce()
print("product, kind odd: {}", q_z)
self.assertTrue(q_z.dt.p == 0)
self.assertTrue(q_z.dt.n == 0)
self.assertTrue(q_z.dx.p == 0)
self.assertTrue(q_z.dx.n == 12)
self.assertTrue(q_z.dy.p == 0)
self.assertTrue(q_z.dy.n == 16)
self.assertTrue(q_z.dz.p == 18)
self.assertTrue(q_z.dz.n == 0)
def test_product_even_minus_odd(self):
q_z = self.Q.product(self.P, kind="even_minus_odd").reduce()
print("product, kind odd: {}", q_z)
self.assertTrue(q_z.dt.p == 0)
self.assertTrue(q_z.dt.n == 1)
self.assertTrue(q_z.dx.p == 16)
self.assertTrue(q_z.dx.n == 0)
self.assertTrue(q_z.dy.p == 13)
self.assertTrue(q_z.dy.n == 0)
self.assertTrue(q_z.dz.p == 0)
self.assertTrue(q_z.dz.n == 18)
def test_product_reverse(self):
QP_rev = self.Q.product(self.P, reverse=True)
PQ = self.P.product(self.Q)
self.assertTrue(QP_rev.equals(PQ))
def test_Euclidean_product(self):
q_z = self.Q.Euclidean_product(self.P).reduce()
print("Euclidean product: {}", q_z)
self.assertTrue(q_z.dt.p == 1)
self.assertTrue(q_z.dt.n == 0)
self.assertTrue(q_z.dx.p == 16)
self.assertTrue(q_z.dx.n == 0)
self.assertTrue(q_z.dy.p == 13)
self.assertTrue(q_z.dy.n == 0)
self.assertTrue(q_z.dz.p == 0)
self.assertTrue(q_z.dz.n == 18)
def test_invert(self):
q_z = self.P.invert().reduce()
print("inverse: {}", q_z)
self.assertTrue(q_z.dt.p == 0)
self.assertTrue(q_z.dt.n == 0)
self.assertTrue(q_z.dx.p == 0)
self.assertTrue(q_z.dx.n == 0.16)
self.assertTrue(q_z.dy.p == 0.12)
self.assertTrue(q_z.dy.n == 0)
self.assertTrue(q_z.dz.p == 0)
self.assertTrue(q_z.dz.n == 0)
def test_divide_by(self):
q_z = self.Q.divide_by(self.Q).reduce()
print("inverse: {}", q_z)
self.assertTrue(q_z.dt.p == 1)
self.assertTrue(q_z.dt.n == 0)
self.assertTrue(q_z.dx.p == 0)
self.assertTrue(q_z.dx.n == 0)
self.assertTrue(q_z.dy.p == 0)
self.assertTrue(q_z.dy.n == 0)
self.assertTrue(q_z.dz.p == 0)
self.assertTrue(q_z.dz.n == 0)
def test_triple_product(self):
q_z = self.Q.triple_product(self.P, self.Q).reduce()
print("triple: {}", q_z)
self.assertTrue(q_z.dt.p == 0)
self.assertTrue(q_z.dt.n == 2)
self.assertTrue(q_z.dx.p == 124)
self.assertTrue(q_z.dx.n == 0)
self.assertTrue(q_z.dy.p == 0)
self.assertTrue(q_z.dy.n == 84)
self.assertTrue(q_z.dz.p == 8)
self.assertTrue(q_z.dz.n == 0)
def test_rotate(self):
q_z = self.Q.rotate(1).reduce()
print("rotate: {}", q_z)
self.assertTrue(q_z.dt.p == 1)
self.assertTrue(q_z.dt.n == 0)
self.assertTrue(q_z.dx.p == 0)
self.assertTrue(q_z.dx.n == 2)
self.assertTrue(q_z.dy.p == 3)
self.assertTrue(q_z.dy.n == 0)
self.assertTrue(q_z.dz.p == 4)
self.assertTrue(q_z.dz.n == 0)
def test_boost(self):
Q_sq = self.Q.square().reduce()
q_z = self.Q.boost(0.003)
q_z2 = q_z.square().reduce()
print("Q_sq: {}".format(Q_sq))
print("boosted: {}", q_z)
print("b squared: {}".format(q_z2))
self.assertTrue(round(q_z2.dt.n, 12) == round(Q_sq.dt.n, 12))
def test_g_shift(self):
Q_sq = self.Q.square().reduce()
q_z = self.Q.g_shift(0.003)
q_z2 = q_z.square().reduce()
print("Q_sq: {}".format(Q_sq))
print("g_shift: {}", q_z)
print("g squared: {}".format(q_z2))
self.assertTrue(q_z2.dt.n != Q_sq.dt.n)
self.assertTrue(q_z2.dx.p == Q_sq.dx.p)
self.assertTrue(q_z2.dx.n == Q_sq.dx.n)
self.assertTrue(q_z2.dy.p == Q_sq.dy.p)
self.assertTrue(q_z2.dy.n == Q_sq.dy.n)
self.assertTrue(q_z2.dz.p == Q_sq.dz.p)
self.assertTrue(q_z2.dz.n == Q_sq.dz.n)
def test_sin(self):
self.assertTrue(Q8([0, 0, 0, 0]).sin().reduce().equals(Q8().q_0()))
self.assertTrue(self.Q.sin().reduce().equals(Q8([91.7837157840346691, -21.8864868530291758, -32.8297302795437673, -43.7729737060583517])))
self.assertTrue(self.P.sin().reduce().equals(Q8([0, 59.3625684622310033, -44.5219263466732542, 0])))
self.assertTrue(self.R.sin().reduce().equals(Q8([0.1411200080598672, 0, 0, 0])))
self.assertTrue(self.C.sin().reduce().equals(Q8([24.8313058489463785, -11.3566127112181743, 0, 0])))
def test_cos(self):
self.assertTrue(Q8([0, 0, 0, 0]).cos().equals(Q8().q_1()))
self.assertTrue(self.Q.cos().equals(Q8([58.9336461679439481, 34.0861836904655959, 51.1292755356983974, 68.1723673809311919])))
self.assertTrue(self.P.cos().equals(Q8([74.2099485247878476, 0, 0, 0])))
self.assertTrue(self.R.cos().equals(Q8([-0.9899924966004454, 0, 0, 0])))
self.assertTrue(self.C.cos().equals(Q8([-11.3642347064010600, -24.8146514856341867, 0, 0])))
def test_tan(self):
self.assertTrue(Q8([0, 0, 0, 0]).tan().equals(Q8().q_0()))
self.assertTrue(self.Q.tan().equals(Q8([0.0000382163172501, -0.3713971716439372, -0.5570957574659058, -0.7427943432878743])))
self.assertTrue(self.P.tan().equals(Q8([0, 0.7999273634100760, -0.5999455225575570, 0])))
self.assertTrue(self.R.tan().equals(Q8([-0.1425465430742778, 0, 0, 0])))
self.assertTrue(self.C.tan().equals(Q8([-0.0005079806234700, 1.0004385132020521, 0, 0])))
def test_sinh(self):
self.assertTrue(Q8([0, 0, 0, 0]).sinh().equals(Q8().q_0()))
self.assertTrue(self.Q.sinh().equals(Q8([0.7323376060463428, 0.4482074499805421, 0.6723111749708131, 0.8964148999610841])))
self.assertTrue(self.P.sinh().equals(Q8([0, -0.7671394197305108, 0.5753545647978831, 0])))
self.assertTrue(self.R.sinh().equals(Q8([10.0178749274099026, 0, 0, 0])))
self.assertTrue(self.C.sinh().equals(Q8([-2.3706741693520015, -2.8472390868488278, 0, 0])))
def test_cosh(self):
self.assertTrue(Q8([0, 0, 0, 0]).cosh().equals(Q8().q_1()))
self.assertTrue(self.Q.cosh().equals(Q8([0.9615851176369565, 0.3413521745610167, 0.5120282618415251, 0.6827043491220334])))
self.assertTrue(self.P.cosh().equals(Q8([0.2836621854632263, 0, 0, 0])))
self.assertTrue(self.R.cosh().equals(Q8([10.0676619957777653, 0, 0, 0])))
self.assertTrue(self.C.cosh().equals(Q8([-2.4591352139173837, -2.7448170067921538, 0, 0])))
def test_tanh(self):
self.assertTrue(Q8([0, 0, 0, 0]).tanh().equals(Q8().q_0()))
self.assertTrue(self.Q.tanh().equals(Q8([1.0248695360556623, 0.1022956817887642, 0.1534435226831462, 0.2045913635775283])))
self.assertTrue(self.P.tanh().equals(Q8([0, -2.7044120049972684, 2.0283090037479505, 0])))
self.assertTrue(self.R.tanh().equals(Q8([0.9950547536867305, 0, 0, 0])))
self.assertTrue(self.C.tanh().equals(Q8([1.0046823121902353, 0.0364233692474038, 0, 0])))
def test_exp(self):
self.assertTrue(Q8([0, 0, 0, 0]).exp().equals(Q8().q_1()))
self.assertTrue(self.Q.exp().equals(Q8([1.6939227236832994, 0.7895596245415588, 1.1843394368123383, 1.5791192490831176])))
self.assertTrue(self.P.exp().equals(Q8([0.2836621854632263, -0.7671394197305108, 0.5753545647978831, 0])))
self.assertTrue(self.R.exp().equals(Q8([20.0855369231876679, 0, 0, 0])))
self.assertTrue(self.C.exp().equals(Q8([-4.8298093832693851, -5.5920560936409816, 0, 0])))
def test_ln(self):
self.assertTrue(self.Q.ln().exp().equals(self.Q))
self.assertTrue(self.Q.ln().equals(Q8([1.7005986908310777, -0.5151902926640850, -0.7727854389961275, -1.0303805853281700])))
self.assertTrue(self.P.ln().equals(Q8([1.6094379124341003, 1.2566370614359172, -0.9424777960769379, 0])))
self.assertTrue(self.R.ln().equals(Q8([1.0986122886681098, 0, 0, 0])))
self.assertTrue(self.C.ln().equals(Q8([1.4978661367769954, 1.1071487177940904, 0, 0])))
def test_q_2_q(self):
self.assertTrue(self.Q.q_2_q(self.P).equals(Q8([-0.0197219653530713, -0.2613955437374326, 0.6496281248064009, -0.3265786562423951])))
suite = unittest.TestLoader().loadTestsFromModule(TestQ8())
unittest.TextTestRunner().run(suite);
In [16]:
class TestQ8Rep(unittest.TestCase):
Q12 = Q8([1.0, 2.0, 0, 0])
Q1123 = Q8([1.0, 1.0, 2, 3])
Q11p = Q8([1.0, 1.0, 0, 0], representation="polar")
Q12p = Q8([1.0, 2.0, 0, 0], representation="polar")
Q12np = Q8([1.0, -2.0, 0, 0], representation="polar")
Q21p = Q8([2.0, 1.0, 0, 0], representation="polar")
Q23p = Q8([2.0, 3.0, 0, 0], representation="polar")
Q13p = Q8([1.0, 3.0, 0, 0], representation="polar")
Q5p = Q8([5.0, 0, 0, 0], representation="polar")
# @unittest.skip("problems implementing")
def test_txyz_2_representation(self):
qr = Q8(self.Q12.txyz_2_representation("")).reduce()
self.assertTrue(qr.equals(self.Q12))
qr = Q8(self.Q12.txyz_2_representation("polar")).reduce()
self.assertTrue(qr.equals(Q8([2.23606797749979, 1.10714871779409, 0, 0])))
qr = Q8(self.Q1123.txyz_2_representation("spherical")).reduce()
self.assertTrue(qr.equals(Q8([1.0, 3.7416573867739413, 0.640522312679424, 1.10714871779409])))
# @unittest.skip("problems implementing")
def test_representation_2_txyz(self):
qr = Q8(self.Q12.representation_2_txyz("")).reduce()
self.assertTrue(qr.equals(self.Q12))
qr = Q8(self.Q12.representation_2_txyz("polar")).reduce()
self.assertTrue(qr.equals(Q8([-0.4161468365471424, 0.9092974268256817, 0, 0])))
qr = Q8(self.Q1123.representation_2_txyz("spherical")).reduce()
self.assertTrue(qr.equals(Q8([1.0, -0.9001976297355174, 0.12832006020245673, -0.4161468365471424])))
def test_polar_products(self):
qr = self.Q11p.product(self.Q12p).reduce()
print("polar 1 1 0 0 * 1 2 0 0: ", qr)
self.assertTrue(qr.equals(self.Q13p))
qr = self.Q12p.product(self.Q21p).reduce()
print("polar 1 2 0 0 * 2 1 0 0: ", qr)
self.assertTrue(qr.equals(self.Q23p))
def test_polar_conj(self):
qr = self.Q12p.conj().reduce()
print("polar conj of 1 2 0 0: ", qr)
self.assertTrue(qr.equals(self.Q12np))
suite = unittest.TestLoader().loadTestsFromModule(TestQ8Rep())
unittest.TextTestRunner().run(suite);
In [17]:
class Q8a(Doubleta):
"""Quaternions on a quaternion manifold or space-time numbers."""
def __init__(self, values=None, qtype="Q", representation=""):
if values is None:
d_zero = Doubleta()
self.a = np.array([d_zero.d[0], d_zero.d[0], d_zero.d[0], d_zero.d[0], d_zero.d[0], d_zero.d[0], d_zero.d[0], d_zero.d[0]])
elif isinstance(values, list):
if len(values) == 4:
self.a = np.array([Doubleta(values[0]).d[0], Doubleta(values[0]).d[1],
Doubleta(values[1]).d[0], Doubleta(values[1]).d[1],
Doubleta(values[2]).d[0], Doubleta(values[2]).d[1],
Doubleta(values[3]).d[0], Doubleta(values[3]).d[1]])
if len(values) == 8:
self.a = np.array([Doubleta([values[0], values[1]]).d[0], Doubleta([values[0], values[1]]).d[1],
Doubleta([values[2], values[3]]).d[0], Doubleta([values[2], values[3]]).d[1],
Doubleta([values[4], values[5]]).d[0], Doubleta([values[4], values[5]]).d[1],
Doubleta([values[6], values[7]]).d[0], Doubleta([values[6], values[7]]).d[1]])
self.representation = representation
if representation != "":
rep = self.representation_2_txyz(representation)
self.a = np.array(rep)
self.qtype=qtype
def __str__(self):
"""Customize the output."""
if self.representation == "":
string = "(({tp}, {tn}), ({xp}, {xn}), ({yp}, {yn}), ({zp}, {zn})) {qt}".format(tp=self.a[0], tn=self.a[1],
xp=self.a[2], xn=self.a[3],
yp=self.a[4], yn=self.a[5],
zp=self.a[6], zn=self.a[7],
qt=self.qtype)
elif self.representation == "polar":
rep = self.txyz_2_representation("polar")
string = "(({Ap}, {An}) A, ({thetaXp}, {thetaXn}) 𝜈x, ({thetaYp}, {thetaYn}) 𝜈y, ({thetaZp}, {thetaZn}) 𝜈z) {qt}".format(Ap=rep[0], An=rep[1], thetaXp=rep[2], thetaXn=rep[3], thetaYp=rep[4], thetaYn=rep[5], thetaZp=rep[6], thetaZn=rep[7],
qt=self.qtype)
elif self.representation == "spherical":
rep = self.txyz_2_representation("spherical")
string = "(({tp}, {tn}) t, ({Rp}, {Rn}) R, ({thetap}, {thetan}) θ , ({phip}, {phin}) φ) {qt}".format(tp=rep[0], tn=rep[1], Rp=rep[2], Rn=rep[3], thetap=rep[4], thetan=rep[5], phip=rep[6], phin=rep[7],
qt=self.qtype)
return string
return "(({tp}, {tn}), ({xp}, {xn}), ({yp}, {yn}), ({zp}, {zn})) {qt}".format(tp=self.a[0], tn=self.a[1],
xp=self.a[2], xn=self.a[3],
yp=self.a[4], yn=self.a[5],
zp=self.a[6], zn=self.a[7],
qt=self.qtype)
return string
def is_symbolic(self):
"""Looks to see if a symbol is inside one of the terms."""
symbolic = False
for i in range(8):
if hasattr(self.a[i], "free_symbols"):
symbolic = True
return symbolic
def txyz_2_representation(self, representation):
"""Converts Cartesian txyz into an array of 4 values in a different representation."""
symbolic = self.is_symbolic()
if representation == "":
rep = [self.a[0], self.a[1], self.a[2], self.a[3], self.a[4], self.a[5], self.a[6], self.a[7]]
elif representation == "polar":
dtr = self.a[0] - self.a[1]
dxr = self.a[2] - self.a[3]
dyr = self.a[4] - self.a[5]
dzr = self.a[6] - self.a[7]
amplitude = (dtr ** 2 + dxr ** 2 + dyr **2 + dzr **2) ** (1/2)
abs_v = self.abs_of_vector().a[0]
if symbolic:
theta = sp.atan2(abs_v, dtr)
else:
theta = math.atan2(abs_v, dtr)
if abs_v == 0:
thetaX, thetaY, thetaZ = 0, 0, 0
else:
thetaX = theta * dxr / abs_v
thetaY = theta * dyr / abs_v
thetaZ = theta * dzr / abs_v
damp = Doubleta(amplitude)
dthetaX = Doubleta(thetaX)
dthetaY = Doubleta(thetaY)
dthetaZ = Doubleta(thetaZ)
rep = [damp.d[0], damp.d[1],
dthetaX.d[0], dthetaX.d[1],
dthetaY.d[0], dthetaY.d[1],
dthetaZ.d[0], dthetaZ.d[1]]
elif representation == "spherical":
dtr = self.a[0] - self.a[1]
dxr = self.a[2] - self.a[3]
dyr = self.a[4] - self.a[5]
dzr = self.a[6] - self.a[7]
R = (dxr ** 2 + dyr **2 + dzr**2) ** (1/2)
if symbolic:
theta = sp.acos(dzr / R)
phi = sp.atan2(dyr, dxr)
else:
theta = math.acos(dzr / R)
phi = math.atan2(dyr, dxr)
dt = Doubleta(dtr)
dR = Doubleta(R)
dtheta = Doubleta(theta)
dphi = Doubleta(phi)
rep = [dt.d[0], dt.d[1],
dR.d[0], dR.d[1],
dtheta.d[0], dtheta.d[1],
dphi.d[0], dphi.d[1]]
else:
print("Oops, don't know representation: ", representation)
return rep
def representation_2_txyz(self, representation):
"""Convert from a representation to Cartesian txyz."""
symbolic = self.is_symbolic()
if representation == "":
rep = [self.a[0], self.a[1], self.a[2], self.a[3], self.a[4], self.a[5], self.a[6], self.a[7]]
elif representation == "polar":
amplitude1, amplitude2 = self.a[0], self.a[1]
thetaX1, thetaX2 = self.a[2], self.a[3]
thetaY1, thetaY2 = self.a[4], self.a[5]
thetaZ1, thetaZ2 = self.a[6], self.a[7]
amp = amplitude1 - amplitude2
thetaXr = thetaX1 - thetaX2
thetaYr = thetaY1 - thetaY2
thetaZr = thetaZ1 - thetaZ2
theta = (thetaXr ** 2 + thetaYr ** 2 + thetaZr ** 2) ** (1/2)
if theta == 0:
t = amp
x, y, z = 0, 0, 0
else:
if symbolic:
t = amp * sp.cos(theta)
x = thetaXr / theta * amp * sp.sin(theta)
y = thetaYr / theta * amp * sp.sin(theta)
z = thetaZr / theta * amp * sp.sin(theta)
else:
t = amp * math.cos(theta)
x = thetaXr / theta * amp * math.sin(theta)
y = thetaYr / theta * amp * math.sin(theta)
z = thetaZr / theta * amp * math.sin(theta)
dt = Doubleta(t)
dx = Doubleta(x)
dy = Doubleta(y)
dz = Doubleta(z)
rep = [dt.d[0], dt.d[1],
dx.d[0], dx.d[1],
dy.d[0], dy.d[1],
dz.d[0], dz.d[1]]
elif representation == "spherical":
t1, t2 = self.a[0], self.a[1]
R1, R2 = self.a[2], self.a[3]
theta1, theta2 = self.a[4], self.a[5]
phi1, phi2 = self.a[6], self.a[7]
t = t1 - t2
R = R1 - R2
thetar = theta1 - theta2
phir = phi1 - phi2
if symbolic:
x = R * sp.sin(thetar) * sp.cos(phir)
y = R * sp.sin(thetar) * sp.sin(phir)
z = R * sp.cos(thetar)
else:
x = R * math.sin(thetar) * math.cos(phir)
y = R * math.sin(thetar) * math.sin(phir)
z = R * math.cos(thetar)
dt = Doubleta(t)
dx = Doubleta(x)
dy = Doubleta(y)
dz = Doubleta(z)
rep = [dt.d[0], dt.d[1],
dx.d[0], dx.d[1],
dy.d[0], dy.d[1],
dz.d[0], dz.d[1]]
else:
print("Oops, don't know representation: ", representation)
return rep
def check_representations(self, q1):
"""If they are the same, report true. If not, kick out an exception. Don't add apples to oranges."""
if self.representation == q1.representation:
return True
else:
raise Exception("Oops, 2 quaternions have different representations: {}, {}".format(self.representation, q1.representation))
return False
def q4(self):
"""Return a 4 element array."""
return [self.a[0] - self.a[1], self.a[0] - self.a[1], self.a[4] - self.a[5], self.a[6] - self.a[7]]
def q_0(self, qtype="0"):
"""Return a zero quaternion."""
q0 = Q8a(qtype=qtype, representation=self.representation)
return q0
def q_1(self, qtype="1"):
"""Return a multiplicative identity quaternion."""
q1 = Q8a([1, 0, 0, 0, 0, 0, 0, 0], qtype=qtype, representation=self.representation)
return q1
def q_i(self, qtype="i"):
"""Return i."""
qi = Q8a([0, 0, 1, 0, 0, 0, 0, 0], qtype=qtype, representation=self.representation)
return qi
def q_j(self, qtype="j"):
"""Return j."""
qj = Q8a([0, 0, 0, 0, 1, 0, 0, 0], qtype=qtype, representation=self.representation)
return qj
def q_k(self, qtype="k"):
"""Return k."""
qk = Q8a([0, 0, 0, 0, 0, 0, 1, 0], qtype=qtype, representation=self.representation)
return qk
def q_random(self, qtype="?"):
"""Return a random-valued quaternion."""
qr = Q8a([random.random(), random.random(), random.random(), random.random(), random.random(), random.random(), random.random(), random.random()], qtype=qtype)
qr.representation = self.representation
return qr
def equals(self, q2):
"""Tests if two quaternions are equal."""
self_red = self.reduce()
q2_red = q2.reduce()
result = True
for i in range(8):
if not math.isclose(self_red.a[i], q2_red.a[i]):
result = False
return result
def conj(self, conj_type=0, qtype="*"):
"""Three types of conjugates."""
conj_q = Q8a()
# Flip all but t.
if conj_type == 0:
conj_q.a[0] = self.a[0]
conj_q.a[1] = self.a[1]
conj_q.a[2] = self.a[3]
conj_q.a[3] = self.a[2]
conj_q.a[4] = self.a[5]
conj_q.a[5] = self.a[4]
conj_q.a[6] = self.a[7]
conj_q.a[7] = self.a[6]
# Flip all but x.
if conj_type == 1:
conj_q.a[0] = self.a[1]
conj_q.a[1] = self.a[0]
conj_q.a[2] = self.a[2]
conj_q.a[3] = self.a[3]
conj_q.a[4] = self.a[5]
conj_q.a[5] = self.a[4]
conj_q.a[6] = self.a[7]
conj_q.a[7] = self.a[6]
qtype += "1"
# Flip all but y.
if conj_type == 2:
conj_q.a[0] = self.a[1]
conj_q.a[1] = self.a[0]
conj_q.a[2] = self.a[3]
conj_q.a[3] = self.a[2]
conj_q.a[4] = self.a[4]
conj_q.a[5] = self.a[5]
conj_q.a[6] = self.a[7]
conj_q.a[7] = self.a[6]
qtype += "2"
conj_q.qtype = self.qtype + qtype
conj_q.representation = self.representation
return conj_q
def vahlen_conj(self, conj_type="-", qtype="vc"):
"""Three types of conjugates -'* done by Vahlen in 1901."""
conj_q = Q8a()
if conj_type == "-":
conj_q.a[0] = self.a[0]
conj_q.a[1] = self.a[1]
conj_q.a[2] = self.a[3]
conj_q.a[3] = self.a[2]
conj_q.a[4] = self.a[5]
conj_q.a[5] = self.a[4]
conj_q.a[6] = self.a[7]
conj_q.a[7] = self.a[6]
qtype += "-"
# Flip the sign of x and y.
if conj_type == "'":
conj_q.a[0] = self.a[0]
conj_q.a[1] = self.a[1]
conj_q.a[2] = self.a[3]
conj_q.a[3] = self.a[2]
conj_q.a[4] = self.a[5]
conj_q.a[5] = self.a[4]
conj_q.a[6] = self.a[6]
conj_q.a[7] = self.a[7]
qtype += "'"
# Flip the sign of only z.
if conj_type == "*":
conj_q.a[0] = self.a[0]
conj_q.a[1] = self.a[1]
conj_q.a[2] = self.a[2]
conj_q.a[3] = self.a[3]
conj_q.a[4] = self.a[4]
conj_q.a[5] = self.a[5]
conj_q.a[6] = self.a[7]
conj_q.a[7] = self.a[6]
qtype += "*"
conj_q.qtype = self.qtype + qtype
conj_q.representation = self.representation
return conj_q
def flip_signs(self, conj_type=0, qtype="-"):
"""Flip all the signs, just like multipying by -1."""
end_qtype = "-{}".format(self.qtype)
t1, t2 = self.a[0], self.a[1]
x1, x2 = self.a[2], self.a[3]
y1, y2 = self.a[4], self.a[5]
z1, z2 = self.a[6], self.a[7]
flip_q = Q8a(qtype=end_qtype)
flip_q.a[0] = t2
flip_q.a[1] = t1
flip_q.a[2] = x2
flip_q.a[3] = x1
flip_q.a[4] = y2
flip_q.a[5] = y1
flip_q.a[6] = z2
flip_q.a[7] = z1
flip_q.qtype = end_qtype
flip_q.representation = self.representation
return flip_q
def _commuting_products(self, q1):
"""Returns a dictionary with the commuting products."""
products = {'tt0': self.a[0] * q1.a[0] + self.a[1] * q1.a[1],
'tt1': self.a[0] * q1.a[1] + self.a[1] * q1.a[0],
'xx+yy+zz0': self.a[2] * q1.a[2] + self.a[3] * q1.a[3] + self.a[4] * q1.a[4] + self.a[5] * q1.a[5] + self.a[6] * q1.a[6] + self.a[7] * q1.a[7],
'xx+yy+zz1': self.a[2] * q1.a[3] + self.a[3] * q1.a[2] + self.a[4] * q1.a[5] + self.a[5] * q1.a[4] + self.a[6] * q1.a[7] + self.a[7] * q1.a[6],
'tx+xt0': self.a[0] * q1.a[2] + self.a[1] * q1.a[3] + self.a[2] * q1.a[0] + self.a[3] * q1.a[1],
'tx+xt1': self.a[0] * q1.a[3] + self.a[1] * q1.a[2] + self.a[3] * q1.a[0] + self.a[2] * q1.a[1],
'ty+yt0': self.a[0] * q1.a[4] + self.a[1] * q1.a[5] + self.a[4] * q1.a[0] + self.a[5] * q1.a[1],
'ty+yt1': self.a[0] * q1.a[5] + self.a[1] * q1.a[4] + self.a[5] * q1.a[0] + self.a[4] * q1.a[1],
'tz+zt0': self.a[0] * q1.a[6] + self.a[1] * q1.a[7] + self.a[6] * q1.a[0] + self.a[7] * q1.a[1],
'tz+zt1': self.a[0] * q1.a[7] + self.a[1] * q1.a[6] + self.a[7] * q1.a[0] + self.a[6] * q1.a[1]
}
return products
def _anti_commuting_products(self, q1):
"""Returns a dictionary with the three anti-commuting products."""
yz0 = self.a[4] * q1.a[6] + self.a[5] * q1.a[7]
yz1 = self.a[4] * q1.a[7] + self.a[5] * q1.a[6]
zy0 = self.a[6] * q1.a[4] + self.a[7] * q1.a[5]
zy1 = self.a[6] * q1.a[5] + self.a[7] * q1.a[4]
zx0 = self.a[6] * q1.a[2] + self.a[7] * q1.a[3]
zx1 = self.a[6] * q1.a[3] + self.a[7] * q1.a[2]
xz0 = self.a[2] * q1.a[6] + self.a[3] * q1.a[7]
xz1 = self.a[2] * q1.a[7] + self.a[3] * q1.a[6]
xy0 = self.a[2] * q1.a[4] + self.a[3] * q1.a[5]
xy1 = self.a[2] * q1.a[5] + self.a[3] * q1.a[4]
yx0 = self.a[4] * q1.a[2] + self.a[5] * q1.a[3]
yx1 = self.a[4] * q1.a[3] + self.a[5] * q1.a[2]
products = {'yz-zy0': yz0 + zy1,
'yz-zy1': yz1 + zy0,
'zx-xz0': zx0 + xz1,
'zx-xz1': zx1 + xz0,
'xy-yx0': xy0 + yx1,
'xy-yx1': xy1 + yx0,
'zy-yz0': yz1 + zy0,
'zy-yz1': yz0 + zy1,
'xz-zx0': zx1 + xz0,
'xz-zx1': zx0 + xz1,
'yx-xy0': xy1 + yx0,
'yx-xy1': xy0 + yx1
}
return products
def _all_products(self, q1):
"""Returns a dictionary with all possible products."""
products = self._commuting_products(q1)
products.update(self._anti_commuting_products(q1))
return products
def square(self, qtype="^2"):
"""Square a quaternion."""
end_qtype = "{}{}".format(self.qtype, qtype)
qxq = self._commuting_products(self)
sq_q = Q8a(qtype=self.qtype)
sq_q.a[0] = qxq['tt0'] + (qxq['xx+yy+zz1'])
sq_q.a[1] = qxq['tt1'] + (qxq['xx+yy+zz0'])
sq_q.a[2] = qxq['tx+xt0']
sq_q.a[3] = qxq['tx+xt1']
sq_q.a[4] = qxq['ty+yt0']
sq_q.a[5] = qxq['ty+yt1']
sq_q.a[6] = qxq['tz+zt0']
sq_q.a[7] = qxq['tz+zt1']
sq_q.qtype = end_qtype
sq_q.representation = self.representation
return sq_q
def reduce(self, qtype="-reduce"):
"""Put all Doubletas into the reduced form so one of each pair is zero."""
end_qtype = "{}{}".format(self.qtype, qtype)
red_t = Doubleta([self.a[0], self.a[1]]).d_reduce()
red_x = Doubleta([self.a[2], self.a[3]]).d_reduce()
red_y = Doubleta([self.a[4], self.a[5]]).d_reduce()
red_z = Doubleta([self.a[6], self.a[7]]).d_reduce()
q_red = Q8a(qtype=self.qtype)
q_red.a[0] = red_t.d[0]
q_red.a[1] = red_t.d[1]
q_red.a[2] = red_x.d[0]
q_red.a[3] = red_x.d[1]
q_red.a[4] = red_y.d[0]
q_red.a[5] = red_y.d[1]
q_red.a[6] = red_z.d[0]
q_red.a[7] = red_z.d[1]
q_red.qtype = end_qtype
q_red.representation = self.representation
return q_red
def norm_squared(self, qtype="|| ||^2"):
"""The norm_squared of a quaternion."""
end_qtype = "||{}||^2".format(self.qtype)
qxq = self._commuting_products(self)
n_q = Q8a()
n_q.a[0] = qxq['tt0'] + qxq['xx+yy+zz0']
n_q.a[1] = qxq['tt1'] + qxq['xx+yy+zz1']
result = n_q.reduce()
result.qtype = end_qtype
result.representation = self.representation
return result
def norm_squared_of_vector(self, qtype="V(|| ||)^2"):
"""The norm_squared of the vector of a quaternion."""
end_qtype = "V||({})||^2".format(self.qtype)
qxq = self._commuting_products(self)
nv_q = Q8a()
nv_q.a[0] = qxq['xx+yy+zz0']
nv_q.a[1] = qxq['xx+yy+zz1']
result = nv_q.reduce()
result.qtype = end_qtype
result.representation = self.representation
return result
def abs_of_q(self, qtype="| |"):
"""The absolute value, the square root of the norm_squared."""
end_qtype = "|{}|".format(self.qtype)
abq = self.norm_squared()
sqrt_t0 = abq.a[0] ** (1/2)
abq.a[0] = sqrt_t0
abq.qtype = end_qtype
abq.representation = self.representation
return abq
def abs_of_vector(self, qtype="|V()|)"):
"""The absolute value of the vector, the square root of the norm_squared of the vector."""
end_qtype = "|V({})|".format(self.qtype, qtype)
av = self.norm_squared_of_vector()
sqrt_t = av.a[0] ** (1/2)
av.a[0] = sqrt_t
av.qtype = end_qtype
av.representation = self.representation
return av
def normalize(self, qtype="U"):
"""Normalize a quaternion"""
end_qtype = "{}U".format(self.qtype)
abs_q_inv = self.abs_of_q().invert()
n_q = self.product(abs_q_inv)
n_q.qtype = end_qtype
n_q.representation=self.representation
return n_q
def add(self, q1, qtype="+"):
"""Form a add given 2 quaternions."""
self.check_representations(q1)
add_q = Q8a()
for i in range(0, 8):
add_q.a[i] = self.a[i] + q1.a[i]
add_q.qtype = "{f}+{s}".format(f=self.qtype, s=q1.qtype)
add_q.representation = self.representation
return add_q
def dif(self, q1, qtype="-"):
"""Form a add given 2 quaternions."""
self.check_representations(q1)
dif_q = Q8a()
dif_q.a[0] = self.a[0] + q1.a[1]
dif_q.a[1] = self.a[1] + q1.a[0]
dif_q.a[2] = self.a[2] + q1.a[3]
dif_q.a[3] = self.a[3] + q1.a[2]
dif_q.a[4] = self.a[4] + q1.a[5]
dif_q.a[5] = self.a[5] + q1.a[4]
dif_q.a[6] = self.a[6] + q1.a[7]
dif_q.a[7] = self.a[7] + q1.a[6]
dif_q.qtype = "{f}-{s}".format(f=self.qtype, s=q1.qtype)
dif_q.representation = self.representation
return dif_q
def product(self, q1, kind="", reverse=False, qtype=""):
"""Form a product given 2 quaternions."""
self.check_representations(q1)
commuting = self._commuting_products(q1)
q_even = Q8a()
q_even.a[0] = commuting['tt0'] + commuting['xx+yy+zz1']
q_even.a[1] = commuting['tt1'] + commuting['xx+yy+zz0']
q_even.a[2] = commuting['tx+xt0']
q_even.a[3] = commuting['tx+xt1']
q_even.a[4] = commuting['ty+yt0']
q_even.a[5] = commuting['ty+yt1']
q_even.a[6] = commuting['tz+zt0']
q_even.a[7] = commuting['tz+zt1']
anti_commuting = self._anti_commuting_products(q1)
q_odd = Q8a()
if reverse:
q_odd.a[2] = anti_commuting['zy-yz0']
q_odd.a[3] = anti_commuting['zy-yz1']
q_odd.a[4] = anti_commuting['xz-zx0']
q_odd.a[5] = anti_commuting['xz-zx1']
q_odd.a[6] = anti_commuting['yx-xy0']
q_odd.a[7] = anti_commuting['yx-xy1']
else:
q_odd.a[2] = anti_commuting['yz-zy0']
q_odd.a[3] = anti_commuting['yz-zy1']
q_odd.a[4] = anti_commuting['zx-xz0']
q_odd.a[5] = anti_commuting['zx-xz1']
q_odd.a[6] = anti_commuting['xy-yx0']
q_odd.a[7] = anti_commuting['xy-yx1']
if kind == "":
result = q_even.add(q_odd)
times_symbol = "x"
elif kind.lower() == "even":
result = q_even
times_symbol = "xE"
elif kind.lower() == "odd":
result = q_odd
times_symbol = "xO"
else:
raise Exception("Three 'kind' values are known: '', 'even', and 'odd'")
if reverse:
times_symbol = times_symbol.replace('x', 'xR')
result.qtype = "{f}{ts}{s}".format(f=self.qtype, ts=times_symbol, s=q1.qtype)
result.representation = self.representation
return result
def Euclidean_product(self, q1, kind="", reverse=False, qtype=""):
"""Form a product p* q given 2 quaternions, not associative."""
self.check_representations(q1)
pq = Q8a()
pq = self.conj().product(q1, kind, reverse, qtype)
pq.representation = self.representation
return pq
def invert(self, qtype="^-1"):
"""Invert a quaternion."""
end_qtype = "{}{}".format(self.qtype, qtype)
q_conj = self.conj()
q_norm_squared = self.norm_squared().reduce()
if q_norm_squared.a[0] == 0:
return self.q0()
q_norm_squared_inv = Q8a([1.0 / q_norm_squared.a[0], 0, 0, 0, 0, 0, 0, 0])
q_inv = q_conj.product(q_norm_squared_inv)
q_inv.qtype = end_qtype
q_inv.representation = self.representation
return q_inv
def divide_by(self, q1, qtype=""):
"""Divide one quaternion by another. The order matters unless one is using a norm_squared (real number)."""
self.check_representations(q1)
q_inv = q1.invert()
q_div = self.product(q_inv)
q_div.qtype = "{f}/{s}".format(f=self.qtype, s=q1.qtype)
q_div.representation = self.representation
return q_div
def triple_product(self, q1, q2):
"""Form a triple product given 3 quaternions."""
self.check_representations(q1)
self.check_representations(q2)
triple = self.product(q1).product(q2)
return triple
# Quaternion rotation involves a triple product: UQU∗
# where the U is a unitary quaternion (having a norm_squared of one).
def rotate(self, a_1p=0, a_1n=0, a_2p=0, a_2n=0, a_3p=0, a_3n=0):
"""Do a rotation given up to three angles."""
u = Q8a([0, 0, a_1p, a_1n, a_2p, a_2n, a_3p, a_3n])
u_abs = u.abs_of_q()
u_norm_squaredalized = u.divide_by(u_abs)
q_rot = u_norm_squaredalized.triple_product(self, u_norm_squaredalized.conj())
q_rot.representation = self.representation
return q_rot
# A boost also uses triple products like a rotation, but more of them.
# This is not a well-known result, but does work.
def boost(self, beta_x=0, beta_y=0, beta_z=0, qtype="boost"):
"""A boost along the x, y, and/or z axis."""
end_qtype = "{}{}".format(self.qtype, qtype)
boost = Q8a(sr_gamma_betas(beta_x, beta_y, beta_z))
b_conj = boost.conj()
triple_1 = boost.triple_product(self, b_conj)
triple_2 = boost.triple_product(boost, self).conj()
triple_3 = b_conj.triple_product(b_conj, self).conj()
triple_23 = triple_2.dif(triple_3)
half_23 = triple_23.product(Q8a([0.5, 0, 0, 0, 0, 0, 0, 0]))
triple_123 = triple_1.add(half_23)
triple_123.qtype = end_qtype
triple_123.representation = self.representation
return triple_123
# g_shift is a function based on the space-times-time invariance proposal for gravity,
# which proposes that if one changes the distance from a gravitational source, then
# squares a measurement, the observers at two different hieghts agree to their
# space-times-time values, but not the intervals.
def g_shift(self, dimensionless_g, g_form="exp", qtype="g_shift"):
"""Shift an observation based on a dimensionless GM/c^2 dR."""
end_qtype = "{}{}".format(self.qtype, qtype)
if g_form == "exp":
g_factor = sp.exp(dimensionless_g)
if qtype == "g_shift":
qtype = "g_exp"
elif g_form == "minimal":
g_factor = 1 + 2 * dimensionless_g + 2 * dimensionless_g ** 2
if qtype == "g_shift":
qtype = "g_minimal"
else:
print("g_form not defined, should be 'exp' or 'minimal': {}".format(g_form))
return self
exp_g = sp.exp(dimensionless_g)
dt = Doubleta([self.a[0] / exp_g, self.a[1] / exp_g])
dx = Doubleta([self.a[2] * exp_g, self.a[3] * exp_g])
dy = Doubleta([self.a[4] * exp_g, self.a[5] * exp_g])
dz = Doubleta([self.a[6] * exp_g, self.a[7] * exp_g])
g_q = Q8a(qtype=self.qtype)
g_q.a[0] = dt.d[0]
g_q.a[1] = dt.d[1]
g_q.a[2] = dx.d[0]
g_q.a[3] = dx.d[1]
g_q.a[4] = dy.d[0]
g_q.a[5] = dy.d[1]
g_q.a[6] = dz.d[0]
g_q.a[7] = dz.d[1]
g_q.qtype = end_qtype
g_q.representation = self.representation
return g_q
def sin(self, qtype="sin"):
"""Take the sine of a quaternion, (sin(t) cosh(|R|), cos(t) sinh(|R|) R/|R|)"""
end_qtype = "sin({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
red_t = Doubleta([self.a[0], self.a[1]]).d_reduce()
if red_t.d[0] == 0 and red_t.d[1] != 0:
if abs_v.a[0] == 0:
return Q8a([-1 * math.sin(red_t.d[1]), 0, 0, 0], qtype=end_qtype, representation=self.representation)
sint = math.sin(-1 * red_t.d[1])
cost = math.cos(-1 *red_t.d[1])
else:
if abs_v.a[0] == 0:
return Q8a([math.sin(red_t.d[0]), 0, 0, 0], qtype=end_qtype, representation=self.representation)
sint = math.sin(red_t.d[0])
cost = math.cos(red_t.d[0])
sinhR = math.sinh(abs_v.a[0])
coshR = math.cosh(abs_v.a[0])
k = cost * sinhR / abs_v.a[0]
q_out_dt = Doubleta(sint * coshR)
q_out_dx = Doubleta(k * (self.a[2] - self.a[3]))
q_out_dy = Doubleta(k * (self.a[4] - self.a[5]))
q_out_dz = Doubleta(k * (self.a[6] - self.a[7]))
q_out = Q8a([q_out_t, q_out_x, q_out_y, q_out_z], qtype=end_qtype, representation=self.representation)
return q_out
def cos(self, qtype="cos"):
"""Take the cosine of a quaternion, (cos(t) cosh(|R|), sin(t) sinh(|R|) R/|R|)"""
end_qtype = "cos({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
red_t = Doubleta([self.a[0], self.a[1]]).d_reduce()
if red_t.d[0] == 0 and red_t.d[1] != 0:
if abs_v.a[0] == 0:
return Q8a([math.cos(-1 * red_t.d[1]), 0, 0, 0], qtype=end_qtype)
sint = math.sin(-1 * red_t.d[1])
cost = math.cos(-1 * red_t.d[1])
else:
if abs_v.a[0] == 0:
return Q8a([math.cos(red_t.d[0]), 0, 0, 0], qtype=end_qtype)
sint = math.sin(red_t.d[0])
cost = math.cos(red_t.d[0])
sinhR = math.sinh(abs_v.a[0])
coshR = math.cosh(abs_v.a[0])
k = -1 * sint * sinhR / abs_v.a[0]
q_out_dt = Doubleta(cost * coshR)
q_out_dx = Doubleta(k * (self.a[2] - self.a[3]))
q_out_dy = Doubleta(k * (self.a[4] - self.a[5]))
q_out_dz = Doubleta(k * (self.a[6] - self.a[7]))
q_out = Q8a([q_out_t, q_out_x, q_out_y, q_out_z], qtype=end_qtype, representation=self.representation)
return q_out
def tan(self, qtype="sin"):
"""Take the tan of a quaternion, sin/cos"""
end_qtype = "tan({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
red_t = Doubleta([self.a[0], self.a[1]]).d_reduce()
if red_t.d[0] == 0 and red_t.d[1] != 0:
if abs_v.dt == 0:
return Q8a([math.tan(-1 * red_t.d[1]), 0, 0, 0], qtype=end_qtype, representation=self.representation)
else:
if abs_v.a[0] == 0:
return Q8a([math.tan(red_t.d[0]), 0, 0, 0], qtype=end_qtype, representation=self.representation)
sinq = self.sin()
cosq = self.cos()
q_out = sinq.divide_by(cosq)
q_out.qtype = end_qtype
q_out.representation = self.representation
return q_out
def sinh(self, qtype="sinh"):
"""Take the sinh of a quaternion, (sinh(t) cos(|R|), cosh(t) sin(|R|) R/|R|)"""
end_qtype = "sinh({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
red_t = Doubleta([self.a[0], self.a[1]]).d_reduce()
if red_t.d[0] == 0 and red_t.d[1] != 0:
if abs_v.a[0] == 0:
return Q8a([math.sinh(-1 * red_t.d[1]), 0, 0, 0], qtype=end_qtype, representation=self.representation)
sinht = math.sinh(-1 * red_t.d[1])
cosht = math.cosh(-1 * red_t.d[1])
else:
if abs_v.a[0] == 0:
return Q8a([math.sinh(red_t.d[0]), 0, 0, 0], qtype=end_qtype, representation=self.representation)
sinht = math.sinh(red_t.d[0])
cosht = math.cosh(red_t.d[0])
sinR = math.sin(abs_v.a[0])
cosR = math.cos(abs_v.a[0])
k = cosht * sinR / abs_v.a[0]
q_out_dt = Doubleta(sinht * cosR)
q_out_dx = Doubleta(k * (self.a[2] - self.a[3]))
q_out_dy = Doubleta(k * (self.a[4] - self.a[5]))
q_out_dz = Doubleta(k * (self.a[6] - self.a[7]))
q_out = Q8a([q_out_t, q_out_x, q_out_y, q_out_z], qtype=end_qtype, representation=self.representation)
return q_out
def cosh(self, qtype="sin"):
"""Take the cosh of a quaternion, (cosh(t) cos(|R|), sinh(t) sin(|R|) R/|R|)"""
end_qtype = "cosh({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
red_t = Doubleta([self.a[0], self.a[1]]).d_reduce()
if red_t.d[0] == 0 and red_t.d[1] != 0:
if abs_v.a[0] == 0:
return Q8a([-1 * math.cosh(self.dt.n), 0, 0, 0], qtype=end_qtype, representation=self.representation)
sinht = math.sinh(-1 * red_t.d[1])
cosht = math.cosh(-1 * red_t.d[1])
else:
if abs_v.a[0] == 0:
return Q8a([math.cosh(self.dt.p), 0, 0, 0], qtype=end_qtype, representation=self.representation)
sinht = math.sinh(red_t.d[0])
cosht = math.cosh(red_t.d[0])
sinR = math.sin(abs_v.a[0])
cosR = math.cos(abs_v.a[0])
k = sinht * sinR / abs_v.a[0]
q_out_dt = Doubleta(cosht * cosR)
q_out_dx = Doubleta(k * (self.a[2] - self.a[3]))
q_out_dy = Doubleta(k * (self.a[4] - self.a[5]))
q_out_dz = Doubleta(k * (self.a[6] - self.a[7]))
q_out = Q8a([q_out_t, q_out_x, q_out_y, q_out_z], qtype=end_qtype, representation=self.representation)
return q_out
def tanh(self, qtype="sin"):
"""Take the tanh of a quaternion, sin/cos"""
end_qtype = "tanh({sq})".format(sq=self.qtype)
abs_v = self.abs_of_vector()
red_t = Doubleta([self.a[0], self.a[1]]).d_reduce()
if abs_v.a[0] == 0:
if red_t.d[0] == 0 and red_t.d[1] != 0:
return Q8a([-1 * math.tanh(self.dt.n), 0, 0, 0], qtype=end_qtype, representation=self.representation)
else:
return Q8a([math.tanh(self.dt.p), 0, 0, 0], qtype=end_qtype, representation=self.representation)
sinhq = self.sinh()
coshq = self.cosh()
q_out = sinhq.divide_by(coshq)
q_out.qtype = end_qtype
q_out.representation = self.representation
return q_out
def exp(self, qtype="exp"):
"""Take the exponential of a quaternion."""
# exp(q) = (exp(t) cos(|R|, exp(t) sin(|R|) R/|R|)
end_qtype = "exp({st})".format(st=self.qtype)
abs_v = self.abs_of_vector()
red_t = Doubleta([self.a[0], self.a[1]]).d_reduce()
if red_t.d[0] == 0 and red_t.d[1] != 0:
et = math.exp(-1 * red_t.d[1])
if (abs_v.a[0] == 0):
return Q8a([et, 0, 0, 0], qtype=end_qtype, representation=self.representation)
cosR = math.cos(abs_v.a[0])
sinR = math.sin(abs_v.a[0])
else:
et = math.exp(red_t.d[0])
if (abs_v.a[0] == 0):
return Q8a([et, 0, 0, 0], qtype=end_qtype, representation=self.representation)
cosR = math.cos(abs_v.a[0])
sinR = math.sin(abs_v.a[0])
k = et * sinR / abs_v.a[0]
expq_dt = Doubleta(et * cosR)
expq_dx = Doubleta(k * (self.a[2] - self.a[3]))
expq_dy = Doubleta(k * (self.a[4] - self.a[5]))
expq_dz = Doubleta(k * (self.a[6] - self.a[7]))
expq = Q8a([expq_dt, expq_dt, expq_dt, expq_dt], qtype=end_qtype, representation=self.representation)
return expq
def ln(self, qtype="ln"):
"""Take the natural log of a quaternion."""
# ln(q) = (0.5 ln t^2 + R.R, atan2(|R|, t) R/|R|)
end_qtype = "ln({st})".format(st=self.qtype)
abs_v = self.abs_of_vector()
red_t = Doubleta([self.a[0], self.a[1]]).d_reduce()
if red_t.d[0] == 0 and red_t.d[1] != 0:
if (abs_v.a[0] == 0):
# I don't understant this, but mathematica does the same thing, but it looks wrong to me.
return(Q8a([math.log(-self.dt.n), math.pi, 0, 0], qtype=end_qtype))
t_value = 0.5 * math.log(red_t.d[1] * red_t.d[1] + abs_v.a[0] * abs_v.a[0])
k = math.atan2(abs_v.a[0], red_t.d[1]) / abs_v.a[0]
else:
if (abs_v.a[0] == 0):
return(Q8a([math.log(self.dt.p), 0, 0, 0], qtype=end_qtype, representation=self.representation))
t_value = 0.5 * math.log(red_t.d[0] * red_t.d[0] + abs_v.a[0] * abs_v.a[0])
k = math.atan2(abs_v.a[0], red_t.d[0]) / abs_v.a[0]
lnq_dt = Doubleta(t_value)
lnq_dx = Doubleta(k * (self.a[2] - self.a[3]))
lnq_dy = Doubleta(k * (self.a[4] - self.a[5]))
lnq_dz = Doubleta(k * (self.a[6] - self.a[7]))
lnq = Q8a([lnq_dt, lnq_dx, lnq_dy, lnq_dz], qtype=end_qtype, representation=self.representation)
return lnq
def q_2_q(self, q1, qtype="P"):
"""Take the natural log of a quaternion, q^p = exp(ln(q) * p)."""
self.check_representations(q1)
end_qtype = "{st}^P".format(st=self.qtype)
q2q = self.ln().product(q1).reduce().exp()
q2q.qtype = end_qtype
q2q.representation = self.representation
return q2q
def trunc(self):
"""Truncates values."""
self.dt = math.trunc(self.dt)
self.dx = math.trunc(self.dx)
self.dy = math.trunc(self.dy)
self.dz = math.trunc(self.dz)
return self
In [18]:
class TestQ8a(unittest.TestCase):
"""Class to make sure all the functions work as expected."""
q1 = Q8a([1, 0, 0, 2, 0, 3, 0, 4])
q2 = Q8a([0, 0, 4, 0, 0, 3, 0, 0])
q_big = Q8a([1, 2, 3, 4, 5, 6, 7, 8])
verbose = True
def test_qt(self):
self.assertTrue(self.q1.a[0] == 1)
def test_q_zero(self):
q_z = self.q1.q_0()
print("q0: {}".format(q_z))
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[5] == 0)
self.assertTrue(q_z.a[6] == 0)
def test_q_1(self):
q_z = self.q1.q_1()
print("q_1: {}".format(q_z))
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[4] == 0)
self.assertTrue(q_z.a[6] == 0)
def test_q_i(self):
q_z = self.q1.q_i()
print("q_i: {}".format(q_z))
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[2] == 1)
self.assertTrue(q_z.a[4] == 0)
self.assertTrue(q_z.a[6] == 0)
def test_q_j(self):
q_z = self.q1.q_j()
print("q_j: {}".format(q_z))
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[4] == 1)
self.assertTrue(q_z.a[6] == 0)
def test_q_k(self):
q_z = self.q1.q_k()
print("q_k: {}".format(q_z))
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[4] == 0)
self.assertTrue(q_z.a[6] == 1)
def test_q_random(self):
q_z = self.q1.q_random()
print("q_random():", q_z)
self.assertTrue(q_z.a[0] >= 0 and q_z.a[0] <= 1)
self.assertTrue(q_z.a[2] >= 0 and q_z.a[2] <= 1)
self.assertTrue(q_z.a[4] >= 0 and q_z.a[4] <= 1)
self.assertTrue(q_z.a[6] >= 0 and q_z.a[6] <= 1)
def test_conj_0(self):
q_z = self.q1.conj()
print("conj 0: {}".format(q_z))
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[2] == 2)
self.assertTrue(q_z.a[4] == 3)
self.assertTrue(q_z.a[6] == 4)
def test_equals(self):
self.assertTrue(self.q1.equals(self.q1))
self.assertFalse(self.q1.equals(self.q2))
def test_conj_1(self):
q_z = self.q1.conj(1)
print("conj 1: {}".format(q_z))
self.assertTrue(q_z.a[1] == 1)
self.assertTrue(q_z.a[3] == 2)
self.assertTrue(q_z.a[4] == 3)
self.assertTrue(q_z.a[6] == 4)
def test_conj_2(self):
q_z = self.q1.conj(2)
print("conj 2: {}".format(q_z))
self.assertTrue(q_z.a[1] == 1)
self.assertTrue(q_z.a[2] == 2)
self.assertTrue(q_z.a[5] == 3)
self.assertTrue(q_z.a[6] == 4)
def test_vahlen_conj_0(self):
q_z = self.q1.vahlen_conj()
print("vahlen conj -: {}".format(q_z))
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[2] == 2)
self.assertTrue(q_z.a[4] == 3)
self.assertTrue(q_z.a[6] == 4)
def test_vahlen_conj_1(self):
q_z = self.q1.vahlen_conj("'")
print("vahlen conj ': {}".format(q_z))
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[2] == 2)
self.assertTrue(q_z.a[4] == 3)
self.assertTrue(q_z.a[7] == 4)
def test_vahlen_conj_2(self):
q_z = self.q1.vahlen_conj('*')
print("vahlen conj *: {}".format(q_z))
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[3] == 2)
self.assertTrue(q_z.a[5] == 3)
self.assertTrue(q_z.a[6] == 4)
def test_square(self):
q_sq = self.q1.square()
q_sq_red = q_sq.reduce()
print("square: {}".format(q_sq))
print("square reduced: {}".format(q_sq_red))
self.assertTrue(q_sq.a[0] == 1)
self.assertTrue(q_sq.a[1] == 29)
self.assertTrue(q_sq.a[3] == 4)
self.assertTrue(q_sq.a[5] == 6)
self.assertTrue(q_sq.a[7] == 8)
self.assertTrue(q_sq_red.a[0] == 0)
self.assertTrue(q_sq_red.a[1] == 28)
def test_reduce(self):
q_red = self.q_big.reduce()
print("q_big reduced: {}".format(q_red))
self.assertTrue(q_red.a[0] == 0)
self.assertTrue(q_red.a[1] == 1)
self.assertTrue(q_red.a[2] == 0)
self.assertTrue(q_red.a[3] == 1)
self.assertTrue(q_red.a[4] == 0)
self.assertTrue(q_red.a[5] == 1)
self.assertTrue(q_red.a[6] == 0)
self.assertTrue(q_red.a[7] == 1)
def test_norm_squared(self):
q_z = self.q1.norm_squared()
print("norm_squared: {}".format(q_z))
self.assertTrue(q_z.a[0] == 30)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 0)
self.assertTrue(q_z.a[4] == 0)
self.assertTrue(q_z.a[5] == 0)
self.assertTrue(q_z.a[6] == 0)
self.assertTrue(q_z.a[7] == 0)
def test_norm_squared_of_vector(self):
q_z = self.q1.norm_squared_of_vector()
print("norm_squared_of_vector: {}".format(q_z))
self.assertTrue(q_z.a[0] == 29)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 0)
self.assertTrue(q_z.a[4] == 0)
self.assertTrue(q_z.a[5] == 0)
self.assertTrue(q_z.a[6] == 0)
self.assertTrue(q_z.a[7] == 0)
def test_abs_of_q(self):
q_z = self.q2.abs_of_q()
print("abs_of_q: {}".format(q_z))
self.assertTrue(q_z.a[0] == 5)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[4] == 0)
self.assertTrue(q_z.a[6] == 0)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[3] == 0)
self.assertTrue(q_z.a[5] == 0)
self.assertTrue(q_z.a[7] == 0)
def test_abs_of_vector(self):
q_z = self.q2.abs_of_vector()
print("abs_of_vector: {}".format(q_z))
self.assertTrue(q_z.a[0] == 5)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[4] == 0)
self.assertTrue(q_z.a[6] == 0)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[3] == 0)
self.assertTrue(q_z.a[5] == 0)
self.assertTrue(q_z.a[7] == 0)
def test_normalize(self):
q_z = self.q2.normalize()
print("q_normalized: {}".format(q_z))
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0.8)
self.assertTrue(q_z.a[3] == 0)
self.assertTrue(q_z.a[4] == 0)
self.assertAlmostEqual(q_z.a[5], 0.6)
self.assertTrue(q_z.a[6] == 0)
self.assertTrue(q_z.a[7] == 0)
def test_add(self):
q_z = self.q1.add(self.q2)
print("add: {}".format(q_z))
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 4)
self.assertTrue(q_z.a[3] == 2)
self.assertTrue(q_z.a[4] == 0)
self.assertTrue(q_z.a[5] == 6)
self.assertTrue(q_z.a[6] == 0)
self.assertTrue(q_z.a[7] == 4)
def test_add_reduce(self):
q_z_red = self.q1.add(self.q2).reduce()
print("add reduce: {}".format(q_z_red))
self.assertTrue(q_z_red.a[0] == 1)
self.assertTrue(q_z_red.a[1] == 0)
self.assertTrue(q_z_red.a[2] == 2)
self.assertTrue(q_z_red.a[3] == 0)
self.assertTrue(q_z_red.a[4] == 0)
self.assertTrue(q_z_red.a[5] == 6)
self.assertTrue(q_z_red.a[6] == 0)
self.assertTrue(q_z_red.a[7] == 4)
def test_dif(self):
q_z = self.q1.dif(self.q2)
print("dif: {}".format(q_z))
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 6)
self.assertTrue(q_z.a[4] == 3)
self.assertTrue(q_z.a[5] == 3)
self.assertTrue(q_z.a[6] == 0)
self.assertTrue(q_z.a[7] == 4)
def test_product(self):
q_z = self.q1.product(self.q2).reduce()
print("product: {}".format(q_z))
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[1] == 1)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 8)
self.assertTrue(q_z.a[4] == 0)
self.assertTrue(q_z.a[5] == 19)
self.assertTrue(q_z.a[6] == 18)
self.assertTrue(q_z.a[7] == 0)
def test_product_even(self):
q_z = self.q1.product(self.q2, kind="even").reduce()
print("product, kind even: {}".format(q_z))
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[1] == 1)
self.assertTrue(q_z.a[2] == 4)
self.assertTrue(q_z.a[3] == 0)
self.assertTrue(q_z.a[4] == 0)
self.assertTrue(q_z.a[5] == 3)
self.assertTrue(q_z.a[6] == 0)
self.assertTrue(q_z.a[7] == 0)
def test_product_odd(self):
q_z = self.q1.product(self.q2, kind="odd").reduce()
print("product, kind odd: {}".format(q_z))
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 12)
self.assertTrue(q_z.a[4] == 0)
self.assertTrue(q_z.a[5] == 16)
self.assertTrue(q_z.a[6] == 18)
self.assertTrue(q_z.a[7] == 0)
def test_product_reverse(self):
q1q2_rev = self.q1.product(self.q2, reverse=True)
q2q1 = self.q2.product(self.q1)
self.assertTrue(q1q2_rev.equals(q2q1))
def test_Euclidean_product(self):
q_z = self.q1.Euclidean_product(self.q2).reduce()
print("Euclidean product: {}".format(q_z))
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 16)
self.assertTrue(q_z.a[3] == 0)
self.assertTrue(q_z.a[4] == 13)
self.assertTrue(q_z.a[5] == 0)
self.assertTrue(q_z.a[6] == 0)
self.assertTrue(q_z.a[7] == 18)
def test_invert(self):
q_z = self.q2.invert().reduce()
print("inverse: {}".format(q_z))
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 0.16)
self.assertTrue(q_z.a[4] == 0.12)
self.assertTrue(q_z.a[5] == 0)
self.assertTrue(q_z.a[6] == 0)
self.assertTrue(q_z.a[7] == 0)
def test_divide_by(self):
q_z = self.q1.divide_by(self.q1).reduce()
print("inverse: {}".format(q_z))
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 0)
self.assertTrue(q_z.a[4] == 0)
self.assertTrue(q_z.a[5] == 0)
self.assertTrue(q_z.a[6] == 0)
self.assertTrue(q_z.a[7] == 0)
def test_triple_product(self):
q_z = self.q1.triple_product(self.q2, self.q1).reduce()
print("triple: {}".format(q_z))
self.assertTrue(q_z.a[0] == 0)
self.assertTrue(q_z.a[1] == 2)
self.assertTrue(q_z.a[2] == 124)
self.assertTrue(q_z.a[3] == 0)
self.assertTrue(q_z.a[4] == 0)
self.assertTrue(q_z.a[5] == 84)
self.assertTrue(q_z.a[6] == 8)
self.assertTrue(q_z.a[7] == 0)
def test_rotate(self):
q_z = self.q1.rotate(1).reduce()
print("rotate: {}".format(q_z))
self.assertTrue(q_z.a[0] == 1)
self.assertTrue(q_z.a[1] == 0)
self.assertTrue(q_z.a[2] == 0)
self.assertTrue(q_z.a[3] == 2)
self.assertTrue(q_z.a[4] == 3)
self.assertTrue(q_z.a[5] == 0)
self.assertTrue(q_z.a[6] == 4)
self.assertTrue(q_z.a[7] == 0)
def test_boost(self):
q1_sq = self.q1.square().reduce()
q_z = self.q1.boost(0.003)
q_z2 = q_z.square().reduce()
print("q1_sq: {}".format(q1_sq))
print("boosted: {}".format(q_z))
print("b squared: {}".format(q_z2))
self.assertTrue(round(q_z2.a[1], 12) == round(q1_sq.a[1], 12))
def test_g_shift(self):
q1_sq = self.q1.square().reduce()
q_z = self.q1.g_shift(0.003)
q_z2 = q_z.square().reduce()
print("q1_sq: {}".format(q1_sq))
print("g_shift: {}".format(q_z))
print("g squared: {}".format(q_z2))
self.assertTrue(q_z2.a[1] != q1_sq.a[1])
self.assertTrue(q_z2.a[2] == q1_sq.a[2])
self.assertTrue(q_z2.a[3] == q1_sq.a[3])
self.assertTrue(q_z2.a[4] == q1_sq.a[4])
self.assertTrue(q_z2.a[5] == q1_sq.a[5])
self.assertTrue(q_z2.a[6] == q1_sq.a[6])
self.assertTrue(q_z2.a[7] == q1_sq.a[7])
suite = unittest.TestLoader().loadTestsFromModule(TestQ8a())
unittest.TextTestRunner().run(suite);
In [19]:
class TestQ8aRep(unittest.TestCase):
Q12 = Q8a([1.0, 2.0, 0, 0])
Q1123 = Q8a([1.0, 1.0, 2.0, 3.0])
Q11p = Q8a([1.0, 1.0, 0, 0], representation="polar")
Q12p = Q8a([1.0, 2.0, 0, 0], representation="polar")
Q12np = Q8a([1.0, -2.0, 0, 0], representation="polar")
Q21p = Q8a([2.0, 1.0, 0, 0], representation="polar")
Q23p = Q8a([2.0, 3.0, 0, 0], representation="polar")
Q13p = Q8a([1.0, 3.0, 0, 0], representation="polar")
Q5p = Q8a([5.0, 0, 0, 0], representation="polar")
# @unittest.skip("problems implementing")
def test_txyz_2_representation(self):
qr = Q8a(self.Q12.txyz_2_representation(""))
self.assertTrue(qr.equals(self.Q12))
qr = Q8a(self.Q12.txyz_2_representation("polar"))
self.assertTrue(qr.equals(Q8a([2.23606797749979, 1.10714871779409, 0, 0])))
qr = Q8a(self.Q1123.txyz_2_representation("spherical"))
self.assertTrue(qr.equals(Q8a([1.0, 3.7416573867739413, 0.640522312679424, 1.10714871779409])))
# @unittest.skip("problems implementing")
def test_representation_2_txyz(self):
qr = Q8a(self.Q12.representation_2_txyz(""))
self.assertTrue(qr.equals(self.Q12))
qr = Q8a(self.Q12.representation_2_txyz("polar"))
self.assertTrue(qr.equals(Q8a([-0.4161468365471424, 0.9092974268256817, 0, 0])))
qr = Q8a(self.Q1123.representation_2_txyz("spherical"))
self.assertTrue(qr.equals(Q8a([1.0, -0.9001976297355174, 0.12832006020245673, -0.4161468365471424])))
def test_polar_products(self):
qr = self.Q11p.product(self.Q12p).reduce()
print("polar 1 1 0 0 * 1 2 0 0: ", qr)
self.assertTrue(qr.equals(self.Q13p))
qr = self.Q12p.product(self.Q21p).reduce()
print("polar 1 2 0 0 * 2 1 0 0: ", qr)
self.assertTrue(qr.equals(self.Q23p))
def test_polar_conj(self):
qr = self.Q12p.conj().reduce()
print("polar conj of 1 2 0 0: ", qr)
self.assertTrue(qr.equals(self.Q12np))
suite = unittest.TestLoader().loadTestsFromModule(TestQ8aRep())
unittest.TextTestRunner().run(suite);
Create a class that can figure out if two quaternions are in the same equivalence class. An equivalence class of space-time is a subset of events in space-time. For example, the future equivalence class would have any event that happens in the future. All time-like events have an interval that is positive.
A series of images were created to show each class. For the future, here is the equivalence class:
In [20]:
def round_sig_figs(num, sig_figs):
"""Round to specified number of sigfigs.
# from http://code.activestate.com/recipes/578114-round-number-to-specified-number-of-significant-di/
"""
if num != 0:
return round(num, -int(math.floor(math.log10(abs(num))) - (sig_figs - 1)))
else:
return 0 # Can't take the log of 0
In [21]:
class EQ(object):
"""A class that compairs pairs of quaternions."""
# Read images in once for the class.
eq_images = {}
qtd_dir = os.path.dirname(IPython.utils.path.filefind('Q_tools.ipynb'))
im_dir = "{qd}/images/eq_classes".format(qd=qtd_dir)
im_files = "{imd}/*png".format(imd=im_dir)
for eq_image_file in glob(im_files):
file_name = basename(eq_image_file)
eq_class_name = (file_name.split(sep='.'))[0]
eq_images[eq_class_name] = mpimg.imread(eq_image_file)
def __init__(self, q1, q2, sig_figs=10):
# Convert the quaternions into the Q8 reduced form.
if isinstance(q1, QH):
self.q1 = Q8([q1.t, q1.x, q1.y, q1.z])
elif(isinstance(q1, Q8)):
self.q1 = q1.reduce()
if isinstance(q2, QH):
self.q2 = Q8([q2.t, q2.x, q2.y, q2.z])
elif(isinstance(q2, Q8)):
self.q2 = q2.reduce()
# The quaternions used by this class are
# linear, square, and the norm_squared of a quaternion so do the calculations once.
self.q1_square = self.q1.square().reduce()
self.q2_square = self.q2.square().reduce()
self.q1_norm_squared_minus_1 = self.q1.norm_squared().dif(self.q1.q_1()).reduce()
self.q2_norm_squared_minus_1 = self.q2.norm_squared().dif(self.q1.q_1()).reduce()
# Store results here
self.classes = {}
self.sig_figs = sig_figs
def get_class(self, q1, q2, names, position):
"""A general tool to figure out a scalar class.
Names is a dictionary that needs values for 'class', 'positive', 'negative', and 'divider'.
position needs to be dt, dx, dy or dz"""
q1_d = {'dt': q1.dt, 'dy': q1.dy, 'dx': q1.dx, 'dz': q1.dz}
q2_d = {'dt': q2.dt, 'dy': q2.dy, 'dx': q2.dx, 'dz': q2.dz}
# Since the quaternions in the Q8 form are reduced just look for non-zero values.
if q1_d[position].p and q2_d[position].p:
if round_sig_figs(q1_d[position].p, self.sig_figs) == round_sig_figs(q2_d[position].p, self.sig_figs):
result = "{np}_exact".format(np=names["positive"])
else:
result = "{np}".format(np=names["positive"])
elif q1_d[position].n and q2_d[position].n:
if round_sig_figs(q1_d[position].n, self.sig_figs) == round_sig_figs(q2_d[position].n, self.sig_figs):
result = "{nn}_exact".format(nn=names["negative"])
else:
result = "{nn}".format(nn=names["negative"])
elif not q1_d[position].p and not q1_d[position].n and not q2_d[position].p and not q2_d[position].n:
result = "{nd}_exact".format(nd=names["divider"])
else:
result = "disjoint"
self.classes[names["class"]] = result
return result
def time(self):
"""Figure out time equivalence class."""
names = {'class': 'time', 'positive': 'future', 'negative': 'past', 'divider': 'now'}
result = self.get_class(self.q1, self.q2, names, 'dt')
return result
def space(self):
"""Figure out time equivalence class."""
positions = ['dx', 'dy', 'dz']
names = []
names.append({'class': 'space-1', 'positive': 'right', 'negative': 'left', 'divider': 'here'})
names.append({'class': 'space-2', 'positive': 'up', 'negative': 'down', 'divider': 'here'})
names.append({'class': 'space-3', 'positive': 'near', 'negative': 'far', 'divider': 'here'})
results = []
for name, position in zip(names, positions):
results.append(self.get_class(self.q1, self.q2, name, position))
return results
def space_time(self):
"""Do both time and space, return an array."""
results = []
results.append(self.time())
results.extend(self.space())
return results
def causality(self):
"""There is only one causality equivalence class."""
names = {'class': 'causality', 'positive': 'time-like', 'negative': 'space-like', 'divider': 'light-like'}
result = self.get_class(self.q1_square, self.q2_square, names, 'dt')
return result
def space_times_time(self):
"""Figure out the space-times-time equivalence class used in the quaternion gravity proposal."""
positions = ['dx', 'dy', 'dz']
names = []
names.append({'class': 'space-times-time-1', 'positive': 'future-right',
'negative': 'future-left', 'divider': 'here-now'})
names.append({'class': 'space-times-time-2', 'positive': 'future-up',
'negative': 'future-down', 'divider': 'here-now'})
names.append({'class': 'space-times-time-3', 'positive': 'future-near',
'negative': 'future-far', 'divider': 'here-now'})
results = []
for name, position in zip(names, positions):
results.append(self.get_class(self.q1, self.q2, name, position))
return results
def squared(self):
"""Return both causality and space_times_time as a list."""
results = []
results.append(self.causality())
results.extend(self.space_times_time())
return results
def norm_squared_of_unity(self):
"""Find out if the norm_squared of both is greater than, less than, exactly equal or both different from unity."""
names = {'class': 'norm_squared_of_unity', 'positive': 'greater_than_unity', 'negative': 'less_than_unity', 'divider': 'unity'}
result = self.get_class(self.q1_norm_squared_minus_1, self.q2_norm_squared_minus_1, names, 'dt')
return result
def compare(self, eq_2):
"""Compares one set of equivalence classes to anther."""
pass
def get_all_classes(self, eq_2=None):
"""Run them all."""
if eq_2 is None:
eq_classes = [self]
else:
eq_classes = [self, eq_2]
for eq_class in eq_classes:
if 'time' not in eq_class.classes:
eq_class.time()
if 'space' not in eq_class.classes:
eq_class.space()
if 'causality' not in eq_class.classes:
eq_class.causality()
if 'space-times-time' not in eq_class.classes:
eq_class.space_times_time()
if 'norm_squared_of_unity' not in eq_class.classes:
eq_class.norm_squared_of_unity()
def visualize(self, eq_2=None):
"""Visualize one or two rows of classes with icons for each of the 5 classes."""
self.get_all_classes(eq_2)
if eq_2 is None:
fig = plt.figure()
plt.rcParams["figure.figsize"] = [50, 30]
ax1 = fig.add_subplot(3, 5, 1)
ax1.imshow(self.eq_images['time_' + self.classes['time']])
plt.axis('off')
ax21 = fig.add_subplot(3, 5, 2)
ax21.imshow(self.eq_images['space-1_' + self.classes['space-1']])
plt.axis('off');
ax22 = fig.add_subplot(3, 5, 7)
ax22.imshow(self.eq_images['space-2_' + self.classes['space-2']])
plt.axis('off');
ax23 = fig.add_subplot(3, 5, 12)
ax23.imshow(self.eq_images['space-3_' + self.classes['space-3']])
plt.axis('off');
ax3 = fig.add_subplot(3, 5, 3)
ax3.imshow(self.eq_images['causality_' + self.classes['causality']])
plt.axis('off');
ax41 = fig.add_subplot(3, 5, 4)
ax41.imshow(self.eq_images['space-times-time-1_' + self.classes['space-times-time-1']])
plt.axis('off');
ax42 = fig.add_subplot(3, 5, 9)
ax42.imshow(self.eq_images['space-times-time-2_' + self.classes['space-times-time-2']])
plt.axis('off');
ax43 = fig.add_subplot(3, 5, 14)
ax43.imshow(self.eq_images['space-times-time-3_' + self.classes['space-times-time-3']])
plt.axis('off');
ax5 = fig.add_subplot(3, 5, 5)
ax5.imshow(self.eq_images['norm_squared_of_unity_' + self.classes['norm_squared_of_unity']])
plt.axis('off');
else:
fig = plt.figure()
plt.rcParams["figure.figsize"] = [50, 60]
ax1 = fig.add_subplot(6, 5, 1)
ax1.imshow(self.eq_images['time_' + self.classes['time']])
plt.axis('off')
ax21 = fig.add_subplot(6, 5, 2)
ax21.imshow(self.eq_images['space-1_' + self.classes['space-1']])
plt.axis('off');
ax22 = fig.add_subplot(6, 5, 7)
ax22.imshow(self.eq_images['space-2_' + self.classes['space-2']])
plt.axis('off');
ax23 = fig.add_subplot(6, 5, 12)
ax23.imshow(self.eq_images['space-3_' + self.classes['space-3']])
plt.axis('off');
ax3 = fig.add_subplot(6, 5, 3)
ax3.imshow(self.eq_images['causality_' + self.classes['causality']])
plt.axis('off');
ax41 = fig.add_subplot(6, 5, 4)
ax41.imshow(self.eq_images['space-times-time-1_' + self.classes['space-times-time-1']])
plt.axis('off');
ax42 = fig.add_subplot(6, 5, 9)
ax42.imshow(self.eq_images['space-times-time-2_' + self.classes['space-times-time-2']])
plt.axis('off');
ax43 = fig.add_subplot(6, 5, 14)
ax43.imshow(self.eq_images['space-times-time-3_' + self.classes['space-times-time-3']])
plt.axis('off');
ax5 = fig.add_subplot(6, 5, 5)
ax5.imshow(self.eq_images['norm_squared_of_unity_' + self.classes['norm_squared_of_unity']])
plt.axis('off');
ax21 = fig.add_subplot(6, 5, 16)
ax21.imshow(self.eq_images['time_' + eq_2.classes['time']])
plt.axis('off')
ax221 = fig.add_subplot(6, 5, 17)
ax221.imshow(self.eq_images['space-1_' + eq_2.classes['space-1']])
plt.axis('off');
ax222 = fig.add_subplot(6, 5, 22)
ax222.imshow(self.eq_images['space-2_' + eq_2.classes['space-2']])
plt.axis('off');
ax223 = fig.add_subplot(6, 5, 27)
ax223.imshow(self.eq_images['space-3_' + eq_2.classes['space-3']])
plt.axis('off');
ax23 = fig.add_subplot(6, 5, 18)
ax23.imshow(self.eq_images['causality_' + eq_2.classes['causality']])
plt.axis('off');
ax241 = fig.add_subplot(6, 5, 19)
ax241.imshow(self.eq_images['space-times-time-1_' + eq_2.classes['space-times-time-1']])
plt.axis('off');
ax242 = fig.add_subplot(6, 5, 24)
ax242.imshow(self.eq_images['space-times-time-2_' + eq_2.classes['space-times-time-2']])
plt.axis('off');
ax243 = fig.add_subplot(6, 5, 29)
ax243.imshow(self.eq_images['space-times-time-3_' + eq_2.classes['space-times-time-3']])
plt.axis('off');
ax25 = fig.add_subplot(6, 5, 20)
ax25.imshow(self.eq_images['norm_squared_of_unity_' + eq_2.classes['norm_squared_of_unity']])
plt.axis('off');
def __str__(self):
"""Prints all the equivalence relations."""
self.get_all_classes()
class_names = ["time", "space-1", "space-2", "space-3", "causality",
"space-times-time-1", "space-times-time-2", "space-times-time-3",
"norm_squared_of_unity"]
result = "The equivalence classes for this pair of events are as follows...\n"
result += "q1: {}\n".format(QH(self.q1.q4()))
result += "q2: {}\n".format(QH(self.q2.q4()))
result += "q1_squared: {}\n".format(QH(self.q1_square.q4()))
result += "q2_squared: {}\n".format(QH(self.q2_square.q4()))
result += "q1_norm_squared -1: {}\n".format(QH(self.q1_norm_squared_minus_1.q4()))
result += "q2_norm_squared -1: {}\n".format(QH(self.q2_norm_squared_minus_1.q4()))
for class_name in class_names:
result += "{cn:>20}: {c}\n".format(cn=class_name, c=self.classes[class_name])
return result
In [22]:
class TestEQ(unittest.TestCase):
"""Class to make sure all the functions work as expected."""
q1 = Q8([1.0, 0, 0, 2.0, 0, 3.0, 0, 4.0])
q2 = QH([0, 4.0, -3.0, 0])
eq_11 = EQ(q1, q1)
eq_12 = EQ(q1, q2)
def test_EQ_assignment(self):
self.assertTrue(self.eq_12.q1.dt.p == 1)
self.assertTrue(self.eq_12.q1.dt.n == 0)
self.assertTrue(self.eq_12.q1_square.dt.p == 0)
self.assertTrue(self.eq_12.q1_square.dt.n == 28)
self.assertTrue(self.eq_12.q1_norm_squared_minus_1.dt.p == 29)
self.assertTrue(self.eq_12.q1_norm_squared_minus_1.dt.n == 0)
self.assertTrue(self.eq_12.q2.dt.p == 0)
self.assertTrue(self.eq_12.q2.dt.n == 0)
def test_get_class(self):
"""Test all time equivalence classes."""
names = {'class': 'time', 'positive': 'future', 'negative': 'past', 'divider': 'now'}
result = self.eq_12.get_class(self.q1, self.q1, names, 'dt')
self.assertTrue(result == 'future_exact')
def test_time(self):
"""Test all time equivalence classes."""
q_now = Q8()
eq_zero = EQ(q_now, q_now)
self.assertTrue(eq_zero.time() == 'now_exact')
self.assertTrue(self.eq_12.time() == 'disjoint')
q1f = QH([4.0, 4.0, 4.0, 4.0])
q1fe = QH([1.0, 4.0, 4.0, 4.0])
self.assertTrue(EQ(self.q1, q1f).time() == 'future')
self.assertTrue(EQ(self.q1, q1fe).time() == 'future_exact')
q1p = QH([-4.0, 4.0, 4.0, 4.0])
q1pe = QH([-4.0, 1.0, 2.0, 3.0])
q1pp = QH([-1.0, 1.0, 2.0, 3.0])
self.assertTrue(EQ(q1p, q1pp).time() == 'past')
self.assertTrue(EQ(q1p, q1pe).time() == 'past_exact')
def test_space(self):
"""Test space equivalence class."""
q_now = Q8()
eq_zero = EQ(q_now, q_now)
self.assertTrue(eq_zero.space()[0] == 'here_exact')
self.assertTrue(eq_zero.space()[1] == 'here_exact')
self.assertTrue(eq_zero.space()[2] == 'here_exact')
self.assertTrue(self.eq_11.space()[0] == 'left_exact')
self.assertTrue(self.eq_11.space()[1] == 'down_exact')
self.assertTrue(self.eq_11.space()[2] == 'far_exact')
self.assertTrue(self.eq_12.space()[0] == 'disjoint')
self.assertTrue(self.eq_12.space()[1] == 'down_exact')
self.assertTrue(self.eq_12.space()[2] == 'disjoint')
q_sp = Q8([1, 0, 0, 4, 0, 6, 0, 8])
eq_sp = EQ(self.q1, q_sp)
self.assertTrue(eq_sp.space()[0] == 'left')
self.assertTrue(eq_sp.space()[1] == 'down')
self.assertTrue(eq_sp.space()[2] == 'far')
def test_causality(self):
"""Test all time equivalence classes."""
q_now = Q8()
eq_zero = EQ(q_now, q_now)
self.assertTrue(eq_zero.causality() == 'light-like_exact')
self.assertTrue(self.eq_12.causality() == 'space-like')
self.assertTrue(self.eq_11.causality() == 'space-like_exact')
tl = Q8([4, 0, 0, 0, 0, 0, 0, 0])
t2 = Q8([5, 0, 0, 3, 0, 0, 0, 0])
t3 = Q8([5, 0, 3, 0, 1, 0, 0, 0])
eq_t1_t2 = EQ(tl, t2)
eq_t1_t3 = EQ(tl, t3)
self.assertTrue(eq_t1_t2.causality() == 'time-like_exact')
self.assertTrue(eq_t1_t3.causality() == 'time-like')
def test_space_times_time(self):
"""Test space equivalence class."""
q_now = Q8()
eq_zero = EQ(q_now, q_now)
self.assertTrue(eq_zero.space_times_time()[0] == 'here-now_exact')
self.assertTrue(eq_zero.space_times_time()[1] == 'here-now_exact')
self.assertTrue(eq_zero.space_times_time()[2] == 'here-now_exact')
self.assertTrue(self.eq_11.space_times_time()[0] == 'future-left_exact')
self.assertTrue(self.eq_11.space_times_time()[1] == 'future-down_exact')
self.assertTrue(self.eq_11.space_times_time()[2] == 'future-far_exact')
self.assertTrue(self.eq_12.space_times_time()[0] == 'disjoint')
self.assertTrue(self.eq_12.space_times_time()[1] == 'future-down_exact')
self.assertTrue(self.eq_12.space_times_time()[2] == 'disjoint')
def test_norm_squared_of_unity(self):
self.assertTrue(self.eq_11.norm_squared_of_unity() == 'greater_than_unity_exact')
q_1 = Q8([1, 0, 0, 0, 0, 0, 0, 0])
q_small = Q8([0.1, 0, 0, 0.2, 0, 0, 0, 0])
q_tiny = Q8([0.001, 0, 0, 0.002, 0, 0, 0, 0])
eq_1 = EQ(q_1, q_1)
eq_q1_small = EQ(q_1, q_small)
eq_small_small = EQ(q_small, q_small)
eq_small_tiny = EQ(q_small, q_tiny)
self.assertTrue(eq_1.norm_squared_of_unity() == 'unity_exact')
self.assertTrue(eq_q1_small.norm_squared_of_unity() == 'disjoint')
self.assertTrue(eq_small_small.norm_squared_of_unity() == 'less_than_unity_exact')
self.assertTrue(eq_small_tiny.norm_squared_of_unity() == 'less_than_unity')
In [23]:
suite = unittest.TestLoader().loadTestsFromModule(TestEQ())
unittest.TextTestRunner().run(suite);
Create a class that can make many, many quaternions.
In [24]:
class QHArray(QH):
"""A class that can generate many quaternions."""
def __init__(self, q_min=QH([0, 0, 0, 0]), q_max=QH([0, 0, 0, 0]), n_steps=100):
"""Store min, max, and number of step data."""
self.q_min = q_min
self.q_max = q_max
self.n_steps = n_steps
def range(self, q_start, q_delta, n_steps, function=QH.add):
"""Can generate n quaternions"""
functions = {}
functions["add"] = QH.add
functions["dif"] = QH.dif
functions["product"] = QH.product
# To do: figure out the operator used in qtype
q_0 = q_start
q_0_qtype = q_0.qtype
self.set_min_max(q_0, first=True)
yield q_0
for n in range(1, n_steps + 1):
q_1 = function(q_0, q_delta)
q_1.qtype = "{q0q}+{n}dQ".format(q0q=q_0_qtype, n=n)
q_0 = q_1.dupe()
self.set_min_max(q_1, first=False)
yield q_1
def set_min_max(self, q1, first=False):
"""Sets the minimum and maximum of a set of quaternions as needed."""
if first:
self.q_min = q1.dupe()
self.q_max = q1.dupe()
else:
if q1.t < self.q_min.t:
self.q_min.t = q1.t
elif q1.t > self.q_max.t:
self.q_max.t = q1.t
if q1.x < self.q_min.x:
self.q_min.x = q1.x
elif q1.x > self.q_max.x:
self.q_max.x = q1.x
if q1.y < self.q_min.y:
self.q_min.y = q1.y
elif q1.y > self.q_max.y:
self.q_max.y = q1.y
if q1.z < self.q_min.z:
self.q_min.z = q1.z
elif q1.z > self.q_max.z:
self.q_max.z = q1.z
def symbol_sub(self, TXYZ_expression, q1):
"""Given a Symbol expression in terms of T X, Y, and Z, plugs in values for q1.t, q1.x, q1.y, and q1.z"""
new_t = TXYZ_expression.t.subs(T, q1.t).subs(X, q1.x).subs(Y, q1.y).subs(Z, q1.z)
new_x = TXYZ_expression.x.subs(T, q1.t).subs(X, q1.x).subs(Y, q1.y).subs(Z, q1.z)
new_y = TXYZ_expression.y.subs(T, q1.t).subs(X, q1.x).subs(Y, q1.y).subs(Z, q1.z)
new_z = TXYZ_expression.z.subs(T, q1.t).subs(X, q1.x).subs(Y, q1.y).subs(Z, q1.z)
return QH([new_t, new_x, new_y, new_z])
In [25]:
class TestQHArray(unittest.TestCase):
"""Test array making software."""
t1=QH([1,2,3,4])
qd=QH([10, .2, .3, 1])
qha = QHArray()
def test_range(self):
q_list = list(self.qha.range(self.t1, self.qd, 10))
self.assertTrue(len(q_list) == 11)
self.assertTrue(q_list[10].qtype == "Q+10dQ")
self.assertTrue(q_list[10].z == 14)
def test_min_max(self):
q_list = list(self.qha.range(self.t1, self.qd, 10))
self.assertTrue(self.qha.q_min.t < 1.01)
self.assertTrue(self.qha.q_max.t > 100)
self.assertTrue(self.qha.q_min.x < 2.01)
self.assertTrue(self.qha.q_max.x > 2.9)
self.assertTrue(self.qha.q_min.y < 4.01)
self.assertTrue(self.qha.q_max.y > 5.8)
self.assertTrue(self.qha.q_min.z < 6.01)
self.assertTrue(self.qha.q_max.z > 13.9)
In [26]:
suite = unittest.TestLoader().loadTestsFromModule(TestQHArray())
unittest.TextTestRunner().run(suite);
In [27]:
class QHaArray(QHa):
"""A class that can generate many quaternions."""
def __init__(self, q_min=QHa([0, 0, 0, 0]), q_max=QHa([0, 0, 0, 0]), n_steps=100):
"""Store min, max, and number of step data."""
self.q_min = q_min
self.q_max = q_max
self.n_steps = n_steps
def range(self, q_start, q_delta, n_steps, function=QHa.add):
"""Can generate n quaternions"""
functions = {}
functions["add"] = QHa.add
functions["dif"] = QHa.dif
functions["product"] = QHa.product
# To do: figure out the operator used in qtype
q_0 = q_start
q_0_qtype = q_0.qtype
self.set_min_max(q_0, first=True)
yield q_0
for n in range(1, n_steps + 1):
q_1 = function(q_0, q_delta)
q_1.qtype = "{q0q}+{n}dQ".format(q0q=q_0_qtype, n=n)
q_0 = q_1.dupe()
self.set_min_max(q_1, first=False)
yield q_1
def set_min_max(self, q1, first=False):
"""Sets the minimum and maximum of a set of quaternions as needed."""
if first:
self.q_min = q1.dupe()
self.q_max = q1.dupe()
else:
if q1.a[0] < self.q_min.a[0]:
self.q_min.a[0] = q1.a[0]
elif q1.a[0] > self.q_max.a[0]:
self.q_max.a[0] = q1.a[0]
if q1.a[1] < self.q_min.a[1]:
self.q_min.a[1] = q1.a[1]
elif q1.a[1] > self.q_max.a[1]:
self.q_max.a[1] = q1.a[1]
if q1.a[2] < self.q_min.a[2]:
self.q_min.a[2] = q1.a[2]
elif q1.a[2] > self.q_max.a[2]:
self.q_max.a[2] = q1.a[2]
if q1.a[3] < self.q_min.a[3]:
self.q_min.a[3] = q1.a[3]
elif q1.a[3] > self.q_max.a[3]:
self.q_max.a[3] = q1.a[3]
In [28]:
class TestQHaArray(unittest.TestCase):
"""Test array making software."""
t1 = QHa([1,2,3,4])
qd = QHa([10, .2, .3, 1])
qha = QHaArray()
def test_range(self):
q_list = list(self.qha.range(self.t1, self.qd, 10))
self.assertTrue(len(q_list) == 11)
self.assertTrue(q_list[10].qtype == "Q+10dQ")
self.assertTrue(q_list[10].a[3] == 14)
def test_min_max(self):
q_list = list(self.qha.range(self.t1, self.qd, 10))
self.assertTrue(self.qha.q_min.a[0] < 1.01)
self.assertTrue(self.qha.q_max.a[0] > 100)
self.assertTrue(self.qha.q_min.a[1] < 2.01)
self.assertTrue(self.qha.q_max.a[1] > 3.9)
self.assertTrue(self.qha.q_min.a[2] < 3.01)
self.assertTrue(self.qha.q_max.a[2] > 4.8)
self.assertTrue(self.qha.q_min.a[3] < 4.01)
self.assertTrue(self.qha.q_max.a[3] > 13.9)
In [29]:
suite = unittest.TestLoader().loadTestsFromModule(TestQHaArray())
unittest.TextTestRunner().run(suite);
Any quaternion can be viewed as the sum of n other quaternions. This is common to see in quantum mechanics, whose needs are driving the development of this class and its methods.
In [30]:
class QHStates(QH):
"""A class made up of many quaternions."""
def __init__(self, qs=None, qtype="", representation=""):
self.qs = qs
if qs is None:
self.d, self.dim, self.dimensions = 0, 0, 0
else:
self.d, self.dim, self.dimensions = len(qs), len(qs), len(qs)
self.qtype = qtype
self.representation = representation
def __str__(self):
"""Print out all the states."""
states = ''
for n, q in enumerate(self.qs, start=1):
states = states + "n={}: {}\n".format(n, q)
return states.rstrip()
def print_states(self, label, spacer=False):
"""Utility for printing states as a quaternion series."""
print(label)
for n, q in enumerate(self.qs, start=1):
print("n={}: {}".format(n, q))
print("sum= {ss}".format(ss=self.summation()))
if spacer:
print("")
def equals(self, q1):
"""Test if two states are equal."""
if self.dim != q1.dim:
return False
result = True
for selfq, q1q in zip(self.qs, q1.qs):
if not selfq.equals(q1q):
result = False
return result
def conj(self, conj_type=0):
"""Take the conjgates of states, default is zero, but also can do 1 or 2."""
new_states = []
for bra in self.qs:
new_states.append(bra.conj(conj_type))
return QHStates(new_states)
def flip_signs(self):
"""Flip signs of all states."""
new_states = []
for bra in self.qs:
new_states.append(bra.flip_signs())
return QHStates(new_states)
def invert(self):
"""Invert states."""
new_states = []
for bra in self.qs:
new_states.append(bra.invert())
return QHStates(new_states)
def normalize(self):
"""Normalize all states."""
new_states = []
q_dim = QH([math.sqrt(1/self.dim), 0, 0, 0])
for bra in self.qs:
new_states.append(bra.normalize().product(q_dim))
return QHStates(new_states)
def add(self, ket):
"""Add two states."""
if self.dim != ket.dim:
oops = "The dimensions are not the same: {} != {}".format(self.dim, ket.dim)
print(oops)
return None
new_states = []
for bra, ket in zip(self.qs, ket.qs):
new_states.append(bra.add(ket))
return QHStates(new_states)
def summation(self):
"""Add them all up, return one quaternion."""
result = None
for q in self.qs:
if result == None:
result = q
else:
result = result.add(q)
return result
def dif(self, ket):
"""Take the difference of two states."""
new_states = []
for bra, ket in zip(self.qs, ket.qs):
new_states.append(bra.dif(ket))
return(QHStates(new_states))
def diagonal(self, dim):
"""Make a state dim*dim with q or qs along the 'diagonal'."""
diagonal = []
if len(self.qs) == 1:
q_values = [self.qs[0]] * dim
elif len(self.qs) == dim:
q_values = self.qs
else:
print("Oops, need the length to be equal to the dimensions.")
for i in range(dim):
for j in range(dim):
if i == j:
diagonal.append(q_values.pop(0))
else:
diagonal.append(QH().q_0())
return QHStates(diagonal)
def product(self, product_type, bra=None, ket=None, operator=None, kind="", reverse=False):
"""Forms the quaternion product for each state."""
if product_type == 'bra':
bra = self
elif product_type == 'ket':
ket = self
elif product_type == 'operator':
operator = self
else:
print("Oops, need to set product_type to bra, ket, or operator.")
return None
def _check_dimensions(op_dim=0, state_1_dim=0, state_2_dim=0, equals=False):
"""Make sure the states and operators are the right sizes. The operator dimension is either
equal to 1 or the product of the bra and ket dimensions."""
oops = ''
if equals:
if state_1_dim != state_2_dim:
oops = "states have different dimensions: {} != {}".format(state_1_dim, state_2_dim)
elif state_2_dim == 0:
if (op_dim % state_1_dim != 0) and (op_dim != 1):
oops = "Operator dimensions don't divide nicely by the state vector: {} % {}".format(
op_dim, state_1_dim)
else:
if (op_dim != state_1_dim * state_2_dim) and (op_dim == 1 and (state_1_dim != state_2_dim)):
oops = "Operator dimensions do not equal the product of the states: {} != {} * {}".format(
op_dim, state_1_dim, state_2_dim)
if oops:
print(oops)
return False
else:
return True
new_states = []
if bra is None and ket is None:
return None
elif bra is None and operator is None:
return ket
elif ket is None and operator is None:
return bra
# <A|B>
elif operator is None:
if _check_dimensions(state_1_dim=bra.dim, state_2_dim=ket.dim, equals=True):
for b, k in zip(bra.qs, ket.qs):
new_states.append(b.product(k, kind, reverse))
# Op|B>
elif bra is None:
if _check_dimensions(op_dim=operator.dim, state_1_dim=ket.dim):
if operator.dim == 1:
one_diagonal = operator.diagonal(ket.dim)
opb = one_diagonal.qs
else:
opb = operator.qs
for ops in zip(*[iter(opb)] * ket.dim):
ok = None
for op, k in zip(ops, ket.qs):
if ok is None:
ok = op.product(k, kind, reverse)
else:
ok = ok.add(op.product(k, kind, reverse))
new_states.append(ok)
# <A|Op
elif ket is None:
if _check_dimensions(op_dim=operator.dim, state_1_dim=bra.dim):
if operator.dim == 1:
one_diagonal = operator.diagonal(bra.dim)
aop = one_diagonal.qs
else:
aop = operator.qs
for ops in zip(*[iter(aop)]*bra.dim):
bop = None
for b, op in zip(bra.qs, ops):
if bop is None:
bop = b.product(op, kind, reverse)
else:
bop = bop.add(b.product(op, kind, reverse))
new_states.append(bop)
# <A|Op|B>
else:
if _check_dimensions(op_dim=operator.dim, state_1_dim=bra.dim, state_2_dim=ket.dim):
new_ket = []
if operator.dim == 1:
one_diagonal = operator.diagonal(ket.dim)
opb = one_diagonal.qs
else:
opb = operator.qs
for ops in zip(*[iter(opb)]*ket.dim):
ok = None
for op, k in zip(ops, ket.qs):
if ok is None:
ok = op.product(k, kind, reverse)
else:
ok = ok.add(op.product(k, kind, reverse))
new_ket.append(ok)
new_ket_state = QHStates(new_ket)
for b, k in zip(bra.qs, new_ket_state.qs):
new_states.append(b.product(k, kind, reverse))
return QHStates(new_states)
def Euclidean_product(self, product_type, bra=None, ket=None, operator=None, kind="", reverse=False):
"""Forms the Euclidean product, what is used in QM all the time."""
if bra is not None:
bra = bra.conj()
if product_type == 'bra':
self = self.conj()
return self.product(product_type, bra, ket, operator, kind, reverse)
def op_n(self, n, first=True, kind="", reverse=False):
"""Mulitply an operator times a number, in that order. Set first=false for n * Op"""
new_states = []
for op in self.qs:
if first:
new_states.append(op.product(n, kind, reverse))
else:
new_states.append(n.product(op, kind, reverse))
return QHStates(new_states)
def norm_squared(self):
"""Take the Euclidean product of each state and add it up, returning one quaternion."""
return self.Euclidean_product('bra', ket=self)
def transpose(self, m=0, n=0):
"""Transposes a series."""
if m==0:
# test if it is square.
if math.sqrt(self.dim).is_integer():
m = sp.sqrt(self.dim)
n = m
if m * n != self.dim:
print("Oops, m * n != state dim: {} {} != {}".format(m, n, self.dim))
return None
new_states = [0] * self.dim
for mi in range(m):
for ni in range(n):
current = mi * n + ni
transpose = mi + ni * n
new_states[transpose] = self.qs[current]
return QHStates(new_states)
def Hermitian_conj(self, m=0, n=0, conj_type=0):
"""Returns the Hermitian conjugate."""
return self.transpose(m, n).conj(conj_type)
def dagger(self, m=0, n=0, conj_type=0):
"""Just calls Hermitian_conj()"""
return self.Hermitian_conj(m, n, conj_type)
def is_square(self):
"""Tests if a quaternion series is square, meaning the dimenion is n^2."""
return math.sqrt(self.dim).is_integer()
def is_Hermitian(self):
"""Tests if a series is Hermitian."""
hc = self.Hermitian_conj()
return self.equals(hc)
@staticmethod
def sigma(kind, theta=None, phi=None):
"""Returns a sigma when given a type like, x, y, z, xy, xz, yz, xyz, with optional angles theta and phi."""
q0, q1, qi = QH().q_0(), QH().q_1(), QH().q_i()
# Should work if given angles or not.
if theta is None:
sin_theta = 1
cos_theta = 1
else:
sin_theta = math.sin(theta)
cos_theta = math.cos(theta)
if phi is None:
sin_phi = 1
cos_phi = 1
else:
sin_phi = math.sin(phi)
cos_phi = math.cos(phi)
x_factor = q1.product(QH([sin_theta * cos_phi, 0, 0, 0]))
y_factor = qi.product(QH([sin_theta * sin_phi, 0, 0, 0]))
z_factor = q1.product(QH([cos_theta, 0, 0, 0]))
sigmas = {}
sigma['x'] = QHStates([q0, x_factor, x_factor, q0])
sigma['y'] = QHStates([q0, y_factor, y_factor.flip_signs(), q0])
sigma['z'] = QHStates([z_factor, q0, q0, z_factor.flip_signs()])
sigmas['xy'] = sigma['x'].add(sigma['y'])
sigmas['xz'] = sigma['x'].add(sigma['z'])
sigmas['yz'] = sigma['y'].add(sigma['z'])
sigmas['xyz'] = sigma['xy'].add(sigma['z'])
if kind not in sigma:
print("Oops, I only know about x, y, z, and their combinations.")
return None
return signma[kind].normalize()
In [31]:
class TestQHStates(unittest.TestCase):
"""Test states."""
q0 = QH().q_0()
q1 = QH().q_1()
qi = QH().q_i()
q0_q1 = QHStates([q0, q1])
q1_q0 = QHStates([q1, q0])
q1_qi = QHStates([q1, qi])
A = QHStates([QH([4,0,0,0]),QH([0,1,0,0])])
B = QHStates([QH([0,0,1,0]),QH([0,0,0,2]),QH([0,3,0,0])])
Op = QHStates([QH([3,0,0,0]),QH([0,1,0,0]),QH([0,0,2,0]),QH([0,0,0,3]),QH([2,0,0,0]),QH([0,4,0,0])])
Op4i = QHStates([QH([0,4,0,0])])
q1234 = QHStates([QH([1, 1, 0, 0]), QH([2, 1, 0, 0]), QH([3, 1, 0, 0]), QH([4, 1, 0, 0])])
sigma_y = QHStates([QH([1, 0, 0, 0]), QH([0, -1, 0, 0]), QH([0, 1, 0, 0]), QH([-1, 0, 0, 0])])
qn = QHStates([QH([3,0,0,4])])
def test_init(self):
self.assertTrue(self.q0_q1.dim == 2)
def test_equals(self):
self.assertTrue(self.A.equals(self.A))
self.assertFalse(self.A.equals(self.B))
def test_conj(self):
qc = self.q1_qi.conj()
qc1 = self.q1_qi.conj(1)
print("q1_qi*: ", qc)
print("q1_qc*1: ", qc1)
self.assertTrue(qc.qs[1].x == -1)
self.assertTrue(qc1.qs[1].x == 1)
def test_flip_signs(self):
qf = self.q1_qi.flip_signs()
print("-q1_qi: ", qf)
self.assertTrue(qf.qs[1].x == -1)
def test_normalize(self):
qn = self.qn.normalize()
print("Op normalized: ", qn)
self.assertAlmostEqual(qn.qs[0].t, 0.6)
self.assertTrue(qn.qs[0].z == 0.8)
def test_summation(self):
q_01_sum = self.q0_q1.summation()
print("sum: ", q_01_sum)
self.assertTrue(type(q_01_sum) is QH)
self.assertTrue(q_01_sum.t == 1)
def test_add(self):
q_0110_add = self.q0_q1.add(self.q1_q0)
print("add 01 10: ", q_0110_add)
self.assertTrue(q_0110_add.qs[0].t == 1)
self.assertTrue(q_0110_add.qs[1].t == 1)
def test_dif(self):
q_0110_dif = self.q0_q1.dif(self.q1_q0)
print("dif 01 10: ", q_0110_dif)
self.assertTrue(q_0110_dif.qs[0].t == -1)
self.assertTrue(q_0110_dif.qs[1].t == 1)
def test_product_AA(self):
AA = self.A.product('bra', ket=self.A)
print("AA: ", AA)
self.assertTrue(AA.qs[0].equals(QH([16, 0, 0, 0])))
self.assertTrue(AA.qs[1].equals(QH([-1, 0, 0, 0])))
def test_Euclidean_product_AA(self):
AA = self.A.Euclidean_product('bra', ket=self.A)
print("A* A", AA)
self.assertTrue(AA.qs[0].equals(QH([16, 0, 0, 0])))
self.assertTrue(AA.qs[1].equals(QH([1, 0, 0, 0])))
def test_product_AOp(self):
AOp = self.A.product('bra', operator=self.Op)
print("A Op: ", AOp)
self.assertTrue(AOp.qs[0].equals(QH([11, 0, 0, 0])))
self.assertTrue(AOp.qs[1].equals(QH([0, 0, 5, 0])))
self.assertTrue(AOp.qs[2].equals(QH([4, 0, 0, 0])))
def test_Euclidean_product_AOp(self):
AOp = self.A.Euclidean_product('bra', operator=self.Op)
print("A* Op: ", AOp)
self.assertTrue(AOp.qs[0].equals(QH([13, 0, 0, 0])))
self.assertTrue(AOp.qs[1].equals(QH([0, 0, 11, 0])))
self.assertTrue(AOp.qs[2].equals(QH([12, 0, 0, 0])))
def test_product_AOp4i(self):
AOp4i = self.A.product('bra', operator=self.Op4i)
print("A Op4i: ", AOp4i)
self.assertTrue(AOp4i.qs[0].equals(QH([0, 16, 0, 0])))
self.assertTrue(AOp4i.qs[1].equals(QH([-4, 0, 0, 0])))
def test_Euclidean_product_AOp4i(self):
AOp4i = self.A.Euclidean_product('bra', operator=self.Op4i)
print("A* Op4i: ", AOp4i)
self.assertTrue(AOp4i.qs[0].equals(QH([0, 16, 0, 0])))
self.assertTrue(AOp4i.qs[1].equals(QH([4, 0, 0, 0])))
def test_product_OpB(self):
OpB = self.B.product('ket', operator=self.Op)
print("Op B: ", OpB)
self.assertTrue(OpB.qs[0].equals(QH([0, 0, 1, -6])))
self.assertTrue(OpB.qs[1].equals(QH([-12, -3, 0, 4])))
def test_Euclidean_product_OpB(self):
OpB = self.B.Euclidean_product('ket', operator=self.Op)
print("Op B: ", OpB)
self.assertTrue(OpB.qs[0].equals(QH([0, 0, 1, -6])))
self.assertTrue(OpB.qs[1].equals(QH([-12, -3, 0, 4])))
def test_product_AOpB(self):
AOpB = self.A.product('bra', operator=self.Op, ket=self.B)
print("A Op B: ", AOpB)
self.assertTrue(AOpB.qs[0].equals(QH([0, 0, 4, -24])))
self.assertTrue(AOpB.qs[1].equals(QH([3, -12, -4, 0])))
def test_Euclidean_product_AOpB(self):
AOpB = self.A.Euclidean_product('bra', operator=self.Op, ket=self.B)
print("A* Op B: ", AOpB)
self.assertTrue(AOpB.qs[0].equals(QH([0, 0, 4, -24])))
self.assertTrue(AOpB.qs[1].equals(QH([-3, 12, 4, 0])))
def test_product_AOp4i(self):
AOp4i = self.A.product('bra', operator=self.Op4i)
print("A Op4i: ", AOp4i)
self.assertTrue(AOp4i.qs[0].equals(QH([0, 16, 0, 0])))
self.assertTrue(AOp4i.qs[1].equals(QH([-4, 0, 0, 0])))
def test_Euclidean_product_AOp4i(self):
AOp4i = self.A.Euclidean_product('bra', operator=self.Op4i)
print("A* Op4i: ", AOp4i)
self.assertTrue(AOp4i.qs[0].equals(QH([0, 16, 0, 0])))
self.assertTrue(AOp4i.qs[1].equals(QH([4, 0, 0, 0])))
def test_product_Op4iB(self):
Op4iB = self.B.product('ket', operator=self.Op4i)
print("Op4i B: ", Op4iB)
self.assertTrue(Op4iB.qs[0].equals(QH([0, 0, 0, 4])))
self.assertTrue(Op4iB.qs[1].equals(QH([0, 0, -8, 0])))
self.assertTrue(Op4iB.qs[2].equals(QH([-12, 0, 0, 0])))
def test_Euclidean_product_Op4iB(self):
Op4iB = self.B.Euclidean_product('ket', operator=self.Op4i)
print("Op4i B: ", Op4iB)
self.assertTrue(Op4iB.qs[0].equals(QH([0, 0, 0, 4])))
self.assertTrue(Op4iB.qs[1].equals(QH([0, 0, -8, 0])))
self.assertTrue(Op4iB.qs[2].equals(QH([-12, 0, 0, 0])))
def test_product_AOp4iB(self):
AOp4iB = self.A.product('bra', operator=self.Op4i, ket=self.B)
print("A* Op4i B: ", AOp4iB)
self.assertTrue(AOp4iB.dim == 0)
def test_Euclidean_product_AOp4iB(self):
AOp4iB = self.A.Euclidean_product('bra', operator=self.Op4i, ket=self.B)
print("A* Op4i B: ", AOp4iB)
self.assertTrue(AOp4iB.dim == 0)
def test_op_n(self):
opn = self.Op.op_n(n=self.qi)
print("op_n: ", opn)
self.assertTrue(opn.qs[0].x == 3)
def test_transpose(self):
opt = self.q1234.transpose()
print("op1234 transposed: ", opt)
self.assertTrue(opt.qs[0].t == 1)
self.assertTrue(opt.qs[1].t == 3)
self.assertTrue(opt.qs[2].t == 2)
self.assertTrue(opt.qs[3].t == 4)
def test_Hermitian_conj(self):
q_hc = self.q1234.Hermitian_conj()
print("op1234 Hermitian_conj: ", q_hc)
self.assertTrue(q_hc.qs[0].t == 1)
self.assertTrue(q_hc.qs[1].t == 3)
self.assertTrue(q_hc.qs[2].t == 2)
self.assertTrue(q_hc.qs[3].t == 4)
self.assertTrue(q_hc.qs[0].x == -1)
self.assertTrue(q_hc.qs[1].x == -1)
self.assertTrue(q_hc.qs[2].x == -1)
self.assertTrue(q_hc.qs[3].x == -1)
def test_is_Hermitian(self):
self.assertTrue(self.sigma_y.is_Hermitian())
self.assertFalse(self.q1234.is_Hermitian())
def test_is_square(self):
self.assertFalse(self.Op.is_square())
self.assertTrue(self.Op4i.is_square())
suite = unittest.TestLoader().loadTestsFromModule(TestQHStates())
unittest.TextTestRunner().run(suite);
Repeat this exercise for:
QHa Q8 Q8a
by old fashioned cut and paste with minor tweaks (boring).
In [32]:
class QHaStates(QHa):
"""A class made up of many quaternions."""
def __init__(self, qs=None, qtype="", representation=""):
self.qs = qs
if qs is None:
self.d, self.dim, self.dimensions = 0, 0, 0
else:
self.d, self.dim, self.dimensions = len(qs), len(qs), len(qs)
def __str__(self):
"""Print out all the states."""
states = ''
for n, q in enumerate(self.qs, start=1):
states = states + "n={}: {}\n".format(n, q)
return states.rstrip()
def print_states(self, label, spacer=False):
"""Utility for printing states as a quaternion series."""
print(label)
for n, q in enumerate(self.qs, start=1):
print("n={}: {}".format(n, q))
print("sum= {ss}".format(ss=self.summation()))
if spacer:
print("")
def summation(self):
"""Add them all up, return one quaternion."""
result = None
for q in self.qs:
if result == None:
result = q
else:
result = result.add(q)
return result
def equals(self, q1):
"""Test if two states are equal."""
if self.dim != q1.dim:
return False
result = True
for selfq, q1q in zip(self.qs, q1.qs):
if not selfq.equals(q1q):
result = False
return result
def conj(self, conj_type=0):
"""Take the conjgates of states, default is zero, but also can do 1 or 2."""
new_states = []
for bra in self.qs:
new_states.append(bra.conj(conj_type))
return(QHaStates(new_states))
def flip_signs(self):
"""Flip signs of all states."""
new_states = []
for bra in self.qs:
new_states.append(bra.flip_signs())
return QHStates(new_states)
def normalize(self):
"""Normalize all states."""
new_states = []
q_dim = QHa([math.sqrt(1/self.dim), 0, 0, 0])
for bra in self.qs:
new_states.append(bra.normalize().product(q_dim))
return QHaStates(new_states)
def add(self, ket):
"""Add two states."""
new_states = []
for bra, ket in zip(self.qs, ket.qs):
new_states.append(bra.add(ket))
return(QHaStates(new_states))
def dif(self, ket):
"""Take the difference of two states."""
new_states = []
for bra, ket in zip(self.qs, ket.qs):
new_states.append(bra.dif(ket))
return(QHaStates(new_states))
def diagonal(self, dim):
"""Make a state dim*dim with q along the 'diagonal'."""
diagonal = []
if len(self.qs) == 1:
q_values = [self.qs[0]] * dim
elif len(self.qs) == dim:
q_values = self.qs
else:
print("Oops, need the length to be equal to the dimensions.")
for i in range(dim):
for j in range(dim):
if i == j:
diagonal.append(q_values.pop(0))
else:
diagonal.append(QHa().q_0())
return QHaStates(diagonal)
def product(self, product_type, bra=None, ket=None, operator=None, kind=""):
"""Forms the quaternion product for each state."""
if product_type == 'bra':
bra = self
elif product_type == 'ket':
ket = self
elif product_type == 'operator':
operator = self
else:
print("Oops, need to set product_type to bra, ket, or operator.")
return None
def _check_dimensions(op_dim=0, state_1_dim=0, state_2_dim=0, equals=False):
"""Make sure the states and operators are the right sizes. The operator dimension is either
equal to 1 or the product of the bra and ket dimensions."""
oops = ''
if equals:
if state_1_dim != state_2_dim:
oops = "states have different dimensions: {} != {}".format(state_1_dim, state_2_dim)
elif state_2_dim == 0:
if (op_dim % state_1_dim != 0) and (op_dim != 1):
oops = "Operator dimensions don't divide nicely by the state vector: {} % {}".format(
op_dim, state_1_dim)
else:
if (op_dim != state_1_dim * state_2_dim) and (op_dim == 1 and (state_1_dim != state_2_dim)):
oops = "Operator dimensions do not equal the product of the states: {} != {} * {}".format(
op_dim, state_1_dim, state_2_dim)
if oops:
print(oops)
return False
else:
return True
new_states = []
if bra is None and ket is None:
return None
elif bra is None and operator is None:
return ket
elif ket is None and operator is None:
return bra
# <A|B>
elif operator is None:
if _check_dimensions(state_1_dim=bra.dim, state_2_dim=ket.dim, equals=True):
for b, k in zip(bra.qs, ket.qs):
new_states.append(b.product(k, kind))
# Op|B>
elif bra is None:
if _check_dimensions(op_dim=operator.dim, state_1_dim=ket.dim):
if operator.dim == 1:
one_diagonal = operator.diagonal(ket.dim)
opb = one_diagonal.qs
else:
opb = operator.qs
for ops in zip(*[iter(opb)] * ket.dim):
ok = None
for op, k in zip(ops, ket.qs):
if ok is None:
ok = op.product(k, kind)
else:
ok = ok.add(op.product(k, kind))
new_states.append(ok)
# <A|Op
elif ket is None:
if _check_dimensions(op_dim=operator.dim, state_1_dim=bra.dim):
if operator.dim == 1:
one_diagonal = operator.diagonal(bra.dim)
aop = one_diagonal.qs
else:
aop = operator.qs
for ops in zip(*[iter(aop)]*bra.dim):
bop = None
for b, op in zip(bra.qs, ops):
if bop is None:
bop = b.product(op, kind)
else:
bop = bop.add(b.product(op, kind))
new_states.append(bop)
# <A|Op|B>
else:
if _check_dimensions(op_dim=operator.dim, state_1_dim=bra.dim, state_2_dim=ket.dim):
new_ket = []
if operator.dim == 1:
one_diagonal = operator.diagonal(ket.dim)
opb = one_diagonal.qs
else:
opb = operator.qs
for ops in zip(*[iter(opb)]*ket.dim):
ok = None
for op, k in zip(ops, ket.qs):
if ok is None:
ok = op.product(k, kind)
else:
ok = ok.add(op.product(k, kind))
new_ket.append(ok)
new_ket_state = QHaStates(new_ket)
for b, k in zip(bra.qs, new_ket_state.qs):
new_states.append(b.product(k, kind))
return QHaStates(new_states)
def Euclidean_product(self, product_type, bra=None, ket=None, operator=None, kind=""):
"""Forms the Euclidean product, what is used in QM all the time."""
if bra is not None:
bra = bra.conj()
if product_type == 'bra':
self = self.conj()
return self.product(product_type, bra, ket, operator, kind)
def op_n(self, n, first=True, kind="", reverse=False):
"""Mulitply an operator times a number, in that order. Set first=false for n * Op"""
new_states = []
for op in self.qs:
if first:
new_states.append(op.product(n, kind, reverse))
else:
new_states.append(n.product(op, kind, reverse))
return QHaStates(new_states)
def norm_squared(self):
"""Take the Euclidean product of each state and add it up, returning one quaternion."""
norm = self.Euclidean_product(self).summation()
return norm
In [33]:
class TestQHaStates(unittest.TestCase):
"""Test states."""
q0 = QHa().q_0()
q1 = QHa().q_1()
qi = QHa().q_i()
q0_q1 = QHaStates([q0, q1])
q1_q0 = QHaStates([q1, q0])
q1_qi = QHaStates([q1, qi])
A = QHaStates([QHa([4,0,0,0]),QHa([0,1,0,0])])
B = QHaStates([QHa([0,0,1,0]),QHa([0,0,0,2]),QHa([0,3,0,0])])
Op = QHaStates([QHa([3,0,0,0]),QHa([0,1,0,0]),QHa([0,0,2,0]),QHa([0,0,0,3]),QHa([2,0,0,0]),QHa([0,4,0,0])])
Op4i = QHaStates([QHa([0,4,0,0])])
qn = QHaStates([QHa([3,0,0,4])])
def test_init(self):
self.assertTrue(self.q0_q1.dim == 2)
def test_equals(self):
self.assertTrue(self.A.equals(self.A))
self.assertFalse(self.A.equals(self.B))
def test_conj(self):
qc = self.q1_qi.conj()
qc1 = self.q1_qi.conj(1)
print("q1_qi*: ", qc)
print("q1_qc*1: ", qc1)
self.assertTrue(qc.qs[1].a[1] == -1)
self.assertTrue(qc1.qs[1].a[1] == 1)
def test_flip_signs(self):
qf = self.q1_qi.flip_signs()
print("-q1_qi: ", qf)
self.assertTrue(qf.qs[1].a[1] == -1)
def test_summation(self):
q_01_sum = self.q0_q1.summation()
print("sum: ", q_01_sum)
self.assertTrue(type(q_01_sum) is QHa)
self.assertTrue(q_01_sum.a[0] == 1)
def test_normalize(self):
qn = self.qn.normalize()
print("Op normalized: ", qn)
self.assertAlmostEqual(qn.qs[0].a[0], 0.6)
self.assertTrue(qn.qs[0].a[3] == 0.8)
def test_add(self):
q_0110_add = self.q0_q1.add(self.q1_q0)
print("add 01 10: ", q_0110_add)
self.assertTrue(q_0110_add.qs[0].a[0] == 1)
self.assertTrue(q_0110_add.qs[1].a[0] == 1)
def test_dif(self):
q_0110_dif = self.q0_q1.dif(self.q1_q0)
print("dif 01 10: ", q_0110_dif)
self.assertTrue(q_0110_dif.qs[0].a[0] == -1)
self.assertTrue(q_0110_dif.qs[1].a[0] == 1)
def test_product_AA(self):
AA = self.A.product('bra', ket=self.A)
print("AA: ", AA)
self.assertTrue(AA.qs[0].equals(QHa([16, 0, 0, 0])))
self.assertTrue(AA.qs[1].equals(QHa([-1, 0, 0, 0])))
def test_Euclidean_product_AA(self):
AA = self.A.Euclidean_product('bra', ket=self.A)
print("A* A", AA)
self.assertTrue(AA.qs[0].equals(QHa([16, 0, 0, 0])))
self.assertTrue(AA.qs[1].equals(QHa([1, 0, 0, 0])))
def test_product_AOp(self):
AOp = self.A.product('bra', operator=self.Op)
print("A Op: ", AOp)
self.assertTrue(AOp.qs[0].equals(QHa([11, 0, 0, 0])))
self.assertTrue(AOp.qs[1].equals(QHa([0, 0, 5, 0])))
self.assertTrue(AOp.qs[2].equals(QHa([4, 0, 0, 0])))
def test_Euclidean_product_AOp(self):
AOp = self.A.Euclidean_product('bra', operator=self.Op)
print("A* Op: ", AOp)
self.assertTrue(AOp.qs[0].equals(QHa([13, 0, 0, 0])))
self.assertTrue(AOp.qs[1].equals(QHa([0, 0, 11, 0])))
self.assertTrue(AOp.qs[2].equals(QHa([12, 0, 0, 0])))
def test_product_OpB(self):
OpB = self.B.product('ket', operator=self.Op)
print("Op B: ", OpB)
self.assertTrue(OpB.qs[0].equals(QHa([0, 0, 1, -6])))
self.assertTrue(OpB.qs[1].equals(QHa([-12, -3, 0, 4])))
def test_Euclidean_product_OpB(self):
OpB = self.B.Euclidean_product('ket', operator=self.Op)
print("Op B: ", OpB)
self.assertTrue(OpB.qs[0].equals(QHa([0, 0, 1, -6])))
self.assertTrue(OpB.qs[1].equals(QHa([-12, -3, 0, 4])))
def test_product_AOpB(self):
AOpB = self.A.product('bra', operator=self.Op, ket=self.B)
print("A Op B: ", AOpB)
self.assertTrue(AOpB.qs[0].equals(QHa([0, 0, 4, -24])))
self.assertTrue(AOpB.qs[1].equals(QHa([3, -12, -4, 0])))
def test_Euclidean_product_AOpB(self):
AOpB = self.A.Euclidean_product('bra', operator=self.Op, ket=self.B)
print("A* Op B: ", AOpB)
self.assertTrue(AOpB.qs[0].equals(QHa([0, 0, 4, -24])))
self.assertTrue(AOpB.qs[1].equals(QHa([-3, 12, 4, 0])))
def test_product_AOp4i(self):
AOp4i = self.A.product('bra', operator=self.Op4i)
print("A Op4i: ", AOp4i)
self.assertTrue(AOp4i.qs[0].equals(QHa([0, 16, 0, 0])))
self.assertTrue(AOp4i.qs[1].equals(QHa([-4, 0, 0, 0])))
def test_Euclidean_product_AOp4i(self):
AOp4i = self.A.Euclidean_product('bra', operator=self.Op4i)
print("A* Op4i: ", AOp4i)
self.assertTrue(AOp4i.qs[0].equals(QHa([0, 16, 0, 0])))
self.assertTrue(AOp4i.qs[1].equals(QHa([4, 0, 0, 0])))
def test_product_Op4iB(self):
Op4iB = self.B.product('ket', operator=self.Op4i)
print("Op4i B: ", Op4iB)
self.assertTrue(Op4iB.qs[0].equals(QHa([0, 0, 0, 4])))
self.assertTrue(Op4iB.qs[1].equals(QHa([0, 0, -8, 0])))
self.assertTrue(Op4iB.qs[2].equals(QHa([-12, 0, 0, 0])))
def test_Euclidean_product_Op4iB(self):
Op4iB = self.B.Euclidean_product('ket', operator=self.Op4i)
print("Op4i B: ", Op4iB)
self.assertTrue(Op4iB.qs[0].equals(QHa([0, 0, 0, 4])))
self.assertTrue(Op4iB.qs[1].equals(QHa([0, 0, -8, 0])))
self.assertTrue(Op4iB.qs[2].equals(QHa([-12, 0, 0, 0])))
def test_product_AOp4iB(self):
AOp4iB = self.A.product('bra', operator=self.Op4i, ket=self.B)
print("A* Op4i B: ", AOp4iB)
self.assertTrue(AOp4iB.dim == 0)
def test_Euclidean_product_AOp4iB(self):
AOp4iB = self.A.Euclidean_product('bra', operator=self.Op4i, ket=self.B)
print("A* Op4i B: ", AOp4iB)
self.assertTrue(AOp4iB.dim == 0)
def test_op_n(self):
opn = self.Op.op_n(n=self.qi)
print("op_n: ", opn)
self.assertTrue(opn.qs[0].a[1] == 3)
suite = unittest.TestLoader().loadTestsFromModule(TestQHaStates())
unittest.TextTestRunner().run(suite);
In [34]:
class Q8States(Q8):
"""A class made up of many quaternions."""
def __init__(self, qs=None, qtype="", representation=""):
self.qs = qs
if qs is None:
self.d, self.dim, self.dimensions = 0, 0, 0
else:
self.d, self.dim, self.dimensions = len(qs), len(qs), len(qs)
def __str__(self):
"""Print out all the states."""
states = ''
for n, q in enumerate(self.qs, start=1):
states = states + "n={}: {}\n".format(n, q)
return states.rstrip()
def print_states(self, label, spacer=False):
"""Utility for printing states as a quaternion series."""
print(label)
for n, q in enumerate(self.qs, start=1):
print("n={}: {}".format(n, q))
print("sum= {ss}".format(ss=self.summation()))
if spacer:
print("")
def equals(self, q1):
"""Test if two states are equal."""
if self.dim != q1.dim:
return False
result = True
for selfq, q1q in zip(self.qs, q1.qs):
if not selfq.equals(q1q):
result = False
return result
def conj(self, conj_type=0):
"""Take the conjgates of states, default is zero, but also can do 1 or 2."""
new_states = []
for bra in self.qs:
new_states.append(bra.conj(conj_type))
return(Q8States(new_states))
def flip_signs(self):
"""Flip signs of all states."""
new_states = []
for bra in self.qs:
new_states.append(bra.flip_signs())
return QHStates(new_states)
def normalize(self):
"""Normalize all states."""
new_states = []
q_dim = Q8([math.sqrt(1/self.dim), 0, 0, 0])
for bra in self.qs:
new_states.append(bra.normalize().product(q_dim))
return Q8States(new_states)
def summation(self):
"""Add them all up, return one quaternion."""
result = None
for q in self.qs:
if result == None:
result = q
else:
result = result.add(q)
return result
def add(self, ket):
"""Add two states."""
new_states = []
for bra, ket in zip(self.qs, ket.qs):
new_states.append(bra.add(ket))
return(Q8States(new_states))
def dif(self, ket):
"""Take the difference of two states."""
new_states = []
for bra, ket in zip(self.qs, ket.qs):
new_states.append(bra.dif(ket))
return(Q8States(new_states))
def diagonal(self, dim):
"""Make a state dim*dim with q along the 'diagonal'."""
diagonal = []
if len(self.qs) == 1:
q_values = [self.qs[0]] * dim
elif len(self.qs) == dim:
q_values = self.qs
else:
print("Oops, need the length to be equal to the dimensions.")
for i in range(dim):
for j in range(dim):
if i == j:
diagonal.append(q_values.pop(0))
else:
diagonal.append(Q8().q_0())
return Q8States(diagonal)
def product(self, product_type, bra=None, ket=None, operator=None, kind=""):
"""Forms the quaternion product for each state."""
if product_type == 'bra':
bra = self
elif product_type == 'ket':
ket = self
elif product_type == 'operator':
operator = self
else:
print("Oops, need to set product_type to bra, ket, or operator.")
return None
def _check_dimensions(op_dim=0, state_1_dim=0, state_2_dim=0, equals=False):
"""Make sure the states and operators are the right sizes. The operator dimension is either
equal to 1 or the product of the bra and ket dimensions."""
oops = ''
if equals:
if state_1_dim != state_2_dim:
oops = "states have different dimensions: {} != {}".format(state_1_dim, state_2_dim)
elif state_2_dim == 0:
if (op_dim % state_1_dim != 0) and (op_dim != 1):
oops = "Operator dimensions don't divide nicely by the state vector: {} % {}".format(
op_dim, state_1_dim)
else:
if (op_dim != state_1_dim * state_2_dim) and (op_dim == 1 and (state_1_dim != state_2_dim)):
oops = "Operator dimensions do not equal the product of the states: {} != {} * {}".format(
op_dim, state_1_dim, state_2_dim)
if oops:
print(oops)
return False
else:
return True
new_states = []
if bra is None and ket is None:
return None
elif bra is None and operator is None:
return ket
elif ket is None and operator is None:
return bra
# <A|B>
elif operator is None:
if _check_dimensions(state_1_dim=bra.dim, state_2_dim=ket.dim, equals=True):
for b, k in zip(bra.qs, ket.qs):
new_states.append(b.product(k, kind))
# Op|B>
elif bra is None:
if _check_dimensions(op_dim=operator.dim, state_1_dim=ket.dim):
if operator.dim == 1:
one_diagonal = operator.diagonal(ket.dim)
opb = one_diagonal.qs
else:
opb = operator.qs
for ops in zip(*[iter(opb)] * ket.dim):
ok = None
for op, k in zip(ops, ket.qs):
if ok is None:
ok = op.product(k, kind)
else:
ok = ok.add(op.product(k, kind))
new_states.append(ok)
# <A|Op
elif ket is None:
if _check_dimensions(op_dim=operator.dim, state_1_dim=bra.dim):
if operator.dim == 1:
one_diagonal = operator.diagonal(bra.dim)
aop = one_diagonal.qs
else:
aop = operator.qs
for ops in zip(*[iter(aop)]*bra.dim):
bop = None
for b, op in zip(bra.qs, ops):
if bop is None:
bop = b.product(op, kind)
else:
bop = bop.add(b.product(op, kind))
new_states.append(bop)
# <A|Op|B>
else:
if _check_dimensions(op_dim=operator.dim, state_1_dim=bra.dim, state_2_dim=ket.dim):
new_ket = []
if operator.dim == 1:
one_diagonal = operator.diagonal(ket.dim)
opb = one_diagonal.qs
else:
opb = operator.qs
for ops in zip(*[iter(opb)]*ket.dim):
ok = None
for op, k in zip(ops, ket.qs):
if ok is None:
ok = op.product(k, kind)
else:
ok = ok.add(op.product(k, kind))
new_ket.append(ok)
new_ket_state = Q8States(new_ket)
for b, k in zip(bra.qs, new_ket_state.qs):
new_states.append(b.product(k, kind))
return Q8States(new_states)
def Euclidean_product(self, product_type, bra=None, ket=None, operator=None, kind=""):
"""Forms the Euclidean product, what is used in QM all the time."""
if bra is not None:
bra = bra.conj()
if product_type == 'bra':
self = self.conj()
return self.product(product_type, bra, ket, operator, kind)
def op_n(self, n, first=True, kind="", reverse=False):
"""Mulitply an operator times a number, in that order. Set first=false for n * Op"""
new_states = []
for op in self.qs:
if first:
new_states.append(op.product(n, kind, reverse))
else:
new_states.append(n.product(op, kind, reverse))
return Q8States(new_states)
def norm_squared(self):
"""Take the Euclidean product of each state and add it up, returning one quaternion."""
norm = self.Euclidean_product(self).summation()
return norm
In [35]:
class TestQ8States(unittest.TestCase):
"""Test states."""
q0 = Q8().q_0()
q1 = Q8().q_1()
qi = Q8().q_i()
q0_q1 = Q8States([q0, q1])
q1_q0 = Q8States([q1, q0])
q1_qi = Q8States([q1, qi])
A = Q8States([Q8([4,0,0,0]),Q8([0,1,0,0])])
B = Q8States([Q8([0,0,1,0]),Q8([0,0,0,2]),Q8([0,3,0,0])])
Op = Q8States([Q8([3,0,0,0]),Q8([0,1,0,0]),Q8([0,0,2,0]),Q8([0,0,0,3]),Q8([2,0,0,0]),Q8([0,4,0,0])])
Op4i = Q8States([Q8([0,4,0,0])])
qn = Q8States([Q8([3,0,0,4])])
def test_init(self):
self.assertTrue(self.q0_q1.dim == 2)
def test_equals(self):
self.assertTrue(self.A.equals(self.A))
self.assertFalse(self.A.equals(self.B))
def test_conj(self):
qc = self.q1_qi.conj()
qc1 = self.q1_qi.conj(1)
print("q1_qi*: ", qc)
print("q1_qc*1: ", qc1)
print("qc.qs[1]: ", qc.qs[1])
self.assertTrue(qc.qs[1].dx.n == 1)
self.assertTrue(qc1.qs[1].dx.p == 1)
def test_flip_signs(self):
qf = self.q1_qi.flip_signs()
print("-q1_qi: ", qf)
self.assertTrue(qf.qs[1].dx.n == 1)
def test_normalize(self):
qn = self.qn.normalize()
print("Op normalized: ", qn)
self.assertAlmostEqual(qn.qs[0].dt.p, 0.6)
self.assertTrue(qn.qs[0].dz.p == 0.8)
def test_summation(self):
q_01_sum = self.q0_q1.summation()
print("sum: ", q_01_sum)
self.assertTrue(type(q_01_sum) is Q8)
self.assertTrue(q_01_sum.dt.p== 1)
def test_add(self):
q_0110_add = self.q0_q1.add(self.q1_q0)
print("add 01 10: ", q_0110_add)
self.assertTrue(q_0110_add.qs[0].dt.p== 1)
self.assertTrue(q_0110_add.qs[1].dt.p== 1)
def test_dif(self):
q_0110_dif = self.q0_q1.dif(self.q1_q0)
print("dif 01 10: ", q_0110_dif)
self.assertTrue(q_0110_dif.qs[0].dt.n== 1)
self.assertTrue(q_0110_dif.qs[1].dt.p== 1)
def test_product_AA(self):
AA = self.A.product('bra', ket=self.A)
print("AA: ", AA)
self.assertTrue(AA.qs[0].equals(Q8([16, 0, 0, 0])))
self.assertTrue(AA.qs[1].equals(Q8([-1, 0, 0, 0])))
def test_Euclidean_product_AA(self):
AA = self.A.Euclidean_product('bra', ket=self.A)
print("A* A", AA)
self.assertTrue(AA.qs[0].equals(Q8([16, 0, 0, 0])))
self.assertTrue(AA.qs[1].equals(Q8([1, 0, 0, 0])))
def test_product_AOp(self):
AOp = self.A.product('bra', operator=self.Op)
print("A Op: ", AOp)
self.assertTrue(AOp.qs[0].equals(Q8([11, 0, 0, 0])))
self.assertTrue(AOp.qs[1].equals(Q8([0, 0, 5, 0])))
self.assertTrue(AOp.qs[2].equals(Q8([4, 0, 0, 0])))
def test_Euclidean_product_AOp(self):
AOp = self.A.Euclidean_product('bra', operator=self.Op)
print("A* Op: ", AOp)
self.assertTrue(AOp.qs[0].equals(Q8([13, 0, 0, 0])))
self.assertTrue(AOp.qs[1].equals(Q8([0, 0, 11, 0])))
self.assertTrue(AOp.qs[2].equals(Q8([12, 0, 0, 0])))
def test_product_OpB(self):
OpB = self.B.product('ket', operator=self.Op)
print("Op B: ", OpB)
self.assertTrue(OpB.qs[0].equals(Q8([0, 0, 1, -6])))
self.assertTrue(OpB.qs[1].equals(Q8([-12, -3, 0, 4])))
def test_Euclidean_product_OpB(self):
OpB = self.B.Euclidean_product('ket', operator=self.Op)
print("Op B: ", OpB)
self.assertTrue(OpB.qs[0].equals(Q8([0, 0, 1, -6])))
self.assertTrue(OpB.qs[1].equals(Q8([-12, -3, 0, 4])))
def test_product_AOpB(self):
AOpB = self.A.product('bra', operator=self.Op, ket=self.B)
print("A Op B: ", AOpB)
self.assertTrue(AOpB.qs[0].equals(Q8([0, 0, 4, -24])))
self.assertTrue(AOpB.qs[1].equals(Q8([3, -12, -4, 0])))
def test_Euclidean_product_AOpB(self):
AOpB = self.A.Euclidean_product('bra', operator=self.Op, ket=self.B)
print("A* Op B: ", AOpB)
self.assertTrue(AOpB.qs[0].equals(Q8([0, 0, 4, -24])))
self.assertTrue(AOpB.qs[1].equals(Q8([-3, 12, 4, 0])))
def test_product_AOp4i(self):
AOp4i = self.A.product('bra', operator=self.Op4i)
print("A Op4i: ", AOp4i)
self.assertTrue(AOp4i.qs[0].equals(Q8([0, 16, 0, 0])))
self.assertTrue(AOp4i.qs[1].equals(Q8([-4, 0, 0, 0])))
def test_Euclidean_product_AOp4i(self):
AOp4i = self.A.Euclidean_product('bra', operator=self.Op4i)
print("A* Op4i: ", AOp4i)
self.assertTrue(AOp4i.qs[0].equals(Q8([0, 16, 0, 0])))
self.assertTrue(AOp4i.qs[1].equals(Q8([4, 0, 0, 0])))
def test_product_Op4iB(self):
Op4iB = self.B.product('ket', operator=self.Op4i)
print("Op4i B: ", Op4iB)
self.assertTrue(Op4iB.qs[0].equals(Q8([0, 0, 0, 4])))
self.assertTrue(Op4iB.qs[1].equals(Q8([0, 0, -8, 0])))
self.assertTrue(Op4iB.qs[2].equals(Q8([-12, 0, 0, 0])))
def test_Euclidean_product_Op4iB(self):
Op4iB = self.B.Euclidean_product('ket', operator=self.Op4i)
print("Op4i B: ", Op4iB)
self.assertTrue(Op4iB.qs[0].equals(Q8([0, 0, 0, 4])))
self.assertTrue(Op4iB.qs[1].equals(Q8([0, 0, -8, 0])))
self.assertTrue(Op4iB.qs[2].equals(Q8([-12, 0, 0, 0])))
def test_product_AOp4iB(self):
AOp4iB = self.A.product('bra', operator=self.Op4i, ket=self.B)
print("A* Op4i B: ", AOp4iB)
self.assertTrue(AOp4iB.dim == 0)
def test_Euclidean_product_AOp4iB(self):
AOp4iB = self.A.Euclidean_product('bra', operator=self.Op4i, ket=self.B)
print("A* Op4i B: ", AOp4iB)
self.assertTrue(AOp4iB.dim == 0)
def test_op_n(self):
opn = self.Op.op_n(n=self.qi)
print("op_n: ", opn)
self.assertTrue(opn.qs[0].dx.p == 3)
suite = unittest.TestLoader().loadTestsFromModule(TestQ8States())
unittest.TextTestRunner().run(suite);
In [36]:
class Q8aStates(Q8a):
"""A class made up of many quaternions."""
def __init__(self, qs=None, qtype="Q", representation=""):
self.qs = qs
if qs is None:
self.d, self.dim, self.dimensions = 0, 0, 0
else:
self.d, self.dim, self.dimensions = len(qs), len(qs), len(qs)
self.qtype = qtype
self.representation = representation
def __str__(self):
"""Print out all the states."""
states = ''
for n, q in enumerate(self.qs, start=1):
states = states + "n={}: {}\n".format(n, q)
return states.rstrip()
def print_states(self, label, spacer=False):
"""Utility for printing states as a quaternion series."""
print(label)
for n, q in enumerate(self.qs, start=1):
print("n={}: {}".format(n, q))
print("sum= {ss}".format(ss=self.summation()))
if spacer:
print("")
def equals(self, q1):
"""Test if two states are equal."""
if self.dim != q1.dim:
return False
result = True
for selfq, q1q in zip(self.qs, q1.qs):
if not selfq.equals(q1q):
result = False
return result
def conj(self, conj_type=0):
"""Take the conjgates of states, default is zero, but also can do 1 or 2."""
new_states = []
for bra in self.qs:
new_states.append(bra.conj(conj_type))
return(Q8aStates(new_states))
def flip_signs(self):
"""Flip signs of all states."""
new_states = []
for bra in self.qs:
new_states.append(bra.flip_signs())
return Q8aStates(new_states)
def normalize(self):
"""Normalize all states."""
new_states = []
q_dim = Q8a([math.sqrt(1/self.dim), 0, 0, 0])
for bra in self.qs:
new_states.append(bra.normalize().product(q_dim))
return Q8aStates(new_states)
def summation(self):
"""Add them all up, return one quaternion."""
result = None
for q in self.qs:
if result == None:
result = q
else:
result = result.add(q)
for q in self.qs:
result = result.add(q)
return result
def add(self, ket):
"""Add two states."""
new_states = []
for bra, ket in zip(self.qs, ket.qs):
new_states.append(bra.add(ket))
return(Q8aStates(new_states))
def dif(self, ket):
"""Take the difference of two states."""
new_states = []
for bra, ket in zip(self.qs, ket.qs):
new_states.append(bra.dif(ket))
return(Q8aStates(new_states))
def diagonal(self, dim):
"""Make a state dim*dim with q along the 'diagonal'."""
diagonal = []
if len(self.qs) == 1:
q_values = [self.qs[0]] * dim
elif len(self.qs) == dim:
q_values = self.qs
else:
print("Oops, need the length to be equal to the dimensions.")
for i in range(dim):
for j in range(dim):
if i == j:
diagonal.append(q_values.pop(0))
else:
diagonal.append(Q8a().q_0())
return Q8aStates(diagonal)
def product(self, product_type, bra=None, ket=None, operator=None, kind=""):
"""Forms the quaternion product for each state."""
if product_type == 'bra':
bra = self
elif product_type == 'ket':
ket = self
elif product_type == 'operator':
operator = self
else:
print("Oops, need to set product_type to bra, ket, or operator.")
return None
def _check_dimensions(op_dim=0, state_1_dim=0, state_2_dim=0, equals=False):
"""Make sure the states and operators are the right sizes. The operator dimension is either
equal to 1 or the product of the bra and ket dimensions."""
oops = ''
if equals:
if state_1_dim != state_2_dim:
oops = "states have different dimensions: {} != {}".format(state_1_dim, state_2_dim)
elif state_2_dim == 0:
if (op_dim % state_1_dim != 0) and (op_dim != 1):
oops = "Operator dimensions don't divide nicely by the state vector: {} % {}".format(
op_dim, state_1_dim)
else:
if (op_dim != state_1_dim * state_2_dim) and (op_dim == 1 and (state_1_dim != state_2_dim)):
oops = "Operator dimensions do not equal the product of the states: {} != {} * {}".format(
op_dim, state_1_dim, state_2_dim)
if oops:
print(oops)
return False
else:
return True
new_states = []
if bra is None and ket is None:
return None
elif bra is None and operator is None:
return ket
elif ket is None and operator is None:
return bra
# <A|B>
elif operator is None:
if _check_dimensions(state_1_dim=bra.dim, state_2_dim=ket.dim, equals=True):
for b, k in zip(bra.qs, ket.qs):
new_states.append(b.product(k, kind))
# Op|B>
elif bra is None:
if _check_dimensions(op_dim=operator.dim, state_1_dim=ket.dim):
if operator.dim == 1:
one_diagonal = operator.diagonal(ket.dim)
opb = one_diagonal.qs
else:
opb = operator.qs
for ops in zip(*[iter(opb)] * ket.dim):
ok = None
for op, k in zip(ops, ket.qs):
if ok is None:
ok = op.product(k, kind)
else:
ok = ok.add(op.product(k, kind))
new_states.append(ok)
# <A|Op
elif ket is None:
if _check_dimensions(op_dim=operator.dim, state_1_dim=bra.dim):
if operator.dim == 1:
one_diagonal = operator.diagonal(bra.dim)
aop = one_diagonal.qs
else:
aop = operator.qs
for ops in zip(*[iter(aop)]*bra.dim):
bop = None
for b, op in zip(bra.qs, ops):
if bop is None:
bop = b.product(op, kind)
else:
bop = bop.add(b.product(op, kind))
new_states.append(bop)
# <A|Op|B>
else:
if _check_dimensions(op_dim=operator.dim, state_1_dim=bra.dim, state_2_dim=ket.dim):
new_ket = []
if operator.dim == 1:
one_diagonal = operator.diagonal(ket.dim)
opb = one_diagonal.qs
else:
opb = operator.qs
for ops in zip(*[iter(opb)]*ket.dim):
ok = None
for op, k in zip(ops, ket.qs):
if ok is None:
ok = op.product(k, kind)
else:
ok = ok.add(op.product(k, kind))
new_ket.append(ok)
new_ket_state = Q8aStates(new_ket)
for b, k in zip(bra.qs, new_ket_state.qs):
new_states.append(b.product(k, kind))
return Q8aStates(new_states)
def Euclidean_product(self, product_type, bra=None, ket=None, operator=None, kind=""):
"""Forms the Euclidean product, what is used in QM all the time."""
if bra is not None:
bra = bra.conj()
if product_type == 'bra':
self = self.conj()
return self.product(product_type, bra, ket, operator, kind)
def op_n(self, n, first=True, kind="", reverse=False):
"""Mulitply an operator times a number, in that order. Set first=false for n * Op"""
new_states = []
for op in self.qs:
if first:
new_states.append(op.product(n, kind, reverse))
else:
new_states.append(n.product(op, kind, reverse))
return Q8aStates(new_states)
def norm_squared(self):
"""Take the Euclidean product of each state and add it up, returning one quaternion."""
norm = self.Euclidean_product(self).summation()
return norm
In [37]:
class TestQ8aStates(unittest.TestCase):
"""Test states."""
q0 = Q8a().q_0()
q1 = Q8a().q_1()
qi = Q8a().q_i()
q0_q1 = Q8aStates([q0, q1])
q1_q0 = Q8aStates([q1, q0])
q1_qi = Q8aStates([q1, qi])
A = Q8aStates([Q8a([4,0,0,0]),Q8a([0,1,0,0])])
B = Q8aStates([Q8a([0,0,1,0]),Q8a([0,0,0,2]),Q8a([0,3,0,0])])
Op = Q8aStates([Q8a([3,0,0,0]),Q8a([0,1,0,0]),Q8a([0,0,2,0]),Q8a([0,0,0,3]),Q8a([2,0,0,0]),Q8a([0,4,0,0])])
Op4i = Q8aStates([Q8a([0,4,0,0])])
qn = Q8aStates([Q8a([3,0,0,4])])
def test_init(self):
self.assertTrue(self.q0_q1.dim == 2)
def test_equals(self):
self.assertTrue(self.A.equals(self.A))
self.assertFalse(self.A.equals(self.B))
def test_conj(self):
qc = self.q1_qi.conj()
qc1 = self.q1_qi.conj(1)
print("q1_qi*: ", qc)
print("q1_qc*1: ", qc1)
self.assertTrue(qc.qs[1].a[3] == 1)
self.assertTrue(qc1.qs[1].a[2] == 1)
def test_flip_signs(self):
qf = self.q1_qi.flip_signs()
print("-q1_qi: ", qf)
self.assertTrue(qf.qs[1].a[3] == 1)
def test_normalize(self):
qn = self.qn.normalize()
print("Op normalized: ", qn)
self.assertAlmostEqual(qn.qs[0].a[0], 0.6)
self.assertTrue(qn.qs[0].a[6] == 0.8)
def test_summation(self):
q_01_sum = self.q0_q1.summation()
print("sum: ", q_01_sum)
self.assertTrue(type(q_01_sum) is Q8a)
self.assertTrue(q_01_sum.a[0]== 2)
def test_add(self):
q_0110_add = self.q0_q1.add(self.q1_q0)
print("add 01 10: ", q_0110_add)
self.assertTrue(q_0110_add.qs[0].a[0]== 1)
self.assertTrue(q_0110_add.qs[1].a[0]== 1)
def test_dif(self):
q_0110_dif = self.q0_q1.dif(self.q1_q0)
print("dif 01 10: ", q_0110_dif)
self.assertTrue(q_0110_dif.qs[0].a[1]== 1)
self.assertTrue(q_0110_dif.qs[1].a[0]== 1)
def test_product_AA(self):
AA = self.A.product('bra', ket=self.A)
print("AA: ", AA)
self.assertTrue(AA.qs[0].equals(Q8a([16, 0, 0, 0])))
self.assertTrue(AA.qs[1].equals(Q8a([-1, 0, 0, 0])))
def test_Euclidean_product_AA(self):
AA = self.A.Euclidean_product('bra', ket=self.A)
print("A* A", AA)
self.assertTrue(AA.qs[0].equals(Q8a([16, 0, 0, 0])))
self.assertTrue(AA.qs[1].equals(Q8a([1, 0, 0, 0])))
def test_product_AOp(self):
AOp = self.A.product('bra', operator=self.Op)
print("(A|Op: ", AOp)
self.assertTrue(AOp.qs[0].equals(Q8a([11, 0, 0, 0])))
self.assertTrue(AOp.qs[1].equals(Q8a([0, 0, 5, 0])))
self.assertTrue(AOp.qs[2].equals(Q8a([4, 0, 0, 0])))
def test_Euclidean_product_AOp(self):
AOp = self.A.Euclidean_product('bra', operator=self.Op)
print("<A*|Op: ", AOp)
self.assertTrue(AOp.qs[0].equals(Q8a([13, 0, 0, 0])))
self.assertTrue(AOp.qs[1].equals(Q8a([0, 0, 11, 0])))
self.assertTrue(AOp.qs[2].equals(Q8a([12, 0, 0, 0])))
def test_product_OpB(self):
OpB = self.B.product('ket', operator=self.Op)
print("Op B: ", OpB)
self.assertTrue(OpB.qs[0].equals(Q8a([0, 0, 1, -6])))
self.assertTrue(OpB.qs[1].equals(Q8a([-12, -3, 0, 4])))
def test_Euclidean_product_OpB(self):
OpB = self.B.Euclidean_product('ket', operator=self.Op)
print("Op B: ", OpB)
self.assertTrue(OpB.qs[0].equals(Q8a([0, 0, 1, -6])))
self.assertTrue(OpB.qs[1].equals(Q8a([-12, -3, 0, 4])))
def test_product_AOpB(self):
AOpB = self.A.product('bra', operator=self.Op, ket=self.B)
print("A Op B: ", AOpB)
self.assertTrue(AOpB.qs[0].equals(Q8a([0, 0, 4, -24])))
self.assertTrue(AOpB.qs[1].equals(Q8a([3, -12, -4, 0])))
def test_Euclidean_product_AOpB(self):
AOpB = self.A.Euclidean_product('bra', operator=self.Op, ket=self.B)
print("A* Op B: ", AOpB)
self.assertTrue(AOpB.qs[0].equals(Q8a([0, 0, 4, -24])))
self.assertTrue(AOpB.qs[1].equals(Q8a([-3, 12, 4, 0])))
def test_product_AOp4i(self):
AOp4i = self.A.product('bra', operator=self.Op4i)
print("A Op4i: ", AOp4i)
self.assertTrue(AOp4i.qs[0].equals(Q8a([0, 16, 0, 0])))
self.assertTrue(AOp4i.qs[1].equals(Q8a([-4, 0, 0, 0])))
def test_Euclidean_product_AOp4i(self):
AOp4i = self.A.Euclidean_product('bra', operator=self.Op4i)
print("A* Op4i: ", AOp4i)
self.assertTrue(AOp4i.qs[0].equals(Q8a([0, 16, 0, 0])))
self.assertTrue(AOp4i.qs[1].equals(Q8a([4, 0, 0, 0])))
def test_product_Op4iB(self):
Op4iB = self.B.product('ket', operator=self.Op4i)
print("Op4i B: ", Op4iB)
self.assertTrue(Op4iB.qs[0].equals(Q8a([0, 0, 0, 4])))
self.assertTrue(Op4iB.qs[1].equals(Q8a([0, 0, -8, 0])))
self.assertTrue(Op4iB.qs[2].equals(Q8a([-12, 0, 0, 0])))
def test_Euclidean_product_Op4iB(self):
Op4iB = self.B.Euclidean_product('ket', operator=self.Op4i)
print("Op4i B: ", Op4iB)
self.assertTrue(Op4iB.qs[0].equals(Q8a([0, 0, 0, 4])))
self.assertTrue(Op4iB.qs[1].equals(Q8a([0, 0, -8, 0])))
self.assertTrue(Op4iB.qs[2].equals(Q8a([-12, 0, 0, 0])))
def test_product_AOp4iB(self):
AOp4iB = self.A.product('bra', operator=self.Op4i, ket=self.B)
print("A* Op4i B: ", AOp4iB)
self.assertTrue(AOp4iB.dim == 0)
def test_Euclidean_product_AOp4iB(self):
AOp4iB = self.A.Euclidean_product('bra', operator=self.Op4i, ket=self.B)
print("A* Op4i B: ", AOp4iB)
self.assertTrue(AOp4iB.dim == 0)
def test_op_n(self):
opn = self.Op.op_n(n=self.qi)
print("op_n: ", opn)
self.assertTrue(opn.qs[0].a[2] == 3)
suite = unittest.TestLoader().loadTestsFromModule(TestQ8aStates())
unittest.TextTestRunner().run(suite);