Variance Reduction - Weight Windows#

Creating and utilizing a weight window to accelerate deep shielding simulations#

This example simulates a shield room / bunker with corridor entrance and a neutron source in the center of the room. This example implements a single step of the Magic method of weight window generation.

In this tutorial we shall focus on generating a weight window to accelerate the simulation of particles through a shield.

Weight Windows are found using the MAGIC method and used to accelerate the simulation.

The variance reduction method used for this simulation is well documented in the OpenMC documentation https://docs.openmc.org/en/stable/methods/neutron_physics.html

The MAGIC method is well described in the original publication https://scientific-publications.ukaea.uk/wp-content/uploads/Published/INTERN1.pdf

First we import openmc and other packages needed for the example and configure the nuclear data path

import time  # used to time the simulation
import numpy as np

from matplotlib import pyplot as plt
from matplotlib.colors import LogNorm  # used for plotting log scale graphs

import openmc
from pathlib import Path
# Setting the cross section path to the correct location in the docker image.
# If you are running this outside the docker image you will have to change this path to your local cross section path.
openmc.config['cross_sections'] = Path.home() / 'nuclear_data' / 'cross_sections.xml'

We create a couple of materials for the simulation

mat_air = openmc.Material(name="Air")
mat_air.add_element("N", 0.784431)
mat_air.add_element("O", 0.210748)
mat_air.add_element("Ar", 0.0046)
mat_air.set_density("g/cc", 0.001205)

mat_concrete = openmc.Material()
mat_concrete.add_element("H",0.168759)
mat_concrete.add_element("C",0.001416)
mat_concrete.add_element("O",0.562524)
mat_concrete.add_element("Na",0.011838)
mat_concrete.add_element("Mg",0.0014)
mat_concrete.add_element("Al",0.021354)
mat_concrete.add_element("Si",0.204115)
mat_concrete.add_element("K",0.005656)
mat_concrete.add_element("Ca",0.018674)
mat_concrete.add_element("Fe",0.00426)
mat_concrete.set_density("g/cm3", 2.3)

materials = openmc.Materials([mat_air, mat_concrete])

Now we define and plot the geometry. This geometry is defined by parameters for every width and height. The parameters input into the geometry in a stacked manner so they can easily be adjusted to change the geometry without creating overlapping cells.

width_a = 100
width_b = 100
width_c = 500
width_d = 100
width_e = 100
width_f = 100
width_g = 100

depth_a = 100
depth_b = 100
depth_c = 700
depth_d = 600
depth_e = 100
depth_f = 100

height_j = 100
height_k = 500
height_l = 100

xplane_0 = openmc.XPlane(x0=0, boundary_type="vacuum")
xplane_1 = openmc.XPlane(x0=xplane_0.x0 + width_a)
xplane_2 = openmc.XPlane(x0=xplane_1.x0 + width_b)
xplane_3 = openmc.XPlane(x0=xplane_2.x0 + width_c)
xplane_4 = openmc.XPlane(x0=xplane_3.x0 + width_d)
xplane_5 = openmc.XPlane(x0=xplane_4.x0 + width_e)
xplane_6 = openmc.XPlane(x0=xplane_5.x0 + width_f)
xplane_7 = openmc.XPlane(x0=xplane_6.x0 + width_g, boundary_type="vacuum")

yplane_0 = openmc.YPlane(y0=0, boundary_type="vacuum")
yplane_1 = openmc.YPlane(y0=yplane_0.y0 + depth_a)
yplane_2 = openmc.YPlane(y0=yplane_1.y0 + depth_b)
yplane_3 = openmc.YPlane(y0=yplane_2.y0 + depth_c)
yplane_4 = openmc.YPlane(y0=yplane_3.y0 + depth_d)
yplane_5 = openmc.YPlane(y0=yplane_4.y0 + depth_e)
yplane_6 = openmc.YPlane(y0=yplane_5.y0 + depth_f, boundary_type="vacuum")

zplane_1 = openmc.ZPlane(z0=0, boundary_type="vacuum")
zplane_2 = openmc.ZPlane(z0=zplane_1.z0 + height_j)
zplane_3 = openmc.ZPlane(z0=zplane_2.z0 + height_k)
zplane_4 = openmc.ZPlane(z0=zplane_3.z0 + height_l, boundary_type="vacuum")

outside_left_region = +xplane_0 & -xplane_1 & +yplane_1 & -yplane_5 & +zplane_1 & -zplane_4
wall_left_region = +xplane_1 & -xplane_2 & +yplane_2 & -yplane_4 & +zplane_2 & -zplane_3
wall_right_region = +xplane_5 & -xplane_6 & +yplane_2 & -yplane_5 & +zplane_2 & -zplane_3
wall_top_region = +xplane_1 & -xplane_4 & +yplane_4 & -yplane_5 & +zplane_2 & -zplane_3
outside_top_region = +xplane_0 & -xplane_7 & +yplane_5 & -yplane_6 & +zplane_1 & -zplane_4
wall_bottom_region = +xplane_1 & -xplane_6 & +yplane_1 & -yplane_2 & +zplane_2 & -zplane_3
outside_bottom_region = +xplane_0 & -xplane_7 & +yplane_0 & -yplane_1 & +zplane_1 & -zplane_4
wall_middle_region = +xplane_3 & -xplane_4 & +yplane_3 & -yplane_4 & +zplane_2 & -zplane_3
outside_right_region = +xplane_6 & -xplane_7 & +yplane_1 & -yplane_5 & +zplane_1 & -zplane_4

room_region = +xplane_2 & -xplane_3 & +yplane_2 & -yplane_4 & +zplane_2 & -zplane_3
gap_region = +xplane_3 & -xplane_4 & +yplane_2 & -yplane_3 & +zplane_2 & -zplane_3
corridor_region = +xplane_4 & -xplane_5 & +yplane_2 & -yplane_5 & +zplane_2 & -zplane_3

roof_region = +xplane_1 & -xplane_6 & +yplane_1 & -yplane_5 & +zplane_1 & -zplane_2
floor_region = +xplane_1 & -xplane_6 & +yplane_1 & -yplane_5 & +zplane_3 & -zplane_4

outside_left_cell = openmc.Cell(region=outside_left_region, fill=mat_air)
outside_right_cell = openmc.Cell(region=outside_right_region, fill=mat_air)
outside_top_cell = openmc.Cell(region=outside_top_region, fill=mat_air)
outside_bottom_cell = openmc.Cell(region=outside_bottom_region, fill=mat_air)
wall_left_cell = openmc.Cell(region=wall_left_region, fill=mat_concrete)
wall_right_cell = openmc.Cell(region=wall_right_region, fill=mat_concrete)
wall_top_cell = openmc.Cell(region=wall_top_region, fill=mat_concrete)
wall_bottom_cell = openmc.Cell(region=wall_bottom_region, fill=mat_concrete)
wall_middle_cell = openmc.Cell(region=wall_middle_region, fill=mat_concrete)
room_cell = openmc.Cell(region=room_region, fill=mat_air)
gap_cell = openmc.Cell(region=gap_region, fill=mat_air)
corridor_cell = openmc.Cell(region=corridor_region, fill=mat_air)

roof_cell = openmc.Cell(region=roof_region, fill=mat_concrete)
floor_cell = openmc.Cell(region=floor_region, fill=mat_concrete)

geometry = openmc.Geometry(
    [
        outside_bottom_cell,
        outside_top_cell,
        outside_left_cell,
        outside_right_cell,
        wall_left_cell,
        wall_right_cell,
        wall_top_cell,
        wall_bottom_cell,
        wall_middle_cell,
        room_cell,
        gap_cell,
        corridor_cell,
        roof_cell,
        floor_cell,
    ]
)

Now we plot the geometry and color by materials.

plot = geometry.plot(basis='xz',  color_by='material')
plot.figure.savefig('geometry_top_down_view.png', bbox_inches="tight")
../../_images/2f54a45b8087c36097b3645d8498c82314be1e88740c49caa3ff944b07ca8212.png

Next we create a point source, this also uses the same geometry parameters to place in the center of the room regardless of the values of the parameters.

# location of the point source
source_x = width_a + width_b + width_c * 0.5
source_y = depth_a + depth_b + depth_c * 0.75
source_z = height_j + height_k * 0.5
space = openmc.stats.Point((source_x, source_y, source_z))

angle = openmc.stats.Isotropic()

# all (100%) of source particles are 2.5MeV energy
energy = openmc.stats.Discrete([2.5e6], [1.0])

source = openmc.IndependentSource(space=space, angle=angle, energy=energy)
source.particle = "neutron"

Next we create a mesh that encompasses the entire geometry and scores neutron flux

mesh = openmc.RegularMesh().from_domain(geometry)
mesh.dimension = (500, 500, 1)

mesh_filter = openmc.MeshFilter(mesh, filter_id=1)
particle_filter = openmc.ParticleFilter('neutron', filter_id=2)

flux_tally = openmc.Tally(name="flux tally")
flux_tally.filters = [mesh_filter, particle_filter]
flux_tally.scores = ["flux"]
flux_tally.id = 42  # we set the ID because we need to access this later

tallies = openmc.Tallies([flux_tally])

Creates the simulation settings

settings = openmc.Settings()

settings.run_mode = "fixed source"
settings.source = source
settings.particles = 80000
settings.batches = 5
# no need to write the tallies.out file which saves space and time when large meshes are used
settings.output = {'tallies': False}

Creates and exports the model

model = openmc.Model(geometry, materials, settings, tallies)

We are going to plot the mesh results with and without weight windows so lets write a function for the plotting

def run_and_plot(model: openmc.Model, image_filename: str) -> openmc.StatePoint:

    !rm *.h5 || true
    sp_filename = model.run()

    with openmc.StatePoint(sp_filename) as sp:
        flux_tally = sp.get_tally(name="flux tally")

    mesh_extent = mesh.bounding_box.extent['xy']

    # create a plot of the mean flux values
    flux_mean = flux_tally.get_reshaped_data(value='mean', expand_dims=True).squeeze()

    fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(10, 5))
    ax1.imshow(
        flux_mean.T,
        origin="lower",
        extent=mesh_extent,
        norm=LogNorm(),
    )

    ax1 = model.plot(
        outline='only',
        extent=model.bounding_box.extent['xz'],
        axes=ax1,  # Use the same axis as ax1\n",
        pixels=10_000_000,  #avoids rounded corners on outline
        color_by='material',
    )
    ax1.set_title("Flux Mean")

    # create a plot of the flux relative error
    flux_std_dev = flux_tally.get_reshaped_data(value='std_dev', expand_dims=True).squeeze()
    ax2.imshow(
        flux_std_dev.T,
        origin="lower",
        extent=mesh_extent,
        norm=LogNorm(),
    )

    ax2 = model.plot(
        outline='only',
        extent=model.bounding_box.extent['xz'],
        axes=ax2,  # Use the same axis as ax2\n",
        pixels=10_000_000,  #avoids rounded corners on outline
        color_by='material',
    )
    ax2.set_title("Flux Std. Dev.")

    plt.savefig(image_filename)
    return sp

Now we run the model without weight windows

# this helps time the simulation
t0 = time.time()

# delete the old summary file if it exists
!rm summary.h5

run_and_plot(model, 'no_ww_statepoint_filename.png')

print(f'total time without weight windows {int(time.time()-t0)}s')
                                %%%%%%%%%%%%%%%
                           %%%%%%%%%%%%%%%%%%%%%%%%
                        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                   %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                                    %%%%%%%%%%%%%%%%%%%%%%%%
                                     %%%%%%%%%%%%%%%%%%%%%%%%
                 ###############      %%%%%%%%%%%%%%%%%%%%%%%%
                ##################     %%%%%%%%%%%%%%%%%%%%%%%
                ###################     %%%%%%%%%%%%%%%%%%%%%%%
                ####################     %%%%%%%%%%%%%%%%%%%%%%
                #####################     %%%%%%%%%%%%%%%%%%%%%
                ######################     %%%%%%%%%%%%%%%%%%%%
                #######################     %%%%%%%%%%%%%%%%%%
                 #######################     %%%%%%%%%%%%%%%%%
                 ######################     %%%%%%%%%%%%%%%%%
                  ####################     %%%%%%%%%%%%%%%%%
                    #################     %%%%%%%%%%%%%%%%%
                     ###############     %%%%%%%%%%%%%%%%
                       ############     %%%%%%%%%%%%%%%
                          ########     %%%%%%%%%%%%%%
                                      %%%%%%%%%%%

                 | The OpenMC Monte Carlo Code
       Copyright | 2011-2025 MIT, UChicago Argonne LLC, and contributors
         License | https://docs.openmc.org/en/latest/license.html
         Version | 0.15.3-dev233
     Commit Hash | 6cf0eac20578463a6d5aea3be55774488015211e
       Date/Time | 2025-08-02 09:23:12
  OpenMP Threads | 4

 Reading model XML file 'model.xml' ...
 Reading cross sections XML file...
 Reading N14 from /home/runner/nuclear_data/neutron/N14.h5
 Reading N15 from /home/runner/nuclear_data/neutron/N15.h5
 Reading O16 from /home/runner/nuclear_data/neutron/O16.h5
 Reading O17 from /home/runner/nuclear_data/neutron/O17.h5
 Reading O18 from /home/runner/nuclear_data/neutron/O18.h5
 Reading Ar36 from /home/runner/nuclear_data/neutron/Ar36.h5
 WARNING: Negative value(s) found on probability table for nuclide Ar36 at 294K
 Reading Ar38 from /home/runner/nuclear_data/neutron/Ar38.h5
 Reading Ar40 from /home/runner/nuclear_data/neutron/Ar40.h5
 Reading H1 from /home/runner/nuclear_data/neutron/H1.h5
 Reading H2 from /home/runner/nuclear_data/neutron/H2.h5
 Reading C12 from /home/runner/nuclear_data/neutron/C12.h5
 Reading C13 from /home/runner/nuclear_data/neutron/C13.h5
 Reading Na23 from /home/runner/nuclear_data/neutron/Na23.h5
 Reading Mg24 from /home/runner/nuclear_data/neutron/Mg24.h5
 Reading Mg25 from /home/runner/nuclear_data/neutron/Mg25.h5
 Reading Mg26 from /home/runner/nuclear_data/neutron/Mg26.h5
 Reading Al27 from /home/runner/nuclear_data/neutron/Al27.h5
 Reading Si28 from /home/runner/nuclear_data/neutron/Si28.h5
 Reading Si29 from /home/runner/nuclear_data/neutron/Si29.h5
 Reading Si30 from /home/runner/nuclear_data/neutron/Si30.h5
 Reading K39 from /home/runner/nuclear_data/neutron/K39.h5
 Reading K40 from /home/runner/nuclear_data/neutron/K40.h5
 Reading K41 from /home/runner/nuclear_data/neutron/K41.h5
 Reading Ca40 from /home/runner/nuclear_data/neutron/Ca40.h5
 Reading Ca42 from /home/runner/nuclear_data/neutron/Ca42.h5
 Reading Ca43 from /home/runner/nuclear_data/neutron/Ca43.h5
 Reading Ca44 from /home/runner/nuclear_data/neutron/Ca44.h5
 Reading Ca46 from /home/runner/nuclear_data/neutron/Ca46.h5
 Reading Ca48 from /home/runner/nuclear_data/neutron/Ca48.h5
 Reading Fe54 from /home/runner/nuclear_data/neutron/Fe54.h5
 Reading Fe56 from /home/runner/nuclear_data/neutron/Fe56.h5
 Reading Fe57 from /home/runner/nuclear_data/neutron/Fe57.h5
 Reading Fe58 from /home/runner/nuclear_data/neutron/Fe58.h5
 Minimum neutron data temperature: 294.0 K
 Maximum neutron data temperature: 294.0 K
 Preparing distributed cell instances...
 Writing summary.h5 file...
 Maximum neutron transport energy: 20000000.0 eV for N15

 ===============>     FIXED SOURCE TRANSPORT SIMULATION     <===============

 Simulating batch 1
 Simulating batch 2
 Simulating batch 3
 Simulating batch 4
 Simulating batch 5
 Creating state point statepoint.5.h5...

 =======================>     TIMING STATISTICS     <=======================

 Total time for initialization     = 3.6554e+00 seconds
   Reading cross sections          = 3.6339e+00 seconds
 Total time in simulation          = 3.8959e+01 seconds
   Time in transport only          = 3.8944e+01 seconds
   Time in active batches          = 3.8959e+01 seconds
   Time accumulating tallies       = 1.1948e-03 seconds
   Time writing statepoints        = 1.2895e-02 seconds
 Total time for finalization       = 2.7100e-07 seconds
 Total time elapsed                = 4.2621e+01 seconds
 Calculation Rate (active)         = 10267.3 particles/second

 ============================>     RESULTS     <============================

 Leakage Fraction            = 0.00190 +/- 0.00007
total time without weight windows 46s
../../_images/d133fcd90281be3a1337b47a0d4464f6b62d89e7c24d2edc15c39c97b1eca063.png

Now we want to run the same model but with weight windows.

We make use of openmc WeightWindowGenerator to make a WeightWindows object

https://docs.openmc.org/en/stable/pythonapi/generated/openmc.WeightWindows.html https://docs.openmc.org/en/latest/pythonapi/generated/openmc.WeightWindowGenerator.html

wwg = openmc.WeightWindowGenerator(
    mesh=mesh,  # this is the mesh that covers the geometry
    energy_bounds=np.linspace(0.0, 2.5e6, 10),  # 10 energy bins from 0 to max source energy
    particle_type='neutron',
)

# The simulation with Weight Windows takes more time per particle because particles are splitting
# and take more time from birth to termination for the particle including all the split particles
# we are reducing the number of particles so this simulation takes a similar amount of time
# to the previous simulation that didn't use weight windows. So we can make a fair comparison
model.settings.particles = 4900
model.settings.max_history_splits = 1_000  # controls the maximum partile splits over the entire lifetime of the particle
model.settings.weight_window_generators = wwg

Now we run the model with Weight Windows enabled and being generated during the simulation.

# this helps time the simulation
t0 = time.time()

run_and_plot(model, 'ww.png')

print(f'total time with weight windows {int(time.time()-t0)}s')
                                %%%%%%%%%%%%%%%
                           %%%%%%%%%%%%%%%%%%%%%%%%
                        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                   %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                                    %%%%%%%%%%%%%%%%%%%%%%%%
                                     %%%%%%%%%%%%%%%%%%%%%%%%
                 ###############      %%%%%%%%%%%%%%%%%%%%%%%%
                ##################     %%%%%%%%%%%%%%%%%%%%%%%
                ###################     %%%%%%%%%%%%%%%%%%%%%%%
                ####################     %%%%%%%%%%%%%%%%%%%%%%
                #####################     %%%%%%%%%%%%%%%%%%%%%
                ######################     %%%%%%%%%%%%%%%%%%%%
                #######################     %%%%%%%%%%%%%%%%%%
                 #######################     %%%%%%%%%%%%%%%%%
                 ######################     %%%%%%%%%%%%%%%%%
                  ####################     %%%%%%%%%%%%%%%%%
                    #################     %%%%%%%%%%%%%%%%%
                     ###############     %%%%%%%%%%%%%%%%
                       ############     %%%%%%%%%%%%%%%
                          ########     %%%%%%%%%%%%%%
                                      %%%%%%%%%%%

                 | The OpenMC Monte Carlo Code
       Copyright | 2011-2025 MIT, UChicago Argonne LLC, and contributors
         License | https://docs.openmc.org/en/latest/license.html
         Version | 0.15.3-dev233
     Commit Hash | 6cf0eac20578463a6d5aea3be55774488015211e
       Date/Time | 2025-08-02 09:23:59
  OpenMP Threads | 4

 Reading model XML file 'model.xml' ...
 Reading cross sections XML file...
 Reading N14 from /home/runner/nuclear_data/neutron/N14.h5
 Reading N15 from /home/runner/nuclear_data/neutron/N15.h5
 Reading O16 from /home/runner/nuclear_data/neutron/O16.h5
 Reading O17 from /home/runner/nuclear_data/neutron/O17.h5
 Reading O18 from /home/runner/nuclear_data/neutron/O18.h5
 Reading Ar36 from /home/runner/nuclear_data/neutron/Ar36.h5
 WARNING: Negative value(s) found on probability table for nuclide Ar36 at 294K
 Reading Ar38 from /home/runner/nuclear_data/neutron/Ar38.h5
 Reading Ar40 from /home/runner/nuclear_data/neutron/Ar40.h5
 Reading H1 from /home/runner/nuclear_data/neutron/H1.h5
 Reading H2 from /home/runner/nuclear_data/neutron/H2.h5
 Reading C12 from /home/runner/nuclear_data/neutron/C12.h5
 Reading C13 from /home/runner/nuclear_data/neutron/C13.h5
 Reading Na23 from /home/runner/nuclear_data/neutron/Na23.h5
 Reading Mg24 from /home/runner/nuclear_data/neutron/Mg24.h5
 Reading Mg25 from /home/runner/nuclear_data/neutron/Mg25.h5
 Reading Mg26 from /home/runner/nuclear_data/neutron/Mg26.h5
 Reading Al27 from /home/runner/nuclear_data/neutron/Al27.h5
 Reading Si28 from /home/runner/nuclear_data/neutron/Si28.h5
 Reading Si29 from /home/runner/nuclear_data/neutron/Si29.h5
 Reading Si30 from /home/runner/nuclear_data/neutron/Si30.h5
 Reading K39 from /home/runner/nuclear_data/neutron/K39.h5
 Reading K40 from /home/runner/nuclear_data/neutron/K40.h5
 Reading K41 from /home/runner/nuclear_data/neutron/K41.h5
 Reading Ca40 from /home/runner/nuclear_data/neutron/Ca40.h5
 Reading Ca42 from /home/runner/nuclear_data/neutron/Ca42.h5
 Reading Ca43 from /home/runner/nuclear_data/neutron/Ca43.h5
 Reading Ca44 from /home/runner/nuclear_data/neutron/Ca44.h5
 Reading Ca46 from /home/runner/nuclear_data/neutron/Ca46.h5
 Reading Ca48 from /home/runner/nuclear_data/neutron/Ca48.h5
 Reading Fe54 from /home/runner/nuclear_data/neutron/Fe54.h5
 Reading Fe56 from /home/runner/nuclear_data/neutron/Fe56.h5
 Reading Fe57 from /home/runner/nuclear_data/neutron/Fe57.h5
 Reading Fe58 from /home/runner/nuclear_data/neutron/Fe58.h5
 Minimum neutron data temperature: 294.0 K
 Maximum neutron data temperature: 294.0 K
 Preparing distributed cell instances...
 Writing summary.h5 file...
 Maximum neutron transport energy: 20000000.0 eV for N15

 ===============>     FIXED SOURCE TRANSPORT SIMULATION     <===============

 Simulating batch 1
 Simulating batch 2
 Simulating batch 3
 Simulating batch 4
 Simulating batch 5
 Creating state point statepoint.5.h5...
 Exporting weight windows to weight_windows.h5...

 =======================>     TIMING STATISTICS     <=======================

 Total time for initialization     = 3.6584e+00 seconds
   Reading cross sections          = 3.6305e+00 seconds
 Total time in simulation          = 6.1144e+01 seconds
   Time in transport only          = 6.0959e+01 seconds
   Time in active batches          = 6.1144e+01 seconds
   Time accumulating tallies       = 9.8322e-03 seconds
   Time writing statepoints        = 8.4869e-02 seconds
 Total time for finalization       = 2.3803e-02 seconds
 Total time elapsed                = 6.4839e+01 seconds
 Calculation Rate (active)         = 400.695 particles/second

 ============================>     RESULTS     <============================

 Leakage Fraction            = 0.00210 +/- 0.00011
total time with weight windows 68s
../../_images/645d23e03458c4e125a434a2c0da56ff2bc12d80819000eed2a480c335fc9b0f.png

You might have to fine tune the particle numbers to get the two simulations taking exactly the same amount of time to make it a fair test.

However on this laptop both simulations took 30 seconds and it is clear from the two images that the weight window simulation was more efficient at getting particles through the shielding.

Weight windows can be fine tuned for a specific problem to improve their performance.

openmc.lib allows fine control over the weight window creation and that is covered in the next notebooks.