Raising and Handling Warnings and Errors
Errors and warnings
During the development and deployment of an AIMMS application, unexpected, possibly harmful, situations can arise. These situations are divided into errors and warnings. An error is a situation that cannot be handled by the procedure encountering it. A warning is a situation that can be handled by the procedure encountering it, but might warrant further inspection by the model developer or by the model user. Note that, even when a procedure cannot handle an error itself, it should be able to recover from that error. In this section, you will find AIMMS facilities to
handle errors; to handle an error, AIMMS will give you access to the information therein. A handler is a piece of AIMMS code that handles selected errors and warnings. Errors and warnings can be communicated to handlers higher in the execution stack.
raise an error; not only AIMMS may detect situations warranting an error or warning message, but also the application itself. For such situations AIMMS provides a facility to raise custom errors from within your model.
handle a legacy situation; external and intrinsic AIMMS procedures may return a status code indicating success or failure. Whenever a failure status of an external and intrinsic procedure remains unnoticed, AIMMS can automatically raise an error in such situations.
extensively check the code; AIMMS can check your application for many different kinds of situations that occassionally warrant a warning. It is usually worthwhile to apply all these checks to your application.
Tip
You may refer to our online training about error handling following this link.
Handling Errors
Subsection overview
In this subsection you will find an introduction to both the global and
local error handling mechanisms available in AIMMS. Global error
handling, by means of specifying a single handler procedure, is used to
treat runtime errors occuring anywhere inside the entire model that are
not handled elsewhere. Local error handling, by means of the OnError
clause in a BLOCK
statement, allows error handling of runtime errors
occuring in a specific block of code. Global and local error handling
are the blocks on which the error handling framework in AIMMS is built.
At the end of this subsection, you will find a description of all the
intrinsic functions available for accessing and manipulating information
regarding errors.
Global error handling
To activate global error handling, the name of a handling procedure in
your model must be assigned to the option Global_error_handler
. Such
a procedure must have a single element parameter argument err
in the
predeclared set errh::PendingErrors
. The global error handling
procedure will be executed for each pending error whenever an execution
run has been terminated because of errors that have not been handled
elsewhere in the model. The global error handler will also be called at
the end of a finished execution run if there are unhandled warnings. In
this context, an execution run is any call to an AIMMS procedure
initiated either through the AIMMS GUI or through the AIMMS API.
Example
Below a global error handling procedure MyErrorHandler
is
illustrated. The lines in the body of the procedure are numbered to
facilitate the explanation of the example.
Procedure MyErrorHandler {
Arguments : err;
ElementParameter err {
Range : errh::PendingErrors;
Property : Input;
}
Body: {
1 if errh::Node(err) = 'DefP' then
2 DialogMessage(errh::Message(err) + "; resetting P to its default.");
3 Empty P ;
4 errh::MarkAsHandled(err);
5 elseif errh::InsideCategory(err,'IO') then
6 errh::Adapt(err,message:"IO error: please consult ...; "
7 + errh::Message(err) ); ! Pass adapted message on to next handler.
8 else
9 ! Errors not handled will be passed on to the error/warning window.
10 endif
}
}
Example explanation
The procedure starts with declaring the argument err
as an element
parameter with the predeclared set errh::PendingErrors
, with a
subset of the predeclared set Integers
as its range. During an
execution run, this set is filled with the numbers of the errors and
warnings raised. Each number refers to an error or warning with various
pieces of information therein, such as its error description, the node
in which the error or warning occurred and its severity. In addition,
each error belongs to a category. All this information can be accessed
using intrinsic functions. The body of the procedure is now explained
line by line:
- line 1
The intrinsic function
errh::Node
is used to determine whether or not the error occurred inside the procedureDefP
. This intrinsic function returns the identifier or node in which the error occured as an element of the predeclared setAllSymbols
.- lines 2, 3
If the error did happen inside the procedure
DefP
, the application user is notified andP
is reset to its default. The notification uses the original error description obtained using the intrinsic functionerrh::Message(err)
.- line 4
Each handled error will be marked as such. When an error handler finishes, it will delete the errors that have been marked as handled from the predeclared set
errh::PendingErrors
.- line 5
To discern the type of an error, errors are divided into categories. For each error, the category to which it belongs can be obtained using the function
errh::Category(err)
. The error categories form a nested structure. For instance, bothIO
andGeneration
errors areExecution
errors. The intrinsic functionerrh::InsideCategory(err)
can be used to determine whether or not an error is within a particular category.- lines 6, 7
Translate the error by adapting information. In this example, only the message is actually adapted, but most parts of an error can be adapted. Note that in this
else
branch, the functionerrh::MarkAsHandled
is not called, the result being that the adapted error message will appear in the messages/errors window.- line 8
In this branch, the error is not handled. An error that has not been handled when the error handler finishes will not be deleted. Instead, it is being displayed in the messages/errors window.
Local error handling by means of the OnError
clause
The following template of a BLOCK
statement illustrates local error
handling by means of the OnError
clause.
1 BLOCK
2 statement_1 ;
3 ...
4 statement_n ;
5 ONERROR err DO
6 ...
7 ...
8 ENDBLOCK ;
All errors occuring inside statement_1
… statement_n
on lines
2 … 4 are handled by the error handler on lines 6 and 7, where err
is an element parameter of the set errh::PendingErrors
.
errh::PendingErrors
is a set, because a single
statement, especially a solve statement, may raise multiple errors.
The warnings/errors in this set are handled one at a time.
The error(s) are actually handled in a while loop, whereby err
refers
to each error one at a time.
Note
On lines 6,7, the predefined parameter Loopcount
can be used
and takes as value the number of the warning/error at hand. If you
want to refer to a loopcount in an outer loop, you will have to use
loop-count strings, see Use of loop strings.
Block statements can be nested, either directly in a single body, or in other procedures called from within block statements. This gives rise to a stack of error handlers as illustrated below. A detailed example of a local error handler is given in Runtime Libraries and the Model Edit Functions.
Error flow architecture
The global error handlers and the OnError
error handlers are
essential building blocks of the error handling framework of AIMMS. This
error handling framework is illustrated in Fig. 1.
Construction of the error handler stack
At the start of each execution run, a new stack of error handlers is
created. At the bottom of this stack is the standard handler
To Global Collector
. When the option Global_error_handler
is
set, the specified procedure is placed on top of this new stack.
Additional handlers are placed on the stack by each OnError
clause
in a nested BLOCK
statement.
Errors flowing through a handler stack
When raised, each error is set aside for handling by the topmost error
handler. When the number of errors set aside reaches the limit specified
by the option Errors_until_execution_interrupt
, the execution is
interrupted and resumes by executing the code in the topmost error
handler. When the execution is not interrupted, but there are pending
errors or warnings, the error handling code is executed after the
completion of the last statement prior to the BLOCK
statement.
Multiple errors may require handling
A single statement may result in multiple error messages, for instance a
solve statement or a data assignment statement with several duplicate
entries. Thus, even if the option Errors_until_execution_interrupt
is 1 (its default), multiple errors may need to be handled. If multiple
errors caused by a single statement are handled inside the OnError
clause of a BLOCK
statement, the code within the OnError
clause
will be executed unconditionally for every single error, unless you
explicitly break away from theOnError
clause.
Break away from handling
If you use a RETURN
, HALT
, BREAK
or RAISE ERROR
statement inside the OnError
clause, the handling of any subsequent
errors or warnings will be stopped. You are actually indicating that
these further errors and warnings are no longer of interest and thus
they will be automatically set as handled. A plain BREAK
statement
just breaks the error handling loop. If the Block
statement is
inside an outer loop statement like FOR
or WHILE
and you want to
break from that loop, you need to use a loop string (see
Advanced Use of WHILE and REPEAT).
SKIP in OnError
A plain Skip
statement in the OnError
clause simply skips the
remaining statements and continues with the next error that needs to be
handled. You can use a SKIP
with a loop string to skip the
statements of an outer loop statement. This will break away from the
OnError
clause as described above.
What to do with an error
For each error, the error handling code will decide whether to handle that error itself, let another handler handle the error, or ignore the error (as was already illustrated in the example above).
Handling an error inside a handler
Errors may also occur during the execution of the OnError
clause or
of a BLOCK
statement or the global error handling procedure. These
errors are handled by the next error handler in the stack of error
handlers.
Error collector
When an error reaches the handler To Global Collector
, it is sent to
the Error and Warning Collector object which collects all errors
that have fallen through the various handlers (if any). Errors in the
Error and Warning Collector can be queried from within the AIMMS API
or viewed from within the messages/errors window of the AIMMS GUI.
The predeclared module ErrorHandling
Errors to be handled can be queried using the following predeclared
identifiers and intrinsic functions from the module ErrorHandling
with prefix errh
:
errh::PendingErrors
A predeclared set filled with the numbers of the errors that can be handled at this point.
errh::IndexPendingErrors
An index of the above predeclared set.
- error parts
An error is made up of several parts; each of which can be obtained separately using the intrinsic functions below. Each of the functions below will raise an error of their own if
err
is not a valid error that can be handled at that point.errh::Severity(err)
An element in
errh::AllErrorSeverities
is returned indicating the severity of the error.errh::Message(err)
A string containing the error description is returned. This string is not empty.
errh::Category(err)
An element in
errh::AllErrorCategories
is returned indicating the category of the error.errh::Code(err)
The element in
errh::ErrorCodes
that is returned by this function identifies the message code of the error. This element name may be cryptic; as it is primarily used for identification of the error within the AIMMS system.errh::NumberOfLocations(err)
The number of locations relevant to this error. For compilation errors, there is typically only one relevant location. For an AIMMS initialization error there are no relevant locations. For an execution error the positions in all the active procedures are recorded. For an error during file read, at least the positions in the data file and the read statement are recorded. Similarly, for an error during the generation of a constraint, at least the constraint and the
SOLVE
statement are recorded as relevant positions.errh::Node(err,loc)
An element in
AllSymbols
is returned for an error location inside the model. The optional argumentloc
defaults to 1 and should be in the range{ 1 .. NumberOfLocations }
. The element returned by this function is non-empty except for the first location when reading data from a file.errh::Attribute(err,loc)
An element in
AllAttributeNames
.errh::Line(err,loc)
An integer indicating the line number of the error in the attribute or file, or 0 if not known.
errh::Column(err)
An integer indicating the column position in an erroneous line being read from a data file. All errors when reading a data file are reported separately, such that the
loc
argument is not applicable.errh::Filename(err)
A non-empty string is returned when reading from a data file. All errors when reading a data file are reported separately, and so the
loc
argument is not applicable.errh::Multiplicity(err)
An integer indicating the number of occurrences of this error. Two errors are considered equal if they are equal in all of the following parts:
Severity
,Message
,Category
,Code
and the first location (if available). The first location is the location in the file being read when the error occurs during a read statement, otherwise it is the statement being executed.errh::CreationTime(err,fmt)
A string representing the creation time of the first occurrence of the error, formatted according to time format
fmt
.
errh::InsideCategory(err,cat)
Returns 1 if the error code of
err
falls inside the categorycat
.errh::IsMarkedAsHandled(err)
Returns 1 if the error is marked as handled.
errh::Adapt(err, severity, message, category, code)
The error
err
is adapted with the components specified. Besides the mandatory argumenterr
, there should be at least one other argument.errh::MarkAsHandled(err,actually)
The error
err
is marked as handled if the argumentactually
is non-zero. Marked errors will not be passed to the next error handler. The default of the optional argumentactually
is 1. Using 0 will remove the mark from the error.
The log file aimms.err
AIMMS logs all errors and warnings to the file aimms.err
as they are
raised. The folder in which this file resides is controlled by the
option Listing_and_temporary_files
. The number of backups retained
of this file is controlled by the option Number_of_log_file_backups
.
Raising Errors and Warnings
Raising errors
The RAISE
statement is used to
raise an error regarding a situation that cannot be handled, or to
raise a warning regarding a situation that can be handled but might warrant further investigation.
The syntax of the RAISE
statement is straightforward.
Syntax
raise-statement:
Example
In the following example an error is raised when the inflow of a node exceeds its capacity.
if inflow > stockCap then
RAISE ERROR "Inflow exceeds stock capacity" CODE 'TooMuchInflow' ;
endif ;
Error code and category
In order to enable an error handler to recognize the type of error being
raised by a RAISE
statement, that statement allows an optional error
code to be specified. This is an element in the set
errh::ErrorCodes
. If the specified element does not yet exist, it is
created and added to that set. The category of an error raised by the
RAISE
statement is fixed to 'User'
.
Position information
AIMMS uses the line/procedure in which the RAISE
statement is
specified as the position information associated with the error. This
permits the messages/errors window to open the attribute window of the
procedure and place the cursor on the statement where the problematic
situation is detected.
Raising warnings
Not only AIMMS itself but also procedures written in AIMMS may recognize
situations that can be handled but might warrant closer inspection by
the application user. For this purpose, the RAISE
statement can
raise a warning, for example:
if card( RawMaterialTraders ) = 0 then
RAISE WARNING "There are no raw material traders, this may lead to " +
"infeasibilities in the case of too many accepted deliveries." ;
endif ;
The handling of warnings generated by a RAISE
statement is
controlled by the option Warning_user
, with default
common_warning_default
. The control of warning handling is further
explained in Warnings.
Legacy: Intrinsics with a Return Status
Legacy situation
AIMMS external procedures and intrinsic procedures can both return a
status code indicating whether or not they were successful. A return
value \(\leq 0.0\) is interpreted as not successful, wheareas a
return value \(> 0.0\) is successful. In addition, when they are not
successful, the error message is often left in CurrentErrorMessage
,
although this is only a guideline. The return value of a call to an
intrinsic procedure is either
- checked
As illustrated in the example:
retval := PageOpen(...) ; if retval <= 0 then ... use CurrentErrorMessage ... endif ;
- not checked
As illustrated in the example:
PageOpen(...) ;
Available error handling methods
In the context of the error handling facility available in AIMMS, how should one handle the “checked” and “not checked” procedure calls when the return value is 0 and these procedures have not raised an error themselves? There are five error handling methods available to choose from:
ignore
An error is never raised for an error occurring inside such a procedure, whether or not the return status is checked.
raise_warning_when_not_checked
A warning is only raised if the return status of an intrinsic procedure is not checked.
raise_when_not_checked
An error is only raised if the return status of an intrinsic procedure is not checked.
raise_always_warning
A warning is raised whether or not the return status is checked.
raise_always
An error is raised whether or not the return status is checked.
Which choice of error handling method is best depends on the application and can be controlled using the options:
Intrinsic_procedure_error_handling
for procedures with a return status supplied by AIMMS and
External_procedure_error_handling
for externally supplied procedures.
The values of these options are the names of the error handling methods
described above. The default of both these options is
raise_when_not_checked
. For projects created prior to the
introduction of the error handling facilities in AIMMS (i.e. created in
AIMMS 3.9 or lower), these options generate the non-default value
raise_warning_when_not_checked
in order to notify the model
developer but do not change the existing behavior of such projects
significantly.
Warnings
Warnings
AIMMS recognizes and warns about several types of possibly problematic situations. These situations might warrant further investigation. As with most other languages, AIMMS warns against the use of identifiers before initializing them. But unlike other languages, AIMMS also warns against the inconsistent use of units of measurement (such as a comparison of a volume against a weight), or of model formulations for which AIMMS can detect either compiletime or runtime issues that lead to sub-optimal performance or ambiguous results. A selection of performance-related warnings is discussed in Using AIMMS’ Advanced Diagnostics.
Complete flexibility
The desired handling of each of these situations depends on the developer and the application; varying from treating it as an error to fully ignoring it. To permit complete flexibility, there is separate option to control the reporting of each type of problematic situation recognized.
Grouping Warning options
Although all warnings can be controlled individually, this is not the most convenient way to employ the diagnostics provided by these warnings. When entertaining a new idea (quick prototyping), most modelers understandably do not want to be bothered by various warnings and want to be able to turn them all off. To facilitate this, all the warnings have been grouped into either common or strict warnings, and the associated options assume default value for common and strict warnings. Thus, all diagnostic warnings can be switched off by just changing the options that control these defaults. For normal development work it is advisable to at least turn the common warnings on. In addition, we would encourage to turn on the strict warnings during application tests.
Choosing the option setting
In order to implement the above scheme and still permit full flexibility, each option controlling the detection of a type of problematic situation can take on one of the following values:
error
The situation is marked as an error and treated as an error.
warning_handle
The warning is raised in the current error handler, but does not count toward the interruption of normal execution.
common_warning_default
The value of the option
Common_warning_default
is used.warning_collect
The warning is raised in the
Global_error_collector
, bypassing the stack of error handlers.strict_warning_default
The value of the option
Strict_warning_default
is used.off
The warning is ignored.
The default of these options is either common_warning_default
or
strict_warning_default
, thereby effectively dividing these options
into common and strict groups. The range of options for
common_warning_default
and strict_warning_default
is
{off, warning_collect, warning_handle, error}
. The default of the
option common_warning_default
is warning_handle
and the default
of the option strict_warning_default
is off
.
Train on error handling
You may refer to our online training about error handling following this link to apply error handling on concrete models.