译自http://www.pathwayslms.com/swipltuts/clpfd/clpfd.html#_simple_constraints,SWI-Prolog官网所推荐的进阶教程。目前还没译完,会不定期更新。

1. 关于本教程

1.1. 谁应该用这个教程

本教程适用于要用clp(fd)、有一定经验的SWI-Prolog程序员。
此外,对于用其他版本Prolog且要用clp(fd)的Prolog程序员,本教程也是有用的。
本教程的重点不在理论。作者不是数学家,而且本文可能对那些能通过学术文献找到材料的称职的数学家也没多少用处。
本教程将帮助你理解有限域上的约束系统的基础知识。
它包涵了一定的实践经验。
这篇教程大多数情况下是对SWI-Prolog的clp(fd)谓词文档的转述,由简明扼要的数学文档变为更平易近人的语言。

1.2. clp(fd)有什么用?

clp(fd)是标准SWI-Prolog发行版​​中包含的库。它解决涉及变量集的问题,其中变量之间的关系需要得到满足。
clp(fd)还可用于替代面向对象语言中由getter和setter提供的许多保护函数。例如,如果我们尚不知道一个人的实际身高,我们仍然可以断言其超过21英寸且不到108英寸(已知的最矮/最高)。 当我们最终找到解决方案时,不合理的值将被丢弃。
clp(fd)用于通过减少搜索空间来优化搜索问题。约束可以削减整个搜索空间的子树。
clp(fd)对解决各种查找变量的问题非常有用。这是一些clp(fd)可以解决的问题的广泛类别:

  • 安排问题,如应该在什么时候做什么工作来生产这些产品?
  • 优化问题,如应该在工厂生产哪种产品以获得最大利润?
  • 需求满足问题,如在医院中找到符合条件的房间,例如在康复室旁边的手术室,或是找到全家都赞成的度假方案。
  • 顺序问题,如找到我们前往目的地的路线。
  • 标识问题,如数独或密码算术问题。
  • 相信与意图问题,比如侦探谜题和游戏中的非玩家角色。
  • 分配问题,如分配飞机机组人员,使其满足工会的要求,并最大时间离家。
  • 为相关联的变量集找可接受的解决方案,如在一群嫉妒的少女中,找出谁会去参加聚会,如果B去了,A才会去;如果C去了,B就不会去,等等。
  • 为多个互相影响的变量创建可接受方案列表。如一个宴席筹备者可能希望看到宴席菜单满足宴席开销、厨师和侍者的日程、烤箱的安排等不同需求,然后自己选择采用哪种方案。
  • 图形的几何约束条件,如CAD系统中,其中两条线必须垂直,另外两条必须平行,其他两条线相距30英尺,依此类推。

clp(fd)的使用练习
为上面每个类别再举一个例子。尽量扩展问题的范围。例如,给定一组火车车厢,将它们按合理的顺序排列,使其遵守一些规则(例如,没有油罐车靠近引擎,守车必须在后面)就是个顺序问题。

1.3. 前提

掌握SWI-Prolog的基础知识。

数学水平与计算机科学本科课程的高等水平相当。

SWI-Prolog已安装并运行正常。

本教程没有附带代码文件。你可以剪贴示例。

1.4.你可以从本教程中学到什么

如果你读完此教程,并完成所有练习,你可以期待:

  • 了解约束编程的基本概念
  • 能编写查找未知值解决方案的程序
  • 能用clp(fd)库阐述现实世界的问题
  • 能对约束系统问题选用适当的解决方案
  • 了解如何在真正的SWI-Prolog的程序中使用clp(fd)

1.5. 完成时间

6-8小时,因人的数学水平而异。

2. clp(fd)库

SWI-Prolog的clp(fd)库是由Markus Triska为他的博士论文编写的。clp(fd)代表有限域上的约束逻辑式编程。
clp(fd)是SWI-Prolog中可用的几种约束求解器之一。其他的有:clp(b),clp(qr), clp(b)处理布尔值;clp(qr)处理有理数和实数。 CHR是用于创建自己的约束系统的工具(其他类型)。
clp(fd)库可以轻易地激活:

1 :- use_module(library(clpfd)).

这将安装个编译时的挂钩,可以优化一些clp(fd)约束,并加载运行时的库。

3. 变量

在计算机科学中,我们很宽松地使用“变量”这个术语。

3.1 替代变量

在命令式编程语言中,比如Java,变量是可变的。其值会随时间而改变。
在C语言中,像变量x:

int x;

... what about here? ...

x = 5;

3.2 逻辑变量

在逻辑式语言中,变量是不同的。它们更像高中代数中的变量或未知数。
在Prolog中,变量可以是绑定或非绑定的。非绑定的变量将与任何值结合。绑定的变量仅与一个模板统一。
实际上,我们要么知道,要么不知道变量的值。
当我们试图结合时,我们实际上是在问:“我们对左边和右边答案的理解是一致的吗?”鲍勃声称自己是主城市长的好友,如果鲍勃是《主城日报》的编辑,这很可能是可信的。如果鲍勃声称从未涉足主城,这就有些矛盾了。 在Prolog中,对于原子结果只有两种可能。完全不知道,或确切知道。显然,在列表或项中可能有非绑定的变量,但对于每个变量,只有这两种状态。

3.3. 约束变量

但在现实世界,我们可以讲出更多关于变量的内容。 我们可能不知道确切的值,但我们知道它是一组可能的值中之一。我们称之为域。
我们可能不知道具体值是什么,但可以说它其他一些我们同样不知道值的变量要大,我们说这个变量是受约束的。
随着我们开始积累约束条件,我们开始能对约束进行推理。假设我们有两个整数变量,X和Y。
现在我告诉你,X在0到10的范围内。
接着,Y在4到8的范围内。
最后,X大于Y。
你可以推断出,X在5到10这个更窄的范围内,因为Y可以具有的最小值是4,并且X必须比Y至少大1。
这便是clp(fd)的样子。如果你没完全理解这些,也不要担心。

:- use_module(library(clpfd)).         1

test(X, Y) :-
X in 0..10, 2
Y in 4..8, 3
X #> Y. 4 14 ?- test(X, Y).
X in 5..10, 5
Y#=<X+ -1, 6
Y in 4..8.

1 导入clp(fd)库
2 使用“in”运算符,约束X在0到10之间
3 使用“in”运算符,约束Y在4到8之间
4 使用“#>”运算符,约束X比Y大
5 现在X被约束为5到10之间
6 现在Y被约束为=< X - 1(同样是X>Y),且范围为4到8。
当我们将一个变量限制为单一值时,会发生一些神奇的事——我们现在已经知道了变量,因此可以绑定变量。在clp(fd)中,确实发生了这种情况!假设我将X约束在0到5的范围内,并且将Y约束在4到8的范围内,并且X> Y,那么突然X现在绑定到5了。ground(X)成功了,并且X是一个非常普通的绑定变量。

:- use_module(library(clpfd)).

test2(X, Y) :-
X in 0..5,
Y in 4..8,
X #> Y. 16 ?- test2(X,Y).
X = 5, 1
Y = 4. 2

1 X被绑定,就像在普通的Prolog中那样
2 注意,Y也被绑定了。此外,它失去了原先的约束。
编辑、运行练习
输入并尝试以上两个例子。使用调试器进入它们,以查看列出的约束。
回溯练习

:- use_module(library(clpfd)).

foo(X) :- X in 1..3 .
foo(X) :- X in 5..7 .
foo(X) :- X in 8..12 .

如果查询foo(X),并通过回溯得到所有解决方案,将会发生什么?预测会发生什么,并尝试它。你的预测正确吗?

3.4. 实现

SWI-Prolog能将属性添加到变量。这是附加到变量的附加元数据。如果你有兴趣,可以在SWI-Prolog网站上阅读有关属性变量的更多内容。
clp(fd)使用此属性数据修饰约束变量。然后clp(fd)实现了约束编程,并作为常规Prolog统一的扩展。
本教程结尾将对此作更多介绍。
属性练习

Extra credit -

考虑除了用于约束,变量属性的另一种用法。

3.5. 域

clp(fd)中的(fd)表示有限域。该域可以具有数百万个值,但它必须是一个有限列表。
我们只关心有限域的变量。就我们的目的而言,这意味我们要对整数集的域进行推理。
clp(fd)可用于[air, land, sea]这样的域,但优化效果不佳。映射air = 0,land = 1,sea = 2,然后说域是0..2,会更好。任何可能的有限列表都可以映射到整数的有限子集。任何有限的可能性列表,都可以映射到有限的整数子集中。
乍一看,这可能显得有些笨拙,但实际上与使用数组索引没什么不同。更大的问题可能是调试——整数列表可能毫无意义。编写一些调试代码以提取并输出数据,可能会有帮助。

4. 示例

我总是被那些毫无根据的讨论恼火。这是一个典型的约束程序,并包含各部分的简要说明。你可能还不了解所有部分。 “SEND + MORE = MONEY”密码算术问题是约束编程中的经典“hello world”。题目是将0到9数字分配给字母,使它们拼出SEND MORE MONEY,当以10为底地读取数字时,将形成真正的算式。数字不允许有前导0。

:- use_module(library(clpfd)).         1

puzzle([S,E,N,D] + [M,O,R,E] = [M,O,N,E,Y]) :-   2
Vars = [S,E,N,D,M,O,R,Y], 3
Vars ins 0..9, 4
all_different(Vars), 5
S*1000 + E*100 + N*10 + D + 6
M*1000 + O*100 + R*10 + E #=
M*10000 + O*1000 + N*100 + E*10 + Y,
M #\= 0, S #\= 0, 7
label(Vars). 8 9 ?- puzzle(X).
X = ([9, 5, 6, 7]+[1, 0, 8, 5]=[1, 0, 6, 5, 2]) ; 9
false.

1 导入clp(fd)库
2 注意,clp(fd)不像pce或准引用那样的嵌入式DSL。clp(fd)与Prolog一起使用,增加了主义。
3 为了方便起见,列出了我们要约束的所有变量
4 将Vars中的每个变量限制为0..9的范围(含)
5 添加约束条件,它们必须是不同的
6 添加定义问题的算术约束
7 单词以M和S打头,因此不能为0
8 尝试尽可能多地确定变量
9 注意,外面没有有趣的属性,只返回了解决方案
FortyTenTen练习
1)通过修改程序来解决密码算术FORTY + TEN + TEN = SIXTY 2)如果允许前导0,是否还有SEND+MORE=MONEY的解决方案?修改程序以找出答案。

5. 标签

附加约束很好,但在我们最终回到简单地绑定变量之前,它用处不大。有着所有固定变量的解称为固定解。
有一些次要的机制可以做到这一点。
如果将定义域缩减至单个值,则变量会被固定。
一般,你可以轻易地为变量赋值。通常,X = 3将变量限制为单个值。
谓词indomain/1会在回溯时,连续地将变量綁定到其可能的值。
通常,我们将找单个固定的解决方案称为label/1或labeling/2。label/1和labeling/2是不确定的,在每次回溯时,都绑定了一组不同的值。
当然,我们的约束系统可能不足以表达每个约束。如果是这样,我们必须依赖生成和测试搜索策略来熟悉所有Prolog程序员了。有了约束系统,生成和测试系统成为
1.约束
2.生成(通过labeling)
3.测试
实际上,大多数clp(fd)程序遵循约束后接标签的一般模式。
提示:设置模型,并在单独的谓词中执行标签和剩余的搜索是良好的做法。这使得在应用标签之前检查模型变得更加容易。
labeling/2有大量选项能影响变量选择策略。由于 clp(fd)程序花费的大部分时间通常是在labeling上,我们会在后面详细介绍这些内容。现在我们使用label/1,它有合理的默认值。
labeling/2的选项也用于最优化的问题。

6. 简单约束

clp(fd)提供一组基本、简单的约束。
请记住,clp(fd)与整数一起使用。

X #>= Y
X #=< Y
X #= Y
X #\= Y
X #> Y
X #< Y

提示
你可以使用#=作为is的声明性版本,否则不是clp(fd)。X is 3+Y使Y被固定,而X #= 3+Y,在X被固定、而Y不是的情况下起作用。
提示
X #< Y可以消除不必要的对称。比如,如果玩家1和3在锦标赛中已配对,再考虑3和1的配对是没意义的。请考虑以下取自SWI-Prolog文档的示例,它找出了使四个配对的所有方法:
示例1 消除对称

?- Vs = [A,B,C,D], Vs ins 1..4,
all_different(Vs),
A #< B, C #< D, A #< C,
findall(pair(A,B)-pair(C,D), label(Vs), Ms).
Ms = [ pair(1, 2)-pair(3, 4),
pair(1, 3)-pair(2, 4),
pair(1, 4)-pair(2, 3)].

严格递增练习
编写谓词increase/1,它接收一个列表,约束它严格递增。
首先记录你预计每个查询的结果。然后测试你的谓词是否符合你的预期。

7. 约束域

变量的域是它可以取的一组值。
在clp(fd)中,每个变量必须被限制到有限域,以进行推理。在尝试标记它们之前,你需要给变量赋予域。如果不是,你将得到令人困惑的异常,错误:参数未被充分实例化。

X in 5..10,   1
Y in 1..10, 2
Y #> X 3

7.1. In和Ins

你可以使用in和ins运算符来限制变量的域。
in和ins都设定右边的域。域是一个简单范围或几个简单范围用\/运算符形成的并集。
一个简单的域可以是单个数字或由双点相连的一对边界。边界可以是数字,或用于最小的inf,用于最大的sup。其中任何一个都可以是被固定的变量,比如N=3, X in 1..N .。

7.2. \/运算符

域可以和\/运算符并在一起。
并集

1..3 \/ 5..7

示例2 复杂域

V in inf.. -2 \/ 0 \/ 2..16 \/ 18..sup

in约束单个变量。
ins将一个变量列表约束为同样范围的值,相当于用in映射整个列表。

7.3. 来自数据的域

如果域来自输入数据呢?假设我们从某处得到一个数据结构,需要以某种方式对其约束。
将你的域汇集为项MyDomain,并在MyDomain中使用X。这有个例子
示例3 来自数据的域

% Bases is a list of ints. Constrain Var to be within B..B+2 for B
% a member of Bases two_domains(Bases, Var) :-
make_two_domains(Bases, Term),
Var in Term. make_two_domains([H], H..HT) :-
HT is H + 2.
make_two_domains([H | T], '\\/'(H .. HT, TDomain)) :-
HT is H + 2,
make_two_domains(T, TDomain). 25 ?- two_domains([1, 8, 16], V).
V in 1..3\/8..10\/16..18 ;

8. 算术运算符

你可以用算术约束来进行很多约束工作。

约束可以采用中缀算术表达式。

X + 2 #= Y * 3

可用的运算符有

    unary -,

    +,

    -,

    *,

    / (truncated integer division),

    ^ (exponentiation),

    min,

    max,

    mod,

    rem,

    abs

9. 逻辑运算符

9.1 #\ 否定

一元运算符。反转所包含的约束。

9.2 #/\ 与

同时约束两边。

9.3 #\/ 或

至少约束一边。

10. 具体化

具体化是通过约束来控制其他约束的过程。clp(fd)包括两个用于具体化的操作符。

10.1. #<==> 等价

每边是个布尔型变量或是个约束。调节约束,使它们都成立或都不成立。

10.2. #==> 蕴含

如果左边成立,则右边必须成立。如果左边不成立,则右边被忽略。
示例 一些具体化的约束
在化工厂有个反应容器。容器中的温度被限定为必须低于某个特定的值,并在不是启动模式的情况下,还要高于另一个值。

chem_tank(Temp, Startup) :-
Temp #< 800,
#\ Startup #==> Temp #> 300.
我们可以将启动模式定义在某个起始时间后的10分钟。
chem_demo(Temp, TimeNow, StartTime) :-
chem_tank(Temp, TimeNow - StartTime #< 10).

注意,StartTime是作为一个约束传入的。你可以构建一个谓词来应用约束和应用约束所适用的条件。
工作练习
编写一个人与工作相匹配的小系统。这些工作对教育水平、能举起的重量、年龄、离家距离都有要求。能够在个人记录中记录特殊约束条件,比如,鲍勃在假释期间,离家不能超过20英里。莎莉需要帮手,但她不喜欢那些不工作时到处闲逛的工人。她想找一个住在10英里以外的人帮忙。
人们都有些有趣的状况,所以尽量把它们一般化。
对于这个练习,你可以假设(不现实地)用户知道Prolog。

CLP(FD)有限域上的约束逻辑式编程的更多相关文章

  1. 逻辑式编程语言极简实现(使用C#) - 1. 逻辑式编程语言介绍

    相信很多朋友对于逻辑式编程语言,都有一种最熟悉的陌生人的感觉.一方面,平时在书籍.在资讯网站,偶尔能看到一些吹嘘逻辑式编程的话语.但另一方面,也没见过周围有人真正用到它(除了SQL). 遥记当时看&l ...

  2. 学习响应式编程 Reactor (1) - 响应式编程

    响应式编程 命令式编程(Imperative Programing),是一种描述计算机所需做出的行为的编程范式.详细的命令机器怎么(How)去处理以达到想要的结果(What). 声明式编程(Decla ...

  3. Atitit.编程语言的主要的种类and趋势 逻辑式语言..函数式语言...命令式语言

    Atitit.编程语言的主要的种类and趋势 逻辑式语言..函数式语言...命令式语言 1. 编程语言的主要的种类 逻辑式语言..函数式语言...命令式语言 1 2. 逻辑式语言,,不必考虑实现过程而 ...

  4. 逻辑式编程语言极简实现(使用C#) - 2. 一道逻辑题:谁是凶手

    本系列前面的文章: 逻辑式编程语言极简实现(使用C#) - 1. 逻辑式编程语言介绍 这是一道Prolog经典的练习题,中文翻译版来自阮一峰的文章<Prolog 语言入门教程>. 问题 B ...

  5. 逻辑式编程语言极简实现(使用C#) - 3. 运行原理

    本系列前面的文章: 逻辑式编程语言极简实现(使用C#) - 1. 逻辑式编程语言介绍 逻辑式编程语言极简实现(使用C#) - 2. 一道逻辑题:谁是凶手 第二天,好为人师的老明继续开讲他的私人课堂. ...

  6. 逻辑式编程语言极简实现(使用C#) - 4. 代码实现(完结)

    本文是本系列的完结篇.本系列前面的文章: 逻辑式编程语言极简实现(使用C#) - 1. 逻辑式编程语言介绍 逻辑式编程语言极简实现(使用C#) - 2. 一道逻辑题:谁是凶手 逻辑式编程语言极简实现( ...

  7. 我发起了一个 .Net 平台上的 产生式编程 开源项目 GP.Net

    大家好 , 我发起了一个 .Net 平台上的 产生式编程 开源项目 GP.Net . 我们可以先看看一个网友的 代码生成器 项目 : <.Net 代码生成器 for PostgreSql> ...

  8. oracle的约束隐式创建索引和先索引后约束的区别

    oracle的约束隐式创建索引和先索引后约束的区别 两种情况:1.对于创建约束时隐式创建的索引,在做删除操作的时候: 9i~11g都会连带删除该索引 2.对于先创建索引,再创建约束(使用到此索引)这种 ...

  9. 第三单元总结——JML契约式编程

    OO第三单元博客作业--JML与契约式编程 OO第三单元的三次作业都是在课程组的JML规格下完成.完成作业的过程是契约式编程的过程:设计者完成规格设计,实现者按照规格具体实现.作业正确性的检查同样围绕 ...

随机推荐

  1. rust 模块组织结构

    rust有自己的规则和约定用来组织模块,比如一个包最多可以有一个库crate,任意多个二进制crate.导入文件夹内的模块的两种约定方式... 知道这些约定,就可以快速了解rust的模块系统. 先把一 ...

  2. DataPicker 日期选择器

    问题描述:DataPicker 日期选择器,如何选取时间段(本日,昨日,上周,上月,当月) 代码实现: <el-date-picker v-model="searchForm.data ...

  3. Android开发之 。。各种Adapter的用法

    同样是一个ListView,可以用不同的Adapter让它显示出来,比如说最常用的ArrayAdapter,SimpleAdapter,SimpleCursorAdapter,以及重写BaseAdap ...

  4. C#还原对图像做的修改

    在C#程序中对图像进行处理,有的时候需要将处理后的图像还原,便于观察两者之间的区别,避免重新运行程序造成的麻烦.我是将之前写的Tab页中打开的图像进行还原,将原始图像数据保存在数据流中,然后从数据流中 ...

  5. NOR Flash 与 NAND Flash 的区别

    闪速存储器 闪速(Flash)存储器是一种电可擦除可多次编程的存储器.工艺上主要有两类:或非(NOR)型阵列和与非(NAND)型阵列. 项目 读取速度 写入速度 擦除速度 特性 其他 NOR Flas ...

  6. js_ts_ec6

    JS.ES.TS三者的关系 https://zhuanlan.zhihu.com/p/148875882 package.json详解 https://www.cnblogs.com/sweet-ic ...

  7. ES6--Array.prototype.fill 替换数组

    Array.prototype.fill

  8. Cocos Creator 性能优化:DrawCall

    前言 在游戏开发中,DrawCall 作为一个非常重要的性能指标,直接影响游戏的整体性能表现. 无论是 Cocos Creator.Unity.Unreal 还是其他游戏引擎,只要说到游戏性能优化,D ...

  9. Prmise.all的简单实现

    注意点 入参一般是个由Promise实例组成的数组,但是也可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例.若参数如果不是 Promise 实例,就会先调用 ...

  10. 【Azure DevOps系列】使ASP.NET Core应用程序托管到Azure Web App Service

    使用Azure DevOps Project设置ASP.NET项目 我们需要先在Azure面板中创建一个Azure WebApp服务,此处步骤我将省略,然后点击部署中心如下图所示: 此处我选择的是Az ...