codex/circle-exponential-oscillator.py

circle-exponential-oscillator.py
"""Generate circle-exponential-oscillator.png in cwd.

Tested with Python 3.14.4, numpy 2.3.5, matplotlib 3.10.7.
All curves are analytic; matching colored samples share the same angle.
"""

from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np


def main():
    angles=np.linspace(0,2*np.pi,600)
    samples=np.array([0,np.pi/3,np.pi/2,np.pi,3*np.pi/2])
    colors=['#2878ad','#c05a2a','#24805e','#8655a4','#ad4459']
    assert np.allclose(np.exp(1j*angles),np.cos(angles)+1j*np.sin(angles))
    assert np.isclose(np.cos(np.pi/3),.5)
    fig,axes=plt.subplots(1,2,figsize=(10.5,4.9),dpi=120,facecolor="white",
                          gridspec_kw={'width_ratios':[1,1.5]})
    axes[0].plot(np.cos(angles),np.sin(angles),color='#536778',lw=1.7)
    for phi,color in zip(samples,colors):
        axes[0].scatter(np.cos(phi),np.sin(phi),s=50,c=color,zorder=4)
        axes[1].scatter([phi,phi],[np.cos(phi),np.sin(phi)],s=35,c=color,zorder=4)
    phi=np.pi/3;x,y=np.cos(phi),np.sin(phi)
    axes[0].plot([0,x],[0,y],color=colors[1],lw=2)
    axes[0].plot([x,x],[0,y],'--',color='#aaaaaa',lw=1)
    axes[0].plot([0,x],[y,y],'--',color='#aaaaaa',lw=1)
    axes[0].text(x+.07,y+.08,r'$e^{i\pi/3}$',fontsize=12,color=colors[1])
    axes[0].text(x,-.22,r'$\cos(\pi/3)=1/2$',ha='center',fontsize=10)
    axes[0].text(-.23,y,r'$\sqrt{3}/2$',ha='right',fontsize=10)
    axes[0].set(xlim=(-1.35,1.35),ylim=(-1.3,1.3),aspect='equal',
                xlabel='Real part',ylabel='Imaginary part',title=r'$z(\theta)=e^{i\theta}$ on the unit circle')
    axes[1].plot(angles,np.cos(angles),color='#2878ad',lw=2,label=r'Cosine: $\operatorname{Re}z$')
    axes[1].plot(angles,np.sin(angles),color='#c05a2a',lw=2,label=r'Sine: $\operatorname{Im}z$')
    axes[1].set(xlim=(0,2*np.pi),ylim=(-1.25,1.25),xlabel=r'Angle $\theta$ (radians)',
                ylabel='Coordinate',title='The coordinates oscillate as the point rotates')
    axes[1].set_xticks([0,np.pi/2,np.pi,3*np.pi/2,2*np.pi],['0',r'$\pi/2$',r'$\pi$',r'$3\pi/2$',r'$2\pi$'])
    axes[1].legend(loc='lower left',fontsize=10,framealpha=.9)
    for ax in axes:
        ax.axhline(0,color='#bbbbbb',lw=.8);ax.axvline(0,color='#bbbbbb',lw=.8)
        ax.grid(alpha=.14);ax.set_facecolor('white')
    fig.suptitle('Geometry, algebra, and differential equations describe the same motion',fontsize=15)
    fig.text(.5,.026,'Matching colors mark the same angle. Both coordinates satisfy '+r'$x^{\prime\prime}(\theta)+x(\theta)=0$'+'.',ha='center',fontsize=10)
    fig.tight_layout(rect=(0,.07,1,.925))
    fig.savefig(Path.cwd()/'circle-exponential-oscillator.png',facecolor='white',transparent=False)
    plt.close(fig)


if __name__=='__main__':
    main()