NISP dispersion calibration — optimization and ML correction
We have:
(y_mosaic, z_mosaic) for each source position and wavelength(ŷ, ẑ) = model(y_nisp, z_nisp, λ; θ)θ to optimizeθ* that minimizes the discrepancy between predictions and observations.
The cost function measures how far the model is from the data. A standard choice is the Mean Squared Error (MSE):
SciPy is a Python library built on top of NumPy, providing algorithms for scientific computing.
scipy.optimize: minimization, root finding, curve fittingscipy.linalg: linear algebrascipy.interpolate: interpolationscipy.stats: statistical distributions and tests
scipy.optimize.minimize provides general-purpose optimizerspip install scipy # install
from scipy.optimize import minimize # import
Full documentation: https://docs.scipy.org/doc/scipy/
Use scipy.optimize.minimize. The choice of method depends on the problem.
| Method | Needs gradient? | Good when |
|---|---|---|
Nelder-Mead | No | Few parameters, noisy cost |
L-BFGS-B | Numerical | Many parameters, smooth cost |
Powell | No | Medium size, no gradient |
Nelder-Mead or Powell: robust for ~5–15 parameters, no derivatives needed.
from scipy.optimize import minimize
result = minimize(cost_function, initial_params, method="Nelder-Mead",
options={"maxiter": 5000, "xatol": 1e-6, "fatol": 1e-8})
Docs: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html
The optimizer calls your cost function many times, varying θ. You need to:
θ (a 1D NumPy array)cost(θ): unpack θ → run model forward → return scalar MSEθ₀ (physically motivated)scipy.optimize.minimize(cost, θ₀, ...)result.x (optimal parameters) and result.fun (final cost)
Implement the full optimization pipeline for a single dataset (e.g. rgs000_0.csv).
cost(theta) function: unpack theta, run the physical model on all data points, return the MSE.theta0 from physical knowledge (nominal focal lengths, etc.).result.fun and result.x.For each data point:
| Metric | Formula | Unit | Interpretation |
|---|---|---|---|
| MSE | \(\frac{1}{N}\sum r_i^2\) | mm² | Sensitive to outliers. |
| RMSE | \(\sqrt{\text{MSE}}\) | mm | Same unit as data. Easy to interpret. |
| MAE | \(\frac{1}{N}\sum |r_i|\) | mm | More robust to outliers. |
| R² | \(1 - \frac{\sum r_i^2}{\sum(y_i-\bar{y})^2}\) | — | Fraction of variance explained. 1 = perfect. |
| Max error | \(\max |r_i|\) | mm | Worst-case prediction error. |
Plots reveal patterns that scalar metrics cannot.
r_y vs y_nisp or y_mosaic.(y_mosaic, z_mosaic) colored by r_y.After fitting, analyze the residuals.
r_y = pred_y - obs_y and r_z for all data points.r_y and r_z. Are they Gaussian? Centered?y_nisp. Is there a trend?(y_mosaic, z_mosaic) colored by |r_y|.plt.hist, plt.scatter(..., c=..., cmap=...), and plt.colorbar().Possible causes:
Machine Learning is a family of algorithms that learn a function from data.
X to outputs y given labeled examples.(r_y, r_z) from (y_nisp, z_nisp, λ).
scikit-learn is the standard Python library for machine learning. It provides a unified API for dozens of algorithms.
fit / predict / score interface
from sklearn.ensemble import RandomForestRegressor
model = RandomForestRegressor()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
pip install scikit-learn
y_nisp, z_nisp, wavelength
r_y = y_mosaic_obs − y_mosaic_phys (same for r_z)
sklearn.model_selection.train_test_split with 80% train / 20% test.
Many algorithms are sensitive to the scale of input features (and output variables). Always preprocess before training.
y_nisp ∈ [−90, 90] mm, λ ∈ [1.0, 1.9] µm: very different rangesStandardScaler: zero mean, unit variance. Best default.MinMaxScaler: maps to [0, 1]. Good when bounds are known.RobustScaler: uses median and IQR. Better when outliers are present.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # fit + transform on train
X_test_scaled = scaler.transform(X_test) # transform only on test
Start simple. Increase complexity only if needed.
| Model | Complexity | Training time | When to use |
|---|---|---|---|
| Linear Regression | Low | Instant | Baseline. Does the residual have a linear trend? |
| Random Forest | Medium | Seconds | Good default. Non-linear, no tuning needed. |
| Gradient Boosting | Medium | Seconds | Often better than RF. Try HistGradientBoosting. |
| MLP (neural net) | High | Minutes | Powerful but needs careful tuning. Use last. |
Train a ML model to predict the residuals of your physical model.
r_y and r_z from your fitted physical model.X = [y_nisp, z_nisp, wavelength] and target y = r_y.random_state.LinearRegression. Evaluate RMSE on the test set.RandomForestRegressor. Evaluate RMSE on the test set.plt.scatter(r_true, r_pred)).max_depth in RF)from sklearn.model_selection import cross_val_score
# 5-fold cross-validation
scores = cross_val_score(model, X, y, cv=5,
scoring="neg_root_mean_squared_error")
print(-scores.mean(), "±", scores.std())
Docs: https://scikit-learn.org/stable/modules/cross_validation.html
Build the hybrid predictor and compare it to the physical model alone.
ŷ_hybrid = ŷ_phys + δy_ML on the test set.rgs180_0 if trained on rgs000_0). Does the ML correction generalize?Fill this table with your results. Use the test set only.
| Model | RMSE y (mm) | RMSE z (mm) | Generalizes? |
|---|---|---|---|
| Physical model (before fit) | — | — | — |
| Physical model (after fit) | — | — | Yes |
| Physical + Linear correction | — | — | ? |
| Physical + Random Forest | — | — | ? |
| Physical + Gradient Boosting | — | — | ? |
| Step | Goal | Key tool |
|---|---|---|
| Fit physical model | Find optimal parameters θ* | scipy.optimize.minimize |
| Evaluate residuals | Identify systematic errors | RMSE, MAE, residual plots |
| ML correction | Predict residuals from inputs | sklearn regressors |
| Overfitting check | Verify generalization | Train/test split, cross-val |
| Hybrid model | Physics + ML correction | Additive combination |
| Comparison | Quantify improvement | RMSE table, residual maps |
| Next step | Full calibration pipeline (section 4) | Autonomous work |