在条件头中使用时如何处理整数暂停
How integer suspension will be handled when it is used in head of a condition
我对两个变量 A
和 B
有以下条件:
[A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
% expression 1
;
% expression 2
)
%cntd
问题在第2行,求解器不知道A
和B
的值,如何在不指定第 2 行的变量?
合理的做法是在求解器遍历变量的可能值时,根据变量的值决定该分支。
但是,正如我发现的那样,它在知道变量的值之前会经过其中一个表达式。防止这种情况的解决方案是什么?
有什么问题?
重要的注意事项是对 if-else 使用 ->
(箭头)。当我们有表达式 S -> T ; U
时,S
将被计算,如果它包含一些变量可以在代码上有一些 side-effects。为了更清楚,尝试运行一些例子:
?-[A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
write("expression 1")
;
write("expression 2")
).
由于A
和B
的值不确定,条件总是为真,会打印expression 1
的值。另外,结果是:
A = A{1 .. 10}
B = B{1 .. 10}
There are 6 delayed goals.
Yes (0.00s cpu)
如您所见,A
和 B
的边界不会改变,因为它暂停到未来的表达式,而我们没有,边界不会改变。
现在试试不同的例子:
?- [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
由于A
的值被确定A #> 3
是不正确的,那么B
呢?那是等于 3 还是大于 3?正如我们所说,条件部分将被执行!因此,B
的最后一个约束是 B #> 3
。而除了write("expression 1")
的执行,结果是:
A = 3
B = B{4 .. 10}
Yes (0.00s cpu)
最后一个例子是:
?- [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
同样在这个例子中打印了expression 1
,结果是:
No (0.00s cpu)
是因为箭头总是expression 1
会被执行。
一个解决方案 正在使用 ;
运算符喜欢以下内容:
[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.
以上代码的结果是:
A = 3
B = 5
Yes (0.00s cpu, solution 1, maybe more)
并打印:
expression 21 expression 11
表示第一个分支down了,但是失败了自动回溯,进入下一个case!当应用下一个案例时,一切都成功了!
只要您坚持纯逻辑,约束编程就非常适合 Prolog。但是,正如您所展示的那样,不能自由地将 cut (!) 和 if-then-else (->;) 等程序元素与约束逻辑。
仅当条件在 "constraint setup time" 处包含(即无条件为真)或不包含(无条件为假)时,使用 if-then-else 或削减才是安全的。实际上,这意味着此类条件不应包含 问题变量 (域变量等),而仅包含先验已知的 问题参数 (常量) .专用建模语言区分了这两件事,但 Prolog 没有。
如何在约束模型中不表达备选方案
上面的意思是你不能用cut/if-then-else来表达你想表达的那种选择。简单地摆脱条件的 committed-choice 方面并重写为纯析取可能很诱人。例如,您可以重写
( 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 通过回溯探索备选方案(使用分号 (;) 或多个子句表示),即首先急切地选择一个备选方案,然后再返回另一个备选方案。这通常会毁掉任何有效约束程序的希望!在约束程序中,所有搜索都应位于 search/labeling 例程中。
具体化约束
如果条件和备选方案的分支都是具有具体实现的约束(即可以在布尔变量中反映约束真实性的实现),那么你很幸运:你可以重写整个借助具体化约束的特殊连接词的条件选择(在 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
很遗憾,只有基本的算术约束有具体化的版本,可以这样组合!
使用其他 built-in 约束
在某种程度上,处理备选方案是解决问题中最困难的部分,许多 built-in 约束解决了这个问题。因此,值得检查问题是否可以在现有 built-in 约束 之上建模,而 在模型中没有任何明确的分离。候选人是
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 有一个很棒的功能叫做 Generalised Propagation
library(propia)。这可以通过使用简单的注释有效地将 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
的域所示,有用的信息已经从析取中提取出来,甚至在决定适用的替代方案之前。
我对两个变量 A
和 B
有以下条件:
[A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
% expression 1
;
% expression 2
)
%cntd
问题在第2行,求解器不知道A
和B
的值,如何在不指定第 2 行的变量?
合理的做法是在求解器遍历变量的可能值时,根据变量的值决定该分支。 但是,正如我发现的那样,它在知道变量的值之前会经过其中一个表达式。防止这种情况的解决方案是什么?
有什么问题?
重要的注意事项是对 if-else 使用 ->
(箭头)。当我们有表达式 S -> T ; U
时,S
将被计算,如果它包含一些变量可以在代码上有一些 side-effects。为了更清楚,尝试运行一些例子:
?-[A,B] #:: 1..10,
(A #= 3) or (B #= 3),
((A #> 3 or B #>3) ->
write("expression 1")
;
write("expression 2")
).
由于A
和B
的值不确定,条件总是为真,会打印expression 1
的值。另外,结果是:
A = A{1 .. 10}
B = B{1 .. 10}
There are 6 delayed goals.
Yes (0.00s cpu)
如您所见,A
和 B
的边界不会改变,因为它暂停到未来的表达式,而我们没有,边界不会改变。
现在试试不同的例子:
?- [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
由于A
的值被确定A #> 3
是不正确的,那么B
呢?那是等于 3 还是大于 3?正如我们所说,条件部分将被执行!因此,B
的最后一个约束是 B #> 3
。而除了write("expression 1")
的执行,结果是:
A = 3
B = B{4 .. 10}
Yes (0.00s cpu)
最后一个例子是:
?- [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
同样在这个例子中打印了expression 1
,结果是:
No (0.00s cpu)
是因为箭头总是expression 1
会被执行。
一个解决方案 正在使用 ;
运算符喜欢以下内容:
[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.
以上代码的结果是:
A = 3
B = 5
Yes (0.00s cpu, solution 1, maybe more)
并打印:
expression 21 expression 11
表示第一个分支down了,但是失败了自动回溯,进入下一个case!当应用下一个案例时,一切都成功了!
只要您坚持纯逻辑,约束编程就非常适合 Prolog。但是,正如您所展示的那样,不能自由地将 cut (!) 和 if-then-else (->;) 等程序元素与约束逻辑。
仅当条件在 "constraint setup time" 处包含(即无条件为真)或不包含(无条件为假)时,使用 if-then-else 或削减才是安全的。实际上,这意味着此类条件不应包含 问题变量 (域变量等),而仅包含先验已知的 问题参数 (常量) .专用建模语言区分了这两件事,但 Prolog 没有。
如何在约束模型中不表达备选方案
上面的意思是你不能用cut/if-then-else来表达你想表达的那种选择。简单地摆脱条件的 committed-choice 方面并重写为纯析取可能很诱人。例如,您可以重写
( 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 通过回溯探索备选方案(使用分号 (;) 或多个子句表示),即首先急切地选择一个备选方案,然后再返回另一个备选方案。这通常会毁掉任何有效约束程序的希望!在约束程序中,所有搜索都应位于 search/labeling 例程中。
具体化约束
如果条件和备选方案的分支都是具有具体实现的约束(即可以在布尔变量中反映约束真实性的实现),那么你很幸运:你可以重写整个借助具体化约束的特殊连接词的条件选择(在 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
很遗憾,只有基本的算术约束有具体化的版本,可以这样组合!
使用其他 built-in 约束
在某种程度上,处理备选方案是解决问题中最困难的部分,许多 built-in 约束解决了这个问题。因此,值得检查问题是否可以在现有 built-in 约束 之上建模,而 在模型中没有任何明确的分离。候选人是 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 有一个很棒的功能叫做 Generalised Propagation library(propia)。这可以通过使用简单的注释有效地将 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
的域所示,有用的信息已经从析取中提取出来,甚至在决定适用的替代方案之前。