Animation Code
The Python code that animates the GEM equations is shown below. This is not a theoretical model that is simulated via an animation - it's the dynamic graphing of the math equations over time.
#FILE: cT_Globals.py
#AUTHOR: Joseph Daniel JD Adamson
#VERSION: 20260908
#PURPOSE: Consolidate all global data for general usage
import sys
import numpy as np
phi = (1 + np.sqrt(5)) / 2.0 # by definition
orTho = np.pi/2.0 # Right-Angles are Orthogonal: T reflects symmetric right_angles. In GEM c=orTho=pi/2 radians
sym_orTho = -np.pi/2.0 # The compliment. In GEM -c = -orTho=-pi/2 radians
max_float = sys.float_info.max # Substitute for infinity..å
#FILE: animate_GEM.py
#AUTHOR: Joseph Daniel JD Adamson
#VERSION: 20260908
#PURPOSE: Animate the GEM::cT_<form> Equations
import math
import cmath
import cT_Utilities
from cT_Utilities import get_inputs
import cT_Globals
from cT_Globals import phi, orTho, sym_orTho, max_float
import cT_Calcs
from cT_Calcs import calc_Spray_Anchor, calc_Stitch
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, PillowWriter
##### Animation Control Inputs #####
object_kind, use_ln_spray, save_animation, show_animation, width, height, frames, interval, fps, elec_color, posi_color, anchor_color, sym_anch_color = \
get_inputs()
print(f"cT_Animate: object_kind={object_kind}, use_ln_spray={use_ln_spray}, save_animation={save_animation}, width={width}, height={height}, show_animation={show_animation}, frames={frames}, interval={interval}, fps={fps}", flush=True)
# Initialize the Animation Elements #
fig, ax = plt.subplots(figsize=(7, 7))
ax.set_xlim(-width, height)
ax.set_ylim(-height, height)
ax.set_aspect('equal')
ax.grid(True, linestyle='--', alpha=0.5)
ax.axhline(0, color='gray', linewidth=0.8)
ax.axvline(0, color='gray', linewidth=0.8)
circulation_dot, = ax.plot( [], [], 'o', ms=15, label='Circulation Complete', color='white')
circulation_dot.set_data([width], [height])
if object_kind == "Light" or object_kind == "Electron":
electron_dot, = ax.plot( [], [], 'o', ms=7, label='-cT_Stitch (e-)', color=elec_color, alpha=0.5)
electron_dots = { 0:electron_dot }
anchor_dot, = ax.plot( [], [], 'o', ms=10, label='-cT_Anchor (Inertia)', color=anchor_color, alpha=0.5)
anchor_dots = { 0:anchor_dot }
if object_kind == "Light" or object_kind == "Positron":
positron_dot, = ax.plot( [], [], 'o', ms=7, label='cT_Stitch (e+)', color=posi_color, alpha=0.5)
positron_dots = { 0:positron_dot }
sym_anchor_dot, = ax.plot( [], [], 'o', ms=10, label='sym_cT_Anchor (~Inertia)', color=sym_anch_color, alpha=0.5)
sym_anchor_dots = { 0:sym_anchor_dot }
ax.legend(loc='upper right', fontsize=8)
ax.set_title(f"{object_kind} Form of GEM 'cT' Equations")
# set non-0 before use in denominator!
dt = 0
sym_dt = 0
ln = "" # for labeling the output file
if use_ln_spray:
ln = "ln_"
#jd To emulate the continuous 0 < n < 1 realm, we're using q/sym_q as the Phi-non-interference path of GEM to flow through, updating the exponent from n
q = sym_q = 0.0
######## functions ##############
max_spray = 0.0
max_anchor = 0.0
max_imag = 0.0
# Perform the GEM equation calculations for the animation
def GEM_Calcs( n, dt ):
global orTho, phi, sym_orTho, max_float
global q, sym_q
global max_spray, max_anchor, max_imag
global width, height
cT_Spray = cT_Anchor = sym_cT_Spray = sym_cT_Anchor = complex(0,0)
cT_Spray, cT_Anchor, sym_cT_Spray, sym_cT_Anchor = \
calc_Spray_Anchor(n, dt)
​
if cT_Spray.imag > max_imag:
max_imag = cT_Spray.imag
print(f"GEM_Calcs(n={n}, dt={dt}): max_imag up to {max_imag} (max_float={max_float})", flush=True)
if cT_Spray.real > max_spray:
max_spray = cT_Spray.real
print(f"GEM_Calcs(n={n}, dt={dt}): max_spray up to {max_spray} (max_float={max_float})", flush=True)
if sym_cT_Spray.real > max_spray:
max_spray = sym_cT_Spray.real
print(f"GEM_Calcs(n={n}, dt={dt}): max_sym_spray up to {max_spray} (max_float={max_float})", flush=True)
if cT_Anchor.real > max_anchor:
max_anchor = cT_Anchor.real
print(f"GEM_Calcs(n={n}, dt={dt}): max_anchor up to {max_anchor} (max_float={max_float})", flush=True)
if sym_cT_Anchor.real > max_anchor:
max_anchor = sym_cT_Anchor.real
print(f"GEM_Calcs(n={n}, dt={dt}): max_sym_anchor up to {max_anchor} (max_float={max_float})", flush=True)
role_colors = False
if role_colors:
# For the animation, keep the colors consistent for the anchor and spray roles,
# not the "energy flowing" through different roles
if cT_Spray.real < 1.0:
temp = cT_Anchor
cT_Anchor = cT_Spray
cT_Spray = temp
if sym_cT_Spray.real < 1.0:
temp = sym_cT_Anchor
sym_cT_Anchor = sym_cT_Spray
sym_cT_Spray = temp
return cT_Spray, cT_Anchor, sym_cT_Spray, sym_cT_Anchor
###########################################################
# Calculate the positions of the points to be displayed as dots in the animation
def get_positions(n, dt):
global orTho, sym_orTho, phi, use_ln_spray
print(f"get_position(n={n}, dt={dt}) Start")
cT_Spray = cT_Anchor = sym_cT_Spray = sym_cT_Anchor = cT_Stitch = sym_cT_Stitch = complex(0,0)
cT_Spray, cT_Anchor, sym_cT_Spray, sym_cT_Anchor = GEM_Calcs( n, dt )
if use_ln_spray:
if math.fabs(cT_Spray.real) > math.fabs(cT_Anchor.real):
if cT_Spray.real < 0:
cT_Spray = complex( -math.log(1.0 + math.fabs(cT_Spray.real)), cT_Spray.imag)
else:
cT_Spray = complex( math.log(1.0 + math.fabs(cT_Spray.real)), cT_Spray.imag)
else:
if cT_Anchor.real < 0:
cT_Anchor = complex( -math.log(1.0 + math.fabs(cT_Anchor.real)), cT_Anchor.imag)
else:
cT_Anchor = complex( math.log(1.0 + math.fabs(cT_Anchor.real)), cT_Anchor.imag)
if sym_cT_Spray.real > sym_cT_Anchor.real:
if sym_cT_Spray.real < 0:
sym_cT_Spray = complex( -math.log(1.0 + math.fabs(sym_cT_Spray.real)), sym_cT_Spray.imag)
else:
sym_cT_Spray = complex( math.log(1.0 + math.fabs(sym_cT_Spray.real)), sym_cT_Spray.imag)
else:
if sym_cT_Anchor.real < 0:
sym_cT_Anchor = complex( -math.log(1.0 + math.fabs(sym_cT_Anchor.real)), sym_cT_Anchor.imag)
else:
sym_cT_Anchor = complex( math.log(1.0 + math.fabs(sym_cT_Anchor.real)), sym_cT_Anchor.imag)
cT_Stitch, sym_cT_Stitch = calc_Stitch( cT_Spray, cT_Anchor )
return cT_Spray, cT_Anchor, sym_cT_Spray, sym_cT_Anchor, cT_Stitch, sym_cT_Stitch
#####################################################################################
# Update the animation points every frame for the specified object_kind: Light, Electron, Positron
prev_frame = -1
def update(frame):
global object_kind, prev_frame, dt, sym_dt, use_ln_spray
global electron_dot, positron_dot, electron_dots, positron_dots, anchor_dot, anchor_dots
global width, height
#jd I'm seeing frame=0 Repeat a 2nd time, but frame>0 does Not Repeat
if prev_frame != frame:
prev_frame = frame
else:
# Skip any duplicate frames
return
print(f"update(frame={frame})", flush=True)
# A fully 720_degree spinor rotation closes at n=8 for 3 orgothogonal 3D "dimensions" (0,1,2), (3,4,5), (6,7,8)
# 0<n<8 -> 9 values
n = frame % 9
# The local clock-tick -> passage_of_time is once per spinor quadrature cycle
# When the Animation Loops, blink a different color than the spinor rotation
circulation_dot.set_color('white')
if n == 0:
dt += 1
sym_dt -= 1
print(f"update(frame={frame}): incremented dt = {dt}; sym_dt={sym_dt}", flush=True)
circulation_dot.set_color('blue')
if (frame == 0):
circulation_dot.set_color('red')
# # Limit the animation based on dt.. after dt=4 the stitch values are HUGE and off any reasonable graph.. so, never mind!
# if dt > 4:
# print(f"update(frame={frame}): dt>4 = {dt}", flush=True)
# return
cT_Spray = cT_Anchor = sym_cT_Spray = sym_cT_Anchor = cT_Stitch = sym_cT_Stitch = complex(0,0)
# Calculate the point positions animated by the colored dots
cT_Spray, cT_Anchor, sym_cT_Spray, sym_cT_Anchor, cT_Stitch, sym_cT_Stitch = \
get_positions(n, dt)
# In GEM the cT_Stitch maps to the Electron while the symmetric object is the Positron
# The real values plot on X_Axis, imag values on Y_Axis as Spatial Coordinates vs Local_Time_Phasing
if object_kind == "Light" or object_kind == "Electron":
electron_dot.set_data([cT_Stitch.real], [cT_Stitch.imag])
electron_dots.update( {n:electron_dot} )
anchor_dot.set_data( [cT_Anchor.real], [cT_Anchor.imag] )
anchor_dots.update( {n:anchor_dot} )
if object_kind == "Light" or object_kind == "Positron":
positron_dot.set_data([sym_cT_Stitch.real], [sym_cT_Stitch.imag])
positron_dots.update( {n:positron_dot} )
sym_anchor_dot.set_data( [sym_cT_Anchor.real], [sym_cT_Anchor.imag] )
sym_anchor_dots.update( {n:sym_anchor_dot} )
#jd Don't return values so big they crash the animation engine..
#jd May as well clamp just outside the display extent.. not going to show anyway
if math.fabs(cT_Spray.real) > width:
cT_Spray = complex(width+100, cT_Spray.imag)
try:
if cT_Spray.imag > height:
cT_Spray = complex(cT_Spray.real, height+100)
except Exception as e:
print(f"Update(frame={frame}): Exception checking imag part of cT_Spray = {cT_Spray}", flush=True)
# Update(frame=0): Exception checking imag part of cT_Spray = (101, 2.7684888595764234)
if math.fabs(sym_cT_Spray.real) > width:
sym_cT_Spray = complex(width+100, sym_cT_Spray.imag)
if sym_cT_Spray.imag > height:
sym_cT_Spray = complex(sym_cT_Spray.real, height+100)
if math.fabs(cT_Anchor.real) > width:
cT_Anchor = complex(width+100, cT_Anchor.imag)
if cT_Anchor.imag > height:
cT_Anchor = complex(cT_Anchor.real, height+100)
if math.fabs(sym_cT_Anchor.real) > width:
sym_cT_Anchor = complex(width+100, sym_cT_Anchor.imag)
if math.fabs(sym_cT_Anchor.imag) > height:
sym_cT_Anchor = complex(sym_cT_Anchor.real, height+100)
if object_kind == "Light":
return anchor_dots, sym_anchor_dots, electron_dots, positron_dots
elif object_kind == "Electron":
return anchor_dots, electron_dots
elif object_kind == "Positron":
return sym_anchor_dots, positron_dots
#Note: The Animation will encounter "divide-by-0" as dx/dt->0/0=c, but conclude "inf" and OverflowError that this code cannot catch, since it's the animation code..
anim = FuncAnimation(fig, update, frames=frames, interval=interval, blit=False)
print(f"Done. {ln} {width}x{height}, frames={frames}", flush=True)
writer = PillowWriter(fps=fps)
if save_animation:
anim_file = f"Gifs/{object_kind}_{ln}{width}x{height}.gif"
anim.save(anim_file, writer=writer)
print(f"Animation saved in {anim_file}", flush=True)
if show_animation:
plt.show()
#FILE: cT_Calcs.py
#AUTHOR: Joseph Daniel JD Adamson
#VERSION: 20260908
#PURPOSE: Encapsulate the GEM equations and math
import cmath
import math
import numpy as np
import cT_Globals
from cT_Globals import phi, orTho, sym_orTho, max_float
import cT_Utilities
from cT_Utilities import is_equiv handle_invariant_ratios
def calc_dx(dt):
global orTho, sym_orTho
dx = dt*orTho # c=dx/dt -> dt*c = dx; in GEM c=orTho
sym_dx = dt*(sym_orTho) # E=m*c^2 -> sqrt(E/m)=(c, -c) -> E/m = (c^2, (-c)^2)
return dx, sym_dx
def calc_GEM(n, dt):
global orTho, phi, max_float
global width, height
dx, sym_dx = calc_dx(dt)
# At dx|sym_dx = orTho|sym_orTho, dx/dt -> 0/0 = c, but the math chokes on all divide-by-0's, even these valid cases.
# Catch values that exceed the graph limits (eg inf!) and just don't display them..
#print(f"calc_GEM(n={n}, dt={dt}): dx={dx}, sym_dx={sym_dx}")
try:
GEM = cmath.exp( phi**((dx * ((1j * orTho)**n))) ) / dt
except Exception as e:
print(f"calc_GEM(n={n}, dt={dt}, dx={dx}): EXCEPTION on GEM calculation.\n{e}", flush=True)
GEM = max_float
print(f"calc_GEM(n={n}, dt={dt}, dx={dx}): Calculated GEM.real={GEM.real}; np.round(GEM,6)={np.round(GEM,6)}", flush=True)
# The GEM equation yields exact 0.5 and 0.3333 values, so let's use precise values without roundoff garbage in those cases
if is_equiv(GEM.real, 0.5) or is_equiv(GEM.real, 0.333333):
GEM = complex(np.round(GEM.real,6), GEM.imag)
return GEM
def calc_q( n, complex_val ):
print(f"calc_q(n={n}, complex_val={complex_val}", flush=True)
# Fractional part of exponent 0<q<1 in phi-compliant non-interference compression
q = complex_val.real
if math.fabs(q) > 1.0:
q = 1.0/q
q = (n + q)
if q > n+1.0:
print(f"calc_q():Error q > n+1?? n={n}, q={q}", flush=True)
return q
def calc_Spray_Anchor( n, dt ):
global max_float
dx, sym_dx = calc_dx(dt)
cT_Spray = cT_Anchor = sym_cT_Spray = sym_cT_Anchor = complex(0,0)
q = sym_q = 0.0
GEM = calc_GEM(n, dt)
q = calc_q( n, GEM )
if q > n + 1.0:
print(f"calc_Spray_Anchor():Error q > n+1?? n={n}, q={q}", flush=True)
try:
cT_Spray = cmath.exp( phi**((dx * ((1j * orTho)**q))) ) / dt
print(f"calc_Spray_Anchor(n={n}, dt={dt}): at dx={dx}, q={q}: cT_Spray={cT_Spray}, np.rounded={np.round(cT_Spray,6)}", flush=True)
except Exception as e:
print(f"calc_Spray_Anchor(n={n}, dt={dt}, dx={dx}): EXCEPTION on cT_Spray calculation. at dx/dt->0/0 v=c at (orTho, -orTho):\n\t{e}", flush=True)
cT_Spray = complex(max_float, 0)
try:
cT_Anchor = cmath.exp( phi**(sym_dx * ((1j * sym_orTho)**q)) ) / dt
print(f"calc_Spray_Anchor(n={n}, dt={dt}): at dx={dx}, q={q}: cT_Anchor={cT_Anchor}, np.rounded={np.round(cT_Anchor,6)}", flush=True)
except Exception as e:
print(f"calc_Spray_Anchor(n={n}, dt={dt}, dx={dx}): EXCEPTION on cT_Anchor calculation:\n\t{e}", flush=True)
cT_Spray = complex(max_float, 0)
# The symmetrical spray&anchor are spatial complements, where -dx/-dt = v = dx/dt;
# but the intrinsically reversed phasing of imaginary-part does not reverse.
sym_cT_Spray = complex( -cT_Spray.real, cT_Spray.imag )
sym_cT_Anchor = complex( -cT_Anchor.real, cT_Anchor.imag )
# The GEM equation yields exact 0.5 and 0.3333 values, so let's use precise values without roundoff garbage in those cases
if is_equiv(cT_Spray.real, 0.5) or is_equiv(cT_Spray.real, 0.333333):
GEM = complex(np.round(cT_Spray.real,6), cT_Spray.imag)
if is_equiv(cT_Anchor.real, 0.5) or is_equiv(cT_Anchor.real, 0.333333):
GEM = complex(np.round(cT_Anchor.real,6), cT_Anchor.imag)
if is_equiv(sym_cT_Spray.real, 0.5) or is_equiv(sym_cT_Spray.real, 0.333333):
GEM = complex(np.round(sym_cT_Spray.real,6), sym_cT_Spray.imag)
if is_equiv(sym_cT_Anchor.real, 0.5) or is_equiv(sym_cT_Anchor.real, 0.333333):
GEM = complex(np.round(sym_cT_Anchor.real,6), sym_cT_Anchor.imag)
# Electron charge is arbitrarily called 'e-' as negative, but aligns with positive dx/dt; positrons the opposite
return cT_Spray, cT_Anchor, sym_cT_Spray, sym_cT_Anchor
def calc_Stitch( cT_Spray, cT_Anchor ):
global orTho
cT_Stitch = sym_cT_Stitch = 0j
cT_Stitch = handle_invariant_ratios( cT_Spray, cT_Anchor )
# The real values plot on X_axis, and the Stitches are complementary in Space. The imaginary internal phasing on the Y_axis is not affected.
sym_cT_Stitch = complex( -cT_Stitch.real, sym_cT_Stitch.imag )
return cT_Stitch, sym_cT_Stitch
#FILE: cT_Utilities.py
#AUTHOR: Joseph Daniel JD Adamson
#VERSION: 20260908
#PURPOSE: Collect the generally-applicable "utility functions".
import sys
import math
import cmath
import cT_Globals
from cT_Globals import phi, orTho, sym_orTho, max_float
# User input option defaults
object_kind = "Light"
use_ln_spray = True
save_animation = False
show_animation = True
width = height = 1
frames = 36
interval = 500
fps = 8 # n in 0..8 = 9 steps times 8 fps -> 72 frames per quad at 4 per ortho
elec_color = "red"
posi_color = "green"
anchor_color = "orange"
sym_anch_color = "blue"
def str_to_bool( bool_str ):
if bool_str == "True" or bool_str == "true" or bool_str == "T" or bool_str == "t" or bool_str == "1":
return True
else:
return False
####################
# Process user inputs
def get_inputs():
global object_kind, use_ln_spray, save_animation, show_animation, width, height, frames, interval, fps
global elec_color, posi_color, anchor_color, sym_anch_color
for arg_num in range(1, len(sys.argv) ):
cur_arg = sys.argv[arg_num]
eq_pos = cur_arg.find('=')
if eq_pos > 0:
parameter = cur_arg[0:eq_pos]
value = cur_arg[eq_pos+1:]
else:
print(f"User_Input not of the form 'parameter=value'; eq_pos={eq_pos} - Using defaults..", flush=True)
break
match parameter:
case "object_kind":
if value == "Light" or value == "light" or value == "Photon" or value == "photon" or value == "cT_Flow":
object_kind = "Light"
elif value == "Electron" or value == "cT_Stitch":
object_kind = "Electron"
elif value == "Positron" or value == "sym_cT_Stitch":
object_kind = "Positron"
print(f"Set object_kind={object_kind}, per user input", flush=True)
case "use_ln":
use_ln_spray = str_to_bool(value)
print(f"Set use_ln_spray={use_ln_spray} ({value}), per user input", flush=True)
case "width":
try:
width = height = int(value)
except Exception as e:
width = height = float(value) # Fractional values less than one
print(f"Set width=height={width}, per user input", flush=True)
case "save":
save_animation = str_to_bool(value)
print(f"Set save_animation={save_animation}, per user input", flush=True)
case "show":
show_animation = str_to_bool(value)
print(f"Set show_animation={show_animation}, per user input", flush=True)
case "frames":
frames = int(value)
print(f"Set frames={frames}, per user input", flush=True)
case "interval":
interval = int(value)
print(f"Set interval={interval}, per user input", flush=True)
case "fps":
fps = int(value)
print(f"Set fps={fps}, per user input", flush=True)
case "elec_color":
elec_color = value
print(f"Set electron color={elec_color}, per user input", flush=True)
case "posi_color":
posi_color = value
print(f"Set positron color={posi_color}, per user input", flush=True)
case "anchor_color":
anchor_color = value
print(f"Set anchor color={anchor_color}, per user input", flush=True)
case "sym_anch_color":
sym_anch_color = value
print(f"Set sym_anchor_color={sym_anch_color}, per user input", flush=True)
case _:
print(f"User Input {cur_arg} is not recognized and being ignored..", flush=True)
return object_kind, use_ln_spray, save_animation, show_animation, width, height, frames, interval, fps, elec_color, posi_color, anchor_color, sym_anch_color
####################################
# Check if floating point number are acceptably close, per absolute or relative epsilon values
# If return_details==True returns the equivalence, the error magnitude, and the absolute and relative epsilon values used
# Example usage:
# if is_equiv(x, 0.2):
# or
# equiv,err,abs_eps,rel_eps = is_equiv(x, 0.2)
# if not equiv:
# #log the error and magnitude with relevant epsilon values
def is_equiv(x, y, abs_eps=1e-9, rel_eps=1e-12, details=False):
#print(f"is_equiv() Start: x={x}, y={y}, abs_eps={abs_eps}, rel_eps={rel_eps}", flush=True)
equiv = abs(x - y) < max(abs_eps, rel_eps * max(abs(x), abs(y)))
if details:
return equiv, abs(x-y), abs_eps, rel_eps
return equiv
################
# Invariant_Ratio Examples: c=dx/dt and pi=C/D, no matter how small the numerator and denominator get.
def handle_invariant_ratios( numerator, denominator ):
global orTho, sym_orTho
# Both numerator and denominator being 0 signals an invariant ratio. We're dealing with dx/dt->0/0=+/-c=+/-orTho
if is_equiv(numerator, 0.0) and is_equiv(denominator, 0.0):
return orTho, sym_orTho
# Instead of generating 'inf', since we expect these large values, clamp at the max_float
if denominator == 0.0:
print(f"handle_invariant_ratios(numerator={numerator}, denominator={denominator}) Detected Non-Invariant_Ratio divide-by-0. You get max_float ({max_float})")
return max_float
return numerator/denominator

