Union tutorial on masks#

There are some geometries that are impossible to build using only the priority based system geometry system, for example making part of a cylinder thinner, which is needed for a cryostat window. In many such cases, masks can be used to solve the problem.

import mcstasscript as ms
instrument = ms.McStas_instr("python_tutorial", input_path="run_folder")

Setting up an example without masks#

First we set up an example with a thick and hollow Al cylinder and a logger to view the spatial distribution of scattering.

if instrument.mccode_version == 3:
    init = instrument.add_component("init", "Union_init") # This component has to be called init

Al_inc = instrument.add_component("Al_inc", "Incoherent_process")
Al_inc.sigma = 0.0082
Al_inc.unit_cell_volume = 66.4

Al_pow = instrument.add_component("Al_pow", "Powder_process")
Al_pow.reflections = '"Al.laz"'

Al = instrument.add_component("Al", "Union_make_material")
Al.process_string = '"Al_inc,Al_pow"'
Al.my_absorption = 100*0.231/66.4 # barns [m^2 E-28]*Å^3 [m^3 E-30]=[m E-2], factor 100

src = instrument.add_component("source", "Source_div")

src.xwidth = 0.2
src.yheight = 0.035
src.focus_aw = 0.01
src.focus_ah = 0.01


src.lambda0 = instrument.add_parameter("wavelength", value=5.0,
                                       comment="Wavelength in [Ang]")
src.dlambda = "0.01*wavelength"
src.flux = 1E13

wall = instrument.add_component("wall", "Union_cylinder")
wall.set_AT([0,0,1], RELATIVE=src)
wall.yheight = 0.15
wall.radius = 0.1
wall.material_string='"Al"' 
wall.priority = 10

wall_vac = instrument.add_component("wall_vacuum", "Union_cylinder")
wall_vac.set_AT([0,0,0], RELATIVE=wall)
wall_vac.yheight = 0.15 + 0.01
wall_vac.radius = 0.1 - 0.02
wall_vac.material_string='"Vacuum"' 
wall_vac.priority = 50

logger_zx = instrument.add_component("logger_space_zx", "Union_logger_2D_space")
logger_zx.set_RELATIVE(wall)
logger_zx.D_direction_1 = '"z"'
logger_zx.D1_min = -0.12
logger_zx.D1_max = 0.12
logger_zx.n1 = 300
logger_zx.D_direction_2 = '"x"'
logger_zx.D2_min = -0.12
logger_zx.D2_max = 0.12
logger_zx.n2 = 300
logger_zx.filename = '"logger_zx.dat"'

master = instrument.add_component("master", "Union_master")

if instrument.mccode_version == 3:
    stop = instrument.add_component("stop", "Union_stop")

instrument.show_diagram()
../_images/e35e8ea50e81cfbcddc67eb5d686efc39863c17683fab6f1ca16d2efa0d6d88d.png ../_images/a4037ce1e6076cee05b599817527cfc7d62a8d38346ba2d220721774aaa0bb83.png

In the above diagram it is clear that the wall is made of Al and the master simulates the wall and the wall_vacuum.

instrument.settings(ncount=2E6, output_path="data_folder/union_masks", suppress_output=True, mpi="auto")

data = instrument.backengine()
ms.make_sub_plot(data)
../_images/99f5ba4dcce56b58165d47b0790e680fb1db54ef60f9feac937d427ce7507a80.png

Masks#

All Union geometries can act as a mask for a list of other already defined geometries. The geometries affected by a mask will only exist inside the mask, while the parts outside will not have any effect on this simulation. This provides some interesting geometrical capabilities, for example by defining two spheres with some overlap and making one a mask of the other, a classical lens shape can be created.

The relevant parameters of all geometry components are:

  • mask_string : comma separated list of geometry names the mask should be applied to

  • mask_setting : selects between “ANY” or “ALL” mode. Default mode is “ALL”.

The mask mode is only important if several masks affect the same geometry, per default just having any of the masks overlap the target geometry allow it to exists, which correspond to the “ANY” mode. If the “ALL” mode is selected, the target geometry will only exists in regions where all the masks and itself overlap.

Note that a unique priority is still necessary, but it is not used.

Adding a window using masks#

Here we add a window to one side of the cylinder by inserting a larger vacuum cylinder, but mask it so that it is only active in the area around the window. In this way we get a nice curved window. We chose a box shape for the mask, but we could also have chosen a cylinder to get a round window.

window = instrument.add_component("window", "Union_cylinder", before="master")
window.set_AT([0,0,0], RELATIVE=wall)
window.yheight = 0.15 + 0.02
window.radius = 0.1 - 0.01
window.material_string='"Vacuum"' 
window.priority = 25

mask = instrument.add_component("mask", "Union_box", before="master")
mask.xwidth = 0.1
mask.yheight = 0.2
mask.zdepth = 0.09
mask.priority = 1
mask.mask_string='"window"'
mask.set_AT([0,0,-0.1], RELATIVE=wall)

instrument.show_diagram()
../_images/0eaabfd7d9fd83ec5e25f1ec7dfdbc7339419bbd98cd4808327d122d17c70ffb.png ../_images/8fbd06826729f5d439963df7615524c79c331d9c15ef25fce02eb663f53554ec.png

The windows was added to the diagram and is connected to the master as expected. The mask shows up as a component that only acts on the window geometry, as the mask itself is not simulated, it just modifies the window.

data = instrument.backengine()
ms.make_sub_plot(data)
../_images/40a9b3a00b4a30cafa851da984af72ab97264c6f56edf07decf6b7e551a58aa5.png

Adding an external window using a mask#

It is also possible to create a thinner section where the material is reduced from the outside. Here we need to add both a vacuum and an aluminium geometry, both of which need to have a priority lower than the original inner vacuum. One mask can handle several geometries, just include both names in the mask_string parameter.

o_window = instrument.add_component("outer_window", "Union_cylinder", before="master")
o_window.set_AT([0,0,0], RELATIVE=wall)
o_window.yheight = 0.15 + 0.03
o_window.radius = 0.1 + 0.01
o_window.material_string='"Vacuum"' 
o_window.priority = 30

o_window_al = instrument.add_component("outer_window_Al", "Union_cylinder", before="master")
o_window_al.set_AT([0,0,0], RELATIVE=wall)
o_window_al.yheight = 0.15 + 0.04
o_window_al.radius = 0.1 - 0.01
o_window_al.material_string='"Al"' 
o_window_al.priority = 31

mask = instrument.add_component("mask_outer", "Union_box", before="master")
mask.xwidth = 0.12
mask.yheight = 0.2
mask.zdepth = 0.09
mask.priority = 2
mask.mask_string='"outer_window,outer_window_Al"'
mask.set_AT([0,0,0.1], RELATIVE=wall)

instrument.show_diagram()
../_images/da3a95b419870e76317549833a6eb6cc2c0b09d278e271423f123382b24ff6f9.png ../_images/9c32240cf6ac3e015fcbdee07b24a22a06285fc599278d32cfe029a57d5cb7fe.png

For the outer window the mask_outer acts on two geometries, outer_window and outer_window_Al. Notice that both the arrow for Al and mask_outer go to outer_window_Al as they both impact that component, one as a material and the other as a mask.

data = instrument.backengine()
ms.make_sub_plot(data)
../_images/2b637ce07e4e046de1db46f3847d6bb28d7dbcb0fcebcdc0005416fca3ad2420.png

Masks are flexible#

Masks can be used to create many interesting shapes with few geometries. Below we create a octagon with rounded corners using just three geometries, two of these being masks. Using masks expands the space of possible geometries greatly, and in many cases can also be a performance advantage when they reduce the number of geometries needed to describe the desired geometry.

instrument = ms.McStas_instr("python_tutorial", input_path="run_folder", output_path="data_folder/masks")

if instrument.mccode_version == 3:
    instrument.add_component("init", "Union_init")

Al_inc = instrument.add_component("Al_inc", "Incoherent_process")
Al_inc.sigma = 0.0082
Al_inc.unit_cell_volume = 66.4

Al_pow = instrument.add_component("Al_pow", "Powder_process")
Al_pow.reflections = '"Al.laz"'

Al = instrument.add_component("Al", "Union_make_material")
Al.process_string = '"Al_inc,Al_pow"'
Al.my_absorption = 100*0.231/66.4 # barns [m^2 E-28]*Å^3 [m^3 E-30]=[m E-2], factor 100

src = instrument.add_component("source", "Source_div")

src.xwidth = 0.2
src.yheight = 0.035
src.focus_aw = 0.01
src.focus_ah = 0.01

instrument.add_parameter("wavelength", value=5.0, comment="Wavelength in [Ang]")
src.lambda0="wavelength"
src.dlambda="0.01*wavelength"
src.flux = 1E13

box = instrument.add_component("box", "Union_box")
box.set_AT([0,0,1], RELATIVE=src)
box.xwidth = 0.2
box.yheight = 0.1
box.zdepth = 0.2
box.material_string='"Al"' 
box.priority = 10

# Cut the corners by using an identical box rotated 45 deg around y
box_mask = instrument.add_component("box_mask", "Union_box")
box_mask.set_AT([0,0,0], RELATIVE=box)
box_mask.set_ROTATED([0,45,0], RELATIVE=box)
box_mask.xwidth = 0.2
box_mask.yheight = 0.11 # Have to increase yheight to avoid perfect overlap
box_mask.zdepth = 0.2
box_mask.mask_string='"box"' 
box_mask.priority = 50

# Round the corners with a cylinder mask
cyl_mask = instrument.add_component("cylinder_mask", "Union_cylinder")
cyl_mask.set_AT([0,0,0], RELATIVE=box)
cyl_mask.radius = 0.105
cyl_mask.yheight = 0.12
cyl_mask.mask_string='"box"' 
cyl_mask.priority = 51

logger_zx = instrument.add_component("logger_space_zx", "Union_logger_2D_space")
logger_zx.set_RELATIVE(box)
logger_zx.D_direction_1 = '"z"'
logger_zx.D1_min = -0.12
logger_zx.D1_max = 0.12
logger_zx.n1 = 300
logger_zx.D_direction_2 = '"x"'
logger_zx.D2_min = -0.12
logger_zx.D2_max = 0.12
logger_zx.n2 = 300
logger_zx.filename = '"logger_zx.dat"'

master = instrument.add_component("master", "Union_master")

if instrument.mccode_version == 3:
    stop = instrument.add_component("stop", "Union_stop")

instrument.show_diagram()
../_images/0eaabfd7d9fd83ec5e25f1ec7dfdbc7339419bbd98cd4808327d122d17c70ffb.png ../_images/5cfa21995796ba237b299cbba0a47032d5ebce4d601919d09490428179083f6c.png
data = instrument.backengine()
INFO: Using directory: "/home/runner/work/McStasScript/McStasScript/docs/source/tutorial/data_folder/masks"
INFO: Regenerating c-file: python_tutorial.c
CFLAGS= -DFUNNEL 
        
WARNING:
 The parameter format of Al_pow is initialized 
 using a static {,,,} vector.
  -> Such static vectors support literal numbers ONLY.
  -> Any vector use of variables or defines must happen via a 
     DECLARE/INITIALIZE pointer.


-----------------------------------------------------------

Generating single GPU kernel or single CPU section layout: 

-----------------------------------------------------------

Generating GPU/CPU -DFUNNEL layout:
Component master is NOACC, CPUONLY=1
-> FUNNEL mode enabled, SPLIT within buffer.
-> CPU section from component master
-> GPU kernel from component stop

-----------------------------------------------------------
INFO: Recompiling: ./python_tutorial.out
lto-wrapper: warning: using serial compilation of 4 LTRANS jobs
lto-wrapper: note: see the '-flto' option documentation for more information
INFO: ===
Opening input file '/home/runner/micromamba/envs/mcstasscript-docs/share/mcstas/resources/data/Al.laz' (Table_Read_Offset)
Table from file 'Al.laz' (block 1) is 26 x 18 (x=1:8), constant step. interpolation: linear
  '# TITLE *Aluminum-Al-[FM3-M] Miller, H.P.jr.;DuMond, J.W.M.[1942] at 298 K; ...'
PowderN: Al_pow: Reading 26 rows from Al.laz
PowderN: Al_pow: Read 26 reflections from file 'Al.laz'
PowderN: Al_pow: Vc=66.4 [Angs] sigma_abs=0.924 [barn] sigma_inc=0.0328 [barn] reflections=Al.laz
Union_master component master initialized sucessfully
*** TRACE end *** 
Detector: logger_space_zx_I=3999.83 logger_space_zx_ERR=10.3645 logger_space_zx_N=159509 "logger_zx.dat"
INFO: Placing instr file copy python_tutorial.instr in dataset /home/runner/work/McStasScript/McStasScript/docs/source/tutorial/data_folder/masks
INFO: Placing generated c-code copy python_tutorial.c in dataset /home/runner/work/McStasScript/McStasScript/docs/source/tutorial/data_folder/masks
ms.make_sub_plot(data)
../_images/7687d177aba8c0b51cd75fa474f302c500d067fea051e1af43cb2a802b6c1c18.png