codexpast-exam-of-the-physics-2-paper-of-the-university-of-oxford2019-dipole-field-and-charge-path.py
past-exam-of-the-physics-2-paper-of-the-university-of-oxford/2019-dipole-field-and-charge-path.py
"""Original electrostatic field and orbit diagrams; Python 3.14, NumPy/Matplotlib."""
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams.update({'font.size': 12})
fig, (field, orbit) = plt.subplots(1, 2, figsize=(10, 4.8), layout='constrained', facecolor='white')
x = np.linspace(-2.5, 2.5, 181)
y = np.linspace(-2.5, 2.5, 181)
xx, yy = np.meshgrid(x, y)
ex = np.zeros_like(xx)
ey = np.zeros_like(xx)
mask = np.zeros_like(xx, dtype=bool)
for q, yq in [(1, 0.45), (-1, -0.45)]:
rr2 = xx**2 + (yy-yq)**2
mask |= rr2 < 0.13**2
rr2 = np.maximum(rr2, 0.05**2)
ex += q*xx/rr2**1.5
ey += q*(yy-yq)/rr2**1.5
norm = np.maximum(np.hypot(ex, ey), 1e-9)
field.streamplot(x, y, np.ma.array(ex/norm, mask=mask), np.ma.array(ey/norm, mask=mask), density=1.1, color='#416f91', linewidth=0.9, arrowsize=1)
for q, yq, color in [(1, 0.45, '#cf4338'), (-1, -0.45, '#2874bb')]:
field.scatter([0], [yq], s=180, color=color, zorder=4)
field.text(0, yq, '+' if q > 0 else '−', color='white', ha='center', va='center', zorder=5)
field.set(aspect='equal', xlim=(-2.4, 2.4), ylim=(-2.4, 2.4), xlabel='x', ylabel='y', title='Field lines of the finite dipole')
t = np.linspace(0, np.pi, 401)
orbit.plot(-np.cos(t), -np.sin(t), color='#226d3c', linewidth=2.5)
orbit.plot([-1, 1], [0, 0], ':', color='#888888')
orbit.scatter([-1, 1, 0], [0, 0, -1], color='#226d3c', zorder=3)
for pos, vec in [((-1, 0), (0, -0.45)), ((1, 0), (0, -0.45)), ((0, -1), (0, 0.55))]:
orbit.annotate('', xy=np.array(pos)+np.array(vec), xytext=pos, arrowprops={'arrowstyle':'->', 'color':'#b53131', 'lw':2})
orbit.annotate('', xy=(0.65, -0.76), xytext=(0.4, -0.91), arrowprops={'arrowstyle':'->', 'color':'#226d3c', 'lw':2})
orbit.annotate('', xy=(0.08, 0.7), xytext=(0.08, 0.25), arrowprops={'arrowstyle':'->', 'color':'#303030', 'lw':2})
orbit.text(0.17, 0.48, r'$\mathbf{p}$')
orbit.text(-1, 0.1, 'Release', ha='center')
orbit.text(1, 0.1, 'Turn back', ha='center')
orbit.text(0, -1.25, r'$r=d$; red arrows show force', ha='center')
orbit.text(0, -1.5, 'Ideal point dipole; release from rest', ha='center', fontsize=11)
orbit.scatter([0], [0], s=28, color='#333333')
orbit.set(aspect='equal', xlim=(-1.5, 1.5), ylim=(-1.7, 1.1), xlabel=r'$x/d$', ylabel=r'$y/d$', title='Particle path, distinct from a field line')
fig.savefig(Path.cwd() / Path(__file__).with_suffix('.png').name, dpi=120, facecolor='white', transparent=False)