+++
title = "Minimizer"
weight = 40
+++

## Minimizer

BornAgain fitting uses standard Python minimization packages.
The recommended choice is [lmfit](https://lmfit.github.io/lmfit-py/),
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](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.differential_evolution.html)
is a good option.

### Fit parameter setup with lmfit

The `lmfit.Parameters` class defines a collection of fit parameters.
Each parameter has a unique name, starting value, and optional bounds.

```python
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)
```

### Running the fit

Pass the `FitObjective` residual function and the parameters to `lmfit.minimize`:

```python
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))
```

### Two-stage fit (global + local)

For difficult problems, combine a global search with a local refinement:

```python
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))
```

### Further resources

+ [lmfit documentation](https://lmfit.github.io/lmfit-py/)
+ [scipy.optimize documentation](https://docs.scipy.org/doc/scipy/reference/optimize.html)
+ [bumps](https://bumps.readthedocs.io/en/latest/)
