Uber Dynkin Diagram by Andrius Kulikauskas using the cloud-based drafting tool math3d.org.
Let's agree that how we relate IVM and XYZ coordinates is somewhat by convention. I would argue that if our purpose is to stay within Synergetics, then some conventions make more sense than others.
Here's what I've come to:
Consider the 8 octants of XYZ, i.e. we can label them by which XYZ basis vectors i,j,k are positive and which negative, to reach points deep inside each of them.
We have (+, +, +) (+, +, -)(+, -, +)(-, +, +), (+, -, -), (-, +, -), (-, -, +), (-, -, -), i.e. eight combos in total. $2^{3}$. That's XYZ.
In the case of Quadrays, we speak of four quadrants and distinguish them in terms of which basis vector is not needed i.e. at least one vector is 0 -- the vector pointing away from the quadrant we're in, when addressing some arbitrary point in space.
The tetrahedron's four quadrays splay apart from one another at the origin (0,0,0,0) at an angle of about 109.47 degrees. We may think of any two of them as forming a pair of "rabbit ears".
import qrays, itertools
from qrays import Qvector, Vector
perm = itertools.permutations
a = Qvector((1,0,0,0))
b = Qvector((0,1,0,0))
c = Qvector((0,0,1,0))
d = Qvector((0,0,0,1))
a.angle(b)
b.angle(c)
My focus is on the cube's six face diagonals. The cube has one corner in each octant of XYZ and I'm making sure my (1,0,0,0) corresponds to a corner in the all-positive octant. I want the edges of my tetrahedron (cube face diagonals) to be 1, so the cube edges are sqrt(2)/2.
a.xyz
xyz_vector(x=0.25*sqrt(2), y=0.25*sqrt(2), z=0.25*sqrt(2))
b.xyz
xyz_vector(x=-0.25*sqrt(2), y=-0.25*sqrt(2), z=0.25*sqrt(2))
The XYZ coordinates of quadray (1,0,0,0):
a.xyz.x.evalf(), a.xyz.y.evalf(), a.xyz.z.evalf()
(0.353553390593274, 0.353553390593274, 0.353553390593274)
To connect back to the topic of cube's 12 mid-edges, per the four mutually intersecting hexagons we've been playing with, that's where I'm defining "the 12 sphere centers around a central sphere" as linear combinations of the four quadrays in question.
perm = itertools.permutations
midpoints = {p for p in perm([2,1,1,0],4)}
midpoints
{(0, 1, 1, 2), (0, 1, 2, 1), (0, 2, 1, 1), (1, 0, 1, 2), (1, 0, 2, 1), (1, 1, 0, 2), (1, 1, 2, 0), (1, 2, 0, 1), (1, 2, 1, 0), (2, 0, 1, 1), (2, 1, 0, 1), (2, 1, 1, 0)}
a.xyz.x
a.xyz.x.evalf()
for v in midpoints:
vec=Qvector(v)
print(f"({float(vec.xyz.x.evalf()):7.4f}, "
f"{float(vec.xyz.y.evalf()):7.4f}, "
f"{float(vec.xyz.z.evalf()):7.4f})")
( 0.7071, 0.0000, -0.7071) ( 0.0000, 0.7071, 0.7071) (-0.7071, 0.7071, 0.0000) (-0.7071, 0.0000, -0.7071) ( 0.0000, 0.7071, -0.7071) ( 0.0000, -0.7071, 0.7071) ( 0.7071, 0.0000, 0.7071) ( 0.7071, 0.7071, 0.0000) ( 0.7071, -0.7071, 0.0000) (-0.7071, 0.0000, 0.7071) (-0.7071, -0.7071, 0.0000) ( 0.0000, -0.7071, -0.7071)
So now we're ready to make hops in any of those 12 directions, from sphere to sphere, and stay with whole number non-negative coordinates. This is the CCP ocean, with 12 spheres around 1.
Give me any four points in space and I can return its tetra-volume using a variant of the Caley-Menger determinant formula. The six edges of the tetrahedron are lowercase a..f while those same lengths to the 2nd power will be A... F.
import numpy as np
import math
def CM_ivm(a, b, c, d, e, f):
# double and 2nd power us
A,B,C,D,E,F = [(2*x)**2 for x in (a,b,c,d,e,f)]
# Construct a 5x5 matrix per Caley-Menger
M = np.ones((5,5))
M[0,0:5] = 0, 1, 1, 1, 1
M[1,0:5] = 1, 0, A, B, C
M[2,0:5] = 1, A, 0, D, F
M[3,0:5] = 1, B, D, 0, E
M[4,0:5] = 1, C, F, E, 0
print(M) # comment out?
return round(math.sqrt(np.linalg.det(M))/16, 4) # Syn3 factored in
CM_ivm(1,1,1,1,1,1)
[[0. 1. 1. 1. 1.] [1. 0. 4. 4. 4.] [1. 4. 0. 4. 4.] [1. 4. 4. 0. 4.] [1. 4. 4. 4. 0.]]
1.0
There's another algorithm that makes more sense to share at this juncture, since we're talking about Quadrays in particular.
Here it is:
$$ V_{ivm} = (1/4) \begin{vmatrix} a0&a1&a2&a3&1\\ b0&b1&b2&b3&1\\ c0&c1&c2&c3&1\\ d0&d1&d2&d3&1\\ 1&1&1&1&0\\ \end{vmatrix} $$In this one, which I credit to Tom Ace, one of the designers behind Quadrays, the 4-tuple coordinates of the Quadrays themselves serve as inputs, to a determinant. This would not be Caley-Menger.
def volume(q0, q1, q2, q3):
"""
Construct a 5x5 matrix per Tom Ace
"""
A = np.ones((5,5)) # shape, all 1s except..
A[4,4] = 0 # zero in lower right corner
A[0,0:4] = q0.coords
A[1,0:4] = q1.coords
A[2,0:4] = q2.coords
A[3,0:4] = q3.coords
print(A) # comment out?
return abs(np.linalg.det(A))/4 # that's it!
volume(a,b,c,d)
[[1. 0. 0. 0. 1.] [0. 1. 0. 0. 1.] [0. 0. 1. 0. 1.] [0. 0. 0. 1. 1.] [1. 1. 1. 1. 0.]]
1.0
from IPython.display import YouTubeVideo
YouTubeVideo("kJYFleyQiGo")
Suppose we're sharing the concept of "vector addition". We use "retro rockets" that shoot mass exclusively away from the origin.
In the assembly below we have six thrusters oriented towards the corners of a regular octahedron or, same thing, to the face centers of a surrounding cube (origin at the center).
The "amount of thrust" is the magnitude of a thrust vector, i.e. its length. When it comes to orientation, we have only these six to choose from. But by firing more than one at the same time, we beget a "vector sum" that puts one vector tail at the tip of the other.
Vector addition is commutative:
$$ \vec{z} + \vec{x} + \vec{y} = \vec{y} + \vec{x} + \vec{z} $$Here's the six rocket assembly on the launch pad, ready to shoot upward vertically when its three downward-aimed thrusters all fire simultaneously.
In the alternative retro rocket assembly, depicted below, we only have four thrusters, aimed from the origin to the vertices of a regular tetrahedron, or, same thing, to a regular tetrahedron's face centers.
As above, the "amount of thrust" is the magnitude of a thrust vector, i.e. its length. When it comes to orientation, we have only these four to choose from. But by firing more than one at the same time, we beget a "vector sum" that puts one vector tail at the tip of the other.
The order in which vectors are added makes no difference:
$$ \vec{d} + \vec{a} + \vec{b} + \vec{c} = \vec{d} + \vec{b} + \vec{a} + \vec{c} $$Realistically speaking, these retro rockets have finite power, so the magnitude of the vectors is finite also. Within a certain radius, limited by thruster power, a combination of thrusts should, in theory, be able to sum to any point.
The ship itself (or pod) is then imagined to move precisely opposite the sum total thrust vector, at 180 degrees thereto. In practice, rotation would enter the picture. However this is a thought experiment and we only allow translation, with no change in orientation, in either assembly.
Both "ships" are capable of moving in any linear direction based on their "thrust pattern" i.e. based on a linear tip-to-tail addition of six or four rockets respectively. Not all rockets need to fire. Indeed, rockets at 180 degrees to one another need not work against each other.
a.xyz
xyz_vector(x=0.25*sqrt(2), y=0.25*sqrt(2), z=0.25*sqrt(2))
b.xyz
xyz_vector(x=-0.25*sqrt(2), y=-0.25*sqrt(2), z=0.25*sqrt(2))
(a-b).length()
1.0
c.xyz
xyz_vector(x=-0.25*sqrt(2), y=0.25*sqrt(2), z=-0.25*sqrt(2))
d.xyz
xyz_vector(x=0.25*sqrt(2), y=-0.25*sqrt(2), z=-0.25*sqrt(2))
print((a - b).length(),
(a - c).length(),
(a - d).length(),
(b - c).length(),
(c - d).length(),
(d - b).length())
1.0 1.0 1.0 1.0 1.0 1.0
for v in midpoints:
print(Qvector(v).length())
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
a
ivm_vector(a=1, b=0, c=0, d=0)
-a
ivm_vector(a=0, b=1, c=1, d=1)
q = Qvector((1,0,0,-1))
q
ivm_vector(a=2, b=1, c=1, d=0)
-q
ivm_vector(a=0, b=1, c=1, d=2)
Lets stack four balls, with diameter = $1$, radius = $1/2$, in the SCP (simple cubic) arrangement, giving a cube of edges D. The face diagonals of said cube are $\sqrt{2}$.
The tetravolume of a tet, with edges $\sqrt{2}$, is $\sqrt{2}^{3}$. The corresponding cube would have three times that volume.
Per convention, the XYZ unit cube has edges R, half the length of the IVM prime vector. The cube we're building therefore consists of 8 such unit cubes for a total volume of 8.
If we start with XYZ coordinates $(1/2, 1/2, 1/2)$ for the ball in the 1st octant (+, +, +), with identically sized spheres in the other octants, such that cube edges = 1 = D, then we have the corresponding quadray coordinates.
i = j = k = 1/2 # XYZ vectors scaled to 1/2 D = R
# 8 SCP ball centers = 8 unit cubes centered around the origin
A = Vector(( i, j, k))
B = Vector(( i, j, -k))
C = Vector(( i, -j, k))
D = Vector((-i, j, k))
E = Vector(( i, -j, -k))
F = Vector((-i, j, -k))
G = Vector((-i, -j, k))
H = Vector((-i, -j, -k))
for v in A,B,C,D,E,F,G,H:
qv = v.quadray()
a, b, c, d = [float(x.evalf(4)) for x in [qv.a, qv.b, qv.c, qv.d]]
print(f"({a:5.3f}, {b:5.3f}, {c:5.3f}, {d:5.3f}), Length = {qv.length():10.8f}")
(1.414, 0.000, 0.000, 0.000), Length = 0.86602540 (1.414, 0.000, 1.414, 1.414), Length = 0.86602540 (1.414, 1.414, 0.000, 1.414), Length = 0.86602540 (1.414, 1.414, 1.414, 0.000), Length = 0.86602540 (0.000, 0.000, 0.000, 1.414), Length = 0.86602540 (0.000, 0.000, 1.414, 0.000), Length = 0.86602540 (0.000, 1.414, 0.000, 0.000), Length = 0.86602540 (0.000, 1.414, 1.414, 1.414), Length = 0.86602540
Such a cube has all edges $1$, face diagonals $\sqrt{2}$ and a body diagonal of $\sqrt{3}$.
The inscribed tetrahedron has edges $\sqrt{2}$ instead of $1$, the length we would have with a CCP packing.
Its tetravolume would be $\sqrt{2}^{3}$.
big_tetra = pow(2, 1/2) ** 3
The corresponding cube tetravolume, in which big_tetra
is inscribed would be three times that.
ivm_big_cube = big_tetra * 3
ivm_big_cube
8.485281374238571
The cube's XYZ volume is 8, because we have an R-edged cube in each octant.
xyz_big_cube = 8.0
The constant S3 is therefore the IVM:XYZ ratio between the two measures.
S3 = ivm_big_cube / xyz_big_cube
S3 # the Synergetics Constant for converting XYZ <--> IVM volume
1.0606601717798214
Related readings: