Custom objective function

The BornAgain fitting API allows users to define a custom objective function to for the minimization engine.

In this example we are going to construct a vector of residuals calculated between the experimental and simulated intensity values after applying an additional $sqrt$ function to the amplitudes.

$$ residuals = [r_{0}, r_{1}, … , r_{n-1}], ~~~ r_{i} = \sqrt{e_{i}} - \sqrt{s_{i}} $$

The length of vector n corresponds to the total number of non-masked detector channels.

This is done by defining a custom residual function my_residuals. It calls fit_objective.evaluate(P) to run the simulation and store the intensity arrays, then retrieves them via fit_objective.flatSimData() and fit_objective.flatExpData() and applies sqrt before differencing. The custom function is then passed directly to lmfit.minimize.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env python3
"""
Using custom objective function to fit GISAS data.

In this example objective function returns vector of residuals computed from
the data and simulation after applying sqrt() to intensity values.
"""
import bornagain as ba
from bornagain import nm
import numpy as np
import lmfit
import model2_hexlattice as model


if __name__ == '__main__':
    data = model.fake_data()

    fit_objective = ba.FitObjective()
    fit_objective.addFitPair(model.get_simulation, data, 1)
    fit_objective.initPrint(10)

    def my_residuals(P):
        """Custom residuals: apply sqrt to intensities before differencing."""
        fit_objective.evaluate(P)
        sim = np.sqrt(np.asarray(fit_objective.flatSimData()))
        exp = np.sqrt(np.asarray(fit_objective.flatExpData()))
        return sim - exp

    P = lmfit.Parameters()
    P.add('radius', value=7*nm, min=5*nm, max=8*nm)
    P.add('length', value=10*nm, min=8*nm, max=14*nm)

    result = lmfit.minimize(my_residuals, P)
    print(lmfit.fit_report(result))
auto/Examples/fit/scatter2d/custom_objective_function.py