Perl Functions

Function Signature

Script functions using Perl are always called with one input array. If the first argument in that array is not undefined, then it is a String containing the name of the calling mode. The calling mode is set to register when the script function is supposed to return an array of requested inputs and outputs; it is usually set to undefined to indicate to the script that it is to compute outputs based on any inputs given in the array, though the calling mode may be set to compute. As an example consider file Perl_CalcObject_Eval.pl, containing the following:

sub Perl_CalcObject_Eval
{
        # the inputs to the script arise as a reference to an array
        # the STKUtil::getInputArray function is used to get at the array itself
        my @stateData = STKUtil::getInputArray(@_);
 
        my @retVal;
        if ( !defined($stateData[0]) )
        {
                # do compute
                @retVal = Perl_CalcObject_Eval_compute(@stateData);
        }
        elsif ( $stateData[0] eq 'register' )
        {
                @retVal = Perl_CalcObject_Eval_register();
                        }
        elsif ( $stateData[0] eq 'compute' )
        {
                @retVal = Perl_CalcObject_Eval_compute(@stateData);
                        }
        else
        {
                # error: do nothing
        }
        # MUST return a reference to an array, as shown below
        return \@retVal;
}

To check for compilation errors, we have adopted the Perl convention that all Perl scripts should end with "1;"

Note that the registration and compute aspects of the Perl_CalcObject_Eval function are handled by other Perl functions:

Perl_CalcObject_Eval_register and
Perl_CalcObject_Eval_compute.

You can define these functions in the same file—this programming style leads to script readability.

Although Perl does understand the concept of namespaces, many Perl programmers are unaware of them, and so we have not assumed such knowledge in developing the call signature above. As an alternative to using namespaces, functions and global variables are named in a manner to prevent name collision in different Perl files. For example, if two separate Perl files that define exactly the same function name are used sometime during the STK session, then the first version of the function is overridden by the second version. Most probably, that type of behavior is not the desired one. To avoid this problem, we highly recommend that all functions and any global variables use the filename as a prefix: that is why the register function for Perl_CalcObject_Eval (above) is not named simply "register" (which could be overridden by some other file that defines a register function) but instead Perl_CalcObject_Eval_register (which is much less likely to be overridden in a different file that obeys this convention).

When the script performs its compute function, the values contained in the input array @stateData are in the same order as that in which the Inputs were registered, with the first Input at index 1, the second at index 2, etc., and with the calling mode occupying index 0 of the array.

Perl handles a function argument that is to be "one input argument that itself is an array" by passing a reference to an array. Thus, STK will call the Perl function by passing to it a reference to an array, and expect from the script a reference to an array. Since references may not be familiar to all users, we have created a helper function, STKUtil::getInputArray, which dereferences the incoming reference into the array itself: see how @stateData is created. Once the array is known, Perl is used in a more normal manner wherein arrays themselves are passed as arguments and outputs. On output back to STK, however, one must return a reference to the array (\@retVal) and not the array itself (@retVal).

Registration of Input and Output Arguments

Each script function requests inputs and outputs by returning a descriptor for each of the requested arguments. The descriptor contains a list of strings of the form "keyword = value". STK parses the keyword-value pairs to identify the requested argument. The number of keyword-value pairs that is required to describe an argument depends upon the complexity of the argument: simple inputs and outputs require fewer pairs than more complicated ones. If a requested argument cannot be identified, then that script function is disabled.

The keyword string is case insensitive—we capitalize certain letters for legibility in examples and documentation. The value string is case sensitive however, with some exceptions (e.g., ArgumentType values of "Input" and "Output" are actually case insensitive).

Every requested input or output argument descriptor must contain the keywords ArgumentType and ArgumentName. The value of ArgumentType is either Input or Output. The value of ArgumentName can be any user-specified variable name that obeys the language's syntax for a valid variable name. (Thus, special characters and spaces are not allowed.) The ArgumentName is intended to be a unique name for that argument when referenced in the script.

The order of the keyword-value pairs within an argument descriptor is arbitrary, although we adopt a convention where ArgumentType is first, so that you can identify which arguments are inputs and outputs more readily.

During the registration process, an array of descriptors must be generated and returned. Each descriptor can be either an array of Strings, where each String is a keyword-value pair, or a String containing keyword-value pairs separated by semicolons. An example follows:

sub Perl_ForceModel_Eval_register
{
     my @argStr;
     push @argStr, "ArgumentType = Output; Name = Status ; ArgumentName =
           Status";
     my @descripArray;
     push @descripArray, "ArgumentType = Output";
     push @descripArray, "Name = Acceleration";
     push @descripArray, "RefName = LVLH";
     push @descripArray, "ArgumentName = accel";
     push @argStr, \@descripArray;
     push @argStr, "ArgumentType = Input; Name = Velocity; RefName = Inertial; 
          ArgumentName = Vel";
     push @argStr, "ArgumentType = Input; Name = DateUTC; ArgumentName = Date";
     return @argStr; 
}

Computing Outputs from Inputs

The order of registration determines the order in the incoming and outgoing arrays of the requested data. For example, based on the registration given above, there are 2 requested inputs. When called upon to compute, the input array will then consist of 3 elements:

Index 0: The calling mode, usually undefined.
Index 1: The velocity (an array of 3 doubles), referred to by the name Vel in the script.
Index 2: The UTC date (a String), referred to as Date in the script.

Accordingly, the output array consists of 2 elements:

Index 0: Status (a String).
Index 1: The acceleration (an array of 3 doubles) relative to the LVLH frame, referred to as accel in the script.

An error results if:

If the script function returns a String instead of an array, an error results which then will cause the script to be turned off. Additionally, the returned string will be written to the Message Viewer window. Thus, the script has the ability to turn the script off and indicate a reason for doing so, simply by returning a string.

Accessing Data on the Basis of ArgumentName

Because the order of registration implies the order of the data in the input array and the output array, it is imperative that the correct association between the argument and its index number be used. Thus, adding new arguments in the middle of the current list or even just re-ordering the list of descriptors in the register function will require modifications of the index numbers used in the compute function. To alleviate this burden somewhat, you can choose to use auxiliary classes that hold the association between argument and index number. This allows you to access data by name, which should result in fewer modifications being necessary in the compute function when the registration function is altered.

After the registration process occurs, STK makes an additional call to Perl to create two Classes (one for inputs, one for outputs) that can be used to access the data by ArgumentName (rather than using index numbers). These classes can be created and stored during the first call to compute, and then accessed by compute thereafter. The classes are accessed from the $g_PluginArrayInterfaceHash hash. For example, consider the function Perl_ForceModel_Eval. The compute function would look, like this:

sub Perl_ForceModel_Eval_compute
{
  # the inputs here are in the order of the requested Inputs, as registered
  my @stateData = @_;
  # $stateData[0] is the calling mode
  # This function ASSUMES that Perl_ForceModel_Eval_register will set
  # Perl_ForceModel_Eval_init to -1
  #
  if($Perl_ForceModel_Eval_init < 0)
  {
          $Perl_ForceModel_Eval_init = 1; 
          # The following hashes have been created automatically after this script 
          # has registered its inputs and outputs.
          #
          # Each hash contains information about the arguments for this script. 
          # The hashes have been created as a user convenience, for those users
          # wanting to know, during the running of the script, what the inputs and 
          # outputs are. In many cases, the script writer doesn't care, in which case 
          # this entire if-block is unneeded and can be removed.
          $Perl_ForceModel_Eval_Inputs =
                 $g_PluginArrayInterfaceHash{'Perl_ForceModel_Eval'}{'Inputs'};
          $Perl_ForceModel_Eval_Outputs = 
               $g_PluginArrayInterfaceHash{'Perl_ForceModel_Eval'}{'Outputs'};
          # comment out the line below if you don't want to see the inputs and outputs 
          # each time the script is run
          Perl_ForceModel_Eval_showArgs();
  }
  # continue with rest of script
  # compute the acceleration: here it is a "reverse" drag,
  # being proportional to the inertial speed
        my @velArray = @{$stateData[$Perl_ForceModel_Eval_Inputs->getArgument('Vel')]};
  my $factor = 0.000001;
  my $cbiSpeed = sqrt($velArray[0]*$velArray[0]
                    +$velArray[1]*$velArray[1]+$velArray[2]*$velArray[2]);
  # accel with be the acceleration in the CbiLVLH frame
  my @accel;
  push @accel, 0.0;                    # x-component: radial
  push @accel, $factor*$cbiSpeed;       # y-component: inTrack 
  push @accel, 0.0;                    # z-component: crossTrack 
  # this defines the return array
  my @retArray = ();
  $retArray[$Perl_ForceModel_Eval_Outputs->getArgument('accel')] = \@accel;
  $retArray[$Perl_ForceModel_Eval_Outputs->getArgument('Status')] = "Okay";
  STKUtil::formatOutputArray(\@returnArray);
  return @returnArray;
}
sub Perl_ForceModel_Eval_showArgs
{
  my @argStrArray;
  STKUtil::printOut "Doing Perl_ForceModel_Eval_compute_init\n";
  @argStrArray = ();
  push @argStrArray, $Perl_ForceModel_Eval_Inputs->
                              {'FunctionName'}->{'Name'} . " Inputs \n";
        # the first arg on input is the calling mode
  push @argStrArray, "0 : this is the calling mode\n";
  my @args = $Perl_ForceModel_Eval_Inputs->getArgumentArray();
  # to see description args
        my $index, $descrip;
        foreach $arg (@args)
  {
           ($index, $descrip) = $Perl_ForceModel_Eval_Inputs->getArgument($arg);
          push @argStrArray, "$index : $arg = $descrip\n";
  }
  STKUtil::printOut @argStrArray;
        @argStrArray = ();
  push @argStrArray, $Perl_ForceModel_Eval_Outputs->
                         {'FunctionName'}->{'Name'} . " Outputs \n";
  my @args = $Perl_ForceModel_Eval_Outputs->getArgumentArray();
  # to see description args
  my $index, $descrip;
  foreach $arg (@args)
  {
           ($index, $descrip) = $Perl_ForceModel_Eval_Outputs->getArgument($arg);
          push @argStrArray, "$index : $arg = $descrip\n";
  }
  STKUtil::printOut @argStrArray;
}

The variables $Perl_ForceModel_Eval_init, $Perl_ForceModel_Eval_Inputs and $Perl_ForceModel_Eval_Outputs are global variables and must be declared outside of any function. During Perl_ForceModel_Eval_register, Perl_ForceModel_Eval_init should be set to -1. The first time Perl_ForceModel_Eval_compute is called, the if-block is executed, causing two classes to be created (Perl_ForceModel_Eval_Inputs and Perl_ForceModel_Eval_Outputs). Because they have been declared global, they are remembered for the next call to compute.

Notice that the inputs are accessed from stateData using the class Perl_ForceModel_Eval_Inputs and the ArgumentName that was registered. Similarly, the outputs are assigned into the returned array using the class Perl_ForceModel_Eval_Outputs and the ArgumentName that was registered. The classes provide a mechanism in the script to access the inputs and outputs by name, rather by index number. Of course, they do this by mapping the ArgumentName to an index value:

Name Index Value
$Perl_ForceModel_Eval_Inputs->getArgument{'Vel') 1
$Perl_ForceModel_Eval_Inputs->getArgument{'Date') 2
$Perl_ForceModel_Eval_Outputs->getArgument{'Status'} 0
$Perl_ForceModel_Eval_Outputs->getArgument{'accel'} 1

(Remember, on input the argument at index 0 is the calling mode.)

The use of hashes to translate ArgumentName to index value as described above does affect performance: the code may execute up to twice as fast when the hashes are not used and the index values are used directly (this contrasts with VBScript where no performance penalty arises).

A matrix is passed in Perl as a single array, containing the rows of the matrix in order, not in the form of an array of references to arrays or hash.

STK 11.2.1 Programming Interface