Library of Functions and Procedures
Creating libraries to share/reuse functionality in multiple projects is a powerful feature of AIMMS. In this article, we present some best practices and tips to create such libraries of functions and procedures.
The following are discussed in detail:
Library organization - interfacing with a library is eased by a proper organization into sections.
Functions - functions are popular because they can be used in expressions.
Procedures - procedures are popular because they can assign new data to global identifiers.
This AIMMS project
is used as an example for the below.
Library organization
A good way of organizing your library is as follows:
1LibraryModule myLibrary {
2 Prefix: myLib;
3 Interface: Public_Section;
4...
5}
There are three remarks on this organization:
To make all declarations in the section
Public_Section
accessible by other libraries and the main model, the interface attribute of the library only contains a reference toPublic_Section
.To make it visually clear which parts of the library require coordination before they can be changed, and which parts can be freely changed, the contents are separated in a
Public_Section
and aPrivate_Section
.The library initialization and termination procedures are executed during application initialization and termination. Many library implementers do not expect such procedures to be called explicitly from the main model, or from other libraries. To enforce this expectation, it is good practice to move the library initialization and termination procedures to a separate section inside the
Private_Section
.
Functions in the library
Functions provide a neat way of abstracting logic. Consider the following example:
1Function fnc_DoSomeCalculation {
2 Arguments: (p_Arg1D);
3 Body: {
4 ! This function takes any one-dimensional numerical vector and returns a scalar value.
5 p_retval := sum(ii, p_Arg1D(ii)) ;
6 fnc_DoSomeCalculation := p_retval ;
7 }
8 Parameter p_Arg1D {
9 IndexDomain: ii;
10 Property: Input;
11 }
12 Set s_ImplicitSet {
13 Index: ii;
14 }
15 Parameter p_retval;
16}
The mechanism to realize “any one-dimensional numerical vector” is illustrated in the following call:
p_calcResult1 := myLib::fnc_DoSomeCalculation(p_modelParam1) ;
When this function starts, the local set s_ImplicitSet
is instantiated with s_modelSet1
based on the following information:
The formal argument
p_Arg1D
is instantiated with the actual argumentp_modelParam1
.This formal argument
p_Arg1D
has local indexii
and the actual argumentp_modelParam1
has indexi
.The range of local index
ii
is the local sets_ImplicitSet
and the range of indexi
iss_modelSet1
; thereby the sets_ImplicitSet
is instantiated withs_modelSet1
. Note thats_ImplicitSet
needs to be local; sets declared outside functions or procedures cannot be instantiated this way.
Similarly, the below call instantiates the local set s_ImplicitSet
with s_modelSet2
.
p_calcResult2 := myLib::fnc_DoSomeCalculation(p_modelParam2) ;
The data flow between formal and actual arguments is summarized below:
Side effects are not allowed for functions.
Avoid side effects
A side effect is when during the computation of identifier A
, identifier B
is modified as well.
The AIMMS language limits side effects. Consider the following example:
A(i,j) := fnc1( b(i,j) ) + C(i,j);
When the evaluation of fnc1
in the above expression, modifies C
, there is a side effect.
The reason to avoid such side effects is that it becomes hard to understand what the outcome should be, because it is not specified in the AIMMS execution engine which identifier is to be evaluated first: C(i,j)
or fnc1( b(i,j) )
.
A nice consequence of this design choice is that the sparse execution system can make more strict assumptions on the behavior of the data structures it reads and thus execute faster.
Statements allowed in function bodies
AIMMS Functions are designed to be used in expressions, including indexed expressions, and in definitions of parameters. To avoid side effects, the following restrictions are placed on the body of a function:
Identifiers declared outside the function cannot be assigned to.
For example, the body of
fnc_DoSomeCalculation
from the previous example cannot be declared as below becausep_calcResult
is not declared locally to the function.1Parameter p_calcResult; 2Function fnc_DoSomeCalculation { 3 Arguments: (p_Arg1D); 4 Body: { 5 ! This function takes any one-dimensional numerical vector and returns a scalar value. 6 p_calcResult := sum(ii, p_Arg1D(ii)) ; ! Not allowed: assigning to global. 7 fnc_DoSomeCalculation := p_calcResult ; 8 } 9 Parameter p_Arg1D { 10 IndexDomain: ii; 11 Property: Input; 12 } 13 Set s_ImplicitSet { 14 Index: ii; 15 } 16 Parameter p_retval; 17}
Solve statements are not allowed.
Procedure calls are not allowed, but calls to other functions are allowed.
Note
AIMMS Functions cannot be used in the expressions of constraints definitions and variable definitions.
Procedures in the library
Relative to functions, there are much fewer restrictions placed on the statements that can be executed in a procedure. This allows you to model much more complicated data flow using procedures.
To illustrate, the above example will be extended to copy data to a set and parameter in the private section of the library.
Consider the following identifiers private to the library interface we are developing:
Set s_libSet {
Index: k;
Parameter: ep_libSet;
}
Parameter p_libParam {
IndexDomain: k;
}
Parameter p_libResult;
These identifiers are used by a procedure private to the library interface:
Procedure pr_WorkSomeCalculation {
Body: {
display p_libParam ;
! In this procedure we can use the private sets and parameters of library 'myLibrary'.
p_libResult := sum( k, p_libParam(k));
}
}
To facilitate this mechanism, the procedure that can be used outside the library as follows:
1Procedure pr_DoSomeCalculation {
2 Arguments: (inpArgument1d,outArgument0d);
3 Body: {
4 block ! Copy input data to the private sets and parameters of this library.
5 For ii do
6 SetElementAdd(s_libSet, ep_new, ii);
7 ep_map(ep_new) := ii;
8 EndFor;
9 p_libParam(k) := inpArgument1d( ep_map(k));
10 endblock ;
11
12 ! Let the workhorse procedures inside the private section of the library do the actual work.
13 pr_WorkSomeCalculation();
14
15 block ! Copy the results in the private sets and parameters to the output arguments of this procedure.
16 outArgument0d := p_libResult ;
17 endblock ;
18
19 block ! Cleanup
20 empty private_section ;
21 endblock ;
22 }
23 Parameter inpArgument1d {
24 IndexDomain: ii;
25 Property: Input;
26 }
27 Parameter outArgument0d {
28 Property: Output;
29 }
30 Set s_ImplicitSet {
31 Index: ii;
32 }
33 ElementParameter ep_map {
34 IndexDomain: k;
35 Range: s_ImplicitSet;
36 }
37 ElementParameter ep_new {
38 Range: s_libSet;
39 }
40}
The instantiation of the arguments is done in a similar way as with functions and not discussed here. More interesting is the copying of the arguments to the sets and parameters private to the library as illustrated in lines 5-9 above:
Line 5: For every element in the implicit argument set
s_ImplicitSet
Line 6: Explicitly add the element to set
s_libSet
.Line 7: We need to map the data associated with the element in
s_ImplicitSet
to the corresponding element ins_libSet
.Line 9: Actually map the data of the parameter argument to the parameter in the private section of the library.
The data flow is now summarized in the following picture:
Blue arrows: The argument passing mechanism of AIMMS takes care.
Green arrows: To be implemented inside the procedure body.
The above mechanism is used in Data for optimization libraries. That article also illustrates the use of indexed output arguments.
Procedures in expressions
The use of procedures inside expressions is limited to scalar evaluation. Typical examples are:
1p_RetCode := pr_someProc();
2
3if pr_otherProc() then
4 ...
5endif ;
Both line 1 and line 3-5, are use cases of old-style error handling.
Line 1 is the allowed exception for side effects. It is allowed if the assignment doesn’t bind any indices.
A better way of error handling is introduced here.
The use of procedures in expressions is not needed, as status information can be passed in output arguments.
A good practice is to avoid the use of procedures in expressions; this permits the reader of a body of a procedure or function to easily distinguish between procedure calls and function calls; procedure calls are not part of an expression and avoid side effects altogether.