Skip to content

Vector (polarization-coupled) GNLSE

Split-step Fourier solver for the coupled two-polarization GNLSE: per-axis dispersion, XPM + coherent polarization FWM, differential group delay, the Manakov (8/9) polarization-averaged mode, and a random-birefringence engine that converges to the Manakov model. Docstrings are the source of truth, rendered with mkdocstrings (numpydoc style). See Vector GNLSE for the physics and model.

photonics_helper.vector_gnlse

Vector (polarization-coupled) GNLSE solver.

Split-step Fourier solver for the coupled GNLSE on two polarization components. The scalar :class:~photonics_helper.gnlse.SplitStepEngine models one linearly-polarized channel; the physics that only exists when the field is genuinely the two-component vector A = (A_x, A_y) lives here:

  • birefringent propagation — per-axis Taylor dispersion and differential group delay (polarization walk-off);
  • cross-phase modulation — the 2/3 anisotropy coefficient of the degenerate linearly-polarized mode pair;
  • coherent polarization FWM — the (i/3)γ A⊥²A* mixing term with the birefringence phase mismatch Δβ (opt-in; it oscillates away in real high-birefringence fiber);
  • Manakov averaging — the 8/9 polarization-averaged nonlinearity (Wai & Menyuk 1996), with a random-birefringence engine that applies the local nonlinear step in a randomly rotated polarization frame.

Scalar-limit contract: with A_y ≡ 0, coupling="incoherent" and identical split-step structure, this engine reduces to the scalar :class:~photonics_helper.gnlse.SplitStepEngine to machine precision (enforced by the test suite), so the scalar engine remains the tool of record for the single-mode/single-polarization regime and all validated scalar reproductions are unaffected.

Physics scope (v1)
  • Raman uses the scalar per-channel response P_j = (1−f_R)|A_j|² + f_R h_R ⊛ |A_j|²; the full vector Raman response (Lin & Agrawal, Opt. Lett. 31, 3086 (2006)) is future work.
  • Self-steepening, TPA and free carriers are scalar-engine features and raise a clear error when requested here.
  • Fixed-step propagation (num_steps / step_size), like the scalar engine's deterministic paths.
References

G. P. Agrawal, Nonlinear Fiber Optics, 5th ed., §6.1–6.3 (coupled GNLSE); P. K. A. Wai & C. R. Menyuk, J. Lightw. Technol. 14, 148 (1996) (random evolution and the Manakov model); C. Marcos Marcos, de Sterke & Sipe et al., Opt. Express 19, 553 (2011).

MANAKOV_FACTOR module-attribute

MANAKOV_FACTOR = 8.0 / 9.0

Coupling module-attribute

Coupling = Literal['incoherent', 'coherent', 'manakov']

VectorSplitStepEngine

VectorSplitStepEngine(pulse_x: Wave, pulse_y: Wave, fiber: FiberProfile, betas: NDArray | None = None, *, betas_x: NDArray | None = None, betas_y: NDArray | None = None, betas_unit: BetasUnit = 'ps^k/m', coupling: Coupling = 'incoherent', delta_beta: float = 0.0, walkoff: float = 0.0, include_raman: bool = False, step_size: Length | None = None)

Split-step engine for the coupled two-polarization GNLSE.

Parameters:

Name Type Description Default
pulse_x Wave

Envelope fields of the two polarization components (envelope_field is used, so Wave.with_field overrides are honoured). Both waves must share one :class:TemporalGrid (pulse_y.grid is pulse_x.grid).

required
pulse_y Wave

Envelope fields of the two polarization components (envelope_field is used, so Wave.with_field overrides are honoured). Both waves must share one :class:TemporalGrid (pulse_y.grid is pulse_x.grid).

required
fiber FiberProfile

Shared scalar parameters (n2, alpha, A_eff, confinement_factor, raman_response, length). Every GNLSE γ here is the scalar γ of this profile.

required
betas array_like

Taylor coefficients [β₂, β₃, …] used by both axes.

None
betas_x array_like

Per-axis dispersion (overrides betas). If only betas_x is given, betas_y defaults to None and a ValueError is raised — both axes must be specified explicitly (or use betas for degenerate axes).

None
betas_y array_like

Per-axis dispersion (overrides betas). If only betas_x is given, betas_y defaults to None and a ValueError is raised — both axes must be specified explicitly (or use betas for degenerate axes).

None
betas_unit ('ps^k/m', 's^k/m', 'SI')

Unit of the dispersion coefficients, as in the scalar engine.

"ps^k/m"
coupling ('incoherent', 'coherent', 'manakov')

Nonlinear model (see the module docstring):

  • "incoherent" (default) — coupled GNLSE with the 2/3 XPM factor and no coherent mixing: the standard model for high-birefringence (PM) fiber, where the FWM term averages out. Reduces exactly to the scalar engine when A_y ≡ 0.
  • "coherent" — adds the polarization four-wave-mixing term (i/3)γ A⊥²A* e^{∓2iΔβz}; requires delta_beta. Physically meaningful only when the beat length is comparable to or longer than the nonlinear length.
  • "manakov" — polarization-averaged model with the 8/9 factor. Requires identical per-axis dispersion and zero walkoff. Note: the 8/9 factor invites the reading that "manakov" at γ is "incoherent" at 8γ/9. That is true only when one channel is unpopulated (P_y ≡ 0): the manakov phase rate is (8/9)·γ·(P_x + P_y) while incoherent at γ' gives γ'·(P_x + (2/3)·P_y) — these agree for all P_x, P_y only when P_y ≡ 0. With both channels populated they are different models, not a re-parameterisation of one another.
"incoherent"
delta_beta float

Birefringent phase mismatch β_x − β_y (rad/m). "coherent" only.

0.0
walkoff float

Differential group delay β₁_y − β₁_x in the retarded frame of the x axis (SI, s/m). A y-pulse drifts by walkoff · z.

0.0
include_raman bool

Per-channel scalar Raman response.

False
step_size Length | None

Fixed step size (m). None uses length/num_steps.

None

pulse_x instance-attribute

pulse_x = pulse_x

pulse_y instance-attribute

pulse_y = pulse_y

fiber instance-attribute

fiber = fiber

betas_x instance-attribute

betas_x = bx

betas_y instance-attribute

betas_y = by

coupling instance-attribute

coupling: Coupling = coupling

delta_beta instance-attribute

delta_beta = float(delta_beta)

walkoff instance-attribute

walkoff = float(walkoff)

include_raman instance-attribute

include_raman = include_raman

step_size instance-attribute

step_size = step_size

grid instance-attribute

grid: TemporalGrid = pulse_x.grid

omega0 instance-attribute

omega0 = pulse_x.central_frequency

A_x instance-attribute

A_x = np.array(pulse_x.envelope_field, dtype=complex)

A_y instance-attribute

A_y = np.array(pulse_y.envelope_field, dtype=complex)

evolution_x instance-attribute

evolution_x: list[Wave] = []

evolution_y instance-attribute

evolution_y: list[Wave] = []

z_array property

z_array: NDArray

Propagation distances (m) for each saved snapshot.

energy_vs_z property

energy_vs_z: NDArray

Total pulse energy ∫(|A_x|²+|A_y|²)dt at each snapshot.

spectra_vs_z property

spectra_vs_z: tuple[NDArray, NDArray]

(ω [rad/s], summed spectrum |A_x|²+|A_y|²) per snapshot.

propagate

propagate(num_steps: int, *, nsaves: int | None = None, show_progress: bool = False) -> None

Run the coupled split-step propagation for num_steps steps.

Snapshot semantics match :meth:~photonics_helper.gnlse.SplitStepEngine.propagate: nsaves evenly spaced snapshots (including z=0 and z=L); with nsaves=None every step is saved. A total-photon-number monitor warns on >5% drift in lossless runs.

fields_vs_z

fields_vs_z() -> tuple[NDArray, NDArray]

Complex field histories: (A_x(z, t), A_y(z, t)).

Returns:

Type Description
(NDArray, NDArray)

Arrays of shape (n_saves, N):: both channels' envelopes at every saved snapshot.

nonlinear_phase_measure

nonlinear_phase_measure(index: Literal[0, 1] = 0) -> float

Nonlinear phase of channel index, measured against the input CW.

Meaningful for CW or constant-amplitude tests; returns arg(A_z / A_0) at the last snapshot.

RandomBirefringenceEngine

RandomBirefringenceEngine(pulse_x: Wave, pulse_y: Wave, fiber: FiberProfile, betas: NDArray, *, include_raman: bool = False, step_size: Length | None = None, seed: int | None = None)

Bases: VectorSplitStepEngine

Coupled GNLSE with a random polarisation-frame evolution.

Segments of length segment_length (default: a fixed step dz) are each propagated with :class:VectorSplitStepEngine's incoherent nonlinearity in a randomly rotated polarisation frame: the field is transformed by a random SU(2) rotation (uniform axis on the Poincaré sphere, uniform rotation angle), the local coupled-nonlinear step is applied, and the field is rotated back. Averaged over segments and seeds this reproduces the polarization-averaged Manakov model with the 8/9 effective nonlinearity (Wai & Menyuk 1996).

This is the engine to use for validating the Manakov limit: identical ensemble spectra to the deterministic Manakov run, depolarization of the mean field, exact total-energy conservation at every step.

seed instance-attribute

seed = seed

propagate

propagate(num_steps: int, *, nsaves: int | None = None, show_progress: bool = False) -> None

Randomly rotated propagation: same signature as the base :meth:VectorSplitStepEngine.propagate, with an SU(2) frame rotation before/after every nonlinear step.