Working with sample parameters

This example shows how to get an access to sample structure information and how to manipulate sample parameters on already constructed sample. This can be useful for debugging and for quick simulations.

In BornAgain a sample is described by a hierarchical tree of objects. For example, the tree representing a multilayer can be printed in a Python session by running.

print(sample.treeToString())

with subsequent output

MultiLayer ('CrossCorrelationLength':0 'ExternalFieldX':0 'ExternalFieldY':0 'ExternalFieldZ':0)
    Layer0
        ParticleLayout ('TotalParticleDensity':1)
            Particle0 ('Abundance':0.5 'PositionX':0 'PositionY':0 'PositionZ':0)
                Cylinder ('Radius':5 'Height':5)
            Particle1 ('Abundance':0.5 'PositionX':0 'PositionY':0 'PositionZ':0)
                Prism3 ('BaseEdge':5 'Height':5)
            InterferenceNone
    LayerInterface
    Layer1

The top MultiLayer object is composed of three children, namely Layer #0, Layer Interface #0 and Layer #1. The children objects might themselves also be decomposed into tree-like structures. For example, Layer #0 contains a ParticleLayout object, which holds information related to the two types of particles populating the layer. All numerical values used during the sample construction (thickness of layers, size of particles, etc.) are part of the same tree structure. These values are registered in the sample parameter pool using the name composed of the corresponding nodes’ names. A list of the names and values of all registered sample’s parameters can be displayed using the command

66
print(sample.parametersToString())

The output will be:

'/MultiLayer/CrossCorrelationLength':0
'/MultiLayer/ExternalFieldX':0
'/MultiLayer/ExternalFieldY':0
'/MultiLayer/ExternalFieldZ':0
'/MultiLayer/Layer0/ParticleLayout/TotalParticleDensity':1
'/MultiLayer/Layer0/ParticleLayout/Particle0/Abundance':0.5
'/MultiLayer/Layer0/ParticleLayout/Particle0/PositionX':0
'/MultiLayer/Layer0/ParticleLayout/Particle0/PositionY':0
'/MultiLayer/Layer0/ParticleLayout/Particle0/PositionZ':0
'/MultiLayer/Layer0/ParticleLayout/Particle0/Cylinder/Radius':5
'/MultiLayer/Layer0/ParticleLayout/Particle0/Cylinder/Height':5
'/MultiLayer/Layer0/ParticleLayout/Particle1/Abundance':0.5
'/MultiLayer/Layer0/ParticleLayout/Particle1/PositionX':0
'/MultiLayer/Layer0/ParticleLayout/Particle1/PositionY':0
'/MultiLayer/Layer0/ParticleLayout/Particle1/PositionZ':0
'/MultiLayer/Layer0/ParticleLayout/Particle1/Prism3/BaseEdge':5
'/MultiLayer/Layer0/ParticleLayout/Particle1/Prism3/Height':5

These values can be accessed/changed during run time. For example, the height of the cylinders populating the first layer can be changed from the current value of 5 nm to 10 nm by running the command

80
81
82
sample.setParameterValue(
        "/MultiLayer/Layer0/ParticleLayout/Particle0/Cylinder/Height",
        10.0*nm)

Wildcards * can be used to reduce typing or to work on a group of parameters. In the example below, the first command will change the height of all cylinders in the same way, as in the previous example.

97
sample.setParameterValue("*/Cylinder/Height", 10.0*nm)

The line below will change simultaneously both, the height and the half-side length of prisms.

98
sample.setParameterValue("*/Prism3/*", 10.0*nm)

Example below demonstrates how to create a sample with fixed parameters and then change these parameters on the fly during runtime. Four simulations are performed one after another. Parameters of the sample are adjusted in between using different matching criteria.

Intensity images

  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
"""
Working with sample parameters.

This example shows how to create a simulation with fixed parameters, and then change
these parameters on the fly during runtime.
"""

from __future__ import print_function
import bornagain as ba
from bornagain import deg, angstrom, nm


def get_sample():
    """
    Returns a sample with uncorrelated cylinders and prisms on a substrate.
    Parameter set is fixed.
    """
    # 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(5*nm, 5*nm)
    cylinder = ba.Particle(m_particle, cylinder_ff)
    prism_ff = ba.FormFactorPrism3(5*nm, 5*nm)
    prism = ba.Particle(m_particle, prism_ff)

    layout = ba.ParticleLayout()
    layout.addParticle(cylinder, 0.5)
    layout.addParticle(prism, 0.5)
    interference = ba.InterferenceFunctionNone()
    layout.setInterferenceFunction(interference)

    # 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():
    """
    Create and return 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)
    return simulation


def run_simulation():
    """
    Runs simulations for the sample with different sample parameters.
    """

    sample = get_sample()
    simulation = get_simulation()
    simulation.setSample(sample)

    print("The tree structure of the simulation")
    print(simulation.treeToString())

    print("The simulation contains following parameters ('name':value)")
    print(simulation.parametersToString())

    results = {}

    # simulation #1
    # initial simulation is used
    simulation.runSimulation()
    results[0] = simulation.result()

    # simulation #2
    # one sample parameter (cylinder height) is changed using exact parameter name
    simulation.setParameterValue("/GISASSimulation/MultiLayer/Layer0/ParticleLayout"
                                 "/Particle0/Cylinder/Height", 10.0*nm)
    simulation.runSimulation()
    results[1] = simulation.result()

    # simulation #3
    # all parameters matching criteria will be changed (cylinder height in this case)
    simulation.setParameterValue("*/Cylinder/Height", 100.0*nm)
    simulation.runSimulation()
    results[2] = simulation.result()

    # simulation #4
    # all parameters which are matching criteria will be changed
    simulation.setParameterValue("*/Cylinder/Height", 10.0*nm)
    simulation.setParameterValue("*/Prism3/*", 10.0*nm)
    simulation.runSimulation()
    results[3] = simulation.result()

    return results


def plot(results):
    """
    Draw results of several simulations on canvas
    """

    from matplotlib import pyplot as plt
    plt.figure(figsize=(12.80, 10.24))

    for nplot, hist in results.items():
        plt.subplot(2, 2, nplot+1)
        ba.plot_colormap(hist, zlabel="")
    plt.tight_layout()
    plt.show()


if __name__ == '__main__':
    results = run_simulation()
    plot(results)
SimulationParameters.py