Find background

This example demonstrates how to introduce a background in the simulation and fit its value. Here we are simulating cylinders on top of a substrate without interference. The function get_simulation requires 4 parameters:

  • the height of the cylinders
  • the radius of the cylinders
  • the value of the constant background
  • a scale factor for the beam’s intensity

The radius and height of the cylinders are passed to the function constructing the multi layer while the scale and background values are used to initialize the instrument.

  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
#!/usr/bin/env python3
"""
Fitting example: looking for background.

Real data contains some "unknown" background.
In the fit we are trying to find cylinder radius and height and background.
"""

from matplotlib import pyplot as plt
import bornagain as ba
from bornagain import deg, nm, nm2
import lmfit


def get_sample(P):
    """
    Uncorrelated cylinders on a substrate, parameterized for fitting.
    """
    substrate_mat = ba.RefractiveMaterial(
        "Substrate", (0.28, 0.57, 0.82), 6e-6, 2e-8)
    particle_mat = ba.RefractiveMaterial(
        "Particle", (0.86, 0.24, 0.18), 6e-4, 2e-8)
    particle = ba.Particle(
        particle_mat, ba.Cylinder(P["radius"], P["height"]))

    particle_layer = ba.Layer(ba.Vacuum())
    particle_layer.deposit2D(ba.Dilute2D(0.01/nm2, particle))

    sample = ba.Sample()
    sample.addLayer(particle_layer)
    sample.addLayer(ba.Layer(substrate_mat))
    return sample


def get_simulation(P):
    """
    GISAS simulation for the parameterized cylinder sample.
    """
    n = 100
    beam = ba.Beam(1e8, 0.1*nm, 0.2*deg)
    detector = ba.SphericalDetector(n, -1*deg, 1*deg, n, 0, 2*deg)
    simulation = ba.ScatteringSimulation(beam, get_sample(P), detector)
    simulation.options().setUseAvgMaterials(False)
    return simulation


def get_background_simulation(P):
    """
    GISAS simulation with a fitted constant background.
    """
    background = P["background"]

    simulation = get_simulation(P)
    simulation.setBackground(ba.ConstantBackground(background))

    return simulation


def fake_data():
    """
    Generate synthetic data with known cylinder dimensions and background.
    """

    P = {
        'radius': 5*nm,
        'height': 10*nm,
        'background': 1000
    }

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

    return result


if __name__ == '__main__':
    data = fake_data()
    flat_exp_values = data.intensities().ravel()

    def residuals(P):
        """
        Runs a simulation for given parameters P, and returns residuals
        vector.
        """
        sim_values = get_background_simulation(
            P.valuesdict()).simulate().intensities()
        flat_sim_values = sim_values.ravel()
        return flat_exp_values - flat_sim_values

    P = lmfit.Parameters()
    P.add("radius", 5.*nm, vary=False)
    P.add("height", 9.*nm, min=8.*nm, max=12.*nm)
    P.add("background", 200, min=100, max=2000)

    result = lmfit.minimize(residuals, P)
    print(lmfit.fit_report(result))
    finalP = result.params.valuesdict()
    ba.showSample3D(get_sample(finalP), sample_size=120*nm, seed=0)

    plt.show()
auto/Examples/fit/scatter2d/find_background.py