Skip to content

χ⁽²⁾ nonlinear optics (SHG / SFG / DFG)

Coupled-wave solvers, quasi-phase-matching and the χ⁽²⁾ coupling constants (rk4ip-integrated solve_shg, solve_sfg, solve_dfg; delta_k_shg, Lambda_qpm). Docstrings are the source of truth, rendered with mkdocstrings (numpydoc style).

For waveguide χ⁽²⁾ devices (x-cut LNOI, PGLN-style grooves) use the mode-overlap entry points instead of the scalar A_eff:

import numpy as np
from photonics_helper.base import Wavelength
from photonics_helper.chi2 import shg_coupling_overlap, pgln_overlap

# transverse mode fields (nx, nz) from a FEM solver, arbitrary units
wl = Wavelength(1550, "nm")
g = shg_coupling_overlap(
    E_pump, E_sh, dx, dz, wavelength=wl, d=27e-12, n_pump=2.14, n_sh=2.19
)
# g in 1/(√W·m) — for a uniform mode this equals shg_coupling(A_eff)

res = pgln_overlap(
    E_pump,
    E_sh,
    dx,
    dz,
    wavelength=wl,
    d0=27e-12,
    d1=d1_profile,  # d^(m) harmonic profiles
    delta_eps1_pump=de1,
    delta_eps1_sh=de1,
    delta_k=2 * np.pi / 2.77e-6,
)  # QPM residual mismatch
res["g_eff"]  # the quasi-phase-matched overlap g' (Wang et al. 2017 Eq. 5)

photonics_helper.chi2

χ⁽²⁾ (second-order) nonlinear optics: coupled SHG / SFG / DFG solver.

Where is phase matching? (χ³ vs χ² map) - photonics_helper.chi2 (this module): χ⁽²⁾ — SHG / SFG / DFG phase matching (delta_k_shg, Lambda_qpm, QPM grating) and the coupling constants (shg_coupling, shg_coupling_overlap, pgln_overlap). - photonics_helper.phase_matching: χ⁽³⁾ — FWM / modulation instability / dispersive-wave roots.

Scalar, long-pulse (CW-like) three-wave mixing in a waveguide. The envelopes are normalized so that |A|² is an optical power in watts; the coupled equations are integrated in z with a classical fourth-order Runge–Kutta step in the interaction picture (RK4IP), which removes the fast exp(±iΔk z) phase from the state vector.

Conventions

For a non-degenerate three-wave process ω₃ = ω₁ + ω₂ with phase mismatch Δk = β(ω₃) − β(ω₁) − β(ω₂) and a single effective coupling σ [1/(√W·m)] the physical envelopes obey

.. math::

\frac{dA_1}{dz} &= i\sigma A_3 A_2^* e^{-i\Delta k z} \\
\frac{dA_2}{dz} &= i\sigma A_3 A_1^* e^{-i\Delta k z} \\
\frac{dA_3}{dz} &= i\sigma A_1 A_2 e^{+i\Delta k z}

Substituting A_3 = a_3 e^{iΔk z} gives the autonomous interaction-picture system that is actually integrated::

da₁/dz = iσ a₃ a₂*
da₂/dz = iσ a₃ a₁*
da₃/dz = iσ a₁ a₂ − iΔk a₃

SHG is the degenerate case A₁ = A₂ = A_f, A₃ = A_sh. With Δk = 0 the undepleted-pump solution is exact:

.. math::

\eta(z) = \frac{|A_\mathrm{sh}(z)|^2}{|A_f(0)|^2}
        = \tanh^2\!\left(\sigma \sqrt{P_0}\, z\right),
\qquad \kappa = \sigma\sqrt{P_0}

which is the regression ground truth used by the reproductions and tests.

The physical coupling for SHG in a waveguide of effective area A_eff and index n is (Boyd convention, fields power-normalised, Z₀ = 1/(c ε₀))::

σ = (ω d_eff / (n c)) · sqrt( 2 Z₀ / (n³ A_eff) )

(v0.1.1: this is 2× smaller than the pre-0.1.1 σ — the earlier formula double-counted the SHG degeneracy factor; conversion efficiencies change by 4×.)

Quasi-phase-matching

Periodic poling flips the sign of d_eff every half period. The square-wave grating g(z) = sign[cos(2πz/Λ)] is applied to σ directly. The first-order QPM period that compensates a mismatch Δk is Λ = 2π/|Δk| (see :func:Lambda_qpm); the effective coupling is reduced by 2/π.

Scope

Scalar envelopes, no dispersion / group-velocity mismatch / walk-off, no loss. Suitable for CW or long-pulse conversion-efficiency estimates and for validating χ⁽²⁾ bookkeeping; broadband ultrafast χ⁽²⁾+χ⁽³⁾ coupling is out of scope.

Public API

delta_k_shg — Δk = β(2ω) − 2β(ω) for SHG Lambda_qpm — first-order QPM poling period from Δk qpm_grating — square-wave poling sign g(z) shg_coupling — σ from d_eff, n, A_eff, λ Chi2Result — z-resolved envelopes and powers solve_shg — degenerate SHG solve_three_wave — generic ω₃ = ω₁ + ω₂ system solve_sfg — sum-frequency generation wrapper solve_dfg — difference-frequency generation wrapper

Chi2Result dataclass

Chi2Result(z: NDArray, A: NDArray, labels: tuple[str, ...], sigma: float, delta_k: float, qpm_period: float | None = None, loss_alpha: tuple[float, float] = (0.0, 0.0))

Z-resolved χ⁽²⁾ mixing result.

Attributes:

Name Type Description
z 1-D array — propagation coordinate (m), ``n_steps + 1`` points.
A 2-D complex array — shape ``(n_fields, n_z)``; ``|A|²`` is power (W).
labels tuple[str, ...] — field names, aligned with ``A`` rows.
sigma float — coupling used, ``1/(√W·m)``.
delta_k float — phase mismatch used, 1/m.
qpm_period float or None — poling period if QPM was enabled.
loss_alpha tuple (pump, sh) — Napierian power-loss coefficients (1/m)

used by the solver; (0.0, 0.0) means lossless.

z instance-attribute

z: NDArray

A instance-attribute

A: NDArray

labels instance-attribute

labels: tuple[str, ...]

sigma instance-attribute

sigma: float

delta_k instance-attribute

delta_k: float

qpm_period class-attribute instance-attribute

qpm_period: float | None = None

loss_alpha class-attribute instance-attribute

loss_alpha: tuple[float, float] = (0.0, 0.0)

powers property

powers: NDArray

Real power array |A|² in watts, shape (n_fields, n_z).

kappa property

kappa: float

Effective coupling κ = σ√P₀ (1/m) for the first field.

field

field(label: str) -> NDArray

Complex envelope for label (shape (n_z,)).

power

power(label: str) -> NDArray

Power in watts for label (shape (n_z,)).

efficiency

efficiency(signal: str = 'sh', pump: str | None = None) -> NDArray

Conversion efficiency P_signal(z) / P_pump(0).

Parameters:

Name Type Description Default
signal str — label of the generated field (default ``"sh"``).
'sh'
pump str or None — label of the reference pump; defaults to the first

field (labels[0]).

None

delta_k_shg

delta_k_shg(beta_fn: Callable[[float], float], omega: float) -> float

Phase mismatch for SHG: Δk = β(2ω) − 2β(ω).

Parameters:

Name Type Description Default
beta_fn callable — ``β(ω)`` in 1/m (e.g. a

:class:~photonics_helper.phase_matching.PropagationConstantAdaptor).

required
omega float — fundamental angular frequency in rad/s.
required

Returns:

Type Description
float — Δk in 1/m.

Lambda_qpm

Lambda_qpm(delta_k: float, order: int = 1) -> float

Quasi-phase-matching poling period Λ = 2π·order / |Δk|.

Parameters:

Name Type Description Default
delta_k float — phase mismatch in 1/m (non-zero).
required
order int — QPM order (default 1). Odd orders are the useful ones for a

50 % duty-cycle square grating.

1

Returns:

Type Description
float — poling period in metres.

qpm_grating

qpm_grating(z: float | NDArray, period: float, duty_cycle: float = 0.5) -> NDArray

Square-wave poling sign g(z) = ±1 with period period.

Parameters:

Name Type Description Default
z float or array — propagation coordinate(s) in metres.
required
period float — poling period Λ in metres (positive).
required
duty_cycle float — fraction of the period with ``+1`` (default 0.5).
0.5

Returns:

Type Description
NDArray — ``+1`` or ``-1``, same shape as ``z``.

shg_coupling

shg_coupling(wavelength: Wavelength, d_eff: float, *, n: float = 1.0, A_eff: Area | None = None) -> float

SHG coupling coefficient κ = σ in 1/(√W·m) (Boyd convention).

Uses the Maxwell/Boyd plane-wave normalisation with the field amplitudes scaled so that |A|² is power:

.. math::

σ = κ/√P₀ = (ω d_eff / (n c)) · sqrt( 2 Z₀ / (n³ A_eff) ),

giving κ = σ√P₀ and the undepleted-pump limit η = tanh²(σ√P₀ z) at perfect phase matching. This is the coupling obtained directly from Boyd's Nonlinear Optics (3rd ed., Ch. 2) coupled-wave equations and the Maxwell derivation with I = ½ n c ε₀ |E|² and P = I·A_eff; the degeneracy (factor 2) of the SHG process is already carried by d_eff (d_eff = χ⁽²⁾/2), so no extra factor of 2 belongs here.

Breaking change (v0.1.1): earlier releases used σ = (2ω d_eff/(n c))·√(2/(n c ε₀ A_eff)), which is exactly 2× this value (efficiencies 4× too large). Re-derive any reference values from before this change.

Parameters:

Name Type Description Default
wavelength Wavelength — fundamental (pump) vacuum wavelength.
required
d_eff float — effective second-order coefficient in m/V (i.e.

χ⁽²⁾/2; for LiNbO₃ d_eff = d33 = 27 pm/V).

required
n float — refractive index at the fundamental (default 1.0). The

second-harmonic index is taken equal (non-dispersive approximation).

1.0
A_eff Area — effective mode area; defaults to 1 µm².
None

Returns:

Type Description
float — σ in ``1/(√W·m)``.

solve_shg

solve_shg(*, length: float, P0: float, sigma: float, n_steps: int = 2000, delta_k: float = 0.0, qpm_period: float | None = None, qpm_duty_cycle: float = 0.5, loss_db_per_cm: tuple[float, float] | None = None) -> Chi2Result

Integrate degenerate SHG ω + ω → 2ω with pump depletion.

Parameters:

Name Type Description Default
length float — interaction length L in metres.
required
P0 float — input fundamental power in watts (``|A_f(0)|²``).
required
sigma float — coupling coefficient ``σ`` in ``1/(√W·m)`` (see

:func:shg_coupling).

required
n_steps int — number of RK4 steps (default 2000).
2000
delta_k float — phase mismatch ``Δk = β(2ω) − 2β(ω)`` in 1/m.
0.0
qpm_period float or None — poling period Λ for QPM; ``None`` disables it.
None
qpm_duty_cycle float — QPM duty cycle (default 0.5).
0.5
loss_db_per_cm tuple (pump, sh) or None — power-loss coefficients in

dB/cm; converted internally to Napierian per metre via α = ln(10)/10 · 10² · (dB/cm) and applied as −α/2·A in the coupled equations. None (default) is lossless and bitwise identical to the pre-loss solver.

None

Returns:

Type Description
Chi2Result — fields labelled ``("fundamental", "sh")``.

solve_three_wave

solve_three_wave(*, length: float, sigma: float, A1_0: complex, A2_0: complex, A3_0: complex = 0j, labels: tuple[str, str, str] = ('field1', 'field2', 'field3'), n_steps: int = 2000, delta_k: float = 0.0, qpm_period: float | None = None, qpm_duty_cycle: float = 0.5) -> Chi2Result

Integrate a generic ω₃ = ω₁ + ω₂ three-wave mixing process.

Covers SFG (two inputs, sum output) and DFG (pump + signal → idler) by choosing the input amplitudes and Δk = β(ω₃) − β(ω₁) − β(ω₂).

Parameters:

Name Type Description Default
length float — interaction length in metres.
required
sigma float — coupling coefficient in ``1/(√W·m)``.
required
A1_0 complex — input envelopes (``√W``).
required
A2_0 complex — input envelopes (``√W``).
required
A3_0 complex — input of the generated field (default 0).
0j
labels tuple[str, str, str] — names for the three fields.
('field1', 'field2', 'field3')
n_steps int
2000
delta_k int
2000
qpm_period int
2000
qpm_duty_cycle int
2000

Returns:

Type Description
Chi2Result — three labelled fields.

solve_sfg

solve_sfg(*, length: float, P1: float, P2: float, sigma: float, n_steps: int = 2000, delta_k: float = 0.0, qpm_period: float | None = None, qpm_duty_cycle: float = 0.5) -> Chi2Result

Sum-frequency generation ω₁ + ω₂ → ω₃ from two real inputs.

Returns a :class:Chi2Result labelled ("signal", "pump", "sum").

solve_dfg

solve_dfg(*, length: float, Ppump: float, Psignal: float, sigma: float, n_steps: int = 2000, delta_k: float = 0.0, qpm_period: float | None = None, qpm_duty_cycle: float = 0.5) -> Chi2Result

Difference-frequency generation ω_pump − ω_signal → ω_idler.

Returns a :class:Chi2Result labelled ("pump", "signal", "idler").

solve_cascaded_shg

solve_cascaded_shg(*, length: float, P0: float, sigma: float, gamma_f: float, gamma_sh: float | None = None, gamma_cross: float | None = None, n_steps: int = 2000, delta_k: float = 0.0, qpm_period: float | None = None, qpm_duty_cycle: float = 0.5, loss_db_per_cm: tuple[float, float] | None = None) -> Chi2Result

Integrate degenerate SHG plus the bulk χ⁽³⁾ Kerr effects.

The two coupled equations (fundamental ω, second harmonic 2ω) in the interacting-frame convention of :func:solve_shg:

::

dA_f/dz  = i σ A_SH A_f* + iγ_f |A_f|² A_f + iγ_cross |A_SH|² A_f
           − (α_f/2) A_f
dA_SH/dz = i σ A_f² − iΔk A_SH + iγ_sh |A_SH|² A_SH
           + iγ_cross |A_f|² A_SH − (α_sh/2) A_SH

i.e. the second-order (quadratic) coupling of :func:solve_shg combined with the cubic Kerr self-/cross-phase modulation. At large phase mismatch (|Δk| ≫ σ√P₀) the quadratic coupling acts on the fundamental like an effective Kerr coefficient γ_φ = σ²P₀/Δk — the cascaded-Kerr limit (Epstein; Saltiel et al.; Agrawal §10.5) — validated in the test suite as the large-mismatch limit of this exact integrator.

Parameters:

Name Type Description Default
length float — interaction length L in metres.
required
P0 float — input fundamental power in watts (``|A_f(0)|²``).
required
sigma float — quadratic coupling ``σ`` in ``1/(√W·m)``

(:func:shg_coupling). Set sigma=0 for a pure-Kerr run.

required
gamma_f float — Kerr SPM coefficient on the fundamental (W⁻¹m⁻¹,

dφ/dz = γ_f |A_f|²).

required
gamma_sh float or None — Kerr SPM coefficient on the SH. ``None``

(default) uses gamma_f (equal-mode-overlap assumption).

None
gamma_cross float or None — Kerr XPM coefficient between the two

waves. None (default) uses 2/3 · gamma_f — the degenerate linearly-polarized mode-pair factor used elsewhere in the library (:mod:photonics_helper.vector_gnlse); non-degenerate geometries should pass the mode overlap explicitly.

None
n_steps int

:func:solve_shg (the Kerr terms are unaffected by the poling).

2000
delta_k int

:func:solve_shg (the Kerr terms are unaffected by the poling).

2000
qpm_period int

:func:solve_shg (the Kerr terms are unaffected by the poling).

2000
qpm_duty_cycle int

:func:solve_shg (the Kerr terms are unaffected by the poling).

2000
loss_db_per_cm int

:func:solve_shg (the Kerr terms are unaffected by the poling).

2000

Returns:

Type Description
Chi2Result — fields labelled ``("fundamental", "sh")``.
Limit contracts (regression-tested)
  • gamma_f = 0 (pure quadratic) equals :func:solve_shg exactly;
  • sigma = 0 (pure Kerr): the fundamental solves the scalar SPM equation with the SH held empty — analytic exp(iγ_f P₀ L) phase;
  • large-Δk cascaded limit: fundamental nonlinear phase ≈ (γ_f + σ²P₀/Δk) · P₀ · L within the undepleted-pump approximation.

shg_coupling_overlap

shg_coupling_overlap(E_pump: ndarray, E_sh: ndarray, dx: float, dz: float, *, wavelength: Wavelength, d: float | ndarray = 2.7e-11, n_pump: float | None = None, n_sh: float | None = None) -> float

Modal nonlinear overlap coupling g in 1/(√W·m).

Implements the Wang et al. 2017 (Eq. 2) overlap integral with the scale-invariant, units-safe shape-factor form::

g = (ω d₀/c)·√(2 Z₀/(n_p² n_s)) · O / (I_p √I_s)

where I_p = ∬|E_ω|²dx dz, I_s = ∬|E_2ω|²dx dz, and O = ∬ (E_2ω)*·(d(x,z)/d₀)·(E_ω)² dx dz. For a uniform field over area A_eff this reduces exactly to the Boyd plane-wave coupling of :func:shg_coupling; for higher-order / sign-alternating (e.g. TE₃) SH modes the partial cancelation shrinks g by the modal overlap factor, which a scalar A_eff cannot represent.

Parameters:

Name Type Description Default
E_pump (nx, nz) complex arrays — transverse mode fields at ω and

2ω; arbitrary absolute scaling (all ratios are scale-invariant).

required
E_sh (nx, nz) complex arrays — transverse mode fields at ω and

2ω; arbitrary absolute scaling (all ratios are scale-invariant).

required
dx float — transverse grid spacings (m).
required
dz float — transverse grid spacings (m).
required
wavelength Wavelength — fundamental (pump) vacuum wavelength.
required
d float or (nx, nz) array — χ⁽²⁾ coefficient d(x,z) (m/V); a scalar

broadcasts (e.g. LiNbO₃ d33 = 27 pm/V); spatial profiles carry groove/poling modulation.

2.7e-11
n_pump float or None — effective indices at ω / 2ω

(default 2.0).

None
n_sh float or None — effective indices at ω / 2ω

(default 2.0).

None

Returns:

Type Description
float — modal coupling ``g`` in ``1/(√W·m)``, same σ convention as
func:`shg_coupling`; directly usable in :func:`solve_shg`.

pgln_overlap

pgln_overlap(E_pump: ndarray, E_sh: ndarray, dx: float, dz: float, *, wavelength: Wavelength, d0: float | ndarray, d1: float | ndarray, delta_eps1_pump: float | ndarray, delta_eps1_sh: float | ndarray, delta_k: float, n_pump: float | None = None, n_sh: float | None = None) -> dict

Quasi-phase-matched overlap of a periodically-grooved (PGLN) guide.

Implements Wang et al. 2017 (Opt. Express 25(6), 6963), Eqs (3)-(5), for the first Fourier order of a geometric (groove) modulation of period Λ. All values use the same proven overg convention as :func:shg_coupling_overlap::

g_NL^(m)  = (2ω d₀/c)·√(2 Z₀/(n_p² n_s)) · D_m
g_L^ω     = (ω ε₀/4)·∫Δε₁|E_ω|² modulated shape  (1-W fields)
g_L^2ω    = (2ω ε₀/4)·∫Δε₁|E_2ω|² modulated shape
φ         = 2 (g_L^2ω − g_L^ω)/Δk
g'        = g_NL^(1)·(J₀(φ) + J₂(φ)) − g_NL^(0)·J₁(φ)

D_m here are Fourier-harmonic-overlap shape factors, computed with the same scale-invariant ratio as :func:shg_coupling_overlap: the caller supplies (nx, nz) profiles of the harmonic mean coefficient d^(m)(x, z) (grooves set d = 0 there) and the index-perturbation profile Δε₁(x, z).

Parameters:

Name Type Description Default
E_pump (nx, nz) complex arrays — transverse mode fields at ω and

2ω; arbitrary absolute scaling.

required
E_sh (nx, nz) complex arrays — transverse mode fields at ω and

2ω; arbitrary absolute scaling.

required
dx float — transverse grid spacings (m).
required
dz float — transverse grid spacings (m).
required
wavelength Wavelength — pump vacuum wavelength.
required
d0 float or (nx, nz) array — mean χ⁽²⁾ coefficient (m/V), e.g. 27 pm/V.
required
d1 float or (nx, nz) array — first-harmonic χ⁽²⁾ profile (0 for a pure

groove-modulated waveguide with grooves that remove the LN).

required
delta_eps1_pump float or (nx, nz) arrays — first-harmonic

permittivity perturbation Δε₁ seen by each harmonic (dimensionless e.g. n_clad² − n_LN² over the groove; harmonic-specific).

required
delta_eps1_sh float or (nx, nz) arrays — first-harmonic

permittivity perturbation Δε₁ seen by each harmonic (dimensionless e.g. n_clad² − n_LN² over the groove; harmonic-specific).

required
delta_k float — residual mismatch ``β(2ω) − 2β(ω) − 2π/Λ`` (1/m).
required
n_pump float or None — effective indices; default 2.0/2.0.
None
n_sh float or None — effective indices; default 2.0/2.0.
None

Returns:

Type Description
dict with ``"g_nl_0"``, ``"g_nl_1"``, ``"g_L_w"``, ``"g_L_2w"``,
``"phi"``, ``"g_eff"`` (floats; coupling values in ``1/(√W·m)``,
``φ`` dimensionless).
Notes

For a zero-modulation check, d1 = 0 gives g_NL^(1) = 0 → g' = j1(φ)·g_NL^(0)·(∓1); the uniform-guide limit (Δε₁ = 0, helper) does NOT reduce to :func:shg_coupling_overlap because a groove with zero depth is not the uniform waveguide — callers verifying divisibility should set (Δε₁ = 0, d1 = d0·shape) (the article's duty-cycle square wave) and check g_eff → g there.