Nonlinear measurements rarely reveal model parameters directly because observed values combine the underlying response with sampling noise. scipy.optimize.curve_fit() estimates the parameters by minimizing squared residuals between the measurements and a model function.

The model callable receives the independent variable first and the parameters to estimate afterward. For an exponential decay, amplitude, decay rate, and baseline form a compact model that can be constrained to nonnegative values.

The fitting call returns parameter estimates and an approximate covariance matrix. Residual RMSE shows how closely the fitted curve follows the measurements, while standard errors and the covariance condition number expose uncertainty or redundant parameter behavior.

Steps to fit a curve to data with SciPy:

  1. Create fit_curve.py with the exponential model and floating-point measurements.
    fit_curve.py
    import numpy as np
    from scipy.optimize import curve_fit
     
     
    def decay_model(x, amplitude, decay, baseline):
        return amplitude * np.exp(-decay * x) + baseline
     
     
    x = np.linspace(0.0, 4.0, 9)
    y = np.array(
        [2.91, 1.77, 1.15, 0.82, 0.62, 0.51, 0.44, 0.41, 0.39],
        dtype=float,
    )
  2. Append the bounded parameter fit to the end of fit_curve.py.
    fit_curve.py
    initial_guess = (2.5, 1.0, 0.3)
    bounds = (0.0, [5.0, 5.0, 2.0])
     
    params, covariance = curve_fit(
        decay_model,
        x,
        y,
        p0=initial_guess,
        bounds=bounds,
    )

    p0 supplies starting values in model-parameter order. The lower bounds allow zero, while the upper bounds cap amplitude and decay at 5.0 and baseline at 2.0.

  3. Append the residual and covariance diagnostics to fit_curve.py.
    fit_curve.py
    standard_errors = np.sqrt(np.diag(covariance))
    residuals = y - decay_model(x, *params)
    rmse = np.sqrt(np.mean(residuals**2))
    condition_number = np.linalg.cond(covariance)
  4. Add the parameter and diagnostic output lines to fit_curve.py.
    fit_curve.py
    print(
        f"parameters: amplitude={params[0]:.3f}, "
        f"decay={params[1]:.3f}, baseline={params[2]:.3f}"
    )
    print(
        f"standard_errors: amplitude={standard_errors[0]:.3f}, "
        f"decay={standard_errors[1]:.3f}, baseline={standard_errors[2]:.3f}"
    )
    print(f"rmse: {rmse:.3f}")
    print(f"covariance_condition: {condition_number:.1f}")
  5. Run fit_curve.py to confirm the exponential model fits the measurements.
    $ python fit_curve.py
    parameters: amplitude=2.534, decay=1.174, baseline=0.371
    standard_errors: amplitude=0.009, decay=0.011, baseline=0.006
    rmse: 0.007
    covariance_condition: 19.9

    The parameter order matches the model signature after x. RMSE has no universal pass threshold; 0.007 is small against measurements from 0.39 to 2.91. A much larger covariance condition number can signal redundant parameters or poorly scaled estimates.