Transition anatomy

Script: examples/example_transitions.py

Explores the discrete Stark-Zeeman transitions of H Ly-α (n=2→1) and related lines: prints a transition table with energies, polarizations, and dipole strengths at B = 5 T, prints oscillator strengths and Einstein A coefficients for a set of H lines, and builds a six-panel figure spanning:

  1. Ly-α stick spectrum at B = 0 (fine-structure splitting only)

  2. Ly-α stick spectrum at B = 5 T (Zeeman comparable to fine structure)

  3. Full static profile with the B = 5 T stick spectrum overlaid

  4. How a Stark field splits an otherwise degenerate B = 2 T manifold

  5. Hβ (n=4→2) stick spectrum at B = 5 T — many Stark-Zeeman components

  6. Ly-α at B = 5000 T — the Zeeman-dominated regime (\(\mu_B B \gg \xi\)), completing the story set up by panels 1 and 2

Code

  1"""
  2example_transitions.py — Discrete Stark-Zeeman transitions and full profiles.
  3
  4Demonstrates:
  5  1. Listing individual transitions and their line strengths
  6  2. Stick spectra at B=0 and B=5T side by side (pure Zeeman)
  7  3. Full static profile with stick spectrum overlay
  8  4. Oscillator strengths and Einstein A coefficients for common lines
  9  5. Stark + Zeeman: how a microfield splits an otherwise degenerate manifold
 10
 11Run:
 12    python example_transitions.py
 13
 14Produces:  example_transitions.png
 15"""
 16
 17import numpy as np
 18import matplotlib.pyplot as plt
 19import matplotlib.gridspec as gridspec
 20import sys, os
 21sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
 22
 23from starkzee.static_profile import discrete_transitions, calculate_static_profile
 24from starkzee.radiator import line_strength, oscillator_strength, einstein_a
 25from starkzee.utils import reduced_mass_rydberg_ev, energy_ev_to_wavelength_nm
 26
 27# ─────────────────────────────────────────────────────────────────────────────
 28# Helpers
 29# ─────────────────────────────────────────────────────────────────────────────
 30
 31POL_COLOR = {0: "#e74c3c", -1: "#3498db", 1: "#2ecc71"}  # π, σ+, σ−
 32POL_LABEL = {0: "π (q=0)", -1: "σ+ (q=−1)", 1: "σ− (q=+1)"}
 33
 34
 35def bohr_energy(n_u, n_l, Z, A=1):
 36    return (Z**2) * reduced_mass_rydberg_ev(Z, A) * (1.0/n_l**2 - 1.0/n_u**2)
 37
 38
 39def plot_stick_spectrum(ax, tr, E0, normalize=True, title="", show_legend=True):
 40    """Plot discrete transitions as vertical lines colored by polarization."""
 41    det = (tr['energy_ev'] - E0) * 1e3  # detuning in meV
 42    s = tr['strength']
 43    if normalize and s.max() > 0:
 44        s = s / s.max()
 45    for q in [0, -1, 1]:
 46        mask = tr['q'] == q
 47        if mask.any():
 48            ax.vlines(det[mask], 0, s[mask],
 49                      colors=POL_COLOR[q], label=POL_LABEL[q],
 50                      linewidth=1.8, alpha=0.85)
 51    ax.set_xlabel("Detuning from E₀ (meV)", fontsize=10)
 52    ax.set_ylabel("Norm. strength" if normalize else "|d|² (a₀²)", fontsize=10)
 53    ax.set_title(title, fontsize=11)
 54    if show_legend:
 55        ax.legend(fontsize=8, framealpha=0.7)
 56    ax.axhline(0, color="grey", linewidth=0.5)
 57    ax.set_xlim(-5, 5)
 58
 59
 60# ─────────────────────────────────────────────────────────────────────────────
 61# 1. Print transition table for H Ly-α at B=5T
 62# ─────────────────────────────────────────────────────────────────────────────
 63
 64print("=" * 65)
 65print("H Ly-α (n=2→1, Z=1) discrete transitions at B=5 T")
 66print("=" * 65)
 67Z, n_u, n_l, B = 1, 2, 1, 5.0
 68E0 = bohr_energy(n_u, n_l, Z)
 69
 70tr = discrete_transitions(n_u=n_u, n_l=n_l, Z=Z, B=B,
 71                           fine_structure=True, min_strength=1e-6)
 72
 73print(f"{'#':>3}  {'E (eV)':>12}  {'ΔE (meV)':>10}  {'q':>4}  "
 74      f"{'|d|² (a₀²)':>12}  {'upper':>6}  {'lower':>6}")
 75print("-" * 65)
 76for k in range(len(tr['energy_ev'])):
 77    det_mev = (tr['energy_ev'][k] - E0) * 1e3
 78    pol = {0: "π", -1: "σ+", 1: "σ−"}[tr['q'][k]]
 79    print(f"{k+1:>3}  {tr['energy_ev'][k]:>12.6f}  {det_mev:>10.4f}  "
 80          f"{pol:>4}  {tr['strength'][k]:>12.6f}  "
 81          f"{tr['upper_idx'][k]:>6}  {tr['lower_idx'][k]:>6}")
 82
 83S_ul = line_strength(n_u, n_l, Z)
 84print(f"\nSum of strengths : {tr['strength'].sum():.6f} a₀²")
 85print(f"line_strength()  : {S_ul:.6f} a₀²  (must match)")
 86
 87
 88# ─────────────────────────────────────────────────────────────────────────────
 89# 2. Atomic data table
 90# ─────────────────────────────────────────────────────────────────────────────
 91
 92print("\n" + "=" * 65)
 93print("Oscillator strengths and Einstein A coefficients")
 94print("=" * 65)
 95LINES = [
 96    ("H  Ly-α", 2, 1, 1),
 97    ("H  Ly-β", 3, 1, 1),
 98    ("H  Ly-γ", 4, 1, 1),
 99    ("H  Hα  ", 3, 2, 1),
100    ("H  Hβ  ", 4, 2, 1),
101    ("H  Hγ  ", 5, 2, 1),
102]
103print(f"{'Line':>12}  {'gf':>8}  {'S_ul (a₀²)':>12}  {'A_ul (s⁻¹)':>14}")
104print("-" * 52)
105for name, nu, nl, Zl in LINES:
106    gf  = oscillator_strength(nu, nl, Zl)
107    S   = line_strength(nu, nl, Zl)
108    A   = einstein_a(nu, nl, Zl)
109    print(f"{name:>12}  {gf:>8.4f}  {S:>12.4f}  {A:>14.4e}")
110
111
112# ─────────────────────────────────────────────────────────────────────────────
113# 3. Build figure
114# ─────────────────────────────────────────────────────────────────────────────
115
116fig = plt.figure(figsize=(10, 9))
117gs = gridspec.GridSpec(2, 3, figure=fig, hspace=0.45, wspace=0.38)
118
119ax_b0   = fig.add_subplot(gs[0, 0])
120ax_b5   = fig.add_subplot(gs[0, 1])
121ax_full = fig.add_subplot(gs[0, 2])
122ax_stark = fig.add_subplot(gs[1, 0])
123ax_hb   = fig.add_subplot(gs[1, 1])
124ax_hib  = fig.add_subplot(gs[1, 2])
125
126# ── Panel 1: H Ly-α stick at B=0 ────────────────────────────────────────────
127tr_b0 = discrete_transitions(n_u=2, n_l=1, Z=1, B=0.0,
128                               fine_structure=True, min_strength=1e-6)
129plot_stick_spectrum(ax_b0, tr_b0, E0,
130                    title="H Ly-α  B=0 T\n(SO splits 2p₁/₂ from 2p₃/₂)")
131
132# ── Panel 2: H Ly-α stick at B=5T ───────────────────────────────────────────
133tr_b5 = discrete_transitions(n_u=2, n_l=1, Z=1, B=5.0,
134                               fine_structure=True, min_strength=1e-6)
135plot_stick_spectrum(ax_b5, tr_b5, E0,
136                    title="H Ly-α  B=5 T\n(Zeeman + SO)")
137
138# ── Panel 3: Full static profile overlaid with sticks ───────────────────────
139Ne, Te = 1e23, 5.0
140energies = E0 + np.linspace(-0.005, 0.005, 1000)
141pi, sp, sm = calculate_static_profile(
142    n_u=2, n_l=1, Z=1, B=5.0, Ne_m3=Ne, Te_ev=Te,
143    energies_ev=energies, num_f=25, num_mu=8,
144    fine_structure=True, frequency_dependent_width=False
145)
146det_ev  = (energies - E0) * 1e3
147total   = pi + sp + sm
148total_n = total / total.max()
149
150ax_full.plot(det_ev, total_n, color="k", linewidth=1.5, label="Total profile")
151ax_full.plot(det_ev, pi / total.max(), color=POL_COLOR[0],
152             linewidth=1.0, linestyle="--", alpha=0.7, label="π")
153ax_full.plot(det_ev, (sp + sm) / total.max(), color=POL_COLOR[-1],
154             linewidth=1.0, linestyle=":", alpha=0.7, label="σ+σ−")
155
156# Overlay sticks (normalized to profile peak)
157s5 = tr_b5['strength']
158s5_n = s5 / s5.max() * 0.5
159det5 = (tr_b5['energy_ev'] - E0) * 1e3
160for q in [0, -1, 1]:
161    mask = tr_b5['q'] == q
162    ax_full.vlines(det5[mask], 0, s5_n[mask],
163                   colors=POL_COLOR[q], alpha=0.4, linewidth=1.2)
164
165ax_full.set_xlabel("Detuning (meV)", fontsize=10)
166ax_full.set_ylabel("Norm. intensity", fontsize=10)
167ax_full.set_title(f"H Ly-α  B=5 T  Ne={Ne:.0e} m⁻³\nProfile + stick spectrum",
168                  fontsize=11)
169ax_full.set_xlim(-5, 5)
170ax_full.legend(fontsize=8, framealpha=0.7)
171
172# ── Panel 4: Stark + Zeeman — how a microfield splits lines ─────────────────
173B_panel = 2.0
174E0_lya = bohr_energy(2, 1, 1)
175for Fz_vm, alpha in [(0.0, 1.0), (5e7, 0.65), (2e8, 0.35)]:
176    tr_f = discrete_transitions(n_u=2, n_l=1, Z=1, B=B_panel,
177                                 Fz=Fz_vm, fine_structure=False,
178                                 min_strength=1e-6)
179    det = (tr_f['energy_ev'] - E0_lya) * 1e3
180    s   = tr_f['strength'] / tr_f['strength'].max()
181    label = f"F={Fz_vm:.0e} V/m" if Fz_vm > 0 else "F=0"
182    ax_stark.vlines(det, 0, s * alpha, alpha=alpha,
183                    colors=["#7f8c8d", "#e67e22", "#8e44ad"][
184                        [0.0, 5e7, 2e8].index(Fz_vm)],
185                    linewidth=1.5, label=label)
186
187ax_stark.set_xlabel("Detuning (meV)", fontsize=10)
188ax_stark.set_ylabel("Norm. strength", fontsize=10)
189ax_stark.set_title(f"H Ly-α  B={B_panel} T + Stark field\n"
190                   f"(sticks: all polarizations combined)", fontsize=11)
191ax_stark.legend(fontsize=8, framealpha=0.7)
192ax_stark.set_xlim(-8, 8)
193ax_stark.axhline(0, color="grey", linewidth=0.5)
194
195# ── Panel 5: H Hβ stick at B=5T ─────────────────────────────────────────────
196E0_hb = bohr_energy(4, 2, 1)
197tr_hb = discrete_transitions(n_u=4, n_l=2, Z=1, B=5.0,
198                               fine_structure=True, min_strength=1e-5)
199det_hb = (tr_hb['energy_ev'] - E0_hb) * 1e3
200s_hb   = tr_hb['strength'] / tr_hb['strength'].max()
201for q in [0, -1, 1]:
202    mask = tr_hb['q'] == q
203    if mask.any():
204        ax_hb.vlines(det_hb[mask], 0, s_hb[mask],
205                     colors=POL_COLOR[q], linewidth=1.2, alpha=0.85,
206                     label=POL_LABEL[q])
207ax_hb.set_xlabel("Detuning (meV)", fontsize=10)
208ax_hb.set_ylabel("Norm. strength", fontsize=10)
209ax_hb.set_title("H Hβ (n=4→2)  B=5 T\nMany Stark-Zeeman components", fontsize=11)
210ax_hb.legend(fontsize=8, framealpha=0.7)
211ax_hb.set_xlim(-12, 12)
212ax_hb.axhline(0, color="grey", linewidth=0.5)
213
214# ── Panel 6: H Ly-α stick at B=5000T — Zeeman dominates over SO ─────────────
215# Complements Panels 1 (B=0, pure SO) and 2 (B=5T, SO and Zeeman comparable):
216# at very high B the Zeeman splitting (∝ B) overtakes the fixed SO splitting,
217# so the pattern below is dominated by the linear-in-B Zeeman structure.
218B_hib = 5000.0
219tr_hib = discrete_transitions(n_u=2, n_l=1, Z=1, B=B_hib,
220                               fine_structure=True, min_strength=1e-6)
221det_hib = (tr_hib['energy_ev'] - E0) * 1e3
222s_hib   = tr_hib['strength'] / tr_hib['strength'].max()
223for q in [0, -1, 1]:
224    mask = tr_hib['q'] == q
225    if mask.any():
226        ax_hib.vlines(det_hib[mask], 0, s_hib[mask],
227                      colors=POL_COLOR[q], linewidth=1.8, alpha=0.85,
228                      label=POL_LABEL[q])
229ax_hib.set_xlabel("Detuning from E₀ (meV)", fontsize=10)
230ax_hib.set_ylabel("Norm. strength", fontsize=10)
231ax_hib.set_title(f"H Ly-α  B={B_hib:.0f} T\nZeeman dominates (μ_B B ≫ ξ)", fontsize=11)
232ax_hib.legend(fontsize=8, framealpha=0.7)
233ax_hib.axhline(0, color="grey", linewidth=0.5)
234
235fig.suptitle("starkzee — Discrete Stark-Zeeman Transitions", fontsize=14, y=1.01)
236plt.tight_layout()
237out = os.path.join(os.path.dirname(__file__), "example_transitions.png")
238plt.savefig(out, dpi=150, bbox_inches="tight")
239print(f"Saved {out}")
240plt.show()

Result

Six-panel figure of discrete Stark-Zeeman transitions for H Lyman-alpha and Balmer lines

From pure fine-structure splitting (B = 0) through Zeeman-comparable (B = 5 T) to Zeeman-dominated (B = 5000 T) regimes, plus Stark mixing and the denser Hβ manifold.