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 residual 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, and lmfit.
In the following we will show how to fit using the BornAgain Python API.
The following example fits one model parameter to synthetic 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/genx_alternating_layers.dat.gz. The generated example includes a copy beside the script.
From the build directory, run as:
python3 ../auto/Examples/fit/specular/Specular1Par.py
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.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 passed to the minimizer. It converts the
lmfit parameters to a plain dictionary, runs the BornAgain simulation,
extracts simulated intensities as a NumPy array, reports the completed
evaluation, and returns the residual vector.
FitPlotter describes one subplot by combining a plotting function, optional
fixed plot data, and plotting arguments. FitMonitor arranges one or more
such plots and adds the fit status. The residual function reports each complete
evaluation directly:
# Fit progress display
fit_plotter = ba.FitPlotter(
ba.plot_curve,
measured=exp_data,
ylabel="Intensity",
)
monitor = ba.FitMonitor(
fit_plotter,
ncols=1,
show_best=True,
max_fps=1,
printer=ba.Printer(every_nth=10))
def residuals(P):
simulation = get_simulation(alpha, P.valuesdict()).simulate()
residuals = exp_values - simulation.intensities()
monitor.update(simulation, P, residuals)
return residuals
result = lmfit.minimize(residuals, P, method="leastsq")
# Recompute and report the simulation at the fitted parameters.
residuals(result.params)
# Render the just-reported evaluation as the final fit state.
monitor.render_final(result.params)
ba.plt.show()
Here exp_data remains fixed as the plotter’s measured argument, while
monitor.update(simulation, P, residuals) supplies one complete evaluation.
The standard plot_curve helper draws the measured and simulated curves
on the subplot managed by FitMonitor. max_fps limits graphical redraws,
independently of the every_nth interval passed to Printer for
terminal output. The explicit final residual call evaluates the fitted
parameters through this same update path before render_final renders them.
Custom plotters can display derived quantities or select from several simulation results. For the reporting interface and available panels, see Fit reference > Fit monitoring.
For masked or invalid pixels, ba.valid_pixel_residual returns a residual
vector with zero contribution at excluded positions, keeping its length
constant. ba.crop_by_mask extracts a rectangular analysis window.
See Residual functions for these helpers and their
different purposes.