只要您坚持纯逻辑,约束编程就非常适合 Prolog。但是,正如您所展示的,人们不能自由地将诸如cut (!)和if-then-else (->;)之类的程序元素与约束逻辑混合。
if-then-else 或cuts 的使用仅在条件在“约束设置时间”被蕴含(即无条件地为真)或解除(无条件地为假)时才是安全的。实际上,这意味着此类条件不应包含问题变量(域变量等),而应仅包含先验已知的问题参数(常数)。专用的建模语言区分了这两件事,但 Prolog 没有。
如何不在约束模型中表达替代方案
以上意味着您不能使用 cut/if-then-else 来表达您想要表达的替代方案。简单地摆脱条件的承诺选择方面,并将其重写为纯析取可能很诱人。例如,您可以重写
( Usage #>= 1000 -> Rate#=7, Bonus#=100 % WRONG
; Rate#=9, Bonus#=0
)
作为纯粹的析取
( Usage #>= 1000, Rate#=7, Bonus#=100 % INEFFICIENT
; Usage #< 1000, Rate#=9, Bonus#=0
)
虽然现在这在逻辑上是正确的,但不要这样做!Prolog 通过回溯探索备选方案(使用分号(;)或多个子句表示),即首先急切地选择一个备选方案,然后再返回另一个备选方案。这通常会破坏对有效约束程序的任何希望!在约束程序中,所有搜索都应位于搜索/标记例程中。
具体化约束
如果替代方案的条件和分支都是具有具体实现的约束(即可以在布尔变量中反映约束的真实性的实现),那么您很幸运:您可以在帮助下重写整个条件替代方案用于具体化约束的特殊连接词(在 ECLiPSe 中:and
, or
, neg
, =>
, #=
)。对于上面的例子:
Usage #>= 1000 => Rate#=7 and Bonus#=100, % OK
Usage #< 1000 => Rate#=9 and Bonus#=0
或者
Usage #>= 1000 and Rate#=7 and Bonus#=100 or % OK
Usage #< 1000 and Rate#=9 and Bonus#=0
不幸的是,只有基本的算术约束有具体化的版本,并且可以以这种方式组合!
使用其他内置约束
在某种程度上,处理备选方案是解决问题最困难的部分,许多内置约束解决了这个问题。因此值得检查问题是否可以在现有的内置约束之上建模,而在模型中没有任何显式析取。候选人是
element/3,table/2。disjunctive/2
和许多其他的。
延迟选择
最后的解决方案是推迟对替代方案的探索,直到可以明确确定条件的真相。在 ECLiPSe 中,使用延迟子句是最简单的。使用 OP 的示例:
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")
).
这有效,但只有在 A 和 B 都被实例化后才会起作用。如果在这种情况下,条件是可具体化的,我们可以做得更好:
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").
当域满足条件时,这将已经起作用:
?- choice(A, B), B #> 3.
expression 1
将一般析取变为约束
ECLiPSe在
library(propia)中有一个名为Generalized Propagation的漂亮功能。通过使用简单的注释,这可以有效地将 Prolog 析取转换为约束。从上面正确但低效的公式开始,我们可以写:
?- ( 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)
正如 和 的域Rate
所示Bonus
,甚至在可以决定适用的替代方案之前,已经从析取中提取了有用的信息。