Consecutive fitting

This example demonstrates how to run two fits one after the other using different minimizer settings and starting values of the fit parameters.

  • In this example we are looking for the radius and height of cylindrical nano particles randomly distributed on a surface.
  • During the first (started at line 101) fit we are setting the initial values of the fit parameters to be quite far from the expected values and use a genetic minimizer to explore a large parameter space.
  • The second fit at line 112 starts from the best parameter values found in the previous step and uses one of the gradient descent algorithms to find the precise location of the minimum.
  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
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
#!/usr/bin/env python3
"""
Fitting example: two-stage global+local fitting.

Stage 1 uses scipy differential_evolution for a global search over large
parameter space. Stage 2 uses lmfit (Levenberg-Marquardt) to refine
the result to a precise minimum.
"""

from matplotlib import pyplot as plt
import bornagain as ba
from bornagain import ba_fitmonitor, deg, angstrom, nm, nm2
import lmfit
import scipy.optimize


def get_sample(P):
    """
    A sample with uncorrelated cylinders and pyramids on a substrate.
    """
    radius = P["radius"]
    height = P["height"]

    vacuum = ba.Vacuum()
    material_substrate = ba.RefractiveMaterial("Substrate", 6e-6, 2e-8)
    material_particle = ba.RefractiveMaterial("Particle", 6e-4, 2e-8)

    ff = ba.Cylinder(radius, height)
    particle = ba.Particle(material_particle, ff, ba.AlignAt_Bottom)

    vacuum_layer = ba.Layer(vacuum)
    vacuum_layer.deposit2D(ba.Dilute2D(0.01/nm2, particle))

    substrate_layer = ba.Layer(material_substrate)
    sample = ba.Sample()
    sample.addLayer(vacuum_layer)
    sample.addLayer(substrate_layer)
    return sample


def get_simulation(P):
    """
    A GISAXS simulation with beam and detector defined.
    """
    beam = ba.Beam(1e8, 1*angstrom, 0.2*deg)
    n = 100
    detector = ba.SphericalDetector(n, 0., 2*deg, n, 0., 2*deg)
    simulation = ba.ScatteringSimulation(beam, get_sample(P), detector)
    simulation.options().setUseAvgMaterials(False)
    return simulation


def fake_data():
    """
    Generating "real" data by adding noise to the simulated data.
    """
    P = {'radius': 5*nm, 'height': 5*nm}

    simulation = get_simulation(P)
    result = simulation.simulate()

    return result.noisy(0.3, 0.5)


def run_fitting():
    data = fake_data()

    fit_objective = ba.FitObjective()
    fit_objective.addFitPair(get_simulation, data, 1)
    fit_objective.initPrint(30)
    observer = ba_fitmonitor.PlotterGISAS()

    fit_objective.initPlot(30, observer)

    P_names = ["height", "radius"]
    bounds = [(0.01, 30), (0.01, 30)]

    # Stage 1: global search with differential evolution
    def de_objective(x):
        P = lmfit.Parameters()
        for name, val in zip(P_names, x):
            P.add(name, val)
        return fit_objective.evaluate(P)

    de_result = scipy.optimize.differential_evolution(
        de_objective, bounds,
        maxiter=3,
        popsize=15,
        seed=42)

    # Stage 2: local refinement seeded from global search result
    P = lmfit.Parameters()
    for name, val, (lo, hi) in zip(P_names, de_result.x, bounds):
        P.add(name, val, min=lo, max=hi)

    result = lmfit.minimize(fit_objective.evaluate_residuals, P)
    print(lmfit.fit_report(result))


if __name__ == '__main__':
    run_fitting()
    plt.show()
auto/Examples/fit/scatter2d/consecutive_fitting.py