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 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 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 Implementation in SPARK 2.xIn order to port this atomic class to the syntax required by SPARK 2.x you need to:
#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 macroIn 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 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 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 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
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 The preprocessor macro TARGET uses a similar syntax to the one of the ARGUMENT macro. For each target variable you need to specify:
EVALUATE( sum_subtract ) { ARGUMENT(0, c); ARGUMENT(1, b); TARGET(0, a); double result = c - b; a = result; }
Also, note that the 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:
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.xWe 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:
# 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:
return ( c + b );   % 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, |
|
|