In the following Prolog program I want to constrain a list of 30 (Slot, Group, Topic) triples. The constraint is super simple: Slot should be a distinct number from 1..30 and each (Group, Topic) pair should exist at least once with Group domain in 1..5 and Topic domain in 1..6. The solution is the list [1, 1, 1, 2, 1, 2, 3, 1, 3, ..., 30, 5, 6]. I constrain the pairs as ((Group * 10) + Topic) integers to mimic a product type.
However, I don't come close to make this work in clpfd.
Trying to locate the performance issue, the run(List) predicate takes a list of "products" that should exist (this list should be a set of 30 elements in the end). From the code I left out a display predicate that prints out the pairs schedule.
So this works sort of okay:
?- profile(test_run([11, 12, 13, 14, 15, 16, 21, 22, 31, 41, 51])).
"1-1" "1-1" "1-1" "1-1" "6-1"
"1-5" "1-1" "1-1" "1-1" "1-1"
"2-1" "1-2" "1-1" "1-1" "1-1"
"1-1" "3-1" "2-2" "1-1" "1-1"
"1-1" "1-1" "4-1" "1-3" "1-1"
"1-1" "1-1" "1-1" "5-1" "1-4"
true.
However, not so sure why this needs 250k backtracks (profile:
)
Adding the (2, 3) pair to the list (how difficult can it be?) makes the program run forever:
?- profile(test_run([11, 12, 13, 14, 15, 16, 21, 22, 23, 31, 41, 51])).
^CAction (h for help) ? abort
% Execution Aborted
I am clearly misusing clpfd at the very basic level, but I would love to get a clue where/why. I don't need a coded solution here, as that is not what I'm struggling with.
:- use_module(library(clpfd)).
generate_less_vars(Vars) :-
length(Vars, 90),
constrain_vars(Vars).
constrain_vars([]).
constrain_vars([Slot, Group, Topic| Rest]) :-
Slot in 1..30,
Group in 1..5,
Topic in 1..6,
constrain_vars(Rest).
extract([], [], [], []).
extract([Slot, Group, Topic|Vars], [Slot|Slots], [Group|Groups], [Topic|Topics]):-
extract(Vars, Slots, Groups, Topics).
constrain_slots(Vars) :-
extract(Vars, Slots, _, _),
all_distinct(Slots).
% all Groups must have all Topics at least once
constrain_Group_Topics(Vars, List) :-
topic_times_group(Vars, Prods),
maplist(at_least_once(Prods), List).
topic_times_group([], []).
topic_times_group([_, Group, Topic|Vars], [Prod| Prods]) :-
Prod #= (Group * 10) + Topic ,
topic_times_group(Vars, Prods).
at_least_once(Vars, Single_Domain_Value):-
element(_, Vars, Single_Domain_Value).
test_run(List) :-
generate_less_vars(Vars),
constrain_slots(Vars),
constrain_Group_Topics(Vars, List),
labeling([ff], Vars).
