homodyne.optimization.nlsq — Wrapper¶
NLSQWrapper is the stable fallback adapter for advanced NLSQ
optimisation. It is preferred over NLSQAdapter
when:
Datasets exceed 100 M points and require custom streaming/chunking strategies
laminar_flowmode uses many phi angles (> 6) where full anti-degeneracy control is requiredCustom parameter transforms or advanced 3-attempt recovery mechanisms are needed
Production stability is the primary concern
Note
For most new code, use fit_nlsq_jax()
with use_adapter=True (default). NLSQWrapper is invoked automatically
as a fallback if NLSQAdapter fails.
Key Differences from NLSQAdapter¶
Capability |
NLSQWrapper |
NLSQAdapter |
|---|---|---|
Model caching |
None (rebuild each fit) |
WeakValueDictionary cache |
JIT compilation |
Manual |
Automatic |
Anti-degeneracy control |
Full (all layers) |
Via |
Streaming (>100 M pts) |
Full custom chunking |
Via NLSQ package |
Recovery strategy |
3-attempt retry |
NLSQ native |
Per-angle stratification |
Angle-stratified chunking |
Automatic |
Out-of-Core Routing¶
When a dataset exceeds the memory threshold (default 75% of RAM), NLSQWrapper routes to an out-of-core accumulation solver that processes data in chunks. This path has a simplified Levenberg-Marquardt implementation that lacks anti-degeneracy layers, hierarchical optimization, shear weighting, and adaptive regularization.
Important
Effective parameter count pre-check: Before memory routing, the wrapper
queries the per_angle_mode and computes the effective parameter count.
For auto mode with n_phi ≥ 3, this is 9 (7 physical + 2 averaged scaling)
rather than the 53 expanded parameters. This prevents auto_averaged datasets
from being unnecessarily routed to the out-of-core path.
Without this pre-check, memory is overestimated by ~6× (53 vs 9 params), triggering out-of-core for datasets that fit comfortably in RAM.
Convergence criteria: The out-of-core solver uses multi-criteria convergence:
xtol = 1e-6: per-component maximum parameter changeftol = 1e-6: relative cost function change
Both must be satisfied simultaneously. This replaces the previous norm-based
criterion (||step||/||params|| < 1e-4) which was scale-sensitive and produced
false single-iteration convergence when large-magnitude parameters (e.g.,
D₀ ~ 19000) dominated the norm.
NLSQWrapper¶
- class homodyne.optimization.nlsq.wrapper.NLSQWrapper[source]
Bases:
NLSQAdapterBaseAdapter class for NLSQ package integration with homodyne optimization.
This class translates between homodyne’s optimization API and the NLSQ package’s curve_fit interface, handling: - Data format transformations - Parameter validation and bounds checking - Automatic strategy selection for large datasets - Hybrid error handling and recovery
- Usage:
wrapper = NLSQWrapper(enable_large_dataset=True) result = wrapper.fit(data, config, initial_params, bounds, analysis_mode)
- __init__(enable_large_dataset=True, enable_recovery=True, enable_numerical_validation=True, max_retries=2, fast_mode=False)[source]
Initialize NLSQWrapper.
- Parameters:
enable_large_dataset (
bool) – Use curve_fit_large for datasets >1M pointsenable_recovery (
bool) – Enable automatic error recovery strategiesenable_numerical_validation (
bool) – Enable NaN/Inf validation at 3 critical pointsmax_retries (
int) – Maximum retry attempts per batch (default: 2)fast_mode (
bool) – Disable non-essential checks for < 1% overhead (Task 5.5)
- 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)[source]
Execute NLSQ optimization with automatic strategy selection and per-angle scaling.
- Parameters:
data (
Any) – XPCS experimental dataconfig (
Any) – Configuration manager with optimization settingsinitial_params (
ndarray|None) – Initial parameter guess (auto-loaded if None)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 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.
- Return type:
OptimizationResult- Returns:
OptimizationResult with converged parameters and diagnostics
- Raises:
ValueError – If bounds are invalid (lower > upper) or if per_angle_scaling=False
NLSQConfig¶
The NLSQConfig dataclass controls all NLSQ optimisation settings.
It is populated from the YAML configuration file via NLSQConfig.from_yaml().
- class homodyne.optimization.nlsq.config.NLSQConfig[source]
Bases:
objectConfiguration for NLSQ (Nonlinear Least Squares) optimization.
This dataclass consolidates NLSQ settings that were previously scattered across wrapper.py, improving maintainability and testability.
- loss
Loss function for robust fitting. Options: “linear”, “soft_l1”, “huber”, “cauchy”, “arctan”. Default: “soft_l1”.
- Type:
- trust_region_scale
Scale factor for trust region. Default: 1.0.
- Type:
- max_iterations
Maximum number of optimization iterations. Default: 1000.
- Type:
- ftol
Function tolerance for convergence. Default: 1e-8.
- Type:
- xtol
Parameter tolerance for convergence. Default: 1e-8.
- Type:
- gtol
Gradient tolerance for convergence. Default: 1e-8.
- Type:
- x_scale
Parameter scaling. “jac” for Jacobian-based, list for manual. Default: “jac”.
- enable_diagnostics
Whether to compute diagnostics (Jacobian stats, etc.). Default: True.
- Type:
- enable_streaming
Whether to enable streaming optimizer for large datasets. Default: True.
- Type:
- streaming_chunk_size
Points per chunk for streaming optimizer. Default: 50000.
- Type:
- enable_stratified
Whether to enable stratified least squares. Default: True.
- Type:
- target_chunk_size
Target points per chunk for stratified optimization. Default: 100000.
- Type:
- enable_recovery
Whether to enable automatic error recovery. Default: True.
- Type:
- max_recovery_attempts
Maximum recovery attempts per strategy. Default: 3.
- Type:
- workflow: str = 'auto'
- goal: str = 'quality'
- loss: str = 'soft_l1'
- trust_region_scale: float = 1.0
- max_iterations: int = 1000
- ftol: float = 1e-08
- xtol: float = 1e-08
- gtol: float = 1e-08
- enable_diagnostics: bool = True
- enable_streaming: bool = True
- streaming_chunk_size: int = 50000
- enable_stratified: bool = True
- target_chunk_size: int = 100000
- enable_recovery: bool = True
- max_recovery_attempts: int = 3
- enable_progress_bar: bool = True
- verbose: int = 1
- log_iteration_interval: int = 10
- enable_hybrid_streaming: bool = True
- hybrid_normalize: bool = True
- hybrid_normalization_strategy: str = 'auto'
- hybrid_warmup_iterations: int = 200
- hybrid_max_warmup_iterations: int = 500
- hybrid_warmup_learning_rate: float = 0.001
- hybrid_gauss_newton_max_iterations: int = 100
- hybrid_gauss_newton_tol: float = 1e-08
- hybrid_chunk_size: int = 10000
- hybrid_trust_region_initial: float = 1.0
- hybrid_regularization_factor: float = 1e-10
- hybrid_enable_checkpoints: bool = True
- hybrid_checkpoint_frequency: int = 100
- hybrid_validate_numerics: bool = True
- hybrid_enable_warm_start_detection: bool = True
- hybrid_warm_start_threshold: float = 0.01
- hybrid_enable_adaptive_warmup_lr: bool = True
- hybrid_warmup_lr_refinement: float = 1e-06
- hybrid_warmup_lr_careful: float = 1e-05
- hybrid_enable_cost_guard: bool = True
- hybrid_cost_increase_tolerance: float = 0.05
- hybrid_enable_step_clipping: bool = True
- hybrid_max_warmup_step_size: float = 0.1
- enable_multi_start: bool = False
- multi_start_n_starts: int = 10
- multi_start_seed: int = 42
- multi_start_sampling_strategy: str = 'latin_hypercube'
- multi_start_n_workers: int = 0
- multi_start_use_screening: bool = True
- multi_start_screen_keep_fraction: float = 0.5
- multi_start_refine_top_k: int = 3
- multi_start_refinement_ftol: float = 1e-12
- multi_start_degeneracy_threshold: float = 0.1
- per_angle_mode: str = 'auto'
- fourier_order: int = 2
- fourier_auto_threshold: int = 6
- constant_scaling_threshold: int = 3
- enable_hierarchical: bool = True
- hierarchical_max_outer_iterations: int = 5
- hierarchical_outer_tolerance: float = 1e-06
- hierarchical_physical_max_iterations: int = 100
- hierarchical_per_angle_max_iterations: int = 50
- regularization_mode: str = 'relative'
- group_variance_lambda: float = 1.0
- regularization_target_cv: float = 0.1
- regularization_target_contribution: float = 0.1
- regularization_max_cv: float = 0.2
- regularization_auto_tune_lambda: bool = True
- enable_gradient_monitoring: bool = True
- gradient_ratio_threshold: float = 0.01
- gradient_consecutive_triggers: int = 5
- gradient_collapse_response: str = 'hierarchical'
- enable_cmaes: bool = False
- cmaes_preset: str = 'cmaes'
- cmaes_sigma: float = 0.5
- cmaes_sigma_warmstart: float = 0.05
- cmaes_warmstart_auto_skip: bool = True
- cmaes_warmstart_skip_threshold: float = 5.0
- cmaes_tol_fun: float = 1e-08
- cmaes_tol_x: float = 1e-08
- cmaes_restart_strategy: str = 'bipop'
- cmaes_max_restarts: int = 9
- cmaes_refine_with_nlsq: bool = True
- cmaes_auto_select: bool = True
- cmaes_scale_threshold: float = 1000.0
- cmaes_memory_limit_gb: float = 8.0
- cmaes_refinement_workflow: str = 'auto'
- cmaes_refinement_ftol: float = 1e-10
- cmaes_refinement_xtol: float = 1e-10
- cmaes_refinement_gtol: float = 1e-10
- cmaes_refinement_max_nfev: int = 500
- cmaes_refinement_loss: str = 'linear'
- cmaes_normalize: bool = True
- cmaes_normalization_epsilon: float = 1e-12
- enable_quality_validation: bool = True
- quality_reduced_chi_squared_threshold: float = 10.0
- quality_warn_on_max_restarts: bool = True
- quality_warn_on_bounds_hit: bool = True
- quality_warn_on_convergence_failure: bool = True
- quality_bounds_tolerance: float = 1e-09
- classmethod from_dict(config_dict)[source]
Create NLSQConfig from configuration dictionary.
- classmethod from_yaml(yaml_path)[source]
Create NLSQConfig from YAML configuration file (T099).
This is the recommended single entry point for loading NLSQ configuration. It reads the YAML file, extracts the optimization.nlsq section, and creates a validated NLSQConfig object.
- Parameters:
yaml_path (
str) – Path to YAML configuration file.- Returns:
Validated configuration object.
- Return type:
NLSQConfig- Raises:
FileNotFoundError – If the YAML file does not exist.
ValueError – If the YAML file is invalid or missing required sections.
Examples
>>> config = NLSQConfig.from_yaml("homodyne_config.yaml") >>> print(config.loss) soft_l1
- validate()[source]
Validate configuration values.
- is_valid()[source]
Check if configuration is valid.
- Returns:
True if configuration has no validation errors.
- Return type:
- to_dict()[source]
Convert configuration to dictionary.
- to_workflow_kwargs()[source]
Convert settings to kwargs for NLSQ’s curve_fit().
Maps NLSQConfig settings to NLSQ 0.6.4+ curve_fit() parameters. Note: Homodyne uses curve_fit() directly, not the fit() unified API.
Notes
NLSQ 0.6.3+ Changes: - Simplified to 3 workflows: “auto”, “auto_global”, “hpc” - Old presets (“streaming”, “standard”) were removed - WorkflowSelector was removed; use MemoryBudgetSelector instead - Homodyne uses its own select_nlsq_strategy() for memory selection
The ‘goal’ parameter can be passed to NLSQ’s fit() API but homodyne uses curve_fit() directly, so goal is handled internally.
Example
>>> config = NLSQConfig.from_dict(yaml_config) >>> kwargs = config.to_workflow_kwargs() >>> result = fitter.curve_fit(f, xdata, ydata, **kwargs)
- __init__(workflow='auto', goal='quality', loss='soft_l1', trust_region_scale=1.0, max_iterations=1000, ftol=1e-08, xtol=1e-08, gtol=1e-08, x_scale='jac', x_scale_map=None, enable_diagnostics=True, enable_streaming=True, streaming_chunk_size=50000, enable_stratified=True, target_chunk_size=100000, enable_recovery=True, max_recovery_attempts=3, enable_progress_bar=True, verbose=1, log_iteration_interval=10, enable_hybrid_streaming=True, hybrid_normalize=True, hybrid_normalization_strategy='auto', hybrid_warmup_iterations=200, hybrid_max_warmup_iterations=500, hybrid_warmup_learning_rate=0.001, hybrid_gauss_newton_max_iterations=100, hybrid_gauss_newton_tol=1e-08, hybrid_chunk_size=10000, hybrid_trust_region_initial=1.0, hybrid_regularization_factor=1e-10, hybrid_enable_checkpoints=True, hybrid_checkpoint_frequency=100, hybrid_validate_numerics=True, hybrid_enable_warm_start_detection=True, hybrid_warm_start_threshold=0.01, hybrid_enable_adaptive_warmup_lr=True, hybrid_warmup_lr_refinement=1e-06, hybrid_warmup_lr_careful=1e-05, hybrid_enable_cost_guard=True, hybrid_cost_increase_tolerance=0.05, hybrid_enable_step_clipping=True, hybrid_max_warmup_step_size=0.1, enable_multi_start=False, multi_start_n_starts=10, multi_start_seed=42, multi_start_sampling_strategy='latin_hypercube', multi_start_n_workers=0, multi_start_use_screening=True, multi_start_screen_keep_fraction=0.5, multi_start_refine_top_k=3, multi_start_refinement_ftol=1e-12, multi_start_degeneracy_threshold=0.1, per_angle_mode='auto', fourier_order=2, fourier_auto_threshold=6, constant_scaling_threshold=3, enable_hierarchical=True, hierarchical_max_outer_iterations=5, hierarchical_outer_tolerance=1e-06, hierarchical_physical_max_iterations=100, hierarchical_per_angle_max_iterations=50, regularization_mode='relative', group_variance_lambda=1.0, regularization_target_cv=0.1, regularization_target_contribution=0.1, regularization_max_cv=0.2, regularization_auto_tune_lambda=True, enable_gradient_monitoring=True, gradient_ratio_threshold=0.01, gradient_consecutive_triggers=5, gradient_collapse_response='hierarchical', enable_cmaes=False, cmaes_preset='cmaes', cmaes_max_generations=None, cmaes_popsize=None, cmaes_sigma=0.5, cmaes_sigma_warmstart=0.05, cmaes_warmstart_auto_skip=True, cmaes_warmstart_skip_threshold=5.0, cmaes_tol_fun=1e-08, cmaes_tol_x=1e-08, cmaes_restart_strategy='bipop', cmaes_max_restarts=9, cmaes_population_batch_size=None, cmaes_data_chunk_size=None, cmaes_refine_with_nlsq=True, cmaes_auto_select=True, cmaes_scale_threshold=1000.0, cmaes_memory_limit_gb=8.0, cmaes_refinement_workflow='auto', cmaes_refinement_ftol=1e-10, cmaes_refinement_xtol=1e-10, cmaes_refinement_gtol=1e-10, cmaes_refinement_max_nfev=500, cmaes_refinement_loss='linear', cmaes_normalize=True, cmaes_normalization_epsilon=1e-12, enable_quality_validation=True, quality_reduced_chi_squared_threshold=10.0, quality_warn_on_max_restarts=True, quality_warn_on_bounds_hit=True, quality_warn_on_convergence_failure=True, quality_bounds_tolerance=1e-09, _validation_errors=<factory>)
HybridRecoveryConfig¶
- class homodyne.optimization.nlsq.config.HybridRecoveryConfig[source]
Bases:
objectConfiguration for hybrid streaming optimizer recovery strategy.
T029: Implements 3-attempt recovery with progressively conservative settings.
When the hybrid streaming optimizer fails, it retries with: - Reduced learning rate (0.5× per retry) - Increased regularization (2× per retry) - Smaller trust region (0.5× per retry)
- max_retries
Maximum retry attempts. Default: 3.
- Type:
- lr_decay
Learning rate multiplier per retry. Default: 0.5.
- Type:
- lambda_growth
Regularization multiplier per retry. Default: 2.0.
- Type:
- trust_decay
Trust region multiplier per retry. Default: 0.5.
- Type:
- log_retries
Whether to log retry attempts. Default: True.
- Type:
- max_retries: int = 3
- lr_decay: float = 0.5
- lambda_growth: float = 2.0
- trust_decay: float = 0.5
- log_retries: bool = True
- get_retry_settings(attempt)[source]
Get settings for a specific retry attempt.
- __init__(max_retries=3, lr_decay=0.5, lambda_growth=2.0, trust_decay=0.5, log_retries=True)
Safe type conversion utilities¶
- homodyne.optimization.nlsq.config.safe_float(value, default)[source]
Convert value to float safely, returning default on failure.
- Parameters:
- Returns:
Converted float value or default.
- Return type:
Examples
>>> safe_float("3.14", 0.0) 3.14 >>> safe_float(None, 1.0) 1.0 >>> safe_float("invalid", 2.5) 2.5
- homodyne.optimization.nlsq.config.safe_int(value, default)[source]
Convert value to int safely, returning default on failure.
- Parameters:
- Returns:
Converted int value or default.
- Return type:
Examples
>>> safe_int("42", 0) 42 >>> safe_int(None, 10) 10 >>> safe_int("invalid", 5) 5
Usage Examples¶
Direct usage of NLSQWrapper¶
from homodyne.optimization.nlsq.wrapper import NLSQWrapper
from homodyne.core.fitting import ParameterSpace
from homodyne.config.manager import ConfigManager
config = ConfigManager("config.yaml").config
param_space = ParameterSpace.from_config(config)
wrapper = NLSQWrapper(config=config)
result = wrapper.fit(data=data, parameter_space=param_space)
print(result.parameters)
print(result.residuals)
Large dataset (>100 M points) with streaming¶
optimization:
nlsq:
memory_fraction: 0.75
# Or set explicit memory threshold:
# memory_threshold_gb: 48
from homodyne.optimization.nlsq import fit_nlsq_jax
# NLSQWrapper handles streaming automatically when memory threshold is exceeded
result = fit_nlsq_jax(data, config, use_adapter=False)
laminar_flow with many phi angles and full anti-degeneracy¶
from homodyne.optimization.nlsq.wrapper import NLSQWrapper
# NLSQWrapper applies all anti-degeneracy layers when n_phi > 6
wrapper = NLSQWrapper(config=config)
result = wrapper.fit(
data=data,
parameter_space=param_space,
analysis_mode="laminar_flow",
per_angle_mode="individual", # Full per-angle scaling
)
3-attempt error recovery¶
NLSQWrapper retries automatically on solver failure, applying progressively
conservative settings per retry:
Attempt 1: Original settings
Attempt 2: 0.5× learning rate, 2× regularisation, 0.5× trust region
Attempt 3: 0.25× learning rate, 4× regularisation, 0.25× trust region
from homodyne.optimization.nlsq.config import HybridRecoveryConfig
recovery = HybridRecoveryConfig(
max_retries=3,
lr_decay=0.5,
lambda_growth=2.0,
trust_decay=0.5,
)
print(recovery.get_retry_settings(attempt=2))
# {"lr_multiplier": 0.25, "lambda_multiplier": 4.0, "trust_multiplier": 0.25}
NLSQ Wrapper for Homodyne Optimization.
Role and When to Use (v2.11.0+)¶
NLSQWrapper (this module) is the stable fallback adapter 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 - Production stability where reliability is critical
Use NLSQAdapter instead 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
Key Differences:
Model caching: NLSQWrapper=None, NLSQAdapter=Built-in
JIT compilation: NLSQWrapper=Manual, NLSQAdapter=Auto
Workflow auto-select: NLSQWrapper=Custom, NLSQAdapter=Via NLSQ
Anti-degeneracy layers: NLSQWrapper=Full, NLSQAdapter=Via fit()
Recovery system: NLSQWrapper=3-attempt, NLSQAdapter=NLSQ native
Streaming support: NLSQWrapper=Full custom, NLSQAdapter=Via NLSQ
Decision Guide:
If you need robust streaming for 100M+ points: Use NLSQWrapper
If you need full anti-degeneracy control: Use NLSQWrapper
If you need maximum speed for multi-start optimization: Use NLSQAdapter
Default recommendation: NLSQAdapter with automatic fallback to NLSQWrapper
This module provides an adapter layer between homodyne’s optimization API and the NLSQ package’s trust-region nonlinear least squares interface.
The NLSQWrapper class implements the Adapter pattern to translate: - Homodyne’s multi-dimensional XPCS data → NLSQ’s flattened array format - Homodyne’s parameter bounds tuple → NLSQ’s (lower, upper) format - NLSQ’s (popt, pcov) output → Homodyne’s OptimizationResult dataclass
Key Features: - Automatic dataset size detection and strategy selection - Angle-stratified chunking for per-angle parameter compatibility (v2.2+) - Intelligent error recovery with 3-attempt retry strategy (T022-T024) - Actionable error diagnostics with 5 error categories - CPU-optimized execution through JAX - Progress logging and convergence diagnostics - Scientifically validated (7/7 validation tests passed, T036-T041) - Serves as fallback when NLSQAdapter fails
Per-Angle Scaling Fix (v2.2): - Fixes silent optimization failures with per-angle parameters on large datasets - Applies angle-stratified chunking when: per_angle_scaling=True AND n_points>100k - Ensures every NLSQ chunk contains all phi angles → gradients always well-defined - <1% performance overhead (0.15s for 3M points) - Reference: ultra-think-20251106-012247
Production Status: - Production-ready with comprehensive error recovery - Scientifically validated (100% test pass rate) - Parameter recovery accuracy: 2-14% on core parameters - Sub-linear performance scaling with dataset size - Per-angle scaling compatible with large datasets (v2.2+)
References: - NLSQ Package: https://github.com/imewei/NLSQ - Validation: See tests/validation/test_scientific_validation.py (T036-T041) - Documentation: See CHANGELOG.md and CLAUDE.md for detailed status
- homodyne.optimization.nlsq.wrapper.create_multistart_warmup_func(model_func, xdata, ydata, bounds=None, warmup_learning_rate=0.001, normalize=True, chunk_size=50_000)[source]
Create a warmup-only fit function for multi-start Phase 1 strategy.
This function creates a warmup_fit_func compatible with the multi-start optimization module Phase 1 strategy. It uses the L-BFGS warmup phase from the NLSQ AdaptiveHybridStreamingOptimizer to quickly explore the parameter space without full Gauss-Newton refinement.
- Parameters:
model_func (
Callable[...,ndarray]) – Model function with signature:func(x, *params) -> predictionsxdata (
ndarray) – Independent variable dataydata (
ndarray) – Dependent variable data (observations)bounds (
tuple[ndarray,ndarray] |None) – Parameter bounds as (lower, upper)warmup_learning_rate (
float) – L-BFGS line search scale for warmup phasenormalize (
bool) – Whether to use parameter normalization (recommended for scale imbalance)chunk_size (
int) – Points per chunk for streaming computation
- Returns:
warmup_fit_func – Function with signature: (data, initial_params, n_iterations) -> SingleStartResult Compatible with run_multistart_nlsq() warmup_fit_func parameter.
- Return type:
- Raises:
RuntimeError – If AdaptiveHybridStreamingOptimizer is not available (NLSQ < 0.3.2)
Examples
>>> from homodyne.optimization.nlsq.wrapper import create_multistart_warmup_func >>> from homodyne.optimization.nlsq.multistart import run_multistart_nlsq >>> >>> # Create warmup function >>> warmup_func = create_multistart_warmup_func( ... model_func=my_model, ... xdata=x_data, ... ydata=y_data, ... bounds=(lower, upper), ... ) >>> >>> # Use with multi-start >>> result = run_multistart_nlsq( ... data=my_data, ... bounds=bounds, ... config=config, ... single_fit_func=full_fit_func, ... warmup_fit_func=warmup_func, # For Phase 1 strategy ... )
Notes
This function integrates with the Phase 1 multi-start strategy which: 1. Runs parallel L-BFGS warmup from multiple starting points 2. Selects the best warmup result 3. Performs full Gauss-Newton refinement from the best starting point
This approach is memory-efficient for very large datasets (>100M points) and provides good exploration of the parameter space.
See also
homodyne.optimization.nlsq.multistart.run_multistart_nlsqMain multi-start function
homodyne.optimization.nlsq.multistart._run_phase1_strategyPhase 1 strategy implementation
Extracted Strategy Modules¶
The following modules were extracted from wrapper.py to improve
maintainability. NLSQWrapper delegates to these internally; they are not
part of the public API.
Fallback Chain¶
Strategy selection and fallback logic when the primary solver fails.
Fallback chain logic for NLSQ optimization strategy selection.
Extracted from wrapper.py to reduce file size and improve maintainability.
This module provides: - OptimizationStrategy enum for strategy selection - Strategy info retrieval for logging/diagnostics - NLSQ result normalization across different return formats - Fallback strategy chain (STREAMING -> CHUNKED -> LARGE -> STANDARD) - Optimization execution with automatic fallback
- class homodyne.optimization.nlsq.fallback_chain.OptimizationStrategy[source]
Bases:
EnumLocal optimization strategy enum for internal use.
Note: This replaces the deprecated selection.py OptimizationStrategy. For new code, use NLSQStrategy from memory.py instead.
- STANDARD = 'standard'
- LARGE = 'large'
- CHUNKED = 'chunked'
- STREAMING = 'streaming'
- homodyne.optimization.nlsq.fallback_chain.get_fallback_strategy(current_strategy)[source]
Get fallback strategy when current strategy fails.
Implements degradation chain: STREAMING -> CHUNKED -> LARGE -> STANDARD -> None
- Parameters:
current_strategy (
OptimizationStrategy) – Strategy that failed- Return type:
OptimizationStrategy|None- Returns:
Next strategy to try, or None if no fallback available
- homodyne.optimization.nlsq.fallback_chain.handle_nlsq_result(result, strategy)[source]
Normalize NLSQ return values to consistent format.
NLSQ v0.1.5 has inconsistent return types across different functions: - curve_fit: Returns tuple (popt, pcov) OR CurveFitResult object - curve_fit_large: Returns tuple (popt, pcov) OR OptimizeResult object - StreamingOptimizer.fit: Returns dict with ‘x’, ‘pcov’, ‘streaming_diagnostics’
This function normalizes all these formats to a consistent tuple: (popt, pcov, info)
- Parameters:
result (
Any) – Return value from NLSQ optimization callstrategy (
OptimizationStrategy) – Optimization strategy used (for logging/diagnostics)
- Returns:
- (popt, pcov, info) where:
popt: np.ndarray of optimized parameters
pcov: np.ndarray covariance matrix
info: dict with additional information (empty if not available)
- Return type:
- Raises:
TypeError – If result format is unrecognized
- homodyne.optimization.nlsq.fallback_chain.execute_optimization_with_fallback(strategy, wrapped_residual_fn, xdata, ydata, validated_params, nlsq_bounds, loss_name, x_scale_value, config, start_time, log, enable_recovery, execute_with_recovery_fn, fit_with_hybrid_streaming_fn, streaming_available, curve_fit_fn, curve_fit_large_fn, fast_mode=False)[source]
Execute optimization with strategy fallback.
Tries selected strategy first, then falls back to simpler strategies if needed. Returns (popt, pcov, info, recovery_actions, convergence_status).
Error Recovery¶
3-attempt error recovery with progressive conservative settings and 5-category error diagnosis.
Error recovery and diagnostics for NLSQ optimization.
Extracted from wrapper.py to reduce file size and improve maintainability.
This module provides: - Safe uncertainty extraction from covariance matrices - Automatic error recovery with retry strategies (T022-T024) - Error diagnosis with actionable recovery guidance
- homodyne.optimization.nlsq.recovery.safe_uncertainties_from_pcov(pcov, n_params)[source]
Extract uncertainties with diagonal regularization for singular pcov.
- Return type:
- homodyne.optimization.nlsq.recovery.execute_with_recovery(residual_fn, xdata, ydata, initial_params, bounds, strategy, log, loss_name, x_scale_value, handle_nlsq_result_fn, curve_fit_fn, curve_fit_large_fn)[source]
Execute optimization with automatic error recovery (T022-T024).
Implements intelligent retry strategies: - Attempt 1: Original parameters with selected strategy - Attempt 2: Perturbed parameters (+/-10%) - Attempt 3: Relaxed convergence tolerance - Final failure: Comprehensive diagnostics
- Parameters:
residual_fn (
Callable[[ndarray],ndarray]) – Residual functionxdata (
ndarray) – Data arraysydata (
ndarray) – Data arraysinitial_params (
ndarray) – Initial parameter guessbounds (
tuple[ndarray,ndarray] |None) – Parameter bounds tuplestrategy (
OptimizationStrategy) – Optimization strategy to uselog (
Logger|LoggerAdapter[Logger]) – Logger instanceloss_name (
str) – Loss function namex_scale_value (
float|str|ndarray) – Scaling value for parametershandle_nlsq_result_fn (
Callable) – Function to normalize NLSQ resultscurve_fit_fn (
Callable) – Standard curve_fit functioncurve_fit_large_fn (
Callable) – Large dataset curve_fit function
- Return type:
- Returns:
(popt, pcov, info, recovery_actions, convergence_status)
- homodyne.optimization.nlsq.recovery.diagnose_error(error, params, bounds, attempt)[source]
Diagnose optimization error and provide actionable recovery strategy (T023).
- Parameters:
- Return type:
- Returns:
Diagnostic dictionary with error analysis and recovery strategy
Stratified Least Squares¶
Primary fitting strategy using stratified JTJ accumulation via NLSQ
least_squares().
Stratified least-squares strategy for NLSQ optimization.
Extracted from wrapper.py to reduce file size and improve maintainability.
This module provides: - Stratified chunk creation from angle-stratified data - Stratified least-squares fitting with anti-degeneracy support - Supports fixed_constant, auto_averaged, individual, and fourier modes
- homodyne.optimization.nlsq.strategies.stratified_ls.create_stratified_chunks(stratified_data, target_chunk_size=100_000)[source]
Convert stratified flat arrays into chunks for StratifiedResidualFunction.
- homodyne.optimization.nlsq.strategies.stratified_ls.fit_with_stratified_least_squares(stratified_data, per_angle_scaling, physical_param_names, initial_params, bounds, log, target_chunk_size=100_000, anti_degeneracy_config=None, nlsq_config_dict=None)[source]
Fit using NLSQ’s least_squares() with stratified residual function.
This method solves the double-chunking problem by using NLSQ’s least_squares() function directly with a StratifiedResidualFunction. This gives us full control over chunking, ensuring angle completeness in each chunk for proper per-angle parameter gradients.
- Parameters:
stratified_data (
Any) – StratifiedData object with flat stratified arraysper_angle_scaling (
bool) – Whether per-angle parameters are enabledphysical_param_names (
list[str]) – List of physical parameter namesinitial_params (
ndarray) – Initial parameter guessbounds (
tuple[ndarray,ndarray] |None) – Parameter bounds (lower, upper) tuplelog (
Logger|LoggerAdapter[Logger]) – Logger instancetarget_chunk_size (
int) – Target size for each chunk (default: 100k points)anti_degeneracy_config (
dict|None) – Optional config dict for Anti-Degeneracy Defense Systemnlsq_config_dict (
dict|None) – Optional NLSQ convergence config dict
- Return type:
- Returns:
(popt, pcov, info) tuple
Hybrid Streaming¶
4-phase adaptive hybrid streaming optimizer for large datasets.
Hybrid streaming optimization strategy for NLSQ optimization.
Extracted from wrapper.py to reduce file size and improve maintainability.
This module provides: - Hybrid streaming optimizer (L-BFGS warmup + Gauss-Newton refinement) - Stratified hybrid streaming with anti-degeneracy defense - Memory estimation and streaming decision logic - Deprecated streaming optimizer stubs
- homodyne.optimization.nlsq.strategies.hybrid_streaming.fit_with_streaming_optimizer_deprecated(residual_fn, xdata, ydata, initial_params, bounds, logger, checkpoint_config=None)[source]
Fit using streaming optimizer for large datasets.
Deprecated since version 2.9.1: The old non-stratified StreamingOptimizer was removed in NLSQ 0.4.0. Use the stratified optimization path with per-angle scaling instead, which automatically uses AdaptiveHybridStreamingOptimizer for large datasets.
- Raises:
RuntimeError – Always - this code path is no longer supported
- Return type:
- homodyne.optimization.nlsq.strategies.hybrid_streaming.fit_with_hybrid_streaming_optimizer(residual_fn, xdata, ydata, initial_params, bounds, logger, nlsq_config=None, fast_mode=False)[source]
Fit using NLSQ AdaptiveHybridStreamingOptimizer for large datasets.
This method uses NLSQ’s four-phase hybrid optimizer to fix three key issues: 1. Shear-term weak gradients (scale imbalance) - via parameter normalization 2. Slow convergence - via L-BFGS warmup + Gauss-Newton refinement 3. Crude covariance - via exact J^T J accumulation + covariance transform
Four Phases: - Phase 0: Parameter normalization setup (bounds-based) - Phase 1: L-BFGS warmup with adaptive switching - Phase 2: Streaming Gauss-Newton with exact J^T J accumulation - Phase 3: Denormalization and covariance transform
- Parameters:
residual_fn (
Any) – Residual function (StratifiedResidualFunction or similar)xdata (
ndarray) – Independent variable data (flattened)ydata (
ndarray) – Dependent variable data (flattened)initial_params (
ndarray) – Initial parameter guessbounds (
tuple[ndarray,ndarray] |None) – Parameter bounds (lower, upper)logger (
Any) – Logger instancenlsq_config (
Any) – NLSQ configuration with hybrid streaming settings
- Return type:
- Returns:
popt (np.ndarray) – Optimized parameters
pcov (np.ndarray) – Covariance matrix (properly transformed to original space)
info (dict) – Optimization information including phase diagnostics
- Raises:
RuntimeError – If AdaptiveHybridStreamingOptimizer is not available
NLSQOptimizationError – If optimization fails
- homodyne.optimization.nlsq.strategies.hybrid_streaming.fit_with_streaming_optimizer_stratified_deprecated(stratified_data, per_angle_scaling, physical_param_names, initial_params, bounds, logger, streaming_config=None)[source]
Fit using NLSQ streaming optimizer for memory-constrained large datasets.
Deprecated since version 2.9.1: The old StreamingOptimizer was removed in NLSQ 0.4.0. This method now delegates to _fit_with_stratified_hybrid_streaming which uses AdaptiveHybridStreamingOptimizer.
- Parameters:
stratified_data (
Any) – StratifiedData object with flat stratified arraysper_angle_scaling (
bool) – Whether per-angle parameters are enabledphysical_param_names (
list[str]) – List of physical parameter namesinitial_params (
ndarray) – Initial parameter guessbounds (
tuple[ndarray,ndarray] |None) – Parameter bounds (lower, upper) tuplelogger (
Any) – Logger instancestreaming_config (
dict|None) – Optional config dict (converted to hybrid_config)
- Return type:
- Returns:
(popt, pcov, info) tuple
- Raises:
RuntimeError – If AdaptiveHybridStreamingOptimizer is not available
- homodyne.optimization.nlsq.strategies.hybrid_streaming.fit_with_stratified_hybrid_streaming(stratified_data, per_angle_scaling, physical_param_names, initial_params, bounds, logger, hybrid_config=None, anti_degeneracy_config=None)[source]
Fit using NLSQ AdaptiveHybridStreamingOptimizer for large datasets.
This method implements the 4-phase hybrid optimization from NLSQ >=0.3.2: - Phase 0: Parameter normalization setup (bounds-based) - Phase 1: L-BFGS warmup with adaptive switching - Phase 2: Streaming Gauss-Newton with exact J^T J accumulation - Phase 3: Denormalization and covariance transform
With Anti-Degeneracy Defense System v2.9.0 integration: - Layer 1: Fourier Reparameterization (reduces per-angle DoF) - Layer 2: Hierarchical Optimization (alternating stage fitting) - Layer 3: Adaptive CV-based Regularization (scales properly) - Layer 4: Gradient Collapse Detection (runtime monitoring)
Key improvements over basic StreamingOptimizer: 1. Shear-term weak gradients: Fixed via parameter normalization 2. Slow convergence: Fixed via L-BFGS warmup + Gauss-Newton refinement 3. Crude covariance: Fixed via exact J^T J accumulation 4. Structural degeneracy: Fixed via anti-degeneracy defense layers
- Parameters:
stratified_data (
Any) – StratifiedData object with flat stratified arraysper_angle_scaling (
bool) – Whether per-angle parameters are enabledphysical_param_names (
list[str]) – List of physical parameter namesinitial_params (
ndarray) – Initial parameter guessbounds (
tuple[ndarray,ndarray] |None) – Parameter bounds (lower, upper) tuplelogger (
Any) – Logger instancehybrid_config (
dict|None) – Optional config dict with keys: - normalize: Enable parameter normalization (default: True) - normalization_strategy: “bounds” or “scale” (default: “bounds”) - warmup_iterations: L-BFGS warmup iterations (default: 100) - max_warmup_iterations: Max L-BFGS iterations (default: 500) - warmup_learning_rate: L-BFGS line search scale (default: 0.001) - gauss_newton_max_iterations: GN iterations (default: 50) - gauss_newton_tol: Convergence tolerance (default: 1e-8) - chunk_size: Points per chunk for streaming (default: 50000)anti_degeneracy_config (
dict|None) – Optional config dict for Anti-Degeneracy Defense: - per_angle_mode: “independent”, “fourier”, or “auto” (default: “auto”) - fourier_order: Fourier harmonic order (default: 2) - fourier_auto_threshold: n_phi threshold for auto mode (default: 6) - hierarchical.enable: Enable hierarchical optimization (default: True) - regularization.mode: “absolute”, “relative”, or “auto” (default: “relative”) - regularization.lambda: Base regularization strength (default: 1.0) - gradient_monitoring.enable: Enable gradient collapse detection (default: True)
- Return type:
- Returns:
(popt, pcov, info) tuple
- Raises:
RuntimeError – If AdaptiveHybridStreamingOptimizer is not available
- homodyne.optimization.nlsq.strategies.hybrid_streaming.estimate_memory_for_stratified_ls(n_points, n_params, n_chunks)[source]
Estimate peak memory usage for stratified least-squares optimization.
The main memory consumers are: 1. Padded arrays: n_chunks × max_chunk_size × 5 arrays × 8 bytes 2. Dense Jacobian: n_points × n_params × 8 bytes 3. JAX autodiff intermediates: ~3× Jacobian size for backprop 4. JAX compilation cache: ~5-10 GB
- homodyne.optimization.nlsq.strategies.hybrid_streaming.should_use_streaming(n_points, n_params, n_chunks, memory_threshold_gb=None, memory_fraction=None)[source]
Determine if streaming optimizer should be used based on memory estimate.
Uses adaptive memory thresholding (v2.7.0+) to automatically compute an appropriate threshold based on total system memory.
- Parameters:
n_points (
int) – Total number of data pointsn_params (
int) – Number of parametersn_chunks (
int) – Number of stratified chunksmemory_threshold_gb (
float|None) – Memory threshold in GB above which to use streaming. If None (default), computes adaptive threshold as 75% of total memory.memory_fraction (
float|None) – Fraction of total memory for adaptive threshold (0.1-0.9). Only used if memory_threshold_gb is None.
- Return type:
- Returns:
(use_streaming, estimated_gb, reason) tuple
Out-of-Core Accumulation¶
Global JTJ accumulation solver for datasets exceeding the memory threshold.
Out-of-Core Global Accumulation strategy for NLSQ optimization.
Extracted from wrapper.py to reduce file size and improve maintainability.
This module provides: - Out-of-core J^T J / J^T r accumulation for massive datasets - Levenberg-Marquardt iteration with chunk-wise gradient accumulation - Parallel chunk computation with shared memory pools
- homodyne.optimization.nlsq.strategies.out_of_core.fit_with_out_of_core_accumulation(stratified_data, data, per_angle_scaling, physical_param_names, initial_params, bounds, log, config, fast_chi2_mode=False, anti_degeneracy_config=None)[source]
Fit using Out-of-Core Global Accumulation for massive datasets.
This strategy virtually chunks the dataset using Index-Based Stratification, accumulates the full Hessian and Gradient (J^T J, J^T r) by iterating over chunks, and takes a global Levenberg-Marquardt step.
Guarantees identical convergence to standard NLSQ but with minimal memory.
- Note (v2.14.1+):
This method now uses FULL homodyne physics via compute_g2_scaled(), identical to stratified least-squares. Anti-Degeneracy Defense System support is planned for a future release.
- Parameters:
stratified_data (
Any) – Stratified data object (unused, kept for API compat)data (
Any) – Original XPCS data object with .phi, .t1, .t2, .g2, .q, .Lper_angle_scaling (
bool) – Whether per-angle scaling is enabledphysical_param_names (
list[str]) – Names of physical parametersinitial_params (
ndarray) – Initial parameter guessbounds (
tuple[ndarray,ndarray] |None) – Parameter bounds (lower, upper) or Nonelog (
Logger|LoggerAdapter[Logger]) – Logger instanceconfig (
Any) – Configuration object or dictfast_chi2_mode (
bool) – If True, subsample chunks for chi2 evaluationanti_degeneracy_config (
dict|None) – Anti-degeneracy configuration (reserved)
- Return type:
- Returns:
(popt, pcov, info) tuple