SPARK Article by Dimitri Curtil - Last updated November 12, 2003.  
 

Porting atomic classes from SPARK 1.x to SPARK 2.x?



What changed in the atomic class syntax in SPARK 2.x?

The syntax used to define the callback functions has changed in SPARK 2.x in order to support new features such as:

  • the extended callback mechanism,
  • the multivalued inverses, and
  • the inverses in residual form.
Note that by SPARK 2.x we refer to all versions starting from SPARK 2.0. By SPARK 1.x we refer to all versions prior to SPARK 2.x, i.e., SPARK 1.0, SPARK 1.01 and SPARK 1.02.

The SPARK language used to specify the class declaration, the port interface and the link declarations is backward-compatible from SPARK 1.x to SPARK 2.x. This means that you do not need to change your problem files or your macro classes when using them with SPARK 2.x. However, you need to port your atomic classes in order to be able to use them with SPARK 2.x. In particular, you need to modify how the callback functions comprising each inverse are implemented. The portion of the atomic class within the #ifdef SPARK_PARSER and #endif directives will remain unchanged except for the PRED language keyword, which has become PREDICT.

This article explains the steps you need to carry out so that you can compile your atomic classes defined in SPARK 1.x in SPARK 2.x. In order to make definining the callback functions easier for the user, C preprocessor macros that hide the implementation details of argument passing as well as the function prototype have been defined in the header file "spark.h". These macros must be used to define the callback functions in place of the explicit function prototypes in SPARK 1.x to ensure that the type of each callback function is correctly conveyed during the build phase.


How to port atomic classes from SPARK 1.x to SPARK 2.x?

How to modify the "sum.cc" atomic class?

To demonstrate how to use the new macros let's port the simple "sum.cc" atomic class from SPARK 1.x to SPARK 2.x. The next code snippet shows this atomic class as found in the globalclass directory of the SPARK 1.02 distribution.

Implementation in SPARK 1.x

#ifdef SPARK_PARSER

PORT a	"Summand 1" ;
PORT b	"Summand 2" ;
PORT c	"Sum" ;

EQUATIONS {
	c = a + b ;
}

FUNCTIONS {
	a = sum_subtract( c, b ) ;
	b = sum_subtract( c, a ) ;
	c = sum_add( a, b ) ;
}

#endif /* SPARK_PARSER */
#include "spark.h"

double sum_subtract ( ARGS )
{
    ARGDEF(0, c);
    ARGDEF(1, b);
    double a;

    a = c - b;
    return a ;
}

double sum_add ( ARGS )
{
    ARGDEF(0, a);
    ARGDEF(1, b);
    double c;

    c = a + b;
    return c;
}

The ARGS macro used in the function prototype stands for ArgList args.

Implementation in SPARK 2.x

In order to port this atomic class to the syntax required by SPARK 2.x you need to:

  • use the EVALUATE preprocessor macro to declare the callback function in place of the explicit function prototype, and
  • use the RETURN preprocessor macro to return the new value for the target variables.
We use a bold, blue font to indicate the modifications in the implementation of the "sum.cc" atomic class required for SPARK 2.x. The modified "sum.cc" file is:

#ifdef SPARK_PARSER

PORT a	"Summand 1" ;
PORT b	"Summand 2" ;
PORT c	"Sum" ;

EQUATIONS {
	c = a + b ;
}

FUNCTIONS {
	a = sum_subtract( c, b ) ;
	b = sum_subtract( c, a ) ;
	c = sum_add( a, b ) ;
}

#endif /* SPARK_PARSER */
#include "spark.h"

EVALUATE( sum_subtract )
{
    ARGDEF(0, c);
    ARGDEF(1, b);
    double a;

    a = c - b;
    RETURN( a );
}

EVALUATE( sum_add )
{
    ARGDEF(0, a);
    ARGDEF(1, b);
    double c;

    c = a + b;
    RETURN( c );
}

How to access the arguments within the body of the callback function?

The ARGDEF preprocessor macro

In the previous code snippet, the macro ARGDEF - already defined in SPARK 1.x - is used to define an argument through its 0-based position in the argument list as specified in the FUNCTIONS block. The position of the first element in the list is thus referred to with the index 0, the position of the second element with the index 1, and so forth.

The ARGUMENT preprocessor macro

A synonym macro named ARGUMENT and which uses the same syntax was added in SPARK 2.x. For example, the following code snippet defines the argument in position 0 in the argument list of the callback sum_subtract as a const reference to a SPARK::TArgument object named c. It is exactly equivalent to using the ARGDEF macro as in the previous code snippet.

EVALUATE( sum_subtract )
{
    ARGUMENT(0, c);
    ARGUMENT(1, b);
    double a;

    a = c - b;
    RETURN( a );
}

Discontinued alternatives

Another way of accessing the argument variables from within the body of the callback function is to explicitly declare const references to the SPARK::TArgument objects stored in the argument list. This is exactly what the ARGDEF and ARGUMENT macros do. For example, the sum_add function could have been written in SPARK 1.x:

double sum_subtract( ArgsList args )
{
    const SPARK::TArgument& a = args[0];
    const SPARK::TArgument& b = args[1];
    double c;

    c = a + b;
    return  c ;
}

We recommend that you modify such callback functions to use the macros ARGDEF or ARGUMENT as this approach will prove more portable in the long run.

Finally, some people used #define directives to name the arguments of a callback function. Although this approach was syntactically correct, we strongly recommend that you do not use it since it is very error prone. Indeed, if you forget the #undef directives or if the C preprocessor does not enforce them correctly, it will likely have undesirable side effects in the header files that are included in the atomic class through the #include "spark.h" directive.

For example, the sum_add function could have been written in SPARK 1.x:

#define  a  args[0]
#define  b  args[1]
double sum_subtract( ArgsList args )
{
    double c;

    c = a + b;
    return  c ;
}
#undef  a
#undef  b

If one of your atomic classes was implemented using this technique, then it is best that you modify it so that it uses the ARGDEF or the ARGUMENT macros as previously explained.

How to return the calculated result for the target variable within the body of the callback function?

The RETURN preprocessor macro

As shown in the code snippet for the atomic class "sum.cc", the RETURN macro must be used in SPARK 2.x to return the result value for the target variable. This replaces the technique in SPARK 1.x that consisted in using the C++ language keyword return followed by the double value for the newly calculated result. The RETURN preprocessor macro mimics the C++ language keyword return used in SPARK 1.x.

  • If you keep using the return keyword followed by a double value then your atomic class will not compile because the new callback prototypes are defined as void functions.
  • If you do not use the RETURN macro to return the new value of the target variable, then the new value will not be propagated to the target variable, which will remain constant throughout the simulation. This is surely an undesirable behavior for an EVALUATE callback function that is supposed to modify its target variable.

The TARGET preprocessor macro

To complement the ARGUMENT macro newly introduced in SPARK 2.0 to give read access to an argument variable, we have also added the TARGET macro to grant write access to a target variable. The target variables are the variables listed on the left-hand side of the '=' sign of each inverse specified in the FUNCTIONS block. For example, in the "sum.cc" atomic class the target variable for the inverse sum_add is the variable connected to the port c.

The preprocessor macro TARGET uses a similar syntax to the one of the ARGUMENT macro. For each target variable you need to specify:

  • the 0-based position of the target variable in the target list (i.e., starting at zero for the first variable), and
  • the name of the reference to the SPARK::TTarget object representing the target variable.
Assigning the calculated results to each target variable ensures the proper data flow across the set of the unknown variables in the problem. Instead of using the RETURN macro you can declare the target variable in the body of the callback function with the TARGET macro and assign the new result value to it. The next code snippet shows how to do this for the sum_subtract callback.

EVALUATE( sum_subtract )
{
    ARGUMENT(0, c);
    ARGUMENT(1, b);
    TARGET(0, a);

    double result = c - b;
    a = result;
}

Also, note that the SPARK::TTarget class does not grant you read access to the current value of the target variable as this would break the topological dependency implied in the FUNCTIONS block and used during the graph-theoretic processing in setupcpp. However, it is possible to access all other properties of a target variable, such as its name, unit, past values, absolute tolerance, etc.

How to port the PRED callback in SPARK 1.x?

Finally, if the atomic class you want to port to SPARK 2.x defines a PRED function in the FUNCTIONS block along with the main EVALUATE callback function, then you need to:

  • replace the PRED language keyword in the FUNCTIONS block with the new PREDICT language keyword,
  • use the PREDICT preprocessor macro to declare the callback function in place of the explicit function prototype, and
  • use the RETURN preprocessor macro to "return" the new value for the target variables.

We demonstrate these steps on the "flow.cc" atomic class, which defines a PRED callback in SPARK 1.x to compute a linear estimate for some mass flow rate / pressure relation. The predicted value returned from the PRED callback is only used if the target variable is selected as a break variable. The "flow.cc" atomic class does not have any physical meaning. It is merely used to explain how to port the PRED callback.

Implementation in SPARK 1.x

#ifdef SPARK_PARSER

PORT mdot;
PORT k;
PORT dp;

EQUATIONS { // Some kind of pressure-flow relation
  mdot = k * sign(dp) * sqrt( abs*(dp) );
}

FUNCTIONS {
  mdot = flow_evaluate( k, dp ),
    PRED = flow_predict( k, dp );
}

#endif /* SPARK_PARSER */
#include "spark.h"

double flow_evaluate ( ARGS )
{
    ARGDEF(0, k);
    ARGDEF(1, dp);
    double result = k * SPARK::sign(dp) * sqrt( SPARK::abs*(dp) );
    return result ;
}

double flow_predict ( ARGS )
{
    ARGDEF(0, k);
    ARGDEF(1, dp);
    double result = k * dp;
    return result ;
}

Implementation in SPARK 2.x

We use a bold, blue font to indicate the modifications in the implementation of the "flow.cc" atomic class required for SPARK 2.x. The modified "flow.cc" file is:

#ifdef SPARK_PARSER

PORT mdot;
PORT k;
PORT dp;

EQUATIONS { // Some kind of pressure-flow relation
  mdot = k * sign(dp) * sqrt( abs*(dp) );
}

FUNCTIONS {
  mdot = flow_evaluate( k, dp ),
    PREDICT = flow_predict( k, dp );
}

#endif /* SPARK_PARSER */
#include "spark.h"

EVALUATE( flow_evaluate )
{
    ARGDEF(0, k);
    ARGDEF(1, dp);
    double result = k * SPARK::sign(dp) * sqrt( SPARK::abs*(dp) );
    RETURN( result );
}

PREDICT( flow_predict )
{
    ARGDEF(0, k);
    ARGDEF(1, dp);
    double result = k * dp;
    RETURN( result );
}

How to use a sed script to modify the callback implementation automatically?

Using the sed program it is possible to write the rules to apply in order to port a well-formed atomic class from SPARK 1.x to SPARK 2.x. The sed program can be found in the unixutils/ directory in the SPARK installation. The following sed script shows these replacement rules:

  • double some_func( ARGS ) with EVALUATE( some_func ),
  • double some_arg = args[ some_idx ] with ARGDEF( some_idx, some_arg ),
  • const double some_arg = args[ some_idx ] with ARGDEF( some_idx, some_arg ),
  • SPARK::TArgument& some_arg = args[ some_idx ] with ARGDEF( some_idx, some_arg ),
  • const SPARK::TArgument& some_arg = args[ some_idx ] with ARGDEF( some_idx, some_arg ),
  • return some_val with RETURN( some_val ), and
  • the PRED language keyword with the new PREDICT language keyword.

# Rule to replace function prototype declaration outside of { } block
#  - Assumes it is an EVALUATE callback.
#  - PREDICT callback must be replaced by hand!
/ARGS/s/double[ \tab]*\(.*\)[ \tab]*(.*)/EVALUATE( \1 )/
/ArgList/s/double[ \tab]*\(.*\)[ \tab]*(.*)/EVALUATE( \1 )/

# Rule to replace:
#	  double name = args[idx] 
#	  const double name = args[idx] 
#	  SPARK::TArgument& name = args[idx] 
#	  const SPARK::TArgument& name = args[idx] 
#	  TArgument& name = args[idx] 
#	  const TArgument& name = args[idx] 
# with 
#	  ARGDEF(idx, name)
s/double[ \tab]*\(.*\)[ \tab]*=[ \tab]*args[ \tab]*\[\(.*\)\]/ARGDEF( \2, \1)/
s/const[ \tab]*double[ \tab]*\(.*\)[ \tab]*=[ \tab]*args \tab]*\[\(.*\)\]/ARGDEF( \2, \1)/
s/const[ \tab]*SPARK::TArgument&[ \tab]*\(.*\)[ \tab]*=[ \tab]*args[ \tab]*\[\(.*\)\]/ARGDEF( \2, \1)/
s/SPARK::TArgument&[ \tab]*\(.*\)[ \tab]*=[ \tab]*args[ \tab]*\[\(.*\)\]/ARGDEF( \2, \1)/
s/const[ \tab]*TArgument&[ \tab]*\(.*\)[ \tab]*=[ \tab]*args[ \tab]*\[\(.*\)\]/ARGDEF( \2, \1)/
s/TArgument&[ \tab]*\(.*\)[ \tab]*=[ \tab]*args[ \tab]*\[\(.*\)\]/ARGDEF( \2, \1)/

# Rule to replace 
#	  return xxx ; 
# with 
#	  RETURN( xxx ); 
# within { } block
# Works only for return statement on a single line!
/return/,/;/{
	N
	s/return[ \tab]*\(.*\);/RETURN( \1 );/
	P
	D
}

# Rule to replace the PRED language keyword within the FUNCTIONS { } block with the PREDICT keyword:
s/PRED/PREDICT/g

The character "\tab" is a CRTL-I or tab character. You would have to explicitly type in the tab in the sed script. This sed script should be able to perform most of the replacement tasks required to port an atomic class to SPARK 2.x. However, there are certain limitations:

  • Statements can only be modified if they are on a single line. E.g., the following return statement
  •   return (
    	  c + b
      );
      
    will not be replaced by the sed script as it spans over multiple lines. You will have to modify it by hand.
  • Arguments defined using the #define directive have to changed by hand as explained in the section above.
  • The PREDICT callback function must be modified by hand as it cannot be distinguished from the EVALUATE callback function because they both have the same function signature.
To use this script at the command-line in order to port some atomic class "MyClass.cc" into a new atomic class named "MyNewClass.cc", type in a SPARK console window:

 
% sed -f $SPARK_DIR/utils/sed/port_atomic_class.sed MyClass.cc > MyNewClass.cc  <enter>

Alternatively, you can use the shell script named "port_atomic_class.sh" to port the "MyClass.cc" file into a file with the same name, whereby the original file will be copied into a file named "MyClass.cc.old":

 
% $SPARK_DIR/utils/sed/port_atomic_class MyClass.cc <enter>

Always check the file resulting from applying the sed script as some constructs might not have been replaced correctly.

In the previous command lines, $SPARK_DIR expands into the SPARK_DIR environment variable that contains the path to the SPARK installation. This is the UNIX syntax for accessing an environment variable. On the Windows platform, the same environment variable will be accessed using the %SPARK_DIR% syntax. Instead of using the SPARK_DIR environment variable, you can also use relative path specification to reach the files in the utils/ directory of your SPARK installation.