codex/barkhausen-pickup-coil-experiment.py

barkhausen-pickup-coil-experiment.py
"""Original functional Barkhausen schematic; Python 3.14, matplotlib 3.10.7.
Run from the desired output directory; emits only this script's basename.png.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch, Rectangle, Ellipse

BLUE = '#245d8c'
ORANGE = '#a45d17'


def main():
    fig, ax = plt.subplots(figsize=(12, 7.6), dpi=110)
    fig.patch.set_facecolor('white')
    ax.set(xlim=(0, 12), ylim=(0, 7.6))
    ax.axis('off')
    ax.text(.3, 7.3, 'Barkhausen measurement: drive, specimen, readout', fontsize=19, weight='bold')
    ax.text(.3, 6.94, 'Functional schematic • control additions are orange and dashed', fontsize=12, color='#444444')

    def box(x, y, w, h, text, color=BLUE, dashed=False):
        p = FancyBboxPatch((x, y), w, h, boxstyle='round,pad=0.07',
                           facecolor='white', edgecolor=color, linewidth=1.6,
                           linestyle='--' if dashed else '-')
        ax.add_patch(p)
        ax.text(x+w/2, y+h/2, text, ha='center', va='center', fontsize=12, color=color)

    def arrow(points, color=BLUE, dashed=False):
        xs, ys = zip(*points)
        ax.plot(xs[:-1], ys[:-1], color=color, lw=1.6, ls='--' if dashed else '-')
        ax.annotate('', xy=points[-1], xytext=points[-2],
                    arrowprops=dict(arrowstyle='->', lw=1.6, color=color,
                                    linestyle='--' if dashed else '-'))

    box(.4, 5.35, 1.75, 1.02, 'Triangle\ngenerator')
    box(2.8, 5.35, 1.7, 1.02, 'Drive\namplifier')
    arrow([(2.22, 5.87), (2.73, 5.87)])
    arrow([(4.57, 5.87), (5.25, 5.87)])
    ax.plot([.7, .98, 1.26, 1.54, 1.82], [5.03, 5.25, 5.03, 5.25, 5.03], color=BLUE, lw=1.6)
    ax.text(.5, 4.65, 'Slow field sweep;\nmonitor drive current', fontsize=11, color=BLUE)

    ax.add_patch(Rectangle((5.3, 4.9), 6.2, 1.65, facecolor='#f1f6fa', edgecolor=BLUE, lw=1.5))
    for x in [5.5, 5.85, 6.2, 6.55, 6.9, 7.25, 7.6, 7.95, 8.3, 8.65, 9, 9.35, 9.7, 10.05, 10.4, 10.75, 11.1, 11.35]:
        ax.plot([x, x], [6.42, 6.55], color=BLUE, lw=1)
        ax.plot([x, x], [4.9, 5.03], color=BLUE, lw=1)
    ax.text(8.4, 6.68, 'Long drive solenoid', ha='center', fontsize=12, color=BLUE)
    ax.add_patch(Rectangle((5.8, 5.52), 3.65, .36, facecolor='#858f98', edgecolor='#40484f'))
    for x in [7.25, 7.34, 7.43, 7.52, 7.61]:
        ax.add_patch(Ellipse((x, 5.7), .15, .86, facecolor='none', edgecolor='#19364d', lw=1.4))
    ax.text(6.4, 5.14, 'Stationary ferromagnet', fontsize=10.5, ha='center')
    ax.text(7.46, 6.24, 'Pickup coil', fontsize=10.5, ha='center')
    for x in [10.17, 10.26, 10.35, 10.44, 10.53]:
        ax.add_patch(Ellipse((x, 5.7), .15, .86, facecolor='none', edgecolor=ORANGE, lw=1.3, linestyle='--'))
    ax.text(10.35, 5.12, 'Empty coil', color=ORANGE, fontsize=10.5, ha='center')
    arrow([(9.3, 6.18), (10.9, 6.18)])
    ax.text(10.1, 6.31, r'$H_{\rm applied}(t)$', fontsize=12, ha='center', color=BLUE)
    ax.text(8.4, 4.62, 'Specimen and coils fixed in position', ha='center', fontsize=11)

    arrow([(7.43, 5.25), (7.43, 4.22), (1.0, 4.22), (1.0, 3.05)])
    ax.text(4.0, 4.3, 'Material + direct drive pickup', ha='center', fontsize=11, color=BLUE)
    arrow([(10.35, 5.25), (10.35, 3.72), (1.95, 3.72), (1.95, 3.05)], ORANGE, True)
    ax.text(6.2, 3.8, 'Matched empty coil: direct drive pickup', ha='center', fontsize=11, color=ORANGE)

    box(.5, 1.95, 2.0, 1.0, 'Difference\n(optional control)', ORANGE, True)
    ax.text(.92, 3.12, '+', fontsize=15, color=BLUE)
    ax.text(1.87, 3.12, '−', fontsize=15, color=ORANGE)
    box(3.2, 1.95, 1.7, 1.0, 'Low-noise\namplifier')
    box(5.6, 1.95, 1.7, 1.0, 'Low-pass\nfilter')
    box(8.05, 1.95, 2.4, 1.0, 'Digitizer\nvoltage vs time')
    for a, b in [(2.57, 3.13), (4.97, 5.53), (7.37, 7.98)]:
        arrow([(a, 2.45), (b, 2.45)])
    ax.text(.45, 1.2, r'Pickup: $e=-d\lambda/dt$     →     gain and filtering     →     recorded $v_{\rm out}(t)$', fontsize=14)
    ax.text(.45, .68, 'Checks: drive off • specimen removed • repeat both ramp directions • vary sweep rate', fontsize=12)
    ax.text(.45, .25, 'No measured trace shown. Coil geometry is schematic; the empty coil links no specimen.', fontsize=11, color='#444444')
    fig.subplots_adjust(left=.02, right=.99, bottom=.02, top=.99)
    fig.savefig(Path(__file__).stem+'.png', facecolor='white')
    plt.close(fig)


if __name__ == '__main__':
    main()