External Functions in Constraints
Variable arguments
AIMMS allows you to use external functions in the constraints of a
mathematical program. To accommodate this, AIMMS makes a distinction
between function arguments of type Parameter
and arguments of type
Variable
. When a function is executed as part of an expression in an
ordinary assignment, AIMMS makes no distinction between both types of
arguments. In the context of a mathematical program, however, AIMMS will
provide the solver with the derivative information for all variable
arguments of the function, while it will not do so for parameter
arguments. The actual computation of the derivatives is explained in the
next section.
Derivative Computation
Functions in constraints
Whenever you use external functions with variable arguments in constraints of a mathematical program, the following rules apply.
AIMMS requires that the mathematical program dependent on these constraints be declared as nonlinear.
All the actual variable arguments must correspond to formal arguments which have been locally declared as
Variables
.
If you fail to comply with these rules, a compiler error will result.
Providing derivatives
During the solution process of a mathematical program containing such functions, partial derivative information of the function with respect to all the variable arguments must be passed to the solver. AIMMS supports three methods to compute the derivatives of a function:
you provide the actual statements for computing the derivatives as a part of the function declaration,
AIMMS estimates the derivatives using a simple differencing scheme.
The DerivativeCall
attribute
In the DerivativeCall
attribute of an external function you can
specify the call to the DLL procedure or function, to which the
derivative computation must be relayed. The syntax of the
DerivativeCall
attribute is the same as that of the BodyCall
,
and is most conveniently completed using the wizard in the Model
Explorer.
Function value and derivative
If the nonlinear solver only needs a function value, AIMMS will simply
call the function specified in the BodyCall
attribute. If the
nonlinear solver requests derivative information as well, AIMMS will
only call the function specified in the DerivativeCall
attribute,
and require that this function compute the function value as well. By
combining these two computations in a single call, AIMMS allows you to
take advantage of any possible optimization that can be obtained in your
code from computing the function value and derivative at the same time.
The .Derivative suffix
For every function argument which is a variable, you must assign the partial derivative value(s) to the .Derivative suffix of that variable. Note that this will have an impact on the number of indices. If the result of a block-valued function is \(m\)-dimensional, the derivative information with respect to an \(n\)-dimensional variable argument will result in an \((m+n)\)-dimensional identifier holding the derivative.
Abstract example
Consider a function f
with an index domain \((i_1,\dots,i_m)\)
and a variable argument x
with index domain \((j_1,\dots,j_n)\).
Then the matrix with partial derivatives of f
with respect to the
argument x
must be provided as assignments to the suffix
\({\texttt{x.Derivative}}(i_1,\dots,i_m,j_1,\dots,j_n)\). Each
element of this identifier represents the partial derivative
Cobb-Douglas function revisited
Consider the Cobb-Douglas function discussed above. Although AIMMS is capable of computing its partial derivatives automatically, you may verify that the derivative with respect to argument \(c_i\) can also be written more compactly as follows:
Implementation in C
Consider the following C
function Cobb_Douglas_Der
which
computes the Cobb-Douglas function and, if required, also the partial
derivatives with respect to the input argument c
. The function
Cobb_Douglas_No_Der
is added to support computation of the
Cobb-Douglas function without derivatives.
double Cobb_Douglas_Der( int n, double *a, double *c, double *c_der ) {
int i;
double CD = 1.0 ;
for ( i = 0; i < n; i++ )
CD = CD * pow(c[i],a[i]) ;
/* Check if derivatives are needed */
if ( c_der )
for ( i = 0; i < n; i++ )
c_der[i] = CD * a[i] / c[i] ;
return CD;
}
double Cobb_Douglas_No_Der( int n, double *a, double *c ) {
return Cobb_Douglas_Der( n, a, c, NULL );
}
Always skip unwanted derivatives
Note that in the above example the derivative computation is skipped
whenever the pointer c_der
is null. You should always check for
this condition when implementing a derivative computation, because AIMMS
will pass a null pointer (and hence reserve no memory for storing the
derivative) whenever the corresponding actual argument is not a variable
but a parameter.
…in FORTRAN
code
When an internal function makes a call to a FORTRAN
procedure to
compute derivative values, then it is not so easy to discover the
presence of null pointer argument. To overcome this, you can call your
FORTRAN
procedure from within a wrapper function written in C
,
and provide your FORTRAN
code with the information whether or not
derivatives need to be computed for a particular variable argument via
an additional argument to your FORTRAN
routine.
Passing derivative arguments
To pass the partial derivatives computed in the external procedure back
to AIMMS, the argument list of the external procedure called in the
Derivative
attribute of the internal function should contain
arguments for the .Derivative suffices of all variable arguments.
AIMMS will implicitly consider such derivative arguments as Output
arguments. They can be passed either as a full array or as an integer
handle
. In the latter case AIMMS API functions have to be used to
pass back the relevant partial derivatives (see also The AIMMS Programming Interface).
Example continued
The following external function declaration provides an interface to the above Cobb-Douglas function with derivative computations, which is ready to be used both inside and outside the context of constraints.
ExternalFunction CobbDouglasPlusDerivative {
Arguments : (a,c);
Range : nonnegative;
DLLName : "Userfunc.dll";
ReturnValue : double;
BodyCall : Cobb_Douglas_No_Der( card : InputFactors, array: a, array: c );
DerivativeCall : {
Cobb_Douglas_Der( card : InputFactors, array: a,
array: c, array: c.Derivative );
}
}
Numerical differencing
When the DerivativeCall
attribute to compute the derivatives of an
external function has not been specified, AIMMS employs a simple
differencing scheme to estimate the derivatives. For example, if AIMMS
requires the derivative of a function \(f(x_1, x_2,\ldots, x_k)\) at
the point \((\bar x_1, \bar x_2, \ldots, \bar x_k)\), then AIMMS
will approximate each partial derivative as follows:
where \(\varepsilon\) is the current value of the global option
Differencing_Delta
.
Disadvantages of numerical differencing
While the numerical differencing scheme does not require any action from the user, there are two distinct disadvantages.
First of all, numerical differencing is not always a stable process, and the results may not be accurate enough. As a result, a nonlinear solver may have trouble converging to a solution.
Secondly, the process can be computationally very expensive.
In general, it is recommended that you do not rely on numerical differencing. This is especially the case when the function body is quite extensive, or when the function, at the individual level, has a lot of variable arguments or contains conditional loops.