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) exit
The
_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_on
The 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.
