homodyne.optimization.nlsq — Adapter¶
NLSQAdapter is the recommended entry point for NLSQ optimisation in
Homodyne. It wraps the NLSQ package’s CurveFit class with:
Built-in JIT compilation (2–3× speedup for single fits)
Model instance caching via
WeakValueDictionary(3–5× speedup for multi-start)Automatic dataset-size and memory-aware workflow selection
Native NLSQ stability and recovery systems
Automatic fallback to
NLSQWrapperon failure
Note
For the simplest usage, call fit_nlsq_jax()
with use_adapter=True (default is False). NLSQAdapter is used
internally when enabled. Drop to NLSQAdapter directly only when you need
fine-grained control over adapter configuration.
When to Use NLSQAdapter vs NLSQWrapper¶
Feature |
NLSQAdapter |
NLSQWrapper |
|---|---|---|
Model caching |
Built-in |
None |
JIT compilation |
Automatic |
Manual |
Multi-start speedup |
3–5× |
1× |
Anti-degeneracy layers |
Via |
Full control |
Streaming (>100 M pts) |
Via NLSQ |
Full custom |
Recovery strategy |
NLSQ native |
3-attempt retry |
Recommended for |
Standard / multi-start |
Complex / large datasets |
fit_nlsq_jax¶
The primary entry point. Automatically selects NLSQAdapter (default) or
NLSQWrapper based on configuration.
- homodyne.optimization.nlsq.core.fit_nlsq_jax(data, config, initial_params=None, per_angle_scaling=True, use_adapter=False, _skip_global_selection=False)[source]
NLSQ trust-region nonlinear least squares optimization with per-angle scaling.
Uses NLSQ package (github.com/imewei/NLSQ) for trust-region optimization.
v2.11.0+: Experimental NLSQAdapter with CurveFit class available for improved JIT caching and automatic workflow selection. Set use_adapter=True to enable (default is False, uses NLSQWrapper).
Primary optimization method implementing the scaled optimization process: c₂(φ,t₁,t₂) = 1 + contrast × [c₁(φ,t₁,t₂)]²
- Parameters:
XPCS experimental data. Accepts two formats:
Format 1 (CLI/loader format): - ‘phi_angles_list’: phi angle array (mapped to ‘phi’) - ‘c2_exp’: experimental correlation data (n_phi, n_t1, n_t2) (mapped to ‘g2’) - ‘t1’: first delay time array - ‘t2’: second delay time array - ‘wavevector_q_list’: q-vector array (first element extracted as scalar ‘q’) - ‘sigma’: (optional) uncertainty array, defaults to 0.01 * ones_like(g2) - ‘L’: (optional) stator-rotor gap (rheology) or sample-detector distance (standard XPCS), defaults to config value or 2000000 Å (200 µm, typical rheology-XPCS gap) - ‘dt’: (optional) time step, defaults to config value or None
Format 2 (Direct format): - ‘phi’: phi angle array - ‘g2’: experimental correlation data (n_phi, n_t1, n_t2) - ‘t1’: first delay time array - ‘t2’: second delay time array - ‘q’: wavevector magnitude (scalar) - ‘sigma’: (optional) uncertainty array - ‘L’: (optional) stator-rotor gap or sample-detector distance [Å] - ‘dt’: (optional) time step [s]
config (
ConfigManager) – Configuration manager with optimization settingsinitial_params (
dict[str,float] |None) – Initial parameter guesses. If None, uses defaults from config.per_angle_scaling (
bool) – MUST be True. Per-angle contrast/offset parameters are physically correct as each scattering angle has different optical properties and detector responses. Legacy scalar mode (False) is no longer supported (removed Nov 2025).use_adapter (
bool) – EXPERIMENTAL (v2.11.0+): If True, use NLSQAdapter with NLSQ’s CurveFit class for improved JIT caching and automatic workflow selection. If False (default), use the stable NLSQWrapper implementation.
Notes
Global Optimization Selection (v2.15.0+): This function serves as the unified entry point for NLSQ optimization. When called, it first checks for global optimization methods:
If
cmaes.enable: true→ delegates tofit_nlsq_cmaes()If
multi_start.enable: true→ delegates tofit_nlsq_multistart()Otherwise → runs local trust-region optimization
The CMA-ES function will fall back to multi-start (if enabled) when the scale ratio is below the threshold, implementing the full fallback chain.
- Returns:
Optimization result with parameters, uncertainties, and diagnostics
- Return type:
OptimizationResult- Raises:
ImportError – If NLSQ package is not available
ValueError – If data validation fails
NLSQAdapter¶
- class homodyne.optimization.nlsq.adapter.NLSQAdapter[source]
Bases:
NLSQAdapterBaseAdapter for NLSQ package using CurveFit class.
Uses NLSQ’s CurveFit for JIT caching and WorkflowSelector for automatic strategy selection. This is the modern integration path for NLSQ v0.4+ with improved performance and reliability.
- Usage:
adapter = NLSQAdapter() result = adapter.fit(data, config, initial_params, bounds, analysis_mode)
- Compared to NLSQWrapper:
Uses CurveFit class for JIT compilation caching
Leverages WorkflowSelector for auto strategy selection
Delegates recovery to NLSQ’s built-in systems
Simpler codebase with less custom logic
Note
Anti-degeneracy layers (hierarchical, shear_weighting, etc.) remain in homodyne as they are physics-specific to XPCS analysis.
- __init__(config=None)[source]
Initialize NLSQAdapter.
- Parameters:
config (
AdapterConfig|None) – Adapter configuration. If None, uses defaults.- Raises:
ImportError – If NLSQ CurveFit class is not available.
- fit(data, config, initial_params=None, bounds=None, analysis_mode='static_isotropic', per_angle_scaling=True, diagnostics_enabled=False, shear_transforms=None, per_angle_scaling_initial=None, anti_degeneracy_controller=None)[source]
Execute NLSQ optimization using CurveFit class.
This method provides the same interface as NLSQWrapper.fit() for backward compatibility while using NLSQ’s modern CurveFit class.
- Parameters:
data (
Any) – XPCS experimental dataconfig (
Any) – Configuration manager with optimization settingsinitial_params (
ndarray|None) – Initial parameter guess (required)bounds (
tuple[ndarray,ndarray] |None) – Parameter bounds as (lower, upper) tupleanalysis_mode (
str) – ‘static_isotropic’ or ‘laminar_flow’per_angle_scaling (
bool) – Must be True (per-angle is physically correct)diagnostics_enabled (
bool) – Enable extended diagnosticsshear_transforms (
dict[str,Any] |None) – Shear parameter transformationsper_angle_scaling_initial (
dict[str,list[float]] |None) – Initial per-angle contrast/offsetanti_degeneracy_controller (
Any|None) – Anti-degeneracy controller (physics-specific)
- Return type:
OptimizationResult- Returns:
OptimizationResult with converged parameters and diagnostics
- Raises:
ValueError – If bounds are invalid or per_angle_scaling=False
ImportError – If NLSQ CurveFit is not available
- property workflow_available: bool
Check if NLSQ WorkflowSelector is available.
AdapterConfig¶
- class homodyne.optimization.nlsq.adapter.AdapterConfig[source]
Bases:
objectConfiguration for NLSQAdapter.
- enable_cache
Enable model instance caching (new in v2.11.0)
- enable_jit
Enable JIT compilation of model functions (new in v2.11.0)
- enable_recovery
Enable NLSQ’s built-in recovery system
- enable_stability
Enable NLSQ’s numerical stability guard
- goal
Optimization goal (fast, robust, quality, memory_efficient)
- workflow
Workflow tier override (auto, standard, streaming)
- enable_cache: bool = True
- enable_jit: bool = True
- enable_recovery: bool = True
- enable_stability: bool = True
- goal: str = 'quality'
- workflow: str = 'auto'
- __init__(enable_cache=True, enable_jit=True, enable_recovery=True, enable_stability=True, goal='quality', workflow='auto')
Usage Examples¶
Basic static mode¶
from homodyne.optimization.nlsq import fit_nlsq_jax
# Minimal usage — NLSQAdapter is used by default
result = fit_nlsq_jax(data, config, use_adapter=True)
print(f"D0 = {result.parameters[0]:.4g}")
print(f"alpha = {result.parameters[1]:.4f}")
print(f"D_offset= {result.parameters[2]:.4g}")
print(f"chi^2 = {result.chi_squared:.4f}")
Laminar flow with per-angle scaling¶
from homodyne.optimization.nlsq import fit_nlsq_jax
from homodyne.config.manager import ConfigManager
config = ConfigManager("flow_config.yaml").config
# Ensure anti-degeneracy layer is active
config["optimization"]["nlsq"]["anti_degeneracy"]["per_angle_mode"] = "auto"
result = fit_nlsq_jax(data, config, use_adapter=True)
# 9 parameters: 7 physical + 2 averaged scaling (auto mode, n_phi >= 3)
Direct NLSQAdapter usage¶
from homodyne.optimization.nlsq.adapter import NLSQAdapter, AdapterConfig
adapter_config = AdapterConfig(
enable_recovery=True,
enable_stability=True,
fallback_to_wrapper=True,
)
adapter = NLSQAdapter(config=adapter_config)
result = adapter.fit(
data=data,
config=config,
initial_params=initial_params,
bounds=bounds,
analysis_mode="laminar_flow",
)
Multi-start optimisation¶
from homodyne.optimization.nlsq import fit_nlsq_multistart
from homodyne.optimization.nlsq.multistart import MultiStartConfig
ms_config = MultiStartConfig(n_starts=8, strategy="latin_hypercube")
best_result = fit_nlsq_multistart(
data=data,
config=config,
multistart_config=ms_config,
)
CMA-ES for multi-scale problems¶
Enable CMA-ES when \(D_0 \sim 10^4\) and \(\dot\gamma_0 \sim 10^{-3}\) (scale ratio > \(10^6\)):
optimization:
nlsq:
cmaes:
enable: true
preset: "cmaes-global" # 200 generations
refine_with_nlsq: true
result = fit_nlsq_jax(data, config) # CMA-ES + NLSQ refinement
Return Type¶
- class homodyne.optimization.nlsq.results.OptimizationResult[source]
Bases:
objectComplete optimization result with fit quality metrics and diagnostics.
- parameters
Converged parameter values.
- Type:
np.ndarray
- uncertainties
Standard deviations from covariance matrix diagonal.
- Type:
np.ndarray
- covariance
Full parameter covariance matrix.
- Type:
np.ndarray
- chi_squared
Sum of squared residuals.
- Type:
- reduced_chi_squared
chi_squared / (n_data - n_params).
- Type:
- convergence_status
‘converged’, ‘max_iter’, or ‘failed’.
- Type:
- iterations
Number of optimization iterations.
- Type:
- execution_time
Wall-clock execution time in seconds.
- Type:
- quality_flag
‘good’, ‘marginal’, or ‘poor’.
- Type:
- stratification_diagnostics
Diagnostics for angle-stratified chunking.
- Type:
StratificationDiagnostics | None
- parameters: ndarray
- uncertainties: ndarray
- covariance: ndarray
- chi_squared: float
- reduced_chi_squared: float
- convergence_status: str
- iterations: int
- execution_time: float
- quality_flag: str = 'good'
- stratification_diagnostics: StratificationDiagnostics | None = None
- sigma_is_default: bool = False
- property success: bool
Return True if optimization converged (backward compatibility).
- property message: str
Return descriptive message about optimization outcome.
- __init__(parameters, uncertainties, covariance, chi_squared, reduced_chi_squared, convergence_status, iterations, execution_time, device_info, recovery_actions=<factory>, quality_flag='good', streaming_diagnostics=None, stratification_diagnostics=None, nlsq_diagnostics=None, sigma_is_default=False)
NLSQ Adapter using CurveFit class for homodyne optimization.
Role and When to Use (v2.11.0+)¶
NLSQAdapter (this module) is the recommended adapter for: - Standard optimizations (static_isotropic mode) - Small to medium datasets (< 10M points) - Multi-start optimization (model caching provides 3-5× speedup) - Performance-critical workflows requiring JIT compilation
Use NLSQWrapper instead for: - Complex optimizations requiring full anti-degeneracy integration - laminar_flow mode with many phi angles (> 6) - Large datasets (> 100M points) requiring streaming/chunking strategies - Custom transforms or advanced recovery mechanisms
Key Differences:
Model caching: NLSQAdapter=Built-in, NLSQWrapper=None
JIT compilation: NLSQAdapter=Auto, NLSQWrapper=Manual
Workflow auto-select: NLSQAdapter=Via NLSQ, NLSQWrapper=Custom
Anti-degeneracy layers: NLSQAdapter=Via fit(), NLSQWrapper=Full
Recovery system: NLSQAdapter=NLSQ native, NLSQWrapper=3-attempt
Streaming support: NLSQAdapter=Via NLSQ, NLSQWrapper=Full custom
Decision Guide:
If you need maximum speed for multi-start optimization: Use NLSQAdapter
If you need robust streaming for 100M+ points: Use NLSQWrapper
If you need full anti-degeneracy control: Use NLSQWrapper
Default recommendation for new code: Use NLSQAdapter (via use_adapter=True)
This module provides a modern adapter layer between homodyne’s optimization API and the NLSQ package’s CurveFit class, leveraging: - CurveFit class for JIT compilation caching - Model instance caching (WeakValueDictionary) for multi-start speedup - WorkflowSelector for automatic strategy selection - Built-in stability and recovery systems - Runtime fallback to NLSQWrapper on failure
This is the recommended integration path for NLSQ v0.4+ (homodyne v2.11.0+).
Key Features: - Model caching: 3-5× speedup for multi-start optimization - JIT compilation: 2-3× speedup for single fits - Automatic workflow selection based on dataset size and memory - Native NLSQ stability and recovery systems - Integration with homodyne’s anti-degeneracy defense system - Backward-compatible interface with NLSQWrapper.fit() - Automatic fallback to NLSQWrapper when adapter fails
Migration Guide: - Replace NLSQWrapper with NLSQAdapter - Set use_adapter=True in fit_nlsq_jax() (default in v2.11.0+) - Anti-degeneracy layers work unchanged
References: - NLSQ Package: https://github.com/imewei/NLSQ - Architecture: See CLAUDE.md for NLSQ integration details
- class homodyne.optimization.nlsq.adapter.ModelCacheKey[source]
Bases:
objectImmutable key for model cache lookup.
Hashable tuple of (analysis_mode, phi_angles_tuple, q, per_angle_scaling). NumPy arrays converted to tuples for hashability.
- analysis_mode
“static_isotropic” or “laminar_flow”
- phi_angles
Unique phi angles (sorted) as tuple
- q
Scattering wavevector magnitude
- per_angle_scaling
Whether per-angle contrast/offset is used
- analysis_mode: str
- q: float
- per_angle_scaling: bool
- __init__(analysis_mode, phi_angles, q, per_angle_scaling)
- class homodyne.optimization.nlsq.adapter.CachedModel[source]
Bases:
objectCached model instance with JIT-compiled prediction function.
Stored in dict with LRU eviction - oldest entries removed when cache is full.
- model
CombinedModel instance for computing g1/g2 values
- model_func
Model prediction function (NumPy-compatible wrapper)
- created_at
time.time() for diagnostics
- n_hits
Cache hit counter for monitoring
- model: Any
- created_at: float
- n_hits: int = 0
- __init__(model, model_func, created_at=<factory>, n_hits=0)
- homodyne.optimization.nlsq.adapter.get_or_create_model(analysis_mode, phi_angles, q, per_angle_scaling=True, config=None, enable_jit=True)[source]
Get cached model or create new one.
This function provides model instance caching to avoid redundant model creation during multi-start optimization. Expected 3-5× speedup.
Uses CombinedModel (not HomodyneModel) for simpler initialization. The model function closure captures the model and experimental setup.
- Parameters:
analysis_mode (
str) – ‘static_isotropic’ or ‘laminar_flow’phi_angles (
ndarray) – Unique phi angles in radiansq (
float) – Scattering wavevector magnitudeper_angle_scaling (
bool) – Whether per-angle contrast/offset is usedconfig (
dict[str,Any] |None) – Optional config dict for model initializationenable_jit (
bool) – Whether to JIT-compile the model function
- Returns:
model: CombinedModel instance (cached or newly created)
model_func: Prediction function (JIT-compiled if enable_jit=True)
cache_hit: True if model was retrieved from cache
- Return type:
- Raises:
ValueError – If analysis_mode is invalid, phi_angles is empty, or q <= 0
Example
>>> model, model_func, hit = get_or_create_model( ... "laminar_flow", ... np.array([0.0, 0.5, 1.0]), ... 0.001, ... ) >>> if hit: ... logger.debug("Model cache hit")
- homodyne.optimization.nlsq.adapter.clear_model_cache()[source]
Clear all cached models.
- Return type:
- Returns:
Number of models removed from cache
Notes
Useful for testing or when configuration changes require fresh models.
- homodyne.optimization.nlsq.adapter.get_cache_stats()[source]
Get cache statistics.
- homodyne.optimization.nlsq.adapter.get_adapter(config=None)[source]
Factory function to get NLSQAdapter instance.
- Parameters:
config (
AdapterConfig|None) – Adapter configuration- Return type:
NLSQAdapter- Returns:
NLSQAdapter instance
- Raises:
ImportError – If NLSQ CurveFit is not available