Calls to Procedures and Functions
Consistency with prototype
Functions and procedures must be called from within AIMMS in accordance with the prototype as specified in their declaration. For every call to a function or procedure, AIMMS will verify not only the number of arguments, but also whether the arguments and result are consistent with the specified domains and ranges.
Example procedure call
Consider the procedure ComputeShortestDistance
defined in
Internal Procedures. Further assume that DistanceMatrix
and
ShortestDistanceMatrix
are two-dimensional identifiers defined over
Cities
\({}\times{}\)Cities
. Then the following
assignment illustrates a valid procedure call.
for ( i ) do
ComputeShortestDistance(i, DistanceMatrix, ShortestDistanceMatrix(i,.)) ;
endfor;
As you will see later on, the .
notation used in the third
argument is a shorthand for the corresponding domain set. In this
instance, the corresponding domain set of
ShortestDistanceMatrix(i,.)
is the set Cities
.
Domain checking of arguments
In analyzing the resulting domains of the arguments, AIMMS takes into account the following considerations.
Due to the surrounding
FOR
statement the indexi
is bound, so that the first argument is indeed an element in the setCities
.The second argument
DistanceMatrix
is provided without an explicit domain. AIMMS will interpret this as offering the complete two-dimensional identifierDistanceMatrix
. As expected, the argument is defined overCities
\({}\times{}\)Cities
.Because of the binding of index
i
, the third argumentShortestDistanceMatrix(i,.)
results into the (expected) one-dimensional slice over the setCities
in which the result of the computation will be stored.
Thus, the domains of the actual arguments coincide with the domains of the formal arguments, and AIMMS can correctly compute the result.
Example function call
Now consider the function ShortestDistance
defined in
Internal Procedures. The following statement is equivalent to the
FOR
statement of the previous example.
ShortestDistanceMatrix(i,j) := ShortestDistance(i, DistanceMatrix)(j) ;
In this example index binding takes place through the indexed
assignment. Per city i
AIMMS will call the function
ShortestDistance
once, and assign the one-dimensional result
(indexed by j
) to the one-dimensional slice
ShortestDistanceMatrix(i,j)
.
Call syntax
The general forms of procedure and function calls are identical, except that a function reference can have additional indexing.
procedure-call:
function-call:
Actual arguments
Each actual argument can be
any type of scalar expression for scalar arguments, and
a reference to an identifier slice of the proper dimensions for non-scalar arguments.
Actual arguments can be tagged with their formal argument name used inside the declaration of the function or procedure. The syntax follows.
tagged-argument:
actual-argument:
identifier-slice:
Scalar and set arguments
For scalar and set arguments that are of type Input
you can enter
any scalar or set expression, respectively. Scalar and set arguments
that are of type InOut
or Output
must contain a reference to a
scalar parameter or set, or to a scalar slice of an indexed parameter or
set. The latter is necessary so that AIMMS knows where to store the
output value.
No slices of indexed sets
Note that AIMMS does not allow you to pass slices of an indexed set as a set arguments to functions and procedures. If you want to pass the contents of a slice of an indexed set as an argument to a procedure or function, you should assign the contents to a simple (sub)set instead, and pass that set as an argument.
Multidimensional arguments
For multidimensional actual arguments AIMMS only allows references to identifiers or slices thereof. Such arguments can be indicated in two manners.
If you just enter the name of a multidimensional identifier, AIMMS assumes that you want to pass the fully dimensioned data block associated with the identifier.
If you enter an identifier name plus
a
.
,a set element, or
a set expression
at each position in the index domain of the identifier, AIMMS will pass the corresponding identifier slice or subdomain.
The .
notation
When passing slices or subdomains of a multidimensional identifier
argument, you can use the .
shorthand notation at a particular
position in the index domain. With it you indicate that AIMMS should use
the corresponding domain set of the identifier at hand at that index
position. Recall the argument ShortestDistanceMatrix(i,.)
in the
call to the procedure ComputeShortestDistance
discussed at the
beginning of this section. As the index domain of
ShortestDistanceMatrix
is the set Cities
\(\times\)
Cities
, the .
reference stands for a reference to the set
Cities
.
Slicing
By specifying an explicit set element or an element expression at a
certain index position of an actual argument, you will decrease the
dimension of the resulting slice by one. The call to the procedure
ComputeShortestDistance
discussed earlier in this section
illustrates an example of an actual argument containing a
one-dimensional slice of a two-dimensional parameter.
Dimensions must match
Note that AIMMS requires that the dimensions of the formal and actual arguments match exactly.
Subdomains
By specifying a subset expression at a particular index position of an indexed argument, you indicate to AIMMS that the procedure or function should only consider the argument as defined over this subdomain.
Example
Consider the Cobb-Douglas function discussed in the previous section,
and assume the existence of a parameter a(f)
and a parameter
c(f)
, both defined over a set Factors
. Then the statement
Result := CobbDouglas(a,c) ;
will compute the result by taking the product of exponents over all
factors f
. If SubFactors
is a subset of Factors
, satisfying
the condition on the share parameter a(f)
, then the following call
will compute the result by only taking the product over factors f
in
the subset SubFactors
.
Result := CobbDouglas( a(SubFactors), c(SubFactors) );
Global subdomains
Whenever a formal argument refers to an indexed identifier defined over global sets, it could be that an actual argument in a function or procedure call refers to an identifier defined over a superset of one or more of these global sets. In this case, AIMMS will automatically restrict the domain of the actual argument to the domain of the formal argument. Likewise, if an index set of an actual argument is a real subset of the corresponding global index set of a formal argument, the values of the formal argument, when referred to from within the body of the procedure, will assume the default value of the formal argument in the complement of the index (sub)set of actual argument.
Local subdomains
Whenever a formal argument refers to an indexed identifier defined over local sets, the domain of the actual argument can be further restricted to a subdomain as in the example above. In any case, the (sub)domain of the actual argument determines the contents of the local set(s) used in the formal arguments. Note that consistency in the specified domains of the actual arguments is required when a local set is used in the index domain of several formal arguments.
Domain restrictions on actual argument
When passing an identifier as the actual argument to the procedure or function a
possible domain restriction on that identifier is not used. In other words if the
identifier contains inactive data outside the domain restriction this data will
be passed to the procedure or function as well. To circumvent this problem you can add a
CLEANUP
statement just before the call which will remove the inactive data from the
identifier.
Tagging arguments
In order to improve the understandability of calls to procedures and functions the actual arguments in a reference may be tagged with the formal argument names used in the declaration. In a procedure reference, it is mandatory to tag all optional arguments which do not occur in their natural order.
Permuting tagged arguments
Tagged arguments may be inserted at any position in the argument list, because AIMMS can determine their actual position based on the tag. The non-tagged arguments must keep their relative position, and will be intertwined with the (permuted) tagged arguments to form the complete argument list.
Example
The following permuted call to the procedure ComputeShortestDistance
illustrates the use of tags.
for ( i ) do
ComputeShortestDistance( Distance : ShortestDistanceMatrix(i,.),
DistanceMatrix : DistanceMatrix,
City : i );
endfor;
Using the return value
As indicated in Internal Procedures procedures in AIMMS can return with an integer return value. Its use is limited to two situations.
You can assign the return value of a procedure to a scalar parameter in the calling procedure. However, a procedure call can never be part of a numerical expression.
You can use the return value in a logical condition in, for instance, an
IF
statement to terminate the execution when a procedure returns with an error condition.
You can use a procedure just as a single statement and ignore the return value, or use the return value as described above. In the latter case, AIMMS will first execute the procedure, and subsequently use the return value as indicated.
Example
Assume the existence of a procedure AskForUserInputs(Inputs,Outputs)
which presents a dialog box to the user, passes the results to the
Outputs
argument, and returns with a nonzero value when the user has
pressed the OK button in the dialog box. Then the following IF
statement illustrates a valid use of the return value.
if ( AskForUserInputs( Inputs, Outputs ) )
then
... /* Take appropriate action to process user inputs */
else
... /* Take actions to process invalid user input */
endif ;
The APPLY
Operator
Data-driven procedures
In many real-life applications the exact nature of a specific type of
computation may heavily depend on particular characteristics of its
input data. To accommodate such data-driven computations, AIMMS offers
the APPLY
operator which can be used to dynamically select a
procedure or function of a given prototype to perform a particular
computation. The following two examples give you some feeling of the
possible uses.
Example: processing events
In event-based applications many different types of events may exist, each of which may require an event-type specific sequence of actions to process it. For instance, a ship arrival event should be treated differently from an event representing a pipeline batch, or an event representing a batch feeding a crude distiller unit. Ideally, such event-specific actions should be modeled as a separate procedure for each event type.
Example: product blending
A common action in the oil-processing industry is the blending of crudes and intermediate products. During this process certain material properties are monitored, and their computation for a blend require a property-specific blending rule. For instance, the sulphur content of a mixture may blend linearly in weight, while for density the reciprocal density values blend linear in weight. Ideally, each blending rule should be implemented as a separate procedure or function.
The APPLY
operator
With the APPLY
operator you can dynamically select a procedure or
function to be called. The first argument of the APPLY
operator must
be the name of the procedure or function that you want to call. If the
called procedure or function has arguments itself, these must be added
as the second and further arguments to the APPLY
operator. In case
of an indexed-valued function, you can add indexing to the APPLY
operator as if it were a function call.
Requirements
In order to allow AIMMS to perform the necessary dynamic type checking
for the APPLY
operator, certain requirements must be met:
the first argument of the
APPLY
operator must be a reference to a string parameter or to an element parameter into the setAllIdentifiers
,this element parameter must have a
Default
value, which is the name of an existing procedure or function in your model, andall other values that this string or element parameter assumes must be existing procedures or functions with the same prototype as its
Default
value.
Example: processing events elaborated
Consider a set of Events
with an index e
and an element
parameter named CurrentEvent
. Assume that each event e
has been
assigned an event type from a set EventTypes
, and that an event
handler is defined for each event type. It is further assumed that the
event handler of a particular event type takes the appropriate actions
for that type. The following declarations illustrates this set up.
ElementParameter EventType {
IndexDomain : e;
Range : EventTypes;
}
ElementParameter EventHandler {
IndexDomain : et in EventTypes;
Range : AllIdentifiers;
Default : NoEventHandlerSelected;
InitialData : {
DATA { ShipArrivalEvent : DischargeShip,
PipelineEvent : PumpoverPipelineBatch,
CrudeDistillerEvent : CrudeDistillerBatch }
}
}
The Default
value of the parameter EventHandler(et)
, as well as
all of the values assigned in the InitialData
attribute, must be
valid procedure names in the model, each having the same prototype. In
this example, it is assumed that the procedures
NoEventHandlerSelected
, DischargeShip
,
PumpoverPipelineBatch
, and CrudeDistillerBatch
all have two
arguments, the first being an element of a set Events
, and the
second being the time at which the event has to commence. Then the
following call to the APPLY
statement implements the call to an
event type specific event handler for a particular event
CurrentEvent
at time NewEventTime
.
Apply( EventHandler(EventType(CurrentEvent)), CurrentEvent, NewEventTime );
When no event handler for a particular event type has been provided, the
default procedure NoEventHandlerSelected
is run which can abort with
an appropriate error message.
Use in constraints
When applied to functions, you can also use the APPLY
operator
inside constraints. This allows you, for instance, to provide a generic
constraint where the individual terms depend on the value of set
elements in the domain of the constraint. Note, that such use of the
APPLY
operator will only work in conjunction with external
functions, which allow the use of variable arguments (see
External Functions in Constraints).
Example: product blending
Consider a set of Products
with index p
, and a set of monitored
Properties
with index q
. With each property q
a blend rule
function can be associated such that the resulting values blend linear
in weight. These property-dependent functions can be expressed by the
element parameter BlendRule(q)
given by
ElementParameter BlendRule {
IndexDomain : q;
Range : AllIdentifiers;
Default : BlendLinear;
InitialData : {
DATA { Sulphur : BlendLinear,
Density : BlendReciprocal,
Viscosity : BlendViscosity }
}
}
Thus, the computation of the property values of a product blend can be expressed by the following single constraint, which takes into account the differing blend rules for all properties.
Constraint ComputeBlendProperty {
IndexDomain : q;
Definition : {
Sum[p, ProductAmount(p) * Apply(BlendRule(q), ProductProperty(p,q))] =
Sum[p, ProductAmount(p)] * Apply(BlendRule(q), BlendProperty(q))
}
}
Depending on the precise computation in the blend rules functions for
every property q
, the APPLY
operator may result in linear or
nonlinear terms being added to the constraint.