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 value
  • ydata (list[list], required): The ydata value
  • exp_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]

Online Calculator