Two-Level: Two-Pulse Photon Echo
In an inhomogeneously broadened two-level medium a pair of short pulses can generate a coherent burst — the photon echo — at time \(t = +\tau\) after the second pulse, where \(\tau\) is the separation between the two pulses.
Physical mechanism
π/2 pulse (at \(t = -\tau\)): places all atoms in an equal superposition of ground and excited state. Each atom begins precessing at its own transition frequency \(\omega_j\) (set by its Doppler shift).
Free precession (\(-\tau < t < 0\)): atoms accumulate different phases \(\phi_j = \omega_j \tau\). The macroscopic polarisation \(P \propto \sum_j \rho_{10}^{(j)}\) rapidly dephases — the free-induction decay (FID).
π pulse (at \(t = 0\)): inverts every Bloch vector, reversing the sense of accumulated phase: \(\phi_j \to -\phi_j\).
Rephasing (\(0 < t < \tau\)): atoms continue precessing in the same direction. Because the sign of the phase offset was flipped, after a further \(\tau\) all phase differences cancel simultaneously, giving a macroscopic polarisation revival — the photon echo at \(t = +\tau\).
The echo amplitude decays as \(e^{-2\tau/T_2}\) where \(T_2\) is the homogeneous coherence time.
Hard-pulse condition
For the echo to form correctly every velocity class must be rotated by exactly π/2 (or π), regardless of its Doppler detuning. This requires
i.e. the pulse must be short and strong enough that the detuning has negligible effect during the pulse. Here we use \(T = 0.025\,\gamma^{-1}\), giving \(\Omega_{\rm peak}^{(\pi/2)} = 10\,{\rm rad}\,\gamma\) and \(\Omega_{\rm peak}^{(\pi)} = 20\,{\rm rad}\,\gamma\). The Gaussian Doppler distribution has \(\sigma \approx 4.2\,{\rm rad}\,\gamma\) (from thermal_width = 0.15; the solver convention is that the Hamiltonian detuning is \(4\pi^2 \times\) the thermal_delta
user value, so thermal_width = 0.15 maps to \(\sigma \approx 4.2\,{\rm rad}\,\gamma\)). This gives \(\Omega_{\rm peak}^{(\pi/2)}/\sigma \approx 2.4\), satisfying the hard-pulse condition for all significantly-weighted velocity classes.
Level structure
|1⟩ ════════════════ (excited)
│ decay rate 0.1 → T₂ ≈ 3.18 γ⁻¹
Ω │ π/2 and π sech pulses (hard-pulse limit)
│
|0⟩ ════════════════ (ground)
Inhomogeneous broadening: Gaussian Doppler distribution, \(\sigma \approx 4.2\,{\rm rad}\,\gamma\) (wider than the homogeneous linewidth \(\Gamma = 2\pi \times {\rm rate} \approx 0.63\,\gamma\), narrower than the pulse bandwidth \(\sim 1/T = 40\,\gamma\)).
Parameters
Quantity |
Value |
Notes |
|---|---|---|
Decay |
\(0.1\) |
\(\Gamma = 2\pi\times0.1 \approx 0.63\,{\rm rad}\,\gamma\); \(T_2 = 2/\Gamma \approx 3.18\,\gamma^{-1}\) |
Doppler \(\sigma\) |
\(4.2\,{\rm rad}\,\gamma\) |
40 velocity classes over \(\pm 0.75\) (user units) |
Pulse delay |
\(\tau = 1.0\,\gamma^{-1}\) |
echo at \(t = +\tau = +1.0\,\gamma^{-1}\) |
Pulse width |
\(T = 0.025\,\gamma^{-1}\) |
hard-pulse: \(\Omega_{\rm peak}^{(\pi/2)} = 10\), \(\Omega_{\rm peak}^{(\pi)} = 20\,{\rm rad}\,\gamma\) |
Echo amplitude |
\(e^{-2/3.18} \approx 53\%\) |
\(T_2\) decay at \(\tau = 1.0\,\gamma^{-1}\) |
[1]:
import numpy as np
import plotly.graph_objects as go
from maxwellbloch import mb_solve, plot
from maxwellbloch.plot import theme # register Plotly template
[2]:
tau = 1.0 # delay: π/2 at t=-τ, π at t=0, echo at t=+τ
T = 0.025 # sech half-width — short hard pulses, Ω_peak >> Δ_Doppler
mb_solve_json = f"""
{{
"atom": {{
"num_states": 2,
"decays": [
{{"channels": [[0, 1]], "rate": 0.1}}
],
"fields": [
{{
"label": "pi_half",
"coupled_levels": [[0, 1]],
"rabi_freq": 1.0,
"rabi_freq_t_func": "sech",
"rabi_freq_t_args": {{"n_pi": 0.5, "centre": {-tau}, "width": {T}}}
}},
{{
"label": "pi",
"coupled_levels": [[0, 1]],
"rabi_freq": 1.0,
"rabi_freq_t_func": "sech",
"rabi_freq_t_args": {{"n_pi": 1.0, "centre": 0.0, "width": {T}}}
}}
]
}},
"t_min": -2.5,
"t_max": 2.0,
"t_steps": 900,
"z_min": 0.0,
"z_max": 1.0,
"z_steps": 20,
"z_steps_inner": 2,
"interaction_strengths": [1.0, 1.0],
"velocity_classes": {{
"thermal_width": 0.15,
"thermal_delta_min": -0.75,
"thermal_delta_max": 0.75,
"thermal_delta_steps": 40
}},
"savefile": "mbs-two-photon-echo"
}}
"""
mbs = mb_solve.MBSolve().from_json_str(mb_solve_json)
mbs.mbsolve(recalc=True)
print("Done")
/home/docs/checkouts/readthedocs.org/user_builds/maxwellbloch/envs/v0.11.0/lib/python3.11/site-packages/qutip/solver/solver_base.py:598: FutureWarning: e_ops will be keyword only from qutip 5.3 for all solver
warnings.warn(
100%|██████████| 20/20 [00:33<00:00, 1.66s/z, Ω_max=38.1]
Saving MBSolve to mbs-two-photon-echo.qu
Done
Output field: photon echo at \(t = +\tau\)
The total transmitted field \(|\Omega_{\pi/2}(z_{\rm max},t)| + |\Omega_{\pi}(z_{\rm max},t)|\) shows three features: the transmitted π/2 pulse (attenuated by Beer-Lambert absorption), the transmitted π pulse, and the photon echo at \(t = +\tau\).
[3]:
t = mbs.tlist
Omega_out = np.abs(mbs.Omegas_zt[0, -1, :]) + np.abs(mbs.Omegas_zt[1, -1, :])
Omega_in = np.abs(mbs.Omegas_zt[0, 0, :]) + np.abs(mbs.Omegas_zt[1, 0, :])
fig = go.Figure(layout=go.Layout(
title="Photon echo: transmitted field at z = z_max",
xaxis_title="time (γ⁻¹)",
yaxis_title="|Ω| (γ)",
template="maxwellbloch",
))
fig.add_trace(go.Scatter(x=t, y=Omega_in, name="input (z = 0)", line=dict(dash="dot")))
fig.add_trace(go.Scatter(x=t, y=Omega_out, name="output (z = z_max)"))
fig.add_vline(x=-tau, line_dash="dash", line_color="grey",
annotation_text="π/2", annotation_position="top right")
fig.add_vline(x=0, line_dash="dash", line_color="grey",
annotation_text="π", annotation_position="top right")
fig.add_vline(x=tau, line_dash="dash", line_color="red",
annotation_text="echo", annotation_position="top right")
fig.show(renderer='notebook_connected')
Macroscopic polarisation: dephasing and rephasing
\(\mathrm{Im}[\rho_{10}(z=0,t)]\) is the velocity-class-averaged polarisation. After the π/2 pulse it decays on the Doppler timescale \(1/\sigma \approx 0.24\,\gamma^{-1}\) (FID). The polarisation is essentially zero by \(t = 0\) (before the π pulse). After the π pulse it revives at \(t = +\tau\): the echo.
[4]:
fig = plot.coherence(mbs, i=1, j=0, z_idx=0, component="imag")
fig.update_layout(
title="Im[ρ₁₀(t)] at z = 0 — free-induction decay and rephasing"
)
fig.add_vline(x=-tau, line_dash="dash", line_color="grey",
annotation_text="π/2", annotation_position="top right")
fig.add_vline(x=0, line_dash="dash", line_color="grey",
annotation_text="π", annotation_position="top right")
fig.add_vline(x=tau, line_dash="dash", line_color="red",
annotation_text="echo", annotation_position="top right")
fig.show(renderer='notebook_connected')
Spacetime: π/2 pulse and echo buildup
The space-time plot of the π/2 field shows the input pulse propagating through the medium and being absorbed. At \(t = +\tau\) the rephasing polarisation drives the echo field, which grows from \(z = 0\) towards \(z_{\rm max}\) as each successive slice adds to the coherent emission.
[5]:
fig = plot.field_spacetime(mbs, field_idx=0)
fig.update_layout(title="|Ω_{π/2}(z, t)| — input pulse and photon echo")
fig.show(renderer='notebook_connected')
Echo amplitude vs delay: \(T_2\) measurement
Running the simulation for several delays \(\tau\) shows the exponential decay of the echo amplitude measured from \(\mathrm{Im}[\rho_{10}(z=0,t)]\):
This allows \(T_2\) to be extracted even when the absorption lineshape is completely dominated by inhomogeneous (Doppler) broadening.
[6]:
import math
T = 0.025
rate = 0.1
T2 = 2.0 / (2 * math.pi * rate) # ≈ 3.18 γ⁻¹
tau_values = [0.5, 0.75, 1.0, 1.25, 1.5]
echo_amps = []
for tau_val in tau_values:
j = f"""
{{
"atom": {{
"num_states": 2,
"decays": [{{"channels": [[0, 1]], "rate": {rate}}}],
"fields": [
{{"label":"pi_half","coupled_levels":[[0,1]],"rabi_freq":1.0,
"rabi_freq_t_func":"sech",
"rabi_freq_t_args":{{"n_pi":0.5,"centre":{-tau_val},"width":{T}}}}},
{{"label":"pi","coupled_levels":[[0,1]],"rabi_freq":1.0,
"rabi_freq_t_func":"sech",
"rabi_freq_t_args":{{"n_pi":1.0,"centre":0.0,"width":{T}}}}}
]
}},
"t_min": {-(tau_val + 1.5)},
"t_max": {tau_val + 1.0},
"t_steps": 500,
"z_min": 0.0, "z_max": 1.0, "z_steps": 20, "z_steps_inner": 2,
"interaction_strengths": [1.0, 1.0],
"velocity_classes": {{
"thermal_width": 0.15,
"thermal_delta_min": -0.75,
"thermal_delta_max": 0.75,
"thermal_delta_steps": 40
}},
"savefile": "mbs-two-photon-echo-tau{tau_val}"
}}
"""
m = mb_solve.MBSolve().from_json_str(j)
m.mbsolve(recalc=True)
t_arr = m.tlist
# Echo amplitude from Im[ρ₁₀] at z=0 in a window around t=+τ
rho10 = m.states_zt[0, :, 1, 0]
mask = (t_arr > tau_val - 5*T) & (t_arr < tau_val + 5*T)
echo_amp = float(rho10[mask].imag.max()) if mask.any() else 0.0
echo_amps.append(echo_amp)
print(f"τ = {tau_val:.2f} γ⁻¹ — echo Im[ρ₁₀]: {echo_amp:.4f}")
/home/docs/checkouts/readthedocs.org/user_builds/maxwellbloch/envs/v0.11.0/lib/python3.11/site-packages/qutip/solver/solver_base.py:598: FutureWarning: e_ops will be keyword only from qutip 5.3 for all solver
warnings.warn(
100%|██████████| 20/20 [00:19<00:00, 1.02z/s, Ω_max=38.3]
Saving MBSolve to mbs-two-photon-echo-tau0.5.qu
τ = 0.50 γ⁻¹ — echo Im[ρ₁₀]: 0.3284
100%|██████████| 20/20 [00:20<00:00, 1.00s/z, Ω_max=38.1]
Saving MBSolve to mbs-two-photon-echo-tau0.75.qu
τ = 0.75 γ⁻¹ — echo Im[ρ₁₀]: 0.2813
100%|██████████| 20/20 [00:20<00:00, 1.04s/z, Ω_max=37.9]
Saving MBSolve to mbs-two-photon-echo-tau1.0.qu
τ = 1.00 γ⁻¹ — echo Im[ρ₁₀]: 0.2326
100%|██████████| 20/20 [00:21<00:00, 1.06s/z, Ω_max=37.9]
Saving MBSolve to mbs-two-photon-echo-tau1.25.qu
τ = 1.25 γ⁻¹ — echo Im[ρ₁₀]: 0.2015
100%|██████████| 20/20 [00:21<00:00, 1.08s/z, Ω_max=37.5]
Saving MBSolve to mbs-two-photon-echo-tau1.5.qu
τ = 1.50 γ⁻¹ — echo Im[ρ₁₀]: 0.1756
[7]:
tau_arr = np.array(tau_values)
fig = go.Figure(layout=go.Layout(
title=f"Echo amplitude vs delay τ — T₂ measurement (T₂ = {T2:.2f} γ⁻¹)",
xaxis_title="τ (γ⁻¹)",
yaxis_title="Im[ρ₁₀] echo amplitude",
template="maxwellbloch",
))
fig.add_trace(go.Scatter(
x=tau_arr, y=echo_amps, mode="markers+lines", name="simulation"
))
# Analytic fit normalised to first data point
t_fine = np.linspace(tau_arr[0], tau_arr[-1], 100)
analytic = echo_amps[0] * np.exp(-(t_fine - tau_arr[0]) * 2 / T2)
fig.add_trace(go.Scatter(
x=t_fine, y=analytic, mode="lines",
name=f"e^(−2τ/T₂), T₂ = {T2:.2f} γ⁻¹", line=dict(dash="dash")
))
fig.show(renderer='notebook_connected')
Summary
π/2 pulse creates superposition; the macroscopic polarisation free-induction decays on the Doppler timescale \(1/\sigma\).
π pulse reverses all accumulated phases — each atom’s precession direction is effectively time-reversed.
Photon echo at \(t = +\tau\): all velocity classes rephase simultaneously and the medium emits a coherent echo pulse.
The echo amplitude \(\propto e^{-2\tau/T_2}\) gives a direct measurement of the homogeneous coherence time \(T_2\) in a medium dominated by inhomogeneous (Doppler) broadening.
Photon echoes are the time-domain counterpart of spin echoes in NMR and are used to characterise quantum memories, cold-atom ensembles, and rare-earth-doped crystals.
References
Hahn, Spin Echoes, Phys. Rev. 80, 580 (1950). Original NMR spin echo.
Kurnit, I. D. Abella, S. R. Hartmann, Observation of a Photon Echo, PRL 13, 567 (1964). First optical photon echo.
Allen, J. H. Eberly, Optical Resonance and Two-Level Atoms (Dover, 1987), Ch. 9. Bloch-vector derivation.
Scully, M. S. Zubairy, Quantum Optics (Cambridge, 1997), Ch. 7.