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}\).