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 index i is bound, so that the first argument is indeed an element in the set Cities.

  • The second argument DistanceMatrix is provided without an explicit domain. AIMMS will interpret this as offering the complete two-dimensional identifier DistanceMatrix. As expected, the argument is defined over Cities\({}\times{}\)Cities.

  • Because of the binding of index i, the third argument ShortestDistanceMatrix(i,.) results into the (expected) one-dimensional slice over the set Cities 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:

image/svg+xmlprocedure ( tagged-argument , )

function-call:

image/svg+xmlfunction ( tagged-argument , ) ( element-expression , )

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:

image/svg+xmlarg-tag : actual-argument

actual-argument:

identifier-slice:

image/svg+xmlidentifier-part ( set-expression . , )

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 set AllIdentifiers,

  • this element parameter must have a Default value, which is the name of an existing procedure or function in your model, and

  • all 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.