Basic Commands

Introduction

Command statements instruct the AMPL processor to perform some action that may or may not make use of model entities (see Statement Types).

There are many different commands that we can call in an AMPL program. Here we are going to take a look on the basic commands, just enough to get you started with AMPL programming. We have divided them into five categories:

Include Input Files

When we have a multiple files program, we can use the commands include, model, data and commands to read other input files.

include "path/to/file.md" ;

When the AMPL processor encounters an include statement, it goes read the statements of the file indicated in the command.

model "path/to/file.md" ; data "path/to/file.dat" ; commands "path/to/file.run" ;

The commands model, data and commands do exactly the same thing that include does, with the addition that they also specify how the statements in the file should be interpreted. The AMPL processor has two modes: model and data. The model and commands statements put the processor in model mode, in which all the statements are interpreted either as declarations or as commands. The data statement puts the processor in data mode, in which all the statatements are interpreted as assignment statements.

NOTE: We can also call model, data and commands without a file path, in which case all the statement does is to change the mode in which the processor currently is. See the Single File Program example.

Solver Usage

AMPL is not a solver. The AMPL processor acts as an interface between us and the actual solver. A solver is a program that solves optimization problems. AMPL is not a solver. We feed data into the AMPL processor, which processes the data and sends it to a solver. The solver then returns some output to the AMPL processor, which in turn returns this output to us, usually by printing it into the terminal. The figure below gives an overview of what happens when we call solve;. AMPL is not a solver.



option solver "CPLEX" ;

We use the option command to set a variety of options that influence the behaviour of commands and solvers. The above command shows how to select the solver that we want to use to solve our optimization problem.

solve ;

The solve command invokes the currently selected solver (see above) to solve the optimization problem that we have implemented. The AMPL processor sends to the solver all the problem information that we have provided (formulation and data) and also a variety of options that affect the solver's behaviour.

Print and Display

There are three main AMPL commands that we can use to print something in the terminal: display, print and printf.

display I, a ; print a[1] ;

We use the display and print commands to print out the value, expression or formula associated with our model entities. The main difference between display and print is that the first can have model entity collections in the list of entities to be printed, while the second can only take a list of individual model entities.

In the example above, suppose that I is a set declared with set I = 1..10; and that a is a parameter collection declared with param a{I} default 0;. With the display statement we are able to print out both the set and the collection, while with the print command we can only print the parameter indicated with the subscript [1].

printf "Objective: %f\n" , z.val ;

printf stands for print formatted. The first argument of the statement is the format of the string, which is basically a template string with missing values. The missing values are specified in the template with a % followed by a character indicating the type of value that goes there. %f means that the missing value is a real number (a floating point value), %d means it is an integer, and %s means it is a string. \n is a special sequence of characters that places a line break at that point.

After the template string comes a comma separated list of template fillers, which are the values with which the template will be filled. The first filler goes where the first missing value is, the second filler goes where the second missing value is, and so on. In the above example, supposing that z is an objective function, the command will print out into the terminal the objective's current value, e.g. Objective: 348.000000, followed by a line break.

All of the three statements above can take an indexing expression, as shown below:

print {i in I : i >= 5} a[i] ;

The indexing expression instructs the the AMPL processor to iterate over all entries in the specified expression and print out the listed entities (orange part). The above statement is roughly equivalent to the for-loop below, but the code below will print each entry in a new line:

for {i in I : i >= 5} { print a[i]; }

Modifying Model Entities

Sometimes we want to modify our problem data and/or formulation during the execution, in which case we can use the following commands.

reset ; reset data ;

The reset command discards something from our program, essentially undoing something that we have done previously. A common use case of the reset command is when we want to solve a given problem with different formulations and/or with multiple data instances.

In order to read a different problem formulation, e.g. model "model-v2.md", we need to discard the current model with all its declarations. Otherwise, they could conflict with the declarations of the new model. In this case, we use the first form of the reset command shown above, which causes the AMPL processor to forget all the previous declarations, as well as the data associated with it.

But sometimes we want to keep the current model with all its declarations and only provide a new data instance of the problem. In this case, we only need to reset the assignments that we've made, which we do by calling reset data;. This will cause the AMPL processor to discard all the values assigned to the model entities that we have declared, after which we are able to load new data by calling e.g. data "data-v2.md";.

update data ;

The update data; command is similar to the reset data; command in that both allow us to make new data assignments to our model entities. But the update command will not discard the current values associated with them. Instead, all it does is to allow us to make reassignments in a new data section, in which we can assign new values to either all model entities or only part of it.

let alpha := 0.9 ;

The let command is an alternative way to update the data assigned to a model entity. While with the update data command we have to provide the new data in a data section, the let is an assignment statement itself, in which we specify the model entity that will have its value updated with a new value.

NOTE: the let statement is not equivalent to the assignment statements in a data section. For instance, we cannot provide data in a list-like or table-like fashion, but can only assign a specific expression to a specific model entity. Use it when convenient.

We can also use indexing expression to update multiple values:

let {i in I} beta[i] := beta[i] + 0.05 ;

In the example above, supposing that beta is a parameter collection declared with param beta{I};, the indexing expression instructs the compiler to iterate over all items in set I and update the value of the associated beta parameter with the value in orange, thus incrementing it by 0.05.

This statement is equivalent to the for-loop below.

for {i in I} { let beta[i] := beta[i] + 0.05; }

Program Flow Control

The main mechanisms that we use to control the flow of our program are conditional statements and loop statements.

We can instruct the AMPL processor to only execute a body of statements if certain conditions are met using if ... then ... else blocks, whose structure and syntax looks like this:

if solve_result_num < 0 then { print "Problem not solved."; }
if solve_result_num < 0 then { print "Problem not solved."; } else { print "Problem solved."; }
if solve_result_num = 200 then { print "Problem is infeasible."; } else if solve_result_num = 300 then { print "Problem is unbounded."; }

As for loop statements, there are different ways in which we can repeat a sequence of instructions.

for {i in 1..10} { solve;
printf "Result: %f\n", z.val;
let alpha := alpha - 0.05;
}

A for-loop statement instructs the AMPL processor to repeat a body of statements (in orange) for each index that satisfies the indexing expression (in yellow). Once the body of statements have been executed for each index, it stops.

repeat while alpha >= 0.05 { solve;
printf "Result: %f\n", z.val;
let alpha := alpha - 0.05;
}

repeat until alpha < 0.05 { solve;
printf "Result: %f\n", z.val;
let alpha := alpha - 0.05;
}

The repeat-while and repeat-until statements instructs the AMPL processor to repeat a body of statements (in orange) while/until the condition in yellow is satisfied. In the while form, it continues to repeat while the condition is true. In the until form, it continues to repeat until the condition is true.

NOTE: When using the repeat command, we can also put the stop conditions after the body of instructions, in which case the condition is checked after each iteration rather than before each iteration: repeat { solve; printf "Result: %f\n", z.val; let alpha := alpha - 0.05; } until alpha < 0.05;

Aside from specifying a loop stop condition in the loop statement, it is also possible to interrupt a loop using a break; command within the body of statements. For instance, we can have the following condition within the for-loop example above, which would interrupt the loop after 10 minutes:

if _ampl_elapsed_time > 600 then { break; }

It is also possible to skip over an iteration using the continue; command within the body of statements. It does not interrupt the loop, but rather, goes directly to the next iteration without execution the remaining statements. For instance, we can have the following condition within the for-loop example above, which would skip over the iterations in which i is not in set S:

if i not in S { continue; }