top of page

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
 

bottom of page