## Experiment at GALAXI

This is an example of a real data fit. We use our own measurements performed at the laboratory diffractometer GALAXI in Forschungszentrum Jülich.

• The file sample_builder.py contains a sample description.
• The sample represents a 4 layer system (substrate, teflon, hmdso and air) with Ag nanoparticles placed inside the hmdso layer on top of the teflon layer.
• The sample is generated with the help of a SampleBuilder, which is able to create samples depending on parameters defined in the constructor and passed through to the create_sample method.
• The nanoparticles have a broad log-normal size distribution.
  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  #!/usr/bin/env python3 """ 3 layers system (substrate, teflon, air). Vacuum layer is populated with spheres with size distribution. """ import bornagain as ba class SampleBuilder: """ Builds complex sample from set of parameters. """ def __init__(self): """ Init SampleBuilder with default sample parameters. """ self.radius = 5.75*ba.nm self.sigma = 0.4 self.distance = 53.6*ba.nm self.disorder = 10.5*ba.nm self.kappa = 17.5 self.ptfe_thickness = 22.1*ba.nm self.hmdso_thickness = 18.5*ba.nm def create_sample(self, params): """ Create sample from given set of parameters. Args: params: A dictionary containing parameter map. Returns: A multi layer. """ self.radius = params["radius"] self.sigma = params["sigma"] self.distance = params["distance"] return self.multilayer() def multilayer(self): """ Constructs the sample from current parameter values. """ # defining materials m_vacuum = ba.HomogeneousMaterial("Vacuum", 0.0, 0.0) m_Si = ba.HomogeneousMaterial("Si", 5.78164736e-6, 1.02294578e-7) m_Ag = ba.HomogeneousMaterial("Ag", 2.24749529E-5, 1.61528396E-6) m_PTFE = ba.HomogeneousMaterial("PTFE", 5.20508729E-6, 1.96944292E-8) m_HMDSO = ba.HomogeneousMaterial("HMDSO", 2.0888308E-6, 1.32605651E-8) # collection of particles with size distribution nparticles = 20 nfwhm = 2.0 sphere_ff = ba.FormFactorFullSphere(self.radius) sphere = ba.Particle(m_Ag, sphere_ff) position = ba.kvector_t(0*ba.nm, 0*ba.nm, -1.0*self.hmdso_thickness) sphere.setPosition(position) ln_distr = ba.DistributionLogNormal(self.radius, self.sigma) par_distr = ba.ParameterDistribution( "/Particle/FullSphere/Radius", ln_distr, nparticles, nfwhm, ba.RealLimits.limited(0.0, self.hmdso_thickness/2.0)) part_coll = ba.ParticleDistribution(sphere, par_distr) # interference function interference = ba.InterferenceFunctionRadialParaCrystal( self.distance, 1e6*ba.nm) interference.setKappa(self.kappa) interference.setDomainSize(20000.0) pdf = ba.FTDistribution1DGauss(self.disorder) interference.setProbabilityDistribution(pdf) # assembling particle layout layout = ba.ParticleLayout() layout.addParticle(part_coll, 1.0) layout.setInterferenceFunction(interference) layout.setTotalParticleSurfaceDensity(1) # roughness r_ptfe = ba.LayerRoughness(2.3*ba.nm, 0.3, 5.0*ba.nm) r_hmdso = ba.LayerRoughness(1.1*ba.nm, 0.3, 5.0*ba.nm) # layers vacuum_layer = ba.Layer(m_vacuum) hmdso_layer = ba.Layer(m_HMDSO, self.hmdso_thickness) hmdso_layer.addLayout(layout) ptfe_layer = ba.Layer(m_PTFE, self.ptfe_thickness) substrate_layer = ba.Layer(m_Si) # assembling multilayer multi_layer = ba.MultiLayer() multi_layer.addLayer(vacuum_layer) multi_layer.addLayerWithTopRoughness(hmdso_layer, r_hmdso) multi_layer.addLayerWithTopRoughness(ptfe_layer, r_ptfe) multi_layer.addLayer(substrate_layer) return multi_layer 
sample_builder.py
• The file fit_galaxi_data.py contains the parts related to detector initialization, simulation setup, importing of the data and finally the fit engine setup.
• The rectangular detector is created to represent the PILATUS detector from the experiment (line 19).
• In the simulation settings the beam is initialized and the detector is assigned to the simulation. A region of interest is assigned at line 39 to simulate only a small rectangular window. Additionally, a rectangular mask is added to exclude the reflected beam from the analysis (line 40).
• The real data is loaded from a tiff file into a histogram representing the detector’s channels.
• The run_fitting() function contains the initialization of the fitting kernel: loading experimental data, assignment of fit pair, fit parameters selection (line 62).

  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  #!/usr/bin/env python3 """ Fitting experimental data: spherical nanoparticles with size distribution in 3 layers system (experiment at GALAXI). """ import bornagain as ba from bornagain import nm as nm from sample_builder import SampleBuilder wavelength = 1.34*ba.angstrom alpha_i = 0.463*ba.deg # detector setup as given from instrument responsible pilatus_npx, pilatus_npy = 981, 1043 pilatus_pixel_size = 0.172 # in mm detector_distance = 1730.0 # in mm beam_xpos, beam_ypos = 597.1, 323.4 # in pixels def create_detector(): """ Returns a model of the GALAXY detector """ u0 = beam_xpos*pilatus_pixel_size # in mm v0 = beam_ypos*pilatus_pixel_size # in mm detector = ba.RectangularDetector(pilatus_npx, pilatus_npx*pilatus_pixel_size, pilatus_npy, pilatus_npy*pilatus_pixel_size) detector.setPerpendicularToDirectBeam(detector_distance, u0, v0) return detector def create_simulation(params): """ Creates and returns GISAS simulation with beam and detector defined """ simulation = ba.GISASSimulation() simulation.setDetector(create_detector()) simulation.setBeamParameters(wavelength, alpha_i, 0.0) simulation.beam().setIntensity(1.2e7) simulation.setRegionOfInterest(85.0, 70.0, 120.0, 92.) simulation.addMask(ba.Rectangle(101.9, 82.1, 103.7, 85.2), True) # beamstop sample_builder = SampleBuilder() sample = sample_builder.create_sample(params) simulation.setSample(sample) return simulation def load_real_data(filename="galaxi_data.tif.gz"): """ Loads experimental data and returns numpy array. """ return ba.IntensityDataIOFactory.readIntensityData(filename).array() def run_fitting(): real_data = load_real_data() fit_objective = ba.FitObjective() fit_objective.addSimulationAndData(create_simulation, real_data, 1.0) fit_objective.initPrint(10) fit_objective.initPlot(10) params = ba.Parameters() params.add("radius", 5.*nm, min=4.0, max=6.0, step=0.1*nm) params.add("sigma", 0.55, min=0.2, max=0.8, step=0.01) params.add("distance", 27.*nm, min=20.0, max=70.0) minimizer = ba.Minimizer() result = minimizer.minimize(fit_objective.evaluate, params) fit_objective.finalize(result) if __name__ == '__main__': run_fitting() 
fit_galaxi_data.py