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

若如初见. 提交于 2019-12-23 01:27:09

问题


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?


回答1:


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.




回答2:


What is the problem? The important note is in using -> (arrow) for the if-else. When we have the expression S -> T ; U, the S will be evaluated and if it contains some variables can have some side-effects on the code. To be more clear, try to run some examples:

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

As the value of A and B is not determined, the condition is always true, and the value of expression 1 will be printed. Also, the result is:

A = A{1 .. 10}
B = B{1 .. 10}
There are 6 delayed goals.
Yes (0.00s cpu)

As you can see, the bounds of A and B do not change as it is suspended to future expressions, and as we have not, the bound does not change.

Now try the different example:

?- [A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
    write("expression 1")
;
    write("expression 2")
),
A = 3. % this line is added

As the value of A is determined A #> 3 is not true, but what about B? Is that equal 3 or greater than 3? As we said, the condition part will be executed! Hence, the last constraint on B is B #> 3. And besides the execution of write("expression 1"), the result is:

A = 3
B = B{4 .. 10}
Yes (0.00s cpu)

The last example is:

?- [A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
    write("expression 1")
;
    write("expression 2")
),
A = 3,
B = 3. % this line is added

Also in this example expression 1 is printed and the result is:

No (0.00s cpu)

It's because of the fact that the head of the arrow and always expression 1 will be executed.

One solution is using ; operator likes the following:

[A,B] #:: 1..10, 
(
    (A = 3, B = 3, write('expression 21'))
    ; 
    (A = 3, B #> 3, write('expression 11'))
    ; 
    (A #> 3, B #> 3, write('expression 12'))
    ; 
    (A #> 3, B = 3, write('expression 13'))
), 
A = 3,
B = 5.

The result of above code is:

A = 3
B = 5
Yes (0.00s cpu, solution 1, maybe more)

And it prints:

expression 21 expression 11

It means the first branch goes down, but it failed and backtraced automatically, and goes to the next case! and as the next case is applied, everything GOES successful there!



来源:https://stackoverflow.com/questions/56111688/how-integer-suspension-will-be-handled-when-it-is-used-in-head-of-a-condition

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!