EXP_ADVANCED
Overview
The EXP_ADVANCED function fits a collection of specialized exponential models to data, covering scenarios beyond simple exponential growth and decay. These models address complex phenomena such as biphasic kinetics, asymptotic saturation, and combined exponential-linear behavior commonly encountered in scientific and engineering applications.
This function uses scipy.optimize.curve_fit, which applies non-linear least squares optimization via the Levenberg-Marquardt algorithm (for unconstrained problems) or Trust Region Reflective and dogbox methods when parameter bounds are specified. The objective is to minimize the sum of squared residuals:
\min_{\mathbf{p}} \sum_{i=1}^{N} \left[ y_i - f(x_i; \mathbf{p}) \right]^2
where \mathbf{p} represents the model parameters, f is the chosen model function, and (x_i, y_i) are the observed data points.
The function provides 12 specialized exponential models:
- Exp Biphasic Growth With Offset: Models two-phase exponential growth with different time constants, useful for analyzing processes with distinct fast and slow components
- Box-Lucas models: Three variants (single exponential rise, power form rise, dual exponential kinetics) commonly used in enzyme kinetics and first-order reaction analysis
- Exp Cumulative Distribution: Models cumulative probability distributions with exponential saturation
- Exp Decay Plus Linear Drift: Combines exponential decay with a linear trend, ideal for systems with baseline drift
- Exp Asymptotic Regression To Plateau: Models saturation behavior approaching a maximum value
- Exp Linear Combination Decay: Combines linear and exponential components for complex decay patterns
Each model includes intelligent initial parameter estimation and appropriate bounds constraints to improve convergence reliability. The function returns fitted parameter values along with standard errors derived from the covariance matrix, enabling assessment of parameter uncertainty.
For more details on the underlying optimization algorithm and its parameters, see the SciPy curve_fit documentation and the SciPy GitHub repository.
This example function is provided as-is without any representation of accuracy.
Excel Usage
=EXP_ADVANCED(xdata, ydata, exp_advanced_model)
xdata(list[list], required): The xdata valueydata(list[list], required): The ydata valueexp_advanced_model(str, required): The exp_advanced_model value
Returns (list[list]): 2D list [param_names, fitted_values, std_errors], or error string.
Examples
Example 1: Demo case 1
Inputs:
| exp_advanced_model | xdata | ydata |
|---|---|---|
| exp_biphasic_growth_with_offset | 0.01 | 5.699979664721567 |
| 2.0075 | 8.132120164735497 | |
| 4.005 | 12.40248069708657 | |
| 6.0024999999999995 | 18.633656252903883 | |
| 8 | 26.72439509164546 |
Excel formula:
=EXP_ADVANCED("exp_biphasic_growth_with_offset", {0.01;2.0075;4.005;6.0024999999999995;8}, {5.699979664721567;8.132120164735497;12.40248069708657;18.633656252903883;26.72439509164546})
Expected output:
"non-error"
Example 2: Demo case 2
Inputs:
| exp_advanced_model | xdata | ydata |
|---|---|---|
| exp_inverse_with_offset | 0.01 | 5.014542136272774 |
| 2.0075 | 3.680293286038805 | |
| 4.005 | 3.3296093952140464 | |
| 6.0024999999999995 | 3.120481628326833 | |
| 8 | 3.0773206406743387 |
Excel formula:
=EXP_ADVANCED("exp_inverse_with_offset", {0.01;2.0075;4.005;6.0024999999999995;8}, {5.014542136272774;3.680293286038805;3.3296093952140464;3.120481628326833;3.0773206406743387})
Expected output:
"non-error"
Example 3: Demo case 3
Inputs:
| exp_advanced_model | xdata | ydata |
|---|---|---|
| exp_inverse_logarithmic | 0.01 | 28.52386789289656 |
| 2.0075 | 20.934353854310647 | |
| 4.005 | 18.93958330453372 | |
| 6.0024999999999995 | 17.750016513923175 | |
| 8 | 17.504506898793743 |
Excel formula:
=EXP_ADVANCED("exp_inverse_logarithmic", {0.01;2.0075;4.005;6.0024999999999995;8}, {28.52386789289656;20.934353854310647;18.93958330453372;17.750016513923175;17.504506898793743})
Expected output:
"non-error"
Example 4: Demo case 4
Inputs:
| exp_advanced_model | xdata | ydata |
|---|---|---|
| exp_quadratic_exponent | 0.01 | 3.560074807769911e+51 |
| 2.0075 | 7.494152844382226e+51 | |
| 4.005 | 5.0028163670050735e+51 | |
| 6.0024999999999995 | 0.01 | |
| 8 | 3.068526146560006e+53 |
Excel formula:
=EXP_ADVANCED("exp_quadratic_exponent", {0.01;2.0075;4.005;6.0024999999999995;8}, {3.560074807769911e+51;7.494152844382226e+51;5.0028163670050735e+51;0.01;3.068526146560006e+53})
Expected output:
"non-error"
Example 5: Demo case 5
Inputs:
| exp_advanced_model | xdata | ydata |
|---|---|---|
| exp_growth_with_rate_constant | 0.01 | 9593161952972396 |
| 2.0075 | 0.01 | |
| 4.005 | 12508968857764500 | |
| 6.0024999999999995 | 29454821598338764 | |
| 8 | 961150013543773600 |
Excel formula:
=EXP_ADVANCED("exp_growth_with_rate_constant", {0.01;2.0075;4.005;6.0024999999999995;8}, {9593161952972396;0.01;12508968857764500;29454821598338764;961150013543773600})
Expected output:
"non-error"
Example 6: Demo case 6
Inputs:
| exp_advanced_model | xdata | ydata |
|---|---|---|
| exp_decay_plus_linear_drift | 0.01 | 10.460264085603878 |
| 2.0075 | 18.492374511242282 | |
| 4.005 | 27.977084672686463 | |
| 6.0024999999999995 | 37.88150687736539 | |
| 8 | 46.33512412887764 |
Excel formula:
=EXP_ADVANCED("exp_decay_plus_linear_drift", {0.01;2.0075;4.005;6.0024999999999995;8}, {10.460264085603878;18.492374511242282;27.977084672686463;37.88150687736539;46.33512412887764})
Expected output:
"non-error"
Example 7: Demo case 7
Inputs:
| exp_advanced_model | xdata | ydata |
|---|---|---|
| exp_cumulative_distribution | 0.01 | 0.024793645188306342 |
| 2.0075 | 0.8966642034004368 | |
| 4.005 | 1.530987568118774 | |
| 6.0024999999999995 | 1.9715729231929349 | |
| 8 | 2.1767957219513487 |
Excel formula:
=EXP_ADVANCED("exp_cumulative_distribution", {0.01;2.0075;4.005;6.0024999999999995;8}, {0.024793645188306342;0.8966642034004368;1.530987568118774;1.9715729231929349;2.1767957219513487})
Expected output:
"non-error"
Example 8: Demo case 8
Inputs:
| exp_advanced_model | xdata | ydata |
|---|---|---|
| box_lucas_single_exp_rise | 0.01 | 0.05499377640414215 |
| 2.0075 | 2.4085739372684514 | |
| 4.005 | 2.7432325546768888 | |
| 6.0024999999999995 | 2.8255122469035165 | |
| 8 | 2.736997888970465 |
Excel formula:
=EXP_ADVANCED("box_lucas_single_exp_rise", {0.01;2.0075;4.005;6.0024999999999995;8}, {0.05499377640414215;2.4085739372684514;2.7432325546768888;2.8255122469035165;2.736997888970465})
Expected output:
"non-error"
Example 9: Demo case 9
Inputs:
| exp_advanced_model | xdata | ydata |
|---|---|---|
| box_lucas_power_form_rise | 0.1 | 0.18179054798488892 |
| 1.3250000000000002 | 1.4984069409953142 | |
| 2.5500000000000003 | 2.1801852782144744 | |
| 3.7750000000000004 | 2.5302062531272407 | |
| 5 | 2.6011300758846088 |
Excel formula:
=EXP_ADVANCED("box_lucas_power_form_rise", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.18179054798488892;1.4984069409953142;2.1801852782144744;2.5302062531272407;2.6011300758846088})
Expected output:
"non-error"
Example 10: Demo case 10
Inputs:
| exp_advanced_model | xdata | ydata |
|---|---|---|
| box_lucas_dual_exp_kinetics | 0.01 | 1 |
| 2.0075 | 1 | |
| 4.005 | 1 | |
| 6.0024999999999995 | 1 | |
| 8 | 1 |
Excel formula:
=EXP_ADVANCED("box_lucas_dual_exp_kinetics", {0.01;2.0075;4.005;6.0024999999999995;8}, {1;1;1;1;1})
Expected output:
"non-error"
Example 11: Demo case 11
Inputs:
| exp_advanced_model | xdata | ydata |
|---|---|---|
| exp_asymptotic_regression_to_plateau | 0.1 | 1.8109614104377125 |
| 1.3250000000000002 | 0.9067827945829691 | |
| 2.5500000000000003 | -1.3837239064319469 | |
| 3.7750000000000004 | -6.166974468821636 | |
| 5 | -14.362989128326044 |
Excel formula:
=EXP_ADVANCED("exp_asymptotic_regression_to_plateau", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {1.8109614104377125;0.9067827945829691;-1.3837239064319469;-6.166974468821636;-14.362989128326044})
Expected output:
"non-error"
Example 12: Demo case 12
Inputs:
| exp_advanced_model | xdata | ydata |
|---|---|---|
| exp_linear_combination_decay | 0.1 | 4.541723810211957 |
| 1.3250000000000002 | 5.014365441261504 | |
| 2.5500000000000003 | 5.862325572203808 | |
| 3.7750000000000004 | 6.844633846489593 | |
| 5 | 8.115043109097572 |
Excel formula:
=EXP_ADVANCED("exp_linear_combination_decay", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {4.541723810211957;5.014365441261504;5.862325572203808;6.844633846489593;8.115043109097572})
Expected output:
"non-error"
Python Code
import numpy as np
from scipy.optimize import curve_fit as scipy_curve_fit
import math
def exp_advanced(xdata, ydata, exp_advanced_model):
"""
Fits exp_advanced models to data using scipy.optimize.curve_fit. See https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html for details.
See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html
This example function is provided as-is without any representation of accuracy.
Args:
xdata (list[list]): The xdata value
ydata (list[list]): The ydata value
exp_advanced_model (str): The exp_advanced_model value Valid options: Exp Biphasic Growth With Offset, Exp Inverse With Offset, Exp Inverse Logarithmic, Exp Quadratic Exponent, Exp Growth With Rate Constant, Exp Decay Plus Linear Drift, Exp Cumulative Distribution, Box Lucas Single Exp Rise, Box Lucas Power Form Rise, Box Lucas Dual Exp Kinetics, Exp Asymptotic Regression To Plateau, Exp Linear Combination Decay.
Returns:
list[list]: 2D list [param_names, fitted_values, std_errors], or error string.
"""
def _validate_data(xdata, ydata):
"""Validate and convert both xdata and ydata to numpy arrays."""
for name, arg in [("xdata", xdata), ("ydata", ydata)]:
if not isinstance(arg, list) or len(arg) < 2:
raise ValueError(f"{name}: 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"{name} row {i}: must be a non-empty list")
try:
vals.append(float(row[0]))
except Exception:
raise ValueError(f"{name} row {i}: non-numeric value")
if name == "xdata":
x_arr = np.asarray(vals, dtype=np.float64)
else:
y_arr = np.asarray(vals, dtype=np.float64)
if x_arr.shape[0] != y_arr.shape[0]:
raise ValueError("xdata and ydata must have the same number of rows")
return x_arr, y_arr
# Model definitions dictionary
models = {
'exp_biphasic_growth_with_offset': {
'params': ['y0', 'x0', 'A1', 't1', 'A2', 't2'],
'model': lambda x, y0, x0, A1, t1, A2, t2: y0 + A1 * np.exp((x - x0) / t1) + A2 * np.exp((x - x0) / t2),
'guess': lambda xa, ya: (float(min(ya)), float(np.min(xa)), float(np.ptp(ya) if np.ptp(ya) else 1.0), 1.0, float(np.ptp(ya) if np.ptp(ya) else 0.5), 5.0),
'bounds': ([-np.inf, -np.inf, -np.inf, 0.0, -np.inf, 0.0], np.inf),
},
'exp_inverse_with_offset': {
'params': ['a', 'b', 'c'],
'model': lambda x, a, b, c: a * np.exp(b / (x + c + 1e-9)),
'guess': lambda xa, ya: (float(np.max(ya) if np.max(ya) else 1.0), 1.0, 1.0),
'bounds': ([0.0, -np.inf, -np.inf], np.inf),
},
'exp_inverse_logarithmic': {
'params': ['a', 'b', 'c'],
'model': lambda x, a, b, c: np.exp(a + b / (x + c + 1e-9)),
'guess': lambda xa, ya: (0.0, 1.0, 1.0),
},
'exp_quadratic_exponent': {
'params': ['a', 'b', 'c'],
'model': lambda x, a, b, c: np.exp(a + b * x + c * np.square(x)),
'guess': lambda xa, ya: (0.0, 0.0, 0.0),
},
'exp_growth_with_rate_constant': {
'params': ['y0', 'A', 'R0'],
'model': lambda x, y0, A, R0: y0 + A * np.exp(R0 * x),
'guess': lambda xa, ya: (float(np.min(ya)), float(np.ptp(ya) if np.ptp(ya) else 1.0), 0.1),
},
'exp_decay_plus_linear_drift': {
'params': ['p1', 'p2', 'p3', 'p4'],
'model': lambda x, p1, p2, p3, p4: p1 * np.exp(-x / p2) + p3 + p4 * x,
'guess': lambda xa, ya: (float(np.ptp(ya) if np.ptp(ya) else 1.0), 1.0, float(np.min(ya)), 0.0),
'bounds': ([-np.inf, 0.0, -np.inf, -np.inf], np.inf),
},
'exp_cumulative_distribution': {
'params': ['y0', 'A', 'mu'],
'model': lambda x, y0, A, mu: np.where(x >= 0, y0 + A * (1.0 - np.exp(-x / mu)), y0),
'guess': lambda xa, ya: (float(np.min(ya)), float(np.ptp(ya) if np.ptp(ya) else 1.0), 1.0),
'bounds': ([-np.inf, 0.0, 0.0], np.inf),
},
'box_lucas_single_exp_rise': {
'params': ['a', 'b'],
'model': lambda x, a, b: a * (1.0 - np.exp(-b * x)),
'guess': lambda xa, ya: (float(np.max(ya)), 1.0),
'bounds': ([-np.inf, 0.0], np.inf),
},
'box_lucas_power_form_rise': {
'params': ['a', 'b'],
'model': lambda x, a, b: a * (1.0 - np.power(b, x)),
'guess': lambda xa, ya: (float(np.max(ya)), 0.5),
'bounds': ([-np.inf, 0.0], [np.inf, 1.0]),
},
'box_lucas_dual_exp_kinetics': {
'params': ['a1', 'a2'],
'model': lambda x, a1, a2: (a1 / (a1 - a2)) * (np.exp(-a2 * x) - np.exp(-a1 * x)),
'guess': lambda xa, ya: (1.0, 0.1),
'bounds': (0.0, np.inf),
},
'exp_asymptotic_regression_to_plateau': {
'params': ['a', 'b', 'c'],
'model': lambda x, a, b, c: a - b * np.power(c, x),
'guess': lambda xa, ya: (float(np.max(ya)), float(np.ptp(ya) if np.ptp(ya) else 1.0), 0.5),
'bounds': ([-np.inf, -np.inf, 0.0], np.inf),
},
'exp_linear_combination_decay': {
'params': ['a', 'b', 'c', 'r'],
'model': lambda x, a, b, c, r: a + b * x + c * np.power(r, x),
'guess': lambda xa, ya: (float(np.mean(ya)), 0.0, float(np.ptp(ya) if np.ptp(ya) else 1.0), 0.5),
'bounds': ([-np.inf, -np.inf, -np.inf, 0.0], [np.inf, np.inf, np.inf, 1.0]),
}
}
# Validate model parameter
if exp_advanced_model not in models:
return f"Invalid model: {str(exp_advanced_model)}. Valid models are: {', '.join(models.keys())}"
model_info = models[exp_advanced_model]
# Validate and convert input data
try:
x_arr, y_arr = _validate_data(xdata, ydata)
except ValueError as e:
return f"Invalid input: {e}"
# Perform curve fitting
try:
p0 = model_info['guess'](x_arr, y_arr)
bounds = model_info.get('bounds', (-np.inf, np.inf))
if bounds == (-np.inf, np.inf):
popt, pcov = scipy_curve_fit(model_info['model'], x_arr, y_arr, p0=p0, maxfev=10000)
else:
popt, pcov = scipy_curve_fit(model_info['model'], x_arr, y_arr, p0=p0, bounds=bounds, maxfev=10000)
fitted_vals = [float(v) for v in popt]
for v in fitted_vals:
if math.isnan(v) or math.isinf(v):
return "Fitting produced invalid numeric values (NaN or inf)."
except ValueError as e:
return f"Initial guess error: {e}"
except Exception as e:
return f"curve_fit error: {e}"
# Calculate standard errors
std_errors = None
try:
if pcov is not None and np.isfinite(pcov).all():
std_errors = [float(v) for v in np.sqrt(np.diag(pcov))]
except Exception:
pass
return [model_info['params'], fitted_vals, std_errors] if std_errors else [model_info['params'], fitted_vals]