BornAgain fitting uses standard Python minimization packages. The recommended choice is lmfit, which provides a convenient parameter interface and several algorithms. For global search (useful when parameters are far from their optimal values), scipy.optimize.differential_evolution is a good option.
The lmfit.Parameters class defines a collection of fit parameters.
Each parameter has a unique name, starting value, and optional bounds.
import lmfit
P = lmfit.Parameters()
P.add("radius", value=5*nm, min=1*nm, max=10*nm)
P.add("length", value=10*nm, min=8*nm, max=14*nm)
P.add("density", value=1e-4, vary=False)
Pass the FitObjective residual function and the parameters to lmfit.minimize:
fit_objective = ba.FitObjective()
fit_objective.addFitPair(run_simulation, exp_data)
fit_objective.initPrint(10)
result = lmfit.minimize(fit_objective.evaluate_residuals, P)
print(lmfit.fit_report(result))
For difficult problems, combine a global search with a local refinement:
import scipy
# Stage 1: global search
bounds = [(1*nm, 10*nm), (8*nm, 14*nm)]
result1 = scipy.optimize.differential_evolution(
fit_objective.evaluate, bounds, seed=42, tol=1e-4, maxiter=100)
# Stage 2: local refinement seeded from stage 1
P["radius"].value = result1.x[0]
P["length"].value = result1.x[1]
result2 = lmfit.minimize(fit_objective.evaluate_residuals, P)
print(lmfit.fit_report(result2))