SWI Prolog CLPFD performance

52 Views Asked by At

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: 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

profile: hangs

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).
0

There are 0 best solutions below