Index

A Tutorial Introduction to the 40m Scheduling Language

The 40m scheduling language is an asynchronous language, meaning that by default it doesn't wait for one scheduling command to complete before starting the next one. This is possible because the commands are actually executed in parallel by separate slave processes in the real time control system. This means that multiple commands execute in parallel, such that, if say a track command is issued for a source that is below the horizon, or the focus mechanism fails to move, the schedule doesn't block waiting for the source to rise, or for the focus mechanism to move. In general, if the same command is sent twice in succession, but with different arguments, the first command is aborted immediately and the new one starts. For example the following commands result in the telescope tracking Saturn, without spending any time on 3c273, because the second track command aborts the first.
  track 3c273
  track Saturn
This probably wasn't what the user intended, and clearly there are many instances like this where one wants to wait for one or more commands to complete before continuing. This is where the until command comes into play. This command prevents subsequent commands in the schedule from being executed until a specified set of conditions becomes true. For example, to ask the control system to track a source, and wait up to 10 minutes for it to acquire it, one could type:
 track 3c273
 until $acquired(source) | $elapsed > 0:10:0
This example uses two functions $acquired(source), and elapsed(), the first of which returns true once the source is acquired by the telescope, and the second of which returns the amount of time that the until statement has been waiting. Note that to invoke a function, it must be preceded by a $ character. The same is true of evaluating variables. This allows the compiler to figure out when you are typing in a variable or function name, as opposed to a literal value, like the name of a source. Also note the use of the boolean OR operator, denoted by |, and the greater-than operator, denoted by >. Thus what the above until command says is "wait until either the source has been acquired, or 10 minutes have passed.

The following is another example, showing a scheduling script which initiates two time-consuming operations, then uses the until command to wait for both of them to complete.

 # Tell the control system to move the focus mechanism to a focus
 # position of 26.4 millimeters.

 set_focus 26.4

 # Tell the control system to start slewing to 3c273.

 track 3c273

 # Wait for the target focus position and the source to be acquired.

 until $acquired(focus) & $acquired(source)

 # Stay on source for another 15 minutes.

 until $elapsed >= 0:15:0

Continuing the above example, imagine that the focus mechanism fails to reach the specified position, or that the source couldn't be acquired. In such cases the
 until $acquired(focus) & $acquired(source)
statement might wait indefinitely. To remedy this, the following example shows a modified version of the statement which times out after 5 minutes have elapsed.
 until $acquired(focus) & $acquired(source) | $elapsed >= 0:5:0
As mentioned above, the | symbol stands for boolean OR, whereas & denotes boolean AND. So the above statement reads; "wait until the desired focus position has been reached, and the source has been acquired, or the elapsed time exceeds 5 minutes." Boolean expressions are evaluated left to right, so each & and | operator operates on the result of the expression to its left and the single boolean value to its right. This associativity rule can be overriden with the use of parentheses. Using parentheses, the above example is equivalent to writing:
 until ($acquired(focus) & $acquired(source)) | $elapsed >= 0:5:0
Having added a timeout to the statement, one might also want to detect that something went wrong before continuing the script. In the following addition to the script, the script is aborted if the source wasn't acquired, but it is allowed to continue if the requested focus position wasn't reached (sometimes the focus mechanism fails to acquire to whithin the optimal precision, but the position that it reaches is usually okay).
 if(!$acquired(source)) {
   print "Source not acquired - aborting."
   abort
 } else if(!$acquired(focus)) {
   print "The focus mechanism failed to acquire - continuing."
 } else {
   print "Now tracking 3c273."
 }
Notice how statements within the if command are grouped between {} braces. Also notice the use of the ! operator to negate the value returned by the $acquired() function. This is the boolean NOT operator. With this in mind the first if clause reads as, "if not acquired source, then...". Admittedly this doesn't read very well in English, but it is understandable.

Having got to this point, you may be thinking that you don't want to have to type so much, every time that you observe a new source. This is where user-defined commands come to the rescue. The following example shows how the above procedure could be encapsulated in a user-defined command.

command observe(Source src, Double focus_posn, Double timeout, Double dt)
{
  # Tell the control system to start moving the focus mechanism to
  # a focus position of $focus_posn millimeters. 

  set_focus $focus_posn

  # Tell the control system to start slewing to the source named in
  # the src variable.

  track $src

  # Wait for the target focus position and the source to be acquired, but
  # don't wait longer than the time contained in the timeout
  # variable (hours).

  until ($acquired(focus) & $acquired(source)) | $elapsed >= $timeout

  # Report our status.

  if(!$acquired(source)) {
    print "Failed to acquire ", $src, " - aborting."
    abort
  }
  if(!$acquired(focus)) {
    print "The focus mechanism failed to acquire - continuing."
  }
  print "Now tracking ", $src, "."

  # Stay on source for the time contained in the dt variable (hours),
  # or the source sets.

  until $elapsed >= $dt | $signaled(source_set)
}
The first line gives the command a name and then lists the types and names of each of its arguments. The arguments are thereafter accessible to the statements within the command as local variables. As previously mentioned, the value of a variable is accessed by prefixing the variable name with a $ character.

Having defined this command, one can then use it in a schedule to sequentially observe a couple of different sources by typing:

 observe 3c273, 26.4, 0:5:0, 0:15:0
 observe 3c286, 25.0, 0:5:0, 0:15:0
This is much more convenient, but there are a few ways to improve the situation. First, you probably don't want to have to manually define the same command in every script that you write. Instead, you can collect commonly used command definitions in separate files and then use the import command to read them into subsequent scripts. For example, if I were to place the above definition of the observe command into a file called ~mcs/scripts/observing.sch, then I could include it in subsequent scripts by typing:
 import ~mcs/scripts/observing.sch
OK so far, but what if one has a lot of sources to observe? It would be tedious to have to type in the observe command for each source, particularly if you wanted to add your own commands before and after the canned observe command. A better alternative is to form a list, and use the foreach command to apply the observe command to each member of the list. But a list of what? The observe command takes more than one argument, so we need to form a list in which each member of the list is a group of values. This is where user-defined group data-types become useful. The following command defines a new datatype called Observation, which contains a list of the arguments needed by the observe command.
 group Observation {
   Source src,        # The source to be observed.
   Double focus_posn, # The desired focus position.
   Double timeout,    # The acquisition timeout (hours).
   Double dt          # The desired integration time (hours).
 }
Having done this, we could create and initialize a variable that contains the above parameters by typing:
 Observation obs = {3c273, 26.4, 0:5:0, 0:15:0}
This sets the src member of the variable to 3c273, the focus_posn member of the variable to 26.4, the timeout member to 5 minutes and the dt member to 15 minutes. To access the src member of the obs variable, you would type $obs.src. Thus we could print out the contents of this variable by typing:
 print $obs.src, " ", $obs.focus_posn, " ", $obs.timeout, " ", $obs.dt

To form a list of observation variables, and then assign the list to a variable called ol one could then type:

 listof Observation ol = {
   {3c273,  26.4, 0:5:0,  0:15:0},
   {3c286,  25.0, 0:5:0,  0:10:0},
   {arp220, 30.0, 0:10:0, 0:4:0}
 }
Having formed such a source list, one could then run the observe command like:
 foreach(Observation obs) $ol {
   print "Moving to source: ", $obs.src
   observe $obs.src, $obs.focus_posn, $obs.timeout, $obs.dt
   print "Finished observing source: ", $obs.src
 }
To make this even more convenient, one could rewrite the observe command to take a list of observations as its sole argument, and then include the foreach loop inside the command. If the definition of an observation datatype and the observe command were then placed in a file called ~mcs/scripts/better.sch, then subsequent observing scripts could be reduced to:
 import ~mcs/scripts/better.sch

 listof Observation ol = {
   {3c273, 26.4, 0:5:0, 0:15:0},
   {3c286, 25,   0:5:0, 0:10:0},
   {arp220, 30,  0:10:0, 0:4:0}
 }

 observe $ol 
In fact we could simplify this even further by writing the list in place, rather than first assigning it to a variable. This would look like:
 import ~mcs/scripts/better.sch

 observe {
   {3c273, 26.4, 0:5:0, 0:15:0},
   {3c286, 25,   0:5:0, 0:10:0},
   {arp220, 30,  0:10:0, 0:4:0}
 }
This brings us to the question of how the compiler figures out when one statement ends and the next begins. To add some fuel, take a look at the following example:
 print "This is actually",
       " one print command",
       " that stretches across a few lines."
Unlike most languages, the 40m scheduling language has neither line termination characters nor continuation characters. Instead the compiler figures out whether a line is continued from context. In the above example, each line ended with a comma. On seeing the comma, the compiler expects another argument. So when it can't find one on the current line, it looks on the next line. Similarly, in the following example, the compiler sees the | operator at the end of the first line and assumes that its second operand must lie on the following line.
  until $elapsed >= 0:20:0 | $elevation(current) < 40.0 |
        $after(23:34, LST)
Unclosed parentheses have the same effect, so the compiler accepts the following statement without complaint.
until (
        $elapsed >= 0:20:0 | $signaled(quit)
      )
The only down side of this is that an accidentally unclosed parenthesis could result in a puzzling error message from the compiler.

Also note that in the above examples, although $elapsed is a function, the parentheses that normally follow such a function call, have been omitted. This is allowed, but not required, for functions that don't expect any arguments.

Catching exceptional conditions

While it is possible to use the until command to detect problems, such as something taking too long, or a source setting, this can get messy if there are many conditions to detect and many places to detect them. For example, the following real-life example looks really messy.
 command calibrate(Time end_time) {
   track Jupiter
   until $elapsed > 0:10:0 | $after($end_time, lst) | $signaled(source_set)
   if($after($end_time, lst) | $signaled(source_set)) {
     return
   }
   fire_noise_cal 2
   track Saturn
   until $elapsed > 0:10:0 | $after($end_time, lst) | $signaled(source_set)
   if($after($end_time, lst) | $signaled(source_set)) {
     return
   }
   fire_noise_cal 2
 }
Not only is it messy, but imagine what happens if "fire_noise_cal" is user-defined command that happens to use an until statement itself. The fact is that it doesn't know anything about the end_time argument of the calibrate() command, so it could end up waiting past the programmed end time. This wouldn't be too bad if you could modify the fire_noise_cal command to take the end time as an additional argument, but if it is imported from a separate file, which is used by many other scheduling scripts, this wouldn't be feasible.

This is where the catch command is indispensable. Using the catch command, the above mess can be rewritten as:

  catch $after($end_time, lst) | $signaled(source_set) {
    track Jupiter
    until $elapsed > 0:10:0
    fire_noise_cal 2
    track Saturn
    until $elapsed > 0:10:0
    fire_noise_cal 2
  } {
    noise_cal off
    offset x=0, y=0
    return
  }
The catch command takes a boolean expression which it re-evaluates repeatedly while executing a specified block of commands. If the expression becomes true, the block of commands is aborted. Effectively the specified condition is invisibly added to all of the until statements that execute within the block of commands, including ones buried inside any user commands that get called from the enclosed block of commands. In more detail, the expression is evaluated before the first statement of the catch statement, while any until commands are executing, and whenever a signal is received. In the above example an optional second block of commands is provided. This block is executed when and if the expression becomes true, and allows you to respond to the problem. In this case, it has been used to make sure that the system is left in a known state, with the knowledge that the aborted fire_noise_cal command might have left the noise cal diode switched on and/or might have left a tracking offset instated. If no second block of commands is given then, execution simply resumes with the first statement that follows the catch statement.

Sending and receiving signals

Interactive control of a running schedule from the telviewer command line can be accomplished by sending the schedule signals. This is done using the signal command, and the schedule can test if a signal has been sent by using the signaled() function. Apart from predefined hardware generated signals, such as the source_set signal, all recognized signal names are normally defined in the telcontrol initialization script, which currently defines two signals, "quit" and "done". The former is normally used to signal that a script should start a new task, such as moving to the next star in a pointing schedule, and the second for telling a schedule to finish what it is doing. The quit signal is actually somewhat redundant, given that the abort command does this in a way that doesn't require a schedule to watch for signals. A schedule that wanted to wait for a done signal, might include a statement like the following:
  until $signaled(done)
Alternatively, a schedule that wanted to abort a block of commands on receiving a done signal could use a catch statement like:
 catch $signaled(done) {
   ...commands...
 }

Martin Shepherd (28-Jun-2010)