AMPL предоставляет команды повторения цикла, которые могут выполнять одну и туже работу неограниченное количество раз автоматически. Например, чтобы выполнить многопериодный сценарий анализа чувствительности производственной задачи четыре раза, можно использовать один оператор for, а затем команду, которую мы хотим повторить:
model steelT.mod; data steelT.dat; for {1..4} commands steelT.sa1; MINOS 5.5: optimal solution found. 15 iterations, objective 515033 MINOS 5.5: optimal solution found. 1 iterations, objective 532033 MINOS 5.5: optimal solution found. 1 iterations, objective 549033 MINOS 5.5: optimal solution found. 2 iterations, objective 565193
Выражение между for и commands может быть любым индексным выражением AMPL.
В качестве альтернативы заимствования команд из отдельного файла можно записать их как тело утверждения, заключенного в фигурные скобки:
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; }
Если этот сценарий хранится в steelT.sa2, тогда весь повторный анализ чувствительности выполняется путем ввода:
commands steelT.sa2;
Этот подход имеет тенденцию быть более ясным и лучше работает. Особенно когда необходимо сделать цикл достаточно сложным. В качестве первого примера рассмотрим, как можно составить таблицу значений цели и двойных значений ограничения Time[3] для последовательных значений avail[3]. Сценарий для этой целевой функции показан ниже.
model steelT.mod; data steelT.dat; option solver_msg 0; set AVAIL3; param avail3_obj {AVAIL3}; param avail3_dual {AVAIL3}; let AVAIL3 := avail[3] .. avail[3] + 15 by 5; 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;
После того, как модель и данные прочитаны, скрипт предоставляет дополнительные объявления для таблицы значений:
set AVAIL3; param avail3_obj {AVAIL3}; param avail3_dual {AVAIL3};
Набор AVAIL3 будет содержать все различные значения для avail[3], которые необходимо применить. Для каждого такого значения a, avail3_obj[a] и avail3_dual[a] будут ассоциированными со значением целевой функции и двойными значениями. Как только они настроены, можно присваивать значение AVAIL3:
let AVAIL3 := avail[3] .. avail[3] + 15 by 5;
а затем использовать цикл for для итерации по этому набору:
for {a in AVAIL3} { let avail[3] := a; solve; let avail3_obj[a] := Total_Profit; let avail3_dual[a] := Time[3].dual; }
Видно, что цикл for может быть определен для произвольного набора, а индекс, перебирающий набор (в данном случае a), может использоваться в инструкциях внутри цикла. После того, как цикл завершен, желаемая таблица создается путем отображения avail3_obj и avail3_dual, как показано в конце сценария на рисунке выше. Если этот скрипт сохранить в steelT.sa3, то желаемые результаты выдаются одной командой:
commands steelT.sa3; : avail3_obj avail3_dual := 32 515033 3400 37 532033 3400 42 549033 3400 47 565193 2980 ;
В этом примере подавлены сообщения от решателя, включив команду solver_msg 0 в скрипт.
Циклы AMPL for также удобны для генерации отформатированных таблиц. Предположим, что после решения многопериодной задачи производства необходимо отобразить продажи как в тоннах, так и в процентах. Можно использовать команду display для создания таблицы, подобной этой:
display {t in 1..T, p in PROD} (Sell[p,t], 100*Sell[p,t] / market[p,t]); : 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 ;
Написав скрипт, использующий команду printf, можно создать более компактную таблицу:
commands steelT.tab1; SALES 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%
Сценарий для написания этой таблицы может быть коротким, в виде двух команд printf:
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];
Однако, этот подход непреднамеренно ограничивает, поскольку он предполагает, что всегда будет два продукта и что они всегда будут называться coils и bands. На самом деле оператор printf не может записать таблицу, в которой как количество строк, так и количество столбцов зависят от данных, поскольку количество записей в строке формата всегда фиксировано.
Более общий скрипт для генерации таблицы показан ниже:
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"; }
Каждый проход через «внешний» цикл по {1..T} генерирует одну строку таблицы. Внутри каждого прохода "внутренний цикл" по PROD генерирует записи о товарах в строке. Здесь используется больше операторов printf, чем в предыдущем примере, но скрипт короче и проще. Мы используем несколько операторов для записи содержимого каждой строки. Printf не начинает новую строку, кроме случаев, когда в строке формата появляется символ новой строки \n.
Циклы могут быть вложены на любую глубину и могут конструироваться для любого набора, который может быть представлен выражением набора AMPL. Для каждого прохода через цикл для каждого элемента набора, и если набор упорядочен – для любого набора чисел, например 1..T, или набор, объявленный упорядоченным или циклическим, - определяют порядок прохода набора.
Если набор не упорядочен (как PROD), то AMPL выбирает индекс, согласованный с порядком прохода, каждый раз выбирая новый элемент. Формулировка на рисунке выше опирается на эту согласованность, чтобы гарантировать, что все записи в одном столбце таблицы относятся к одному и тому же продукту.