Search code examples
prologeclipse-clp

How integer suspension will be handled when it is used in head of a condition


I have the following conditions over two variable A and B:

[A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
     % expression 1
;
     % expression 2
)
%cntd

The problem is in line 2, the solver doesn't know about the value of A and B, how to decide which branch of condition will be continued without specifying the value of the variables at line 2?

The reasonable act is to decide on this branch based on the value of the variables when the solver traverse the possible values for the variables. But, As I found it goes through one of these expressions before knowing the value of the variables. What is the solution to prevent that?


Solution

  • Constraint Programming fits well with Prolog as long as you stick to pure logic. But, as you demonstrate, one cannot freely mix procedural elements such as cut (!) and if-then-else (->;) with the constraint logic.

    The use of if-then-else or cuts is only safe if the condition is entailed (i.e. unconditionally true) or disentailed (unconditionally false) at "constraint setup time". In practice that means that such conditions should not contain problem variables (domain variables etc), but only problem parameters (constants) that are known a priori. Dedicated modelling languages distinguish these two things, but Prolog doesn't.

    How NOT to express alternatives in constraint models

    The above means that you cannot use cut/if-then-else to express the kind of alternative that you wanted to express. It might be tempting to simply get rid of the committed-choice aspect of the conditional, and rewrite as a pure disjunction. For example, you could rewrite

    ( Usage #>= 1000 -> Rate#=7, Bonus#=100              % WRONG
    ;                   Rate#=9, Bonus#=0
    )
    

    as a pure disjunction

    ( Usage #>= 1000, Rate#=7, Bonus#=100                % INEFFICIENT
    ; Usage #<  1000, Rate#=9, Bonus#=0
    )
    

    While this is now logically correct, do not do this! Prolog explores alternatives (expressed using semicolon (;) or multiple clauses) via backtracking, i.e. by eagerly choosing one alternative first, and going back to the other later. This will normally ruin any hope for an efficient constraint program! In a constraint program, all search should be located in the search/labeling routine.

    Reified constraints

    If both the condition and the branches of the alternatives are constraints that have a reified implementation (i.e. an implementation that can reflect the truth of a constraint in a boolean variable), you are in luck: you can rewrite the whole conditional alternative with the help of the special connectives for reified constraints (in ECLiPSe: and, or, neg, =>, #=). For the above example:

    Usage #>= 1000  =>  Rate#=7 and Bonus#=100,            % OK
    Usage #<  1000  =>  Rate#=9 and Bonus#=0
    

    or

    Usage #>= 1000 and Rate#=7 and Bonus#=100 or           % OK
    Usage #<  1000 and Rate#=9 and Bonus#=0
    

    Unfortunately, only the basic arithmetic constraints have reified versions and can be combined in this way!

    Using other built-in constraints

    In a way, dealing with alternatives is the most difficult part of problem solving, and many built-in constraints address this. Is is therefore worth checking whether the problem can be modelled on top of existing built-in constraints without having any explicit disjunctions in the model. Candidates are element/3, table/2. disjunctive/2 and many others.

    Delaying the choice

    A last resort solution is to delay the exploration of the alternatives until the truth of the condition can be unambiguously decided. In ECLiPSe this is easiest with delay clauses. Using the OP's example:

    delay choice(A, B) if var(A);var(B).     % wait for A,B to be known
    choice(A, B) :-
        ( (A>3 ; B>3) ->                     % can now use normal Prolog tests
            write("expression 1")
        ;
            write("expression 2")
        ).
    

    This works, but will only act once both A and B are instantiated. If, as in this case, the condition is reifiable, we can do somewhat better:

    choice(A, B) :-
        Bool #= (A#>3 or B#>3),
        delayed_choice(Bool).
    
    delay delayed_choice(Bool) if var(Bool).
    delayed_choice(1) :- write("expression 1").
    delayed_choice(0) :- write("expression 2").
    

    This will already act when the condition is satisfied by the domains:

    ?- choice(A, B), B #> 3.
    expression 1
    

    Turning general disjunctions into a constraint

    ECLiPSe has a nifty feature called Generalised Propagation in library(propia). This can effectively turn Prolog disjunctions into a constraint, by using a simple annotation. Starting with the correct, but inefficient formulation above, we can write:

    ?- ( Usage #>= 1000, Rate#=7, Bonus#=100
       ; Usage #<  1000, Rate#=9, Bonus#=0
       ) infers most.
    
    Usage = Usage{-1.0Inf .. 1.0Inf}
    Rate = Rate{[7, 9]}
    Bonus = Bonus{[0, 100]}
    There is 1 delayed goal.
    Yes (0.00s cpu)
    

    As the domains of Rate and Bonus show, useful information has been extracted from the disjunction, even before the applicable alternative can be decided.