spec

Software for Diffraction

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; calcHKL
insure 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.