# Developing Quaternion and Space-time Number Tools for iPython3

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:

1. QH - Quaternions for Hamilton, can do symbolic manipulations
2. QHa - Quaternions for Hamilton numpy arrays
3. Q8 - Quaternions that are represented by 8 numbers
4. Q8a - Quaternions that are represented by 8 numbers that are numpy arrays

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]



## Quaternions for Hamilton

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.

class QH(object): """Quaternions as Hamilton would have defined them, on the manifold R^4.""" def __init__(self, values=None, qtype="Q", representation=""): if values is None: self.t, self.x, self.y, self.z = 0, 0, 0, 0 elif len(values) == 4: self.t, self.x, self.y, self.z = values[0], values[1], values[2], values[3] elif len(values) == 8: self.t, self.x = values[0] - values[1], values[2] - values[3] self.y, self.z = values[4] - values[5], values[6] - values[7] self.representation = representation if representation != "": self.t, self.x, self.y, self.z = self.representation_2_txyz(representation) self.qtype = qtype def __str__(self): """Customize the output.""" if len(self.qtype) > 66: report_qtype = "..." else: report_qtype = self.qtype if self.representation == "": string = "({t}, {x}, {y}, {z}) {qt}".format( t=self.t, x=self.x, y=self.y, z=self.z, qt=report_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=report_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=report_qtype) return string def is_symbolic(self): """Figures out if an expression has symbolic terms.""" symbolic = False if hasattr(self.t, "free_symbols") or hasattr(self.x, "free_symbols") or \ hasattr(self.y, "free_symbols") or hasattr(self.z, "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.t, self.x, self.y, self.z] elif representation == "polar": amplitude = (self.t ** 2 + self.x ** 2 + self.y **2 + self.z **2) ** (1/2) abs_v = self.abs_of_vector().t if symbolic: theta = sp.atan2(abs_v, self.t) else: theta = math.atan2(abs_v, self.t) if abs_v == 0: thetaX, thetaY, thetaZ = 0, 0, 0 else: thetaX = theta * self.x / abs_v thetaY = theta * self.y / abs_v thetaZ = theta * self.z / abs_v rep = [amplitude, thetaX, thetaY, thetaZ] elif representation == "spherical": t = self.t R = (self.x ** 2 + self.y **2 + self.z **2) ** (1/2) if R == 0: theta = 0 else: if symbolic: theta = sp.acos(self.z / R) else: theta = math.acos(self.z / R) if symbolic: phi = sp.atan2(self.y, self.x) else: phi = math.atan2(self.y, self.x) 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.t, "free_symbols") or hasattr(self.x, "free_symbols") or \ hasattr(self.y, "free_symbols") or hasattr(self.z, "free_symbols"): symbolic = True if representation == "": t, x, y, z = self.t, self.x, self.y, self.z elif representation == "polar": amplitude, thetaX, thetaY, thetaZ = self.t, self.x, self.y, self.z theta = (thetaX ** 2 + thetaY ** 2 + thetaZ ** 2) ** (1/2) if theta == 0: t = self.t x, y, z = 0, 0, 0 else: if symbolic: t = amplitude * sp.cos(theta) x = self.x / theta * amplitude * sp.sin(theta) y = self.y / theta * amplitude * sp.sin(theta) z = self.z / theta * amplitude * sp.sin(theta) else: t = amplitude * math.cos(theta) x = self.x / theta * amplitude * math.sin(theta) y = self.y / theta * amplitude * math.sin(theta) z = self.z / theta * amplitude * math.sin(theta) elif representation == "spherical": t, R, theta, phi = self.t, self.x, self.y, self.z 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.t) display(self.x) display(self.y) display(self.z) return def simple_q(self): """Simplify each term.""" self.t = sp.simplify(self.t) self.x = sp.simplify(self.x) self.y = sp.simplify(self.y) self.z = sp.simplify(self.z) return self def expand_q(self): """Expand each term.""" self.t = sp.expand(self.t) self.x = sp.expand(self.x) self.y = sp.expand(self.y) self.z = sp.expand(self.z) return self def q_0(self, qtype="0"): """Return a zero quaternion.""" q0 = QH([0, 0, 0, 0], qtype=qtype, representation=self.representation) return q0 def q_1(self, qtype="1"): """Return a multiplicative identity quaternion.""" q1 = QH([1, 0, 0, 0], qtype=qtype, representation=self.representation) return q1 def q_i(self, qtype="i"): """Return i.""" qi = QH([0, 1, 0, 0], qtype=qtype, representation=self.representation) return qi def q_j(self, qtype="j"): """Return j.""" qj = QH([0, 0, 1, 0], qtype=qtype, representation=self.representation) return qj def q_k(self, qtype="k"): """Return k.""" qk = QH([0, 0, 0, 1], qtype=qtype, representation=self.representation) return qk def q_random(self, qtype="?"): """Return a random-valued quaternion.""" qr = QH([random.random(), random.random(), random.random(), random.random()], qtype=qtype) return qr def dupe(self, qtype=""): """Return a duplicate copy, good for testing since qtypes persist""" du = QH([self.t, self.x, self.y, self.z], qtype=self.qtype, representation=self.representation) return du 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.t), sp.expand(self.x), sp.expand(self.y), sp.expand(self.z) q1_t, q1_x, q1_y, q1_z = sp.expand(q1.t), sp.expand(q1.x), sp.expand(q1.y), sp.expand(q1.z) 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.t, self.x, self.y, self.z conj_q = QH() if conj_type == 0: conj_q.t = t if x != 0: conj_q.x = -1 * x if y != 0: conj_q.y = -1 * y if z != 0: conj_q.z = -1 * z elif conj_type == 1: if t != 0: conj_q.t = -1 * t conj_q.x = x if y != 0: conj_q.y = -1 * y if z != 0: conj_q.z = -1 * z qtype += "1" elif conj_type == 2: if t != 0: conj_q.t = -1 * t if x != 0: conj_q.x = -1 * x conj_q.y = y if z != 0: conj_q.z = -1 * z qtype += "2" conj_q.qtype = self.qtype + qtype conj_q.representation = self.representation return conj_q def flip_signs(self, qtype="-"): """Flip the signs of all terms.""" end_qtype = "-{}".format(self.qtype) t, x, y, z = self.t, self.x, self.y, self.z flip_q = QH(qtype=end_qtype, representation=self.representation) if t != 0: flip_q.t = -1 * t if x != 0: flip_q.x = -1 * x if y != 0: flip_q.y = -1 * y if z != 0: flip_q.z = -1 * z 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.t, self.x, self.y, self.z conj_q = QH() if conj_type == '-': conj_q.t = t if x != 0: conj_q.x = -1 * x if y != 0: conj_q.y = -1 * y if z != 0: conj_q.z = -1 * z qtype += "*-" if conj_type == "'": conj_q.t = t if x != 0: conj_q.x = -1 * x if y != 0: conj_q.y = -1 * y conj_q.z = z qtype += "*'" if conj_type == '*': conj_q.t = t conj_q.x = x conj_q.y = y if z != 0: conj_q.z = -1 * 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.t, self.x, self.y, self.z q1_t, q1_x, q1_y, q1_z = q1.t, q1.x, q1.y, q1.z 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.x, self.y, self.z q1_x, q1_y, q1_z = q1.x, q1.y, q1.z 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 = QH(qtype=end_qtype, representation=self.representation) sq_q.t = qxq['tt'] - qxq['xx+yy+zz'] sq_q.x = qxq['tx+xt'] sq_q.y = qxq['ty+yt'] sq_q.z = qxq['tz+zt'] 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 = QH(qtype=end_qtype, representation=self.representation) n_q.t = qxq['tt'] + qxq['xx+yy+zz'] return n_q 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 = QH(qtype=end_qtype, representation=self.representation) nv_q.t = qxq['xx+yy+zz'] return nv_q def abs_of_q(self, qtype="||"): """The absolute value, the square root of the norm_squared.""" end_qtype = "|{}|".format(self.qtype) a = self.norm_squared() sqrt_t = a.t ** (1/2) a.t = sqrt_t a.qtype = end_qtype a.representation = self.representation return a 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 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(qtype=end_qtype) sqrt_t = av.t ** (1/2) av.t = sqrt_t av.representation = self.representation return av def add(self, qh_1, qtype=""): """Form a add given 2 quaternions.""" self.check_representations(qh_1) end_qtype = "{f}+{s}".format(f=self.qtype, s=qh_1.qtype) t_1, x_1, y_1, z_1 = self.t, self.x, self.y, self.z t_2, x_2, y_2, z_2 = qh_1.t, qh_1.x, qh_1.y, qh_1.z add_q = QH(qtype=end_qtype, representation=self.representation) add_q.t = t_1 + t_2 add_q.x = x_1 + x_2 add_q.y = y_1 + y_2 add_q.z = z_1 + z_2 return add_q def dif(self, qh_1, qtype=""): """Form a add given 2 quaternions.""" self.check_representations(qh_1) end_qtype = "{f}-{s}".format(f=self.qtype, s=qh_1.qtype) t_1, x_1, y_1, z_1 = self.t, self.x, self.y, self.z t_2, x_2, y_2, z_2 = qh_1.t, qh_1.x, qh_1.y, qh_1.z dif_q = QH(qtype=end_qtype, representation=self.representation) dif_q.t = t_1 - t_2 dif_q.x = x_1 - x_2 dif_q.y = y_1 - y_2 dif_q.z = z_1 - z_2 return dif_q def product(self, q1, kind="", reverse=False, qtype=""): """Form a product given 2 quaternions. Kind can be '' aka standard, even, odd, or even_minus_odd. Setting reverse=True is like changing the order.""" self.check_representations(q1) commuting = self._commuting_products(q1) q_even = QH() q_even.t = commuting['tt'] - commuting['xx+yy+zz'] q_even.x = commuting['tx+xt'] q_even.y = commuting['ty+yt'] q_even.z = commuting['tz+zt'] anti_commuting = self._anti_commuting_products(q1) q_odd = QH() if reverse: q_odd.x = anti_commuting['zy-yz'] q_odd.y = anti_commuting['xz-zx'] q_odd.z = anti_commuting['yx-xy'] else: q_odd.x = anti_commuting['yz-zy'] q_odd.y = anti_commuting['zx-xz'] q_odd.z = anti_commuting['xy-yx'] 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') if qtype: result.qtype = qtype else: 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 = QH(qtype, representation=self.representation) pq = self.conj().product(q1, kind, reverse) 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.t == 0: print("oops, zero on the norm_squared.") return self.q0() q_norm_squared_inv = QH([1.0 / q_norm_squared.t, 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) end_qtype = "{f}/{s}".format(f=self.qtype, s=q1.qtype) q1_inv = q1.invert() q_div = self.product(q1.invert()) 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_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 = QH([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, 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 = QH(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(QH([0.5, 0, 0, 0])) triple_123 = triple_1.add(half_23, qtype=end_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 = QH(qtype=end_qtype) g_q.t = self.t / g_factor g_q.x = self.x * g_factor g_q.y = self.y * g_factor g_q.z = self.z * 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.t == 0: return QH([math.sin(self.t), 0, 0, 0], qtype=end_qtype) sint = math.sin(self.t) cost = math.cos(self.t) sinhR = math.sinh(abs_v.t) coshR = math.cosh(abs_v.t) k = cost * sinhR / abs_v.t q_out = QH() q_out.t = sint * coshR q_out.x = k * self.x q_out.y = k * self.y q_out.z = k * self.z q_out.qtype = end_qtype q_out.representation = self.representation 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.t == 0: return QH([math.cos(self.t), 0, 0, 0], qtype=end_qtype) sint = math.sin(self.t) cost = math.cos(self.t) sinhR = math.sinh(abs_v.t) coshR = math.cosh(abs_v.t) k = -1 * sint * sinhR / abs_v.t q_out = QH() q_out.t = cost * coshR q_out.x = k * self.x q_out.y = k * self.y q_out.z = k * self.z q_out.qtype = end_qtype q_out.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() if abs_v.t == 0: return QH([math.tan(self.t), 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.t == 0: return QH([math.sinh(self.t), 0, 0, 0], qtype=end_qtype) sinht = math.sinh(self.t) cosht = math.cosh(self.t) sinR = math.sin(abs_v.t) cosR = math.cos(abs_v.t) k = cosht * sinR / abs_v.t q_out = QH(qtype=end_qtype, representation=self.representation) q_out.t = sinht * cosR q_out.x = k * self.x q_out.y = k * self.y q_out.z = k * self.z 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() if abs_v.t == 0: return QH([math.cosh(self.t), 0, 0, 0], qtype=end_qtype) sinht = math.sinh(self.t) cosht = math.cosh(self.t) sinR = math.sin(abs_v.t) cosR = math.cos(abs_v.t) k = sinht * sinR / abs_v.t q_out = QH(qtype=end_qtype, representation=self.representation) q_out.t = cosht * cosR q_out.x = k * self.x q_out.y = k * self.y q_out.z = k * self.z 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.t == 0: return QH([math.tanh(self.t), 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.t) if (abs_v.t == 0): return QH([et, 0, 0, 0], qtype=end_qtype) cosR = math.cos(abs_v.t) sinR = math.sin(abs_v.t) k = et * sinR / abs_v.t expq = QH([et * cosR, k * self.x, k * self.y, k * self.z], 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.t == 0): if self.t > 0: return(QH([math.log(self.t), 0, 0, 0], qtype=end_qtype)) else: # I don't understant this, but mathematica does the same thing. return(QH([math.log(-self.t), math.pi, 0, 0], qtype=end_type)) return QH([lt, 0, 0, 0]) t_value = 0.5 * math.log(self.t * self.t + abs_v.t * abs_v.t) k = math.atan2(abs_v.t, self.t) / abs_v.t expq = QH([t_value, k * self.x, k * self.y, k * self.z], qtype=end_qtype, representation=self.representation) 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.t = math.trunc(self.t) self.x = math.trunc(self.x) self.y = math.trunc(self.y) self.z = math.trunc(self.z) return self

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);




...........................................

Euclidean product:  (1, 16, 13, -18) Q*xP
abs_of_q:  (5.0, 0, 0, 0) |P|
abs_of_vector:  (5.0, 0, 0, 0) |V(P)|^2
add:  (1, 2, -6, -4) Q+P
q1_sq:  (-28, -4, -6, -8) Q^2
boosted:  (1.0120181081629736, -2.006036054324489, -3.0, -4.0) Qboost
boosted squared:  (-28.0, -4.0602896252083704, -6.072108648977841, -8.096144865303788) Qboost^2
q_conj 0:  (1, 2, 3, 4) Q*
q_conj 1:  (-1, -2, 3, 4) Q*1
q_conj 2:  (-1, 2, -3, 4) Q*2
dif:  (1, -6, 0, -4) Q-P
divide_by:  (1.0, 0.0, 0.0, 0.0) Q/Q
q1_sq:  (-28, -4, -6, -8) Q^2
g_shift:  (0.997004495503373, -2.00600900900675, -3.00901351351013, -4.01201801801351) Qg_shift
g squared:  (-28.1805050815139, -4.00000000000000, -6.00000000000000, -8.00000000000000) Qg_shift^2
invert:  (0.0, -0.16, 0.12, 0.0) P^-1
norm_squared:  (30, 0, 0, 0) ||Q||^2
norm_squared_of_vector:  (29, 0, 0, 0) |V(Q)|^2
q_normalized:  (0.0, 0.8, -0.6000000000000001, 0.0) PU
product:  (-1, -8, -19, 18) QxP
product, kind even:  (-1, 4, -3, 0) QxEP
product, kind even_minus_odd:  (-1, 16, 13, -18) QxE-OP
product, kind odd:  (0, -12, -16, 18) QxOP
q_0:  (0, 0, 0, 0) 0
q_1:  (1, 0, 0, 0) 1
q_i:  (0, 1, 0, 0) i
q_j:  (0, 0, 1, 0) j
q_k:  (0, 0, 0, 1) k
q_random(): (0.22742587358987465, 0.07639695607462316, 0.1111897931332777, 0.549200518973763) ?
rotate:  (1.0, -2.0, 3.0, 4.0) Qrot
square:  (-28, -4, -6, -8) Q^2
triple product:  (-2, 124, -84, 8) QxPxQ
q_vahlen_conj -:  (1, 2, 3, 4) Qvc*-
q_vahlen_conj ':  (1, 2, 3, -4) Qvc*'
q_vahlen_conj *:  (1, -2, -3, 4) Qvc*

----------------------------------------------------------------------
Ran 43 tests in 0.080s

OK




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);




....
----------------------------------------------------------------------
Ran 4 tests in 0.011s

OK

polar conj of 1 2 0 0:  (1.0 A, -2.0 𝜈x, 0.0 𝜈y, 0.0 𝜈z) Q*
polar 1 1 0 0 * 1 2 0 0:  (1.0 A, 3.0 𝜈x, 0.0 𝜈y, 0.0 𝜈z) QxQ
polar 1 2 0 0 * 2 1 0 0:  (2.0 A, 3.0 𝜈x, 0.0 𝜈y, 0.0 𝜈z) QxQ



## Numpy Arrays for Hamilton

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);




...........................................

Euclidean product:  (1.0, 16.0, 13.0, -18.0) Q*xP
abs_of_q:  (5.0, 0.0, 0.0, 0.0) |P|
abs_of_vector:  (5.0, 0.0, 0.0, 0.0) |V(P)|
add:  (1.0, 2.0, -6.0, -4.0) Q+P
q1_sq:
boosted:  (1.0120181081629736, -2.006036054324489, -3.0, -4.0) Qboost
boosted squared:
{}
q_conj 0:  (1.0, 2.0, 3.0, 4.0) Q*
q_conj 1:  (-1.0, -2.0, 3.0, 4.0) Q*1
q_conj 2:  (-1.0, 2.0, -3.0, 4.0) Q*2
dif:  (1.0, -6.0, 0.0, -4.0) Q-P
divide_by:  (1.0, 0.0, 0.0, 0.0) Q/Q
q1_sq:
g_shift:  (0.9970044955033729, -2.006009009006754, -3.0090135135101312, -4.012018018013508) Qg_shift
g squared:
invert:  (0.0, -0.16, 0.12, 0.0) P^-1
norm_squared:  (30.0, 0.0, 0.0, 0.0) ||Q||^2
norm_squared_of_vector:  (29.0, 0.0, 0.0, 0.0) ||V(Q)||
q_normalized:  (0.0, 0.8, -0.6000000000000001, 0.0) PU
product:  (-1.0, -8.0, -19.0, 18.0) QxP
product even:  (-1.0, 4.0, -3.0, 0.0) QxEP
product even_minus_odd:  (-1.0, 16.0, 13.0, -18.0) QxE-OP
product odd:  (0.0, -12.0, -16.0, 18.0) QxOP
q_0:  (0.0, 0.0, 0.0, 0.0) 0
q_1:  (1.0, 0.0, 0.0, 0.0) 1
q_i:  (0.0, 1.0, 0.0, 0.0) i
q_1:  (0.0, 0.0, 1.0, 0.0) j
q_k:  (0.0, 0.0, 0.0, 1.0) k
q_random():  (0.8329465195276711, 0.5093547292621532, 0.527419681336517, 0.4273757400810684) ?
rotate:  (1.0, -2.0, 3.0, 4.0) Qrot
square:  (-28.0, -4.0, -6.0, -8.0) Q^2
triple product:  (-2.0, 124.0, -84.0, 8.0) QxPxQ
q_vahlen_conj -:  (1.0, 2.0, 3.0, 4.0) Qvc*-
q_vahlen_conj ':  (1.0, 2.0, 3.0, -4.0) Qvc*'
q_vahlen_conj *:  (1.0, -2.0, -3.0, 4.0) Qvc*

----------------------------------------------------------------------
Ran 43 tests in 0.083s

OK




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);




....
----------------------------------------------------------------------
Ran 4 tests in 0.016s

OK

polar conj of {}: {} (1.0 A, 2.0 𝜈x, 0.0 𝜈y, 0.0 𝜈z) Q (1.0 A, -2.0 𝜈x, 0.0 𝜈y, 0.0 𝜈z) Q*
Q12np:  (1.0 A, -2.0 𝜈x, 0.0 𝜈y, 0.0 𝜈z) Q
polar 1 1 0 0 * 1 2 0 0:  (1.0 A, 3.0 𝜈x, 0.0 𝜈y, 0.0 𝜈z) QxQ
polar 1 2 0 0 * 2 1 0 0:  (2.0 A, 3.0 𝜈x, 0.0 𝜈y, 0.0 𝜈z) QxQ



## Using More Numbers via Doublets

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);




...........
----------------------------------------------------------------------
Ran 11 tests in 0.012s

OK



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);




...........
----------------------------------------------------------------------
Ran 11 tests in 0.013s

OK



## Quaternion Group Q8

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);




.............................................

Euclidean product: {} ((1, 0), (16, 0), (13, 0), (0, 18)) Q*xQ-reduced
abs_of_q: {} ((5.0, 0), (0, 0), (0, 0), (0, 0)) |Q|
abs_of_vector: {} ((5.0, 0), (0, 0), (0, 0), (0, 0)) |Q|
add: {} ((1, 0), (4, 2), (0, 6), (0, 4)) Q+Q
add reduce: ((1, 0), (2, 0), (0, 6), (0, 4)) Q+Q-reduced
Q_sq: ((0, 28), (0, 4), (0, 6), (0, 8)) Q^2-reduced
boosted: {} ((1.51802716224446, 0.5060090540814867), (1.0030180271622444, 3.0090540814867333), (1.5240542164879485, 4.524054216487949), (2.0180721626494638, 6.018072162649464)) QBoost!
b squared: ((0, 28.000000000000007), (0, 4.06028962520837), (0, 6.072108648977842), (0, 8.096144865303788)) QBoost!^2-reduced
conj 0: {} ((1, 0), (2, 0), (3, 0), (4, 0)) Q*
conj 1: {} ((0, 1), (0, 2), (3, 0), (4, 0)) Q*1
conj 2: {} ((0, 1), (2, 0), (0, 3), (4, 0)) Q*2
dif: {} ((1, 0), (0, 6), (3, 3), (0, 4)) Q-Q
inverse: {} ((1.0, 0.0), (0, 0), (0, 0), (0, 0)) Q/Q-reduced
Q_sq: ((0, 28), (0, 4), (0, 6), (0, 8)) Q^2-reduced
g_shift: {} ((0.997004495503373, 0), (0, 2.00600900900675), (0, 3.00901351351013), (0, 4.01201801801351)) QG_shift
g squared: ((0, 28.1805050815139), (0, 4.00000000000000), (0, 6.00000000000000), (0, 8.00000000000000)) QG_shift^2-reduced
inverse: {} ((0.0, 0.0), (0.0, 0.16), (0.12, 0.0), (0.0, 0.0)) Q^-1-reduced
norm_squared: {} ((30, 0), (0, 0), (0, 0), (0, 0)) ||Q||^2
norm_squared_of_vector: {} ((29, 0), (0, 0), (0, 0), (0, 0)) ||Q||^2
q_normalized: {} ((0.0, 0.0), (0.8, 0.0), (0.0, 0.6000000000000001), (0.0, 0.0)) QU
product: {} ((0, 1), (0, 8), (0, 19), (18, 0)) QxQ-reduced
product, kind even: {} ((0, 1), (4, 0), (0, 3), (0, 0)) QxEQ-reduced
product, kind odd: {} ((0, 1), (16, 0), (13, 0), (0, 18)) QxE-OQ-reduced
product, kind odd: {} ((0, 0), (0, 12), (0, 16), (18, 0)) QxOQ-reduced
q_0: {} ((0, 0), (0, 0), (0, 0), (0, 0)) 0
q_1: {} ((1, 0), (0, 0), (0, 0), (0, 0)) 1
q_i: {} ((0, 0), (1, 0), (0, 0), (0, 0)) i
q_j: {} ((0, 0), (0, 0), (1, 0), (0, 0)) j
q_k: {} ((0, 0), (0, 0), (0, 0), (1, 0)) k
q_random(): ((0.4223541664091415, 0), (0.7377423919696418, 0), (0.3964313487306321, 0), (0.8980359267305139, 0)) ?
q_big reduced: ((0, 1), (0, 1), (0, 1), (0, 1)) Q-reduced
rotate: {} ((1.0, 0.0), (0.0, 2.0), (3.0, 0.0), (4.0, 0.0)) Q/|Q|xQxQ/|Q|*-reduced
square: ((1, 29), (0, 4), (0, 6), (0, 8)) Q^2
square reduced: ((0, 28), (0, 4), (0, 6), (0, 8)) Q^2-reduced
triple: {} ((0, 2), (124, 0), (0, 84), (8, 0)) QxQxQ-reduced
vahlen conj -: {} ((1, 0), (2, 0), (3, 0), (4, 0)) Qvc-
vahlen conj ': {} ((1, 0), (2, 0), (3, 0), (0, 4)) Qvc'
vahlen conj *: {} ((1, 0), (0, 2), (0, 3), (4, 0)) Qvc*

----------------------------------------------------------------------
Ran 45 tests in 0.058s

OK




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);




....

polar conj of 1 2 0 0:  ((1.0, 0) A, (0, 2.0)  𝜈x, (0.0, 0) 𝜈y, (0.0, 0) 𝜈z) Q*-reduced
polar 1 1 0 0 * 1 2 0 0:  ((0.0, 0.9899924966004455), (0.1411200080598673, 0), (0.0, 0.0), (0.0, 0.0)) QxQ-reduced
polar 1 2 0 0 * 2 1 0 0:  ((0.0, 1.979984993200891), (0.2822400161197346, 0), (0.0, 0.0), (0.0, 0.0)) QxQ-reduced

----------------------------------------------------------------------
Ran 4 tests in 0.005s

OK



## Class Q8a as nparrays



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]))