codexmagnetostriction-strain-reversal-and-frequency-model.py
magnetostriction-strain-reversal-and-frequency-model.py
"""Original stipulated models, not fitted or measured material data.
Python 3.14, matplotlib 3.10.7, numpy 2.3.5. Run from _media; write to CWD.
The saturated angular model and the unsaturated field law are separate models.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
plt.rcParams.update({"font.size": 11, "font.family": "DejaVu Sans"})
fig, axes = plt.subplots(2, 2, figsize=(11, 8), dpi=120, facecolor="white")
fig.subplots_adjust(left=0.085, right=0.975, top=0.86, bottom=0.105,
wspace=0.29, hspace=0.58)
blue, orange, ink = "#176b99", "#b85c18", "#263746"
fig.suptitle("Magnetostriction: direction, reversal, and doubled frequency",
y=0.965, fontsize=17, weight="bold", color=ink)
fig.text(0.5, 0.912, "Stipulated models • no measured data • 1 microstrain = 1 ppm",
ha="center", fontsize=11, color=ink)
lam_s = 20e-6
kappa = 5e-14 # m²/A²
h0 = 20e3 # A/m
freq = 50.0 # Hz
ax = axes[0, 0]
angle = np.linspace(0, 180, 361)
axial = 0.5 * lam_s * (3 * np.cos(np.deg2rad(angle)) ** 2 - 1)
ax.plot(angle, axial * 1e6, color=blue, lw=2.3)
ax.scatter([0, 90, 180], [20, -10, 20], color=blue, zorder=3)
ax.annotate("Reversal: same strain", xy=(177, 20), xytext=(56, 24),
fontsize=10, arrowprops=dict(arrowstyle="->", color=ink))
ax.set(xlim=(-5, 185), ylim=(-14, 31), xticks=[0, 45, 90, 135, 180],
xlabel=r"Angle $\theta$ to measuring axis (degrees)",
ylabel="Axial strain (microstrain)")
ax.set_title(r"A Saturated direction model: $\lambda_s=20$ ppm", loc="left", fontsize=11)
ax = axes[0, 1]
field = np.linspace(-h0, h0, 401)
ax.plot(field / 1e3, kappa * field ** 2 * 1e6, color=orange, lw=2.3)
ax.set(xlabel=r"Axial field intensity $H$ (kA/m)", ylabel="Axial strain (microstrain)",
xlim=(-21, 21), ylim=(-1, 23), xticks=[-20, -10, 0, 10, 20])
ax.set_title(r"B Unsaturated toy law: $q=\kappa H^2$", loc="left", fontsize=11)
ax.text(0, 17.8, r"$\kappa=5\times10^{-14}\ \mathrm{m^2/A^2}$",
ha="center", color=ink, fontsize=10)
ax.text(0, 14.1, r"$q(-H)=q(H)$", ha="center", color=ink)
ax = axes[1, 0]
time = np.linspace(0, 0.04, 801)
q = kappa * (h0 * np.sin(2 * np.pi * freq * time)) ** 2
ax.plot(time * 1e3, q * 1e6, color=orange, lw=2.3, label="Axial")
ax.plot(time * 1e3, -q * 0.5e6, color=blue, lw=2.0, label="Each transverse")
ax.axhline(10, color=orange, ls="--", lw=1.1)
ax.set(xlim=(0, 40), ylim=(-13, 25), xlabel="Time (ms)",
ylabel="Strain (microstrain)", xticks=[0, 10, 20, 30, 40])
ax.set_title(r"C $H(t)=20\sin(2\pi\,50t)$ kA/m; strain at 100 Hz", loc="left", fontsize=11)
ax.legend(loc="upper center", ncol=2, frameon=False, fontsize=9)
ax.text(24, 10.8, "Axial mean = 10 ppm", fontsize=9, color=orange)
ax = axes[1, 1]
x = np.array([0, 50, 100])
y = np.array([10, 0, -10])
ax.vlines(x, 0, y, color=orange, lw=2.8)
ax.scatter(x, y, color=orange, zorder=3, s=40)
ax.set(xlim=(-15, 125), ylim=(-14, 16), xticks=x,
xlabel="Frequency (Hz)", ylabel="Cosine coefficient (microstrain)")
ax.set_title("D Signed axial coefficients; DC = mean", loc="left", fontsize=11)
for xx, yy, lab in zip(x, y, ["+10", "0", "−10"]):
ax.annotate(lab, (xx, yy), (7, 5), textcoords="offset points", fontsize=10)
ax.text(70, 10.8, "No 50 Hz term\nwithout bias", ha="center", fontsize=10, color=ink)
for ax in axes.flat:
ax.axhline(0, color="#8c99a3", lw=0.8, zorder=0)
ax.grid(alpha=0.18)
ax.spines[["top", "right"]].set_visible(False)
ax.set_facecolor("white")
fig.text(0.5, 0.025, "A changes orientation at fixed saturation; B–D prescribe a separate quadratic field response.",
ha="center", fontsize=10, color=ink)
fig.savefig(Path.cwd() / f"{Path(__file__).stem}.png", facecolor="white", dpi=120)
plt.close(fig)
if __name__ == "__main__":
main()