Per-Angle Scaling

Each azimuthal angle \(\phi_i\) in a heterodyne XPCS measurement has its own speckle contrast and baseline offset. This page explains how the package handles these per-angle parameters and how Fourier reparameterisation reduces the parameter count for joint multi-angle fits.

Per-Angle Parameters

For \(N_\phi\) azimuthal angles the model has:

  • 14 shared physics parameters – identical across all angles.

  • 2 scaling parameters per angle: contrast_i (\(\beta_i\)) and offset_i.

Total free parameters: \(14 + 2 N_\phi\).

For a typical 8-angle dataset: \(14 + 16 = 30\).

Per-Angle Modes

Heterodyne’s NLSQ optimizer supports four per-angle scaling modes, with identical semantics to the homodyne reference at homodyne anti-degeneracy theory. For \(n_\phi\) angles and Fourier truncation order \(K\):

Mode

Optimizer params

β(φ), o(φ) status

Behaviour

"constant"

14 (physics only)

Frozen per angle

β,o pre-estimated by quantile and held fixed in the residual. The optimizer never sees β, o as free variables.

"auto" (default)

16 = 14 + 2

Averaged, then optimized

Per-angle quantile estimates averaged to one β̄, ō and optimized jointly with physics. Selected automatically when n_phi >= constant_scaling_threshold (default 3).

"fourier" (K=2)

\(14 + 2(2K+1)\) = 24

Optimized as Fourier coeffs

β(φ), o(φ) modeled as truncated Fourier series in φ. The \(2K+1\) coefficients per scalar are optimized jointly.

"individual"

\(14 + 2 n_\phi\)

Per-angle, fully free

Each angle gets its own free β_k, o_k. Use with caution: prone to scaling absorption for many angles.

Note

Heterodyne uses 14 physics parameters (homodyne uses 7) — the per-angle parameter counts above add the same per-angle blocks on top of the larger physics block.

Deprecated since version 0.7: per_angle_mode="independent" is a deprecation alias for "individual" (homodyne’s canonical name). It will be removed in heterodyne v1.0.

Layer 5 (shear weighting) — intentionally excluded

Homodyne’s anti-degeneracy theory defines a fifth defense layer that re-weights the residual by the shear-sensitivity sinc term in the g₂ formula. Heterodyne uses a velocity-phase physics model: the g₂ expression contains no shear sinc term and there is nothing for L5 to re-weight. The exclusion is enforced at the code level — AntiDegeneracyController has no shear_weighter field, and use_shear_weighting() always returns False.

Fourier Reparameterisation

When the per-angle contrast and offset vary smoothly with \(\phi\), they can be represented as truncated Fourier series:

\[\beta(\phi) = a_0 + \sum_{k=1}^{K} \bigl[ a_k \cos(k\phi) + b_k \sin(k\phi) \bigr]\]

This reduces the number of free scaling parameters from \(2 N_\phi\) to \(2 (2K + 1)\). For \(K = 1\) and 8 angles, the count drops from 16 to 6.

The FourierReparamConfig controls this behaviour:

from heterodyne.optimization.nlsq.fourier_reparam import FourierReparamConfig

fourier_config = FourierReparamConfig(
    per_angle_mode="fourier",   # "independent" | "fourier" | "auto"
    fourier_order=1,            # Truncation order K
    fourier_auto_threshold=6,   # Switch to Fourier if N_angles >= threshold
)

Modes:

"independent"

No reparameterisation; each angle has free contrast and offset.

"fourier"

Express contrast and offset as Fourier series of order \(K\).

"auto"

Use "fourier" when \(N_\phi \ge\) fourier_auto_threshold, otherwise "independent".

Joint Multi-Angle Fitting

The fit_nlsq_multi_phi() function performs a joint fit across all angles simultaneously. When Fourier mode is active, the optimiser vector is structured as:

[ physics_varying | fourier_contrast_coeffs | fourier_offset_coeffs ]

This is transparent to the user – the result object reports per-angle contrast and offset values reconstructed from the Fourier coefficients.

from heterodyne.optimization.nlsq.core import fit_nlsq_multi_phi

result = fit_nlsq_multi_phi(
    model=model,
    c2_data=c2_stack,
    phi_angles=phi_angles,
    config=nlsq_config,
)

# Per-angle values are in the result
for name, val in result.params_dict.items():
    if name.startswith("contrast") or name.startswith("offset"):
        print(f"  {name}: {val:.4f}")

When to Use Fourier Mode

  • Many angles (>= 6) – Fourier mode significantly reduces the parameter count and regularises the fit.

  • Smooth angular variation – If contrast varies smoothly with \(\phi\) (as expected from beam geometry), Fourier mode is well-justified physically.

  • Noisy individual fits – If per-angle fits show erratic contrast values, Fourier mode pools information across angles for more stable estimates.

Fourier mode is not recommended when:

  • Contrast varies discontinuously (e.g., due to detector gaps or beamstop shadows).

  • Only 2–3 angles are available (too few data points for meaningful Fourier coefficients).