"""One exact finite x-k loop, drawn as a full complex function. Chronology: Tk(b), Tx(a), Tk(-b), Tx(-a), with Tx(a)f(x)=f(x-a). The terminal function is exp(-iab)f0; ab=pi/2. No residual-phase replay. The camera is fixed. The gray reference is always the original function. Gold is the silhouette of the magnitude surface; there is no internal mesh. No beads, tracked curve points, renormalization, or physical-time evolution. """ from __future__ import annotations import argparse from functools import lru_cache import json import math import subprocess import time import generate_symmetry_packet_phase_modes as art np = art.np Image, ImageDraw = art.Image, art.ImageDraw OUT = art.OUT NAME = "symmetry-ccr-loop-complex" WIDTH, HEIGHT, SS = 1440, 900, art.SS FPS = 30 INTRO, LEG, END_HOLD = 1., 3., 3. CLOSE = INTRO+4*LEG DURATION = CLOSE+END_HOLD FRAMES = round(DURATION*FPS) A = 1.25 B = math.pi/(2*A) SIGMA, K0 = .85, 10. XMIN, XMAX = -4.8, 5.8 X = np.linspace(XMIN, XMAX, 2401) PLOT_LEFT, PLOT_RIGHT = 403., 1367. BASELINE, GAIN, DEPTH = 413., 122., .30 CAMERA = np.exp(-1j*math.radians(25)) BG, PANEL = art.BG, art.PANEL INK, MUTED, BORDER = art.INK, art.MUTED, art.BORDER BLUE, GOLD, AXIS = art.BLUE, art.GOLD, art.AXIS PURPLE = (111,78,143) GRAY = (163,158,148) BACK_BLUE = tuple(round(.38*c+.62*p) for c,p in zip(BLUE,PANEL)) text, line, curve = art.text, art.line, art.curve arrow, px = art.arrow, art.px SAMPLES = (0., 3.9, 6.9, 9.9, 12.3, 15.5) def initial(x): x=np.asarray(x) return np.exp(-x*x/(4*SIGMA**2)+1j*K0*x) def parameters(seconds): if seconds<=INTRO: return 0.,0.,0.,0,0. if seconds>=CLOSE: return 0.,0.,-A*B,5,1. elapsed=seconds-INTRO leg=min(3,int(elapsed//LEG)) s=art.eased((elapsed-leg*LEG)/LEG) if leg==0: return 0.,B*s,0.,1,s if leg==1: return A*s,B,-A*B*s,2,s if leg==2: return A,B*(1-s),-A*B,3,s return A*(1-s),0.,-A*B,4,s def value(x,u,v,gamma): return np.exp(1j*(v*np.asarray(x)+gamma))*initial(np.asarray(x)-u) def screen_x(x): return PLOT_LEFT+(np.asarray(x)-XMIN)/(XMAX-XMIN)*(PLOT_RIGHT-PLOT_LEFT) def project(x,z): w=np.asarray(z)*CAMERA return np.column_stack((screen_x(x)+GAIN*DEPTH*w.imag, BASELINE-GAIN*w.real)) def dashed(d,points,color,width=1.1,dash=7.,gap=6.): # Use screen arc length, so the dashes stay consistent along a helix. drawing=True remaining=dash for p,q in zip(points[:-1],points[1:]): delta=q-p length=float(np.linalg.norm(delta)) if length<1e-9: continue unit=delta/length used=0. while used=0 cuts=np.r_[0,np.flatnonzero(front[1:]!=front[:-1])+1,len(front)] for side,color in ((False,BACK_BLUE),(True,BLUE)): for start,end in zip(cuts[:-1],cuts[1:]): if bool(front[start])==side: curve(d,points[start:end+1],color,2.8) @lru_cache(None) def magnitude_outline(): # Outline of the projected union of all transverse magnitude disks. xs=np.linspace(-4.8,4.8,1601) radius=GAIN*np.exp(-xs*xs/(4*SIGMA**2)) centers=screen_x(xs) lo=float(np.min(centers-DEPTH*radius)) hi=float(np.max(centers+DEPTH*radius)) unit=(1-np.cos(np.linspace(0,np.pi,1801)))/2 sx=lo+(hi-lo)*unit h=np.sqrt(np.maximum(0,np.max(radius[:,None]**2- ((sx[None,:]-centers[:,None])/DEPTH)**2,axis=0))) return np.vstack((np.column_stack((sx,BASELINE-h)), np.column_stack((sx[::-1],BASELINE+h[::-1])))) def key(d): # Complex-plane axes are amplitude directions, not two extra spatial axes. origin=np.array([453.,578.]) theta=math.radians(25) for label,delta,offset,anchor in ( ("x",np.array([61.,0.]),(10.,0.),"lm"), ("Re",np.array([-DEPTH*math.sin(theta),-math.cos(theta)])*49,(-5.,-5.),"rb"), ("Im",np.array([DEPTH*math.cos(theta),-math.sin(theta)])*49,(9.,-6.),"lb"), ): tip=origin+delta arrow(d,origin,tip,(*MUTED,190),1.2,5) text(d,tip+offset,label,15,MUTED,anchor=anchor) @lru_cache(None) def background(): image=Image.new("RGB",(WIDTH*SS,HEIGHT*SS),BG) d=ImageDraw.Draw(image,"RGBA") text(d,(38,25),"A closed loop leaves a phase",32,bold=True) text(d,(40,77),"The shifts return to zero. The complex function retains a turn.",19,MUTED) for box in ((28,136,343,817),(363,136,1412,817)): d.rounded_rectangle(px(box),radius=14*SS,fill=PANEL,outline=BORDER,width=SS) text(d,(50,157),"The x–k loop",25,bold=True) text(d,(389,157),"Complex wave function",25,bold=True) # Compact legend, with the actual visual styles. for x,label,color in ((391,"original",GRAY),(607,"current",BLUE),(823,"magnitude envelope",GOLD)): if color==GRAY: dashed(d,np.array([[x,209.],[x+30,209.]]),color,1.2) else: line(d,(x,209),(x+30,209),color,2 if color==BLUE else 1.3) text(d,(x+42,197),label,17,color) line(d,(PLOT_LEFT,BASELINE),(PLOT_RIGHT,BASELINE),(*AXIS,120),.8) # Reference curve and envelope are stationary throughout the entire movie. dashed(d,magnitude_outline(),(*GRAY,100),.8,dash=5,gap=7) dashed(d,project(X,initial(X)),(*GRAY,220),1.2) key(d) ruler=624. line(d,(PLOT_LEFT,ruler),(PLOT_RIGHT,ruler),AXIS,1.) for x in (-4,-2,0,2,4): sx=float(screen_x(x)) line(d,(sx,ruler-4),(sx,ruler+4),AXIS,1.) text(d,(sx,ruler+12),str(x).replace("-","−"),15,MUTED,anchor="ma") text(d,(PLOT_RIGHT+12,ruler),"x",18,MUTED,anchor="lm") # Fixed parameter rectangle; its cursor is not a marker on the function. left,right,top,bottom=87.,277.,252.,420. line(d,(left-18,bottom),(right+13,bottom),AXIS,1.2) line(d,(left,bottom+15),(left,top-18),AXIS,1.2) d.rectangle(px((left,top,right,bottom)),outline=(*GRAY,110),width=SS) text(d,(left-13,bottom+12),"0",15,MUTED,anchor="ra") text(d,(right,bottom+12),"a",18,MUTED,anchor="ma") text(d,(left-13,top),"b",18,MUTED,anchor="rm") text(d,(right+20,bottom),"Δx",17,MUTED,anchor="lm") text(d,(left,top-35),"Δk",17,MUTED,anchor="mm") text(d,(40,848),"Continuous translations along the four sides; a fixed view of (x, Re ψ, Im ψ).",15,MUTED) return image def draw_loop(d,u,v,leg,s): vertices=np.array([[87.,420.],[87.,252.],[277.,252.],[277.,420.],[87.,420.]]) completed=4 if leg==5 else max(0,leg-1) for j in range(completed): arrow(d,vertices[j],vertices[j+1],GOLD if j%2==0 else BLUE,3.,9) cursor=np.array([87.+190*u/A,420.-168*v/B]) if 1<=leg<=4: arrow(d,vertices[leg-1],cursor,GOLD if leg%2 else BLUE,3.,9) art.dot(d,cursor,PURPLE,5.5,outline=PANEL) text(d,(58,463),f"Δx = {u:.2f}",19,BLUE) text(d,(204,463),f"Δk = {v:.2f}",19,GOLD) formulas=(r"$T_k(b)$",r"$T_x(a)$",r"$T_k(-b)$",r"$T_x(-a)$") labels=("shift in k","shift in x","return in k","return in x") for j in range(4): y=521+j*57 active=(leg==j+1) color=GOLD if j%2==0 else BLUE if active: d.rounded_rectangle(px((48,y-4,324,y+43)),radius=6*SS, fill=(*color,15),outline=(*color,120),width=SS) text(d,(61,y+6),str(j+1),16,color if active or j