Download
/*
* SICSTUS CLPFD DEMONSTRATION PROGRAM
* Purpose : The Rehearsal Problem
* Author : Mats Carlsson
*
* | ?- rehearsal(concert).
* | ?- rehearsal(film1).
* | ?- rehearsal(film2).
*/
:- module(rehearsal, [rehearsal/1]).
:- use_module(library(lists)).
:- use_module(library(clpfd)).
rehearsal(ID) :-
system(ID, Cost, Dur, TaskOrder, Pieces, Players),
search(Cost, Dur, TaskOrder),
print_solution(Pieces, Players).
print_solution(Pieces, Players) :-
piece_events(Pieces, 0, L1, L2),
player_events(Players, 0, L2, []),
sort(L1, L3), % for end < leave < arrive < start
( foreach(Key-Val,L3)
do print_event(Val, Key)
),
print_players(Players, 0, 0).
piece_events([], _) --> [].
piece_events([task(S,_,E,_,_)|L], I) --> [S-start(J),E-end(J)],
{J is I+1},
piece_events(L, J).
player_events([], _) --> [].
player_events([player(S,_,E,_,_)|L], I) --> [S-rive(J),E-leave(J)],
{J is I+1},
player_events(L, J).
print_event(start(P), At) :-
format('~d: piece ~d starts.\n', [At,P]).
print_event(end(P), At) :-
format('~d: piece ~d ends.\n', [At,P]).
print_event(rive(P), At) :-
format('~d: player ~d arrives.\n', [At,P]).
print_event(leave(P), At) :-
format('~d: player ~d leaves.\n', [At,P]).
print_players([], _, Cost) :-
format('Total cost ~d.\n', [Cost]).
print_players([player(_,D,_,Duty,Sal)|L], I, Cost1) :-
J is I+1,
Idle is (D-Duty),
Inc is Idle*Sal,
format('Player ~d playing ~d, idle ~d, cost ~d.\n',
[J,Duty,Idle,Inc]),
Cost2 is Cost1+Inc,
print_players(L, J, Cost2).
search(Cost, Dur, TaskOrder) :-
min_max(1000000, Cost, [], TaskOrder, Dur, []).
min_max(Cost0, Cost, _, TaskOrder, Dur, Ora1) :-
Cost #< Cost0,
findall(f(TaskOrder,Cost,Ora2), (left(TaskOrder,0,Dur,Ora1,Ora2)->true), [f(TO1,C1,O2)]), !,
min_max(C1, Cost, TO1, TaskOrder, Dur, O2).
min_max(Cost, Cost, TaskOrder, TaskOrder, _, _).
left([_], _, _, [], []) :- !.
left(L1, Est, Lct, Ora1, [N|Ns]) :-
ora_head_tail(Ora1, M, Ms),
Item = task(Est,_,Est1,_,_),
select_nth(Item, L1, L2, 0, M, N),
(M=:=N -> Ora2=Ms ; Ora2=[]),
right(L2, Est1, Lct, Ora2, Ns).
ora_head_tail([], 0, []).
ora_head_tail([M|T], M, T).
right([_], _, _, [], []) :- !.
right(L1, Est, Lct, Ora1, [N|Ns]) :-
ora_head_tail(Ora1, M, Ms),
Item = task(Lct1,_,Lct,_,_),
select_nth(Item, L1, L2, 0, M, N),
(M=:=N -> Ora2=Ms ; Ora2=[]),
left(L2, Est, Lct1, Ora2, Ns).
system(ID, Cost, Dur, TaskOrder, Items, Players) :-
durations(ID, Ds),
sumlist(Ds, Dur),
( foreach(D,Ds),
foreach(S,Ss),
foreach(E,Es),
foreach(task(S,D,E,1,0),Items)
do true
),
domain(Ss, 0, Dur),
domain(Es, 0, Dur),
findall(L, plays_in_pieces(ID,L,_), PieceMat),
findall(C, plays_in_pieces(ID,_,C), Salaries),
( foreach(PieceRow,PieceMat),
foreach(Sal,Salaries),
foreach(player(Min,_,Max,Duty1,Sal),Players),
param([Ss,Es,Ds,Dur])
do active(PieceRow, Ss, Es, Ds, Ss1, Es1, Ds1),
minimum(Min, Ss1),
maximum(Max, Es1),
sumlist(Ds1, Duty1)
),
costs(Players, Salaries, 0, Duty, CVs, []),
( foreach(K-V1,CVs),
foreach(K,Coeffs),
foreach(V1,Vars)
do true
),
scalar_product([-1|Coeffs], [Duty|Vars], #=, Cost),
% non-overlapping pieces
cumulative(Items, [/*global(true) does not pay off*/]),
% heuristic piece order
transpose(PieceMat, PieceMatT),
tag_by_occ(PieceMatT, Items, L1, []),
keysort(L1, L2),
( foreach(_-V2,L2),
foreach(V2,TaskOrder)
do true
),
% symmetries
TaskOrder = [task(S1,_,_,_,_),task(S2,_,_,_,_)|_],
S1 #< S2,
% redundant optimality constraints
findall(R, redundancy(PieceMat,PieceMatT,R), Red),
redundant(Red, Ss, Es, Players).
/*
Redundancy idea 1: implied constraints.
If player P1 plays a subset of the pieces played by player P2,
then [min(P1),max(P1)] must be contained in [min(P2),max(P2)].
Redundancy idea 2: optimality constraints.
Find Piece1 and Piece2 have identical columns except for some players Ps,
who all play Piece2 but not Piece1. Then for any solution where:
[1] all P in Ps : min(P) < Piece1 < Piece2
there is a solution which is no worse where:
[2] all P in Ps : min(P) < Piece2 < Piece1
Similarly, for any solution where:
[3] all P in Ps : Piece2 < Piece1 < max(P)
there is a solution which is no worse where:
[4] all P in Ps : Piece1 < Piece2 < max(P)
So exclude [1] and [3], taking care to not wipe out
a solution that matches both [1] and [3].
*/
redundancy(_, MatT, swap(Players,Piece1,Piece2)) :-
select_nth(Col1, MatT, _, 1, 1, Piece1),
select_nth(Col2, MatT, _, 1, 1, Piece2),
Piece1=\=Piece2,
Col1\==Col2,
subset_01(Col1, Col2, 0, Players).
redundancy(Mat, _, subset(Player1,Player2,D)) :-
select_nth(Row1, Mat, _, 1, 1, Player1),
select_nth(Row2, Mat, _, 1, 1, Player2),
Player1=\=Player2,
subset_01_diff(Row1, Row2, 0, D).
% % didn't help
% redundancy(Mat, _, disjoint(Player1,Player2)) :-
% select_nth(Row1, Mat, _, 1, 1, Player1),
% select_nth(Row2, Mat, _, 1, 1, Player2),
% Player1 < Player2,
% disjoint_01(Row1, Row2).
subset_01_diff([], [], D, D).
subset_01_diff([X|L1], [Y|L2], D1, D3) :-
X =< Y,
D2 is D1+(Y-X),
subset_01_diff(L1, L2, D2, D3).
subset_01([], [], _, []).
subset_01([X|L1], [X|L2], I, L3) :- !,
J is I+1,
subset_01(L1, L2, J, L3).
subset_01([0|L1], [1|L2], I, [J|L3]) :-
J is I+1,
subset_01(L1, L2, J, L3).
% disjoint_01([], []).
% disjoint_01([X|L1], [Y|L2]) :-
% X+Y < 2,
% disjoint_01(L1, L2).
redundant([], _, _, _).
redundant([subset(Player1,Player2,_)|Red], Ss, Es, MinMax) :- !,
nth1(Player1, MinMax, player(Min1,_D1,Max1,_,_)),
nth1(Player2, MinMax, player(Min2,_D2,Max2,_,_)),
Min1 #>= Min2, % helps
Max1 #=< Max2, % helps
% _D1 #=< _D2, % does not help
redundant(Red, Ss, Es, MinMax).
% redundant([disjoint(Player1,Player2)|Red], Ss, Es, MinMax) :- !,
% nth1(Player1, MinMax, player(Min1,_,Max1,_,_)),
% nth1(Player2, MinMax, player(Min2,_,Max2,_,_)),
% Min1 #\= Min2,
% Max1 #\= Max2,
% redundant(Red, Ss, Es, MinMax).
redundant([swap(Players,Piece1,Piece2)|Red], Ss, Es, MinMax) :- !,
mins_maxs(Players, MinMax, Mins, Maxs),
maximum(Min, Mins),
minimum(Max, Maxs),
nth1(Piece1, Ss, S1),
nth1(Piece2, Ss, S2),
nth1(Piece1, Es, E1),
S1 #=< S2 #=> E1 #=< Min,
Min #= S2 #/\ S2 #=< S1 #=> S1 #>= Max, % not in the paper
redundant(Red, Ss, Es, MinMax).
mins_maxs([], _, [], []).
mins_maxs([P|Ps], MinMax, [Min|Mins], [Max|Maxs]) :-
nth1(P, MinMax, player(Min,_,Max,_,_)),
mins_maxs(Ps, MinMax, Mins, Maxs).
% order: (i) min #occs, (ii) max dur
tag_by_occ([], []) --> [].
tag_by_occ([Col|Mat], [Item|Items]) --> [key(Occ,NDur)-Item],
{Item = task(_,D,_,_,_)},
{NDur is -D},
{sumlist(Col, Occ)},
tag_by_occ(Mat, Items).
active([], [], [], [], [], [], []).
active([1|Row], [S|Ss], [E|Es], [D|Ds], [S|Ss1], [E|Es1], [D|Ds1]) :- !,
active(Row, Ss, Es, Ds, Ss1, Es1, Ds1).
active([0|Row], [_|Ss], [_|Es], [_|Ds], Ss1, Es1, Ds1) :-
active(Row, Ss, Es, Ds, Ss1, Es1, Ds1).
costs([], [], Duty, Duty) --> [].
costs([player(Min,Dur,Max,Duty,_)|MM], [Sal|Sals], Duty1, Duty3) --> [Sal-Dur],
{Dur in Duty..sup},
{Dur #= Max-Min},
{Duty2 is Duty1+Sal*Duty},
costs(MM, Sals, Duty2, Duty3).
select_nth(X, [Y|R], R, K, Min, K) :-
K >= Min,
X = Y.
select_nth(X, [A|L], [A|R], I, Min, K) :-
J is I+1,
select_nth(X, L, R, J, Min, K).
:- discontiguous
plays_in_pieces/3,
durations/2.
plays_in_pieces(concert, [1,1,0,1,0,1,1,0,1], 1).
plays_in_pieces(concert, [1,1,0,1,1,1,0,1,0], 1).
plays_in_pieces(concert, [1,1,0,0,0,0,1,1,0], 1).
plays_in_pieces(concert, [1,0,0,0,1,1,0,0,1], 1).
plays_in_pieces(concert, [0,0,1,0,1,1,1,1,0], 1).
durations(concert, [2,4,1,3,3,2,5,7,6]).
plays_in_pieces(film1, [1,1,1,1,0,1,0,1,0,1,1,0,0,0,0,0,0,0,0,0], 10).
plays_in_pieces(film1, [1,1,1,0,0,0,1,1,0,1,0,0,1,1,1,0,1,0,0,1], 4).
plays_in_pieces(film1, [0,1,1,0,1,0,1,1,0,0,0,0,1,1,1,0,0,0,0,0], 5).
plays_in_pieces(film1, [0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0], 5).
plays_in_pieces(film1, [0,1,0,0,0,0,1,1,0,0,0,1,0,1,0,0,0,1,1,1], 5).
plays_in_pieces(film1, [0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0], 40).
plays_in_pieces(film1, [0,0,0,0,1,0,1,1,0,0,0,0,0,0,1,0,0,0,0,0], 4).
plays_in_pieces(film1, [0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0], 20).
durations(film1, [2,1,1,1,1,3,1,1,1,2,1,1,2,1,2,1,1,2,1,1]).
plays_in_pieces(film2, [0,0,1,0,0,0,0,0,1,1,1,1,0], 40).
plays_in_pieces(film2, [1,1,0,0,1,1,1,1,1,1,1,0,1], 20).
plays_in_pieces(film2, [0,1,0,0,0,0,0,1,0,0,0,0,0], 20).
plays_in_pieces(film2, [1,0,0,1,1,1,1,1,1,1,0,0,1], 10).
plays_in_pieces(film2, [0,0,0,1,0,0,0,0,0,1,0,0,0], 5).
plays_in_pieces(film2, [1,0,0,0,0,1,1,0,1,1,1,1,0], 10).
plays_in_pieces(film2, [0,1,0,0,1,0,0,0,1,1,1,0,0], 5).
plays_in_pieces(film2, [0,0,0,0,0,1,0,0,0,1,0,0,0], 4).
plays_in_pieces(film2, [0,0,0,0,0,0,0,0,0,0,1,0,1], 5).
plays_in_pieces(film2, [0,0,0,0,0,0,0,0,1,1,0,0,0], 4).
durations(film2, [1,1,1,1,3,1,1,1,1,1,1,1,1]).