Reproducing Figure 1 from Ferri et al. (2022)

A script computing the full Balmer-series (Hα–Hε) Stark-Zeeman spectrum for H, in the style of Figure 1 of Ferri, Peyrusse & Calisti (2022), at B = 100, 500, 1000 T, comparing the profile with and without the quadratic Zeeman term in the Hamiltonian. All five lines are summed on one common wavelength grid (Case B recombination weighting) so the individual line wings overlap into a single continuous spectrum.

Note

This script is a qualitative illustration, not a quantitative reproduction of Ferri et al. Fig. 1. Ferri et al. build a configuration-interaction Hamiltonian mixing states of different principal quantum numbers, and note this inter-\(n\) mixing is crucial for the quadratic Zeeman term at these field strengths. StarkZee currently diagonalizes the quadratic Zeeman (and Stark) Hamiltonian within a single principal shell only (see Approximations and Numerical Choices); each Balmer line here is computed independently and the results are overlaid. In particular, the inter-\(n\)-driven red shift of the high-PQN lines (Hβ, Hδ) that Ferri Fig. 1(c) shows at B = 1 kT is not reproduced — StarkZee’s intra-shell treatment shifts these lines blue instead. See TODO item 1 for the underlying analysis.

Full Balmer series (reproduce_fig1.py)

  1#!/usr/bin/env python3
  2"""
  3reproduce_fig1.py
  4==================
  5Full Balmer-series (H-alpha ... H-epsilon) Stark-Zeeman spectrum for H, in
  6the style of Ferri, Peyrusse & Calisti (2022) Fig. 1.
  7
  8All five lines are computed on one common wavelength grid spanning the whole
  9Balmer range, so their Stark-broadened wings overlap into a single continuous
 10spectrum rather than five disconnected local windows.  Each line is weighted
 11by its Case B recombination intensity relative to H-beta, divided by its
 12oscillator strength, so the integrated flux of each transition reproduces
 13the Case B Balmer decrement.
 14
 15Conditions (Ferri, Peyrusse & Calisti 2022, Fig. 1):
 16  Z = 1, Ne = 1e23 m^-3, Te = 5 eV, transverse observation.
 17
 18Three B values (100, 500, 1000 T) are shown, each with and without the
 19quadratic Zeeman term in the Hamiltonian.
 20"""
 21
 22import os, sys
 23import numpy as np
 24import matplotlib.pyplot as plt
 25sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
 26
 27from starkzee.utils import wavelength_nm_to_energy_ev
 28from starkzee.static_profile import calculate_static_profile
 29from starkzee.radiator import line_strength
 30
 31
 32# ---------------------------------------------------------------------------
 33# Balmer line definitions
 34# ---------------------------------------------------------------------------
 35BALMER_LINES = [
 36    (3, "Hα", r"H$\alpha$",   6563),
 37    (4, "Hβ", r"H$\beta$",    4861),
 38    (5, "Hγ", r"H$\gamma$",   4340),
 39    (6, "Hδ", r"H$\delta$",   4102),
 40    (7, "Hε", r"H$\epsilon$", 3970),
 41]
 42
 43# Standard Case B recombination relative intensities (relative to H-beta = 1)
 44# (Osterbrock & Ferland 2006, T ~ 10,000 K, ne -> 0, optically thick Lyman series).
 45_CASEB = {3: 2.86, 4: 1.00, 5: 0.47, 6: 0.26, 7: 0.16}
 46
 47
 48def balmer_spectrum(B, Ne, Te, Ti, wavelengths_nm, quadratic_zeeman):
 49    """Case-B-weighted transverse intensity summed over Halpha-Hepsilon on a
 50    single common wavelength grid, so the individual line wings overlap into
 51    one continuous spectrum instead of disconnected local windows."""
 52    energies = wavelength_nm_to_energy_ev(wavelengths_nm)
 53    total = np.zeros_like(energies)
 54    for n_u, name_ascii, _, _ in BALMER_LINES:
 55        print(f"    {name_ascii} (n={n_u}->2) ...", end=" ", flush=True)
 56        S_n = line_strength(n_u, n_l=2, Z=1)
 57        weight = _CASEB[n_u] / S_n   # integral of weight * profile dE -> _CASEB[n_u]
 58        pi, sp, sm = calculate_static_profile(
 59            n_u=n_u, n_l=2, Z=1, B=B, Ne_m3=Ne, Te_ev=Te,
 60            energies_ev=energies,
 61            num_f=20, num_mu=6,
 62            use_screening=True,
 63            quadratic_zeeman=quadratic_zeeman,
 64            frequency_dependent_width=False,
 65            Ti_ev=Ti,
 66        )
 67        total += weight * (pi + 0.5 * (sp + sm))
 68        print("done.")
 69    return total
 70
 71
 72# ---------------------------------------------------------------------------
 73if __name__ == "__main__":
 74    Ne, Te = 1e23, 5.0
 75    Ti = Te  # ion temperature not separately specified; assume Ti = Te
 76    B_list = [100.0, 500.0, 1000.0]
 77
 78    wavelengths_nm  = np.linspace(380.0, 720.0, 3000)
 79    wavelengths_ang = wavelengths_nm * 10.0
 80
 81    print("reproduce_fig1: full Balmer-series Stark-Zeeman spectrum")
 82    print(f"  Ne = {Ne:.1e} m-3, Te = {Te} eV, Z = 1")
 83    print()
 84
 85    spectra = {}
 86    for B in B_list:
 87        print(f"B = {int(B)} T:")
 88        print("  Without quadratic Zeeman:")
 89        nq = balmer_spectrum(B, Ne, Te, Ti, wavelengths_nm, quadratic_zeeman=False)
 90        print("  With    quadratic Zeeman:")
 91        yq = balmer_spectrum(B, Ne, Te, Ti, wavelengths_nm, quadratic_zeeman=True)
 92        spectra[B] = (nq, yq)
 93        print()
 94
 95    print("All done. Plotting ...")
 96
 97    # ── Figure layout: 3 rows (one per B), full Balmer range per panel ───────
 98    fig, axes = plt.subplots(3, 1, figsize=(11, 10), sharex=True)
 99    fig.suptitle(
100        rf"Full Balmer series (H$\alpha$-H$\epsilon$)  |  H, "
101        rf"$N_e={Ne:.0e}$ m$^{{-3}}$, $T_e={Te:.0f}$ eV"
102        "\n"
103        r"Solid = with quadratic Zeeman, Dashed = without",
104        fontsize=13)
105
106    colors = ["tab:blue", "tab:orange", "tab:red"]
107    B_labels = [f"B = {int(B)} T" for B in B_list]
108
109    for ax, B, label, color in zip(axes, B_list, B_labels, colors):
110        nq, yq = spectra[B]
111        peak = max(yq.max(), nq.max(), 1.0)
112
113        ax.plot(wavelengths_ang, nq / peak, "--", color=color,
114                lw=1.2, alpha=0.75, label=f"{label}  (no QZ)")
115        ax.plot(wavelengths_ang, yq / peak, "-",  color=color,
116                lw=1.6, label=f"{label}  (with QZ)")
117
118        for _, _, _, wl in BALMER_LINES:
119            ax.axvline(wl, color="gray", lw=0.6, ls=":", alpha=0.5)
120
121        ax.set_yscale("log")
122        ax.set_ylim(1e-5, 3.0)
123        ax.set_ylabel("Norm. intensity", fontsize=10)
124        ax.legend(loc=4, fontsize=9)
125        ax.grid(True, which="both", alpha=0.3)
126
127    for _, _, name_tex, wl in BALMER_LINES:
128        axes[0].text(wl, 1.0, name_tex, ha="center", va="bottom", fontsize=9,
129                     bbox=dict(facecolor="white", alpha=0.7, pad=1, edgecolor="none"))
130
131    axes[-1].set_xlabel(r"Wavelength ($\AA$)", fontsize=12)
132    axes[-1].set_xlim(wavelengths_ang.min(), wavelengths_ang.max())
133
134    plt.tight_layout()
135    out = os.path.join(os.path.dirname(__file__), "reproduce_fig1.png")
136    plt.savefig(out, dpi=200, bbox_inches="tight")
137    print(f"Saved plot to {out}")
138    plt.show()
Full Balmer series Stark-Zeeman spectrum at three B values, with and without quadratic Zeeman

Hα–Hε summed on a common grid (Case B weighting) at B = 100, 500, 1000 T, solid = with quadratic Zeeman, dashed = without.