Source code for nlsq.core.adapters.curve_fit_adapter
"""Curve fitting adapter implementing OptimizerProtocol.
This module provides an adapter that wraps the core curve_fit functionality
behind the OptimizerProtocol interface, enabling dependency injection and
loose coupling in the NLSQ architecture.
"""
from collections.abc import Callable
from typing import TYPE_CHECKING, Any
import numpy as np
if TYPE_CHECKING:
from nlsq.caching.unified_cache import UnifiedCache
from nlsq.diagnostics.types import DiagnosticsConfig
from nlsq.stability.guard import NumericalStabilityGuard
[docs]
class CurveFitAdapter:
"""Adapter that provides curve fitting via protocol interface.
This adapter wraps the core curve_fit functionality and implements
CurveFitProtocol, allowing it to be used interchangeably with other
curve fitting implementations.
Parameters
----------
cache : UnifiedCache | None
Optional cache for JIT compilation and results.
stability_guard : NumericalStabilityGuard | None
Optional numerical stability guard.
diagnostics_config : DiagnosticsConfig | None
Optional diagnostics configuration.
Examples
--------
>>> adapter = CurveFitAdapter()
>>> popt, pcov = adapter.curve_fit(model, xdata, ydata, p0=[1.0, 0.1])
"""
__slots__ = (
"_cache",
"_diagnostics_config",
"_global_config",
"_stability_guard",
)
[docs]
def __init__(
self,
cache: "UnifiedCache | None" = None,
stability_guard: "NumericalStabilityGuard | None" = None,
diagnostics_config: "DiagnosticsConfig | None" = None,
) -> None:
"""Initialize the adapter with optional dependencies."""
self._cache = cache
self._stability_guard = stability_guard
self._diagnostics_config = diagnostics_config
self._global_config: Any = None
[docs]
def curve_fit(
self,
f: Callable[..., np.ndarray],
xdata: np.ndarray,
ydata: np.ndarray,
p0: np.ndarray | None = None,
sigma: np.ndarray | None = None,
bounds: tuple[np.ndarray, np.ndarray] | None = None,
**kwargs: Any,
) -> tuple[np.ndarray, np.ndarray] | Any:
"""Fit a function to data.
Delegates to the core curve_fit implementation while providing
a clean protocol-based interface.
Parameters
----------
f : Callable
Model function ``f(x, *params) -> y``.
xdata : np.ndarray
Independent variable data.
ydata : np.ndarray
Dependent variable data.
p0 : np.ndarray or None
Initial parameter guess.
sigma : np.ndarray or None
Uncertainty in ydata.
bounds : tuple or None
(lower, upper) bounds for parameters.
**kwargs : Any
Additional keyword arguments passed to curve_fit.
Returns
-------
tuple[np.ndarray, np.ndarray]
(popt, pcov) - optimal parameters and covariance matrix.
"""
# Import here to avoid circular dependency
from nlsq.core.minpack import curve_fit as _curve_fit
# Map diagnostics_config to the named parameter curve_fit accepts
if self._diagnostics_config is not None:
kwargs.setdefault("diagnostics_config", self._diagnostics_config)
return _curve_fit(
f,
xdata,
ydata,
p0=p0,
sigma=sigma,
bounds=bounds,
**kwargs,
)
[docs]
@staticmethod
def with_global_optimization(
global_config: Any | None = None,
) -> "CurveFitAdapter":
"""Create an adapter configured for global optimization.
Parameters
----------
global_config : GlobalOptimizationConfig | None
Configuration for global optimization.
Returns
-------
CurveFitAdapter
Adapter configured for global optimization.
"""
adapter = CurveFitAdapter()
if global_config is not None:
adapter._global_config = global_config
return adapter
# Note: Protocol conformance assertion moved to tests/core/adapters/test_curve_fit_adapter.py
# to avoid import-time overhead (~5-10ms per module load)