Choosing Your Model¶
Learning Objectives
By the end of this section you will understand:
How to decide between static and laminar_flow mode
Memory and performance implications of each mode
How to use quantitative criteria (angular dependence, chi-squared) for model selection
Performance benchmarks for each mode
—
Overview¶
Model selection in homodyne is primarily driven by physics: the model you choose should reflect the physical dynamics present in your experiment. Using an overly complex model (e.g., laminar_flow on equilibrium data) risks overfitting and spurious parameter values.
This section provides a systematic decision process.
—
Decision Guide¶
Step 1: Know your experimental setup
Answer these questions:
Is the sample in a shear cell (Couette, cone-plate)?
Is there applied flow or pressure-driven flow?
Does the scattering show an angular dependence?
If the answers are all “no” → use static mode.
If any answer is “yes” → consider laminar_flow mode.
Step 2: Inspect the raw C2 data
Before fitting, visually inspect the correlation data:
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']
# Plot diagonal (lag-time dependence) for each angle
fig, ax = plt.subplots()
n_t = c2.shape[1]
for i_phi, phi_val in enumerate(phi):
# Extract anti-diagonal (fixed lag time)
lag_idx = np.arange(n_t - 1)
g2_lag = [c2[i_phi, k, k+1] for k in lag_idx]
ax.plot(lag_idx, g2_lag, label=f"phi={phi_val:.0f}°")
ax.set_xlabel("Lag index")
ax.set_ylabel("C2")
ax.legend()
plt.show()
If all curves overlap → use static mode
If curves spread with \(\phi\) → use laminar_flow mode
Step 3: Quantitative criterion — angular variance test
import numpy as np
def angular_variance_ratio(c2, lag_index=1):
"""Ratio of angular variance to mean at given lag.
Values > 0.05 suggest significant angular dependence."""
c2_at_lag = c2[:, :-lag_index, lag_index:] # shape (n_phi, ...)
# Extract diagonal element at lag_index for each phi
vals = np.array([c2[i, 0, lag_index] for i in range(c2.shape[0])])
if vals.mean() == 0:
return 0.0
return vals.std() / vals.mean()
ratio = angular_variance_ratio(data['c2_exp'], lag_index=5)
print(f"Angular variance ratio: {ratio:.3f}")
if ratio > 0.05:
print("Significant angular dependence detected → consider laminar_flow mode")
else:
print("No angular dependence → use static mode")
Step 4: Compare model fits
If still uncertain, fit both modes and compare:
from homodyne.config import ConfigManager
from homodyne.data import load_xpcs_data
from homodyne.optimization.nlsq import fit_nlsq_jax
data = load_xpcs_data("config.yaml")
# Fit static mode
config_static = ConfigManager("config_static.yaml")
result_static = fit_nlsq_jax(data, config_static)
print(f"Static chi2_nu: {result_static.reduced_chi_squared:.3f}")
# Fit laminar flow mode
config_flow = ConfigManager("config_flow.yaml")
result_flow = fit_nlsq_jax(data, config_flow)
print(f"Flow chi2_nu: {result_flow.reduced_chi_squared:.3f}")
Use the Akaike Information Criterion (AIC) for model comparison:
def aic(chi_squared, n_params, n_data):
"""Akaike Information Criterion (lower is better)."""
return chi_squared + 2 * n_params
n_data = data['c2_exp'].size
# 3 params for static (+ 2 if per-angle auto), 7 for flow (+ 2 if auto)
aic_static = aic(result_static.chi_squared, n_params=5, n_data=n_data)
aic_flow = aic(result_flow.chi_squared, n_params=9, n_data=n_data)
print(f"AIC static: {aic_static:.1f}")
print(f"AIC flow: {aic_flow:.1f}")
print(f"Preferred model: {'flow' if aic_flow < aic_static else 'static'}")
—
Memory Considerations¶
The memory footprint scales with the number of data points and the model complexity:
Mode |
Parameters |
Jacobian columns |
Memory per million points |
|---|---|---|---|
|
3 |
3 |
~0.3 GB |
|
5 |
5 |
~0.5 GB |
|
9 |
9 |
~1.5 GB |
|
53 |
53 |
~9 GB |
Warning
individual per-angle mode with many angles creates very large Jacobians.
For datasets > 1M points with > 10 angles, prefer auto mode or use
streaming (enabled automatically when memory exceeds the threshold).
—
Performance Benchmarks¶
Approximate execution times on a 16-core CPU (JAX JIT-compiled):
Dataset |
Mode |
Points |
Time |
|---|---|---|---|
Small |
static (auto) |
100K |
~1 s |
Medium |
static (auto) |
1M |
~5 s |
Large |
static (auto) |
10M |
~30 s (streaming) |
Small |
laminar_flow (auto) |
100K |
~3 s |
Medium |
laminar_flow (auto) |
1M |
~15 s |
Large |
laminar_flow (auto) |
10M |
~90 s (streaming) |
Note
First-time execution includes JAX JIT compilation overhead (~10–60 s depending on model complexity). Subsequent calls with the same model structure are faster.
—
Anti-Degeneracy System¶
For laminar_flow mode, enabling the anti-degeneracy system is critical:
optimization:
nlsq:
anti_degeneracy:
per_angle_mode: "auto"
Without per-angle correction, the shear parameters (\(\dot\gamma_0\), \(\beta\)) can absorb the contrast and offset, producing physically meaningless but numerically low \(\chi^2\) fits.
The auto mode prevents this by:
Estimating per-angle contrast and offset from quantile analysis
Averaging them across angles to get single representative values
Optimizing these 2 averaged values alongside the 7 physical parameters
This adds only 2 parameters (total 9) while fully protecting against degeneracy.
—
See Also¶
Analysis Modes — Mode equations and parameters
NLSQ Fitting Guide — Running NLSQ fits
Interpreting Results — Comparing fit quality
Per-Angle Scaling Modes — Per-angle scaling options
Laminar Flow Analysis Guide — Complete laminar flow guide