#prolog #scheduling #swi-prolog #clpfd #clp
#пролог #планирование #swi-prolog #clpfd #clp
Вопрос:
Я решаю задачу планирования в SWI Prolog, используя библиотеку CLPFD. Поскольку я впервые решаю что-то более серьезное, чем sendmory, мне, вероятно, нужны хорошие советы от более опытных пользователей. Позвольте мне кратко описать домен / задачу.
Домен
У меня есть «календарь» на месяц. Каждый день есть 2 на весь день, 2 на всю ночь (длительное 12-часовое обслуживание). Есть также, только с понедельника по пятницу, еще 10 рабочих на 8 часов (короткое обслуживание).
Очевидно, что ограничения домена:
- Нет последовательных служб (нет дня после ночи и наоборот, нет короткого дневного обслуживания после ночи)
- On worker может обслуживать до 2 последовательных ночных служб подряд
- У каждого работника ограниченное количество часов в месяц
- Доступно 19 рабочих
Мой подход заключается в следующем:
Переменные
Для каждого поля в календаре у меня определена переменная:
DxD_y
гдеx
— номер дня иy
равно 1 или 2 для службы долгого дняDxN_y
гдеx
— номер дня иy
равно 1 или 2 для службы long nightDxA_y
гдеx
— номер дня иy
равно 0 .. 9 для службы короткого дняSUM_x
гдеx
— номер работника (1 ..19), обозначающий сумму часов для работника
Каждая из D
переменных имеет домен 1..19
. Чтобы упростить его на данный момент, SUM_X #=< 200
для каждого X
.
Ограничения
all_distinct()
для каждой переменной за один и тот же день — каждый работник может обслуживать только одну услугу / деньglobal_cardinality()
для подсчета количества вхождений для каждого числа 1 ..19 для списка с короткими службами и длинными службами — это определяет переменныеLSUM_X
иSSUM_X
— количество вхождений workerX
вL
ong илиS
hort servicesSUM_X #= 12*LSUM_X 8*SSUM_X
для каждого работникаDxN_y #= Dx 1D_z
— чтобы избежать долгого дневного обслуживания после ночного- набор аналогичных ограничений, подобных приведенному выше, для покрытия всех ограничений домена
DxNy #= Dx 1Ny #==> DxNy #= Dx 2Ny
— чтобы избежать трех последовательных ночных служб, существуют ограничения для каждой комбинацииx
иy
Примечания
Все переменные и ограничения указаны непосредственно в сценарии pl. Я не использую предикаты prolog для создания ограничений — потому что у меня есть модель в приложении .NET (интерфейс), и я могу легко сгенерировать все материалы из .ЧИСТЫЙ код в код prolog.
Я думаю, что мой подход в целом хорош. Запуск планировщика на каком-нибудь меньшем примере работает хорошо (7 дней, 4 длительных сервиса, 1 короткий сервис, 8 рабочих). Также я смог получить некоторые достоверные результаты по полномасштабному делу — 30 дней, 19 рабочих, 4 длинных и 10 коротких сервисов в день.
Однако я не полностью удовлетворен текущим статусом. Позвольте мне объяснить, почему.
Вопросы
- Я прочитал несколько статей о проблемах моделирования планирования, и в некоторых из них используется немного другой подход — вводятся только логические переменные для каждой комбинации моей переменной (поле календаря) и worker, чтобы отметить, назначен ли worker определенному полю календаря. Это лучший подход?
- Если вы рассчитаете общие ограничения на объем работы и общее количество часов в календаре, вы обнаружите, что работники не загружены на 100%. Но решатель создает решение, скорее всего, таким образом:
utilize the first worker for 100% and then grab the next one
. Таким образом, суммы в решении выглядят[200,200,200...200,160,140,80,50,0,]
следующим образом. Я был бы рад, если рабочие будут использоваться более или менее одинаково. Есть ли какой-нибудь простой / эффективный способ добиться этого? Я подумал о том, чтобы определить что-то вроде определения различий между рабочими и свести их к минимуму, но для меня это звучит очень сложно, и я боюсь, что мне потребуется много времени, чтобы вычислить это. Я используюlabeling([random_variable(29)], Vars)
, но он только переупорядочивает переменные, поэтому эти пробелы все еще есть, только в другом порядке. Вероятно, я хочуlabeling
, чтобы процедура принимала значения в каком-то другом порядке, чемup
илиdown
(каким-то псевдослучайным способом). - Как я должен упорядочить ограничения? Я думаю, что порядок ограничений имеет значение в отношении эффективности маркировки.
- Как отладить / оптимизировать производительность маркировки? Я надеялся, что решение такого типа задач займет несколько секунд или максимум пару минут в случае очень жестких условий для сумм. Например, маркировка с
bisect
помощью опции заняла целую вечность.
При необходимости я могу предоставить еще несколько примеров кода.
Ответ №1:
Это много вопросов, позвольте мне попытаться ответить на некоторые.
… вводим только логические переменные для каждой комбинации моей переменной (поле календаря) и работника, чтобы отметить, назначен ли работник определенному полю календаря. Это лучший подход?
Обычно это делается, когда используется решатель MILP (смешанное целочисленное линейное программирование), где концепции более высокого уровня (такие как alldifferent
etc) должны быть выражены в виде линейных неравенств. Такие формулировки обычно требуют большого количества логических переменных. Программирование ограничений здесь более гибкое и предлагает больше вариантов моделирования, но, к сожалению, простого ответа нет, это зависит от проблемы. Ваш выбор переменных влияет как на то, насколько сложно выразить ограничения вашей проблемы, так и на то, насколько эффективно она решается.
Таким образом, суммы в решении выглядят как [200,200,200 …200,160,140,80,50,0,]. Я был бы рад, если рабочие будут использоваться более или менее одинаково. Есть ли какой-нибудь простой / эффективный способ добиться этого?
Вы уже упоминали идею минимизации различий, и именно так обычно реализуется такое требование балансировки. Это не должно быть сложным. Если изначально у нас есть это несбалансированное первое решение:
?- length(Xs,5), Xs#::0..9, sum(Xs)#=20, labeling(Xs).
Xs = [0, 0, 2, 9, 9]
тогда простое минимизирование максимального количества элементов списка уже даст вам (в сочетании с ограничением суммы) сбалансированное решение:
?- length(Xs,5), Xs#::0..9, sum(Xs)#=20, Cost#=max(Xs), minimize(labeling(Xs),Cost).
Xs = [4, 4, 4, 4, 4]
Cost = 4
Вы также можете минимизировать разницу между минимальным и максимальным:
?- length(Xs,5), Xs#::0..9, sum(Xs)#=20, Cost#=max(Xs)-min(Xs), minimize(labeling(Xs),Cost).
Xs = [4, 4, 4, 4, 4]
Cost = 0
или даже сумма квадратов.
[Извините, мои примеры предназначены для ECLiPSe, а не для SWI / clpfd, но должны показать общую идею.]
Как я должен упорядочить ограничения? Я думаю, что порядок ограничений имеет значение в отношении эффективности маркировки.
Вы не должны беспокоиться об этом. Хотя это может иметь некоторое влияние, оно слишком непредсказуемо и слишком сильно зависит от деталей реализации, чтобы давать какие-либо общие рекомендации. Это действительно работа разработчика решателя.
Как отладить / оптимизировать производительность маркировки?
Для реальных проблем вам часто понадобится (а) эвристическая маркировка, специфичная для конкретной проблемы, и (б) некоторое разнообразие неполного поиска. Визуализация дерева поиска или хода выполнения поиска может помочь в настройке эвристики. Вы можете найти некоторое обсуждение этих вопросов в главе 6 этого онлайн-курса.
Комментарии:
1. Спасибо за ваш большой ответ. Вероятно, я попытаюсь переключиться с SWI на Eclipse, поскольку код должен быть совместимым, и я прочитал, что решатель Eclipse FD намного быстрее, чем SWI.
2. @JanDrozen как получилось, что вы перенесли SWI из SWI в Eclipse FD? Это намного быстрее?
3. @peter.cyc Прошло довольно много времени, однако, насколько я помню, был действительно хороший прирост производительности, и у меня возникло ощущение, что среда лучше подходит для такого приложения. Но в конце я не стал вдаваться в подробности.
4. @JanDrozen Спасибо! Я просто хотел получить общее представление, а не детали. Однако с тех пор все могло измениться…