diff --git "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-1.md" "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-1.md" index 7ef411d5a..95d31a97c 100644 --- "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-1.md" +++ "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-1.md" @@ -1,5 +1,7 @@ # Exercise 1 - Parameters, Drivers, Components +## Introduction to Reflectometry and Specific Terminology + ![image](refl_beamline_setup.PNG) Fundamentally, the Reflectometry Config defines a geometry model of the beamline, which we use to calculate relative positions for each axis in the model based on the current beam path. We think about this model in 3 layers: @@ -18,73 +20,194 @@ To Note: - Parameters/Drivers have a many-to-one relationship to Components - Parameters and Drivers do not _have_ to be a one-to-one match, even though often this is the case (like a height offset parameter on the POLREF bench will equally displace front and back height jacks). +## The `config.py` file + In the following exercise, we will add a single item to the reflectometry configuration, a Supermirror, complete with Parameters and Drivers. Before we start making changes, let's review the content of the blank config in front of you: -``` -# Reference documentation for writing reflectometry configurations available at Reflectometry-Configuration - -from ReflectometryServer import * - +```Python +from typing import Dict -def get_beamline(macros): - """ +from ReflectometryServer.beamline import Beamline +from ReflectometryServer.config_helper import ( + add_mode, + get_configured_beamline, +) - Returns: The beamline model - """ +def get_beamline(macros: Dict[str, str]) -> Beamline: ######################### # FIXED BEAMLINE VALUES # ######################### - add_constant(BeamlineConstant("OPI", "SURF", "OPIs to show on front panel")) - - DISTANCE = 10.0 - angle_of_movement = 90 # Modes - nr = add_mode("NR") + _nr = add_mode("NR") ############################## # BEAMLINE MODEL STARTS HERE # ############################## return get_configured_beamline() + ``` -- `from ReflectometryServer import *`: This is required to use classes and helper methods which are used to construct the model of the beamline + +- `from typing import Dict` relates to the output of the function and the enforcement of typing via PyRight. +- The various imports from `ReflectometryServer` are the items used below. Any classes or helper methods needed to construct the model of the beamline is within this namespace. - `def get_beamline`: While the python config file gives you tremendous freedom to include arbitrary python code, this is the one method we expect to be here as the reflectometry server calls it on config load. It should return an object of type `Beamline` -- `add_constant(BeamlineConstant("OPI", "SURF", "OPIs to show on front panel"))`: This adds a PV intended to expose constant values that are used across the instrument so that these do not have to be defined in multiple places. In this case, we are creating the PV `REFL_01:CONST:OPI` which holds the value "SURF". This PV then is used to populate the Front Panel OPI with hardcoded items for the named beamline. -- `DISTANCE`: This is a constant we will be using just inside the config file to space every item in the beamline model an equal distance apart for simplicity as it helps with understanding & verifying position tracking behaviour. This is just for the training course, you will not find this on a real beamline. -- `ANGLE_OF_MOVEMENT`: Another constant we will be using throughout the config. This lets us define the angle of movement of our physical components relative to the natural beam which defines our coordinate system, i.e. the angle between the dotted blue line and the dotted grey lines above. Usually this is 90 + 1.5 for TS1, and 90 + 2.3 for TS2 instruments. However, in this training course for now we will assume that the natural beam is level to the floor for simplicity. -- `nr = add_mode("NR")`: Modes are "presets" used to define which devices are in use & should automatically track depending on the type of experiment being run. +- The `fixed beamline values` will contain variables and things which do not change. For example, the distances between components. +- The `beamline model` describes the actual beamline, in order, from the beam entry point to the detectors. +- `_nr = add_mode("NR")`: Modes are "presets" used to define which devices are in use & should automatically track depending on the type of experiment being run. At least one mode should be specified. This version has the underscore `_` at the front because at present the variable is not used, and PyRight requires the variable to be used, but it accepts one with an underscore at the front. ## Exercise 1 +The first thing to add to our configuration is the `NATURAL_ANGLE` as per [here](../Reflectometry-Configuration). This defines the angle of movement of the physical components relative to the natural beam which defines our coordinate system, i.e. the angle between the dotted blue line and the dotted grey lines above. Usually this is 90 + 1.5 for TS1, and 90 + 2.3 for TS2 instruments. However, in this training course for now we will assume that the natural beam is level to the floor for simplicity. +The constants which need to be named according to the interactions above do need to be set somewhere. This could be as a set of constants in another file, or simply a block before the definition for `get_beamline`, which is what will be used here. For the training, please set `NATURAL_ANGLE` to `90`. -Add the following to your beamline model. -1. A component for the Supermirror, which may reflect the beam -1. Two beamline parameters (for SM Height offset and SM angle) -1. Two drivers (for driving the SM height and SM angle axes) -You can use the generic code below for reference: +This is a fixed beamline parameter, so can be set as a constant value in the `FIXED BEAMLINE VALUES` section. +```python +def get_beamline(macros: Dict[str, str]) -> Beamline: + ######################### + # FIXED BEAMLINE VALUES # + ######################### + + # Constants + add_constant(BeamlineConstant("NATURAL_ANGLE", NATURAL_ANGLE, "The angle between the natural beamline and our coordinate system")) + # Modes + _nr = add_mode("NR") ``` +Note that `add_constant` is in the `ReflectometryServer.config_helper` library, while `BeamlineConstant` needs to be imported from the `ReflectometryServer.beamline_constant` library. + +The first item on our imagined reflectometer is going to be a supermirror. This supermirror will have a few things that need to be added. + +### 1. Add a constant for the distance +The supermirror will have a distance from the beam entry point, and this will be constant. Add it in a similar way to the previous constant, using `SM_Z` as a name, and a value of `20.0`. + +### 2. Add the supermirror component +The supermirror is considered a component within the beam line model, see above for the definition of this. +The generic code for adding a component is as follows: +```python component = add_component(Component("comp_name", PositionAndAngle(Y, Z, Angle))) -add_parameter(AxisParameter("param_name", component, ChangeAxis.[Axis parameter])) -add_driver(IocDriver(component, ChangeAxis.[Axis parameter], MotorPVWrapper("MOT:MTRXXXX"))) ``` - -Some Tips: -- There are different subclasses of Components: +`add_component` is in `ReflectometryServer.config_helper`, `Component` is in `ReflectometryServer.components`, and `PositionAndAngle` is in `ReflectometryServer.geometry`. +There are different subclasses of Components: - `Component` just tracks the beam path in height - `TiltingComponent` tracks the beam path in height and angle - `ReflectingComponent` tracks the in height and angle and can also change the path of the beam for components further downstream -- `ChangeAxis` is used to link a given `AxisParameter` to a given `IocDriver`. For more information on the different options for `AxisParameter, see [here](../Reflectometry-Configuration) -- `MTRXXXX` should be replaced with the appropriate motor axis. In this case, we are looking for "Supermirror Height" and "Supermirror Rot" in the table of motors. +This will be a `ReflectingComponent`, as it can impact on the direction of the beam. It can be added using the `add_component` helper method shown above. This will need a `name` and a geometry setup, in this case a `PositionAndAngle` setup will be suitable, setting `Y` to `0`, `Z` to `SM_Z`, and `Angle` to `NATURAL_ANGLE`. Don't forget to import this from `ReflectometryServer.components` + +### 3. Add parameters for the supermirror +Our supermirror has two things which can be varied, its height, and its angle. Each of these will need a parameter to interact with them. +The generic code for adding a parameter is as follows: +```python +add_parameter(AxisParameter("param_name", component, ChangeAxis.[Axis parameter], "description of parameter")) +``` +`add_parameter` is in `ReflectometryServer.config_helper`, `AxisParameter` is in `ReflectometryServer.parameters`, and `ChangeAxis` is in `ReflectometryServer.geometry`. +`ChangeAxis` is used to link a given `AxisParameter` to a given `IocDriver`. For more information on the different options for `AxisParameter`, see [here](../Reflectometry-Configuration) +Here, the angle, to be called `SMANGLE`, will be of the type `ANGLE` and the height, to be called `SMOFFSET`, of type `POSITION` +Note that you need to name parameters according to a certain standard in order to be able to view them readily in the reflectometry OPIs. That naming makes the use of a description, which will appear as a tooltip in the GUI which can be extremely useful. + +### 4. Add drivers for the supermirror +Those items which can be varied here will need a driver as well, although that isn't always the case for every parameter. +The generic code for adding a driver is as follows: +```python +add_driver(IocDriver(component, ChangeAxis.[Axis parameter], MotorPVWrapper("MOT:MTRXXXX"))) +``` +Don't forget to add the imports. `add_driver` is in `ReflectometryServer.config_helper`, `IocDriver` is in `ReflectometryServer.ioc_driver`, and `MotorPVWrapper` is in `ReflectometryServer.pv_wrapper` +`MTRXXXX` should be replaced with the appropriate motor axis. In this case, they should have been set up as `MTR0207` for the angle, and `MTR0206` for the height (which is still has an `Axis Parameter` of `POSITION`. ## To Test -Once you are done making changes, you can load the updated config by restarting the REFL_01 IOC. You should be able to see 2 parameters in the "Collimation Plane Parameters" tab, that, when set, will move the appropriate Galil axes. +Once you are done making changes, you can load the updated config by restarting the REFL_01 IOC. +On the `Front Panel` tab, the `SM Angle` value should now be visible. Don't worry about the rest of the disconnected items, they will be added in as you progress through the exercises. +You should be able to see 2 parameters in the `Collimation Plane Parameters` tab, that, when set, will move the appropriate Galil axes. If you hover the parameter names the description should appear in the tool tip. +You should also be able to see 2 constants in the `Constants` tab. ## Troubleshooting: - **The Reflectometry server gets stuck Initialising** - Likely trying to monitor a PV that does not exist, check the parameters on your IocDrivers - **The Reflectometry server reports an Error status!** - There is an error somewhere in your config file: Check the log in `/Instrument/var/logs/ioc/REFL_01_.log` for stack traces - **Parameter is not showing up!** - Check you have defined the right ChangeAxes, if not they may show in "Other Parameters" instead. - **Parameter is there but I can't set it!** - Check you can move the motors in question from the low motor table. If so, check the parameter you are setting and the IocDriver for the Galil axis have matching ChangeAxes + +## Solution +
+Should you have trouble the following is what the code could look like + +```python +from typing import Dict + +from ReflectometryServer.beamline import Beamline +from ReflectometryServer.beamline_constant import BeamlineConstant +from ReflectometryServer.components import ( + ReflectingComponent, +) +from ReflectometryServer.config_helper import ( + add_component, + add_constant, + add_driver, + add_mode, + add_parameter, + get_configured_beamline, +) +from ReflectometryServer.geometry import ChangeAxis, PositionAndAngle +from ReflectometryServer.ioc_driver import IocDriver +from ReflectometryServer.parameters import AxisParameter +from ReflectometryServer.pv_wrapper import MotorPVWrapper + +# Beamline Constants +NATURAL_ANGLE = 90 +SM_Z = 20.0 + + +def get_beamline(macros: Dict[str, str]) -> Beamline: + ######################### + # FIXED BEAMLINE VALUES # + ######################### + + # Constants + add_constant( + BeamlineConstant( + "NATURAL_ANGLE", + NATURAL_ANGLE, + "The difference between the beam and straight through", + ) + ) + add_constant(BeamlineConstant("SM_Z", SM_Z, "The distance to the supermirror")) + + # Modes + _nr = add_mode( + "NR" + ) # Using underscre to pass pyright as mode has to be created but is not used + + ############################## + # BEAMLINE MODEL STARTS HERE # + ############################## + + # Mirror + mirror_comp = add_component( + ReflectingComponent("Mirror", PositionAndAngle(0, SM_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "SMANGLE", + mirror_comp, + ChangeAxis.ANGLE, + description="Angle of the Supermirror", + ) + ) + add_parameter( + AxisParameter( + "SMOFFSET", + mirror_comp, + ChangeAxis.POSITION, + description="Vertical Position of the Supermirror", + ) + ) + add_driver(IocDriver(mirror_comp, ChangeAxis.ANGLE, MotorPVWrapper("MOT:MTR0207"))) + add_driver( + IocDriver(mirror_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0206")) + ) + + return get_configured_beamline() + +``` +
diff --git "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-2.md" "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-2.md" index 5011480be..3d2ec5194 100644 --- "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-2.md" +++ "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-2.md" @@ -10,24 +10,208 @@ In the previous exercise, we created parameters of type `AxisParameter`. These r In the following exercise, we will add some parameters for Slit gaps & centres (which are a type of DirectParameter), and flesh out the beamline a bit more and solidify what we learned in the last exercise. ## Exercise 2 +At the end of this exercise your beamline model should have the following items (in this order): +- Slit 1, with a height +- a supermirror, with a height and an angle (as was set up in exercise 1) +- Slit 2, with a height +- A sample stack, with height, translation, phi, and psi axes. -Add the following items to your configuration: -- Slit 1: Should have VG, VC, HG, HC parameters as well as a linear height offset. -- (from Exercise 1): Supermirror, unchanged -- Slit 2: Equivalent to Slit 1 but using the right axes -- Sample: Should have parameters PHI and SA_OFFSET which drive the angle and height relative to the reflected beam. Should also have PSI and TRANS parameters. +### 1. Add Slit 1 +Slit 1 exists on our imaginary beamline between the source and the supermirror, this means it will have to be added to the beamline model before the supermirror. +The slits have already been set up with the addition of the `jaws.cmd` during the setup. +You will need to add a constant for the distance to these slits, `S1_Z`, which needs to be set to `10.0`. +You can add slit parameters to your config with a helper method, e.g.:`add_slit_parameters(slit_number=1, include_centres=True)`, for this slit set the centres should be included. +This helper method is in `ReflectometryServer.config_helper`. +As with the supermirror in the previous exercise add a component for the slits, using `0.0`, `S1_Z`, and the `NATURAL_ANGLE` to set the `PositionAndAngle` of the component. +Add another parameter here, but this time the axis parameter is a `POSITION` related to the height of the slit set, which has to be called `S1OFFSET`. +Add the driver for that parameter pointing at `MTR0301`. -Additional notes: -- Perhaps counterintuitively, we do not want the sample to change the beam path! While the sample reflects the beam in the physical world, in the reflectometry server this is handled via a special parameter "Theta" which we will talk about more later. We do, however, want this component to track the beam in both height and angle. -- Jaws records should already be configured on the `REFL_TRAINING` branch -- You can add slit parameters to your config with a helper method, e.g.:`add_slit_parameters(slit_number=1, include_centres=True)` +### 2. Add Slit 2 +Slit 2 exists on our imaginary beamline after the supermirror. +The slits have already been set up with the addition of the `jaws.cmd` during the setup. +You will need to add a constant for the distance to these slits, `S2_Z`, which needs to be set to `S1_Z + 10`. Whilst these distances can be absolute, they are often given as relative to the previous item on the beamline. +Add the slit parameters to your config with the helper method, including the centres and making sure you set the `slit_number` to `2`. +As previously add a component for these slits, using `0.0`, `S2_Z`, and the `NATURAL_ANGLE` to set the `PositionAndAngle` of the component. +Add another parameter here, called `S2OFFSET`, with a `POSITION` axis parameter related to the height of the slit set. +Add the driver for that parameter pointing at `MTR0302`. + +### 3. Add the Sample stack +The sample is placed after Slits 2. +We added in the axes for this during the setup via the `axes.cmd`. +Again, a distance will be needed, this time it is `SAMPLE_Z` and should be set to `S2_Z + 10.0`. +Add parameters and drivers for the height, translation, phi, and psi, which should be called `SAMPOFFSET`, `SAMPTRANS`, `SAMPPHI`, and `SAMPPSI`. +The height (or vertical position) is a `POSITION` axis, on motor `MTR0307`, phi (or the pitch) is an `ANGLE` on `MTR0306`, psi (or the roll) is a `PSI` on `MTR0308`, and the translation (or horizontal position) is a `TRANS` on `MTR0305`. +Perhaps counterintuitively, we do not want the sample to change the beam path! While the sample reflects the beam in the physical world, in the reflectometry server this is handled via a special parameter "Theta" which we will talk about more later. We do, however, want this component to track the beam in both height and angle, so it's a `TiltingComponent`, which should be imported from `ReflectometryServer.components`. ## To Test -Once you have added all these components, you should now be able to set the parameters and see the related motor axes move as in the previous exercise. You now also have enough in your beamline model to see beamline parameters react to changes in the beam: -1. Make sure all parameters are at 0 to start with -1. Set an angle SP for `sm_angle`. I recommend 22.5 as this results in a reflection angle of 45 degrees - this produces tracked positions for downstream components that are easy to understand. -1. When you move `sm_angle` to this setpoint you should now see the RBV of all downstream components change. This is because while the physical axes have not moved, the *beam* has, so their relative positions are now different. - - `s2_offset` should read equivalent to its distance due to the 45 degree angle i.e. **-10** (negative because it is still centred on the natural beam i.e. below the new reflected beam - - `sa_offset` similar to `s2_offset` i.e. should be at -20 - - `sa_phi` as still sitting perpendicular to the natural beam, but the reflected beam has been bounced up 45 degrees, i.e. this should now read back -45 -1. If you now click move on any of the downstream parameters, you should see it re-apply its last SP of 0, i.e. move to re-centre itself on the new, reflected beam. The Motor axis should now read back the offset required to move these parameters back into the beam i.e. 10, 20 and 45 respectively. +Once you have added all these components, you should now be able to set the parameters and see the related motor axes move as in the previous exercise. You now also have enough in your beamline model to see beamline parameters react to changes in the beam. +1. Go to the table of motors and make sure all are at a `0` position. +2. Restart the IOC to pick up the updated `config.py`. +3. When you look at the `Front Panel` you should now see fewer disconnected items, similarly there should be more values available in the following tabs. +4. If you go to the `Collimation` tab, and set the SP of `SMANGLE` to `22.5` (this results in a reflection angle of 45 degrees - which produces tracked positions for downstream components that are easy to understand), when you click on `Move` you should now see the RBV of all downstream components change. This is because while the physical axes have not moved, the *beam* has, so their relative positions are now different. + - `S2OFFSET` should read equivalent to its distance due to the 45 degree angle i.e. **-10** (negative because it is still centred on the natural beam i.e. below the new reflected beam + - `SAMPOFFSET` similar to `S2OFFSET` i.e. should be at -20 + - `SAMPPHI` as still sitting perpendicular to the natural beam, but the reflected beam has been bounced up 45 degrees, i.e. this should now read back -45 +5. At this stage, if you look at the table of motors, only the supermirror angle will have moved, everything else is reported according to the relationship to the beam. +6. If you now click move on any of the downstream parameters, you should see it re-apply its last SP of 0, i.e. move to re-centre itself on the new, reflected beam. When the move has finished the `Collimation` tab in the reflectometry perspective should have 0s for everything except `SMANGLE`. The appropriate motor axes on the table of motors should now read back the offset required to move these parameters back into the beam i.e. 10, 20 and 45 respectively, alongside the value of 22.5 for the supermirror angle. + +## Solution +
+Should you have trouble the following is what the code could look like + +```python +from typing import Dict + +from ReflectometryServer.beamline import Beamline +from ReflectometryServer.beamline_constant import BeamlineConstant +from ReflectometryServer.components import ( + Component, + ReflectingComponent, + TiltingComponent, +) +from ReflectometryServer.config_helper import ( + add_component, + add_constant, + add_driver, + add_mode, + add_parameter, + add_slit_parameters, + get_configured_beamline, +) +from ReflectometryServer.geometry import ChangeAxis, PositionAndAngle +from ReflectometryServer.ioc_driver import IocDriver +from ReflectometryServer.parameters import AxisParameter +from ReflectometryServer.pv_wrapper import MotorPVWrapper + +# Beamline Constants +NATURAL_ANGLE = 90 +S1_Z = 10.0 +SM_Z = 20.0 +S2_Z = SM_Z + 10.0 +SAMPLE_Z = S2_Z + 10.0 + + +def get_beamline(macros: Dict[str, str]) -> Beamline: + ######################### + # FIXED BEAMLINE VALUES # + ######################### + + # Constants + add_constant( + BeamlineConstant( + "NATURAL_ANGLE", + NATURAL_ANGLE, + "The difference between the beam and straight through", + ) + ) + add_constant(BeamlineConstant("S1_Z", S1_Z, "The distance to slits 1")) + add_constant(BeamlineConstant("SM_Z", SM_Z, "The distance to the supermirror")) + add_constant(BeamlineConstant("S2_Z", S2_Z, "The distance to slits 2")) + + # Modes + _nr = add_mode( + "NR" + ) # Using underscore to pass pyright as mode has to be created but is not used + + ############################## + # BEAMLINE MODEL STARTS HERE # + ############################## + + # Slits 1 + add_slit_parameters(1, include_centres=True) + s1_comp = add_component(Component("s1", PositionAndAngle(0.0, S1_Z, NATURAL_ANGLE))) + add_parameter( + AxisParameter( + "S1OFFSET", + s1_comp, + ChangeAxis.POSITION, + description="Vertical Position of Slit 1", + ) + ) + add_driver(IocDriver(s1_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0301"))) + + # Mirror + mirror_comp = add_component( + ReflectingComponent("Mirror", PositionAndAngle(0, SM_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "SMANGLE", + mirror_comp, + ChangeAxis.ANGLE, + description="Angle of the Supermirror", + ) + ) + add_parameter( + AxisParameter( + "SMOFFSET", + mirror_comp, + ChangeAxis.POSITION, + description="Vertical Position of the Supermirror", + ) + ) + add_driver(IocDriver(mirror_comp, ChangeAxis.ANGLE, MotorPVWrapper("MOT:MTR0207"))) + add_driver( + IocDriver(mirror_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0206")) + ) + + # Slits 2 + add_slit_parameters(2, include_centres=True) + s2_comp = add_component(Component("s2", PositionAndAngle(0.0, S2_Z, NATURAL_ANGLE))) + add_parameter( + AxisParameter( + "S2OFFSET", + s2_comp, + ChangeAxis.POSITION, + description="Vertical Position of Slit 2", + ) + ) + add_driver(IocDriver(s2_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0302"))) + + # SAMPLE + sample_comp = add_component( + TiltingComponent("sample", PositionAndAngle(0, SAMPLE_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "SAMPOFFSET", + sample_comp, + ChangeAxis.POSITION, + description="Vertical Position of Sample", + ) + ) + add_parameter( + AxisParameter( + "SAMPPHI", + sample_comp, + ChangeAxis.ANGLE, + description="Phi Angle of Sample (Pitch)", + ) + ) + add_parameter( + AxisParameter( + "SAMPPSI", + sample_comp, + ChangeAxis.PSI, + description="Psi Angle of Sample (Roll)", + ) + ) + add_parameter( + AxisParameter( + "SAMPTRANS", + sample_comp, + ChangeAxis.TRANS, + description="Horizontal Position of Sample", + ) + ) + add_driver( + IocDriver(sample_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0307")) + ) + add_driver(IocDriver(sample_comp, ChangeAxis.ANGLE, MotorPVWrapper("MOT:MTR0306"))) + add_driver(IocDriver(sample_comp, ChangeAxis.PSI, MotorPVWrapper("MOT:MTR0308"))) + add_driver(IocDriver(sample_comp, ChangeAxis.TRANS, MotorPVWrapper("MOT:MTR0305"))) + + return get_configured_beamline() + +``` +
diff --git "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-3.md" "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-3.md" index 4842a6c4b..a2fa995b9 100644 --- "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-3.md" +++ "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-3.md" @@ -8,16 +8,188 @@ Next, we will look at modes. Modes are a way to configure the beamline/tracking In the following exercise we will categorize the parameters we have added so far by mode and give them some default values. ## Exercise 3 +### 1. Create your modes +So far there has always been a mode `_nr`, without a mode the reflectometry server, but as this variable wasn't used there is an underscore to let Pyright know that it could be ignored. +Start by removing that `_`. +Add another mode called `PNR` in the same way that `NR` is added. +Create a list `all_modes` which includes both `nr` and `pnr`. -Make the following changes to your `config.py`: -- In addition to `NR` mode, add another mode called `PNR` to your config using the `add_mode` helper method. -- Add each parameter to the appropriate modes using the `modes` argument e.g. `add_parameter(AxisParameter(...), modes=[nr])` - - Slits should track the beam in every mode - - The Super Mirror should track the beam in `PNR` mode only - - The sample axes should never track the beam automatically. This is because the scientists don't want this component to move implicitly on beam changes, only when they explicitly tell it to +### 2. Add modes to parameters +Give each parameter a `modes` argument following on after the `AxisParameter` argument. Note that `modes` has to be a list, so if you are only applying a single mode don't forget to add square brackets. +The slit offsets should track the beam in `all_modes`. +Both of the supermirror parameters should use `pnr` mode. +The sample axes should never track the beam automatically. This is because the scientists don't want this component to move implicitly on beam changes, only when they explicitly tell it to. ## To Test -1. Once you have made these changes, restart the `REFL_01` IOC. -1. You should now be able to switch between `NR` and `PNR` mode via the front panel OPI. -1. When doing so, you should see a little green "M" appear/disappear next to parameters, indicating whether they are part of the currently selected mode. -1. Try moving `sm_angle`. You should now see `s2_offset` automatically staying aligned to the reflected beam, while `sa_offset` and `sa_phi` are not (as they were prior to this exercise) +1. Don't forget to restart the IOC to pick up the changes to `config.py` you have just made. +2. You'll likely start up in `NR` mode, and if you go to the `Collimation` tab, you should see two green Ms, by each slit offset. +3. Go back to the front panel, and you should be able to switch between the two modes using the buttons next to the status information. If you leave it on `PNR` and go back to collimation then as well as the previous green Ms by the slit offsets there should be ones alongside the supermirror parameters. +4. Assuming nothing has changed after the steps in the previous exercise, everything else should be reading as 0 on the `Collimation` tab, and if you look at the Table of Motors, then the supermirror angle is `22.5`, the slit 2 height at `10`, the sample height at `20`, and the sample phi at `45`. +5. Staying in `PNR` mode, set the `SMANGLE` to `11.25` on the `Collimation` tab. +6. The downstream values on this tab will still change with that value, except the `S2OFFSET` will end up back at 0 as it has moved with the change to the angle. The readback value of `SAMPOFFSET` and `SAMPPHI` will read as `11.716` and `22.5` respectively as these values as this is where these axes are now in relation to the beam. If you look at the Table of Motors, then the supermirror angle is now `11.25`, the slit 2 offset is `4.143`, and the sample height and phi haven't changed and are still at `20` and `45` respectively. So in this mode you can see that the height of slit 2 is moving automatically with changes to the beam. + +## Solution +
+Should you have trouble the following is what the code could look like + +```python +from typing import Dict + +from ReflectometryServer.beamline import Beamline +from ReflectometryServer.beamline_constant import BeamlineConstant +from ReflectometryServer.components import ( + Component, + ReflectingComponent, + TiltingComponent, +) +from ReflectometryServer.config_helper import ( + add_component, + add_constant, + add_driver, + add_mode, + add_parameter, + add_slit_parameters, + get_configured_beamline, +) +from ReflectometryServer.geometry import ChangeAxis, PositionAndAngle +from ReflectometryServer.ioc_driver import IocDriver +from ReflectometryServer.parameters import AxisParameter +from ReflectometryServer.pv_wrapper import MotorPVWrapper + +# Beamline Constants +NATURAL_ANGLE = 90 +S1_Z = 10.0 +SM_Z = 20.0 +S2_Z = SM_Z + 10.0 +SAMPLE_Z = S2_Z + 10.0 + + +def get_beamline(macros: Dict[str, str]) -> Beamline: + ######################### + # FIXED BEAMLINE VALUES # + ######################### + + # Constants + add_constant( + BeamlineConstant( + "NATURAL_ANGLE", + NATURAL_ANGLE, + "The difference between the beam and straight through", + ) + ) + add_constant(BeamlineConstant("S1_Z", S1_Z, "The distance to slits 1")) + add_constant(BeamlineConstant("SM_Z", SM_Z, "The distance to the supermirror")) + add_constant(BeamlineConstant("S2_Z", S2_Z, "The distance to slits 2")) + + # Modes + nr = add_mode("NR") + pnr = add_mode("PNR") + all_modes = [nr, pnr] + + ############################## + # BEAMLINE MODEL STARTS HERE # + ############################## + + # Slits 1 + add_slit_parameters(1, include_centres=True) + s1_comp = add_component(Component("s1", PositionAndAngle(0.0, S1_Z, NATURAL_ANGLE))) + add_parameter( + AxisParameter( + "S1OFFSET", + s1_comp, + ChangeAxis.POSITION, + description="Vertical Position of Slit 1", + ), + modes=all_modes, + ) + add_driver(IocDriver(s1_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0301"))) + + # Mirror + mirror_comp = add_component( + ReflectingComponent("Mirror", PositionAndAngle(0, SM_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "SMANGLE", + mirror_comp, + ChangeAxis.ANGLE, + description="Angle of the Supermirror", + ), + modes=[pnr], + ) + add_parameter( + AxisParameter( + "SMOFFSET", + mirror_comp, + ChangeAxis.POSITION, + description="Vertical Position of the Supermirror", + ), + modes=[pnr], + ) + add_driver(IocDriver(mirror_comp, ChangeAxis.ANGLE, MotorPVWrapper("MOT:MTR0207"))) + add_driver( + IocDriver(mirror_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0206")) + ) + + # Slits 2 + add_slit_parameters(2, include_centres=True) + s2_comp = add_component(Component("s2", PositionAndAngle(0.0, S2_Z, NATURAL_ANGLE))) + add_parameter( + AxisParameter( + "S2OFFSET", + s2_comp, + ChangeAxis.POSITION, + description="Vertical Position of Slit 2", + ), + modes=all_modes, + ) + add_driver(IocDriver(s2_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0302"))) + + # SAMPLE + sample_comp = add_component( + TiltingComponent("sample", PositionAndAngle(0, SAMPLE_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "SAMPOFFSET", + sample_comp, + ChangeAxis.POSITION, + description="Vertical Position of Sample", + ) + ) + add_parameter( + AxisParameter( + "SAMPPHI", + sample_comp, + ChangeAxis.ANGLE, + description="Phi Angle of Sample (Pitch)", + ) + ) + add_parameter( + AxisParameter( + "SAMPPSI", + sample_comp, + ChangeAxis.PSI, + description="Psi Angle of Sample (Roll)", + ) + ) + add_parameter( + AxisParameter( + "SAMPTRANS", + sample_comp, + ChangeAxis.TRANS, + description="Horizontal Position of Sample", + ) + ) + add_driver( + IocDriver(sample_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0307")) + ) + add_driver(IocDriver(sample_comp, ChangeAxis.ANGLE, MotorPVWrapper("MOT:MTR0306"))) + add_driver(IocDriver(sample_comp, ChangeAxis.PSI, MotorPVWrapper("MOT:MTR0308"))) + add_driver(IocDriver(sample_comp, ChangeAxis.TRANS, MotorPVWrapper("MOT:MTR0305"))) + + return get_configured_beamline() + +``` + +
diff --git "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-4.md" "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-4.md" index a20e99fa5..33a7c0c96 100644 --- "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-4.md" +++ "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-4.md" @@ -1,6 +1,6 @@ # Exercise 4 - Theta -Other than the properties of the sample itself, the reflectivity of surfaces and interfaces between media is dependant on a primary parameter. This parameter is the incident angle of the beam at the sample, also known as Theta. For any given sample, the scientists usually take measurements at 3 or 4 different Theta angles, then stitch the data together to get the complete reflectivity profile. +Other than the properties of the sample itself, the reflectivity of surfaces and interfaces between media is dependant on the incident angle of the beam at the sample, this primary parameter is also known as Theta. For any given sample, the scientists usually take measurements at 3 or 4 different Theta angles, then stitch the data together to get the complete reflectivity profile. > Side note: taking more measurements at finer Theta steps would produce better data. Theoretically, scientists could take measurements at many more discrete angles, stopping and starting data collection in between, however this is impractical due to the added overhead in time and effort required for setting up each measurement. The current approach of measuring a handful of Theta is considered a "good enough" solution. > @@ -8,7 +8,7 @@ Other than the properties of the sample itself, the reflectivity of surfaces and Setting a Theta angle is similar to setting a super mirror angle in that every component downstream from the `ThetaComponent` in the reflectometry config will be subject to the new beam path. There is an important difference however in that Theta is NOT linked to the physical angle of the sample stack. This is because the scientists do not want this angle to ever be set implicitly as it could have severe consequences if the sample is e.g. a large tank of liquid which could accidentally be angled and spill all over the blockhouse. Instead, Theta only sets the positions of downstream components for a theoretical incident angle. To ensure the actual beam follows this beam path, the sample Phi angle needs to be set to match this theoretical beam path, the Theta parameter and component are however entirely separate entities from the Sample component and angle. -Similarly, the Theta RBV is not derived from the sample Phi axis, but from a axis representing the position of the detector. At ISIS, we have two different types of detectors from a motion point of view: +Similarly, the Theta RBV is not derived from the sample Phi axis, but from an axis representing the position of the detector. At ISIS, we have two different types of detectors from a motion point of view: 1. Detectors moving on a linear height stage that can tilt towards the beam (SURF, CRISP) 1. Detectors mounted on another component that moves on an arc around the sample (POLREF, INTER, OFFSPEC) @@ -16,21 +16,236 @@ For the former, Theta is calculated via trigonometry, where the Detector Height From a config point of view, we can provide the `ThetaComponent` with a list of downstream components from which Theta's RBV can be derived. This is a list as some beamlines may use one of several detectors. Theta will look for the next component in the list that is currently active in the beam and derive it's angle from it. -As an example, let's say SURF has a structure of `Beam Source - Sample - Point Detector - Linear Detector` where both detectors are driven by linear height stages. If both detectors are in the beam, Theta will derive its value from the Point Detector Height axis. If the point detector is parked out of the beam, Theta will derive its value from the Linear Detector Height axis. +As an example, let's say our beamline has a structure of `Beam Source - Sample - Point Detector - Linear Detector` where both detectors are driven by linear height stages. If both detectors are in the beam, Theta will derive its value from the Point Detector Height axis. If the point detector is parked out of the beam, Theta will derive its value from the Linear Detector Height axis. > Side note: The arc detector solution is technically superior as it maintains a constant distance between sample and detector, meaning e.g. Time of Flight is not dependant on Theta. This produces a slight inaccuracy for SURF and CRISP, however due to the optical lever on those beamlines being fairly short the effect is small enough to be ignored. ## Exercise 4 +In this exercise, let's add Theta and a detector component from which we can derive its value to our beamline model. For this exercise, we will assume we are using the setup that is found on SURF and CRISP, i.e. a detector that can move up and down on a linear height axis, and tilt towards the beam via a separate rotation axis. (we will look at a bench setup in a later exercise). +At the end of this exercise your beamline should have the following in order: +* Slit 1, with a height (as was set up in exercise 2) +* a supermirror, with a height and an angle (as was set up in exercise 1) +* Slit 2, with a height (as was set up in exercise 2) +* A sample stack, with height, translation, phi, and psi axes (as was set up in exercise 2) +* A theta component +* A detector, with a height and an angle -In this exercise, let's add Theta and a detector component from which we can derive its value to our beamline model. For this exercise, we will assume we are using the setup that is found on SURF and CRISP, i.e. a detector that can move up and down on a linear height axis, and tilt towards the beam via a separate rotation axis. (we will look at a bench setup in a later exercise). Add the following to your config: -1. Theta Component and Parameter -1. Detector Component and Detector Height & Angle parameters linked to appropriate motor axes (referred to in the motor axis descriptions as `Sgl detector` = single detector) -1. At this point you can restart the IOC and you should be able to move detector height and angle parameters as with everything we have added previously. Theta should also be there and read `NaN` as we have not yet told it what axis it should derive its value from. -1. Add the detector to Theta's list of components define its angle. The ThetaComponent provides the methods `add_angle_to(component)` to do this for a linear height axis, or `.add_angle_of(component)` for an arc component. -1. You should now be able to set `Theta` and see it move the detector height & angle. This works the same as the `sm_angle` - you can try setting Theta to 22.5 which should move the detector height axis to the distance between the Theta and detector components, and the angle to 45 deg. - -Some tips: -- Theta has it's own component type, `ThetaComponent`. This component has the added functionality of being able to listen to a different component from which to derive it's values. -- The (virtual) Theta component's coordinates need to match the (real) Sample Components coordinates. -- The `Theta` parameter itself is just a simple Axis Parameter that should look basically the same as e.g. `sm_angle` -- All parameters should be in all modes - we always need Theta as well as the detector. +### 1. Add in the Theta Component and Parameter +Because Theta is related in reality to the sample position, this component should be added next to the sample stack. Because we don't want theta to influence the sample stack, as explained above, it should be put directly after the sample stack if using one. In our imaginary beamline, we are keeping the sample stack in. +Theta has it's own component type, `ThetaComponent`, which is in `ReflectometryServer.components`. This component has the added functionality of being able to listen to a different component from which to derive it's values. +The (virtual) Theta component's coordinates need to match the (real) Sample Components coordinates, so you should using `0.0`, `SAMPLE_Z`, and the `NATURAL_ANGLE` to set the PositionAndAngle of the component. +The `Theta` parameter itself is just a simple Axis Parameter, same as we added for the supermirror in Exercise 1. This parameter is used in `all_modes`. + +### 2. Add in the detector component +Start by creating the constant for `DET_Z`, which is the distance to the detector, and for this exercise will be `SAMPLE_Z` + `10.0`. +The detector is added after Theta, and is another `TiltingComponent`. The `PositionAndAngle` setup should be set to `0.0`, `DET_Z`, and the `NATURAL_ANGLE`. +We also need to add 2 parameters, the angle and the height, which will be referenced to `MTR0201` and `MTR0202` respectively, and are available in all modes. This is done in the same way as items were added in previous exercises. + +At this point you can restart the IOC and you should be able to see the detector height and angle parameters as with everything we have added previously. The angle should be reading `22.5` and the height `-4.142`, whilst on the table of motors both are at `0.0`. Theta should also be there and read `NaN` as we have not yet told it what axis it should derive its value from. Similarly the downstream `RBV` value on the `Collimation` tab will be `NaN`, as they are downstream of something unknown. At this point you can move those axes like the others. + +### 3. Linking the detector and Theta +The ThetaComponent provides the methods `add_angle_to(component)` to do this for a linear height axis, or `.add_angle_of(component)` for an arc component. +Add in the detector component created above as a linear axis. + +## To Test +1. Go to the table of motors and make sure all are at a `0` position. +2. Restart the IOC to pick up the updated `config.py`. +3. If you go to the Collimation tab and set `Theta` to `22.5` the readbacks there should stay as `0.0` after the move. If you check the table of motors, then the detector angle should now read `45 deg`, and the height will be `10.0`. That height is the difference between Theta and the detector components. + +## Solution +
+Should you have trouble the following is what the code could look like + +```python +from typing import Dict + +from ReflectometryServer.beamline import Beamline +from ReflectometryServer.beamline_constant import BeamlineConstant +from ReflectometryServer.components import ( + Component, + ReflectingComponent, + ThetaComponent, + TiltingComponent, +) +from ReflectometryServer.config_helper import ( + add_component, + add_constant, + add_driver, + add_mode, + add_parameter, + add_slit_parameters, + get_configured_beamline, +) +from ReflectometryServer.geometry import ChangeAxis, PositionAndAngle +from ReflectometryServer.ioc_driver import IocDriver +from ReflectometryServer.parameters import AxisParameter +from ReflectometryServer.pv_wrapper import MotorPVWrapper + +# Beamline Constants +NATURAL_ANGLE = 90 +S1_Z = 10.0 +SM_Z = 20.0 +S2_Z = SM_Z + 10.0 +SAMPLE_Z = S2_Z + 10.0 +DET_Z = SAMPLE_Z + 10.0 + + +def get_beamline(macros: Dict[str, str]) -> Beamline: + ######################### + # FIXED BEAMLINE VALUES # + ######################### + + # Constants + add_constant( + BeamlineConstant( + "NATURAL_ANGLE", + NATURAL_ANGLE, + "The difference between the beam and straight through", + ) + ) + add_constant(BeamlineConstant("S1_Z", S1_Z, "The distance to slits 1")) + add_constant(BeamlineConstant("SM_Z", SM_Z, "The distance to the supermirror")) + add_constant(BeamlineConstant("S2_Z", S2_Z, "The distance to slits 2")) + add_constant(BeamlineConstant("DET_Z", DET_Z, "The distance to the detector")) + + # Modes + nr = add_mode("NR") + pnr = add_mode("PNR") + all_modes = [nr, pnr] + + ############################## + # BEAMLINE MODEL STARTS HERE # + ############################## + + # Slits 1 + add_slit_parameters(1, include_centres=True) + s1_comp = add_component(Component("s1", PositionAndAngle(0.0, S1_Z, NATURAL_ANGLE))) + add_parameter( + AxisParameter( + "S1OFFSET", + s1_comp, + ChangeAxis.POSITION, + description="Vertical Position of Slit 1", + ), + modes=all_modes, + ) + add_driver(IocDriver(s1_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0301"))) + + # Mirror + mirror_comp = add_component( + ReflectingComponent("Mirror", PositionAndAngle(0, SM_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "SMANGLE", + mirror_comp, + ChangeAxis.ANGLE, + description="Angle of the Supermirror", + ), + modes=[pnr], + ) + add_parameter( + AxisParameter( + "SMOFFSET", + mirror_comp, + ChangeAxis.POSITION, + description="Vertical Position of the Supermirror", + ), + modes=[pnr], + ) + add_driver(IocDriver(mirror_comp, ChangeAxis.ANGLE, MotorPVWrapper("MOT:MTR0207"))) + add_driver( + IocDriver(mirror_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0206")) + ) + + # Slits 2 + add_slit_parameters(2, include_centres=True) + s2_comp = add_component(Component("s2", PositionAndAngle(0.0, S2_Z, NATURAL_ANGLE))) + add_parameter( + AxisParameter( + "S2OFFSET", + s2_comp, + ChangeAxis.POSITION, + description="Vertical Position of Slit 2", + ), + modes=all_modes, + ) + add_driver(IocDriver(s2_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0302"))) + + # SAMPLE + sample_comp = add_component( + TiltingComponent("sample", PositionAndAngle(0, SAMPLE_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "SAMPOFFSET", + sample_comp, + ChangeAxis.POSITION, + description="Vertical Position of Sample", + ) + ) + add_parameter( + AxisParameter( + "SAMPPHI", + sample_comp, + ChangeAxis.ANGLE, + description="Phi Angle of Sample (Pitch)", + ) + ) + add_parameter( + AxisParameter( + "SAMPPSI", + sample_comp, + ChangeAxis.PSI, + description="Psi Angle of Sample (Roll)", + ) + ) + add_parameter( + AxisParameter( + "SAMPTRANS", + sample_comp, + ChangeAxis.TRANS, + description="Horizontal Position of Sample", + ) + ) + add_driver( + IocDriver(sample_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0307")) + ) + add_driver(IocDriver(sample_comp, ChangeAxis.ANGLE, MotorPVWrapper("MOT:MTR0306"))) + add_driver(IocDriver(sample_comp, ChangeAxis.PSI, MotorPVWrapper("MOT:MTR0308"))) + add_driver(IocDriver(sample_comp, ChangeAxis.TRANS, MotorPVWrapper("MOT:MTR0305"))) + + # THETA + theta_comp = add_component( + ThetaComponent("ThetaComp", PositionAndAngle(0.0, SAMPLE_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "THETA", + theta_comp, + ChangeAxis.ANGLE, + description="Incident Angle at Sample", + ), + modes=all_modes, + ) + + # DETECTOR + det_comp = add_component( + TiltingComponent("Detector", PositionAndAngle(0.0, DET_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "DETANGLE", det_comp, ChangeAxis.ANGLE, description="Angle of Detector" + ), + modes=all_modes, + ) + add_parameter( + AxisParameter("DETHEIGHT", det_comp, ChangeAxis.POSITION, description="Vertical Position of Detector"), + modes=all_modes, + ) + add_driver(IocDriver(det_comp, ChangeAxis.ANGLE, MotorPVWrapper("MOT:MTR0201"))) + add_driver(IocDriver(det_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0202"))) + theta_comp.add_angle_to(det_comp) + + return get_configured_beamline() +``` diff --git "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-5.md" "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-5.md" index e811a53b1..ada69178e 100644 --- "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-5.md" +++ "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-5.md" @@ -2,9 +2,9 @@ ## Parking Components -As we briefly touched on in a previous section, the beamline occasionally needs be re-configured by moving components in or out of the beam. This can be the polariser when switching between NR and PNR mode, moving the sample out of the beam to record the base neutron flux, moving everything out of the way of the laser (and back in) for alignment, among many more reasons. In order to facilitate this action, the reflectometry server provides `InBeam Parameters`, a toggle parameter type for moving a given component in or out of the beam, an abstraction layer for applying one out of a set of pre-defined, named numerical positions. As the `InBeam Parameter` is applied at the component level, this may set those pre-defined positions on multiple axes of that component. +As we briefly touched on in a previous section, the beamline occasionally needs be re-configured by moving components in or out of the beam. This could be a polariser when switching between NR and PNR mode, moving the sample out of the beam to record the base neutron flux, moving everything out of the way of the laser (and back in) for alignment, among many more reasons. In order to facilitate this action, the reflectometry server provides `InBeam Parameters`, a toggle parameter type for moving a given component in or out of the beam. This is an abstraction layer for applying one out of a set of pre-defined, named numerical positions. As the `InBeam Parameter` is applied at the component level, this may set those pre-defined positions on multiple axes of that component. -When you move a component out of the beam, it's offset Setpoints and SP:RBVs are preserved but each axis physically moves to its park position (if defined). Once it's parked, a component will not track or change the beam path in the beamline model. You should also see the move button for other parameters on this component being locked on the OPI for this reason. +When you move a component out of the beam, it's offset Setpoints and `SP:RBV`s are preserved but each axis physically moves to its park position (if defined). Once it's parked, a component will not track or change the beam path in the beamline model. You should also see the move button for other parameters on this component being locked on the OPI for this reason. When you move a component into the beam, each parked axis returns to its SP:RBV and it can become part of the active tracking model again (if all other conditions are met too e.g. its part of the current mode). @@ -18,20 +18,248 @@ These parameters function very similarly to [Motion Set Points](/specific_iocs/m ## Exercise 5 -In this exercise, we will add parking behaviour to the super mirror and sample components. We want the following: -- An `InBeamParameter` each for the Super Mirror and Sample components, which lets you toggle between in and out of beam for each component -- When the Super Mirror gets parked, move its height to -20 and its angle to 0 -- When the Sample gets parked, move its `TRANS` axis to 100 -- Think about which parameters are used in which experimental modality and give them appropriate Modes and Mode Inits +### 1. Add an in beam parameter to the supermirror +Adding in this kind of parameter is very similar to other parameters, `add_parameter(InBeamParameter("comp_in", some_compon, description=""),modes=[mode], mode_inits=[(mode, value)])`. `InBeamParameter` in in `ReflectometryServer.parameters`. Whilst it is typically best practice to only include this in appropriate modalities, for now keep this in `all_modes`. To initialise the value of this parameter for specific modes use the `mode_inits` list, at the moment set this to `0` for `nr`, and `1` for `pnr`. +Add the information for the parked position to the existing drivers using the parameter `out_of_beam_positions` setting it to a list containing the appropriate position. In the case of the supermirror the height should be `-20.0` and the angle set to `0.0` +To make all this work don't forget to import `OutOfBeamPosition` from `ReflectometryServer.out_of_beam`. -## Tips: +### 2. And an in beam parameter to the sample component +Add an in beam parameter to the sample in just the same way as above. This time however, only the translation needs to be given a position, set it to `100.0`, and the mode items can be omitted, as there will always be a sample point to consider. -- You can create an `InBeam` parameter like this: +## To Test +1. Go to the table of motors and make sure all are at a 0 position. +2. Go to the reflectometry server view and make sure you are in `PNR` mode. +3. Restart the IOC to pick up the updated config.py. +4. If you go to the `Activation Parameters` tab in the reflectometry view you should now see the two parameters created in this exercise listed there, and both should be `IN`. +5. Change the mode to `NR`, go back to the `Activation Parameters` tab, and the `IN|OUT` buttons for the supermirror parameter should be swapped, and highlighted in yellow. Note that the move is saved until other values are changed, but if you hit move at this point, the `RBV` for the supermirror parameter should start moving. When it reads out, if you look on the table of motors then the height axis for the supermirror should be reading `-20`. +6. Go back to the reflectometry front panel, go back to PNR mode, set theta to 22.5, and select `Move`. Theta should start to change, along with the other associated values. However, as things being in/out are meant to be a specific decision, you will still have to go to the activation parameters tab to trigger the move for the supermirror. -`add_parameter(InBeamParameter("comp_in", some_compon, description=""),modes=[mode], mode_inits=[(mode, value)])` +## Solution +
+Should you have trouble the following is what the code could look like -- You can add a Park Position on an axis like this: +```python +from typing import Dict -`add_driver(IocDriver(..., out_of_beam_positions=[OutOfBeamPosition(-10)]))` +from ReflectometryServer.beamline import Beamline +from ReflectometryServer.beamline_constant import BeamlineConstant +from ReflectometryServer.components import ( + Component, + ReflectingComponent, + ThetaComponent, + TiltingComponent, +) +from ReflectometryServer.config_helper import ( + add_component, + add_constant, + add_driver, + add_mode, + add_parameter, + add_slit_parameters, + get_configured_beamline, +) +from ReflectometryServer.geometry import ChangeAxis, PositionAndAngle +from ReflectometryServer.ioc_driver import IocDriver +from ReflectometryServer.out_of_beam import OutOfBeamPosition +from ReflectometryServer.parameters import AxisParameter, InBeamParameter +from ReflectometryServer.pv_wrapper import MotorPVWrapper -- You might get a warning if you do not enable autosave on the `InBeam` parameter. This does not stop you from running the server but it means that on startup, we cannot differentiate between an offset from a parameter setpoint and an offset from being parked so they might be initialised wrong. More detail can be found [here](#reflectometry_parameter_init) +# Beamline Constants +NATURAL_ANGLE = 90 +S1_Z = 10.0 +SM_Z = 20.0 +S2_Z = SM_Z + 10.0 +SAMPLE_Z = S2_Z + 10.0 +DET_Z = SAMPLE_Z + 10.0 + + +def get_beamline(macros: Dict[str, str]) -> Beamline: + ######################### + # FIXED BEAMLINE VALUES # + ######################### + + # Constants + add_constant( + BeamlineConstant( + "NATURAL_ANGLE", + NATURAL_ANGLE, + "The difference between the beam and straight through", + ) + ) + add_constant(BeamlineConstant("S1_Z", S1_Z, "The distance to slits 1")) + add_constant(BeamlineConstant("SM_Z", SM_Z, "The distance to the supermirror")) + add_constant(BeamlineConstant("S2_Z", S2_Z, "The distance to slits 2")) + add_constant(BeamlineConstant("DET_Z", DET_Z, "The distance to the detector")) + + # Modes + nr = add_mode("NR") + pnr = add_mode("PNR") + all_modes = [nr, pnr] + + ############################## + # BEAMLINE MODEL STARTS HERE # + ############################## + + # Slits 1 + add_slit_parameters(1, include_centres=True) + s1_comp = add_component(Component("s1", PositionAndAngle(0.0, S1_Z, NATURAL_ANGLE))) + add_parameter( + AxisParameter( + "S1OFFSET", + s1_comp, + ChangeAxis.POSITION, + description="Vertical Position of Slit 1", + ), + modes=all_modes, + ) + add_driver(IocDriver(s1_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0301"))) + + # Mirror + mirror_comp = add_component( + ReflectingComponent("Mirror", PositionAndAngle(0, SM_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "SMANGLE", + mirror_comp, + ChangeAxis.ANGLE, + description="Angle of the Supermirror", + ), + modes=[pnr], + ) + add_parameter( + AxisParameter( + "SMOFFSET", + mirror_comp, + ChangeAxis.POSITION, + description="Vertical Position of the Supermirror", + ), + modes=[pnr], + ) + add_parameter( + InBeamParameter("SMIN", mirror_comp, description="Toggle Supermirror In Beam"), + modes=all_modes, + mode_inits=[(nr, 0), (pnr, 1)], + ) + add_driver( + IocDriver( + mirror_comp, + ChangeAxis.ANGLE, + MotorPVWrapper("MOT:MTR0207"), + out_of_beam_positions=[OutOfBeamPosition(0.0)], + ) + ) + add_driver( + IocDriver( + mirror_comp, + ChangeAxis.POSITION, + MotorPVWrapper("MOT:MTR0206"), + out_of_beam_positions=[OutOfBeamPosition(-20.0)], + ) + ) + + # Slits 2 + add_slit_parameters(2, include_centres=True) + s2_comp = add_component(Component("s2", PositionAndAngle(0.0, S2_Z, NATURAL_ANGLE))) + add_parameter( + AxisParameter( + "S2OFFSET", + s2_comp, + ChangeAxis.POSITION, + description="Vertical Position of Slit 2", + ), + modes=all_modes, + ) + add_driver(IocDriver(s2_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0302"))) + + # SAMPLE + sample_comp = add_component( + TiltingComponent("sample", PositionAndAngle(0, SAMPLE_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "SAMPOFFSET", + sample_comp, + ChangeAxis.POSITION, + description="Vertical Position of Sample", + ) + ) + add_parameter( + AxisParameter( + "SAMPPHI", + sample_comp, + ChangeAxis.ANGLE, + description="Phi Angle of Sample (Pitch)", + ) + ) + add_parameter( + AxisParameter( + "SAMPPSI", + sample_comp, + ChangeAxis.PSI, + description="Psi Angle of Sample (Roll)", + ) + ) + add_parameter( + AxisParameter( + "SAMPTRANS", + sample_comp, + ChangeAxis.TRANS, + description="Horizontal Position of Sample", + ) + ) + add_parameter( + InBeamParameter( + "SAMPIN", + sample_comp, + description="Toggle Sample In Beam", + ) + ) + add_driver( + IocDriver(sample_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0307")) + ) + add_driver(IocDriver(sample_comp, ChangeAxis.ANGLE, MotorPVWrapper("MOT:MTR0306"))) + add_driver(IocDriver(sample_comp, ChangeAxis.PSI, MotorPVWrapper("MOT:MTR0308"))) + add_driver( + IocDriver( + sample_comp, + ChangeAxis.TRANS, + MotorPVWrapper("MOT:MTR0305"), + out_of_beam_positions=[OutOfBeamPosition(100.0)], + ) + ) + + # THETA + theta_comp = add_component( + ThetaComponent("ThetaComp", PositionAndAngle(0.0, SAMPLE_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "THETA", + theta_comp, + ChangeAxis.ANGLE, + description="Incident Angle at Sample", + ), + modes=all_modes, + ) + + # DETECTOR + det_comp = add_component( + TiltingComponent("Detector", PositionAndAngle(0.0, DET_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "DETANGLE", det_comp, ChangeAxis.ANGLE, description="Angle of Detector" + ), + modes=all_modes, + ) + add_parameter( + AxisParameter("DETHEIGHT", det_comp, ChangeAxis.POSITION, description="Vertical Position of Detector"), + modes=all_modes, + ) + add_driver(IocDriver(det_comp, ChangeAxis.ANGLE, MotorPVWrapper("MOT:MTR0201"))) + add_driver(IocDriver(det_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0202"))) + theta_comp.add_angle_to(det_comp) + + return get_configured_beamline() +``` diff --git "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-6.md" "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-6.md" index 94a2d47b9..1f7a7c37f 100644 --- "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-6.md" +++ "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-6.md" @@ -1,23 +1,264 @@ # Exercise 6 - Optional Features -In this short section, I just want to briefly highlight some other optional functionality Beamline Parameters can provide: - ## `autosave` -Parameters have an optional `autosave` flag which determines how Setpoints for those parameters get initialised on start-up. At this point in time **we probably just want to autosave every parameter by default** - read below for rationale -- If `True`, they are read from a file in `/Instrument/var/refl/`. Setpoints get autosaved whenever a parameter is moved i.e. a new SP is applied as SP:RBV. -- If `False`, parameters are initialised to their current RBV. This option was implemented as a way for the reflectometry server to account for positions being changed outside of IBEX when swapping between SECI and IBEX for testing. This option is informally deprecated as not autosaving positions can lead to some ambiguity when initialising setpoints, and the workflow it supported is outdated as reflectometers are not going back to SECI anymore. +With autosave set to `True`, if you change a value when the reflectometry server is not running it should come back with the last SP before IOC restart. Without, it should come back with the current RBV applied as SP. This is usually desired for parameters which are assigned to variables, which is why you will get a warning if you don't add this to the `AxisParameter`. ## `characteristic_value` Sometimes, scientists want to be able to see a beamline parameter and the low level axis it derives its value from side by side for diagnostics purposes. You can do this by "tagging" a beamline parameter with the relevant axis e.g. `AxisParameter(..., characteristic_value="MOT:MTR0101")`. This will just make the value for the given PV display next to the parameter, there is nothing too clever happening under the hood. -## `custom_function` -Run a custom function whenever a given parameter is being "moved". This is potentially quite powerful as this can be arbitrary code, however this should be used sparingly and cautiously, as it is not subject to reviews, automated tests etc. We have used this in the past e.g. to load appropriate wiring tables when moving Point / Linear detectors in or out of the beam. - ## `DirectParameter` A type of beamline parameter that forgoes the `Component` and `IocDriver` layers, and directly mirrors the value from a given `PvWrapper` instead. We use this in instances where we want a parameter on the front panel but what the parameter controls is completely independent of the beam path. Slit Gap and Centre parameters are instances of `DirectParameter` for example. ## Exercise 6 +### 1. Add a `characteristic_value` +Go to the `AxisParameter` creation for the sample offset, and add in the `chatacteristic_value` parameter after the description, and assign it to `MOT:MTR0307` in our fictitious beamline. + +### 2. Add a `DirectParameter` +Create this somewhere in the config file, call it `MONITORPOS`, give it a `pv_wrapper` value of a `MotorPVWrapper` pointing at `MOT:MTR0208` which in our beamline is the axis controlling the position of the monitor. Don't forget to import `DirectParameter` from `ReflectometryServer.parameters`. + +## Testing +1. Go to the table of motors and make sure all are at a 0 position. +2. Restart the IOC to pick up the updated config.py. +3. On the `Collimation Plane Parameters` tab, the `SAMPOFFSET` parameter should now have a label alongside it, reading `0.0`. +4. Set the `SMANGLE` to `22.5`. Whilst the `RDB` for `SAMPOFFSET` should update to `-20.0` the label should remain at `0.0`, that difference is equal to the displacement of the reflected beam. +5. Go now to the `Slit Parameters` page and you should see the direct parameter created above listed in there, in the appropriate place in the list in relation to the slit sets. If you set this parameter, then the appropriate axis on the table of motors should move with it with no differences seen. + +## Solution +
+Should you have trouble the following is what the code could look like + +```python +from typing import Dict + +from ReflectometryServer.beamline import Beamline +from ReflectometryServer.beamline_constant import BeamlineConstant +from ReflectometryServer.components import ( + Component, + ReflectingComponent, + ThetaComponent, + TiltingComponent, +) +from ReflectometryServer.config_helper import ( + add_component, + add_constant, + add_driver, + add_mode, + add_parameter, + add_slit_parameters, + get_configured_beamline, +) +from ReflectometryServer.geometry import ChangeAxis, PositionAndAngle +from ReflectometryServer.ioc_driver import IocDriver +from ReflectometryServer.out_of_beam import OutOfBeamPosition +from ReflectometryServer.parameters import AxisParameter, DirectParameter, InBeamParameter +from ReflectometryServer.pv_wrapper import MotorPVWrapper + +# Beamline Constants +NATURAL_ANGLE = 90 +S1_Z = 10.0 +SM_Z = 20.0 +S2_Z = SM_Z + 10.0 +SAMPLE_Z = S2_Z + 10.0 +DET_Z = SAMPLE_Z + 10.0 + + +def get_beamline(macros: Dict[str, str]) -> Beamline: + ######################### + # FIXED BEAMLINE VALUES # + ######################### + + # Constants + add_constant( + BeamlineConstant( + "NATURAL_ANGLE", + NATURAL_ANGLE, + "The difference between the beam and straight through", + ) + ) + add_constant(BeamlineConstant("S1_Z", S1_Z, "The distance to slits 1")) + add_constant(BeamlineConstant("SM_Z", SM_Z, "The distance to the supermirror")) + add_constant(BeamlineConstant("S2_Z", S2_Z, "The distance to slits 2")) + add_constant(BeamlineConstant("DET_Z", DET_Z, "The distance to the detector")) + + # Modes + nr = add_mode("NR") + pnr = add_mode("PNR") + all_modes = [nr, pnr] + + # Direct Parameters + add_parameter( + DirectParameter( + "MONITORPOS", + pv_wrapper=MotorPVWrapper("MOT:MTR0208"), + description="Vertical pos of beam monitor", + ) + ) + + ############################## + # BEAMLINE MODEL STARTS HERE # + ############################## + + # Slits 1 + add_slit_parameters(1, include_centres=True) + s1_comp = add_component(Component("s1", PositionAndAngle(0.0, S1_Z, NATURAL_ANGLE))) + add_parameter( + AxisParameter( + "S1OFFSET", + s1_comp, + ChangeAxis.POSITION, + description="Vertical Position of Slit 1", + ), + modes=all_modes, + ) + add_driver(IocDriver(s1_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0301"))) + + # Mirror + mirror_comp = add_component( + ReflectingComponent("Mirror", PositionAndAngle(0, SM_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "SMANGLE", + mirror_comp, + ChangeAxis.ANGLE, + description="Angle of the Supermirror", + ), + modes=[pnr], + ) + add_parameter( + AxisParameter( + "SMOFFSET", + mirror_comp, + ChangeAxis.POSITION, + description="Vertical Position of the Supermirror", + ), + modes=[pnr], + ) + add_parameter( + InBeamParameter("SMIN", mirror_comp, description="Toggle Supermirror In Beam"), + modes=all_modes, + mode_inits=[(nr, 0), (pnr, 1)], + ) + add_driver( + IocDriver( + mirror_comp, + ChangeAxis.ANGLE, + MotorPVWrapper("MOT:MTR0207"), + out_of_beam_positions=[OutOfBeamPosition(0.0)], + ) + ) + add_driver( + IocDriver( + mirror_comp, + ChangeAxis.POSITION, + MotorPVWrapper("MOT:MTR0206"), + out_of_beam_positions=[OutOfBeamPosition(-20.0)], + ) + ) + + # Slits 2 + add_slit_parameters(2, include_centres=True) + s2_comp = add_component(Component("s2", PositionAndAngle(0.0, S2_Z, NATURAL_ANGLE))) + add_parameter( + AxisParameter( + "S2OFFSET", + s2_comp, + ChangeAxis.POSITION, + description="Vertical Position of Slit 2", + ), + modes=all_modes, + ) + add_driver(IocDriver(s2_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0302"))) + + # SAMPLE + sample_comp = add_component( + TiltingComponent("sample", PositionAndAngle(0, SAMPLE_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "SAMPOFFSET", + sample_comp, + ChangeAxis.POSITION, + description="Vertical Position of Sample", + characteristic_value="MOT:MTR0307", + ) + ) + add_parameter( + AxisParameter( + "SAMPPHI", + sample_comp, + ChangeAxis.ANGLE, + description="Phi Angle of Sample (Pitch)", + ) + ) + add_parameter( + AxisParameter( + "SAMPPSI", + sample_comp, + ChangeAxis.PSI, + description="Psi Angle of Sample (Roll)", + ) + ) + add_parameter( + AxisParameter( + "SAMPTRANS", + sample_comp, + ChangeAxis.TRANS, + description="Horizontal Position of Sample", + ) + ) + add_parameter( + InBeamParameter( + "SAMPIN", + sample_comp, + description="Toggle Sample In Beam", + ) + ) + add_driver( + IocDriver(sample_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0307")) + ) + add_driver(IocDriver(sample_comp, ChangeAxis.ANGLE, MotorPVWrapper("MOT:MTR0306"))) + add_driver(IocDriver(sample_comp, ChangeAxis.PSI, MotorPVWrapper("MOT:MTR0308"))) + add_driver( + IocDriver( + sample_comp, + ChangeAxis.TRANS, + MotorPVWrapper("MOT:MTR0305"), + out_of_beam_positions=[OutOfBeamPosition(100.0)], + ) + ) + + # THETA + theta_comp = add_component( + ThetaComponent("ThetaComp", PositionAndAngle(0.0, SAMPLE_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "THETA", + theta_comp, + ChangeAxis.ANGLE, + description="Incident Angle at Sample", + ), + modes=all_modes, + ) + + # DETECTOR + det_comp = add_component( + TiltingComponent("Detector", PositionAndAngle(0.0, DET_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "DETANGLE", det_comp, ChangeAxis.ANGLE, description="Angle of Detector" + ), + modes=all_modes, + ) + add_parameter( + AxisParameter("DETHEIGHT", det_comp, ChangeAxis.POSITION, description="Vertical Position of Detector"), + modes=all_modes, + ) + add_driver(IocDriver(det_comp, ChangeAxis.ANGLE, MotorPVWrapper("MOT:MTR0201"))) + add_driver(IocDriver(det_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0202"))) + theta_comp.add_angle_to(det_comp) -- Try adding the sample height axis as a `characteristic value` of the `sa_offset` parameter. This should add a readback label of the motor value next to the beamline parameter in the `Collimation Plane` tab. When using the super mirror with a non-zero angle, you should be able to see a difference between the parameter and motor values equal to the displacement of the reflected beam. -- Add a `DirectParameter` called `monitor_pos` which drives the appropriate motor axis (should be 0408). It does not matter where in the config file this parameter appears. Confirm that setting the parameter moves the associated motor axis. -- Try setting `autosave` for `monitor_pos` to `False`. To see the difference, try killing the IOC, moving the motor axis via the low motor table, and restarting it. With autosave, it should come back with the last SP before IOC restart. Without, it should come back with the current RBV applied as SP + return get_configured_beamline() +``` diff --git "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-7.md" "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-7.md" index a2a1d0987..fb0e86979 100644 --- "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-7.md" +++ "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-7.md" @@ -11,25 +11,289 @@ We have different types of engineering corrections, namely: Any of the above can also be made to apply for specific modes only. ## Exercise 7 +Here we will add corrections to the detector, as we will be considering things against actual motor values do consider adding in characteristic values to the detector height and angle to save on moving between views. -In this exercise, let's add: -- A constant correction of `0.3` on the detector angle -- A user function correcting the detector height parameter by `0.1 * sm_angle` +### 1. Add a constant correction +Create a `constant_correction` variable using the `ConstantCorrection` function with a value of `0.3`. `ConstantCorrection`, and `UserFunctionCorrection` which you will use in the next step are both in `ReflectometryServer.engineering_corrections`. +Add this as an `engineering_correction` to the driver for the detector angle. -A constant correction looks like e.g. -``` -constant_correction = ConstantCorrection(1) -``` +### 2. Add a user function correction +Create a function in your config file for this training, normally it would probably be cleaner to put these functions in a separate file and import them. +This function should take in a float, and return that float multiplied by `0.1` +For our example, let's use the supermirror angle as the source for our float, which is already defined as a parameter. However, to use this that parameter will need to be assigned to a variable, so make sure you do that. +It's also a good idea to specify an autosave on any parameter you use like this, that way even if the motor is moved it is easy to see what the setpoint was. If you don't, then there will be a warning displayed. +To use this function create a variable that calls `UserFunctionCorrection`, supplying it with the name of the function you have just created, and the `BeamlineParameter` to use, in this case use the variable you have just created. Apply this correction to the detector height. -A user function correction looks like e.g.: -``` -def correction_func(corrected_param, input_param): - return input_param +## To Test +1. Before doing anything else, have a look at your collimation panel, to see the behaviour without the corrections. If it is in the same state as it was before then `theta` is currently `0.0`, the detector angle is `0.0` for collimation purposes, but is at `45.0` on the actual motor, and the detector height is `0.0` with a motor position of `30.0`. Your supermirror angle should be at `22.5`. +2. Set `theta` to `2.0`. Note that the height and angle for the detector will still read `0.0` (or very close to that value), whilst the motors will read `49.0` for the angle and `31.504` for the height. +3. Go to the table of motors and make sure all are at a 0 position. +4. Restart the IOC to pick up the updated config.py. +5. If you look at the collimation tab in the reflectometry server view you will now see that the detector angle is reading `-0.3`, this is the correction from `0.0` applied by the constant created during this exercise. If you look at the table of motors (or the characteristic value if you set it up) you will see that the motor position is still `0.0`. +6. Set the supermirror angle to `22.5`. Looking at the values when the move completes, `theta` is still `0.0`, the detector angle is reading `-0.3`, with an actual motor position of `45.0`, which shows that the engineering corrections are made in relation to the final value in the reflectometry server. This time the height is also still reading '0.0', but the motor position is `32.25`, which is to be expected given that the engineering correction adds `22.5*0.1` to the value. +7. Set `theta` to `2.0`. +8. At this point, the detector angle and height should still be reading as `-0.3` and `0.0`, but, the motors themselves have moved to `49.0` and `33.754` respectively, to maintain that theta value. -user_correction = UserFunctionCorrection(correction_function, some_BeamlineParameter) -``` +## Solution +
+Should you have trouble the following is what the code could look like -Any correction can be applied like this: -``` -add_driver(IocDriver(..., engineering_correction=SomeCorrection)) +```python +from typing import Dict + +from ReflectometryServer.beamline import Beamline +from ReflectometryServer.beamline_constant import BeamlineConstant +from ReflectometryServer.components import ( + Component, + ReflectingComponent, + ThetaComponent, + TiltingComponent, +) +from ReflectometryServer.config_helper import ( + add_component, + add_constant, + add_driver, + add_mode, + add_parameter, + add_slit_parameters, + get_configured_beamline, +) +from ReflectometryServer.engineering_corrections import ConstantCorrection, UserFunctionCorrection +from ReflectometryServer.geometry import ChangeAxis, PositionAndAngle +from ReflectometryServer.ioc_driver import IocDriver +from ReflectometryServer.out_of_beam import OutOfBeamPosition +from ReflectometryServer.parameters import AxisParameter, DirectParameter, InBeamParameter +from ReflectometryServer.pv_wrapper import MotorPVWrapper + +# Beamline Constants +NATURAL_ANGLE = 90 +S1_Z = 10.0 +SM_Z = 20.0 +S2_Z = SM_Z + 10.0 +SAMPLE_Z = S2_Z + 10.0 +DET_Z = SAMPLE_Z + 10.0 + + +def correction_function(_: None, param: float) -> float: + return param * 0.1 + + + +def get_beamline(macros: Dict[str, str]) -> Beamline: + ######################### + # FIXED BEAMLINE VALUES # + ######################### + + # Constants + add_constant( + BeamlineConstant( + "NATURAL_ANGLE", + NATURAL_ANGLE, + "The difference between the beam and straight through", + ) + ) + add_constant(BeamlineConstant("S1_Z", S1_Z, "The distance to slits 1")) + add_constant(BeamlineConstant("SM_Z", SM_Z, "The distance to the supermirror")) + add_constant(BeamlineConstant("S2_Z", S2_Z, "The distance to slits 2")) + add_constant(BeamlineConstant("DET_Z", DET_Z, "The distance to the detector")) + + # Modes + nr = add_mode("NR") + pnr = add_mode("PNR") + all_modes = [nr, pnr] + + # Direct Parameters + add_parameter( + DirectParameter( + "MONITORPOS", + pv_wrapper=MotorPVWrapper("MOT:MTR0208"), + description="Vertical pos of beam monitor", + ) + ) + + ############################## + # BEAMLINE MODEL STARTS HERE # + ############################## + + # Slits 1 + add_slit_parameters(1, include_centres=True) + s1_comp = add_component(Component("s1", PositionAndAngle(0.0, S1_Z, NATURAL_ANGLE))) + add_parameter( + AxisParameter( + "S1OFFSET", + s1_comp, + ChangeAxis.POSITION, + description="Vertical Position of Slit 1", + ), + modes=all_modes, + ) + add_driver(IocDriver(s1_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0301"))) + + # Mirror + mirror_comp = add_component( + ReflectingComponent("Mirror", PositionAndAngle(0, SM_Z, NATURAL_ANGLE)) + ) + SMANGLE = add_parameter( + AxisParameter( + "SMANGLE", + mirror_comp, + ChangeAxis.ANGLE, + description="Angle of the Supermirror", + autosave=True, + ), + modes=[pnr], + ) + add_parameter( + AxisParameter( + "SMOFFSET", + mirror_comp, + ChangeAxis.POSITION, + description="Vertical Position of the Supermirror", + ), + modes=[pnr], + ) + add_parameter( + InBeamParameter("SMIN", mirror_comp, description="Toggle Supermirror In Beam"), + modes=all_modes, + mode_inits=[(nr, 0), (pnr, 1)], + ) + add_driver( + IocDriver( + mirror_comp, + ChangeAxis.ANGLE, + MotorPVWrapper("MOT:MTR0207"), + out_of_beam_positions=[OutOfBeamPosition(0.0)], + ) + ) + add_driver( + IocDriver( + mirror_comp, + ChangeAxis.POSITION, + MotorPVWrapper("MOT:MTR0206"), + out_of_beam_positions=[OutOfBeamPosition(-20.0)], + ) + ) + + # Slits 2 + add_slit_parameters(2, include_centres=True) + s2_comp = add_component(Component("s2", PositionAndAngle(0.0, S2_Z, NATURAL_ANGLE))) + add_parameter( + AxisParameter( + "S2OFFSET", + s2_comp, + ChangeAxis.POSITION, + description="Vertical Position of Slit 2", + ), + modes=all_modes, + ) + add_driver(IocDriver(s2_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0302"))) + + # SAMPLE + sample_comp = add_component( + TiltingComponent("sample", PositionAndAngle(0, SAMPLE_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "SAMPOFFSET", + sample_comp, + ChangeAxis.POSITION, + description="Vertical Position of Sample", + characteristic_value="MOT:MTR0307", + ) + ) + add_parameter( + AxisParameter( + "SAMPPHI", + sample_comp, + ChangeAxis.ANGLE, + description="Phi Angle of Sample (Pitch)", + ) + ) + add_parameter( + AxisParameter( + "SAMPPSI", + sample_comp, + ChangeAxis.PSI, + description="Psi Angle of Sample (Roll)", + ) + ) + add_parameter( + AxisParameter( + "SAMPTRANS", + sample_comp, + ChangeAxis.TRANS, + description="Horizontal Position of Sample", + ) + ) + add_parameter( + InBeamParameter( + "SAMPIN", + sample_comp, + description="Toggle Sample In Beam", + ) + ) + add_driver( + IocDriver(sample_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0307")) + ) + add_driver(IocDriver(sample_comp, ChangeAxis.ANGLE, MotorPVWrapper("MOT:MTR0306"))) + add_driver(IocDriver(sample_comp, ChangeAxis.PSI, MotorPVWrapper("MOT:MTR0308"))) + add_driver( + IocDriver( + sample_comp, + ChangeAxis.TRANS, + MotorPVWrapper("MOT:MTR0305"), + out_of_beam_positions=[OutOfBeamPosition(100.0)], + ) + ) + + # THETA + theta_comp = add_component( + ThetaComponent("ThetaComp", PositionAndAngle(0.0, SAMPLE_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "THETA", + theta_comp, + ChangeAxis.ANGLE, + description="Incident Angle at Sample", + ), + modes=all_modes, + ) + + # DETECTOR + constant_correction = ConstantCorrection(0.3) + function_correction = UserFunctionCorrection(correction_function, SMANGLE) + + det_comp = add_component( + TiltingComponent("Detector", PositionAndAngle(0.0, DET_Z, NATURAL_ANGLE)) + ) + add_parameter( + AxisParameter( + "DETANGLE", det_comp, ChangeAxis.ANGLE, description="Angle of Detector", characteristic_value="MOT:MTR0201", + ), + modes=all_modes, + ) + add_parameter( + AxisParameter("DETHEIGHT", det_comp, ChangeAxis.POSITION, description="Vertical Position of Detector", characteristic_value="MOT:MTR0202",), + modes=all_modes, + ) + add_driver( + IocDriver( + det_comp, + ChangeAxis.ANGLE, + MotorPVWrapper("MOT:MTR0201"), + engineering_correction=constant_correction, + ) + ) + add_driver( + IocDriver( + det_comp, + ChangeAxis.POSITION, + MotorPVWrapper("MOT:MTR0202"), + engineering_correction=function_correction, + ) + ) + theta_comp.add_angle_to(det_comp) + + return get_configured_beamline() ``` diff --git "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-8.md" "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-8.md" index 52a707118..0b013ce9e 100644 --- "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-8.md" +++ "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Exercise-8.md" @@ -20,18 +20,336 @@ The transformation between these two sets of axes happens in the BenchComponent, In terms of the configuration, the bench takes a `BenchSetup` object instead of the usual `PositionAndAngle`, which defines the specific geometry of the bench. It is worth noting that all the benches (i.e. Detector bench on POLREF and the Front- and Detector benches on OFFSPEC) are identical regarding their dimensions. -### Exercise 8 +## Exercise 8 +### 1. Comment out the existing tracking detectors +We shall consider here a post sample bench, which replaces the tracking detector component usually. However, as this is a simulated beamline we can use the appropriate motors set up initially. however, as everything else stays valid, just put the detector part of `config.py` into a block comment. -In this exercise we will add the bench component to the configuration. You may notice that there aren't any bench related axes in the low motor table for the configuration - this is because the instrument config on the `REFL_TRAINING` branch is based on SURF which does not have a bench. Instead, for the purpose of this training you can re-purpose `MTR0401`, `0402` and `0403`, as the bench will replace the tracking detector component in the reflectometry config anyway. If you so wish, you can rename these axes by writing to the `DESC` field of a motor axis: -- `MTR0401.DESC` should be `Bench Front Jack` -- `MTR0402.DESC` should be `Bench Back Jack` -- `MTR0403.DESC` should be `Bench Slide` +### 2. Add the bench constants +Add in `BENCH_PIVOT_Z` and set it to `SAMPLE_Z`, as that is the ideal pivot point. Also create `BENCH_FRONT_Z` which is `10.0` from `BENCH_PIVOT_Z`, `BENCH_PIVOT_TO_FRONT` and set it to `10.0`, `BENCH_PIVOT_TO_REAR` and set it to `20.0`, and `BENCH_PIVOT_TO_BEAM` and set it to `5.0`. +We also need to add in the concept of both the `NATURAL_ANGLE` we've been using throughout and the `ANGLE_OF_MOVEMENT`. Rename `NATURAL_ANGLE` to `ANGLE_OF_MOVEMENT` throughout and add in a new `NATURAL_ANGLE` of `0.0`. Update the `ANGLE_OF_MOVEMENT` to be the `NATURAL_ANGLE` + `90.0`. -You can add the bench component like so ( -[Documentation on parameters required for `BenchSetup` can be found here](../Reflectometry-Configuration)): +### 3. Add the bench component +Use the bench component helper method, and the associated setup to create the bench as per: `bench = add_component(BenchComponent("bench", BenchSetup( ... )))`. +Both `BenchComponent` and `BenchSetup` are in `ReflectometryServer.components`. +Give the setup the following arguments: `0.0`, `BENCH_PIVOT_Z`, `ANGLE_OF_MOVEMENT`, `BENCH_PIVOT_TO_FRONT`, `BENCH_PIVOT_TO_REAR`, `NATURAL_ANGLE`, `BENCH_PIVOT_TO_BEAM`, `0.0`, and `10.0`. -`bench = add_component(BenchComponent("bench", BenchSetup( ... )))` +### 4. Add parameters and drivers for the bench +We need to add the parameters for the Change Axes `POSITION`, `ANGLE` and `SEESAW`, as well as drivers for Change Axes `JACK_FRONT`, `JACK_REAR` and `SLIDE` which are associated with `MOT:MTR0203`, `MOT:MTR0204`, `MOT:MTR0205` respectively. -You then need to add parameters for the Change Axes `POSITION`, `ANGLE` and `SEESAW`, as well as drivers for Change Axes `JACK_FRONT`, `JACK_REAR` and `SLIDE`. +### 5. Add the bench to `theta` +This time you are using the `theta.add_angle_of` method, and asking it to use the bench which is a different method to the one used by the detector previously. -Remember also that Theta is now calculated by the angle of the bench, rather than the height of the detector component, which needs to be reflected in the configuration with the `theta.add_angle_of` method! +## To Test +1. Set everything to `0.0`. +2. Restart the IOC to pick up the updated `config.py`. +3. The collimation panel should now have swapped out the detector entries for the bench angle and offset. The bench seesaw parameter should have been added to the other parameters tab. +4. Set the supermirror angle to `22.5`, and `theta` to `2.0`, use the move all button to set both values at the same time. +5. After the move, everything in the reflectometry server should be reading `0.0`, but the motors will be reading quite different values, as those are maintaining the transformed values to be `0.0`. The bench front should have gone to `29.785`, the back to `41.288`, and the slide to `0.578`. + +## Solution +
+Should you have trouble the following is what the code could look like + +```python +from typing import Dict + +from ReflectometryServer.beamline import Beamline +from ReflectometryServer.beamline_constant import BeamlineConstant +from ReflectometryServer.components import ( + BenchComponent, + BenchSetup, + Component, + ReflectingComponent, + ThetaComponent, + TiltingComponent, +) +from ReflectometryServer.config_helper import ( + add_component, + add_constant, + add_driver, + add_mode, + add_parameter, + add_slit_parameters, + get_configured_beamline, +) +from ReflectometryServer.engineering_corrections import ConstantCorrection, UserFunctionCorrection +from ReflectometryServer.geometry import ChangeAxis, PositionAndAngle +from ReflectometryServer.ioc_driver import IocDriver +from ReflectometryServer.out_of_beam import OutOfBeamPosition +from ReflectometryServer.parameters import AxisParameter, DirectParameter, InBeamParameter +from ReflectometryServer.pv_wrapper import MotorPVWrapper + +# Beamline Constants +NATURAL_ANGLE = 0.0 +ANGLE_OF_MOVEMENT = NATURAL_ANGLE + 90.0 +S1_Z = 10.0 +SM_Z = 20.0 +S2_Z = SM_Z + 10.0 +SAMPLE_Z = S2_Z + 10.0 +DET_Z = SAMPLE_Z + 10.0 +BENCH_PIVOT_Z = SAMPLE_Z +BENCH_FRONT_Z = BENCH_PIVOT_Z + 10.0 +BENCH_PIVOT_TO_FRONT = 10.0 +BENCH_PIVOT_TO_REAR = 20.0 +BENCH_PIVOT_TO_BEAM = 5.0 + + +def correction_function(_: None, param: float) -> float: + return param * 0.1 + + + +def get_beamline(macros: Dict[str, str]) -> Beamline: + ######################### + # FIXED BEAMLINE VALUES # + ######################### + + # Constants + add_constant( + BeamlineConstant( + "ANGLE_OF_MOVEMENT", + ANGLE_OF_MOVEMENT, + "The difference between the beam and straight through", + ) + ) + add_constant(BeamlineConstant("S1_Z", S1_Z, "The distance to slits 1")) + add_constant(BeamlineConstant("SM_Z", SM_Z, "The distance to the supermirror")) + add_constant(BeamlineConstant("S2_Z", S2_Z, "The distance to slits 2")) + add_constant(BeamlineConstant("DET_Z", DET_Z, "The distance to the detector")) + add_constant(BeamlineConstant("BENCH_PIVOT_Z", BENCH_PIVOT_Z, "The distance to the bench pivot point")) + add_constant(BeamlineConstant("BENCH_FRONT_Z", BENCH_FRONT_Z, "The distance to the front of the bench")) + add_constant(BeamlineConstant("BENCH_PIVOT_TO_FRONT", BENCH_PIVOT_TO_FRONT, "The distance from the bench pivot point to the front of the bench")) + add_constant(BeamlineConstant("BENCH_PIVOT_TO_REAR", BENCH_PIVOT_TO_REAR, "The distance from the bench pivot point to the rear of the bench")) + add_constant(BeamlineConstant("BENCH_PIVOT_TO_BEAM", BENCH_PIVOT_TO_BEAM, "The distance from the bench pivot point to the beam")) + + # Modes + nr = add_mode("NR") + pnr = add_mode("PNR") + all_modes = [nr, pnr] + + # Direct Parameters + add_parameter( + DirectParameter( + "MONITORPOS", + pv_wrapper=MotorPVWrapper("MOT:MTR0208"), + description="Vertical pos of beam monitor", + ) + ) + + ############################## + # BEAMLINE MODEL STARTS HERE # + ############################## + + # Slits 1 + add_slit_parameters(1, include_centres=True) + s1_comp = add_component(Component("s1", PositionAndAngle(0.0, S1_Z, ANGLE_OF_MOVEMENT))) + add_parameter( + AxisParameter( + "S1OFFSET", + s1_comp, + ChangeAxis.POSITION, + description="Vertical Position of Slit 1", + ), + modes=all_modes, + ) + add_driver(IocDriver(s1_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0301"))) + + # Mirror + mirror_comp = add_component( + ReflectingComponent("Mirror", PositionAndAngle(0, SM_Z, ANGLE_OF_MOVEMENT)) + ) + SMANGLE = add_parameter( + AxisParameter( + "SMANGLE", + mirror_comp, + ChangeAxis.ANGLE, + description="Angle of the Supermirror", + autosave=True, + ), + modes=[pnr], + ) + add_parameter( + AxisParameter( + "SMOFFSET", + mirror_comp, + ChangeAxis.POSITION, + description="Vertical Position of the Supermirror", + ), + modes=[pnr], + ) + add_parameter( + InBeamParameter("SMIN", mirror_comp, description="Toggle Supermirror In Beam"), + modes=all_modes, + mode_inits=[(nr, 0), (pnr, 1)], + ) + add_driver( + IocDriver( + mirror_comp, + ChangeAxis.ANGLE, + MotorPVWrapper("MOT:MTR0207"), + out_of_beam_positions=[OutOfBeamPosition(0.0)], + ) + ) + add_driver( + IocDriver( + mirror_comp, + ChangeAxis.POSITION, + MotorPVWrapper("MOT:MTR0206"), + out_of_beam_positions=[OutOfBeamPosition(-20.0)], + ) + ) + + # Slits 2 + add_slit_parameters(2, include_centres=True) + s2_comp = add_component(Component("s2", PositionAndAngle(0.0, S2_Z, ANGLE_OF_MOVEMENT))) + add_parameter( + AxisParameter( + "S2OFFSET", + s2_comp, + ChangeAxis.POSITION, + description="Vertical Position of Slit 2", + ), + modes=all_modes, + ) + add_driver(IocDriver(s2_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0302"))) + + # SAMPLE + sample_comp = add_component( + TiltingComponent("sample", PositionAndAngle(0, SAMPLE_Z, ANGLE_OF_MOVEMENT)) + ) + add_parameter( + AxisParameter( + "SAMPOFFSET", + sample_comp, + ChangeAxis.POSITION, + description="Vertical Position of Sample", + characteristic_value="MOT:MTR0307", + ) + ) + add_parameter( + AxisParameter( + "SAMPPHI", + sample_comp, + ChangeAxis.ANGLE, + description="Phi Angle of Sample (Pitch)", + ) + ) + add_parameter( + AxisParameter( + "SAMPPSI", + sample_comp, + ChangeAxis.PSI, + description="Psi Angle of Sample (Roll)", + ) + ) + add_parameter( + AxisParameter( + "SAMPTRANS", + sample_comp, + ChangeAxis.TRANS, + description="Horizontal Position of Sample", + ) + ) + add_parameter( + InBeamParameter( + "SAMPIN", + sample_comp, + description="Toggle Sample In Beam", + ) + ) + add_driver( + IocDriver(sample_comp, ChangeAxis.POSITION, MotorPVWrapper("MOT:MTR0307")) + ) + add_driver(IocDriver(sample_comp, ChangeAxis.ANGLE, MotorPVWrapper("MOT:MTR0306"))) + add_driver(IocDriver(sample_comp, ChangeAxis.PSI, MotorPVWrapper("MOT:MTR0308"))) + add_driver( + IocDriver( + sample_comp, + ChangeAxis.TRANS, + MotorPVWrapper("MOT:MTR0305"), + out_of_beam_positions=[OutOfBeamPosition(100.0)], + ) + ) + + # THETA + theta_comp = add_component( + ThetaComponent("ThetaComp", PositionAndAngle(0.0, SAMPLE_Z, ANGLE_OF_MOVEMENT)) + ) + add_parameter( + AxisParameter( + "THETA", + theta_comp, + ChangeAxis.ANGLE, + description="Incident Angle at Sample", + ), + modes=all_modes, + ) + + """ + Comment out for Exercise 8 + # DETECTOR + constant_correction = ConstantCorrection(0.3) + function_correction = UserFunctionCorrection(correction_function, SMANGLE) + + det_comp = add_component( + TiltingComponent("Detector", PositionAndAngle(0.0, DET_Z, ANGLE_OF_MOVEMENT)) + ) + add_parameter( + AxisParameter( + "DETANGLE", det_comp, ChangeAxis.ANGLE, description="Angle of Detector", characteristic_value="MOT:MTR0201", + ), + modes=all_modes, + ) + add_parameter( + AxisParameter("DETHEIGHT", det_comp, ChangeAxis.POSITION, description="Vertical Position of Detector", characteristic_value="MOT:MTR0202",), + modes=all_modes, + ) + add_driver( + IocDriver( + det_comp, + ChangeAxis.ANGLE, + MotorPVWrapper("MOT:MTR0201"), + engineering_correction=constant_correction, + ) + ) + add_driver( + IocDriver( + det_comp, + ChangeAxis.POSITION, + MotorPVWrapper("MOT:MTR0202"), + engineering_correction=function_correction, + ) + ) + theta_comp.add_angle_to(det_comp) + """ + + # BENCH + bench = add_component( + BenchComponent( + "bench", + BenchSetup( + 0.0, + BENCH_PIVOT_Z, + ANGLE_OF_MOVEMENT, + BENCH_PIVOT_TO_FRONT, + BENCH_PIVOT_TO_REAR, + NATURAL_ANGLE, + BENCH_PIVOT_TO_BEAM, + 0.0, + 10.0, + ), + ) + ) + + add_parameter(AxisParameter("B_ANGLE", bench, ChangeAxis.ANGLE), modes=all_modes) + add_parameter(AxisParameter("B_OFFSET", bench, ChangeAxis.POSITION), modes=all_modes) + add_parameter(AxisParameter("B_SEESAW", bench, ChangeAxis.SEESAW), modes=all_modes) + + add_driver(IocDriver(bench, ChangeAxis.JACK_FRONT, MotorPVWrapper("MOT:MTR0203"))) + add_driver(IocDriver(bench, ChangeAxis.JACK_REAR, MotorPVWrapper("MOT:MTR0204"))) + add_driver(IocDriver(bench, ChangeAxis.SLIDE, MotorPVWrapper("MOT:MTR0205"))) + + theta_comp.add_angle_of(bench) + + return get_configured_beamline() +``` diff --git "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Overview-&-Setup.md" "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Overview-&-Setup.md" index 62c65895d..f73c64723 100644 --- "a/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Overview-&-Setup.md" +++ "b/doc/specific_iocs/reflectometry/config_training/Reflectometry-Config-Training-\342\200\220-Overview-&-Setup.md" @@ -2,7 +2,7 @@ This training unit presents a series of exercises which take you through the creation of a reflectometry config. The aim of this is to become more confident in working with the python configuration itself as well as to hopefully learn something about how the reflectometry IOC works internally. -**NB:** While I have taken care that the examples in the exercises produce a beamline model that resembles those of real instruments, it will be simplified in many ways for the sake of clarity. +**NB:** Whilst care has been taken so that the examples in the exercises produce a beamline model that resembles those of real instruments, it will be simplified in many ways for the sake of clarity. ## Useful Resources: @@ -12,18 +12,75 @@ This training unit presents a series of exercises which take you through the cre ## Setup Instrument Configuration & Dev Environment: -1. Make sure your IBEX Server is not running -1. In `/Instrument/settings/config//`, pull branch `REFL_TRAINING`. This contains a blank reflectometry config `config.py` for the exercises, as well as example solutions. **Please do not commit anything to this branch directly** as it is intended as a blank starting point for anyone wanting to do this course. If you want to keep your local changes under version control, please create your own branch based off `REFL_TRAINING`. -1. (Optional) in `/Instrument/settings/config//Python/` rename `init_inst.py` to `init_.py`. This will load relevant reflectometry routines into genie_python sessions. Scripting is outside of the scope of this course so you should be able to complete everything without having done this, so this is just in case you want to play around. -1. (Optional but recommended) in `/Instrument/Apps/EPICS/ioc/master/Galil/iocBoot/iocGALIL-IOC-01/st-common.cmd`, find a line that says "Save motor settings every 30 seconds" and delete the relevant `$(IFNOTRECSIM)` conditional. This will make it so that simulated Galil axes retain their settings (limits, naming etc.) on IOC restart which can otherwise be a bit tedious to reapply every time. -1. Start IBEX Server -1. From an EPICS terminal, run `/Instrument/settings/config//configurations/refl/setup_motors.bat`. This will set up your motor axes to look like the SURF beamline through a series of caputs. SURF was chosen as a base as it is one of the more simple reflectometers. -1. In the IBEX Menu Bar, navigate to `Preferences > Select Visible Perspectives` and tick yes for "Reflectometry" -1. Restart the `REFL_01` IOC. The front panel on the Reflectometry Perspective should display a server status of "OKAY". If you see a lot of purple disconnected boxes, don't worry, this is expected. All other tabs should be blank at this point, i.e. no items listed. -1. You will be editing the python reflectometry configuration at `/Instrument/settings/config//configurations/refl/config.py` Next to it, there should be a list of example solutions to all exercises. I highly recommend opening this in an IDE of your choice that lets you add `/Instrument/Apps/EPICS/support/refl/` as a project dependency, as having access to class/function definitions and autocomplete will be extremely useful. - -You should now be all set up! If you want to load one of the example solutions as the config for the reflectometry server, you can do this by setting a macro on the reflectometry IOC: -1. Navigate to `Menu > Configurations > Components > Edit > Reflectometry` -1. Edit the `REFL_01` IOC -1. Set the `CONFIG_FILE` macro to e.g. `ex1_solution.py` +1. Navigate to `...\Apps\EPICS\support\motorExtensions\master\settings\\galil` +2. Create a `jaws.cmd` with the following contents, so that the appropriate jaws are available later. + +``` +$(IFIOC_GALIL_01) dbLoadRecords("$(JAWS)/db/jaws.db","P=$(MYPVPREFIX)MOT:,JAWS=JAWS1:,mXN=MTR0101,mXS=MTR0102,mXW=MTR0104,mXE=MTR0103") +$(IFIOC_GALIL_01) dbLoadRecords("$(JAWS)/db/jaws.db","P=$(MYPVPREFIX)MOT:,JAWS=JAWS2:,mXN=MTR0105,mXS=MTR0106,mXW=MTR0108,mXE=MTR0107") +``` + +3. In the same folder create an `axes.cmd` with the following, these aliases are useful later. + +``` +$(IFIOC_GALIL_03) dbLoadRecords("$(AXIS)/db/axis.db","P=$(MYPVPREFIX)MOT:,AXIS=STACK:TRANS,mAXIS=MTR0305") +$(IFIOC_GALIL_03) dbLoadRecords("$(AXIS)/db/axis.db","P=$(MYPVPREFIX)MOT:,AXIS=STACK:HEIGHT,mAXIS=MTR0307") +$(IFIOC_GALIL_03) dbLoadRecords("$(AXIS)/db/axis.db","P=$(MYPVPREFIX)MOT:,AXIS=STACK:PHI,mAXIS=MTR0306") +$(IFIOC_GALIL_03) dbLoadRecords("$(AXIS)/db/axis.db","P=$(MYPVPREFIX)MOT:,AXIS=STACK:PSI,mAXIS=MTR0308") +$(IFIOC_GALIL_02) dbLoadRecords("$(AXIS)/db/axis.db","P=$(MYPVPREFIX)MOT:,AXIS=MONITOR,mAXIS=MTR0208") +``` + +4. Open up the IBEX GUI. +5. Create a new configuration called `REFL_TRAINING` or something similar. +6. Add the `GALIL_01` IOC to the configuration, give it a `Sim. Level` of `RECSIM`, and assign `01` to the `MTRCTRL` macro, everything else can stay as the default values. +7. Add the `GALIL_02` IOC to the configuration, give it a `Sim. Level` of `RECSIM`, and assign `02` to the `MTRCTRL` macro, everything else can stay as the default values. +8. Add the `GALIL_03` IOC to the configuration, give it a `Sim. Level` of `RECSIM`, and assign `03` to the `MTRCTRL` macro, everything else can stay as the default values. +9. Add the `REFL_01` IOC to the configuration, everything stays as the default values. +10. Load your configuration. +11. It may make things clearer if you update the motor descriptions as per the following table: + +| Axis | Galil 01 | Galil 02 | Galil 03 | +| -- | -- | -- | -- | +| 01 | Jaws 1, North | Detector Angle | Slit 1 Offset | +| 02 | Jaws 1, South | Detector Position | Slit 2 Offset | +| 03 | Jaws 1, East | Bench Front | | +| 04 | Jaws 1, West | Bench Back | | +| 05 | Jaws 2, North | Bench Slide | Sample Stack Translation | +| 06 | Jaws 2, South | Supermirror Position | Sample Stack Phi | +| 07 | Jaws 2, East | Supermirror Angle | Sample Stack Height/Offset | +| 08 | Jaws 2, West | Monitor | Sample Stack Psi | + +12. Make sure the Reflectometry perspective is available to you, if it isn't you can make it visible via the `Preferences` menu. +13. At this point if you open the Reflectometry perspective the `Server Status` should indicate an `ERROR` as it can't find the configuration. +14. In the configurations folder for the computer you are using create a folder called `refl`, and in `refl` create `config.py`. +15. If you restart the `REFL_01` IOC at this point, the server will still be in error, but the error text should have changed to being unable to read the file rather than being unable to find it. +16. Open `config.py` in the editor of your choice, and copy in this code, which is the reflectometry configuration you have that will edit and load during the exercises. This is the simplest version you can have to load and not create an error or warning. + +```Python +from typing import Dict + +from ReflectometryServer.beamline import Beamline +from ReflectometryServer.config_helper import ( + add_mode, + get_configured_beamline, +) + + +def get_beamline(macros: Dict[str, str]) -> Beamline: + ######################### + # FIXED BEAMLINE VALUES # + ######################### + + # Modes + _nr = add_mode("NR") # The underscore is to ensure this passes for pyright because the mode is needed for getting the configured beamline, but is unused locally + + ############################## + # BEAMLINE MODEL STARTS HERE # + ############################## + + return get_configured_beamline() + +``` + +17. At this point you should be able to edit `config.py` in the following pages and build up your example system. The make up of the configuration will be covered there as well.