Toolbox: calibration in code
This page is optional. Everything in the course can be done in a spreadsheet, and nothing here is needed for the exam. It is for students who are curious about how the same calibration looks in code, or who will need it later in research.
The idea is the one you learned in Module 2 (OptimizationM2): a model is a machine with knobs, and an optimizer turns the knobs until the error between model and data (the RMSEM2) is as small as possible. Excel's SolverM2 does it for you behind a button. In code, you write the same three ingredients yourself:
- the model: a function that takes the parameters and returns predictions;
- the error: a function that compares predictions and data, here the RMSE;
- the optimizer: a library function that turns the knobs to minimize the error.
When to move beyond spreadsheets
Consider switching to a programming language when you need:
- models with many parameters, differential equations, or nested functions;
- thousands of data points, which slow Excel down;
- constraints, bounds, or specialized optimization methods;
- to process many athletes or conditions in one batch;
- reproducible analyses, with version control and automated pipelines.
Start with Excel to learn how optimization and RMSE minimization work. Move to a programming language when your models get more complex and your datasets larger.
Python: fitting the critical power model
import numpy as np
from scipy.optimize import minimize
# Experimental data: time (s) and power (W)
time_data = np.array([180, 300, 600, 1200])
power_data = np.array([400, 350, 300, 275])
def critical_power_model(params, t):
CP, W_prime = params
return W_prime / t + CP
def objective_function(params):
predicted = critical_power_model(params, time_data)
rmse = np.sqrt(np.mean((power_data - predicted)**2))
return rmse
# Initial parameter guess
initial_guess = [250, 15000] # CP=250W, W'=15000J
# Optimization
result = minimize(objective_function, initial_guess)
CP_opt, W_prime_opt = result.x
print(f"Optimal CP: {CP_opt:.1f} W")
print(f"Optimal W': {W_prime_opt:.0f} J")
print(f"RMSE: {result.fun:.2f} W")
The three ingredients are easy to spot: critical_power_model is the machine, objective_function measures the RMSE, and minimize turns the two knobs (CP and W′) starting from the initial guess.
MATLAB: fitting VO₂ kinetics
% Measured data (column vectors of equal length):
% time - time points of the test (s)
% vo2_exp - measured VO2 at each time point (L/min)
% VO2 kinetics model: VO2(t) = A + Delta*(1 - exp(-t/tau))
vo2_model = @(p, t) p(1) + p(2) * (1 - exp(-t / p(3)));
% Objective function (RMSE)
objective = @(p) sqrt(mean((vo2_exp - vo2_model(p, time)).^2));
% Optimization
p_opt = fminsearch(objective, [1.0, 2.0, 30.0]);
fprintf('Optimal parameters: A=%.2f, Delta=%.2f, tau=%.1f\n', p_opt);
Same recipe: vo2_model is the machine with three knobs (A, Δ and τ), objective is the RMSE, and fminsearch does the turning.
Remember: the math does not change. Whether you use Excel's Solver or Python's scipy.optimize, you are still adjusting parameters to minimize the RMSE. Only the tool is different.