Linear Absorption — Beer–Lambert Law

When an electromagnetic field is weak enough that it never significantly depletes the ground-state population, the medium responds linearly: the field amplitude decays exponentially with propagation distance. This is the Beer–Lambert law,

\[I(z, \omega) = I_0(\omega)\,e^{-\alpha(\omega)\,z},\]

where the absorption coefficient is \(\alpha(\omega) := k\chi_I(\omega)\) (the wavenumber times the imaginary part of the susceptibility). Since \(I \propto |\Omega|^2\) this is equivalent to

\[|\Omega(z, \omega)| = |\Omega_0(\omega)|\,e^{-\alpha(\omega)\,z/2}.\]

For the resonant two-level system \(\alpha(0) = 2Ng/\Gamma\), giving \(|\Omega(z)| = |\Omega_0|\,e^{-Ng\,z/\Gamma}\) at \(\omega = 0\). We use \(\Omega_0 = 10^{-3}\,\Gamma\) throughout.

This notebook demonstrates:

  1. How increasing optical depth attenuates the pulse.

  2. The role of spontaneous decay in reaching a true Beer–Lambert steady state.

  3. That the exponential attenuation is independent of pulse shape (Gaussian, CW).

Parameters

The interaction_strengths parameter \(Ng\) sets the coupling between field and medium. It is proportional to atomic density \(N\) and the dipole matrix element squared: \(C \propto N |\mu|^2 / \hbar\). In the weak-field limit the resonant absorption coefficient is \(\alpha \propto Ng\), so doubling \(Ng\) doubles the optical depth of the medium.

We will compare three values:

\(Ng\)

Character

0.1

Optically thin — pulse passes through almost unchanged

1.0

Moderate absorption

10.0

Optically thick — strong attenuation

[1]:
import numpy as np
import plotly.graph_objects as go
from maxwellbloch import mb_solve, plot

Optically thin medium — \(Ng = 0.1\)

With very few atoms the pulse propagates essentially undistorted. There is a small reduction in amplitude and a slight group delay, but the pulse shape is preserved.

[2]:
mb_solve_json_ng01 = """
{
  "atom": {
    "fields": [
      {
        "coupled_levels": [[0, 1]],
        "detuning": 0.0,
        "rabi_freq": 1.0e-3,
        "rabi_freq_t_args": {"ampl": 1.0, "centre": 0.0, "fwhm": 1.0},
        "rabi_freq_t_func": "gaussian"
      }
    ],
    "num_states": 2,
    "decays": [{"channels": [[0, 1]], "rate": 1.0}]
  },
  "t_min": -2.0,
  "t_max": 10.0,
  "t_steps": 100,
  "z_min": -0.2,
  "z_max": 1.2,
  "z_steps": 10,
  "interaction_strengths": [0.1],
  "savefile": "mbs-linear-absorption-ng01-decay"
}
"""

mbs_ng01 = mb_solve.MBSolve().from_json_str(mb_solve_json_ng01)
mbs_ng01.mbsolve(recalc=False);
/home/docs/checkouts/readthedocs.org/user_builds/maxwellbloch/envs/v0.12.0/lib/python3.11/site-packages/maxwellbloch/mb_solve.py:344: UserWarning: Savefile was built with maxwellbloch==0.11.0, current version is 0.12.0.
  self.load_results()
[3]:
fig = plot.field_spacetime(mbs_ng01)
fig.update_layout(title="|Ω(z, t)| — Gaussian pulse, Ng = 0.1 (optically thin)")
fig.show(renderer='notebook_connected')

Moderate optical depth — \(Ng = 1\)

With \(Ng = 1\) the absorption is noticeable: the output amplitude is appreciably smaller than the input and the pulse acquires a clear group delay as the leading edge drives the atoms before the trailing edge arrives.

[4]:
mb_solve_json_ng1 = """
{
  "atom": {
    "fields": [
      {
        "coupled_levels": [[0, 1]],
        "detuning": 0.0,
        "rabi_freq": 1.0e-3,
        "rabi_freq_t_args": {"ampl": 1.0, "centre": 0.0, "fwhm": 1.0},
        "rabi_freq_t_func": "gaussian"
      }
    ],
    "num_states": 2,
    "decays": [{"channels": [[0, 1]], "rate": 1.0}]
  },
  "t_min": -2.0,
  "t_max": 10.0,
  "t_steps": 100,
  "z_min": -0.2,
  "z_max": 1.2,
  "z_steps": 20,
  "z_steps_inner": 2,
  "interaction_strengths": [1.0],
  "savefile": "mbs-linear-absorption-ng1-decay"
}
"""

mbs_ng1 = mb_solve.MBSolve().from_json_str(mb_solve_json_ng1)
mbs_ng1.mbsolve(recalc=False);
/home/docs/checkouts/readthedocs.org/user_builds/maxwellbloch/envs/v0.12.0/lib/python3.11/site-packages/maxwellbloch/mb_solve.py:344: UserWarning: Savefile was built with maxwellbloch==0.11.0, current version is 0.12.0.
  self.load_results()
[5]:
fig = plot.field_spacetime(mbs_ng1)
fig.update_layout(title="|Ω(z, t)| — Gaussian pulse, Ng = 1")
fig.show(renderer='notebook_connected')

Optically thick medium — \(Ng = 10\)

With \(Ng = 10\) the medium is strongly absorbing. The pulse is heavily attenuated and significantly reshaped: the leading spectral components are absorbed first, delaying the effective peak and broadening the transmitted pulse.

[6]:
mb_solve_json_ng10 = """
{
  "atom": {
    "fields": [
      {
        "coupled_levels": [[0, 1]],
        "detuning": 0.0,
        "rabi_freq": 1.0e-3,
        "rabi_freq_t_args": {"ampl": 1.0, "centre": 0.0, "fwhm": 1.0},
        "rabi_freq_t_func": "gaussian"
      }
    ],
    "num_states": 2,
    "decays": [{"channels": [[0, 1]], "rate": 1.0}]
  },
  "t_min": -2.0,
  "t_max": 10.0,
  "t_steps": 100,
  "z_min": -0.2,
  "z_max": 1.2,
  "z_steps": 100,
  "z_steps_inner": 2,
  "interaction_strengths": [10.0],
  "savefile": "mbs-linear-absorption-ng10-decay"
}
"""

mbs_ng10 = mb_solve.MBSolve().from_json_str(mb_solve_json_ng10)
mbs_ng10.mbsolve(recalc=False);
/home/docs/checkouts/readthedocs.org/user_builds/maxwellbloch/envs/v0.12.0/lib/python3.11/site-packages/maxwellbloch/mb_solve.py:344: UserWarning: Savefile was built with maxwellbloch==0.11.0, current version is 0.12.0.
  self.load_results()
[7]:
fig = plot.field_spacetime(mbs_ng10)
fig.update_layout(title="|Ω(z, t)| — Gaussian pulse, Ng = 10 (optically thick)")
fig.show(renderer='notebook_connected')

Output envelope comparison

Plotting the output field envelope \(|\Omega(z=1, t)|\) for all three cases alongside the common input shows the progressive attenuation. In the Beer–Lambert limit the peak amplitude scales as \(e^{-\alpha/2}\) with \(\alpha \propto Ng\).

[8]:
fig = go.Figure()

# input (same for all runs — use Ng=0.1 z=0 as reference)
fig.add_trace(go.Scatter(
    x=mbs_ng01.tlist,
    y=np.abs(mbs_ng01.Omegas_zt[0, 0, :]),
    mode="lines",
    name="input",
    line=dict(dash="dash", color="black"),
))

for mbs, label, color in [
    (mbs_ng01, "Ng = 0.1", "#636EFA"),
    (mbs_ng1,  "Ng = 1",   "#EF553B"),
    (mbs_ng10, "Ng = 10",  "#00CC96"),
]:
    fig.add_trace(go.Scatter(
        x=mbs.tlist,
        y=np.abs(mbs.Omegas_zt[0, -1, :]),
        mode="lines",
        name=label,
        line=dict(color=color),
    ))

fig.update_layout(
    title="Output pulse envelope |Ω(z=1, t)| vs optical depth",
    xaxis_title="t (γ⁻¹)",
    yaxis_title="|Ω| (γ)",
    template="maxwellbloch",
    width=700,
    height=400,
)
fig.show(renderer='notebook_connected')

Pulse-shape independence — CW field

Beer–Lambert attenuation depends only on frequency and optical depth, not on pulse shape. A continuous-wave (CW) field ramped on at \(t = 0\) reaches a steady-state transmitted amplitude determined solely by \(Ng\) once the transient has decayed. This is the same \(e^{-\alpha/2}\) factor seen above.

With decay present the transient ring-on settles in a time \(\sim 1/\Gamma\).

[9]:
mb_solve_json_cw = """
{
  "atom": {
    "decays": [{"channels": [[0, 1]], "rate": 1.0}],
    "fields": [
      {
        "coupled_levels": [[0, 1]],
        "detuning": 0.0,
        "rabi_freq": 1.0e-3,
        "rabi_freq_t_args": {"ampl": 1.0, "on": 0.0, "off": 8.0, "fwhm": 1.0},
        "rabi_freq_t_func": "ramp_onoff"
      }
    ],
    "num_states": 2
  },
  "t_min": -2.0,
  "t_max": 10.0,
  "t_steps": 100,
  "z_min": -0.2,
  "z_max": 1.2,
  "z_steps": 20,
  "interaction_strengths": [10.0],
  "savefile": "mbs-linear-absorption-cw"
}
"""

mbs_cw = mb_solve.MBSolve().from_json_str(mb_solve_json_cw)
mbs_cw.mbsolve(recalc=False);
/home/docs/checkouts/readthedocs.org/user_builds/maxwellbloch/envs/v0.12.0/lib/python3.11/site-packages/maxwellbloch/mb_solve.py:344: UserWarning: Savefile was built with maxwellbloch==0.11.0, current version is 0.12.0.
  self.load_results()
[10]:
fig = plot.field_spacetime(mbs_cw)
fig.update_layout(title="|Ω(z, t)| — CW ramp-on field, Ng = 10, Γ = 1")
fig.show(renderer='notebook_connected')

Beer–Lambert comparison

With decay and a CW field, the medium reaches a true steady state: the on-resonance (\(\omega = 0\)) field amplitude decays exponentially with penetration depth. Extracting the spatial profile at a time well inside the CW plateau (\(t = 5\,\gamma^{-1}\), long after the ramp-on transient has decayed) and comparing with the Beer–Lambert prediction \(|\Omega(z)| = |\Omega_0|\,e^{-Ng\,z/\Gamma}\) shows excellent agreement.

[11]:
# Time index for plateau (t ≈ 5, well after ramp-on transient)
i_t5 = np.argmin(np.abs(mbs_cw.tlist - 5.0))

# CW amplitude vs z at steady state
Omega_cw_z = np.abs(mbs_cw.Omegas_zt[0, :, i_t5])

# Reference amplitude before the medium (z < 0, vacuum region)
Omega_0 = Omega_cw_z[0]   # at z = z_min = -0.2

# Beer-Lambert curve: Ω(z) = Ω₀ exp(−Ng z/Γ) inside medium [0, 1]
# outside [0, 1] the density is zero so amplitude is constant
Ng_val = 10.0
Gamma = 1.0
z_arr = mbs_cw.zlist
beer_lambert = Omega_0 * np.exp(-Ng_val / Gamma * np.clip(z_arr, 0.0, 1.0))

fig = go.Figure()
fig.add_trace(go.Scatter(
    x=z_arr, y=Omega_cw_z,
    mode='lines', name='simulation (CW steady state)',
))
fig.add_trace(go.Scatter(
    x=z_arr, y=beer_lambert,
    mode='lines', name='Beer–Lambert: Ω₀ exp(−Ng z/Γ)',
    line=dict(dash='dash'),
))
fig.update_layout(
    title='CW steady-state spatial profile vs Beer–Lambert (Ng = 10, Γ = 1)',
    xaxis_title='z',
    yaxis_title='|Ω| (γ)',
    yaxis_type='log',
    template='maxwellbloch',
    width=700,
    height=400,
)
fig.show(renderer='notebook_connected')

Summary

  • In the weak-field limit (\(\Omega_0 \ll \Gamma\)) the Maxwell–Bloch equations reduce to the Beer–Lambert law: amplitude decays exponentially with \(Ng\).

  • Without spontaneous decay the atoms respond coherently; the medium acts as a resonant absorber but also re-emits energy, producing a free induction decay tail after the pulse.

  • Adding decay at rate \(\Gamma\) damps the coherence on timescale \(T_2 = 2/\Gamma\). Once \(T_2 \lesssim \tau_\text{pulse}\) the transmission matches the steady-state Beer–Lambert prediction.

  • The steady-state attenuation is pulse-shape independent: both Gaussian and CW fields with the same \(Ng\) reach the same transmitted amplitude once transients have decayed.

The nonlinear regime — where the field is strong enough to saturate the transition and the area theorem governs propagation — is covered in the Area Theorem and SIT Solitons notebooks.