Tutorial¶
Boilerplate setup¶
The commands below are for creating a simple testing system in the tutorial. This merely guarantees that the tutorial is always in sync with the actual behaviour of the software. The tutorial proper begins in the next section.
>>> import logging
>>> import sys
>>> ch = logging.StreamHandler(sys.stdout)
>>> ch.setLevel(logging.DEBUG)
>>> logging.getLogger().setLevel(logging.DEBUG)
>>> logging.getLogger().addHandler(ch)
Basic examples¶
First, we’ll just do a simple measurement on the main detector for 600 frames.
>>> from src import *
>>> from src.genie import gen
>>> measure("Sample Name", frames=600)
Setup Larmor for event
Using the following Sample Parameters
Geometry=Flat Plate
Width=10
Height=10
Thickness=1.0
Measuring Sample Name_SANS for 600 frames
The ScanningInstrument.measure() command is the primary entry
point for all types of SANS measurement. We can pass it a sample
changer position if we wish to measure at a specific location.
>>> measure("Sample Name", "QT", aperature="Medium", uamps=5)
Moving to sample changer position QT
Using the following Sample Parameters
Geometry=Flat Plate
Width=10
Height=10
Thickness=1.0
Measuring Sample Name_SANS for 5 uamps
A couple of things changed with this new command.
- I’ve measured for 5 µamps instead of the 600 frames we did before.
The measure command will take and of the time commands that
genie_python’s
waitforcommand will accept, thoughuamps,frames, andsecondswill almost always be the ones which are needed. - We’ve passed sample position QT in as the position parameter and the instrument has dutifully moved into position QT before starting the measurement.
- We specified the beam size. The individual beamlines will have the opportunity to decide their own aperature settings, but there should hopefully reach a consensus on the names.
- You’ll notice that there is no message about putting the instrument in event mode. Since we were already in event mode, the instrument didn’t perform the redundant step.
>>> measure("Sample Name", CoarseZ=25, uamps=5, thickness=2.0, trans=True)
Moving CoarseZ to 25
Setup Larmor for transmission
Using the following Sample Parameters
Geometry=Flat Plate
Width=10
Height=10
Thickness=2.0
Measuring Sample Name_TRANS for 5 uamps
Here we are directly setting the moving the CoarseZ motor on the sample stack to our desired position, instead of just picking a position for the sample changer. We have also recorded that this run is on a 2 mm sample, unlike our previous 1 mm runs. Finally, the instrument has converted into transmission mode, setting the appropriate wiring tables and moving the M4 monitor into the beam.
>>> measure("Sample Name", "CT", SampleX=10, Julabo1_SP=35, uamps=5)
Moving to sample changer position CT
Moving Julabo1_SP to 35
Moving SampleX to 10
Setup Larmor for event
Using the following Sample Parameters
Geometry=Flat Plate
Width=10
Height=10
Thickness=1.0
Measuring Sample Name_SANS for 5 uamps
We can combine a sample changer position with motor movements. This is useful for custom mounting that may not perfectly align with the sample changer positions. Alternately, since any block can be set within the measure command, it is also possible to set temperatures and other beam-line parameters for a measurement.
>>> def weird_place():
... gen.cset(Translation=100)
... gen.cset(CoarseZ=-75)
>>> measure("Sample Name", weird_place, Julabo1_SP=37, uamps=10)
Moving to position weird_place
Moving Julabo1_SP to 37
Using the following Sample Parameters
Geometry=Flat Plate
Width=10
Height=10
Thickness=1.0
Measuring Sample Name_SANS for 10 uamps
Finally, if the experiment requires a large number of custom positions, they can be set independently in their own functions. Measure can then move to that position as though it were a standard sample changer position. It’s still possible to override or amend these custom positions with measurement specific values, as we have done above with the Julabo temperature again.
>>> setup_dae_bsalignment()
Setup Larmor for bsalignment
>>> measure("Beam stop", frames=300, dae_fixed=True)
Using the following Sample Parameters
Geometry=Flat Plate
Width=10
Height=10
Thickness=1.0
Measuring Beam stop_SANS for 300 frames
>>> measure("Beam stop", frames=300)
Setup Larmor for event
Using the following Sample Parameters
Geometry=Flat Plate
Width=10
Height=10
Thickness=1.0
Measuring Beam stop_SANS for 300 frames
By default, when taking a sans measurement, the
ScanningInstrument.measure() function puts the instrument
into event mode. Similarly, trans measurements are always in
transmission mode. Setting the dae_fixed property to True ignores
the default mode and maintains whatever mode the instrument is
currently in.
Automated script checking¶
This module includes a decorator user_script() that can be
added to the front of any user function. This will allow the
scripting system to scan the script for common problems before it is
run, ensuring that problems are noticed immediately and not at one in
the morning. All that’s required of the user is putting
@user_script on the line before any functions that they define.
>>> @user_script
... def trial():
... measure("Test1", "BT", uamps=30)
... measure("Test2", "VT", uamps=30)
... measure("Test1", "BT", trans=True, uanps=10)
... measure("Test2", "VT", trans=True, uamps=10)
>>> trial()
Traceback (most recent call last):
...
RuntimeError: Position VT does not exist
What may not be immediately obvious from reading is that this error message occurs instantly, not forty five minutes into the run after the first measurement has already been performed. Fixing the “VT” positions to “CT” then gives:
>>> @user_script
... def trial():
... measure("Test1", "BT", uamps=30)
... measure("Test2", "TT", uamps=30)
... measure("Test1", "BT", trans=True, uanps=10)
... measure("Test2", "TT", trans=True, uamps=10)
>>> trial()
Traceback (most recent call last):
...
RuntimeError: Unknown Block uanps
Again, an easy typo to make at midnight that normally would not be found until two in the morning.
>>> @user_script
... def trial():
... measure("Test1", "BT", uamps=30)
... measure("Test2", "TT", uamps=30)
... measure("Test1", "BT", trans=True, uamps=10)
... measure("Test2", "TT", trans=True, uamps=10)
>>> trial()
The script should finish in 2.0 hours
...
Measuring Test2_TRANS for 10 uamps
Once the script has been validated, which should happen nearly instantly, the program will print an estimate of the time needed for the script and the approximate time of completion (not shown). It will then run the script for real.
Large script handling¶
The ScanningInstrument.measure_file() function allows the
user to define everything in a CSV file with excel and then run it
through python.
| title | uamps | pos | thickness | trans | CoarseZ |
|---|---|---|---|---|---|
| Sample1 | 10 | AT | 1 | ||
| Sample2 | 10 | BT | 1 | 30 | |
| Sample3 | 10 | CT | 2 | ||
| Sample4 | 10 | DT | 2 | ||
| Sample5 | 20 | ET | 1 | ||
| Sample1 | 10 | AT | 1 | TRUE | |
| Sample2 | 10 | BT | 1 | TRUE | 30 |
| Sample3 | 10 | CT | 2 | TRUE | |
| Sample4 | 10 | DT | 2 | TRUE | |
| Sample5 | 20 | ET | 1 | TRUE |
>>> measure_file("tests/test.csv")
The script should finish in 3.0 hours
...
Measuring Sample5_TRANS for 20 uamps
The particular keyword argument to the
ScanningInstrument.measure() function is given in the header
on the first line of the file. Each subsequent line represents a
single run with the parameters given in the columns of that row. If
an argument is left blank, then the keyword’s default value is used.
The boolean values True and False are case insensitive, but all other
strings retain their case.
| title | uamps | pos | thickness | trans | Julabo |
|---|---|---|---|---|---|
| Sample1 | 10 | AT | 1 | ||
| Sample2 | 10 | BT | 1 | TRUE | 7 |
>>> measure_file("tests/bad_julabo.csv")
Traceback (most recent call last):
...
RuntimeError: Unknown Block Julabo
Each CSV file is run through the user_script()
function defined above, so the script will be checked for errors before being run.
In the example above, the user set the column header to “Julabo”, but
the actual block name is “Julabo1_SP”.
If we fix the script file
| title | uamps | pos | thickness | trans | Julabo1_SP |
|---|---|---|---|---|---|
| Sample1 | 10 | AT | 1 | ||
| Sample2 | 10 | BT | 1 | TRUE | 7 |
>>> measure_file("tests/good_julabo.csv")
The script should finish in 0.5 hours
...
Measuring Sample2_TRANS for 10 uamps
>> measure_file(“tests/good_julabo.csv”, forever=True)
The scan then runs as normal. If the users are leaving and you want
to ensure that the script keeps taking data until they return, the
forever flag causes the instrument to repeatedly cycle through the
script until there is a manual intervention at the keyboard. The
output is not shown above because there is infinite output.
Detector Status¶
As an obvious sanity check, it is possible to check if the detector is on.
>>> detector_on()
True
We can also power cycle the detector.
>>> detector_on(False)
Waiting For Detector To Power Down (60s)
False
If we try to start a measurement with the detector off, the detector will be turned back on.
>>> measure("Sample", frames=100)
The detector was off. Turning on the detector
Waiting For Detector To Power Up (180s)
Setup Larmor for event
Using the following Sample Parameters
Geometry=Flat Plate
Width=10
Height=10
Thickness=1.0
Measuring Sample_SANS for 100 frames
Performing transmission measurements does not require the detector
>>> detector_on(False)
Waiting For Detector To Power Down (60s)
False
>>> measure("Sample", trans=True, frames=100)
Setup Larmor for transmission
Using the following Sample Parameters
Geometry=Flat Plate
Width=10
Height=10
Thickness=1.0
Measuring Sample_TRANS for 100 frames
>>> detector_on(True)
Waiting For Detector To Power Up (180s)
True
Under the hood¶
>>> gen.reset_mock()
>>> measure("Test", "BT", aperature="Medium", uamps=15)
Moving to sample changer position BT
Setup Larmor for event
Using the following Sample Parameters
Geometry=Flat Plate
Width=10
Height=10
Thickness=1.0
Measuring Test_SANS for 15 uamps
This command returns no result, but should cause a large number of actions to be run through genie-python. We can verify those actions through the mock genie object that’s created when the actual genie-python isn’t found.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | >>> print(gen.mock_calls)
[call.get_runstate(),
call.get_pv('IN: LARMOR: CAEN: hv0: 0: 8: status'),
call.get_pv('IN: LARMOR: CAEN: hv0: 0: 9: status'),
call.get_pv('IN: LARMOR: CAEN: hv0: 0: 10: status'),
call.get_pv('IN: LARMOR: CAEN: hv0: 0: 11: status'),
call.cset(SamplePos='BT'),
call.change(nperiods=1),
call.change_start(),
call.change_tables(detector='C:\\Instrument\\Settings\\Tables\\detector.dat'),
call.change_tables(spectra='C:\\Instrument\\Settings\\Tables\\spectra_1To1.dat'),
call.change_tables(wiring='C:\\Instrument\\Settings\\Tables\\wiring_event.dat'),
call.change_tcb(high=100000.0, log=0, low=5.0, step=100.0, trange=1),
call.change_tcb(high=0.0, log=0, low=0.0, step=0.0, trange=2),
call.change_tcb(high=100000.0, log=0, low=5.0, regime=2, step=2.0, trange=1),
call.change_finish(),
call.cset(T0Phase=0),
call.cset(TargetDiskPhase=2750),
call.cset(InstrumentDiskPhase=2450),
call.cset(a1hgap=20.0, a1vgap=20.0, s1hgap=14.0, s1vgap=14.0),
call.cset(m4trans=200.0),
call.waitfor_move(),
call.change_sample_par('Thick', 1.0),
call.get_sample_pars(),
call.change(title='Test_SANS'),
call.begin(),
call.waitfor(uamps=15),
call.end()]
|
That’s quite a few commands, so it’s worth running through them.
| 2: | Ensure that the instrument is ready to start a measurement |
|---|---|
| 3-6: | Check that the detector is on |
| 7: | Move the sample into position |
| 8-19: | Put the instrument in event mode |
| 20: | Set the upstream slits |
| 21: | Move the M4 transmission monitor out of the beam |
| 22: | Let motors finish moving. |
| 23: | Set the sample thickness |
| 24: | Print and log the sample parameters |
| 25: | Set the sample title |
| 26: | Start the measurement. |
| 27: | Wait the requested time |
| 28: | Stop the measurement. |