writing "scripts" in the AMPL command language

This section introduces these commands, using formatted printing and sensitivity analysis as examples. For those who want to get started faster, a syntax summary for all these commands appears at the end of this section.

The following section describes additional commands and features that permit you to express new algorithmic schemes by means of AMPL scripts.

As an example, consider how we might perform a simple
sensitivity analysis on the multi-period production problem of Section
4.2. Only 32 hours of production time are available in week 3,
compared to 40 hours in the other weeks. Suppose that we want to see
how much extra profit could be gained for each extra hour in week 3.
We can accomplish this by repeatedly solving, displaying the solution
values, and increasing `avail[3]`:

ampl:To try all integer values ofampl:model steelT.mod;ampl:data steelT.dat;MINOS 5.4: optimal solution found. 15 iterations, objective 515033 ampl:solve;ampl:display total_profit >steelT.sens;ampl:option display_1col 0;ampl:option omit_zero_rows 0;ampl:display Make >steelT.sens;ampl:display Sell >steelT.sens;ampl:option display_1col 20;ampl:option omit_zero_rows 1;ampl:display Inv >steelT.sens;ampl:let avail[3] := avail[3] + 5;MINOS 5.4: optimal solution found. 6 iterations, objective 532033 ampl:solve;ampl:display total_profit >steelT.sens;ampl:option display_1col 0;ampl:option omit_zero_rows 0;ampl:display Make >steelT.sens;ampl:display Sell >steelT.sens;ampl:option display_1col 20;ampl:option omit_zero_rows 1;ampl:display Inv >steelT.sens;ampl:let avail[3] := avail[3] + 5;MINOS 5.4: optimal solution found. 6 iterations, objective 549033 ampl:solve;

To avoid having to type the same commands again and again, we can create a new file listing the commands to be repeated:

solve; display total_profit >steelT.sens; option display_1col 0; option omit_zero_rows 0; display Make >steelT.sens; display Sell >steelT.sens; option display_1col 20; option omit_zero_rows 1; display Inv >steelT.sens; let avail[3] := avail[3] + 5;If we call this file

ampl:We refer to the contents of a file likeampl:model steelT.mod;ampl:data steelT.dat;MINOS 5.4: optimal solution found. 15 iterations, objective 515033 ampl:commands steelT.sa1;MINOS 5.4: optimal solution found. 6 iterations, objective 532033 ampl:commands steelT.sa1;MINOS 5.4: optimal solution found. 6 iterations, objective 549033 ampl:commands steelT.sa1;MINOS 5.4: optimal solution found. 7 iterations, objective 565193 ampl:commands steelT.sa1;

A script can itself contain a `commands` command that
refers to another script. Scripts can be nested in this way to a
depth of up to 10.

[[Can alternatively use the `include` command -- makes
no difference here, but does inside a loop as in the next section]]

We begin with the `for` command, which executes a
statement or collection of statements once for each member of some
set. To execute our multi-period production problem sensitivity
analysis script four times, for example, we need only type a single
`for` command followed by the command that we want to repeat:

ampl:The expression betweenampl:model steelT.mod;ampl:data steelT.dat;MINOS 5.4: optimal solution found. 15 iterations, objective 515033 MINOS 5.4: optimal solution found. 6 iterations, objective 532033 MINOS 5.4: optimal solution found. 6 iterations, objective 549033 MINOS 5.4: optimal solution found. 7 iterations, objective 565193 ampl:for {1..4} commands steelT.sa1;

As an alternative, we can add the `for` command to the
script. We then want it to loop over a whole series of commands; this
is accomplished by enclosing the commands in braces:

model steelT.mod; data steelT.dat; for {1..4} { solve; display total_profit >steelT.sens; option display_1col 0; option omit_zero_rows 0; display Make >steelT.sens; display Sell >steelT.sens; option display_1col 20; option omit_zero_rows 1; display Inv >steelT.sens; let avail[3] := avail[3] + 5; }If this script is stored in

This latter approach tends to be clearer and easier to work
with, particularly as we make the loop more sophisticated. As a first
example, consider how we would go about compiling a table of the
objective and the dual value on constraint `time[3]`, for
successive values of `avail[3]`. A script for this purpose is
shown in Figure 1. After the model and data are read, the script
provides additional declarations for the table of values:

set AVAIL3; param avail3_obj {AVAIL3}; param avail3_dual {AVAIL3};The set

let AVAIL3 := avail[3] .. avail[3] + 15 by 5;and then use a for loop to iterate over this set:

for {a in AVAIL3} { let avail[3] := a; solve; let avail3_obj[a] := total_profit; let avail3_dual[a] := time[3].dual; }We see here that a

ampl:In this case we have suppressed the messages from the solver, by including the command: avail3_obj avail3_dual := 32 515033 3400 37 532033 3400 42 549033 3400 47 565193 2980 ;include steelT.sa3

--------------------------------------------------------------- model steelT.mod; data steelT.dat; set AVAIL3; param avail3_obj {AVAIL3}; param avail3_dual {AVAIL3}; let AVAIL3 := avail[3] .. avail[3] + 15 by 5; option solver_msg 0; for {a in AVAIL3} { let avail[3] := a; solve; let avail3_obj[a] := total_profit; let avail3_dual[a] := time[3].dual; } display avail3_obj, avail3_dual; ---------------------------------------------------------------

AMPL's `for` loops are also very convenient for
generating formatted tables. Suppose that after solving the
multi-period production problem, you want to display sales both in
tons and as a percentage of the market limit. You could use a
`display` command to produce a table like this:

ampl:By writing a script that uses AMPL'sampl?display {t in 1..T, p in PROD}: Sell[p,t] 100*Sell[p,t]/market[p,t] := 1 bands 6000 100 1 coils 307 7.675 2 bands 6000 100 2 coils 2500 100 3 bands 1400 35 3 coils 3500 100 4 bands 2000 30.7692 4 coils 4200 100 ;(Sell[p,t], 100*Sell[p,t]/market[p,t]);

ampl:The script to write this table can be as short as twoSALES bands coils week 1 6000 100.0% 307 7.7% week 2 6000 100.0% 2500 100.0% week 3 1399 35.0% 3500 100.0% week 4 1999 30.8% 4200 100.0%commands steelT.tab1

printf "\n%s%14s%17s\n", "SALES", "bands", "coils"; printf {t in 1..T}: "week %d%9d%7.1f%%%9d%7.1f%%\n", t, Sell["bands",t], 100*Sell["bands",t]/market["bands",t], Sell["coils",t], 100*Sell["coils",t]/market["coils",t];This approach is undesirably restrictive, however, because it assumes that there will always be two products and that they will always be named

A more generally applicable script for generating the above
table, using a `for` loop over `1..T`, is shown in
Figure 2. Each pass through the loop generates one row of the table.
There are more `printf` statements than in the previous
example, but they are shorter and simpler. We use several statements
to write the contents of each line; `printf` does not begin a
new line except where `\n` appears in its format string.

--------------------------------------------------------------- printf "\nSALES"; printf {p in PROD}: "%14s ", p; printf "\n"; for {t in 1..T} { printf "week %d", t; for {p in PROD} { printf "%9d", Sell[p,t]; printf "%7.1f%%", 100 * Sell[p,t]/market[p,t]; } printf "\n"; } ---------------------------------------------------------------

Within the "outer" loop over `1..T` we generate each
line's product entries by use of an "inner" loop over `PROD`.
Loops can in general be nested to any depth, and may be over any set
that can be represented by an AMPL set expression. There is one pass
through the loop for every member of the set, and if the set is
ordered -- any set of numbers like `1..T`, or a set declared
`ordered` or `circular` -- then the order of the passes
is determined by the ordering of the set. If the set is unordered
(like `PROD`) then AMPL chooses the order of the passes, but
the choice is the same every time; the Figure 2 script relies on this
consistency to insure that all of the entries in one column of the
table refer to the same product.

[[Note: alternatives not using `for`]]

Returning to the multi-period production example, we observe
that the dual value on the constraint `time[3]` provides an
upper limit on the additional profit that can be realized from each
extra hour added to `avail[3]`. When `avail[3]` is made
sufficiently large, so that there is more third-week capacity than can
be used, the associated dual value falls to zero and further increases
in `avail[3]` have no effect on the optimal solution. Either
the expression `time[3]` alone, or `time[3].dual`,
represents the dual value in AMPL expressions.

We can specify that looping should stop once the dual value
falls to zero, by writing a `repeat` statement that has one of
the following forms:

repeat while time[3].dual > 0 {. . .}; repeat until time[3].dual = 0 {. . .}; repeat {. . .} while time[3].dual > 0; repeat {. . .} until time[3].dual = 0;

A complete script using `repeat` is shown in Figure 3.
The `until` phrase is placed after the loop body, so that
`time[3].dual` will not be tested until after a `solve`
has been executed in the first pass. Two other features of this
script are worth noting, as they are relevant to many AMPL scripts of
this kind.

--------------------------------------------------------------- model steelT.mod; data steelT.dat; option solution_precision 10; option solver_msg 0; set AVAIL3 default {}; param avail3_obj {AVAIL3}; param avail3_dual {AVAIL3}; param avail3_step := 5; repeat { solve; let AVAIL3 := AVAIL3 union {avail[3]}; let avail3_obj[avail[3]] := total_profit; let avail3_dual[avail[3]] := time[3].dual; let avail[3] := avail[3] + avail3_step; } until time[3].dual = 0; display avail3_obj, avail3_dual; ---------------------------------------------------------------

At the beginning of the script, we don't know how many passes
the `repeat` statement will make through the loop. Thus we
cannot determine `AVAIL3` in advance as we did in Figure 1.
Instead, we declare it initially to be the empty set,

set AVAIL3 default {}; param avail3_obj {AVAIL3}; param avail3_dual {AVAIL3};and add each new value of

let AVAIL3 := AVAIL3 union {avail[3]}; let avail3_obj[avail[3]] := total_profit; let avail3_dual[avail[3]] := time[3].dual;By adding a new member to

Because numbers in the computer are represented with a limited
number of bits of precision, a solver may return values that differ
very slightly from the solution that would be computed using exact
arithmetic. Ordinarily you don't see this, because AMPL's
`display` command is set by default to round values to six
significant digits. Compare what is shown when rounding is dropped,
by setting `display_precision` to 0:

ampl:These seemingly tiny differences can have undesirable effects whenever a script makes a comparison that uses values returned by the solver. The rounded table would lead you to believe thatMake [*,*] (tr) : bands coils := 1 5990 1407 2 6000 1400 3 1400 3500 4 2000 4200 ; ampl:display Make;ampl:option display_precision 0;Make [*,*] (tr) : bands coils := 1 5989.999999999999 1407.0000000000002 2 6000 1399.9999999999998 3 1399.9999999999995 3500 4 1999.9999999999993 4200 ;display Make;

You can avoid this kind of surprise by writing your tests more
carefully; instead of `until` `time[3].dual = 0`, for
instance, you might say `until` `time[3].dual`
`<=` `0.0000001`. Alternatively, you can instruct AMPL
to round all solution values that are returned by the solver, so that
number that are supposed to be equal really do come out equal. The
statement

option solution_precision 10;toward the beginning of Figure 3 has this effect; it states that solution values are to be rounded to 10 significant digits. These and related options are discussed and illustrated in Section 10.5 of the AMPL book.

Note finally that the script declares set `AVAIL3` as
`default` `{}` rather than `:=` `{}`. The
former allows `AVAIL3` to be changed by `let` commands
as the script proceeds, whereas the latter permanently defines
`AVAIL3` to be the empty set.

if Make["coils",2] < 1500 then printf "under 1500\n";The action may also be a series of AMPL commands grouped by braces as in the

if Make["coils",2] < 1500 then { printf "Fewer than 1500 coils in week 2.\n"; let market["coils",2] := market["coils",2] * 1.1; }An optional

if Make["coils",2] < 1500 then { printf "Fewer than 1500 coils in week 2.\n"; let market["coils",2] := market["coils",2] * 1.1; } else printf "More than 1500 coils in week 2.\n";or a group of commands:

if Make["coils",2] < 1500 then printf "under 1500\n"; else { printf "over 1500\n"; let market["coils",2] := market["coils",2] * 0.9; };AMPL executes these commands by first evaluating the logical expression following

The `if` command is most useful for regulating the flow
of control in scripts. In Figure 2, we could suppress the many
occurrences of `100%` by placing the statement that prints
`Sell[p,t]/market[p,t]` inside an `if`:

if Sell[p,t] < market[p,t] then printf "%7.1f%%", 100 * Sell[p,t]/market[p,t]; else printf " --- ";In Figure 3, we can use an

let avail[3] := 1; param avail3_step := 1; param previous_dual default Infinity; repeat while previous_dual > 0 { solve; if time[3].dual < previous_dual then { let AVAIL3 := AVAIL3 union {avail[3]}; let avail3_obj[avail[3]] := total_profit; let avail3_dual[avail[3]] := time[3].dual; let previous_dual := time[3].dual; } let avail[3] := avail[3] + avail3_step; }When used within the Figure 3 script, this loop creates a table that has exactly one entry for each different dual value discovered.

If you run the Figure 3 script with the above changes
(`steelT.sa5`), you will find that the dual value changes when
`avail[3]` is increased from 22 to 23 hours. Figure 4 exhibits
a script (`steelT.sa6`) that carries out a binary search to
determine more exactly (to 7 decimal places) the value of
`avail[3]` where the dual value changes:

ampl: commands steelT.sa6; Dual value 3620.000000 for avail[3] < 22.807142 Dual value 3500.000000 for avail[3] > 22.807144The

if time[3].dual = dual_lower then let avail3_lower := avail[3]; else let avail3_upper := avail[3];is key to the binary search algorithm, since it determines which half of the current search interval is removed at each step. The equality at the beginning of this

--------------------------------------------------------------- model steelT.mod; data steelT.dat; option solution_precision 10; option solver_msg 0; param avail3_lower default 22; param dual_lower; param avail3_upper default 23; param dual_upper; let avail[3] := avail3_lower; solve; let dual_lower := time[3].dual; let avail[3] := avail3_upper; solve; let dual_upper := time[3].dual; repeat { let avail[3] := (avail3_lower + avail3_upper) / 2; solve; if time[3].dual = dual_lower then let avail3_lower := avail[3]; else let avail3_upper := avail[3]; } until (avail3_upper - avail3_lower) / avail[3] < 0.0000001; printf "Dual value %11.6f for avail[3] < %9.6f\n", dual_lower, avail3_lower; printf "Dual value %11.6f for avail[3] > %9.6f\n", dual_upper, avail3_upper; ---------------------------------------------------------------

The Figure 4 script requires an initial interval, bounded by
`avail3_lower` and `avail3_upper`, such that the dual
value changes once as `avail[3]` moves through the interval.
If the dual value might change more than once, then the following
`if` ensures that the script will find the largest value of
`avail[3]` at which there is a change:

if time[3].dual > dual_upper then { let avail3_lower := avail[3]; let dual_lower := time[3].dual; } else let avail3_upper := avail[3];A further refinement counts the number of times that the search detects an additional change in the dual value within the initial interval:

if time[3].dual = dual_lower then let avail3_lower := avail[3]; else if time[3].dual = dual_upper then let avail3_upper := avail[3]; else { let other_bkpts := other_bkpts + 1; let avail3_lower := avail[3]; let dual_lower := time[3].dual; };Here we see how the statement following

As an example of both these commands, Figure 6 exhibits
another way of rewriting the loop from the Figure 3 script, so that a
table entry is made only when there is a change in the dual value
associated with `avail[3]`. After solving, we test to see if
the new dual value is equal to the previous one:

if time[3].dual = previous_dual then continue;If yes, there is nothing to be done for this value of

--------------------------------------------------------------- model steelT.mod; data steelT.dat; option solution_precision 10; option solver_msg 0; set AVAIL3 default {}; param avail3_obj {AVAIL3}; param avail3_dual {AVAIL3}; let avail[3] := 0; param previous_dual default Infinity; repeat { let avail[3] := avail[3] + 1; solve; if time[3].dual = previous_dual then continue; let AVAIL3 := AVAIL3 union {avail[3]}; let avail3_obj[avail[3]] := total_profit; let avail3_dual[avail[3]] := time[3].dual; if time[3].dual = 0 then break; let previous_dual := time[3].dual; } display avail3_obj, avail3_dual; ---------------------------------------------------------------

After adding an entry to the table, we test to see if the dual
value has fallen to zero:

if time[3].dual = 0 then break;If yes, we are done with the loop. We use the

When a `break` or `continue` lies within more
than one loop, it applies only to the innermost loop. This convention
generally has the effect desired. As an example, consider how we
could expand Figure 5 to perform a separate sensitivity analysis on
each `avail[t]`. The `repeat` loop would be nested in a
`for` `{t in 1..T}` loop (`steelT.sa7a`), but the
`continue` and `break` commands would apply to the inner
repeat as before.

There do exist situations in which the logic of a script
requires breaking out of a loop other than the inner loop. In the
script of Figure 5, for instance, we can imagine that instead of
stopping when the `time[3].dual` is zero,

if time[3].dual = 0 then break;we want to stop when

for {t in 1..T} if time[t].dual < 2700 then break;This statement does not have the desired effect, however, because

repeat sens_loop {. . .}

for {t in 1..T} if time[t].dual < 2700 then break sens_loop;The loop name always comes immediately after

To step through a script that does not execute any other
scripts, simply reset the AMPL option `single_step` from its
default value of zero to one. For example, here is how you might
begin stepping through the script in Figure 4:

ampl:The expressionampl:option single_step 1;steelT.sa6:3(19) data ... <2>ampl:commands steelT.sa6;

At this point you may use the `step` command to execute
individual commands of the script. Type `step` by itself to
execute one command,

<2>ampl:or type step followed by a number to execute that number of commands:steelT.sa6:5(37) option ... <2>ampl:stepsteelT.sa6:6(67) option ... <2>ampl:stepsteelT.sa6:11(188) let ... <2>ampl:step

<2>ampl:Every command is counted except those having to do with model declarations, such assteelT.sa6:14(258) let ... <2>ampl:step 3

Each `step` returns you to an AMPL prompt as before.
You may continue in this way, but at some point you will want to
display some values to see if the script is working as you intended:

<2>ampl:Any series of AMPL commands may be typed while single-stepping. After each command, theavail[3] = 22 dual_lower = 3620 <2>ampl:display avail[3], dual_lower;steelT.sa6:17(328) repeat ... <2>ampl:step 3avail[3] = 23 dual_upper = 3500 <2>ampl:display avail[3], dual_upper;

At this point, execution of the script has reached the
beginning of a `repeat` loop. The `step` command takes
you into the first pass through the loop,

<2>ampl:and into the branch of the enclosedsteelT.sa6:19(341) let ... <2>ampl:stepsteelT.sa6:21(396) solve ... <2>ampl:step

<2>ampl:Further use ofsteelT.sa6:23(407) if ... <2>ampl:stepsteelT.sa6:24(448) let ... <2>ampl:step

<2>ampl:and similarly through all of the subsequent passes, after which control passes to the command followingsteelT.sa6:19(341) let ... <2>ampl:stepsteelT.sa6:21(396) solve ... <2>ampl:step

To help you step through lengthy compound commands
(`for`, `repeat`, or `if`) AMPL provides several
alternatives to `step`. The `next` command steps past a
compound command rather than into it. In our example, `next`
would cause the entire `repeat` command to be executed,
stopping again only at the following `printf` command on line
29:

<2>ampl:TypesteelT.sa6:17(328) repeat ... <2>ampl:stepsteelT.sa6:29(586) printf ... <2>ampl:next

The commands `skip` and `skip` *n* work
like `step` and `step` *n*, except that they skip
the next 1 or *n* commands in the script rather than executing
them.

Write to

*Return to the AMPL update page.*

LAST MODIFIED 24 JANUARY 1996 BY 4er.