Laminar Flow Analysis Guide

Learning Objectives

By the end of this section you will understand:

  • The physical setup for laminar flow XPCS (Taylor-Couette geometry)

  • How to configure homodyne for laminar flow experiments

  • The anti-degeneracy system and why it matters

  • Angular dependence diagnostics

  • Common laminar flow fitting problems and solutions

Physical Setup: Taylor-Couette Geometry

Laminar flow XPCS is most commonly performed in a Taylor-Couette (concentric cylinder) shear cell. The sample fills the gap between a stationary outer cylinder (stator) and a rotating inner cylinder (rotor).

The key geometric parameters:

  • Gap distance \(h\): stator-rotor separation (µm). Configured as gap_distance in YAML (µm, converted to Å internally: 1 µm = 1×10⁴ Å).

  • Flow direction \(\phi_0\): azimuthal angle of the flow velocity relative to the detector coordinate system.

  • Wavevector \(q\): in the horizontal plane, magnitude in Å⁻¹.

The shear-induced dynamics appear in the XPCS signal because particles are displaced by the shear flow during the correlation time. The displacement at time lag \(\Delta t = t_2 - t_1\) is:

\[\Delta x(\phi) = h \, \Gamma(t_1, t_2) \cos(\phi - \phi_0)\]

where \(\Gamma\) is the accumulated shear strain. This displacement introduces a sinc² factor in \(C_2\):

\[C_2 \propto \mathrm{sinc}^2\!\left(\frac{q h \, \Gamma \cos(\phi - \phi_0)}{2}\right)\]

The sinc² function creates zeros (nulls) at specific combinations of \(q\), \(h\), \(\Gamma\), and \(\phi\), giving XPCS its sensitivity to shear rate.

Configuring for Laminar Flow

Minimum required configuration:

data:
  file_path: "shear_data.h5"
  gap_distance: 500.0     # µm (e.g., 500 µm = 0.5 mm gap)
  q_value: 0.054          # Å⁻¹
  dt: 0.1                 # s (frame interval)

analysis:
  mode: "laminar_flow"

optimization:
  method: "nlsq"
  nlsq:
    anti_degeneracy:
      per_angle_mode: "auto"   # Critical!

parameter_space:
  D0:
    initial: 1.0
    bounds: [0.001, 1.0e5]
  alpha:
    initial: -0.5
    bounds: [-2.0, 1.0]
  D_offset:
    initial: 0.01
    bounds: [0.0, 1.0e3]
  gamma_dot_0:
    initial: 0.5          # Start near expected shear rate
    bounds: [1.0e-6, 1.0e4]
  beta:
    initial: 0.0
    bounds: [-2.0, 2.0]
  gamma_dot_t_offset:
    initial: 0.001
    bounds: [0.0, 1.0e3]
  phi_0:
    initial: 0.0
    bounds: [-180.0, 180.0]

See YAML Configuration Reference for the complete YAML schema.

Generate a laminar flow template:

homodyne-config --mode laminar_flow --output config_flow.yaml
# Edit the output file to set your q_value, gap_distance, and dt

Anti-Degeneracy System

The per_angle_mode: "auto" setting is the most important configuration choice for laminar flow analysis. Without it, the optimizer can find degenerate solutions where:

  • \(\dot\gamma_0\) becomes very large (unphysical)

  • Contrast drops to near-zero to compensate

  • \(\chi^2\) appears low but the result is physically wrong

How ``auto`` mode prevents degeneracy:

  1. Before optimization, quantile-based estimates of contrast and offset are computed for each angle separately

  2. These estimates are averaged across all angles

  3. The averaged contrast and averaged offset are added as 2 free parameters (total: 9 parameters for laminar_flow)

  4. The optimizer adjusts these 2 averaged values, preventing the physical parameters from absorbing them

Empirical validation:

The auto mode has been validated on synthetic data with known ground truth. It recovers:

  • \(D_0\) to within 2–5%

  • \(\dot\gamma_0\) to within 3–8%

  • \(\phi_0\) to within 1–3°

Without anti-degeneracy, \(D_0\) errors can exceed 50%.

Angular Dependence Visualization

Visualizing the angular dependence helps verify that the laminar flow model is appropriate and that the fit is capturing the physics:

import numpy as np
import matplotlib.pyplot as plt
from homodyne.data import load_xpcs_data

data = load_xpcs_data("config.yaml")
c2 = data['c2_exp']       # (n_phi, n_t1, n_t2)
phi = data['phi_angles_list']
t1 = data['t1']
t2 = data['t2']

# Choose a fixed lag time
lag = 5   # index into time axis
fig, ax = plt.subplots(figsize=(8, 5))

for i_phi, phi_val in enumerate(phi):
    # Extract one-lag diagonal of C2 for this angle
    n = min(c2.shape[1], c2.shape[2]) - lag
    c2_lag = np.array([c2[i_phi, k, k + lag] for k in range(n)])
    ax.plot(np.arange(n), c2_lag, label=f"{phi_val:.0f}°", alpha=0.7)

ax.set_xlabel("Frame index")
ax.set_ylabel(f"C2 at lag={lag}")
ax.set_title("Angular dependence of C2 (expect spread for laminar flow)")
ax.legend(ncol=3, fontsize=8)
plt.tight_layout()
plt.show()

Expected patterns:

  • No angular dependence (curves overlap) → use static mode instead

  • Systematic spread with \(\phi\) → laminar flow present; check \(\phi_0\)

  • Oscillatory pattern (sinc nulls) → strong shear, clear signal

Interpreting Shear Parameters

gamma_dot_0 (reference shear rate):

Compare to the applied shear rate from your rheometer:

applied_shear_rate = 10.0   # s⁻¹ (from rheometer setting)
fitted_shear_rate = params_dict['gamma_dot_0']
print(f"Applied:  {applied_shear_rate:.3f} s⁻¹")
print(f"Fitted:   {fitted_shear_rate:.3f} s⁻¹")
print(f"Ratio:    {fitted_shear_rate / applied_shear_rate:.2f}")

A ratio near 1.0 validates the measurement. Significant deviations may indicate slip at the walls or non-Newtonian flow profiles.

phi_0 (angular offset):

Should be reproducible across measurements with the same shear cell orientation. If it varies randomly, check:

  1. That the flow direction is consistent between experiments

  2. That the shear cell is properly aligned to the beam

beta (shear rate time dependence):

For steady Couette flow: \(\beta \approx 0\). Significant \(|\beta| > 0.2\) may indicate:

  • Transient startup behavior in the measured time window

  • Shear banding or wall slip at some timescales

  • Data collected during a flow ramp

Troubleshooting Laminar Flow Fits

Problem: gamma_dot_0 converges to near zero

Possible causes:

  1. The shear rate is too small to produce visible sinc oscillations (check: \(q h \dot\gamma \Delta t_\text{max} \ll 1\))

  2. phi_0 is far from correct — the sinc cos(phi - phi_0) factor cancels the angular dependence at all angles

Fix: provide a tighter initial estimate for phi_0 and gamma_dot_0.

# Estimate expected sinc argument at maximum lag time
q = 0.054       # Å⁻¹
h = 5000.0      # Å (0.5 µm gap)
gamma_dot = 1.0  # s⁻¹ (expected)
t_max = 10.0    # s (max lag)

sinc_arg = 0.5 * q * h * gamma_dot * t_max
print(f"Max sinc argument: {sinc_arg:.2f}")
# Values < 0.5 → weak shear signal; values 1-3 → clear oscillations

Problem: phi_0 oscillates between +90° and -90°

The cos factor in the sinc argument is periodic, so \(\phi_0\) and \(\phi_0 + 180°\) can give similar \(\chi^2\). This is a true degeneracy. Fix: constrain phi_0 to a smaller range based on known geometry:

parameter_space:
  phi_0:
    initial: 0.0
    bounds: [-90.0, 90.0]   # Constrained by geometry

Problem: Very slow convergence

Laminar flow has a more complex loss landscape than static mode. Options:

  1. Enable multi-start NLSQ with LHS sampling

  2. Enable CMA-ES for the first pass (see CMA-ES for Multi-Scale Problems)

  3. Increase NLSQ max iterations:

optimization:
  nlsq:
    max_nfev: 5000   # Increase from default 1000

Problem: Physical parameters unphysical (D0 >> expected)

Enable anti-degeneracy:

optimization:
  nlsq:
    anti_degeneracy:
      per_angle_mode: "auto"   # Was "constant" or missing

See Also