简体   繁体   English

优化的CLP(FD)求解器,可解决数字拼图难题

[英]Optimized CLP(FD) solver for number board puzzle

Consider the problem from https://puzzling.stackexchange.com/questions/20238/explore-the-square-with-100-hops : 考虑来自https://puzzling.stackexchange.com/questions/20238/explore-the-square-with-100-hops的问题:

Given a grid of 10x10 squares, your task is to visit every square exactly once. 给定一个10x10正方形的网格,您的任务是恰好访问每个正方形一次。 In each step, you may 在每一步中,您都可以

  • skip 2 squares horizontally or vertically or 水平或垂直跳过2个正方形,或
  • skip 1 square diagonally 对角跳1平方

In other words (closer to my implementation below), label a 10x10 grid with numbers from 1 to 100 such that each square at coordinates (X, Y) is 1 or is equal to one more than the "previous" square at (X, Y-3) , (X, Y+3) , (X-3, Y) , (X+3, Y) , (X-2, Y-2) , (X-2, Y+2) , (X+2, Y-2) , or (X+2, Y+2) . 换句话说(更接近我的下方实作),将10x10的网格标记为1到100之间的数字,以使坐标(X, Y)上的每个正方形等于1或等于(X, Y-3)(X, Y+3)(X-3, Y)(X+3, Y)(X-2, Y-2)(X-2, Y+2)(X+2, Y-2)(X+2, Y+2)

This looks like a straightforward constraint programming problem, and Z3 can solve it in 30 seconds from a simple declarative specification: https://twitter.com/johnregehr/status/1070674916603822081 这看起来像一个简单的约束编程问题,Z3可以在30秒内通过一个简单的声明性规范解决它: https : //twitter.com/johnregehr/status/1070674916603822081

My implementation in SWI-Prolog using CLP(FD) does not scale quite as nicely. 我在SWI-Prolog中使用CLP(FD)的实现扩展得不太好。 In fact, it cannot even solve the 5x5 instance of the problem unless almost two rows are pre-specified: 实际上,除非预先指定了几乎两行,否则它甚至无法解决问题的5x5实例:

?- number_puzzle_(_Square, Vars), Vars = [1,24,14,2,25, 16,21,5,8,20 |_], time(once(labeling([], Vars))).
% 10,063,059 inferences, 1.420 CPU in 1.420 seconds (100% CPU, 7087044 Lips)
_Square = square(row(1, 24, 14, 2, 25), row(16, 21, 5, 8, 20), row(13, 10, 18, 23, 11), row(4, 7, 15, 3, 6), row(17, 22, 12, 9, 19)),
Vars = [1, 24, 14, 2, 25, 16, 21, 5, 8|...].

?- number_puzzle_(_Square, Vars), Vars = [1,24,14,2,25, 16,21,5,8,_ |_], time(once(labeling([], Vars))).
% 170,179,147 inferences, 24.152 CPU in 24.153 seconds (100% CPU, 7046177 Lips)
_Square = square(row(1, 24, 14, 2, 25), row(16, 21, 5, 8, 20), row(13, 10, 18, 23, 11), row(4, 7, 15, 3, 6), row(17, 22, 12, 9, 19)),
Vars = [1, 24, 14, 2, 25, 16, 21, 5, 8|...].

?- number_puzzle_(_Square, Vars), Vars = [1,24,14,2,25, 16,21,5,_,_ |_], time(once(labeling([], Vars))).
% 385,799,962 inferences, 54.939 CPU in 54.940 seconds (100% CPU, 7022377 Lips)
_Square = square(row(1, 24, 14, 2, 25), row(16, 21, 5, 8, 20), row(13, 10, 18, 23, 11), row(4, 7, 15, 3, 6), row(17, 22, 12, 9, 19)),
Vars = [1, 24, 14, 2, 25, 16, 21, 5, 8|...].

(This is on an oldish machine with SWI-Prolog 6.0.0. On a newer machine with SWI-Prolog 7.2.3 it runs about twice as fast, but that's not enough to beat the apparent exponential complexity.) (这是在具有SWI-Prolog 6.0.0的旧机器上。在具有SWI-Prolog 7.2.3的较新机器上,它的运行速度大约是它的两倍,但这还不足以克服明显的指数复杂性。)

The partial solution used here is from https://www.nurkiewicz.com/2018/09/brute-forcing-seemingly-simple-number.html 此处使用的部分解决方案来自https://www.nurkiewicz.com/2018/09/brute-forcing-seemingly-simple-number.html

So, my question: How can I speed up the following CLP(FD) program? 所以,我的问题是: 如何加快以下CLP(FD)程序的速度?

Additional question for extra thanks: Is there a specific labeling parameter that speeds up this search significantly, and if so, how could I make an educated guess at which one it might be? 还要特别感谢的另一个问题: 是否有一个特定的标记参数可以显着加快搜索速度,如果可以,那么我如何做出有根据的猜测呢?

:- use_module(library(clpfd)).

% width of the square board
n(5).


% set up a term square(row(...), ..., row(...))
square(Square, N) :-
    length(Rows, N),
    maplist(row(N), Rows),
    Square =.. [square | Rows].

row(N, Row) :-
    functor(Row, row, N).


% Entry is the entry at 1-based coordinates (X, Y) on the board. Fails if X
% or Y is an invalid coordinate.
square_coords_entry(Square, (X, Y), Entry) :-
    n(N),
    0 < Y, Y =< N,
    arg(Y, Square, Row),
    0 < X, X =< N,
    arg(X, Row, Entry).


% Constraint is a CLP(FD) constraint term relating variable Var and the
% previous variable at coordinates (X, Y). X and Y may be arithmetic
% expressions. If X or Y is an invalid coordinate, this predicate succeeds
% with a trivially false Constraint.
square_var_coords_constraint(Square, Var, (X, Y), Constraint) :-
    XValue is X,
    YValue is Y,
    (   square_coords_entry(Square, (XValue, YValue), PrevVar)
    ->  Constraint = (Var #= PrevVar + 1)
    ;   Constraint = (0 #= 1) ).


% Compute and post constraints for variable Var at coordinates (X, Y) on the
% board. The computed constraint expresses that Var is 1, or it is one more
% than a variable located three steps in one of the cardinal directions or
% two steps along a diagonal.
constrain_entry(Var, Square, X, Y) :-
    square_var_coords_constraint(Square, Var, (X - 3, Y), C1),
    square_var_coords_constraint(Square, Var, (X + 3, Y), C2),
    square_var_coords_constraint(Square, Var, (X, Y - 3), C3),
    square_var_coords_constraint(Square, Var, (X, Y + 3), C4),
    square_var_coords_constraint(Square, Var, (X - 2, Y - 2), C5),
    square_var_coords_constraint(Square, Var, (X + 2, Y - 2), C6),
    square_var_coords_constraint(Square, Var, (X - 2, Y + 2), C7),
    square_var_coords_constraint(Square, Var, (X + 2, Y + 2), C8),
    Var #= 1 #\/ C1 #\/ C2 #\/ C3 #\/ C4 #\/ C5 #\/ C6 #\/ C7 #\/ C8.


% Compute and post constraints for the entire board.
constrain_square(Square) :-
    n(N),
    findall(I, between(1, N, I), RowIndices),
    maplist(constrain_row(Square), RowIndices).

constrain_row(Square, Y) :-
    arg(Y, Square, Row),
    Row =.. [row | Entries],
    constrain_entries(Entries, Square, 1, Y).

constrain_entries([], _Square, _X, _Y).
constrain_entries([E|Es], Square, X, Y) :-
    constrain_entry(E, Square, X, Y),
    X1 is X + 1,
    constrain_entries(Es, Square, X1, Y).


% The core relation: Square is a puzzle board, Vars a list of all the
% entries on the board in row-major order.
number_puzzle_(Square, Vars) :-
    n(N),
    square(Square, N),
    constrain_square(Square),
    term_variables(Square, Vars),
    Limit is N * N,
    Vars ins 1..Limit,
    all_different(Vars).

First of all: 首先:

What is going on here? 这里发生了什么?

To see what is happening, here are PostScript definitions that let us visualize the search: 要查看正在发生的事情,以下是PostScript定义,这些定义使我们可以可视化搜索:

/n 5 def

340 n div dup scale
-0.9 0.1 translate % leave room for line strokes

/Palatino-Roman 0.8 selectfont

/coords { n exch sub translate } bind def

/num { 3 1 roll gsave coords 0.5 0.2 translate
    5 string cvs dup stringwidth pop -2 div 0 moveto show
    grestore } bind def

/clr { gsave coords 1 setgray 0 0 1 1 4 copy rectfill
     0 setgray 0.02 setlinewidth rectstroke grestore} bind def

1 1 n { 1 1 n { 1 index clr } for pop } for

These definitions give you two procedures: 这些定义为您提供了两个过程:

  • clr to clear a square clr清除方
  • num to show a number on a square. num以在正方形上显示数字。

For example, if you save these definitions to tour.ps and then invoke the PostScript interpreter Ghostscript with: 例如,如果将这些定义保存到tour.ps ,然后使用以下命令调用PostScript解释器Ghostscript

gs -r72 -g350x350 tour.ps

and then enter the following instructions: 然后输入以下说明:

1 2 3 num
1 2 clr
2 3 4 num

you get: 你得到:

PostScript示例说明

PostScript is a great programming language for visualizing search processes, and I also recommend to check out for more information. PostScript是一种出色的编程语言,可用于可视化搜索过程,我还建议您查看以获取更多信息。

We can easily modify your program to emit suitable PostScript instructions that let us directly observe the search. 我们可以轻松地修改您的程序,以发出合适的PostScript指令,让我们直接观察搜索。 I highlight the relevant additions: 我重点介绍了相关补充:

constrain_entries([], _Square, _X, _Y).
constrain_entries([E|Es], Square, X, Y) :-
    freeze(E, postscript(X, Y, E)),
    constrain_entry(E, Square, X, Y),
    X1 #= X + 1,
    constrain_entries(Es, Square, X1, Y).

postscript(X, Y, N) :- format("~w ~w ~w num\n", [X,Y,N]).
postscript(X, Y, _) :- format("~w ~w clr\n", [X,Y]), false.

I have also taken the liberty to change (is)/2 to (#=)/2 to make the program more general. 我还自由地将(is)/2更改为(#=)/2以使程序更通用。

Assuming that you saved the PostScript definitions in tour.ps and your Prolog program in tour.pl , the following invocation of SWI-Prolog and Ghostscript illustrates the situation: 假设将PostScript定义保存在tour.ps ,将Prolog程序保存在tour.pl ,则以下SWI-Prolog和Ghostscript调用说明了这种情况:

swipl -g "number_puzzle_(_, Vs), label(Vs)" tour.pl | gs -g350x350 -r72 tour.ps -dNOPROMPT

For example, we see a lot of backtracking at the highlighted position: 例如,我们在突出显示的位置看到很多回溯:

脱粒图

However, essential problems already lie completely elsewhere: 但是,基本问题已经完全存在于其他地方:

抛出原因

None of the highlighted squares are valid moves! 突出显示的方块都不是有效的移动!

From this, we see that your current formulation does not—at least not sufficiently early—let the solver recognize when a partial assignment cannot be completed to a solution! 由此可见,您的当前公式并没有(至少还不是很早)让求解器识别出何时无法完成对解决方案的部分分配! This is bad news , since failure to recognize inconsistent assignments often leads to unacceptable performance. 这是个坏消息 ,因为无法识别不一致的作业通常会导致无法接受的性能。 For example, in order to correct the 1 → 3 transition (which can never occur in this way, yet is already one of the first choices made in this case), the solver would have to backtrack over approximately 8 squares, after enumerating—as a very rough estimate—25 8 = 152587890625 partial solutions, and then start all over at only the second position in the board. 例如,为了纠正1→3过渡(这种过渡永远不会发生,但在这种情况下已经是首选选择之一),求解器在枚举后必须回溯大约8个正方形。一个非常粗略的估计-25 8 = 152587890625部分解决方案,然后仅从电路板的第二个位置重新开始。

In the constraint literature, such backtracking is called thrashing . 在约束性文献中,这种回溯称为“ 抖动” It means repeated failure due to the same reason . 这意味着由于相同原因而反复失败。

How is this possible? 这怎么可能? Your model seems to be correct, and can be used to detect solutions. 您的模型似乎是正确的,可用于检测解决方案。 That's good! 那很好! However, a good constraint formulation not only recognizes solutions, but also quickly detects partial assignments that cannot be completed to solutions. 但是,良好的约束条件公式不仅可以识别解决方案,而且可以快速检测 无法完成的部分分配。 This is what allows the solver to effectively prune the search, and it is in this important respect that your current formulation falls short. 这就是使求解程序能够有效地简化搜索的原因,并且在这一重要方面,您当前的公式还很欠缺。 One of the reasons for this has to do with constraint propagation in reified constraints that you are using. 造成这种情况的原因之一与正在使用的已约束化约束中的约束传播有关。 In particular, consider the following query: 特别是,请考虑以下查询:

?- (X + 1 #= 3) #<==> B, X #\= 2.

Intuitively, we expect B = 0 . 直观地,我们期望B = 0 But that is not the case! 但事实并非如此! Instead, we get: 相反,我们得到:

X in inf..1\/3..sup,
X+1#=_3840,
_3840#=3#B,
B in 0..1.

So, the solver does not propagate reified equality very strongly. 因此,求解器不会非常强烈地传播化等式。 Maybe it should though! 也许应该吧! Only sufficient feedback from Prolog practitioners will tell whether this area of the constraint solver should be changed, possibly trading a bit of speed for stronger propagation. 只有Prolog从业人员的充分反馈将告诉您是否应该更改约束求解器的该区域,可能会牺牲一点速度来实现更强的传播。 The high relevance of this feedback is one of the reasons why I recommend to use CLP(FD) constraints whenever you have the opportunity, ie, every time you are reasoning about integers . 此反馈的高度相关性是我建议在有机会时(即,每次对整数进行推理时)都使用CLP(FD)约束的原因之一。

For this particular case, I can tell you that making the solver stronger in this sense does not make that much of a difference. 对于这种特殊情况,我可以告诉您,从这种意义上说,使求解器更强大不会带来太大的改变。 You essentially end up with a version of the board where the core issue still arises, with many transitions (some of them highlighted below) that cannot occur in any solution: 从本质上讲,您最终得到的是仍然存在核心问题的电路板版本,其中有许多转换(在下面的其中一些突出显示)在任何解决方案中都不可能发生:

还具有域一致性

Fixing the core issue 解决核心问题

We should eliminate the cause of the backtracking at its core . 我们应该在其核心消除回溯的原因。 To prune the search, we must recognize inconsistent (partial) assignments earlier. 要修剪搜索,我们必须早些识别不一致的(部分)分配。

Intuitively, we are searching for a connected tour , and want to backtrack as soon as it is clear that the tour cannot be continued in the intended way. 直观地,我们正在搜索关联的游览 ,并且希望在明显无法按预期方式继续游览时回溯。

To accomplish what we want, we have at least two options: 为了完成我们想要的,我们至少有两个选择:

  1. change the allocation strategy to take connectedness into account 更改分配策略以考虑连通性
  2. model the problem in such a way that connectedness is more strongly taken into account. 以更紧密地考虑连通性的方式对问题进行建模。

Option 1: Allocation strategy 选项1:分配策略

A major attraction of CLP(FD) constraints is that they let us decouple the task description from the search. CLP(FD)约束的主要吸引力在于,它们使我们可以任务描述与搜索分离。 When using CLP(FD) constraints, we often perform search via label/1 or labeling/2 . 使用CLP(FD)约束时,我们通常通过label/1labeling/2进行搜索。 However, we are free to assign values to variables in any way we want . 但是,我们可以随意使用任意方式为变量赋值。 This is very easy if we follow—as you have done—the good practice of putting the "constraint posting" part into its own predicate, called the core relation . 如果我们按照您所做的那样遵循将“约束发布”部分放到称为核心关系的谓词中的良好做法,这将非常容易。

For example, here is a custom allocation strategy that makes sure that the tour remains connected at all times: 例如,这是一个自定义分配策略,可确保游览始终保持连接状态:

allocation(Vs) :-
        length(Vs, N),
        numlist(1, N, Ns),
        maplist(member_(Vs), Ns).

member_(Es, E) :- member(E, Es).

With this strategy, we get a solution for the 5×5 instance from scratch: 通过这种策略,我们可以从头开始获得5×5实例的解决方案:

?- number_puzzle_(Square, Vars), time(allocation(Vars)).
% 5,030,522 inferences, 0.907 CPU in 0.913 seconds (99% CPU, 5549133 Lips)
Square = square(row(1, 8, 5, 2, 11), ...),
Vars = [1, 8, 5, 2, 11, 16, 21, 24, 15|...]

There are various modifications of this strategy that are worth trying out. 此策略有多种修改值得尝试。 For example, when multiple squares are admissible, we could try to make a more intelligent choice by taking into account the number of remaining domain elements of the squares. 例如,当允许使用多个正方形时,我们可以尝试通过考虑正方形的剩余域元素数来做出更明智的选择。 I leave trying such improvements as a challenge. 我将尝试改进作为挑战。

From the standard labeling strategies, the min labeling option is in effect quite similar to this strategy in this case, and indeed it also finds a solution for the 5×5 case: 从标准标记策略来看,在这种情况下, min标记选项实际上与该策略非常相似,实际上,它还为5×5情况找到了解决方案:

?- number_puzzle_(Square, Vars), time(labeling([min], Vars)).
% 22,461,798 inferences, 4.142 CPU in 4.174 seconds (99% CPU, 5422765 Lips)
Square = square(row(1, 8, 5, 2, 11), ...),
Vars = [1, 8, 5, 2, 11, 16, 21, 24, 15|...] .

However, even a fitting allocation strategy cannot fully compensate weak constraint propagation. 但是,即使是拟合分配策略也无法完全补偿弱约束传播。 For the 10×10 instance, the board looks like this after some search with the min option: 对于10×10的实例,在使用min选项进行一些搜索之后,木板看起来像这样:

使用标签选项<code> min </ code>进行脱粒

Note that we also have to adapt the value of n in the PostScript code to visualize this as intended. 请注意,我们还必须调整PostScript代码中的n值,以按预期将其可视化。

Ideally, we should formulate the task in such a way that we benefit from strong propagation, and then also use a good allocation strategy. 理想情况下,我们应制定以这样的方式,我们从强大的传播中受益的任务,然后还要使用一个很好的分配策略。

Option 2: Remodeling 选项2:重塑

A good CLP formulation propagates as strongly as possible (in acceptable time). 良好的CLP配方应尽可能强地传播(在可接受的时间内)。 We should therefore strive to use constraints that allow the solver to reason more readily about the task's most important requirements. 因此,我们应该努力使用约束,这些约束使求解器可以更轻松地推理任务的最重要要求。 In this concrete case, it means that we should try to find a more suitable formulation for what is currently expressed as a disjunction of reified constraints that, as shown above, do not allow much propagation. 在这种具体情况下,这意味着我们应该尝试为目前表示为修正约束的析取关系找到更合适的公式,如上所述,该约束不允许太多的传播。 In theory, the constraint solver could recognize such patterns automatically. 从理论上讲,约束求解器可以自动识别这种模式。 However, that is impractical for many use cases, and we therefore must sometimes experiment by manually trying several promising formulations. 但是,这对于许多用例来说是不切实际的,因此我们有时必须通过手动尝试几种有希望的配方进行试验。 Still, also in this case: With sufficient feedback from application programmers, such cases are more likely to be improved and worked on! 尽管如此,在这种情况下:在有来自应用程序程序员的充分反馈的情况下,这种情况更有可能得到改进和解决!

I now use the CLP(FD) constraint circuit/1 to make clear that we are looking for a Hamiltonian circuit in a particular graph. 现在,我使用CLP(FD)约束circuit/1来表明我们正在寻找特定图形中的哈密​​顿电路 The graph is expressed as a list of integer variables, where each element denotes the position of its successor in the list. 该图表示为整数变量列表,其中每个元素表示其后继元素在列表中的位置。

For example, a list with 3 elements admits precisely 2 Hamiltonian circuits: 例如,一个包含3个元素的列表正好允许2个哈密顿回路:

?- Vs = [_,_,_], circuit(Vs), label(Vs).
Vs = [2, 3, 1] ;
Vs = [3, 1, 2].

I use circuit/1 to describe solutions that are also closed tours . 我使用circuit/1来描述也是封闭式游览的解决方案。 This means that, if we find such a solution, then we can start again from the beginning via a valid move from the last square in the found tour: 这意味着,如果我们找到了这样的解决方案,那么我们可以通过从找到的游览中的最后一个广场开始的有效移动,从头开始重新开始:

n_tour(N, Vs) :-
        L #= N*N,
        length(Vs, L),
        successors(Vs, N, 1),
        circuit(Vs).

successors([], _, _).
successors([V|Vs], N, K0) :-
        findall(Num, n_k_next(N, K0, Num), [Next|Nexts]),
        foldl(num_to_dom, Nexts, Next, Dom),
        V in Dom,
        K1 #= K0 + 1,
        successors(Vs, N, K1).

num_to_dom(N, D0, D0\/N).

n_x_y_k(N, X, Y, K) :- [X,Y] ins 1..N, K #= N*(Y-1) + X.

n_k_next(N, K, Next) :-
        n_x_y_k(N, X0, Y0, K),
        (   [DX,DY] ins -2 \/ 2
        ;   [DX,DY] ins -3 \/ 0 \/ 3,
            abs(DX) + abs(DY) #= 3
        ),
        [X,Y] ins 1..N,
        X #= X0 + DX,
        Y #= Y0 + DY,
        n_x_y_k(N, X, Y, Next),
        label([DX,DY]).

Note how admissible successors are now expressed as domain elements , reducing the number of constraints and entirely eliminating the need for reifications. 请注意,现在如何将可接受的后继者表示为领域元素 ,从而减少了约束数量并完全消除了对版本化的需求。 Most importantly, the intended connectedness is now automatically taken into account and enforced at every point during the search. 最重要的是,现在将自动考虑预期的连接性,并在搜索过程中的所有时间点都将其强制执行。 The predicate n_x_y_k/4 relates (X,Y) coordinates to list indices. 谓词n_x_y_k/4(X,Y)坐标与列表索引相关联。 You can easily adapt this program to other tasks (eg, knight's tour) by changing n_k_next/3 . 您可以通过更改n_k_next/3轻松地使该程序适应其他任务(例如,骑士之旅)。 I leave the generalization to open tours as a challenge. 我将归纳归结为公开旅行是一个挑战。

Here are additional definitions that let us print solutions in a more readable form: 这是使我们以更易读的形式打印解决方案的其他定义:

:- set_prolog_flag(double_quotes, chars).

print_tour(Vs) :-
        length(Vs, L),
        L #= N*N, N #> 0,
        length(Ts, N),
        tour_enumeration(Vs, N, Es),
        phrase(format_string(Ts, 0, 4), Fs),
        maplist(format(Fs), Es).

format_(Fs, Args, Xs0, Xs) :- format(chars(Xs0,Xs), Fs, Args).

format_string([], _, _) --> "\n".
format_string([_|Rest], N0, I) -->
        { N #= N0 + I },
        "~t~w~", call(format_("~w|", [N])),
        format_string(Rest, N, I).

tour_enumeration(Vs, N, Es) :-
        length(Es, N),
        maplist(same_length(Es), Es),
        append(Es, Ls),
        foldl(vs_enumeration(Vs, Ls), Vs, 1-1, _).

vs_enumeration(Vs, Ls, _, V0-E0, V-E) :-
        E #= E0 + 1,
        nth1(V0, Ls, E0),
        nth1(V0, Vs, V).

In formulations with strong propagation, the predefined ff search strategy is often a good strategy. 在具有强传播性的公式中,预定义的ff搜索策略通常是一个很好的策略。 And indeed it lets us solve the whole task, ie, the original 10×10 instance, within a few seconds on a commodity machine: 实际上,它可以让我们在商用机器上几秒钟内解决整个任务,即原始的10×10实例:

?- n_tour(10, Vs),
   time(labeling([ff], Vs)),
   print_tour(Vs).
% 5,642,756 inferences, 0.988 CPU in 0.996 seconds (99% CPU, 5710827 Lips)
   1  96  15   2  97  14  80  98  13  79
  93  29  68  94  30  27  34  31  26  35
  16   3 100  17   4  99  12   9  81  45
  69  95  92  28  67  32  25  36  33  78
  84  18   5  83  19  10  82  46  11   8
  91  23  70  63  24  37  66  49  38  44
  72  88  20  73   6  47  76   7  41  77
  85  62  53  86  61  64  39  58  65  50
  90  22  71  89  21  74  42  48  75  43
  54  87  60  55  52  59  56  51  40  57
Vs = [4, 5, 21, 22, 8, 3, 29, 26, 6|...]

For utmost performance, I recommend you also try this with other Prolog systems. 为了获得最佳性能,我建议您也将其与其他Prolog系统一起尝试。 The efficiency of commercial-grade CLP(FD) systems is often an important reason for buying a Prolog system. 商业级CLP(FD)系统的效率通常是购买Prolog系统的重要原因。

Note also that this is by no means the only promising Prolog or even CLP(FD) formulation of the task, and I leave thinking about other formulations as a challenge. 还要注意,这绝不是该任务唯一有希望的Prolog或CLP(FD)公式,我将其他公式视为挑战。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM