In fitting, we estimate the optimum parameters in a numerical model, specifically a scattering simulation, by minimizing the difference between simulated and reference data.
BornAgain provides the simulation result. Experimental data import,
parameter management, and the objective function are ordinary Python code.
This keeps fitting scripts independent of any BornAgain-specific data format
and lets users combine BornAgain with standard packages such as
numpy, fabio, lmfit, and scipy.optimize.
In the following we will show how to fit using the BornAgain Python API.
In the following, a very simple example shows how to fit a parametric model to given data.
The model is a specular reflectometry scan,
with a sample consisting of 20 alternating Ti and Ni layers on a Si substrate.
Using this model, synthetic data
have been generated with GenX.
These data are part of the BornAgain sources,
testdata/specular/genx_alternating_layers.dat.gz.
To make them findable by the script,
the environment variable BA_DATA_DIR must point
to the testdata/ directory in your local BornAgain repository.
From the build directory, run as:
BA_DATA_DIR=../testdata python3 ../auto/Examples/fit/...
The fit model is identical to the model used for generating the data.
There is just one fit parameter, namely the thickness of the Ti layers.
The resulting fit is indistinguishable from the data:
|
|
The arrays alpha and exp_values contain the data to be fitted.
They are read with ba_io.read_columns and converted explicitly to the
units required by the simulation; see Data import.
The lmfit.Parameters object P contains the fit parameters.
The function residuals(P) is the objective passed to the minimizer:
it converts the lmfit parameters to a plain dictionary, runs the
BornAgain simulation, extracts simulated intensities as a NumPy array,
and returns the residual vector.
The actual fit is controlled by the external minimizer:
result = lmfit.minimize(residuals, P)
For masked detector pixels, BornAgain simulations return NaN.
Residual functions that use detector masks should replace those entries
explicitly, for example with np.where(np.isfinite(r), r, 0.0).
The helper ba_fit.valid_pixel_residual(exp, sim) does this,
and additionally excludes non-finite or negative experimental values,
the dead-pixel markers of real detectors.