Как определить несколько штрафов, чтобы свести к минимуму в целом в Clingo?

Я пытаюсь использовать clingo для создания распределения турнирных игровых комнат:

player(1..20).
room(1..4).
played(1..20, 0).    
rank(1..20, 1).
played(1..20, 1..20, 0).

0 { used_room(R) } 1 :- room(R).
3 { game(P, R) } 4 :- used_room(R), player(P).
:- game(P, R1), game(P, R2), R1 != R2.

penalty(Y) :- Y = #sum {
  X: game(P1, R), game(P2, R), played(P1, P2, X);
  X: game(P1, R), game(P2, R), rank(P1, R1), rank(P2, R2), abs(R1-R2) = X;
  4 - X: played(P, X), not game(P, _)
}.

#minimize { X: penalty(X) }.

Первые 5 строк должны быть вводом:

  • Количество присутствующих игроков варьируется
  • Количество свободных номеров
  • Каждому игроку нужно сыграть 4 раунда за ночь, поэтому мы записываем количество раундов, сыгранных каждым игроком на данный момент.
  • У каждого игрока есть ранг (в таблице лиг), который обновляется после каждого раунда — в идеале игроки в каждой комнате должны иметь одинаковые уровни (например, ELO).
  • Чтобы препятствовать тому, чтобы алгоритм постоянно объединял одних и тех же игроков, мы также отслеживаем количество раундов, которое любая пара игроков провела вместе в одной комнате.

Идея состоит в том, чтобы обновлять эти входные данные после каждого раунда (после получения точек) и возвращать их обратно в решатель для создания распределения следующего раунда.

Затем я попытался добавить некоторые ограничения:

  • Доступно определенное количество комнат, но не обязательно использовать их все. Каждую комнату можно использовать или не использовать в каждом раунде.
  • В любой используемой комнате должны быть назначены 3 или 4 игрока (из-за механики игры - 4 всегда предпочтительнее, 3 для работы с крайними случаями)
  • Ни один игрок не может быть назначен более чем в одну комнату в течение одного раунда.

Наконец, я попытался определить некоторые штрафы, чтобы помочь решателю выбрать наилучшее распределение:

  • Для каждой пары игроков P1, P2, которые были размещены в одной комнате, добавьте X к штрафу, где X — количество раз, когда они уже играли вместе.
  • Для каждой пары игроков P1, P2, которые были размещены в одной комнате, к штрафу добавляется (абсолютная) разница в их рангах.
  • Для каждого игрока, который должен сыграть еще X раундов, но не был выбран для этого раунда, добавьте X к штрафу.

Я хотел сделать так, чтобы этот штраф накапливался, чтобы каждый игрок, у которого осталось 4 раунда (то есть каждый игрок в начале), добавлял к штрафу 4 очка, а не только одно (что и произошло с этим кодом). На практике выполнение этого получает penalty(4). и вообще не выделяет game(player, room)..

Кроме того, я хотел бы иметь некоторое ограничение, чтобы я не мог оказаться в ситуации, когда у некоторых игроков еще остались раунды для игры, но осталось недостаточно игроков (например, если у вас остался 1, 2 или 5 игроков, которым просто нужно играть один раунд). Я не уверен, каков правильный инвариант, который мог бы гарантировать, что этого не произойдет даже на несколько раундов вперед. Это скорее реальный логический вопрос, чем цепляние. На практике у вас есть около 3-4 доступных комнат и около 20-30 игроков — важно, никогда нет гарантии, что количество игроков будет в 4 раза больше.

Что-то еще, чего не хватает в моей текущей реализации, — это ограничение, согласно которому для определенного подмножества игроков (назовем их экспертами) по крайней мере один из них должен оставаться вне текущего раунда (и вести его). И вообще для каждой используемой комнаты должен остаться хотя бы один игрок (включая одного эксперта). Это должно быть жестким ограничением.

Наконец, мы хотели бы максимизировать использование комнат, то есть максимизировать количество игроков за раунд и минимизировать количество раундов в целом. Это должно быть слабым ограничением (точно так же, как ограничения, связанные с рангами и играми, сыгранными до сих пор вместе).

Заранее большое спасибо за любую помощь или совет! К сожалению, в документации не так много сложных примеров, поэтому я не смог понять, какой правильный синтаксис подходит для моих вариантов использования.


person rudolfovic    schedule 09.05.2021    source источник
comment
1. Clingo ничего не компилирует. Также помогает сообщение об ошибке. 2. Все ваши факты и правила нуждаются в . в конце. 3. используйте #minimize {X@Y: штраф(X,Y)}. где Y должен быть приоритетом вашего оператора минимизации.   -  person Max Ostrowski    schedule 10.05.2021
comment
reddit.com/r/explainlikeimfive/comments/233dq5/. Я добавил точки везде, и теперь он жалуется на ':' в 3 { P: game(P, R) } 4   -  person rudolfovic    schedule 10.05.2021


Ответы (2)


Писать все в начале и пытаться отлаживать потом сложно в программировании наборов ответов. В вашем случае может быть лучше сначала определить пространство поиска и одно за другим написать ограничения, чтобы удалить нежелательные ответы.

Чтобы обновлять входные данные после каждого раунда, вам придется работать с онлайн-ASP. Вы можете рассмотреть возможность просмотра https://potassco.org/clingo/, так как он содержит ценный учебный материал, который может помочь вам в обучении. Кодировка ниже может быть хорошей отправной точкой для вас

%%% constants %%%
#const numberOfRounds = 4.
#const numberOfPlayers = 2.
#const numberOfRooms = 4.
%%% constants %%%

%%% define players and their initial ranks %%%
player(1..numberOfPlayers,1).
%%% define players and their initial ranks %%%

%%% define rooms %%%
room(1..numberOfRooms).
%%% define rooms %%%

%%% define rounds %%%
round(1..numberOfRounds).
%%% define rounds %%%

%%% define search space (all possible values) %%%
search(P,R,S) :- player(P,_), room(R), round(S).
%%% define search space (all possible values) %%%

%%% define played %%%
{played(P,R,S)} :- search(P,R,S).
%%% define played %%%

%%% remove answers that does not satisfy the condition "Each player needs to play 4 rounds" %%%
:- player(P,_), X = #count{S : played(P,_,S)}, X != numberOfRounds.
%%% remove answers that does not satisfy the condition "Each player needs to play 4 rounds" %%%

%%% show output %%%
#show.
#show played/3.
%%% show output %%%
person NTP    schedule 11.05.2021
comment
Спасибо за Ваш ответ. Не могли бы вы объяснить, как определить термин суммы, чтобы оценивались все возможные экземпляры каждого правила, а не только один? - person rudolfovic; 11.05.2021
comment
@rudolfovic, не могли бы вы привести пример того, что вы пытаетесь сделать? - person NTP; 11.05.2021
comment
@rudolfovic И рассмотрите возможность использования параметра --text в clingo, чтобы увидеть фактические основные правила. Так будет легче увидеть, правильно ли вы написали. - person Max Ostrowski; 11.05.2021
comment
Я начал сталкиваться с проблемами производительности для значений игроков > 16. Выиграю ли я что-нибудь, заменив {played(P,R,S)} :- search(P,R,S) на nPlayers*nRounds {played(P,R,S)} nPlayers*nRounds. Это все еще безнадежно застревает, но помогает ли это вообще? Также может быть, что он работает медленно, потому что я запускаю его онлайн (potassco.org/clingo/run)? Мой браузер перестает отвечать, поэтому я предполагаю, что он работает в JS или что-то в этом роде. - person rudolfovic; 12.05.2021
comment
Я имел в виду под nPlayers*nRounds {played(1..nPlayers, 1..nRooms, 1..nDecks) } nPlayers*nRounds или еще лучше какой-то эквивалент for i in 1..nPlayers [ nRounds {played(i, 1..nRooms, 1..nDecks) nRounds}]. Есть ли для этого синтаксис в clingo? Я мог бы просто сгенерировать кучу таких предложений на другом языке, но я был бы удивлен, если бы это было невозможно в clingo из коробки. - person rudolfovic; 12.05.2021

Основываясь на совете NTP, я снова попытался переписать, и теперь почти все ограничения присутствуют и, похоже, работают, за исключением штрафа на основе ранжирования, который мне еще нужно добавить.

%%% constants %%%
#const nRounds = 3.
#const nPlayers = 4.
#const nRooms = 3.
#const nDecks = 4.

player(1..nPlayers).
room(1..nRooms).
deck(1..nDecks).
writer(1,1;2,2;3,3;4,4).

{ played(P, R, D) } :- player(P), room(R), deck(D).

% A player can only play a deck in a single room.
:- played(P, R1, D), played(P, R2, D), R1 != R2.
% A player must play nRounds decks overall.
:- player(P), X = #count { R, D: played(P, R, D) }, X != nRounds.
% Any deck in any room must be played by 3-4 players.
legal_player_count(3;4).
:- room(R), deck(D),
  X = #count { P: played(P, R, D) },
  X > 0,
  not legal_player_count(X).
% Writers cannot play their own decks.
:- writer(P, D), played(P, _, D).
% At least one non-playing player per room.
:- deck(D),
  Playing = #count { P, R: played(P, R, D) },
  Rooms = #count { R: played(_, R, D) },
  nPlayers - Playing < Rooms.

% Input points(P, R, D, X) to report points.
% winner(P, R, D) :- points(P, R, D, X), X = #max { Y : points(_, R, D, Y) }.

% Total number of decks played throughout the night (for minimisation?)
decks(X) :- X = #count { D: played(_, _, D) }.
% Total number of games played together by the same players (for minimisation)
% The total sum of this predicate is invariant
% Minimisation should took place by a superlinear value (e.g. square)
common_games(P1, P2, X) :- player(P1), player(P2), P1 != P2,
  X = #count { R, D: played(P1, R, D), played(P2, R, D) }, X > 0.

% For example:
% common_game_penalty(X) :- X = #sum { Y*Y, P1, P2 : common_games(P1, P2, Y) }.

% Another rank-based penalty needs to be added once the rank mechanics are there

% Then the 2 types of penalties need to be combined and / or passed to the optimiser

#show decks/1.
#show played/3.
#show common_games/3.
person rudolfovic    schedule 10.05.2021
comment
Я рад, что смог помочь. - person NTP; 11.05.2021