BornAgain’s fit-monitoring helpers display fit results, parameter values, and the objective while a fit is running. Their progress interface is independent of the minimizer package: the residual function reports the result, parameters, and residuals from each completed evaluation. The monitor numbers these evaluations itself.
For an introductory fit, see Python scripting > Fitting.
The constructor is
ba.FitMonitor(
plotters,
ncols,
show_best=True,
live=True,
figsize=None,
max_fps=1,
printer=None)
| Parameter | Default | Meaning |
|---|---|---|
plotters |
required | One FitPlotter or an iterable of them. |
ncols |
required | Positive integer number of plot columns. |
show_best |
True |
Show the best rather than current live state. |
live |
True |
Draw and display states during the fit. |
figsize |
None |
Matplotlib figure size in inches, or automatic. |
max_fps |
1 |
Maximum number of live redraws per second. |
printer |
None |
Optional callback for textual fit progress. |
ncols is used exactly as given. It may exceed the number of plots, leaving
space in the grid for the fit status.
max_fps limits only redraws during the fit. With live=True, the first state
is drawn immediately. render_final always draws the last reported state.
Drawing is synchronous: max_fps limits its frequency but does not guarantee
a fixed rendering overhead.
Text output has an independent evaluation-based cadence. To retain a terminal
history alongside the graphical monitor, supply a Printer:
monitor = ba.FitMonitor(
plotters,
ncols=2,
show_best=True,
max_fps=1,
printer=ba.Printer(every_nth=10))
Here the figure is redrawn at most once per second. The first evaluation and
every tenth numbered evaluation are printed, and render_final always prints
the final state. Printing remains active with live=False.
Each report contains the evaluation number, squared-residual objective, and
current parameter names and values.
evaluation 1 objective 3.18472916e+06 initial
parameters radius= 5.000000e+00
evaluation 20 objective 2.91662381e+06
parameters radius= 5.004830e+00
evaluation 37 objective 2.90184272e+06 final
parameters radius= 5.008150e+00
The displayed objective is the sum of the squared residual values, written in
scientific notation with eight digits after the decimal point. Its fixed width
keeps the status typography independent of redraw frequency.
With show_best=True, the monitor displays the result and parameters
that produced the lowest finite objective seen so far. The monitor does not
infer a different scalar reduction used internally by an optimizer.
Report every completed fit evaluation with one call:
monitor.update(result, parameters, residuals)
The parameters may be a mapping or any object that provides valuesdict().
The result is opaque to the monitor and is passed unchanged to every plotter.
For multiple simulations, use an explicit tuple or another container:
monitor.update((simulation1, simulation2), parameters, residuals)
update must run in the process and thread that created the monitor. Parallel
evaluation has no single ordered state to display and is not
supported. The evaluation count is the number of completed update calls. It
can differ from an optimizer’s reported nfev when that optimizer evaluates
the residual function during setup or finalization. The count remains
monotonic when the same monitor and residual function are used across several
sequential optimization stages. Best-state comparisons across stages are
meaningful only while the residual definition stays unchanged.
After optimization, call the residual function once with the final parameters. This
reports a fresh result through the same update path. Then mark that last
evaluation as final:
# 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()
The explicit final residual call adds one reported evaluation.
render_final rejects parameters that do not match the last update, preventing
an optimizer’s last trial or derivative probe from being presented as the
fitted result. render_final(..., filename="fit.png") also writes the final
figure to a file.
The standard Matplotlib show call is needed at the end of a standalone
interactive script so that the process remains open. Omit it when saving a
figure in headless mode or when the interactive environment owns the display.
The residual function can report each complete evaluation directly:
def residuals(P):
simulation = get_simulation(P.valuesdict()).simulate()
residuals = exp_values - simulation.intensities()
monitor.update(simulation, P, residuals)
return residuals
result = lmfit.minimize(residuals, P, method="leastsq")
This does not occupy lmfit’s iter_cb; applications remain free to use that
callback for another purpose.
The same direct reporting works with another sequential optimizer:
def objective(values):
parameters, simulation, residuals = evaluate(values)
monitor.update(simulation, parameters, residuals)
return np.sum(residuals*residuals)
No optimizer callback or external evaluation counter is required.
A FitPlotter describes one subplot:
ba.FitPlotter(plotter_fn, *, context_data=None, **plot_args)
| Parameter | Default | Meaning |
|---|---|---|
plotter_fn |
required | Function that draws one subplot. |
context_data |
None |
Optional fixed data passed to plotter_fn. |
plot_args |
none | Keyword arguments forwarded to plotter_fn. |
The plotting function is called as
plotter_fn(result, ax=subplot, **plot_args). The current result comes from
FitMonitor.update. If context_data is not None, it is also passed as a
keyword argument. It is fixed data needed by that plot, not sample or detector
configuration and not fit state. For example, the standard specular plot needs
experimental data as context_data and the current simulation as result:
plotter = ba.FitPlotter(
ba.plot_specular,
context_data=experimental,
ylabel="Intensity",
)
Custom functions use the same result-first signature and declare the
keyword-only context_data argument only when they need fixed plot data. When
several simulations are fitted together, pass them as one tuple result and
select from that tuple in a custom function. The result-consuming standard
helpers expect one simulation; plot_experimental remains usable because it
only reads context_data.
Plotting functions should only derive display data; they must not run
simulations or modify the supplied objects. FitMonitor supplies the subplot
internally. When a custom function calls ba.plot_heatmap, pass
with_cb=True to give that subplot its own colorbar. The ax argument is
therefore reserved and cannot be supplied to the FitPlotter constructor.
The name context_data is likewise reserved. Leaving it as None means that
the keyword is not passed to the plotting function.
The status uses free cells in an incomplete plot row when its parameter table fits. Otherwise it occupies a row below the plots. Up to eight parameters whose names have at most 15 characters first try one column in a free cell; larger parameter sets use two columns in the separate status row.
The rendered text extents are checked after drawing. The figure may grow to
keep all status text visible. If bounded growth cannot make it fit, the
monitor emits a RuntimeWarning and continues with clipped status text.
ba.intensity_norm(data, zmin=None, zmax=None) creates a fixed logarithmic
normalization shared by several heat maps. Without zmin, its range spans at
most six decades below the maximum positive intensity. Explicit zmin and
zmax values override the inferred limits.
ba.relative_difference(experimental, simulated) returns
2*(simulation-experiment)/(|simulation|+|experiment|). Non-finite pixels
remain NaN; a finite zero divided by zero is defined as zero.
The standard FitPlotter functions are:
ba.plot_experimental(result, ..., context_data=experimental) plots fixed
experimental data.ba.plot_heatmap plots the current simulation directly and needs no
context_data.ba.plot_difference(simulation, ..., context_data=experimental) plots the
signed relative difference on the default [-2, 2] color scale.ba.plot_masked_experimental(simulation, ..., context_data=experimental) plots
the experimental data and dims pixels that are non-finite in the simulation.ba.plot_specular(simulation, ..., context_data=experimental) plots a measured
and simulated specular curve pair.For text-only progress, pass ba.Printer(every_nth=10) directly to the optimizer
instead of creating a FitMonitor. Printer displays the evaluation number
supplied by its caller: FitMonitor supplies its complete update count, while
an optimizer callback supplies the optimizer’s own nfev. Since an optimizer
callback cannot know when optimization has ended, call
printer.final(params, nfev, residuals) explicitly to print the final
text-only state.