User-defined subroutines
Topics covered in this chapter:
- Example
- Structures for passing information
- Implementing the new subroutine
- Contexts and the Mosel stack
- Module vs. package
It is possible to define subroutines within a Mosel (.mos) program. However, in certain cases it may be preferrable to implement subroutines in the form of a module:
- An implementation of this function in C exists already.
- The subroutine manipulates data structures that are not supported by Mosel or accesses low-level (system) functions that are not available in Mosel.
- The subroutine is time-critical and must be executed as fast as possible.
Example
Some users of Mosel are annoyed by the fact that after solving an optimization problem they have to retrieve the solution value for every variable separately using function getsol. We therefore show in this example how to write a module solarray providing a procedure that copies the solution values of an array of variables into an array of reals. The arrays may be static or dynamic and of any number of dimensions (but of course, the solution array must correspond to the array of variables). Our aim is to be able to write a model along the following lines (assuming that the new procedure is also called solarray):
model "test solarray module" uses "solarray", "mmxprs" declarations R1=1..2 R2={6,7,9} x: array(R1,R2) of mpvar sol: array(R1,R2) of real end-declarations ... solarray(x,sol) writeln(sol) end-model
Structures for passing information
Our module needs to do the following:
- retrieve any necessary information from Mosel
- initialize itself
- define the new subroutine
- pass the new subroutine on to Mosel
To start with, we shall look at the structures that are required for exchanging information.
List of subroutines
The library function that implements the new subroutine will be called ar_getsol. This function and a standardized description of the subroutine it implements must be put into a list of subroutines that is passed to Mosel:
static XPRMdsofct tabfct[]= { {"solarray", 1000, XPRM_TYP_NOT, 2, "A.vA.r", ar_getsol} };
The entries of the subroutine description are the following:
- the name of the new subroutine (in a Mosel program),
- its order number within the module (not less than 1000),
- the type of the return value (here: none, we implement a procedure),
- the number and type(s) of the parameters (here: A.v: an array of variables and A.r: an array of reals), and
- the name of the C function that implements it.
A complete description of the possible values for the entries of this list is given in Section List of subroutines.
Interface structure
The list of subroutines in turn needs to be put into the interface structure. Since no constants, services or types are defined by this module all other entries of this structure remain empty:
static XPRMdsointer dsointer= { 0, NULL, sizeof(tabfct)/sizeof(XPRMdsofct), tabfct, 0, NULL, 0, NULL };
Initialization function
The module initialization function is almost the same as in the previous example, except for its name which must correspond to the name of the module:
DSO_INIT solarray_init(XPRMnifct nifct, int *interver,int *libver, XPRMdsointer **interf) { mm=nifct; /* Get the list of Mosel NI functions */ *interver=XPRM_NIVERS; /* Mosel NI version */ *libver=XPRM_MKVER(0,0,1); /* Module version */ *interf=&dsointer; /* Pass info about module contents to Mosel */ return 0; }
Note that in this example — as opposed to the previous one — we are going to use functions of the Native Interface and therefore need to obtain the list of these functions from Mosel (mm is of type XPRMnifct).
Implementing the new subroutine
We now implement the new subroutine, which has to perform the following steps:
- Get the variable and solution arrays from the stack.
- Check whether the arrays are correct: verify the types, compare the array sizes and the indexing sets.
- Get the solution for all variables and copy it into the solution array.
The prototype of any library function that implements a subroutine or operator (that is, anything that is passed to Mosel via the list of subroutines structure XPRMdsofct) is fixed by Mosel:
int functionname(XPRMcontext ctx, void *libctx);
The first argument is the context of Mosel, the second the context of the module (see Section Mosel and module contexts for further detail). This module does not define its own context, we therefore do not use this parameter. The return value of the function indicates whether it was executed successfully.
The prescribed prototype of the library function does not allow any parameters to be passed directly; instead, these must be obtained from the stack of Mosel (see Section Working with the Mosel stack for details). In the present case, the stack is accessed via the macro XPRM_POP_REF, meaning that a reference (here: array pointer) is taken from the stack. The parameter values always must be taken in the same order as they appear in the subroutine in the Mosel program.
When the library function implements a function, its return value must be put onto the stack. Since in our example we want to implement a procedure, there is no return value.
Here is the code of the module. For clarity's sake we omit the error handling in function ar_getsol. The same example complete with error handling, is provided with the module examples of the Mosel distribution.
#include <stdlib.h> #include "xprm_ni.h" #define MAXDIM 20 static int ar_getsol(XPRMcontext ctx,void *libctx); /* List of subroutines */ static XPRMdsofct tabfct[]= { {"solarray", 1000, XPRM_TYP_NOT, 2, "A.vA.r", ar_getsol} }; /* Interface structure */ static XPRMdsointer dsointer= { 0, NULL, sizeof(tabfct)/sizeof(XPRMdsofct), tabfct, 0, NULL, 0, NULL }; /* Structure for getting function list from Mosel */ static XPRMnifct mm; /* Module initialization function */ DSO_INIT solarray_init(XPRMnifct nifct, int *interver,int *libver, XPRMdsointer **interf) { mm=nifct; /* Get the list of Mosel functions */ *interver=XPRM_NIVERS; /* Mosel NI version */ *libver=XPRM_MKVER(0,0,1); /* Module version: must be <= Mosel NI version */ *interf=&dsointer; /* Pass info about module contents to Mosel */ return 0; } static int ar_getsol(XPRMcontext ctx,void *libctx) { XPRMarray varr, solarr; XPRMmpvar var; int indices[MAXDIM]; /* Get variable and solution arrays from stack in the order that they are used as parameters for `getsol' */ varr=XPRM_POP_REF(ctx); solarr=XPRM_POP_REF(ctx); /* Error handling: - compare the number of array dimensions and the index sets - make sure the arrays do not exceed the maximum number of dimensions MAXDIM */ /* Get the solution values for all variables and copy them into the solution array */ if(!mm->getfirstarrtruentry(varr,indices)) do { mm->getarrval(varr,indices,&var); mm->setarrvalreal(ctx,solarr,indices,mm->getvsol(ctx,var)); } while(!mm->getnextarrtruentry(varr,indices)); return XPRM_RT_OK; }
Contexts and the Mosel stack
The implementation of a new subroutine (function ar_getsol in the previous section) introduces several notions that may require further explanation: the Mosel and module contexts and the Mosel stack.
Mosel and module contexts
Any library function that implements a subroutine (or operator, as shown later in this document) takes as arguments the Mosel and the module contexts. The Mosel context communicates the current state of the Mosel program in question. This is necessary because several models may be executed simultaneously. Consequently, most functions of the Native Interface take the Mosel context as their first argument.
A module may also have a context of its own. The context of a module may be any structure that saves information about the current state of the module. Defining a module context becomes necessary when any information needs to be preserved between different calls to functions of the module during the execution of a model. In the examples discussed so far in this document (definition of constants and subroutines) this is not the case, so we do not use this parameter. Typical uses for a module context are to save the current values of control parameters published by the module or to keep track of memory allocated by the module during the execution of a model so that it may be freed at its termination. In the following chapters we give examples of these uses.
Working with the Mosel stack
In the case of a C library function that defines a subroutine for the Mosel language, we need to obtain the values of its parameters that have been specified in the model. The prototype for such library functions as fixed by Mosel does not allow any parameters to be passed directly; instead, the parameter values, and also the return value (if the implemented subroutine is a function), are communicated via the stack of Mosel.
The stack is accessed via the stack access macros XPRM_POP_type where type is one of
- INT
- an integer or Boolean (C type int),
- REAL
- a real value (C type double),
- STRING
- a string (C type const char*),
- REF
- any reference.
The parameter values need to be taken in the same order as they appear in the subroutine in the Mosel program. For example, if we want to implement a procedure do_something with the following prototype
procedure do_something(val1:real, num:integer, arr:array(range) of mpvar, val2:real)
we need to take the parameters in the following order from the stack (ctx is the Mosel context):
XPRMarray arr; int i; double r1,r2; r1=XPRM_POP_REAL(ctx); i=XPRM_POP_INT(ctx); arr=XPRM_POP_REF(ctx); r2=XPRM_POP_REAL(ctx);
In the example above where we implement a procedure, there is no return value. In the case of a function, the returned value must be put onto the stack using another type of stack access macro: XPRM_PUSH_type where type is one of the 4 types listed above. To implement a function with the prototype
function return_two:integer
that simply returns the integer value 2, we write the following:
static int my_return_two(XPRMcontext ctx,void *libctx) { XPRM_PUSH_INT(ctx, 2); return XPRM_RT_OK; }
Module vs. package
An implementation of the solarray procedure by a package is given in Chapter 'Packages' of the Mosel User Guide. An advantage of this package version clearly is a less technical implementation that focusses on the required functionality without any programming overhead such as the various data structures used for communication or the module initialization function. However, whilst at the C level we simply check that the two arguments have the same index sets without having to include any more precise information about the nature of these indices, within the Mosel language the type and number of the array index sets must be known. As a consequence we have to provide a separate implementation for every case that we wish to use (one-, two-, three-,...,n-dimensional arrays indexed by integers, strings,...), restricting the functionality defined by the package to those versions that are explicitly defined.
© 2001-2024 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.