Draft Writeup of July 30, 1998


In addition to the solution and related numerical values, it can be useful to have certain symbolic information about the results of the most recent solve. For example,

A new feature of the AMPL-solver interface permits solvers to return these and related kinds of status information so that you can examine and use it in a standard way.

We begin by describing a convenient and precise new way of testing the overall result of a solver run, and the result of the most recent solver run for a specified objective or named problem. Then we describe the solver status for variables and constraints and its use to specify an advanced start. We conclude by describing a complementary AMPL status that characterizes model components that have not been sent to the solver for various reasons. For most purposes, a variable or constraint's status of interest can be denoted by adding the suffix .status to its name.

Solve results

A solver finishes its work because it has identified an optimal solution or has encountered some other terminating condition. In addition to the values of the variables, the solver may set two built-in AMPL parameters and an AMPL option that provide information about the outcome of the optimization process:

ampl: model diet.mod;
ampl: data diet2.dat;

ampl: display solve_result_num, solve_result;
solve_result_num = -1
solve_result = '?'

ampl: solve;
MINOS 5.5: infeasible problem.
9 iterations

ampl: display solve_result_num, solve_result;
solve_result_num = 200
solve_result = infeasible

ampl: option solve_result_table;
option solve_result_table '\
0       solved\
100     solved?\
200     infeasible\
300     unbounded\
400     limit\
500     failure\
At the beginning of an AMPL session, solve_result_num is -1 and solve_result is '?'. Any solve command resets these parameters, however, so that they describe the solver's status at the end of its run, solve_result_num by a number and solve_result by a character string. The solve_result_table lists the possible combinations, which may be interpreted as follows:

      number   string       interpretation
     0 -  99   solved       optimal solution found
   100 - 199   solved?      optimal solution indicated, but error likely
   200 - 299   infeasible   constraints cannot be satisfied
   300 - 399   unbounded    objective can be improved without limit
   400 - 499   limit        stopped by a limit that you set (such as on iterations)
   500 - 599   failure      stopped by an error condition in the solver routines
Normally this status information is used in scripts, where it can be tested to distinguish among cases that must be handled in different ways. As an example, the following script for the diet problem progressively decrements the sodium limit, n_max["NA"], until it finds a value that makes the problem infeasible:

model diet.mod;
data diet2a.dat;

repeat {
   if solve_result = "infeasible" then break;
   let n_max["NA"] := n_max["NA"] - 1000;
The if condition solve_result = "infeasible" could equivalently be written 200 <= solve_result_num < 300. Normally you will want to avoid the latter alternative, since it only makes the script more cryptic. It can occasionally be useful, however, in making fine distinctions among different solver termination conditions. For example, here are some of the values that the CPLEX solver sets for different optimality conditions:

    solve_result_num   message at termination
                  0    optimal solution
                  1    primal has unbounded optimal face
                  2    optimal integer solution
                  3    optimal integer solution within mipgap or absmipgap
The value of solve_result is "solved" in all of these cases, but you can test solve_result_num if you need to distinguish among them. The interpretations of solve_result_num are entirely solver-specific; you'll have to look at a particular solver's instructions to see which values it returns and what they mean.

The brief result message that the solver returns to AMPL is also accessible via a built-in symbolic parameter, solve_message:

ampl: display solve_message;
solve_message = 'MINOS 5.5: infeasible problem.\
9 iterations'
You can use AMPL's string functions to test the contents of solve_message, but for most situations a test of solve_result will be simpler and less solver-dependent.

Solve results can be returned as described above only if AMPL's invocation of the solver has been successful. Invocation can fail because the operating system is unable to find or execute the specified solver, or because some low-level error prevents the solver from attempting or completing the optimization. Typical causes include a misspelled solver name, improper installation of the solver, missing or corrupt input files, insufficient memory or disk space, and termination of the solver process by an execution fault ("core dump") or a "break" from the keyboard.

The built-in parameter solve_exitcode records the success or failure of the most recent solver invocation. Initially -1, it is reset to 0 whenever there has been a successful invocation, and to some nonzero value otherwise:

ampl: reset;
ampl: display solve_exitcode;
solve_exitcode = -1

ampl: model diet.mod;
ampl: data diet2.dat;
ampl: option solver xplex;
ampl: solve;

Cannot invoke xplex: No such file or directory
exit code 4

ampl: display solve_exitcode;
solve_exitcode = 1024

ampl: display solve_result, solve_result_num;
solve_result = '?'
solve_result_num = -1
Here the failed invocation, due to the misspelled solver name xplex, is reflected by a positive solve_exitcode value. The solve status parameters solve_result and solve_result_num are also reset to their initial values '?' and -1.

If solve_exitcode exceeds the value in option solve_exitcode_max, then AMPL aborts any currently executing compound commands (include, commands, repeat, for, if). The default value of solve_exitcode_max is 0, so that AMPL normally aborts compound commands whenever a solver's invocation fails. A script that sets solve_exitcode_max to a higher value may test the value of solve_exitcode, but in general its interpretation is not consistent across operating systems or solvers.

Solver statuses of objectives and problems

Sometimes it is convenient to be able to refer to the solve result obtained when a particular objective was most recently optimized, or when a particular named problem was most recently solved. For this purpose, AMPL associates with each built-in solve result parameter a "status" suffix:
built-in parameter    suffix
solve_result         .result
solve_result_num     .result_num
solve_message        .message
solve_exitcode       .exitcode
Appended to an objective or problem name, this suffix indicates the value of the corresponding built-in parameter at the most recent solve in which the objective or problem was current. Thus our sample script trnloc1p.run for Benders decomposition suffixes SubPoint with .result to refer to the most recent subproblem solution:
if SubPoint.result = "infeasible" then { ...
As another example, consider the small McDonald's diet model. Its objective functions are defined as:
minimize Total_Cost: sum {j in FOOD} cost[j] * Buy[j];
minimize Nutr_Amt {i in NUTR}: sum {j in FOOD} amt[i,j] * Buy[j];
After minimizing three of these objectives, we can view the solve status values for all of them:
ampl: model diet1.mod;
ampl: data diet1.dat;
ampl: solve;

CPLEX 5.0: optimal integer solution; objective 15.05
74 simplex iterations
69 branch-and-bound nodes
Objective = Total_Cost

ampl: objective Nutr_Amt['Cal'];
ampl: solve;

CPLEX 5.0: optimal integer solution; objective 2500
11 simplex iterations
3 branch-and-bound nodes

ampl: objective Nutr_Amt['VitC'];
ampl: solve;

CPLEX 5.0: optimal integer solution; objective 100
34 simplex iterations
26 branch-and-bound nodes

ampl: display Total_Cost.result, Nutr_Amt.result;

Total_Cost.result = solved

Nutr_Amt.result [*] :=
    Cal  solved
  Carbo  '?'
Protein  '?'
   VitA  '?'
   VitC  solved
   Calc  '?'
   Iron  '?'
For the objectives that have not yet been used, the .result suffix is unchanged (at its initial value of '?' in this case).

Solver statuses of variables

In addition to providing for return of the overall status of the optimization process as described above, AMPL lets a solver return an individual status for each variable. This feature is intended mainly for reporting the basis status of variables after a linear program is solved either by the simplex method, or by an interior-point (barrier) method followed by a "crossover" routine. The basis status is also relevant to solutions returned by certain nonlinear solvers, notably MINOS, that employ an extension of the concept of a basic solution.

In addition to the variables declared by var statements in an AMPL model, solvers also define "slack" or "artificial" variables that are associated with constraints. Solver statuses for these latter variables are defined in a similar way, as explained in the next section. Both variables and constraints also have an "AMPL status" that distinguishes those in the current problem from those that have been removed from the problem by presolve or by commands such as drop. The interpretation of AMPL statuses and their relationship to solver statuses are considered in a subsequent section.

The major use of solver status values from an optimal basic solution is to provide a good starting point for the next optimization run. The new option send_statuses, when left at its default value of 1, instructs AMPL to include statuses with the information about variables sent to the solver at each solve. You can see the effect of this feature in almost any sensitivity analysis that re-solves after making some small change to the problem. As an example, consider what happens when the multi-period production example from the AMPL book is solved repeatedly after increases of 5% in the availability of labor. With the send_statuses option set to 0, the solver reports about 30 iterations of the simplex method each time it is run:

ampl: model steelT3.mod;
ampl: data steelT3.dat;
ampl: option send_statuses 0;

ampl: solve;
CPLEX 5.0: optimal solution; objective 514521.7143
30 iterations (1 in phase I)

ampl: let {t in 1..T} avail[t] := 1.05 * avail[t];
ampl: solve;
CPLEX 5.0: optimal solution; objective 537104
31 iterations (1 in phase I)

ampl: let {t in 1..T} avail[t] := 1.05 * avail[t];
ampl: solve;
CPLEX 5.0: optimal solution; objective 560800.4
31 iterations (1 in phase I)

ampl: let {t in 1..T} avail[t] := 1.05 * avail[t];
ampl: solve;
CPLEX 5.0: optimal solution; objective 585116.22
30 iterations (1 in phase I)
With send_statuses left at its default value of 1, however, only the first solve takes 30 iterations. Subsequent runs take a few iterations at most:
ampl: model steelT3.mod;
ampl: data steelT3.dat;

ampl: solve;
CPLEX 5.0: optimal solution; objective 514521.7143
30 iterations (1 in phase I)

ampl: let {t in 1..T} avail[t] := 1.05 * avail[t];
ampl: solve;
CPLEX 5.0: optimal solution; objective 537104
3 iterations (2 in phase I)

ampl: let {t in 1..T} avail[t] := 1.05 * avail[t];
ampl: solve;
CPLEX 5.0: optimal solution; objective 560800.4
0 iterations

ampl: let {t in 1..T} avail[t] := 1.05 * avail[t];
ampl: solve;
CPLEX 5.0: optimal solution; objective 585116.22
2 iterations (1 in phase I)
Each solve after the first automatically uses the variables' basis statuses from the previous solve to construct a starting point that turns out to be only a few iterations from the optimum. In the case of the third solve, the previous basis remains optimal; the solver thus confirms optimality immediately and reports taking 0 iterations.

The following discussion explains how you can view, interpret, and change variables' status values in the AMPL environment. You don't need to know any of this to use optimal bases as starting points as shown above, but these features can be useful in certain advanced circumstances.

AMPL refers to a variable's solver status by appending .sstatus to its name. Thus you can exhibit the statuses of variables by use of the display command. At the beginning of a session (or after a reset), when no problem has yet been solved, all variables have the status none:

ampl: model diet.mod;
ampl: data diet2a.dat;

ampl: display Buy.sstatus;

Buy.sstatus [*] :=
BEEF  none
 CHK  none
FISH  none
 HAM  none
 MCH  none
 MTL  none
 SPG  none
 TUR  none
After an invocation of a simplex method solver, the same display lists the statuses of the variables at the optimal basic solution:
ampl: solve;
MINOS 5.5: optimal solution found.
13 iterations, objective 118.0594032

ampl: display Buy.sstatus;

Buy.sstatus [*] :=
BEEF  bas
 CHK  low
FISH  low
 HAM  upp
 MCH  upp
 MTL  upp
 SPG  bas
 TUR  low
Two of the variables -- Buy['BEEF'] and Buy['SPG'] -- have status bas, which means they are in the optimal basis. Three have status low and three upp, indicating that they are nonbasic at lower and upper bound, respectively. A table of the recognized solver status values is stored in the AMPL option sstatus_table:
ampl: option sstatus_table;

option sstatus_table '\
0	none	no status assigned\
1	bas	basic\
2	sup	superbasic\
3	low	nonbasic <= (normally =) lower bound\
4	upp	nonbasic >= (normally =) upper bound\
5	equ	nonbasic at equal lower and upper bounds\
6	btw	nonbasic between bounds\
Numbers and short strings representing status values are given in the first two columns. (The numbers are mainly for communication between AMPL and solvers, though you can access them by using the suffix .sstatus_num in place of .sstatus.) The entries in the third column are comments. For nonbasic variables as defined in many textbook simplex methods, only the low status is applicable; other nonbasic statuses are required for the more general bounded-variable simplex methods in large-scale implementations. The sup status is used by solvers like MINOS to accommodate nonlinear problems. This is AMPL's standard sstatus_table; a solver may substitute its own table, in which case its instructions will indicate the interpretation of the table entries.

You can change a variable's status by use of AMPL's let command. This facility is sometimes useful when you want to re-solve a problem after a small, well-defined change. For example, consider the variables defined in our cutting-stock model, cut1.mod:

param nPAT integer >= 0;   # number of patterns
set PATTERNS := 1..nPAT;   # set of patterns

var Cut {PATTERNS} integer >= 0;  # rolls cut using each pattern
At each pass through the Gilmore-Gomory procedure, as implemented in cut1.run, nPAT is stepped by one, causing a new variable Cut[nPAT] to be created. It has an initial solver status of "none", like all new variables, but it is guaranteed -- by the way that the pattern generation procedure is constructed -- to enter the basis as soon as the expanded cutting problem is re-solved. Thus we give it a status of "bas" instead:
let Cut[nPAT].sstatus := "bas";
It turns out that this change tends to reduce the number of iterations in each re-optimization of the cutting problem, at least with some simplex solvers. Setting a few statuses in this way is not guaranteed to reduce the number of iterations, however. Its success depends on the particular problem and solver, and on their interaction with a number of complicating factors: Each solver has its own way of adjusting the statuses that it receives from AMPL, when necessary, to produce an initial basic solution that it can use. Thus some experimentation is usually necessary to determine whether any particular strategy for modifying the statuses is useful.

For models that have several var declarations, AMPL's generic synonyms for variables provide a convenient way of getting information about the statuses of all variables. Using expressions such as _var, _varname and _var.sstatus in a display statement, you can produce a quick summary table of the results. You can also easily list the names of all variables that satisfy some condition, by using the statuses to define an appropriate subset of the variables. Here, for example, are the names of all the basic variables:

ampl: display {j in 1.._nvars: _var[j].sstatus = "bas"} _varname[j];

_varname[j] [*] :=
 1  "Make['bands',1]"
 2  "Make['bands',2]"
 3  "Make['bands',3]"
 4  "Make['bands',4]"
 5  "Make['coils',1]"
 6  "Make['coils',2]"
 7  "Make['coils',3]"
 8  "Make['coils',4]"
15  "Inv['coils',1]"
22  "Sell['bands','east',4]"
32  "Sell['coils','west',2]"
33  "Sell['coils','west',3]"
To display the values of the variables along with their names, you could use the same statement with _varname[j] replaced by (_varname[j],_var[j]).

Solver statuses of constraints

Implementations of the simplex method typically add one variable for each constraint that they receive from AMPL. Each added variable has a coefficient of 1 or -1 in its associated constraint, and coefficients of 0 in all other constraints. If the associated constraint is an inequality, then the addition is used as a "slack" or "surplus" variable; its bounds are chosen so that it has the effect of turning the inequality into an equivalent equation. If the associated constraint is an equality to begin with, then the added variable is an "artificial" one whose lower and upper bounds are both zero.

A simplex solver gains two advantages from having these "logical" variables added to the "structural" ones its gets from AMPL:

The artificial variables can also play a special role in helping the solver to find a solution that satisfies all the constraints (a feasible solution), but they are not needed for such a purpose in efficient versions of the simplex method. Large-scale implementations thus commonly treat all logical variables in much the same way as the structural ones, with only very minor adjustments to handle the case in which a variable's lower and upper bounds are equal. A basic optimal solution is defined by the collection of basis statuses of all variables.

To accommodate statuses of logical variables, AMPL permits a solver to return status values corresponding to the constraints as well as the variables. The solver status of a constraint -- written as the constraint name suffixed by .sstatus -- is interpreted as the status of the logical variable associated with that constraint. For example, in our diet model, where the constraints are all inequalities,

subject to diet {i in NUTR}:
   n_min[i] <= sum {j in FOOD} amt[i,j] * Buy[j] <= n_max[i];
the logical variables are slacks that have the same variety of statuses as the structural variables:
ampl: model diet.mod;
ampl: data diet2a.dat;
ampl: option show_stats 1;

ampl: solve;

8 variables, all linear
6 constraints, all linear; 47 nonzeros
1 linear objective; 8 nonzeros.

MINOS 5.5: optimal solution found.
13 iterations, objective 118.0594032

ampl: display Buy.sstatus;
Buy.sstatus [*] :=
BEEF  bas
 CHK  low
FISH  low
 HAM  upp
 MCH  upp
 MTL  upp
 SPG  bas
 TUR  low

ampl: display diet.sstatus;
diet.sstatus [*] :=
  A  bas
 B1  bas
 B2  low
  C  bas
CAL  bas
 NA  upp
There are a total of 6 basic variables, equal in number to the 6 constraints (one for each member of set NUTR) as is always the case at a basic solution. In our transportation model, where the constraints are equalities,
subject to Supply {i in ORIG}:
   sum {j in DEST} Trans[i,j] = supply[i];

subject to Demand {j in DEST}:
   sum {i in ORIG} Trans[i,j] = demand[j];
the logical variables are artificials that receive the status "equ" when nonbasic. Here's how the statuses for all constraints might be displayed using AMPL's constraint generic synonyms (analogous to the variable synonyms previously shown):
ampl: model transp.mod;
ampl: data transp.dat;

ampl: solve;
MINOS 5.5: optimal solution found.
13 iterations, objective 196200

ampl: display _conname,_con.sstatus;
:        _conname     _con.sstatus    :=
1    "Supply['GARY']"   equ
2    "Supply['CLEV']"   equ
3    "Supply['PITT']"   equ
4    "Demand['FRA']"    bas
5    "Demand['DET']"    equ
6    "Demand['LAN']"    equ
7    "Demand['WIN']"    equ
8    "Demand['STL']"    equ
9    "Demand['FRE']"    equ
10   "Demand['LAF']"    equ
One artificial variable, on the constraint Demand['FRA'], is in the optimal basis, though at a value of zero like all artificial variables in any feasible solution. (In fact there must be some artificial variable in every basis for this problem, due to a linear dependency among the equations in the model.)

AMPL statuses

Only those variables, objectives and constraints that AMPL actually sends to a solver can receive solver statuses on return. So that you can distinguish these from components that are removed prior to a solve, a separate "AMPL status" is also maintained. You can work with AMPL statuses much like solver statuses, by using the suffix .astatus in place of .sstatus, and referring to option astatus_table for a summary of the recognized values:
ampl: option astatus_table;
option astatus_table '\
0	in	normal state (in problem)\
1	drop	removed by drop command\
2	pre	eliminated by presolve\
3	fix	fixed by fix command\
4	sub	defined variable, substituted out\
5	unused	not used in current problem\
Here's an example of the most common cases, using one of our diet models:
ampl: model dietu.mod;
ampl: data dietu.dat;

ampl: drop diet_min['CAL'];
ampl: fix Buy['SPG'] := 5;
ampl: fix Buy['CHK'] := 3;

ampl: solve;
MINOS 5.5: optimal solution found.
3 iterations, objective 54.76

ampl: display Buy.astatus;
Buy.astatus [*] :=
BEEF  in
 CHK  fix
FISH  in
 HAM  in
 MCH  in
 MTL  in
 SPG  fix
 TUR  in

ampl: display diet_min.astatus;
diet_min.astatus [*] :=
  A  in
 B1  pre
 B2  pre
  C  in
CAL  drop
An AMPL status of "in" indicates components that are in the problem sent to the solver, such as variable Buy['BEEF'] and constraint diet_min['A']. Three other statuses indicate components left out of the problem: The objective and problem commands also have the effect of dropping certain components, such as alternative objectives in this example:
ampl: model dietobj.mod;
ampl: data dietobj.dat;
ampl: objective total_cost['JEWEL'];

ampl: solve;
MINOS 5.5: optimal solution found.
8 iterations, objective 74.3532967

ampl: display total_cost.astatus;
total_cost.astatus [*] :=
'A&P'  drop
 VONS  drop
Not shown here are the AMPL status "unused" for a variable that does not appear in any objective or constraint, and "sub" for variables and constraints eliminated by substitution (as explained on pages 247-248 of the AMPL book).

For a variable or constraint, you will normally be interested in only one of the statuses at a time: the solver status if the variable or constraint was included in the problem sent most recently to the solver, or the AMPL status otherwise. Thus AMPL provides the suffix .status to denote the one status of interest:

ampl: display Buy.status, Buy.astatus, Buy.sstatus;
:    Buy.status Buy.astatus Buy.sstatus    :=
BEEF   low        in          low
CHK    fix        fix         none
FISH   low        in          low
HAM    low        in          low
MCH    bas        in          bas
MTL    low        in          low
SPG    fix        fix         none
TUR    low        in          low

ampl: display diet_min.status, diet_min.astatus, diet_min.sstatus;
:   diet_min.status diet_min.astatus diet_min.sstatus    :=
A     bas             in               bas
B1    pre             pre              none
B2    pre             pre              none
C     low             in               low
CAL   drop            drop             none
In general, name.status is equal to name.sstatus if name.astatus is "in", and is equal to name.astatus otherwise.

Comments or questions?
Write to info@ampl.com or use our comment form.

Return to the AMPL update page.

Return to the AMPL home page.