Quick start¶
The recommended entry point is the LineProfile
class. It wraps all solver calls, stores results as attributes, and provides
convenience properties for common observation geometries.
Basic profile¶
import numpy as np
import matplotlib.pyplot as plt
from starkzee.line_profile import LineProfile
# Hα (n=3→2), DIII-D edge conditions
lp = LineProfile(n_u=3, n_l=2, Z=1, B=5.0, Ne_m3=1e20, Te_ev=5.0)
# Build an energy grid ±3 nm around the line center
lam0 = lp.E0_wavelength_nm # ≈ 656.1 nm
HC = 1239.84193 # hc/e [eV·nm]
energies = np.linspace(HC/(lam0+3), HC/(lam0-3), 1000)
lp.compute_profile(energies, num_f=30, num_mu=8,
use_screening=True, quadratic_zeeman=False)
# Observation at 90° to B (transverse): π + ½(σ+ + σ-)
plt.plot(lp.wavelengths_nm - lam0, lp.profile_transverse, label="90°")
# Along B (0°): σ+ + σ-
plt.plot(lp.wavelengths_nm - lam0, lp.profile_parallel,
ls="--", label="0°")
plt.xlabel("Δλ (nm)")
plt.legend()
plt.show()
The profile is stored as three polarization components
(profile_pi, profile_sig_plus, profile_sig_minus).
Use profile_at_angle() for
arbitrary observation angle θ.
Discrete transition stick spectrum¶
lp.compute_discrete(Fz=0.0, Fx=0.0)
disc = lp.discrete
print(disc) # DiscreteTransitions(89 transitions, ...)
# Sorted by energy; q = 0 (π), +1 (σ+), -1 (σ-)
for E, q, S in zip(disc.energy_ev, disc.q, disc.strength):
print(f" E={E:.4f} eV q={q:+d} |d|²={S:.4e} a₀²")
Both compute_profile and compute_discrete return self, so they can
be chained:
lp.compute_profile(energies).compute_discrete()
Balmer series at once¶
from starkzee.line_profile import LineProfile
import numpy as np
lines = [(3,2,"Hα"), (4,2,"Hβ"), (5,2,"Hγ"), (6,2,"Hδ")]
B, Ne, Te = 5.0, 1e20, 5.0
profiles = {}
for n_u, n_l, label in lines:
lp = LineProfile(n_u=n_u, n_l=n_l, Z=1, B=B, Ne_m3=Ne, Te_ev=Te)
HC = 1239.84193
lam0 = lp.E0_wavelength_nm
energies = np.linspace(HC/(lam0+2), HC/(lam0-2), 800)
lp.compute_profile(energies, num_f=30, num_mu=8)
profiles[label] = lp
Lower-level API¶
The class delegates to calculate_static_profile()
and discrete_transitions(). These can be
called directly for more control:
from starkzee.static_profile import calculate_static_profile
import numpy as np
from starkzee.utils import RYDBERG_EV
E0 = RYDBERG_EV * (1/4 - 1/9) # Hα
energies = E0 + np.linspace(-0.005, 0.005, 1000)
pi, sig_plus, sig_minus = calculate_static_profile(
n_u=3, n_l=2, Z=1, B=5.0,
Ne_m3=1e20, Te_ev=5.0,
energies_ev=energies,
num_f=30, num_mu=8,
)
FFM (dynamic ion) profile¶
Use calculate_ffm_profile() when ion dynamics matter
(high density, low B, or high-n transitions):
from starkzee.ffm import calculate_ffm_profile
pi, sp, sm = calculate_ffm_profile(
n_u=3, n_l=2, Z=1, B=5.0,
Ne_m3=1e23, Te_ev=5.0, Ti_ev=0.1,
A_ion=1, energies_ev=energies,
num_f=30, num_mu=8,
)
Post-processing: Doppler and instrumental broadening¶
Doppler and instrumental broadening are not applied automatically.
Apply them as a post-processing step using apply_doppler_broadening()
and apply_instrument_broadening().
Both require a uniform wavelength grid:
from starkzee.convolutions import (
apply_doppler_broadening,
apply_instrument_broadening,
)
lp.compute_profile(energies, num_f=30, num_mu=8)
total = lp.profile_transverse
# Convert energy grid → uniform wavelength grid
lam = lp.wavelengths_nm
# Doppler: Ti=0.5 eV, hydrogen emitter (A=1)
broadened = apply_doppler_broadening(lam, total, Ti_ev=0.5,
A_emitter=1, E0_ev=lp.E0)
# Instrument: FWHM = 0.05 nm
broadened = apply_instrument_broadening(lam, broadened, fwhm_nm=0.05)
The complete pipeline is \(I_\text{obs} = (I_\text{SZ} * G_D) * G_\text{inst}\).