Второй тип циклической конструкции, оператор repeat, продолжает повторяться, до тех пор, пока выполняется логическое условие. Возвращаясь к примеру анализа чувствительности, мы хотели бы воспользоваться информацией о двойной стоимости ограничения Time[3].dual (дополнительная прибыль, которая может быть получена за каждый дополнительный час, добавленный для avail[3]). Для прекращения приращения avail[3], в случае достижения им границы эффективности (значения, при котором связанное с ним двойное значение Time[3].dual снижается до нуля, и дальнейшее увеличение avail[3] не влияет на оптимальное решение). Можно указать, что цикл должен прекратиться, как только двойное значение упадет до нуля, написав оператор повторения, который имеет одну из следующих форм:
repeat while Time[3].dual > 0 {...}; repeat until Time[3].dual = 0 {...}; repeat {...} while Time[3].dual > 0; repeat {...} until Time[3].dual = 0;
Тело цикла, обозначенное {...}, должно быть заключено в фигурные скобки. Проходы по циклу продолжаются до тех пор, пока условие while истинно, или до тех пор, пока until условие false ложно. Условие, которое появляется перед телом цикла, тестирует условие перед каждым проходом. Если условие while ложно или условие until является истинным перед первым проходом, то тело цикла не выполняется. Условие, которое появляется после тела цикла, проверяется после каждого прохода, поэтому в этом случае тело цикла выполняется хотя бы один раз. Если нет условия while или until, цикл повторяется бесконечно и должен завершаться другими способами, например, оператором break.
Полный сценарий с использованием repeat показан ниже:
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;
Для этого случая выбрана фраза until, которая размещена после тела цикла, так как мы не хотим, чтобы Time[3].dual тестировался до тех пор, пока решение не будет выполнено в первом проходе. В начале сценария мы не знаем, сколько проходов будет повторять repeat оператор повторения в цикле. Таким образом, мы не можем определить AVAIL3 заранее. Вместо этого мы объявляем его изначально пустым:
set AVAIL3 default {}; param avail3_obj {AVAIL3}; param avail3_dual {AVAIL3};
и добавляем каждое новое значение avail[3] после решения:
let AVAIL3 := AVAIL3 union {avail[3]}; let avail3_obj[avail[3]] := Total_Profit; let avail3_dual[avail[3]] := Time[3].dual;
Добавляя новый элемент в AVAIL3, мы также создаем новые компоненты параметров avail3_obj и avail3_dual, которые индексируются через AVAIL3, и поэтому мы можем приступить к назначению соответствующих значений этим компонентам. Любые изменения в наборе распространяются на все объявления, которые используют набор, так же, как распространяются любые изменения для параметра. Поскольку числа в компьютере представлены с ограниченным числом битов точности, решатель может возвращать значения, которые очень незначительно отличаются от решения, которое было бы вычислено с использованием точной арифметики. Обычно пользователь этого не видит, потому что команда display по умолчанию округляет значения до шести значащих цифр. Например:
model steelT.mod; data steelT.dat; solve; display Make; Make [*,*] (tr) : bands coils := 1 5990 1407 2 6000 1400 3 1400 3500 4 2000 4200 ;
Сравним, что отображается при отсутствии округления, установив display_precision в "0":
option display_precision 0; display Make; Make [*,*] (tr) : bands coils := 1 5989.999999999999 1407.0000000000002 2 6000 1399.9999999999998 3 1399.9999999999995 3500 4 1999.9999999999993 4200 ;
Эти, казалось бы, мелкие различия могут иметь нежелательные последствия всякий раз, когда сценарий выполняет сравнение, в котором используются значения, возвращаемые решающим устройством. Округленные значения могут заставить пользователя поверить, что Make["coils", 2]> = 1400, например, верно, тогда как из второй таблицы видно, что на самом деле это ложь. Можно избежать такого рода сюрпризов, написав арифметические тесты более тщательно; например, вместо until Time[3].dual=0, можно указать Time[3].dual <= 0,0000001. В качестве альтернативы можно округлить все значения решения, которые возвращаются решающим устройством, так что числа, которые должны быть равными, были действительно равны. Заявление:
option solution_precision 10;
имеет такой же эффект. В нем говорится, что значения решения должны быть округлены до 10 значащих цифр. Этот и связанные варианты округления обсуждаются и иллюстрируются в других разделах документации. Наконец, обратите внимание, что сценарий объявляет набор AVAIL3 как default{}, а не ={}. Первый вариант позволяет изменять AVAIL3 командами let по мере выполнения сценария, тогда как последний назначает постоянное значение AVAIL3 в виде пустого набора.