CURVE_FIT
Overview
The CURVE_FIT function is an Excel-oriented wrapper around SciPy’s scipy.optimize.curve_fit, enabling nonlinear least-squares fits by selecting a named model and providing paired x/y observations. Model functions, default bounds, and heuristic initial guesses are sourced from the repository’s curve_fit_models.json, so users can choose among numerous prebuilt functional forms without writing Python code. The optimization minimizes the residual sum of squares for the selected model:
For more details on solver behavior, see the SciPy documentation . This example function is provided as-is without any representation of accuracy.
Usage
To use the function in Excel:
=CURVE_FIT(model_id, xdata, ydata, [initial_guess], [bounds], [curve_fit_method])model_id(string (enum), required): Identifier of the model to fit. Valid options:Allometric1,Allometric2,Asym2Sig,Asymptotic1,BET,BETMod,Belehradek,Beta,BiDoseResp,BiHill,Bigaussian,Bingham,Biphasic,BlNeld,BlNeldSmp,BoltzIV,Boltzmann,BoxLucas1,BoxLucas1Mod,BoxLucas2,Bradley,CCE,Carreau,Chapman,CompInhib,Constant,Cross,Cubic,Curve Fitting Functions,Dhyperbl,DoseResp,DoubleBoltzmann,ECS,Exp1P1,Exp1P3Md,Exp1P4,Exp1P4Md,Exp1p2,Exp1p2md,Exp1p3,Exp2P,Exp2PMod1,Exp2PMod2,Exp3P1,Exp3P1Md,Exp3P2,ExpAssoc,ExpDec1,ExpDec2,ExpDec3,ExpDecay1,ExpDecay2,ExpDecay3,ExpGro1,ExpGro2,ExpGro3,ExpGrow1,ExpGrow2,ExpGrow3Dec2,ExpGrowDec,ExpLinear,Exponential,ExponentialCDF,Extreme,FarazdaghiHarris,FreundlichEXT,GCAS,GammaCDF,Gauss,GaussAmp,GaussMod,Gaussian,GaussianLorentz,Giddings,Goldman,Gumbel,Gunary,Herschel,Hill,Hill1,HillBurk,Holliday,Holliday1,Hyperbl,HyperbolaGen,HyperbolaMod,InvsPoly,Langevin,LangevinMod,LangmuirEXT1,LangmuirEXT2,Laplace,Line,Line3,LineExp,LineMod,Log2P1,Log2P2,Log3P1,LogNormal,Logarithm,Logistic,Logistic5,Logistpk,LognormalCDF,Lorentz,MYEGA,MichaelisMenten,MixedModelInhib,MnMolecular,MnMolecular1,Nelder,NoncompInhib,OneSiteBind,OneSiteComp,PWL2,PWL3,Parabola,Pareto,Pareto2,Peak Analyzer Functions,PearsonIV,PearsonVII,Poisson,Poly,Poly4,Poly5,Pow2P1,Pow2P2,Pow2P3,Power,Power0,Power1,Power2,PsdVoigt1,PsdVoigt2,Pulse,Rational0,Rational1,Rational2,Rational3,Rational4,Rational5,Rayleigh,Reciprocal,Reciprocal0,Reciprocal1,ReciprocalMod,RectHyperbola,SGompertz,SLogistic1,SLogistic2,SLogistic3,SRichards1,SRichards2,SWeibull1,SWeibull2,SawtoothWave,Shah,Sine,SineDamp,SineSqr,SquareWave,SquareWaveMod,Step,Stirling,SubstrateInhib,TwoSiteBind,TwoSiteComp,UncompInhib,VFT,Voigt,Weibull,Weibull3,WeibullCDF,YldFert,YldFert1.xdata(2D list, required): Column vector with at least two rows containing the independent variable values. The first column is used; additional columns are ignored.ydata(2D list, required): Column vector with at least two rows containing observed dependent variable values aligned withxdata.initial_guess(2D list, optional): Single-row table specifying initial parameter values in the order expected by the selected model. If omitted, the wrapper computes a heuristic guess.bounds(2D list, optional): Two-row table where the first row contains parameter lower bounds and the second row contains upper bounds. Use large magnitudes for effectively unbounded parameters.curve_fit_method(string (enum), optional): Solver algorithm. Valid options:Levenberg-Marquardt (LM),Trust Region Reflective (TRF),Dogleg Box (DOGBOX).LMignores bounds;TRFandDOGBOXsupport bound constraints.
The function returns a 2D list where the first row contains parameter names and the second row contains the fitted parameter values (floats). If validation fails or SciPy reports an error, a descriptive error message string is returned instead.
Examples
Example 1: Allometric Power Law Fit
Inputs:
| xdata | ydata |
|---|---|
| 1.0 | 2.000 |
| 2.0 | 5.657 |
| 3.0 | 10.392 |
| 4.0 | 16.000 |
| 5.0 | 22.361 |
Excel formula:
=CURVE_FIT("Allometric1", {1;2;3;4;5}, {2;5.656854;10.392305;16;22.36068})Expected output:
| a | b |
|---|---|
| 2.000 | 1.500 |
The fitted curve recovers the original amplitude and exponent used to generate the synthetic data.
Example 2: Two-Phase Association with TRF Solver
Inputs:
| xdata | ydata | method |
|---|---|---|
| 0.0 | 0.000 | trf |
| 1.0 | 2.592 | |
| 2.0 | 4.512 | |
| 3.0 | 5.934 | |
| 4.0 | 6.988 | |
| 5.0 | 7.769 |
Excel formula:
=CURVE_FIT("ExpAssoc", {0;1;2;3;4;5}, {0;2.591818;4.511884;5.934303;6.988058;7.768698},,,"trf")Expected output:
| A | B |
|---|---|
| 10.000 | 0.300 |
Using the trust-region reflective solver reproduces the amplitude and rate constants for the exponential association model.
Example 3: Gaussian Peak with Custom Initial Guess
Inputs:
| xdata | ydata | initial_guess | ||
|---|---|---|---|---|
| -3.0 | 0.056 | 4.500 | 0.200 | 0.900 |
| -2.0 | 0.677 | |||
| -1.0 | 3.033 | |||
| 0.0 | 5.000 | |||
| 1.0 | 3.033 | |||
| 2.0 | 0.677 | |||
| 3.0 | 0.056 |
Excel formula:
=CURVE_FIT("Gauss", {-3;-2;-1;0;1;2;3}, {0.055545;0.676676;3.032653;5;3.032653;0.676676;0.055545}, {4.5,0.2,0.9})Expected output:
| A | x0 | sigma |
|---|---|---|
| 5.000 | 0.000 | 1.000 |
Supplying a reasonable initial guess helps the optimizer converge quickly to the true Gaussian parameters.
Example 4: Sine Wave with Bounds and DOGBOX Solver
Inputs:
| xdata | ydata | initial_guess | bounds | method | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| 0.0 | 0.000 | 1.500 | 1.000 | 0.000 | 0.000 | 0.000 | 0.500 | -0.500 | -0.500 | dogbox |
| 1.0 | 1.732 | 3.000 | 2.500 | 0.500 | 0.500 | |||||
| 2.0 | 1.732 | |||||||||
| 3.0 | 0.000 | |||||||||
| 4.0 | -1.732 | |||||||||
| 5.0 | -1.732 | |||||||||
| 6.0 | 0.000 |
Excel formula:
=CURVE_FIT("Sine", {0;1;2;3;4;5;6}, {0;1.732051;1.732051;0;-1.732051;-1.732051;0}, {1.5,1,0,0}, {0,0.5,-0.5,-0.5;3,2.5,0.5,0.5}, "dogbox")Expected output:
| A | omega | phi | y0 |
|---|---|---|---|
| 2.000 | 1.047 | 0.000 | 0.000 |
The bounded DOGBOX solve maintains amplitude and offset limits while recovering the sine wave’s true parameters.
Python Code
from typing import List, Union, Optional
from scipy.optimize import curve_fit as scipy_curve_fit
from scipy import special as sc
import math
import inspect
import numpy as np
import json
from urllib.request import urlopen, Request
from urllib.error import URLError, HTTPError
import os
Number = Union[float, int]
def curve_fit(model_id: str,
xdata: List[List[Number]],
ydata: List[List[Number]],
initial_guess: Optional[List[List[Number]]] = None,
bounds: Optional[List[List[Number]]] = None,
curve_fit_method: Optional[str] = None) -> Union[List[List[Union[str, float]]], str]:
"""
Fit a model specified by model_id to xdata, ydata using scipy.optimize.curve_fit.
Args:
model_id: String identifier for the model (e.g. "exp_gro_two", "exp_grow_two").
xdata: 2D list where each row is a sample; must have at least two rows.
ydata: 2D list matching xdata in length; each row contains the y value in its first element.
initial_guess: Optional 2D list representing initial guess for parameters.
bounds: Optional 2D list [[lower_list],[upper_list]] for parameters.
curve_fit_method: Optional method string passed to curve_fit (e.g., 'trf', 'dogbox', 'lm').
Returns:
2D list where first row is parameter names and second row are the fitted values, or an error string on failure.
This example function is provided as-is without any representation of accuracy.
"""
def _ensure_2d_list(arg: Union[List[List[Number]], object]) -> np.ndarray:
"""
Validate that arg is a 2D list with at least two rows and return a 1-D numpy
array (float64) of the first column values. This wrapper intentionally does
NOT support vector predictors (multi-column x rows) for now and will raise
on single-sample inputs.
"""
if not isinstance(arg, list) or len(arg) < 2:
raise ValueError("must be a 2D list with at least two rows")
vals = []
for i, row in enumerate(arg):
if not isinstance(row, list) or len(row) == 0:
raise ValueError(f"row {i} must be a non-empty list")
# We only accept the first element of each row as the scalar value
try:
vals.append(float(row[0]))
except Exception:
raise ValueError(f"row {i} contains non-numeric value")
arr = np.asarray(vals, dtype=np.float64)
return arr
# Persistent cache attached to the function object so it survives across calls
if not hasattr(curve_fit, "_cached_models"):
curve_fit._cached_models = None
def _load_models():
"""Load model definitions from JSON file and combine with function implementations."""
# Use the persistent cache on the function object
if curve_fit._cached_models is not None:
return curve_fit._cached_models
model_metadata = None
# Prefer a local JSON file (useful for tests / offline development). If not
# present, fall back to the remote URL used in production.
try:
local_path = os.path.join(os.path.dirname(__file__), "curve_fit_models.json")
if os.path.exists(local_path):
try:
with open(local_path, "r", encoding="utf-8") as fh:
model_metadata = json.load(fh)
except ValueError as exc:
raise RuntimeError(f"Failed to parse local curve fit models file {local_path}: {exc}") from exc
except NameError:
# __file__ is not defined (e.g., in Pyodide), skip local file loading
pass
if model_metadata is None:
json_url = "https://preview.python-functions.pages.dev/resources/solvers/optimization/curve_fitting/curve_fit/curve_fit_models.json"
try:
request = Request(json_url, headers={"User-Agent": "python-functions/curve-fit-loader"})
with urlopen(request) as response:
payload = response.read().decode("utf-8")
model_metadata = json.loads(payload)
except (HTTPError, URLError, ValueError) as exc:
raise RuntimeError(f"Failed to fetch curve fit models from {json_url}: {exc}") from exc
def normalize_bounds(bounds_value):
if not bounds_value:
return None
lower = bounds_value.get("lower")
upper = bounds_value.get("upper")
if lower is None or upper is None:
return None
return [list(lower), list(upper)]
# Build lookup from JSON metadata only. Each JSON entry should include
# 'model' and 'guess' as strings which we will eval below.
metadata_lookup = {}
for entry in model_metadata:
name = entry.get("name")
if not name:
continue
# Ensure keys exist; if model/guess/bounds are missing, keep them as None
entry.setdefault("model", None)
entry.setdefault("guess", None)
entry.setdefault("bounds", None)
metadata_lookup[name] = entry
eval_globals = {"np": np, "sc": sc, "math": math}
combined_models = {}
for name, metadata in metadata_lookup.items():
model_code = metadata.get("model")
guess_code = metadata.get("guess")
if not model_code or not guess_code:
continue
try:
model_callable = eval(model_code, eval_globals)
guess_callable = eval(guess_code, eval_globals)
except Exception as exc:
raise ValueError(f"Failed to evaluate model '{name}': {exc}") from exc
combined_models[name] = {
"model": model_callable,
"guess": guess_callable,
"bounds": normalize_bounds(metadata.get("bounds")),
"equation": metadata.get("equation", ""),
"source": metadata.get("source", ""),
"description": metadata.get("description", ""),
"category": metadata.get("category", ""),
"sample_image": metadata.get("sample_image", ""),
"model_source": model_code,
"guess_source": guess_code,
}
curve_fit._cached_models = combined_models
return curve_fit._cached_models
try:
available_models = _load_models()
except Exception as exc:
return f"Model loading error: {exc}"
if model_id not in available_models:
supported_models = sorted(available_models.keys())
preview = ", ".join(supported_models[:10])
if len(supported_models) > 10:
preview = f"{preview}, ..."
return f"Invalid model_id: {model_id}. Must be one of: {preview}"
model_info = available_models[model_id]
model_func = model_info["model"]
guess_func = model_info["guess"]
# Derive parameter names from the model function signature (skip the first arg, typically 'x')
try:
sig = inspect.signature(model_func)
# parameters preserves order; skip parameter 0 (x)
param_names = [p.name for i, p in enumerate(sig.parameters.values()) if i > 0]
except Exception:
return "Internal error: unable to inspect model function signature."
n_params = len(param_names)
try:
x_arr = _ensure_2d_list(xdata)
except ValueError as e:
return f"Invalid input for xdata: {e}"
try:
y_arr = _ensure_2d_list(ydata)
except ValueError as e:
return f"Invalid input for ydata: {e}"
if x_arr.shape[0] != y_arr.shape[0]:
return "Invalid input: xdata and ydata must have the same number of rows."
# Already ensured float64 in _ensure_2d_list
xa = x_arr
ya = y_arr
# Derive initial guess (p0) and normalize to a 1-D tuple of float64
if initial_guess is not None:
if not isinstance(initial_guess, list) or len(initial_guess) == 0 or not isinstance(initial_guess[0], list):
return f"Invalid input: initial_guess must be a 2D list (e.g. [[{','.join(param_names)}]])."
try:
p0_raw = initial_guess[0]
except Exception as e:
return f"Invalid initial_guess structure: {e}"
else:
# allow guess_func to return array-like or tuple/list
try:
p0_raw = guess_func(xa, ya)
except Exception as e:
return f"Initial guess generation error: {e}"
# Normalize p0 to a flat tuple of float64 and validate length
try:
p0_arr = np.asarray(p0_raw, dtype=np.float64).ravel()
p0 = tuple(float(x) for x in p0_arr.tolist())
except Exception as e:
return f"Invalid initial_guess values: {e}"
if len(p0) != n_params:
return f"Invalid input: initial_guess must contain {n_params} values."
# Bounds handling: prefer user-provided bounds, otherwise model-specific defaults if present
model_bounds = model_info.get('bounds') if isinstance(model_info, dict) else None
if bounds is None and model_bounds is not None:
bounds = model_bounds
# If bounds provided (either by user or model defaults), validate and normalize
if bounds is not None:
if not isinstance(bounds, list) or len(bounds) != 2 or not isinstance(bounds[0], list) or not isinstance(bounds[1], list):
return f"Invalid input: bounds must be a 2D list like [[lower1,...],[upper1,...]]."
try:
raw_lower = bounds[0]
raw_upper = bounds[1]
if not isinstance(raw_lower, list) or not isinstance(raw_upper, list):
return f"Invalid input: bounds must be a 2D list like [[lower1,...],[upper1,...]]."
if len(raw_lower) != n_params or len(raw_upper) != n_params:
return f"Invalid input: bounds must contain {n_params} values for lower and {n_params} for upper."
lower = []
upper = []
# Map None to -inf for lower and +inf for upper; otherwise convert to float
for v in raw_lower:
if v is None:
lower.append(-np.inf)
else:
lower.append(float(v))
for v in raw_upper:
if v is None:
upper.append(np.inf)
else:
upper.append(float(v))
except Exception as e:
return f"Invalid bounds values: {e}"
# Convert to numpy arrays (preserve original behavior otherwise)
bounds_tuple = (np.asarray(lower, dtype=np.float64), np.asarray(upper, dtype=np.float64))
else:
bounds_tuple = None
allowed_methods = {"lm", "trf", "dogbox"}
method_value = None
if curve_fit_method is not None:
if isinstance(curve_fit_method, str):
method_candidate = curve_fit_method.strip().lower()
else:
return "Invalid input: curve_fit_method must be one of 'lm', 'trf', or 'dogbox'."
if method_candidate not in allowed_methods:
return "Invalid input: curve_fit_method must be one of 'lm', 'trf', or 'dogbox'."
method_value = method_candidate
# method vs bounds validation: Levenberg-Marquardt (lm) cannot handle bounds
if method_value == 'lm' and bounds_tuple is not None:
return "Invalid input: curve_fit_method='lm' cannot be used with bounds; use 'trf' or 'dogbox' instead."
# With lm, number of observations (M) must be >= number of parameters (N)
if method_value == 'lm' and xa.shape[0] < n_params:
return f"Invalid input: curve_fit_method='lm' requires number of observations >= number of parameters ({xa.shape[0]} < {n_params})."
# Wrap the model to ensure outputs are float64 (SciPy recommends float64)
def _model(x, *params):
try:
return np.asarray(model_func(x, *params), dtype=np.float64)
except Exception as e:
# Return exception as a string via our interface
raise
try:
curve_fit_kwargs = dict(p0=p0)
if bounds_tuple is not None:
curve_fit_kwargs['bounds'] = bounds_tuple
if method_value is not None:
curve_fit_kwargs['method'] = method_value
curve_fit_kwargs['maxfev'] = 10000
popt, pcov = scipy_curve_fit(_model, xa, ya, **curve_fit_kwargs)
except Exception as e:
return f"curve_fit error: {e}"
try:
fitted_vals = [float(v) for v in popt]
except Exception as e:
return f"Result parsing error: {e}"
for v in fitted_vals:
if math.isnan(v) or math.isinf(v):
return "Fitting produced invalid numeric values (NaN or inf)."
return [param_names, fitted_vals]
Reference
This comprehensive reference guide covers the curve_fit function for non-linear least-squares curve fitting in Excel. It provides fundamentals of curve fitting and optimization, the mathematical frameworks underlying the implemented solvers, the Python library foundations, detailed practical implementation examples in Excel, advanced techniques and optimization strategies, best practices for effective use, troubleshooting guidance, and complete API documentation.
Fundamentals and Theory
What is Curve Fitting?
Curve fitting is the process of finding parameters of a mathematical model function that best explain observed data. Given observations for , curve fitting seeks to find parameters that minimize the objective function:
This is the least squares objective, where we minimize the sum of squared residuals. The residuals are .
Approaches to Curve Fitting
Linear least squares: The model is linear in its parameters. For example:
Linear models can be solved analytically using the normal equations:
Solutions are obtained via QR decomposition or SVD, which are numerically stable and efficient.
Non-linear least squares: The model is non-linear in its parameters. For example: (Michaelis-Menten)
(exponential decay)
Non-linear models require iterative optimization algorithms. SciPy’s curve_fit implements three primary algorithms:
-
Levenberg-Marquardt (LM): A blend of Gauss-Newton and steepest descent that adapts the search direction based on convergence. Works well on smooth problems but cannot handle bounds.
-
Trust-Region Reflective (TRF): A modern algorithm that maintains a region where a quadratic approximation is trusted. Handles bounds by reflecting steps off boundary constraints.
-
Dogleg with box constraints (DOGBOX): Combines dogleg steps (a simpler variant of trust-region) with box-constraint handling via reflections.
Key Concepts
Jacobian matrix: The matrix of partial derivatives of the residuals with respect to parameters:
The Jacobian guides the optimization direction in non-linear solvers.
Normal equations: For least squares, the gradient of the objective is:
Setting this to zero gives the normal equations (or in iterative methods, we move toward this gradient).
Parameter covariance: After fitting, parameter uncertainties are estimated as:
where is the residual variance (M observations, p parameters).
Condition number: A measure of problem sensitivity:
High condition numbers (e.g., ) indicate that small data perturbations cause large parameter changes. Remedy: rescale variables to similar magnitudes.
Convergence: Iterative solvers stop when:
- Gradient is near zero:
- Parameters change little:
- Objective does not improve: consecutive iterations give similar
- Maximum iterations reached
When to Use Non-linear Least Squares
Use curve fitting when:
- Your model is non-linear in parameters, so linearization is not possible or degrades precision.
- You need automatic parameter extraction and covariance estimation.
- You want to try multiple functional forms systematically.
- Bounds or constraints on parameters are necessary.
Avoid non-linear fitting if:
- Your model is linear in parameters (use LINEST in Excel instead).
- You have very few observations relative to parameters (overfitting risk).
- Your data contain extreme outliers or heteroscedasticity that violates least-squares assumptions.
Python Library Overview
SciPy and Least-Squares Optimization
SciPy (version 1.9+) provides scipy.optimize.curve_fit and the lower-level scipy.optimize.least_squares:
from scipy.optimize import curve_fit
import numpy as np
# Define a model function
def model(x, a, b):
return a * np.exp(-x / b)
# Data
xdata = np.array([1, 2, 3, 4, 5])
ydata = np.array([8.0, 6.0, 4.5, 3.4, 2.6])
# Fit with initial guess
popt, pcov = curve_fit(model, xdata, ydata, p0=[10, 2])
print(f"Fitted parameters: a={popt[0]:.3f}, b={popt[1]:.3f}")
print(f"Covariance:\n{pcov}")Key features:
- Automatic Jacobian estimation: SciPy uses finite differences to approximate the Jacobian if you do not provide an analytical one.
- Method selection: Choose ‘lm’, ‘trf’, or ‘dogbox’ via the
methodparameter. - Bounds: Provide bounds as
bounds=(lower, upper)lists (for ‘trf’ and ‘dogbox’ only). - Initial guess: Via
p0parameter (critical for convergence). - Covariance matrix:
pcovencodes parameter uncertainties. - Advanced options:
maxfev(max function evaluations),ftol(objective tolerance),xtol(step tolerance), etc.
SciPy Algorithm Details
Levenberg-Marquardt: Balances Gauss-Newton (fast, but unstable) and steepest descent (slow, but stable) using a damping parameter :
When the step reduces , decreases (favor Gauss-Newton). When step fails, increases (favor descent).
Trust-Region Reflective: Solves a local quadratic model within a trust region :
Bounds are handled by reflecting steps: if a parameter would exceed a bound, the solver reflects the step off the boundary. TRF is more robust to ill-conditioning than LM.
Dogleg with box constraints: A simplified trust-region approach combining a scaled steepest-descent step with a Gauss-Newton step. The dogleg path is piecewise linear and computationally cheaper than full trust-region solve. Box constraints (simple bounds) are handled via reflections.
Installation and Version
SciPy is a core scientific Python package. Install via:
pip install scipy>=1.9or in a Conda environment:
conda install -c conda-forge scipyThe curve_fit function is stable across SciPy versions; features used by our wrapper are available in all modern versions.
Available Functions and Parameters
Core SciPy interface:
scipy.optimize.curve_fit(f, xdata, ydata, p0=None, sigma=None, absolute_sigma=False,
check_finite=True, bounds=(-np.inf, np.inf), method=None,
jac=None, full_output=False, nan_policy='propagate', **kwargs)Returns popt, pcov where popt are optimal parameters and pcov is the covariance matrix.
The Excel wrapper simplifies this interface to Excel-compatible inputs and outputs (see API Reference section below).
Excel Capabilities
Design Philosophy
The curve_fit wrapper is optimized for Excel’s custom function environment:
- Scalar and 2D list inputs only: Excel custom functions cannot pass arbitrary Python objects. All inputs are scalars, strings, or rectangular 2D lists.
- No weighted regression: The wrapper omits the
sigmaparameter (used to specify measurement uncertainties and weights) because Excel cannot reliably pass 1-D arrays or covariance matrices. - No custom Jacobian: The
jacparameter is omitted; SciPy uses finite-difference Jacobian estimation. - Simplified return types: Returns either a 2D list (success) or a single error string (failure).
- Predefined models: Rather than requiring Excel users to provide Python lambda functions, the wrapper loads models from a JSON file. This allows Excel users to select from 200+ named models without writing code.
Array Handling in Excel
Excel passes data as rectangular 2D lists. The wrapper expects:
- xdata and ydata: 2D lists where each row is a sample. Only the first column of each row is used (scalar treatment). Additional columns are ignored.
- Minimum two rows: The solver requires at least two data points.
- initial_guess: A 2D list with one row containing initial parameter values in the same order as the model expects.
- bounds: A 2D list with two rows (lower and upper bounds). Use large magnitudes (e.g., -1e10 or 1e10) for effectively unbounded parameters, or None in a cell to denote unbounded.
Example Excel data layout:
A1: 1.0 B1: 2.5
A2: 2.0 B2: 5.8
A3: 3.0 B3: 10.1
A4: 4.0 B4: 15.9Pass as xdata = A1:A4 and ydata = B1:B4.
Performance Considerations
- Model loading: Models are cached after the first call, so subsequent calls incur minimal overhead.
- Finite-difference Jacobian: Estimating the Jacobian via finite differences requires function evaluations per iteration (where is the number of parameters). For problems with many parameters (>10), analytical Jacobian (not currently supported in the wrapper) would be faster.
- Convergence: Non-linear problems can require 100+ iterations. Set
curve_fit_method='lm'for problems where you expect fast convergence; use ‘trf’ or ‘dogbox’ for difficult or bounded problems. - Data type handling: All numeric inputs are converted to float64 internally for numerical stability.
Error Handling in Excel
The wrapper validates inputs and returns descriptive error messages:
- Invalid model_id: “Invalid model_id: {…}. Must be one of: {…}”
- Malformed xdata/ydata: “Invalid input for xdata: {reason}”
- Bounds shape mismatch: “Invalid input: bounds must contain {N} values…”
- Solver failure: “curve_fit error: {SciPy error message}”
- NaN/Inf in output: “Fitting produced invalid numeric values (NaN or inf).”
Practical Implementation
Step-by-Step Usage
-
Prepare data: Arrange your independent variable (x) in one column and dependent variable (y) in another. Ensure both have at least two rows.
-
Choose a model: Consult the Supported Models section below or use the model enumeration in the Excel function dialog to pick a model ID.
-
Optionally provide initial guess: If the default heuristic guess does not work, supply a 2D list with one row containing parameter values. Example:
[[a0, b0]]. -
Optionally set bounds: Provide a 2D list with two rows
[[lower_bounds], [upper_bounds]]. Use 0 or 1e-10 for “must be positive” constraints. -
Optionally choose solver method:
- ‘lm’ (default for unbounded problems): Fast, but cannot handle bounds.
- ‘trf’ (default for bounded problems): Robust, handles bounds, slightly slower.
- ‘dogbox’: Similar to TRF, sometimes converges faster on difficult problems.
-
Call the function: In Excel:
=CURVE_FIT("ModelID", xdata, ydata, [initial_guess], [bounds], [method]) -
Parse the result: The function returns either:
- A 2D list with two rows: row 1 = parameter names (strings), row 2 = fitted values (floats).
- An error message string if fitting failed.
Example 1: Simple Exponential Decay
Scenario: You have time-series data of radioactive decay and want to fit .
Data preparation:
| Time (hours) | Counts |
|---|---|
| 0 | 100 |
| 1 | 60 |
| 2 | 36 |
| 3 | 22 |
| 4 | 13 |
| 5 | 8 |
Excel layout: Time in A2:A7, Counts in B2:B7 (headers in row 1).
Excel formula:
=CURVE_FIT("ExpDec1", A2:A7, B2:B7)Output interpretation:
| Parameter | Value |
|---|---|
| y0 | 1.2 |
| A | 98.5 |
| t | 1.45 |
The fitted model is . The time constant hours means the signal decays to 37% (1/e) of its initial amplitude after 1.45 hours.
Advanced: To fit with bounds (force y0 >= 0 and A > 0):
=CURVE_FIT("ExpDec1", A2:A7, B2:B7, , [[0, 0, 0], [1e10, 1e10, 1e10]], "trf")Example 2: Michaelis-Menten Kinetics
Scenario: Enzyme kinetics data with substrate concentration and reaction velocity.
Data preparation:
| Substrate (µM) | Velocity (µmol/min) |
|---|---|
| 0.5 | 2.0 |
| 1.0 | 3.8 |
| 2.0 | 6.2 |
| 4.0 | 8.6 |
| 8.0 | 10.4 |
| 16.0 | 11.2 |
Model: (Michaelis-Menten)
Excel formula:
=CURVE_FIT("Hyperbl", A2:A7, B2:B7)This uses the “Hyperbl” (rectangular hyperbola) model with parameters P1 = and P2 = .
Output interpretation:
| Parameter | Value |
|---|---|
| P1 | 12.1 |
| P2 | 1.8 |
So µmol/min and µM. is the substrate concentration at half-maximal velocity.
Advanced insight: If you want to fit multiple datasets and compare kinetic constants, use initial guesses from previous fits to accelerate convergence:
=CURVE_FIT("Hyperbl", A2:A7, B2:B7, [[12, 2]], , "trf")Example 3: Gaussian Peak with Bounds
Scenario: Spectroscopic data with a peak you want to fit to a Gaussian.
Data preparation:
| Wavelength (nm) | Intensity |
|---|---|
| 400 | 5 |
| 410 | 15 |
| 420 | 45 |
| 430 | 80 |
| 440 | 95 |
| 450 | 72 |
| 460 | 32 |
| 470 | 10 |
| 480 | 2 |
Model: (Gauss, area parameterization)
Excel formula with bounds:
=CURVE_FIT("Gauss", A2:A10, B2:B10, [[0, 440, 20, 3000]], [[0, 400, 10, 1000], [100, 480, 50, 10000]], "trf")Here:
- Initial guess: y0=0, xc=440, w=20, A=3000.
- Bounds: y0 ∈ [0, 100], xc ∈ [400, 480], w ∈ [10, 50], A ∈ [1000, 10000].
Output interpretation:
| Parameter | Value |
|---|---|
| y0 | 4.5 |
| xc | 439.8 |
| w | 18.2 |
| A | 8450 |
The peak is centered at 439.8 nm with a width (standard deviation in x-space) of 18.2 nm and integrated area of 8450.
Example 4: Multi-Exponential Decay
Scenario: Fluorescence decay data with multiple exponential components (biexponential).
Data preparation:
| Time (ns) | Counts |
|---|---|
| 0.0 | 1000 |
| 0.5 | 750 |
| 1.0 | 562 |
| 1.5 | 420 |
| 2.0 | 315 |
| 3.0 | 177 |
| 4.0 | 100 |
| 5.0 | 56 |
Model: (ExpDec2)
Excel formula:
=CURVE_FIT("ExpDec2", A2:A9, B2:B9)Output interpretation:
| Parameter | Value |
|---|---|
| y0 | 2.0 |
| A1 | 600 |
| t1 | 0.8 |
| A2 | 400 |
| t2 | 3.5 |
The signal comprises two exponential decays: a fast component (A1=600 counts, τ1=0.8 ns) and a slow component (A2=400 counts, τ2=3.5 ns), plus a small baseline (y0=2).
Analysis: The relative contributions are A1/(A1+A2) ≈ 60% fast and 40% slow. This is typical for a protein with multiple fluorescent environment/conformations.
Example 5: Sine Wave with Constrained Phase
Scenario: Periodic oscillatory data where you want to fit a sinusoid with constraints.
Data preparation:
| Time (s) | Signal |
|---|---|
| 0.0 | -1.5 |
| 0.5 | 0.0 |
| 1.0 | 1.5 |
| 1.5 | 0.0 |
| 2.0 | -1.5 |
| 2.5 | 0.0 |
| 3.0 | 1.5 |
Model: (Sine)
Excel formula with initial guess and bounds:
=CURVE_FIT("Sine", A2:A8, B2:B8, [[2, 1, 0, 0]], [[-3, 0.5, -0.5, -3], [3, 2, 0.5, 3]], "dogbox")Initial guess: amplitude=2, period=1, phase=0, offset=0. Bounds constrain amplitude ∈ [-3, 3], period ∈ [0.5, 2], phase ∈ [-0.5, 0.5], offset ∈ [-3, 3].
Output interpretation:
| Parameter | Value |
|---|---|
| A | 1.5 |
| omega | 1.0 |
| phi | 0.0 |
| y0 | 0.0 |
The signal oscillates with amplitude 1.5, period 1.0 s, phase shift 0, and no offset. The fitted period matches the observed one (peaks at t = 0, 2, …).
Advanced Topics
Solver Selection and Convergence Diagnostics
Levenberg-Marquardt (LM):
- Pros: Fast on well-behaved problems; no bounds needed.
- Cons: Cannot handle bounds; may fail if Jacobian is rank-deficient.
- Use when: Problem is smooth, no bounds, and fast convergence desired.
- Failure modes: Singular design matrix, very non-linear models.
Trust-Region Reflective (TRF):
- Pros: Robust; handles bounds; more stable than LM on ill-conditioned problems.
- Cons: Slightly slower than LM; more iterations typically needed.
- Use when: Bounds are needed or problem is ill-conditioned.
- Failure modes: Rare; tends to converge even when LM fails.
Dogleg with box constraints (DOGBOX):
- Pros: Computationally cheaper than TRF; sometimes faster.
- Cons: Less robust than TRF on difficult problems.
- Use when: Speed matters and problem is moderately ill-conditioned.
- Failure modes: Similar to TRF but may stall on nearly-singular problems.
Convergence diagnostics:
If fitting fails, inspect:
- Residual sum of squares (RSS): Does the objective decrease over iterations?
- Parameter changes: Do parameters stabilize or oscillate?
- Gradient norm: Is the gradient near zero at the termination?
In practice, when fitting fails in Excel:
- Try a different initial guess (domain knowledge helps).
- Rescale variables to similar magnitudes (e.g., divide by their maximum).
- Use ‘trf’ or ‘dogbox’ if ‘lm’ fails.
- Check that your data are not inconsistent with the model (e.g., trying to fit negative data with an exponential model that requires y > 0).
Numerical Scaling and Conditioning
The problem: If parameters differ by many orders of magnitude (e.g., , ), the Jacobian becomes ill-conditioned, and optimization stalls.
Solution: Rescale parameters to order-1 magnitude. For example:
Original model: y = a * exp(-b*x) + c
Original params: a = 1e6, b = 1e-3, c = 1e4
Rescaled: y = (a') * 1e6 * exp(-(b')*1e-3*x) + (c')*1e4
Where a' = a / 1e6, b' = b / 1e-3, c' = c / 1e4
Now a', b', c' are all order-1.
Fit to rescaled params, then back-transform.In the Excel wrapper, you handle this by providing appropriately scaled initial guesses and bounds.
Parameter Identifiability
Definition: A parameter is identifiable if it can be uniquely determined from the data. When two or more parameters are highly correlated, they are weakly identifiable.
Symptoms:
- Very large parameter standard errors.
- Small changes in data cause large parameter changes.
- Fitted parameters differ greatly from initial guesses.
Example: Fitting with only a few noisy observations makes it hard to separate A and B if they are similar in magnitude.
Remedies:
- Collect more data or data with better signal-to-noise.
- Fix one parameter if its value is known (not supported in current wrapper; would require manual Solver setup).
- Use regularization (penalize large parameters; not supported in current wrapper).
- Reduce model complexity.
Weights and Heteroscedasticity
The standard least-squares objective treats all residuals equally. If measurements have different uncertainties, you should use weighted least squares:
where (inverse variance weight). The current wrapper does not expose the sigma parameter because Excel cannot reliably pass 1-D weight arrays.
Workaround: If you know the relative weights, you can reweight observations manually:
- Multiply and by .
- Fit the rescaled data.
- Back-transform parameters.
However, this is tedious in Excel; for production workflows with known measurement uncertainties, consider using a Python-based pipeline.
Outlier Resistance
Least-squares fitting is sensitive to outliers because the objective squares residuals. A single extreme outlier can dominate the fit.
Detection: After fitting, plot residuals vs. x and flag residuals > 3 standard deviations as potential outliers.
Handling:
- Inspect outliers for data entry errors or experimental anomalies.
- Refit after removing confirmed outliers.
- Use robust regression (e.g., Huber loss) if outliers are expected; not supported in current wrapper.
Best Practices
Scenario Decision Table
| Scenario | Approach | Rationale |
|---|---|---|
| Model with 2-3 parameters, smooth landscape | LM, default initial guess | Likely to converge quickly; default guess usually adequate. |
| Model with bounds (positivity, etc.) | TRF or DOGBOX with bounds | LM cannot handle bounds; TRF is robust. |
| Many parameters (>5) with uncertain initial guess | TRF with grid search over initial guesses | TRF is less sensitive to starting point; grid search improves odds. |
| Problem fails with LM, succeeds with TRF | Use TRF | TRF is more robust; LM may have numerical issues. |
| Need parameter uncertainties | Fit in Python with SciPy, export covariance | Covariance not returned by Excel wrapper; use external tool. |
| Very tight bounds, many constraints | External optimization tool (SciPy/LMFIT) | Excel wrapper supports simple bounds; for complex constraints, use Python. |
Validation and Sanity Checks
Before fitting:
- Plot scatter of y vs. x. Does the model shape look plausible?
- Check data ranges. Are there obvious outliers or errors?
- Ensure at least 2-3x as many observations as parameters.
After fitting:
- Plot fitted model overlaid on data. Visually assess goodness of fit.
- Compute residuals and plot vs. x. Check for systematic patterns (not ideal).
- Compare fitted parameter values to domain knowledge. Do they make sense?
- Try fitting with different initial guesses. Do you get the same answer?
- Compute sum of squared residuals. Is it small relative to data variance?
Numerical checks:
- Is the gradient near zero at the solution? (Solver should report this.)
- Are any fitted parameters at or near bounds? (May indicate a boundary solution.)
- Do parameters differ by many orders of magnitude? (Consider rescaling.)
Debugging Failed Fits
If fitting fails (error message returned), follow this checklist:
-
Check data format: xdata and ydata must be 2D lists with at least 2 rows.
/* This is correct: */ =CURVE_FIT("ModelID", A2:A10, B2:B10) /* This fails: */ =CURVE_FIT("ModelID", A2:A10, "not a range") -
Validate model ID: Use the enumeration dropdown to pick a valid model.
/* Check the const_curve_fit.py enums list for valid model names. */ -
Provide a reasonable initial guess: Use domain knowledge or a rough linear approximation.
=CURVE_FIT("Gauss", A2:A10, B2:B10, [[peak_height, center_x, width_estimate, area_estimate]]) -
Switch solver method: If LM fails, try TRF.
=CURVE_FIT("ModelID", A2:A10, B2:B10, , , "trf") -
Add bounds: If parameters should be positive or bounded, enforce it.
=CURVE_FIT("ModelID", A2:A10, B2:B10, , [[0, 0, 0], [100, 100, 100]], "trf") -
Rescale data: If variables span very different magnitudes, rescale.
/* E.g., divide large variables by their maximum value. */ -
Check model assumption: Does the model match your data? (E.g., exponential models require y > 0 or y_min > 0.)
-
Simplify the model: Fit a simpler model (fewer parameters) first, then build up.
Common Pitfalls
Invalid Model ID
Problem: Error message “Invalid model_id: …”.
Cause: Model name is misspelled or does not exist.
Solution: Use the enumeration dropdown to pick a valid model.
/* Wrong: */
=CURVE_FIT("exponentialdecay", ..., ...)
/* Correct: */
=CURVE_FIT("ExpDec1", ..., ...)Malformed Input Data
Problem: Error message “Invalid input for xdata: must be a 2D list with at least two rows”.
Cause: xdata or ydata is not a rectangular 2D range, has fewer than 2 rows, or contains non-numeric values.
Solution: Ensure both xdata and ydata are ranges like A2:A10 (at least 2 rows, 1+ columns). Excel cells with text, blanks, or errors will be flagged.
Example:
/* Wrong: */
=CURVE_FIT("ExpDec1", A2, B2:B10) /* A2 is a scalar, not a range */
/* Correct: */
=CURVE_FIT("ExpDec1", A2:A10, B2:B10) /* both are ranges with same # of rows */Shape Mismatch: xdata and ydata
Problem: Error message “Invalid input: xdata and ydata must have the same number of rows”.
Cause: xdata and ydata have different lengths.
Solution: Ensure both ranges have the same number of rows.
Example:
/* Wrong: */
=CURVE_FIT("ExpDec1", A2:A8, B2:B10) /* A has 7 rows, B has 9 */
/* Correct: */
=CURVE_FIT("ExpDec1", A2:A10, B2:B10) /* both have 9 rows */Incorrect Bounds Format
Problem: Error message “Invalid input: bounds must be a 2D list like [[lower1,…],[upper1,…]]”.
Cause: bounds is malformed (not a 2D list with exactly 2 rows) or has wrong shape.
Solution: bounds must be a 2D list with 2 rows and as many columns as there are parameters.
Example:
/* Model "Gauss" has 4 parameters: [y0, xc, w, A] */
/* Wrong: */
=CURVE_FIT("Gauss", ..., ..., , [0, 0, 0, 0; 100, 500, 50, 5000]) /* wrong delimiter */
/* Correct: */
=CURVE_FIT("Gauss", ..., ..., , [[0, 0, 0, 0], [100, 500, 50, 5000]]) /* 2 rows, 4 cols */LM Method with Bounds
Problem: Error message “Invalid input: curve_fit_method=‘lm’ cannot be used with bounds”.
Cause: You specified method=‘lm’ and also provided bounds. LM does not support bounds.
Solution: Omit bounds (if the problem allows) or switch to ‘trf’ or ‘dogbox’.
Example:
/* Wrong: */
=CURVE_FIT("ExpDec1", ..., ..., , [[0, 0, 0], [100, 100, 100]], "lm")
/* Correct (option 1): remove bounds */
=CURVE_FIT("ExpDec1", ..., ..., , , "lm")
/* Correct (option 2): use trf with bounds */
=CURVE_FIT("ExpDec1", ..., ..., , [[0, 0, 0], [100, 100, 100]], "trf")Insufficient Observations for LM
Problem: Error message “Invalid input: curve_fit_method=‘lm’ requires number of observations >= number of parameters”.
Cause: You have fewer data points than model parameters and selected LM. LM requires M >= p.
Solution: Either collect more data or use TRF/DOGBOX (which can work with M < p, though results may be unreliable).
Example:
/* Model has 4 parameters, but only 3 observations */
/* Wrong: */
=CURVE_FIT("Gauss", A2:A4, B2:B4, , , "lm") /* 3 rows < 4 params */
/* Correct: */
=CURVE_FIT("Gauss", A2:A4, B2:B4, , , "trf") /* TRF can attempt with 3 params */NaN or Inf in Fitted Parameters
Problem: Error message “Fitting produced invalid numeric values (NaN or inf)”.
Cause: The optimization produced non-finite parameter values, typically because:
- The objective function is unbounded (model -> inf or -inf for certain params).
- Bounds are inconsistent (lower > upper).
- Initial guess leads to division by zero or log of negative number in the model.
Solution:
- Check that bounds are sensible: lower[i] < upper[i] for all i.
- Provide a better initial guess away from singularities.
- Rescale variables to avoid extreme magnitudes.
- Verify that your model is appropriate for the data (e.g., exponential model with y <= 0).
Example:
/* Wrong: bounds are reversed */
=CURVE_FIT("ExpDec1", ..., ..., , [[100, 100, 100], [0, 0, 0]])
/* Correct: */
=CURVE_FIT("ExpDec1", ..., ..., , [[0, 0, 0], [100, 100, 100]])Model Error: Evaluation Fails
Problem: Error message “Initial guess generation error: …”.
Cause: The model’s initial guess heuristic failed (e.g., it tried to take log of negative data).
Solution: Provide an explicit initial guess that avoids problematic operations.
Example:
/* Model tries ln(y) for initial guess, but y contains 0 or negative values */
/* Wrong: */
=CURVE_FIT("LogNormal", A2:A10, B2:B10) /* B contains y <= 0 */
/* Correct: */
=CURVE_FIT("LogNormal", A2:A10, B2:B10, [[baseline, center, width, area]]) /* explicit guess */API Reference
Function Signature
curve_fit(model_id: str,
xdata: List[List[Number]],
ydata: List[List[Number]],
initial_guess: Optional[List[List[Number]]] = None,
bounds: Optional[List[List[Number]]] = None,
curve_fit_method: Optional[str] = None) -> Union[List[List[Union[str, float]]], str]Parameters
model_id (str, required)
- String identifier for a pre-registered model.
- Must match exactly (case-sensitive) one of the model names in
const_curve_fit.py. - Example: “ExpDec1”, “Gauss”, “Logistic”.
- See Supported Models section for complete list.
xdata (2D list, required)
- Independent variable data: a list of lists where each sublist is a sample row.
- Minimum 2 rows required. Additional columns are ignored; only the first element of each row is used.
- Example:
[[1.0], [2.0], [3.0], [4.0]]or passing Excel rangeA2:A5. - All numeric values are converted to float.
ydata (2D list, required)
- Dependent variable data: same format as xdata. Must have the same number of rows as xdata.
- Example:
[[2.5], [5.8], [10.1]].
initial_guess (2D list, optional)
- Initial guess for model parameters: a 2D list with exactly one row and as many columns as the model has parameters.
- Format:
[[p1_init, p2_init, ..., pN_init]]. - If omitted, the wrapper uses the model’s built-in heuristic (or raises an error if heuristic unavailable).
- Example for ExpDec1 (3 parameters):
[[0.0, 100.0, 1.0]].
bounds (2D list, optional)
- Parameter bounds: a 2D list with exactly 2 rows.
- Row 1: lower bounds. Row 2: upper bounds.
- Use
Nonein a cell to indicate unbounded (equivalent to -inf or +inf). - Format:
[[lower_1, lower_2, ..., lower_N], [upper_1, upper_2, ..., upper_N]]. - Number of bounds must match the number of parameters.
- Bounds are only used with ‘trf’ and ‘dogbox’ methods; LM ignores bounds.
- Example for 3 parameters:
[[0, 0, 0], [100, 500, 10]].
curve_fit_method (str, optional)
- Algorithm to use: one of “lm”, “trf”, or “dogbox”.
- If omitted, the wrapper chooses ‘lm’ if no bounds, or ‘trf’ if bounds provided.
- “lm”: Levenberg-Marquardt (fast, unbounded only).
- “trf”: Trust-region reflective (robust, supports bounds).
- “dogbox”: Dogleg with box constraints (cheaper than TRF, somewhat less robust).
- Case-insensitive (accepts “LM”, “Lm”, “lm”, etc.).
Return Value
On success: Returns a 2D list with 2 rows:
- Row 1: Parameter names (strings), in the order expected by the model.
- Row 2: Fitted parameter values (floats).
Example return: [["a", "b"], [2.5, 1.3]]
On failure: Returns a single string error message describing the problem.
Examples:
"Invalid model_id: ModelX. Must be one of: ...""Invalid input for xdata: must be a 2D list with at least two rows""curve_fit error: The number of observations (10) is less than the number of parameters (5).""Fitting produced invalid numeric values (NaN or inf)."
Supported Models (Brief List)
Over 200 models are available. Below is a representative subset:
Exponential decay:
- ExpDec1: (one phase).
- ExpDec2: (two phase).
- ExpDec3: Three-phase variant.
Exponential growth:
- ExpGrow1: (one phase with shift).
- ExpGrow2: Two-phase exponential growth.
Gaussian peaks:
- Gauss: Area parameterization of Gaussian.
- GaussAmp: Amplitude parameterization of Gaussian.
Lorentzian and Voigt:
- Lorentz: Lorentzian peak function.
- Voigt: Convolution of Gaussian and Lorentzian.
Saturation and hyperbola:
- Hyperbl: Michaelis-Menten / rectangular hyperbola.
- LogNormal: Log-normal distribution (peak function).
Sigmoid and dose-response:
- Boltzmann: Sigmoidal Boltzmann function.
- Logistic: Logistic dose-response curve.
Power law and rational:
- Allometric1: Power law .
- Rational0: Rational function .
Oscillatory:
- Sine: Sinusoidal waveform.
- SineDamp: Damped sine wave.
Polynomial:
- Line: Linear function.
- Parabola: Quadratic function.
- Cubic: Cubic polynomial.
See const_curve_fit.py for the complete enumeration.
Further Reading
Foundational texts
-
Press, W. H., Teukolsky, S. A., Vetterling, W. T., & Flannery, B. P. (2007). Numerical Recipes: The Art of Scientific Computing (3rd ed.). Cambridge University Press.
- Comprehensive coverage of least-squares fitting, non-linear optimization, and numerical stability. Available online at http://numerical.recipes/ .
-
Montgomery, D. C., Peck, E. A., & Vining, G. G. (2012). Introduction to Linear Regression Analysis (5th ed.). Wiley.
- Detailed treatment of regression theory, diagnostics, and model selection.
-
Bard, Y. (1974). Nonlinear Parameter Estimation. Academic Press.
- Classic reference on non-linear optimization algorithms.
Software documentation
-
SciPy
curve_fitdocumentation: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html- Official SciPy documentation with examples and parameter descriptions.
-
SciPy
least_squaresdocumentation: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.least_squares.html- Lower-level interface with detailed algorithm descriptions for LM, TRF, and DOGBOX.
-
LMFIT Python package: https://lmfit.github.io/lmfit-py/
- High-level wrapper around SciPy providing parameter constraints, bounds, and uncertainty quantification.
-
NumPy documentation: https://numpy.org/doc/
- Array operations and numerical utilities used throughout SciPy.
Practical guides and papers
-
Levenberg, K. (1944). “A method for the solution of certain non-linear problems in least squares.” Quarterly of Applied Mathematics, 2(2), 164-168.
- Original Levenberg-Marquardt paper.
-
Marquardt, D. W. (1963). “An algorithm for least-squares estimation of nonlinear parameters.” SIAM Journal on Applied Mathematics, 11(2), 431-441.
- Classical treatment of the Levenberg-Marquardt algorithm.
-
Nocedal, J., & Wright, S. J. (2006). Numerical Optimization (2nd ed.). Springer.
- Comprehensive modern textbook on optimization algorithms including trust-region methods.
-
Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations (4th ed.). Johns Hopkins University Press.
- Definitive reference on numerical linear algebra including SVD, QR, and condition numbers.
Online resources
-
Wikipedia: https://en.wikipedia.org/wiki/Levenberg%E2%80%93Marquardt_algorithm
- Accessible overview of the Levenberg-Marquardt algorithm.
-
Wikipedia: https://en.wikipedia.org/wiki/Trust_region
- Overview of trust-region methods.
-
OriginLab Curve Fitting Documentation: https://www.originlab.com/doc/Origin-Help/Curve-Fitting-Function
- Extensive catalog of fitting models and their parameters (our wrapper includes many OriginLab models).
-
Cross-validation (machine learning): https://en.wikipedia.org/wiki/Cross-validation_(statistics)
- Overview of cross-validation for model assessment.
-
Information criterion: https://en.wikipedia.org/wiki/Akaike_information_criterion
- Details on AIC, BIC, and model selection.