2025, Dec 27 21:02
Как починить модель упорядочивания в AMPL+HiGHS: alldiff, границы и цель
Почему модель AMPL+HiGHS для упорядочивания с затратами переналадки становилась недопустимой, и как получить рабочую версию с alldiff и корректными границами.
Сокращение переналадок форм кажется простым на первый взгляд: у вас есть набор заданий, каждое привязано к форме, и матрица стоимостей перехода от одной формы к другой. Цель — найти порядок заданий, минимизирующий суммарные затраты на смену формы. На практике достаточно одной крошечной ошибки в модели, чтобы решатель объявил задачу недопустимой или даже не запустился. Ниже — компактный разбор рабочей конфигурации AMPL+HiGHS и точных подводных камней, которые мешали получить допустимое решение.
Постановка задачи
Каждое задание требует ровно одну форму. Смена формы имеет стоимость, заданную матрицей переходов «форма→форма». Мы ищем перестановку заданий, которая минимизирует сумму затрат на смену формы между соседними заданиями.
Код, вызывающий проблему
Следующие фрагменты воспроизводят постановку и шаблон моделирования, из‑за которого запуск оказывался недопустимым. Основная проблема — в файле модели.
%pip install -q amplpy
ENGINE = "highs"
from amplpy import AMPL, ampl_notebook
am = ampl_notebook(
modules=["highs"],
license_uuid="default",
)
import pandas as pd
job_to_mold = pd.DataFrame(
{
"Job1": {"mold": 1},
"Job2": {"mold": 2},
"Job3": {"mold": 1},
"Job4": {"mold": 3},
"Job5": {"mold": 2},
"Job6": {"mold": 3},
"Job7": {"mold": 2},
}
).T
mold_move_cost = pd.DataFrame(
{
"mold1": {"mold1": 0, "mold2": 2, "mold3": 4},
"mold2": {"mold1": 2, "mold2": 0, "mold3": 4},
"mold3": {"mold1": 7, "mold2": 1, "mold3": 0},
}
).T
%%writefile MfgSequence.mod
set JOBSET;
set TOOLSET;
set jj within {JOBSET,JOBSET};
set mm within {TOOLSET,TOOLSET};
set TURNIDX;
param cost_job{jj};
param cost_mold{mm};
param nj;
param nm;
param nturns;
var pos{JOBSET} integer >=0,<nj
minimize obj:
sum{k in TURNIDX, a in JOBSET, b in JOBSET}
(if pos[k]==a and pos[k+1]==b then 1 else 0) * cost_job[a,b];
subject to no_ties{a in JOBSET, b in JOBSET}:
(pos[a] > pos[b] or pos[a] < pos[b]);
def build_sequence_model(job_to_mold, mold_move_cost):
mdl = AMPL()
mdl.read("MfgSequence.mod")
nj = len(job_to_mold)
JOBSET = range(nj)
TURNIDX = range(nj - 1)
nturns = len(TURNIDX)
jj_pairs = {(i, j) for i in JOBSET for j in JOBSET}
mdl.param["nj"] = nj
mdl.set["JOBSET"] = JOBSET
mdl.set["TURNIDX"] = TURNIDX
mdl.set["jj"] = jj_pairs
mdl.param["nturns"] = nturns
nm = len(mold_move_cost)
TOOLSET = range(nm)
mm_pairs = {(i, j) for i in TOOLSET for j in TOOLSET}
mdl.param["nm"] = nm
mdl.set["TOOLSET"] = TOOLSET
mdl.set["mm"] = mm_pairs
mcost = mold_move_cost.values
mdl.param["cost_mold"] = mcost
j2m = job_to_mold.values
job_cost = {
(i, j): mcost[(k, l)].tolist()
for (i, j) in jj_pairs
for (k, l) in mm_pairs
if (j2m[i] - 1) == k and (j2m[j] - 1) == l
}
mdl.param["cost_job"] = job_cost
return mdl
opt = build_sequence_model(job_to_mold, mold_move_cost)
opt.solve(solver=ENGINE)
print(opt.solve_result)
opt.display("pos")
Почему это не работает
Недопустимость возникает не из‑за математики целевой функции упорядочивания. Решению мешает само объявление модели. Первый стоп‑фактор — синтаксическая ошибка: в строке объявления переменной пропущена точка с запятой, поэтому AMPL так и не получает корректную модель для решения.
После исправления синтаксиса всплывает вторая проблема. Строгая верхняя граница для целочисленной переменной не позволяет HiGHS вывести пригодные границы. Область значений переменной задана строгим неравенством, и из‑за этого решатель не видит валидной верхней границы. Поскольку переменная целочисленная, верхнюю границу нужно задавать нестрого и равной «на единицу меньше количества». Иными словами, замените строгую верхнюю границу на нестрогую, уменьшенную на единицу.
Есть и более аккуратный способ гарантировать, что каждое задание встречается в последовательности ровно один раз. Вместо попарных неравенств через логическую дизъюнкцию используйте ограничение alldiff над переменными позиций. Оно напрямую выражает попарную различность и задаёт идею перестановки.
Исправление и компактная модель
Целевая функция целиком определяется переходами между соседними заданиями и связанными затратами на смену формы. Ниже — формулировка, которая устраняет синтаксическую ошибку, использует ограничение alldiff для перестановки и задаёт корректную нестрогую границу.
set JOBSET;
set TOOLSET;
set jj within {JOBSET,JOBSET};
set mm within {TOOLSET,TOOLSET};
set TURNIDX;
param cost_job{jj};
param cost_mold{mm};
param nj;
param nm;
param nturns;
var pos{JOBSET} integer >= 0, <= nj - 1;
minimize total_cost:
sum{k in TURNIDX, a in JOBSET, b in JOBSET}
if pos[k]==a and pos[k+1]==b then cost_job[a,b];
subject to visit_every_job:
alldiff{k in JOBSET} pos[k];
С этой формулировкой остальная интеграция с Python остаётся прежней. Вы читаете файл модели, загружаете JOBSET, TURNIDX и матрицу стоимостей, выведенную из соответствий «задание→форма», и решаете задачу в HiGHS.
model = build_sequence_model(job_to_mold, mold_move_cost)
model.solve(solver=ENGINE, verbose=True)
assert model.solve_result == "solved", model.solve_result
sol = model.var['pos'].to_dict()
print(sol)
Для приведённых данных решение такое:
{0: 3, 1: 5, 2: 1, 3: 4, 4: 6, 5: 2, 6: 0}
Почему это важно
Модели упорядочивания чувствительны к мелочам в формулировке. Пропущенная точка с запятой не даёт AMPL даже разобрать модель. Строгое неравенство для целочисленной переменной превращается для решателя в отсутствие верхней границы. А выражать перестановку через alldiff и яснее, и менее подвержено ошибкам, чем вручную кодировать попарные неравенства логическими дизъюнкциями.
Итоги
Если модель упорядочивания неожиданно не имеет допустимых решений, начните с проверки синтаксиса и границ целочисленных переменных. Там, где нужно, замените строгие границы на нестрогие и используйте ограничение alldiff, чтобы утверждать уникальность каждой позиции. Сохраняйте цель напрямую через переходы между соседними заданиями — так формулировка остаётся компактной и естественно согласуется с матрицей стоимостей смен формы.