## Basic fit example

In this example we use a simple geometry: cylinders and prisms in an air layer, deposited on a substrate layer, with no interference. There are 4 fitting parameters:

1. the radius of the cylinders
2. the height of the cylinders
3. the side length of the prisms
4. the height of the prisms

Our reference data is a “noisy” two-dimensional intensity map obtained from the simulation of the same geometry with a fixed value of 5nm for all sizes of the cylinders and prisms. Then we run the fit using the default minimizer settings starting with a cylinder height of $4$ nm, a cylinder radius of $6$ nm, a prism half side of $12$ nm and height equal to $4$ nm. As a result, the fitting procedure is able to find the correct value of $5$ nm for all four parameters.

  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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131  """ Fitting example: 4 parameters fit for mixture of cylinders and prisms on top of substrate. """ import bornagain as ba from bornagain import deg, angstrom, nm import numpy as np from matplotlib import pyplot as plt def get_sample(params): """ Returns a sample with uncorrelated cylinders and prisms on a substrate. """ cylinder_height = params["cylinder_height"] cylinder_radius = params["cylinder_radius"] prism_height = params["prism_height"] prism_base_edge = params["prism_base_edge"] # defining materials m_air = ba.HomogeneousMaterial("Air", 0.0, 0.0) m_substrate = ba.HomogeneousMaterial("Substrate", 6e-6, 2e-8) m_particle = ba.HomogeneousMaterial("Particle", 6e-4, 2e-8) # collection of particles cylinder_ff = ba.FormFactorCylinder(cylinder_radius, cylinder_height) cylinder = ba.Particle(m_particle, cylinder_ff) prism_ff = ba.FormFactorPrism3(prism_base_edge, prism_height) prism = ba.Particle(m_particle, prism_ff) layout = ba.ParticleLayout() layout.addParticle(cylinder, 0.5) layout.addParticle(prism, 0.5) # air layer with particles and substrate form multi layer air_layer = ba.Layer(m_air) air_layer.addLayout(layout) substrate_layer = ba.Layer(m_substrate, 0) multi_layer = ba.MultiLayer() multi_layer.addLayer(air_layer) multi_layer.addLayer(substrate_layer) return multi_layer def get_simulation(params): """ Returns a GISAXS simulation with beam and detector defined """ simulation = ba.GISASSimulation() simulation.setDetectorParameters(100, -1.0*deg, 1.0*deg, 100, 0.0*deg, 2.0*deg) simulation.setBeamParameters(1.0*angstrom, 0.2*deg, 0.0*deg) simulation.setBeamIntensity(1e+08) simulation.setSample(get_sample(params)) return simulation def create_real_data(): """ Generating "experimental" data by running simulation with certain parameters. The data is saved on disk in the form of numpy array. """ # default sample parameters params = {'cylinder_height': 5.0*nm, 'cylinder_radius': 5.0*nm, 'prism_height': 5.0*nm, 'prism_base_edge': 5.0*nm} # retrieving simulated data in the form of numpy array simulation = get_simulation(params) simulation.runSimulation() real_data = simulation.result().array() # spoiling simulated data with noise to produce "real" data np.random.seed(0) noise_factor = 0.1 noisy = np.random.normal(real_data, noise_factor*np.sqrt(real_data)) noisy[noisy < 0.1] = 0.1 np.savetxt("basic_fitting_tutorial_data.txt.gz", real_data) def load_real_data(): """ Loads experimental data from file """ return np.loadtxt("basic_fitting_tutorial_data.txt.gz", dtype=float) def run_fitting(): """ Setup simulation and fit """ real_data = load_real_data() fit_objective = ba.FitObjective() fit_objective.addSimulationAndData(get_simulation, real_data) # Print fit progress on every n-th iteration. fit_objective.initPrint(10) # Plot fit progress on every n-th iteration. Will slow down fit. fit_objective.initPlot(10) params = ba.Parameters() params.add("cylinder_height", 4.*nm, min=0.01) params.add("cylinder_radius", 6.*nm, min=0.01) params.add("prism_height", 4.*nm, min=0.01) params.add("prism_base_edge", 6.*nm, min=0.01) minimizer = ba.Minimizer() result = minimizer.minimize(fit_objective.evaluate, params) fit_objective.finalize(result) print("Fitting completed.") print("chi2:", result.minValue()) for fitPar in result.parameters(): print(fitPar.name(), fitPar.value, fitPar.error) # saving simulation image corresponding to the best fit parameters # np.savetxt("data.txt", fit_objective.simulationResult().array()) if __name__ == '__main__': # uncomment line below to regenerate "experimental" data file # create_real_data() run_fitting() plt.show() 
basic_fitting_tutorial.py