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

  1. π/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).

  2. 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).

  3. π pulse (at \(t = 0\)): inverts every Bloch vector, reversing the sense of accumulated phase: \(\phi_j \to -\phi_j\).

  4. 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

\[\Omega_{\rm peak} \gg \Delta_{\rm Doppler}\]

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 rate

\(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")
100%|██████████| 20/20 [00:32<00:00,  1.63s/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)]\):

\[A_{\rm echo}(\tau) \propto e^{-2\tau/T_2}, \qquad T_2 = \frac{2}{\Gamma} = \frac{2}{2\pi\times{\rm rate}} \approx 3.18\,\gamma^{-1}\]

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}")
100%|██████████| 20/20 [00:19<00:00,  1.04z/s, Ω_max=38.3]
Saving MBSolve to mbs-two-photon-echo-tau0.5.qu
τ = 0.50 γ⁻¹ — echo Im[ρ₁₀]: 0.3284
100%|██████████| 20/20 [00:19<00:00,  1.01z/s, Ω_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.02s/z, Ω_max=37.9]
Saving MBSolve to mbs-two-photon-echo-tau1.0.qu
τ = 1.00 γ⁻¹ — echo Im[ρ₁₀]: 0.2326
100%|██████████| 20/20 [00:20<00:00,  1.03s/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.06s/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

      1. Hahn, Spin Echoes, Phys. Rev. 80, 580 (1950). Original NMR spin echo.

      1. Kurnit, I. D. Abella, S. R. Hartmann, Observation of a Photon Echo, PRL 13, 567 (1964). First optical photon echo.

    1. Allen, J. H. Eberly, Optical Resonance and Two-Level Atoms (Dover, 1987), Ch. 9. Bloch-vector derivation.

      1. Scully, M. S. Zubairy, Quantum Optics (Cambridge, 1997), Ch. 7.