codex/pulsar-discovery-array-timing.py

pulsar-discovery-array-timing.py
"""Original pulsar discovery functional/timing illustration; Python 3.14, NumPy, Matplotlib.

Run from _media: python ../pulsar-discovery-array-timing.py
Synthetic signal only; apparatus values from Hewish et al., Nature 217, 709 (1968).
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch
import numpy as np


def main():
    plt.rcParams.update({'font.size': 11, 'axes.spines.top': False, 'axes.spines.right': False})
    fig = plt.figure(figsize=(10, 7.2), facecolor='white', layout='constrained')
    gs = fig.add_gridspec(3, 2, height_ratios=[1.25, 1, 1.1])
    flow = fig.add_subplot(gs[0, :])
    flow.set(xlim=(0, 10), ylim=(0, 3.3))
    flow.axis('off')
    boxes = [(0.1, 'Dipole array\n2,048 elements\n16 rows', '#e7f0f9'),
             (3.5, 'Phasing + receivers\n81.5 MHz centre\n1 MHz bandwidth', '#e9f4ec'),
             (6.9, 'Pen recorder\nSignal versus time\nCompare with second pips', '#fff1dc')]
    for x, label, color in boxes:
        flow.add_patch(FancyBboxPatch((x, 1.1), 2.9, 1.45, boxstyle='round,pad=0.08', fc=color, ec='#415367'))
        flow.text(x + 1.45, 1.83, label, ha='center', va='center', fontsize=11)
    for x in [3.03, 6.43]:
        flow.annotate('', xy=(x + .35, 1.8), xytext=(x, 1.8), arrowprops={'arrowstyle': '->', 'lw': 1.8})
    flow.text(5, .6, 'Four declination channels; two array halves used as an east–west interferometer', ha='center', fontsize=10.5)
    flow.text(5, .16, 'Functional diagram only: layout and traces are not historical reproductions', ha='center', color='#555555', fontsize=10)
    t = np.linspace(0, 7, 5000)
    pulse = sum(np.exp(-.5 * ((t - p) / .055) ** 2) for p in np.arange(.6, 7, 1.4))
    ax = fig.add_subplot(gs[1, 0])
    # Compress the same pulse train to show why record speed matters, without simulating a specific instrument.
    ax.plot(t, pulse, color='#26718f', lw=1)
    ax.set(xlim=(0, 7), ylim=(-.06, 1.15), xlabel='Time (s)', ylabel='Chosen signal', title='Same signal, compressed time axis')
    ax.set_box_aspect(.23)
    ax = fig.add_subplot(gs[1, 1])
    ax.plot(t, pulse, color='#26718f', lw=1.7)
    ax.annotate('', xy=(2, 1.03), xytext=(.6, 1.03), arrowprops={'arrowstyle': '<->', 'color': '#a04d10'})
    ax.text(1.3, 1.1, 'Chosen P = 1.4 s', ha='center', color='#a04d10', fontsize=10)
    ax.set(xlim=(0, 4.8), ylim=(-.06, 1.32), xlabel='Time (s)', title='Expanded time axis resolves pulses')
    ax = fig.add_subplot(gs[2, :])
    days = np.arange(11)
    ax.plot(days, np.zeros_like(days), 'o--', color='#a04d10', label='24 hour schedule')
    ax.plot(days, -3.93 * days, 'o-', color='#26718f', label='Fixed sky direction: sidereal recurrence')
    ax.set(xlim=(-.2, 10.2), ylim=(-47, 8), xlabel='Number of sidereal recurrences', ylabel='Shift from 24 h\nschedule (min)', title='Daily sky transit is a different clock from pulse spacing')
    ax.legend(loc='lower left', fontsize=9, frameon=False)
    ax.annotate('39.3 min earlier', xy=(10, -39.3), xytext=(7.7, -18), arrowprops={'arrowstyle': '->', 'color': '#415367'}, ha='center', fontsize=10)
    fig.suptitle('Two clocks in the pulsar discovery', fontsize=16)
    fig.savefig(Path(__file__).stem + '.png', dpi=110, facecolor='white')
    plt.close(fig)


if __name__ == '__main__':
    main()