Disordered 3D particle assemblies

Classes Dilute3D and Dense3D model random assemblies of particles dispersed in the bulk of a layer. They can be used for thin films as long as the film thickness is substantially greater than the particle diameter.

Possible applications are

  • nanoparticle-filled polymer films,
  • colloidal suspensions, and
  • any other system where particles (or holes) are dispersed in a matrix.

Creating a 3D assembly

To fill a layer with randomly distributed particles:

layer = ba.Layer(matrix_material, thickness)
# Dilute approximation (uncorrelated particles):
layer.fill3D(ba.Dilute3D(density, particle))

# Or dense approximation (Percus-Yevick hard spheres):
layer.fill3D(ba.Dense3D(density, particle))

If the layer contains an incoherent mixture of the disordered assembly and something else, use add3D with a coverage parameter:

layer = ba.Layer(matrix_material, thickness)
disordered = ba.Dilute3D(density, particle)  # or ba.Dense3D(...)
layer.add3D(coverage, disordered)

Parameters:

  • density: Volume number density in nm⁻³ (particles per cubic nanometer)
  • particle: The particle to distribute throughout the layer
  • coverage (second form only): surface coverage, between 0 and 1

Correlation models

For dilute systems, particles scatter independently. For denser systems, spatial correlations between particles affect the scattering. Two classes are available:

  • ba.Dilute3D(density, particle): Dilute approximation, assumes no inter-particle correlations (structure factor S=1)
  • ba.Dense3D(density, particle): Percus-Yevick hard-sphere model, accounts for excluded volume effects

The Percus-Yevick approximation models hard-sphere correlations and becomes important when the particle volume fraction is significant.

Requirements

3D assemblies require material averaging to be enabled (the default). The layer thickness must be finite and larger than the particle size.

Examples

Dilute film

Spherical nanoparticles dispersed in a substrate layer:

Scattering from dilute film

 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
#!/usr/bin/env python3
"""
Dilute film of small spheres
"""
import bornagain as ba
from bornagain import ba_plot as bp, deg, nm, nm3


def get_sample():
    # Materials
    material_particle = ba.RefractiveMaterial("Particle", 6e-05, 2e-08)
    material_substrate = ba.RefractiveMaterial("Substrate", 6e-06, 2e-08)
    vacuum = ba.Vacuum()

    # Particles
    ff = ba.Sphere(2*nm)
    particle = ba.Particle(material_particle, ff, ba.AlignAt_Bottom)

    # Layers
    layer_1 = ba.Layer(vacuum)
    layer_2 = ba.Layer(material_substrate, 30*nm)
    layer_3 = ba.Layer(material_substrate)
    layer_2.fill3D(ba.Dilute3D(.0005/nm3, particle))

    # Sample
    sample = ba.Sample()
    sample.addLayer(layer_1)
    sample.addLayer(layer_2)
    sample.addLayer(layer_3)

    return sample


def get_simulation(sample):
    beam = ba.Beam(1e9, 0.1*nm, 0.2*deg)
    n = 100
    detector = ba.SphericalDetector(n, -1*deg, 1*deg, n, 0., 2*deg)
    simulation = ba.ScatteringSimulation(beam, sample, detector)
    return simulation


if __name__ == '__main__':
    sample = get_sample()
    simulation = get_simulation(sample)
    result = simulation.simulate()
    bp.plot_datafield(result, unit_aspect=1)
    bp.plt.show()
auto/Examples/scatter2d/DiluteFilm.py

Dense film: dilute vs Percus-Yevick

Comparison of dilute approximation and Percus-Yevick model for a denser film. The PY model shows structure factor oscillations due to hard-sphere correlations:

Dilute vs Percus-Yevick

scatter2d/DenseFilm.py
 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
#!/usr/bin/env python3
"""
Dilute film of small spheres
"""
import bornagain as ba
from bornagain import ba_plot as bp, deg, nm, nm3


def get_sample(is_dense):
    # Materials
    material_particle = ba.RefractiveMaterial("Particle", 6e-05, 2e-08)
    material_substrate = ba.RefractiveMaterial("Substrate", 6e-06, 2e-08)
    vacuum = ba.Vacuum()

    # Particles
    ff = ba.Sphere(4*nm)
    particle = ba.Particle(material_particle, ff, ba.AlignAt_Bottom)

    # Layers
    layer_1 = ba.Layer(vacuum)
    layer_2 = ba.Layer(material_substrate, 30*nm)
    layer_3 = ba.Layer(material_substrate)
    if is_dense:
        layer_2.fill3D(ba.Dense3D(.002/nm3, particle))
    else:
        layer_2.fill3D(ba.Dilute3D(.002/nm3, particle))

    # Sample
    sample = ba.Sample()
    sample.addLayer(layer_1)
    sample.addLayer(layer_2)
    sample.addLayer(layer_3)

    return sample


def get_simulation(sample):
    beam = ba.Beam(1e9, 0.1*nm, 0.2*deg)
    n = 100
    # Just compute a 1d cut at phi=0
    detector = ba.SphericalDetector(1, -1*deg, 1*deg, n, 0., 2*deg)
    simulation = ba.ScatteringSimulation(beam, sample, detector)
    return simulation


if __name__ == '__main__':
    samples = [
        get_sample(False),  # Dilute
        get_sample(True)    # Dense (PY)
    ]
    results = [ get_simulation(sample).simulate() for sample in samples ]
    for r in results:
        bp.plot_datafield(r)
    bp.plt.legend(['Dilute', 'Percus-Yevick'])
    bp.plt.show()
auto/Examples/scatter2d/DenseFilm.py

Film vs monolayer

Comparison between a 3D film (fill3DRandom) and a 2D monolayer (depositParticle). As film thickness increases, the scattering pattern evolves from the monolayer limit:

Film thickness dependence

scatter2d/FilmVsMonolayer.py
 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
#!/usr/bin/env python3
"""
Dilute film of small spheres
"""
import bornagain as ba
from bornagain import ba_plot as bp, deg, nm, nm2, nm3


def get_sample(thickness):
    # Materials
    material_particle = ba.RefractiveMaterial("Particle", 1.5e-05, 2e-08)
    material_substrate = ba.RefractiveMaterial("Substrate", 1e-06, 2e-08)
    vacuum = ba.Vacuum()

    # Particles
    ff = ba.Sphere(4*nm)
    particle = ba.Particle(material_particle, ff, ba.AlignAt_Bottom)

    # Layers
    layer_1 = ba.Layer(vacuum)
    layer_3 = ba.Layer(material_substrate)
    if thickness > 0:
        layer_2 = ba.Layer(material_substrate, thickness)
        layer_2.fill3D(ba.Dense3D(.002/nm3, particle))
    else:
        # Monolayer case: particles deposited on vacuum layer, sitting on substrate
        layer_1.deposit2D(ba.Dense2D(.012/nm2, particle))

    # Sample
    sample = ba.Sample()
    sample.addLayer(layer_1)
    if thickness > 0:
        sample.addLayer(layer_2)
    sample.addLayer(layer_3)

    return sample


def get_simulation(sample):
    beam = ba.Beam(1e9, 0.1*nm, 0.2*deg)
    n = 100
    # Just compute a 1d cut at phi=0
    detector = ba.SphericalDetector(1, -1*deg, 1*deg, n, 0., 2*deg)
    simulation = ba.ScatteringSimulation(beam, sample, detector)
    return simulation


if __name__ == '__main__':
    thicknesses = [0*nm, 8.01*nm, 16*nm, 32*nm, 64*nm]
    labels = ["Monolayer"]
    labels.extend(f'{t/nm:.0f} nm' for t in thicknesses[1:])
    samples = [ get_sample(t) for t in thicknesses ]
    results = [ get_simulation(sample).simulate() for sample in samples ]
    for r in results:
        bp.plot_datafield(r, intensity_max=1e6, intensity_min=1e-4)
    bp.plt.legend(labels)
    bp.plt.show()
auto/Examples/scatter2d/FilmVsMonolayer.py