Component object

McStas components are the essential building blocks used to make McStas simulations. This sections focuses on how to work with component objects in McStasScript.

Since a component is always part of an instrument, new components are created in the instrument object so that they may immediately be registered and controlled by that instrument object.

import mcstasscript as ms

instrument = ms.McStas_instr("component_examples")

Creating a component object

A component object is returned from the add_component and copy_component instrument object methods. When creating a component object, it needs an instance name and the name of the component in the library. When copying a component, the same library component is used.

source = instrument.add_component("source", "Source_div")
print(source)
COMPONENT source = Source_div(
  xwidth : Required parameter not yet specified
  yheight : Required parameter not yet specified
  focus_aw : Required parameter not yet specified
  focus_ah : Required parameter not yet specified
)
AT (0, 0, 0) ABSOLUTE

Setting component parameters

Since the underlying McStas component is read, McStasScript is aware of the parameters, including which are required from the user. These can be set directly as attributes or with the set_parameters method.

source.xwidth = 0.1
source.set_parameters(yheight=0.03)
print(source)
COMPONENT source = Source_div(
  xwidth = 0.1, // [m]
  yheight = 0.03 // [m]
  focus_aw : Required parameter not yet specified
  focus_ah : Required parameter not yet specified
)
AT (0, 0, 0) ABSOLUTE

Trying to set a parameter that does not exist would lead to an error. The full list of parameters in a component can be shown with show_parameters. This also show the current state of the component, including default values and values set by the user.

source.show_parameters()
 ___ Help Source_div ________________________________________________________________
|optional parameter|required parameter|default value|user specified value|
xwidth = 0.1 [m] // Width of source
yheight = 0.03 [m] // Height of source
focus_aw [deg] // FWHM (Gaussian) or maximal (uniform) horz. width divergence
focus_ah [deg] // FWHM (Gaussian) or maximal (uniform) vert. height divergence
E0 = 0.0 [meV] // Mean energy of neutrons.
dE = 0.0 [meV] // Energy half spread of neutrons.
lambda0 = 0.0 [Ang] // Mean wavelength of neutrons (only relevant for E0=0)
dlambda = 0.0 [Ang] // Wavelength half spread of neutrons.
gauss = 0.0 [0|1] // Criterion
flux = 1.0 [1/(s cm 2 st energy_unit)] // flux per energy unit, Angs or meV
-------------------------------------------------------------------------------------

Setting the component position and rotation

The default location of a component is at the origin of the absolute coordinate system used in McStas. The placement of components in space is very important, and McStas provides easy coordinate transfers when placing each component, which are accessible from McStasScript. In McStas one specifies the position with AT and orientation with ROTATED. Each of these can be relative to a reference, which is set with RELATIVE. This behavior is adopted in McStasScript where set_AT and set_ROTATED methods are available.

If for example we want to shift our source a bit horizontally, we would use a list as a vector.

source.set_AT([0.02, 0, 0])
print(source)
COMPONENT source = Source_div(
  xwidth = 0.1, // [m]
  yheight = 0.03 // [m]
  focus_aw : Required parameter not yet specified
  focus_ah : Required parameter not yet specified
)
AT (0.02, 0, 0) ABSOLUTE

In McStas convention the beam direction is along z, and it is common to place components some distance down beam, and so if a number is given instead of a list, it is assumed to be along z.

source.set_AT(0.01)
print(source)
COMPONENT source = Source_div(
  xwidth = 0.1, // [m]
  yheight = 0.03 // [m]
  focus_aw : Required parameter not yet specified
  focus_ah : Required parameter not yet specified
)
AT (0, 0, 0.01) ABSOLUTE

The position and rotation can also be specified when creating a new component with the AT and RELATIVE keywords. With the RELATIVE option we are now in the coordinate system of the reference component.

monitor = instrument.add_component("PSD", "PSD_monitor", AT=[0, 0, 2], RELATIVE=source)
print(monitor)
COMPONENT PSD = PSD_monitor(
)
AT (0, 0, 2) RELATIVE source

Setting the rotation is similar with the set_ROTATED method. The rotation is specified with euler angles in degrees.

monitor.set_ROTATED([0, 4, 0])
print(monitor)
COMPONENT PSD = PSD_monitor(
)
AT (0, 0, 2) RELATIVE source
ROTATED (0, 4, 0) RELATIVE source

Here McStasScript assumed that the rotation should be relative to the source, but it is possible to have another reference for the rotation as shown here.

monitor.set_ROTATED([0, 4, 0], RELATIVE="ABSOLUTE")
print(monitor)
COMPONENT PSD = PSD_monitor(
)
AT (0, 0, 2) RELATIVE source
ROTATED (0, 4, 0) ABSOLUTE

When setting both position and rotation at initialization one can distinguish between the reference for position and rotation with the AT_RELATIVE and ROTATED_RELATIVE keywords.

guide = instrument.add_component("guide", "Guide",
                                 AT=2, AT_RELATIVE=source, 
                                 ROTATED=[0,0,0], ROTATED_RELATIVE=monitor)
print(guide)
COMPONENT guide = Guide(
  w1 : Required parameter not yet specified
  h1 : Required parameter not yet specified
  l : Required parameter not yet specified
)
AT (0, 0, 2) RELATIVE source
ROTATED (0, 0, 0) RELATIVE PSD

Using parameters in a component

It is common to use defined parameters and declare variables in a component, and they can even be defined directly where needed. The parameter section of the documentation explain the details of setting up parameters, but here it is shown how they are used in components.

After creating a parameter, it can be used when assigning component parameters. Just use a string with the same name, and it can even contain basic math.

instrument.add_parameter("guide_width")
guide.w1 = "guide_width"
guide.h1 = "1.5*guide_width"
print(guide)
COMPONENT guide = Guide(
  w1 = guide_width, // [m]
  h1 = 1.5*guide_width // [m]
  l : Required parameter not yet specified
)
AT (0, 0, 2) RELATIVE source
ROTATED (0, 0, 0) RELATIVE PSD

When creating a parameter or declare variable, an object is returned which can be provided to the component.

guide_length = instrument.add_declare_var("double", "guide_length")
guide.l = guide_length

guide.m = instrument.add_parameter("guide_m_value")

print(guide)
COMPONENT guide = Guide(
  w1 = guide_width, // [m]
  h1 = 1.5*guide_width, // [m]
  l = guide_length, // [m]
  m = guide_m_value // [1]
)
AT (0, 0, 2) RELATIVE source
ROTATED (0, 0, 0) RELATIVE PSD

These features cover the majority of use-cases for McStas components.

guide.set_comment("This guide points in the same direction as the PSD monitor")
print(guide)
// This guide points in the same direction as the PSD monitor
COMPONENT guide = Guide(
  w1 = guide_width, // [m]
  h1 = 1.5*guide_width, // [m]
  l = guide_length, // [m]
  m = guide_m_value // [1]
)
AT (0, 0, 2) RELATIVE source
ROTATED (0, 0, 0) RELATIVE PSD

Advanced features

In McStas / McXtrace there are a number of advanced features which can be used to control component behavior in the simulation. In the tutorial section these keywords are explained and demonstrated, here it is shown how they are applied. Most of these can be set when creating a component or through component methods.

SPLIT

The split keyword instructs the simulation to split the ray into multiple smaller rays to improve statistics. This is done before the component that has the SPLIT keyword applied. The split value must be an integer. Use the SPLIT keyword to set split while creating a new component.

guide.set_SPLIT(150)
print(guide)
// This guide points in the same direction as the PSD monitor
SPLIT 150 COMPONENT guide = Guide(
  w1 = guide_width, // [m]
  h1 = 1.5*guide_width, // [m]
  l = guide_length, // [m]
  m = guide_m_value // [1]
)
AT (0, 0, 2) RELATIVE source
ROTATED (0, 0, 0) RELATIVE PSD
print(instrument.add_component("Splitter", "Arm", SPLIT=180))
SPLIT 180 COMPONENT Splitter = Arm(
)
AT (0, 0, 0) ABSOLUTE

The split keyword can be removed from a component by setting split to 0.

guide.set_SPLIT(0)
print(guide)
// This guide points in the same direction as the PSD monitor
COMPONENT guide = Guide(
  w1 = guide_width, // [m]
  h1 = 1.5*guide_width, // [m]
  l = guide_length, // [m]
  m = guide_m_value // [1]
)
AT (0, 0, 2) RELATIVE source
ROTATED (0, 0, 0) RELATIVE PSD

EXTEND

A component can be extended with additional C code that has a special scope. It can access both parameters and variable from the component, and the instrument. Lines of extend code can be added with the append_EXTEND method. Beware that this code is executed for each neutron that reach the component and during the ray-tracing simulation.

instrument.add_declare_var("int", "flag")
guide.append_EXTEND("  if (x>0) flag = 1;")
guide.append_EXTEND("  else flag = 0;")
print(guide)
// This guide points in the same direction as the PSD monitor
COMPONENT guide = Guide(
  w1 = guide_width, // [m]
  h1 = 1.5*guide_width, // [m]
  l = guide_length, // [m]
  m = guide_m_value // [1]
)
AT (0, 0, 2) RELATIVE source
ROTATED (0, 0, 0) RELATIVE PSD
EXTEND %{
  if (x>0) flag = 1;
  else flag = 0;
%}

WHEN

The WHEN keyword can be used to set a condition for when a component should be applied to the ray. The input for a WHEN condition is a logical c expression that can use variables in the instrument scope. Since just the expression is needed, there is no terminating semicolon.

Below a monitor is created which checks the flag defined during the extend example. This monitor will thus only record rays that had a positive x coordinate after the guide. WHEN and EXTEND are often used in conjunction with each other in this way, this is also explored in the tutorial.

left_PSD = instrument.add_component("Left_side_PSD", "PSD_monitor")
left_PSD.set_WHEN("flag == 1")
print(left_PSD)
COMPONENT Left_side_PSD = PSD_monitor(
) WHEN (flag == 1)
AT (0, 0, 0) ABSOLUTE

The WHEN keyword can also be applied when adding a component.

right_PSD = instrument.add_component("Right_side_PSD", "PSD_monitor", WHEN="flag == 0")
print(right_PSD)
COMPONENT Right_side_PSD = PSD_monitor(
) WHEN (flag == 0)
AT (0, 0, 0) ABSOLUTE

JUMP

The JUMP keyword allows the user to modify the order in which components are executed on a per neutron level. Using JUMP is complex with several pitfalls, but the syntax is simple. The system is explored in the tutorial on this site, but for full documentation refer to the McStas / McXtrace documentation.

Here the jump keyword is used to jump from the guide component to the target_arm in case the y component of the velocity is positive. That would skip the monitors!

instrument.add_component("target_arm", "Arm", RELATIVE=guide)
guide.set_JUMP("target_arm WHEN (vy>0)")
print(guide)
// This guide points in the same direction as the PSD monitor
COMPONENT guide = Guide(
  w1 = guide_width, // [m]
  h1 = 1.5*guide_width, // [m]
  l = guide_length, // [m]
  m = guide_m_value // [1]
)
AT (0, 0, 2) RELATIVE source
ROTATED (0, 0, 0) RELATIVE PSD
EXTEND %{
  if (x>0) flag = 1;
  else flag = 0;
%}
JUMP target_arm WHEN (vy>0)

GROUP

The GROUP keyword allows the user to set a number of components in parallel, but only allows a ray to interact with one of them. When two components have the same group name, this behavior is applied.

crystal_1 = instrument.add_component("mono_1", "Monochromator_flat")
crystal_1.set_GROUP("monochromator")

crystal_2 = instrument.add_component("mono_2", "Monochromator_flat", GROUP="monochromator")

# Place two crystals in parallel
crystal_1.set_AT([0.01, 0, 5]) 
crystal_2.set_AT([-0.01, 0, 5])

print(crystal_1)
print(crystal_2)
COMPONENT mono_1 = Monochromator_flat(
)
AT (0.01, 0, 5) ABSOLUTE
GROUP monochromator
COMPONENT mono_2 = Monochromator_flat(
)
AT (-0.01, 0, 5) ABSOLUTE
GROUP monochromator

Set c code

It is possible to add C code between components in the instrument trace section. In McStasScript this is attached to component objects as one can insert code before or after a component.

code_arm = instrument.add_component("code_arm", "Arm", c_code_before="// code before")
code_arm.set_c_code_after("// code after")
print(code_arm)
// code before
COMPONENT code_arm = Arm(
)
AT (0, 0, 0) ABSOLUTE
// code after

Here this is used to set comments, but there is also the option of setting a comment directly with the comment keyword.

commented_component = instrument.add_component("commented_component", "Arm", comment="This is a comment!")
print(commented_component)
// This is a comment!
COMPONENT commented_component = Arm(
)
AT (0, 0, 0) ABSOLUTE