lambdaprologconstraint-programmingclpfdresource-scheduling

Prolog: foreach or forall for constraint solving?


I'm attempting project scheduling with SWI prolog and CLP. I managed to support sequential dependencies but I'm struggling with avoiding double booking people.

I have a list called Schedule containing elements like [taskname, starttime] where starttime is a free variable for the constraint solver. They're already constrained by sequential dependencies.

I'm trying to write a loop like this to rule out double bookings:

  forall /* or maybe foreach*/ (isa(P,person), (
    % Filter scheduled tasks on that person...
    include(\[T,S]^(assigned(T,P)), Schedule, HisSchedule),
    % Present what serialized expects..
    maplist(\[T,S]^S^true, HisSchedule, Sts),
    % duration is just user-defined data... 
    maplist(\[T,S]^D^(duration(T,D)), HisSchedule, Dus),
    % Hit it...
    serialized(Sts, Dus)
  )),

With foreach it always fails and with forall it always succeeds without constraining anything.

Schedule is global as far as this loop is concerned, and the purpose is to constrain its starttime elements using serialized. OTOH, HisSchedule, Sts and Dus depend on the particular person. So I think I need foreach to make Schedule happy but forall to make HisSchedule etc happy. Is that the problem? And if so how do I fix it?


Solution

  • The forall/2 built-in is offered by some Prolog systems, it relies heavily on non-monotonic constructs, and was never designed to collaborate with constraints. Same is true for foreach/2 which attempts to be a bit smarter.

    Answers, solutions, constraints

    So, what is the big, fundamental problem here? A lot of Prolog received its current form when constraints were not widely known. Many constructs thus take success of a goal as a yes as ultimate truth. But with constraints, things are a bit different. A succeeding goal produces an answer which now may contain no solution at all! For this reason success is not what it used to be. Here is an example using SICStus:

    | ?- asserta(clpfd:full_answer).
    yes
    | ?- X mod 2 #= 1.
    clpfd:(X mod 2#=1),
    X in inf..sup ? 
    yes
    | ?- X mod 2 #= 1, X mod 2 #= 0.
    clpfd:(X mod 2#=0),
    clpfd:(X mod 2#=1),
    X in inf..sup ? ;
    no
    | ?- X mod 2 #= 1, X mod 2 #= 0, X in 0..9.
    no
    

    Answers may now contain no solution at all, in other words, they may be false.

    In your example include/3 is highly problematic, as is forall/2. Ah, and also setof/3 gets crazy with constraints:

    | ?- setof(t, (I in 1..3 ; I in 3..5 ), _). % SICStus
    yes
    
    ?- setof(t, (I in 1..3 ; I in 3..5 ),_).  % SWI
    I = 3.
    

    If at all, the correct answer would be I in 1..5.

    To solve this problem, first transform the relational data into lists:

       ...,
       setof(P, isa(P, person), Ps),
       maplist(perperson(P,Global),Ps),
       ...