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()
Hα–Hε summed on a common grid (Case B weighting) at B = 100, 500, 1000 T, solid = with quadratic Zeeman, dashed = without.