3.12. - The Scan Macros In Detail
All the scan macros in the standard package share a similar structure. To keep the format of the output sent to the data file, printer and screen consistent, common parts of each scan are defined as macros that are called by all the scans. For example, the
scan_head
macro is called by each scan to write
scan
headers on all the output files and devices.
Certain macros are shared by all the scans for another reason.
Special operating modes or options are implemented by redefining
shared macros.
For example, the
scan_move
macro, called within the looping portion of the scans, is
normally defined as
_move
,
which is:
def _move 'move_em; waitmove; getangles; calcHKL'In powder mode,
scan_move
is defined as
_pmove
,
a slightly more complicated macro, designed to move the designated
powder averaging motor some width
on alternating sides of the
center trajectory of the scan,
def _pmove ' if (_stype&2) _cp = A[_pmot] A[_pmot] = _cp + _pwid/2 _pwid = -_pwid move_em; waitmove; getangles; A[_pmot] = _cp; calcHKL '
The following paragraphs explain in detail the construction of the scan macros, using the single-motor scan,
ascan
,
as an example.
Here is its definition:
def ascan ' if ($# != 5) { print "Usage: ascan motor start finish intervals time" exit } _check0 "$1" { _m1 = $1; _s1 = $2; _f1 = $3; _n1 = int($4); _ctime = $5 } if (_n1 <= 0) { print "Intervals <= 0" exit } _bad_lim = 0 _chk_lim _m1 _s1 _chk_lim _m1 _f1 if (_bad_lim) exit HEADING = sprintf("ascan %s %g %g %g %g","$1",$2,$3,$4,$5) _d1 = (_f1 - _s1) / _n1++ _cols=4 X_L = motor_name(_m1) _sx = _s1 ; _fx = _f1 _stype = 1|(1<<8) FPRNT=sprintf("%s H K L", motor_name(_m1)) PPRNT=sprintf("%8.8s", motor_name(_m1)) VPRNT=sprintf("%9.9s", motor_name(_m1)) scan_head def _scan_on \' for (; NPTS < _n1; NPTS++) { A[_m1] = _s1 + NPTS * _d1 scan_move FPRNT=sprintf("%g %g %g %g",A[_m1],H,K,L) PPRNT=sprintf("%8.4f",A[_m1]) VPRNT=sprintf("%9.4f",A[_m1]) scan_loop pl_put(NPTS, A[_m1], S[DET]) scan_plot } scan_tail \' _scan_on '
In
ascan
, as in all scans, the first thing to do is to check
the number of arguments,
$#
, and if incorrect, print a usage message:
if ($# != 5) { print "Usage: ascan motor start finish intervals time" exit }Next, the
_check0
macro is called,
_check0 "$1"as it is whenever a motor mnemonic is used as an argument in the standard macros. The macro checks its argument against all valid motor mnemonics and motor numbers. The purpose is to prevent unintentionally sending motors into motion if the user mistypes a mnemonic. The definition of
_check0
is
def _check0 '{ local _i for (_i = 0; _i <= MOTORS; _i++) if (_i == MOTORS) { print "Invalid motor name: $1" exit } else if ($1 == _i) { if ("$1" != motor_mne(_i) && "$1" != _i) { print "Invalid motor name: $1" exit } else break } '
Next in
ascan
, the global variables used in the scan are initialized from the arguments.
{ _m1 = $1; _s1 = $2; _f1 = $3; _n1 = int($4); _ctime = $5 }The global variables being assigned are shared by all the scans.
Next in
ascan
, a check is made to ensure the number of intervals is positive.
if (_n1 <= 0) { print "Intervals <= 0" exit }
The next four lines do a motor limit check before the start of the scan.
_bad_lim = 0 _chk_lim _m1 _s1 _chk_lim _m1 _f1 if (_bad_lim) exitThe
_chk_lim
macro sets the flag
_bad_lim
if the position given by the second argument is outside the limits
of the motor given by the first argument.
def _chk_lim '{ local _u _t if ((_u = dial($1, $2)) < (_t = get_lim($1, -1))) { printf("%s will hit low limit at %g.\n",motor_name($1),_t) _bad_lim++ } else if (_u > (_t = get_lim($1, 1))) { printf("%s will hit high limit at %g.\n",motor_name($1),_t) _bad_lim++ } }'The prescan limit check is straightforward for simple motor scans. For reciprocal space scans, the limit check must loop through all the points of the scan since the motor positions are not necessarily monotonic functions of the scan variables.
Next in
ascan
, the global variable
HEADING
is initialized.
HEADING = sprintf("ascan %s %g %g %g %g","$1",$2,$3,$4,$5)It is used in the scan headers written to the file, screen and printer, and records the arguments with which the scan was invoked.
Next, some global scan variables are initialized.
_d1 = (_f1 - _s1) / _n1++ _cols=4 X_L = motor_name(_m1) _sx = _s1 ; _fx = _f1 _stype = 1|(1<<8)The
_d1
variable is set to the step size for the scan.
The number of intervals in
_n1
is incremented so its value will be the actual number of points.
The
_cols
global variable
is set to the number of extra columns this scan
will use in the data file.
Here it is four, for the motor position and values of
H,
K
and
L
at each point.
X_L
is set to the
x-axis
label to use on the plot of the scan.
The globals
_sx
and
_fx
are set to the endpoints of the
x
axis to be used in plotting the data on the screen during
the scan.
The variable
_stype
is treated as a two byte integer and
holds
a code representing the current scan type.
The low-order byte is a bit flag, while
the high order byte contains a number value.
The expression
1|(1<<8)
use the bitwise-or and the bitwise-shift operators to put values in each byte.
Currently, the following codes are used:
Code | Type Of Scan | High-Order Byte |
|
||
1 | motor | number of motors |
2 | HKL | nothing |
4 | temperature | nothing |
Next in
ascan
, the global variables
FPRNT
, PPRNT
and
VPRNT
are given string values to be used for file, printer and
video-screen column labels particular to this scan.
FPRNT=sprintf("%s H K L", motor_name(_m1)) PPRNT=sprintf("%8.8s", motor_name(_m1)) VPRNT=sprintf("%9.9s", motor_name(_m1))Each label contains the name of the motor being scanned, although printed with a different field width. Different widths are used to fit the widths and number of fields on the target devices. A challenge in constructing the scan macros is to fit all the desired columns of information within a single line. All the scans must limit the line length to 132 columns for output sent to the printer. (80-column printers must be operated in compressed mode to make their carriages effectively 132 columns wide.) The video screen is 80 columns wide. For the data file, there is no restriction on width. Also for the data file, no attempt is made to line up items in columns.
Next in
ascan
is the
scan_head
macro, called to do the general initialization.
All scan macros call
scan_head
. The default definition of
scan_head
is
def scan_head '_head'where
_head
is defined as,
def _head ' _scan_time waitall; get_angles; calcHKL NPTS = T_AV = MT_AV = 0 DATE = date() TIME = TIME_END = time() _cp = A[_pmot] rdef cleanup "_scanabort" # DATA FILE HEADER ond; offt printf("\n#S %d %s\n#D %s\n",++SCAN_N,HEADING,DATE) if (_ctime < 0) printf("#M %g (%s)\n", -_ctime, S_NA[MON]) else printf("#T %g (%s)\n", _ctime, S_NA[sec]) printf("#G") for (_i=0; _i<NPARAM; _i++) printf(" %g", G[_i]) printf("\n") printf("#Q %g %g %g\n", H, K, L) { local _i _j _k for (_i = 0, _k = MOTORS; _i < _k; _i += 8) { printf("#P%d ", _i/8) _mo_loop .6g "A[mA[_j]]" } } Fheader printf("#N %d\n", _cols + 3) printf("#L %s%s Epoch %s %s\n",FPRNT,Flabel,\ S_NA[_ctime < 0? sec:MON],S_NA[DET]) offd # PRINTER HEADER onp; offt printf("\n\f\nScan %3d %s file = %s %s user = %s\n%s\n\n",\ SCAN_N,DATE,DATAFILE,TITLE,USER,HEADING) { local _i _j _k for (_i = 0, _k = MOTORS; _i < _k; _i += 8) { printf(" ") _mo_loop 9.9s "motor_name(mA[_j])" printf(" ") _mo_loop 9.6g "A[mA[_j]]" } } Pheader printf("\n # %11.9s %11.9s %11.9s %8.8s %8.8s %8.8s %s%s\n",\ "H","K","L",S_NA[sec],S_NA[MON],S_NA[DET],PPRNT,Plabel) offp # TTY HEADER ont printf("\nScan %3d %s file = %s %s user = %s\n%s\n\n",\ SCAN_N,DATE,DATAFILE,TITLE,USER,HEADING) printf(" # %s %8.8s %8.8s %10.10s%s\n",\ VPRNT,S_NA[DET],S_NA[MON],S_NA[sec],Plabel) '
The commands at the beginning of
_head
, waitall; get_angles; calcHKLinsure the motors are stopped and positions current before proceeding. Next,
_head
initializes some variables.
NPTS
is the loop variable in the scans that will run from 0 to
_n1
. T_AV
and
MT_AV
maintain the average temperature
(from the global variable
DEGC
) and the average monitor counts or time per point during the scan.
DATE
and
TIME
are set to the current date and time.
TIME_END
is updated at each point with the current time.
The
_cp
variable is used in powder mode and is set to the center position of
the powder-average motor.
Next in the header macro, the real space motor positions and the reciprocal-space position are made current with
getangles
and
calcHKL
.
The
cleanup
macro is defined to be
the standard macro
_scanabort
.
The macro named
cleanup
is special as spec automatically invokes that macro when a user
types
^C
or on any other error, such as
hitting motor limits, trying
to go to an unreachable position or encountering a
syntax error
in a macro.
The definition of
_scanabort
is,
def _scanabort ' _cleanup2 _cleanup3 comment "Scan aborted after %g points" NPTS sync undef cleanup 'The
_cleanup2
macro is defined
for delta scans
to
move motors back to their starting positions.
The
_cleanup3
macro is available to users for defining some kind of
private clean up actions.
Finally, the headers are written to the file, printer and screen in turn. Included in the headers are the user-defined
Fheader
,
Flabel
,
Pheader
and
Plabel
.
Returning back to
ascan
, the next part of the macro is the loop:
def _scan_on \' for (; NPTS < _n1; NPTS++) { A[_m1] = _s1 + NPTS * _d1 scan_move FPRNT=sprintf("%g %g %g %g",A[_m1],H,K,L) PPRNT=sprintf("%8.4f",A[_m1]) VPRNT=sprintf("%9.4f",A[_m1]) scan_loop pl_put(NPTS, A[_m1], S[DET]) scan_plot } scan_tail \' _scan_onThe loop is implemented as a macro to enable the scan to be continued with the
resume
macro.
The relevant global variables are initialized outside the loop,
so that invoking
_scan_on
continues the scan where it had left off when interrupted.
Here is the
resume
macro.
def resume ' if (NPTS >= (index(HEADING, "mesh")? _n1*_n2 : _n1)) { print "Last scan appears to be finished." exit } def cleanup "_scanabort" comment "Scan continued" _scan_on '
The
scan_move
,
scan_loop
and
scan_plot
macros are invoked by all the scans.
In the loop, the motor array
A[]
is set to the target position for the scanned motor
and the motor is moved using the
scan_move
macro, normally defined as
_move
: def _move 'move_em; waitmove; getangles; calcHKL'String variables are then assigned to values that will be written to the output devices using the
scan_loop
macro.
The
scan_loop
macro is generally defined as
_loop
which has the definition,
# The loop macro, called by all the scans at each iteration def _loop ' scan_count _ctime measuretemp calcHKL z = _ctime < 0? S[sec]/1000:S[MON] T_AV += DEGC; MT_AV += z printf("%3d %s %8.0f %8.0f %10.6g%s\n",\ NPTS,VPRNT,S[DET],S[MON],S[sec]/1000,Pout) onp; offt printf("%3d %11.5g %11.5g %11.5g %8.6g %8.0f %8.0f %s%s\n",\ NPTS,H,K,L,S[sec]/1000,S[MON],S[DET],PPRNT,Pout) offp; ond; offt printf("%s%s %d %g %g\n",FPRNT,Fout,(TIME_END=time())-EPOCH,z,S[DET]) offd; ont 'This macro first counts by calling the
scan_count
macro, normally defined as
_count
, which is, in turn,
defined as
count
. (In powder mode, or when using updated counting during scans,
_count
is defined differently.)
The
_loop
macro then calls
measuretemp
.
With this macro, you can have any
per-point action done, not limited to, nor
necessarily even including, measuring the temperature of the sample.
Next in
_loop
the sums for computing the average
temperature and monitor count rate are adjusted.
Finally the video screen, printer and data file are updated with the
results of the current iteration.
The last thing in
_scan_on
is a call to
scan_tail
,
normally defined as
_tail
: # The tail macro, called by all the scans when they complete def _tail ' undef cleanup TIME_END = time() if (!(_stype&8)) { ond; offt Ftail offd; ont plot } 'This macro removes the definition of
cleanup
, since it is no longer needed,
and if not a
mesh
scan, adds the user defined results to the file and
calls the
plot
macro.