MiniZinc学习贴

1.1.1第一步

参数

MiniZinc中有两种变量

第一种变量是参数

与标准编程语言中的变量相似。它们必须被赋值 (但只能是一次) 它们被声明为某一类型如intfloat,或者bool( 或者是一个范围/集合)
可以(在声明中)添上 par 作为前缀,但这不是强制的(可选的)

int: i=3;
par int: i=3;
int: i, i = 3;
另一种变量是决策变量

与数学中的变量相似 用var与一个类型 (或者一个范围/集合)来声明 也可以由一个具有固定值的表达式来赋值(仅一次 )
范围:写作 l..u 一个从 lu 的连续整数序列

var int: i; constraint i >= 0; constraint i <= 4;
var 0..4: i;
var {0,1,2,3,4}: i;
var int: i = x + 3;
var int: i; constraint i = x + 3;

约束

基本的算术约束是基于标准算术关系操作符来创建的= != > < >= <=
在MiniZinc中,约束以下面的形式来表示constraint <约束表达式>

输出与字符串

一个输出项具有以下形式output<字符串列表>
字符串常量与C中相似写在" "中
他们不会超过一行
反斜杠用于(输入)特殊字符如\n \t等等

show(v) 以字符串形式输出v的值
\(v) 在字符串常量中显示v
"house" ++ "boat" 用于连接字符串

求解模型

我们可以运行如下命令求解MiniZinc模型$ minizinc army.mzn

F =0, L = 392, Z = 104, J = 0;
----------
==========			

战斗力为 4752 ,人数是 496
直线 ---------- 标示解
直线 ========== 标示没有更好的解(也就 是说这是最优解)
MiniZinc模型文件以.mzn为后缀
MiniZinc也有集成开发环境(IDE)
我们可与在IDE中按Run来运行

小结

MiniZinc使我们可以直接描述/表示并解决离散优化问题
线性模型是建模中最常见的

  • 整数线性规划更困难(NP-hard)
  • 依然是大多数现实世界离散优化的基础

1.1.2第二个模型

默认输出

注意 count.mzn没有输出项
默认情况下,MiniZinc输出

  • 所有声明的变量
  • 且没有被表达式赋值

我们会在简单的模型中利用这个特性

小结

满足问题

  • 不需要求得一个最优解

约束未必只能是线性等式和不等式

  • 取模 (mod),乘法,除法
  • 非等式 (≠)

1.1.3第三个模型

枚举类型

枚举类型(enums)定义一个具有有限对象的集合

  • 决策变量和参数可以是枚举类型
  • 数组下标(之后介绍)可以是枚举类型
  • 集合(之后介绍)可以基于枚举类型来定义

枚举类型可以如下声明
enum 枚举类型名
枚举类型可以由一个标识符集合来定义
枚举类型名 = { id1, …, idn } ;
我们可以使用枚举类型名来声明变量
[var] 枚举类型名: 变量名

小结

枚举类型

  • 引入了一个已命名对象的集合
  • 有助于模型的“类型安全性”
  • 之后会有更多关于枚举类型的内容

不可满足的模型

  • 并不是每个模型都有一个解!

图(地图)着色问题

  • 一个经典的图论问题
  • 在寄存器分配,时间表问题都有应用
  • 纯图着色问题最好用专门的算法来解决

1.1.4模型和实例

为参数赋值

每个参数必须有一个值$ minizinc armyd.mzn army.dzn
army.dznbudget = 20000; 得到的结果为 F = 243, L = 398, Z = 499, J = 0 又或者用$ minizinc armyd.mzn -D"budget = 20000;"
在IDE中你可以在弹窗中输入参数的值 , 在弹窗中输入,或从下拉菜单中选择一个已 加载的 .dzn 文件。

参数和数据

通常数据文件会为参数赋值
参数也可以是枚举类型
例如: color.mzn
enum: COLOR; (在 color.mzn中)
COLOR={R,W,B,G,P}; (color.dzn)

数据文件

MiniZinc数据文件后缀必须是 .dzn
数据文件只包含赋值项

  • 通常只为参数赋值
  • 但也可以为决策变量赋值

模型中未赋值的参数都必须在数据文件中赋值
可以添加多个数据文件来为不同的参数/变 量赋值,例如
$ minizinc model.mzn d1.dzn d2.dzn

小结

一个模型是一类优化问题的形式化描述
一个实例是一个特定的优化问题
把一个模型变成实例

  • 只需加入具体数据

在MiniZinc中,可以利用数据文件 (.dzn)

1.1.5对象建模

MiniZinc的新特性

下标范围表达式 l .. u(l,u为整数)或枚举类型
参数和变量数组
array[范围] of 变量声明
数组查找
数组名[下标表达式]
生成器表达式
forall(i in 范围)(布尔型表达式)

  • 对于范围内所有的i,对应布尔型表达式都为真

sum(i in 范围)(表达式)

  • 对范围内所有i对应的表达式累加

对象建模

创建一个枚举类型来为对象命名,例如, DISH
创建一个参数数组来表示对象的每一个属性 ,例如,sizesatisf
创建一个变量数组来表达对象的每一个决策 ,例如,amt
使用生成器表达式/推导式来建立限制对象的约束
注意,一个模型可能含有多个对象集合

小结

枚举类型用来表示对象集合
定义在对象集合上的数组用来表示

  • 对象特征
  • 对象决策

生成器表达式

  • forallsum
  • 用于构造表达式(约束多个对象)

桃园宴会问题是众所周知的背包问题的一个版本

1.1.6数组和推导式

数组

一个数组可以是多维的,可如下声明为
array[下标集合1,下标集合2, …] of 类型
数组的下标集合必须是

  • 一个整型范围或者枚举类型
  • 或者是固定值的集合表达式,而它的值则是一个 范围

数组的元素可以是任何类型,但不可以是另外 一个数组,例如,
array[PRODUCT,RESOURCE] of int: consumption;
内建函数length返回一维数组的长度
一维数组使用列表的形式来初始化
profit = [400, 500];
capacity = [4000, 6, 2000, 60, 50];
二维数组使用二维数组语法来初始化 起始于[||用来分隔行(第一维),结束于 |]

consumption =  [| 1.5, 1.0, 1.0, 1.0 
						  	| 2.0, 0.0, 2.0, 0.0 
								| 1.5, 0.5, 1.0, 1.0 
								| 0.5, 1.0, 0.9, 1.5 
								| 0.1, 2.5, 0.1, 2.5 |];

对任何维度的数组(≤ 6)都可以使用arraynd 族的函数进行初始化。它们把一个一维数组 转换为一个n 维数组,例如,同等地

 consumption = array2d(1..5, 1..4, 
							[1.5, 1.0, 1.0, 1.0, 
							2.0, 0.0, 2.0, 0.0, 
							1.5, 0.5, 1.0, 1.0, 
							0.5, 1.0, 0.9, 1.5, 
							0.1, 2.5, 0.1, 2.5];  

数组推导式

MiniZinc提供数组推导式(类似Haskell和ML)
数组推导式有以下形式
[ 表达式 | 生成器1, 生成器2, … ]
[ 表达式 | 生成器1, 生成器2, … where 测试 ]
例如

[i + j | i, j in 1..4 where i < j] 
= [1+2, 1+3, 1+4, 2+3, 2+4, 3+4] 
= [3, 4, 5, 5, 6, 7]  

对任何维度的数组(≤ 6)都可以使用arraynd 族的函数进行初始化。它们把一个一维数组 转换为一个n 维数组,例如,同等地

consumption = array2d(1..4, 1..5,
[1.5,1.0,1.0,1.0,2.0,0.0,2.0,0.0,1.5,0.5,
1.0,1.0,0.5,1.0,0.9,1.5,0.1,2.5,0.1,2.5]);

arraynd 把一个一维数组转换为一个n维数组 ;我们也可以使用推导式来把一个n 维数组展 平为一个列表(一维数组),例如,

array[1..20] of int: list =
[consumption[i,j] | i in 1..5, j in 1..4];

迭代

MiniZinc提供了各种对列表或者集合进行操作的内建函数
数字列表:sum, product, min, max
约束列表:forall, exists
在调用这些(以及其他的生成器函数)时, MiniZinc提供了特殊的语法,例如,
forall(i,j in 1..10 where i < j) (a[i] != a[j])
等价于(单个参数的forall)
forall([a[i] != a[j] | i,j in 1..10 where i < j])

小结

真正的模型可以应用到不同大小的数据上
MiniZinc使用

  • 枚举类型为对象命名
  • 数组来捕捉(存放)对象的信息

推导式用来创建适用于不同规模的数据的

  • 约束,以及
  • 表达式

1.1.7全局约束

集合参数和变量对应表

集合参数可如下定义

  • set of 类型: 变量名 = 固定值集合;
  • 它们可以作为固定值集合使用 变

量对应表

  • 下标表达式可以包括决策变量
  • 这给了我们很强的建模(描述)能力

include

include 语句
include "文件名";

  • 会在MiniZinc模型中包含该文件
  • 会在当前文件夹和MiniZinc库路径中寻找

include 语句用于

  • 将较大模型分成各个小部分
  • 加载全局约束的定义
  • 控制MiniZinc面对某一求解器时应加载哪个全局
  • 约束的定义

全局约束

原则上,任何可以取无限个变量为参数的约束(就是全局约束)

  • 所以线性约束是“全局的”

全局约束是

  • 在很多问题都会出现的约束

全局约束可以使

  • 模型更简短
  • 求解变得更容易(因为求解器可以利用全局约束 的结构信息)

小结

全局约束是我们在课程会遇到的最重要有效 的概念之一
类似地,允许在数组的下标表达式中使用决 策变量也是一个非常有效的建模工具

1.2.1集合的选择

bool2int

布尔型数据转换为整型数据
bool2int(false) = 0
bool2int(true) = 1
对于0-1整型和布尔型数据,很多求解器会用 同样的内部表示
如果在MiniZinc期望为整型数据的地方使用 布尔型数据,MiniZinc会自动”使用bool2int 函数

从一个对象集合中选择子集

黄巾之乱故事是一个需要我们从一个对象集 合中选择一个子集同时

  • 满足一些条件,以及
  • 优化一些目标函数

的典型问题

0-1/集合选择问题

0-1整型变量数组
布尔型变量数组
一个集合变量

集合变量

MiniZinc中的集合变量从一个给定的固定超集中选择一个子集,例如: var set of {1,2,3}: x;

其他的集合表示方式

其他可对集合变量的可选值建模的集合表示方式,例如:
array[1..3] of var 0..1: x;

集合操作符

MiniZinc提供了(中缀)集合操作符
in(集合中的元素 例如: x in s
subset, superset(子集,超集)
intersect(交集)
union(并集)
card(集合势)
diff(差运算,例如:x diff y = x \ y )
symdiff(对称差)例如:{1, 2, 5, 6} symdiff {2, 3, 4, 5} = {1, 3, 4, 6}

哪一个模型更好?

大部分求解器对所有的模型同样地处理

  • CP求解器或许能更好的处理最后一个模型,因 为它可以把势的推理和其它集合约束的推理互相 结合

我们更倾向于能更简洁表达约束的模型

  • 第一个0-1整型模型

我们更倾向于更高级的模型

  • 最后一个集合模型

小结

用集合去建模在组合优化问题中很常见
黄巾之乱故事实际上是众所周知的0-1背包问题的一个变体

  • 这个问题经常出现在现实情景中,例如投资选择 ,原材料切割浪费最小化以及背包密码系统

至少有三种建模方法

  • 指示变量:0-1整型变量或者布尔型变量
  • 原生集合变量

1.2.2集合表示的选择

八卦问题的分析

10个攻击点
8种八卦属性
攻击点具有八卦属性
一旦一个与某个属性相关联的攻击点被攻击 ,所有其他具有此属性的攻击点都会被强化 。因此,对于每个属性,只有一个攻击点可以被攻击
有些攻击点会比较弱!
目标: 产生最大的伤害!
换句话说,选择所有攻击点的一个子集去攻击

  • 每个属性最多有一个攻击点
  • 最大化伤害

每个SYMB中的属性,给定一个数字 1..nSpots的子集。选择一个1..nSpots 的子集,使得每个属性的子集中最多有一个 元素在其中,并且最大化选择的集合的伤害值

nSpots = 10;
damage = [10, 8, 4, 2, 6, 9, 5, 3, 8, 10];
SYMB = {'天','泽','火','雷','风','水','山','地'};
group = [{1,4,6}, {1,2,6,7}, {1,3,6,8}, {1,2,3},
{2,9,10}, {5,6,8,10}, {7,8,10}, {1,3,5}];

八卦问题 数据 + 决策变量

数据

int: nSpots;
set of int: SPOT = 1..nSpots;
array[SPOT] of int: damage;
enum SYMB;
array[SYMB] of set of SPOT: group;

决策变量
var set of SPOT: attacks;

集合选择约束 + 目标

交集最多只有一个元素

forall(s in SYMB)
(card(attacks intersect group[s])
<= 1);

目标

var int: totalDamages =
sum(p in attacks)(damage[p]);
solve maximize (totalDamages);

求解模型

attacks: {4,5,7,9} & damages: 21;

1.2.3固定势集合的选择

修改的集合选择问题

每个SYMB中的属性,给定一个数字 1..nSpots的子集。选择一个大小为size1..nSpots的子集,使得每个属性的子集中最多有一个元素在其中,并且最大化选择的集合的伤害值

nSpots = 10;
damage = [10, 8, 4, 2, 6, 9, 5, 3, 8, 10];
size = 3;
SYMB = {'天','泽','火','雷','风','水','山','地'};
group = [{1,4,6}, {1,2,6,7}, {1,3,6,8}, {1,2,3},
{2,9,10}, {5,6,8,10}, {7,8,10}, {1,3,5}];

对修改的集合选择问题增加约束

增加的约束 card(attacks) = size;
对模型求解 attacks: {5,7,9} & damage: 19;
但是我们可以用不同方式对已知势的集合建模!

确定一个固定势集合

不去定义一个集合变量 var set of SPOT: attacks;
和使用势约束 card(attacks) = size;
而是定义一个元素个数为size的数组
array[1..size] of var SPOT: attacks; 以及一些其他的约束 …
原因:假设 nSpots=1000,size=4
第一种表示方法:1000个布尔型变量 第二种表示方法:4个整型变量
第一个问题:有一些数组表示并不是势为3 的集合
例如 [1,1,1] = {1}, [1,2,1] = {1,2}
解决方案:确保它们全部都不相同
forall(i,j in 1..u where i < j) (x[i] != x[j]);
第二个问题:同一个集合有多重表示
例如,{1,6,10} = [1,6,10], [10,1,6], [10,6,1], [1,10,6], [6,10,1], [6,1,10]
解决方案:确保按顺序排列
forall(i in 1..u-1)(x[i] < x[i+1]);

决策变量建模中的关键问题

问题中的决策
不一定是模型中的决策
如果可以就使之相同,但是

  • 假如你在决定一个图中的路径,一个树结构

建模的关键点(1)

  • (满足约束的)模型中的决策
  • 是问题中的有效决策

加约束来达到此目的

  • 例如,加约束强制数组元素

模型中的多个决策

  • 代表问题中同一个的决策
  • 例如,x=[1,2,7],x=[7,2,1] 代表了 x={1,2,7}

建模关键点(2)

  • 对于每个解,模型中尽量只有一组决策与其对应
  • 代表了问题中的有效决策

加约束移除(冗余),只保留一组(决策表 示)

  • 例如,加约束强制数组元素 <

小结

有多种方式去表示固定势集合

  • var set of OBJ + 势约束
  • 适用情况:求解器本身支持集合
  • 适用情况:OBJ不是太大
  • array[1..u] of var OBJ
  • 适用情况:当u比较小

两个对决策变量建模时的关键问题(点)

  • 确保模型的每个解是问题的一个解
  • 尽量确保问题的每个解在模型中只有一个对应解 ( 对称)

1.2.4有界势的集合

修改的集合选择问题

nSpots = 10;
damage = [10, 8, 4, 2, 6, 9, 5, 3, 8, 10];
size = 3;
SYMB = {'天','泽','火','雷','风','水','山','地'};
group = [{1,4,6}, {1,2,6,7}, {1,3,6,8}, {1,2,3},
{2,9,10}, {5,6,8,10}, {7,8,10}, {1,3,5}];

每个SYMB中的属性,给定一个数字 1..nSpots的子集。选择一个势不大于 size1..nSpots的子集,使得每个属性的子集中最多有一个元素在其中,并且最大化选择的集合的伤害值
集合变量
constraint card(attacks) <= size;
有size个变量的数组
array[1..size] of var SPOTx: attacks
SPOTx = SPOT union {0}

两个关键要素

模型的每个解代表问题中的一个解

  • [3,0,3] ❌ 没有重复值
  • [0,2,0] ✅ 附加值可以重复

问题中的每个解都只有一个模型的解来对应

  • [0,2,0], [0,0,2], [2,0,0] = {2} ❌
  • [0,1,2], [0,2,1], [1,0,2], [1,2,0], [2,0,1], [2,1,0] ❌

添加约束来实现

有界势约束

需要的约束 array[1..size] of var SPOTx: attacks;
将元素排序(递减)
forall(i in 1..size-1) (attacks[i] > attacks[i+1]);

  • ❌ 表示 {2} 的模型解 [2,0,0] 不再满足条件

非严格排序
forall(i in 1..size-1) (attacks[i] >= attacks[i+1]);

  • ❌ 有重复值的解 [3,2,2]

结合两条:允许0重复
forall(i in 1..size-1) (attacks[i] >= (attacks[i]!=0)+attacks[i+1]);
有界势的表示
表示 var set of {1,2,3}: x; array[1..3] of var 0..3: x;

小结

有多种方式去表示集合
var set of OBJ

  • 适用情况:求解器本身支持集合
  • 适用情况:OBJ不是太大

array[OBJ] of var bool / 0..1

  • 适用情况:OBJ不是太大

array[1..u] of var OBJ

  • 只用于固定势u
  • 适用情况:当u比较小

array[1..u] of var OBJx

  • 需要表示“无”这个元素

(没有势约束的)这个集合选择问题其实是 一个加权集合打包问题变体,在组合数学中是已被充分研究的 NP完全问题
集合打包问题是集合覆盖的对偶问题。而这个对偶问题是在组合问题中被研究得最多的问题之一

1.3.1函数建模

确定函数

很多组合问题有以下形式:

  • 给一个集合DOM(定义域)中的每个对象
  • 分配一个取自另外一个集合COD(值域)的值

我们可以把这理解为

  • 定义一个函数DOM ➔ COD

  • 或者划分集合DOM(为以COD中的元素标记的 集合)

    这个函数可以为

  • 单射:分配问题

  • 双射(|DOM|=|COD|):匹配问题

全局约束

捕捉了问题的结构
交给求解器来决定使用它已知的处理这个子 结构的最好方法
在给这些子结构建模时,我们使用全局约束

alldifferent

全局约束版本 alldifferent(pos);
强制使每个英雄被分配到不同的攻击点
求解器可以利用子结构来更好地求解问题
全局约束的第一个例子
捕捉了

  • 分配问题子结构,或者换句话说
  • 决定一个单射函数

小结

确定一个(有限的)函数是常见的
确定一个单射函数是一个

  • 分配(子)问题

全局约束alldifferent很好地捕捉了这个子结构

1.3.2另一个分配问题例子

牢房问题的数据

enum PRISONER;
int: n;
set of int: ROW = 1..n;
int: m;
set of int: COL = 1..m;
array[ROW,COL] of int: cost;
set of PRISONER: danger;
set of PRISONER: female;
set of PRISONER: male = PRISONER diff female;

牢房问题的决策变量

定义域的对象是什么?

  • DOM = PRISONER

值域的对象是什么?

  • COD = ROW x COL

表示:两个函数
array[PRISONER] of var ROW: r;
array[PRISONER] of var COL: c;

牢房问题的约束

同一个牢房不能有两个囚犯
forall(p1, p2 in PRISONER where p1 < p2) (abs(r[p1]-r[p2]) + abs(c[p1]-c[p2]) > 0);
难道我们不能使用alldifferent吗?
可以
alldifferent([r[p] * m + c[p] | p in PRISONER]);
将每个牢房映射到一个独有的数
危险囚犯不能有邻居
forall(p in PRISONER, d in DANGER where p != d)
(abs(r[p] - r[d]) + abs(c[p] - c[d]) > 1);
性别约束
forall(p in female)(r[p] <= (n + 1) div 2);
forall(p in male)(r[p] >= n div 2 + 1);
注意male的使用

  • 比使用定义的方式更清晰

目标

目标函数
var int: tCost = sum(p in PRISONER)(cost[r[p],c[p]]);
solve minimize tCost;

小结

分配子问题在很多应用中都很常见
在以后一个关于刘备的问题中,也有一个分配子问题,但是那个函数是双射的

  • 这个就是匹配问题了

1.3.3划分建模

划分问题

确定一个函数 f: DOM ➔ COD
可以看成是一个划分问题
若有以下条件,这可以给我们额外的洞察
我们想对集合进行约束或者操作
F(c) = { d in DOM | f(d) = c },
其中 F: COD ➔ 2^DOM (f 的伪逆函数)

排班对象

定义我们想要推理的对象集合

enum SOLDIER;
enum SHIFT;
int: nDays;
set of int: DAY = 1..nDays;
int: o; % required number for NIGHT
int: l; % lower bound for EVE
int: u; % upper bound for EVE

排班决策变量

定义域中的对象是什么?
DOM = SOLDIER × DAY
值域中的对象是什么?
COD = SHIFT
对每一天每一个士兵,选择班次
array[SOLDIER,DAY] of var SHIFT: roster;
可以看成是一个划分问题

  • 根据班次类型来划分士兵
  • 对每个班次的士兵集合进行推理

排班模式约束

每一个士兵都不能连续多于两个晚上值班 我们如何来表达它?
forall(s in SOLDIER, k in 1..nDays-2) (sum(d in k..k+2)(roster[s,d] = NIGHT) <= 2);
呀, 不是很清楚啊!!
使用逻辑联结词来使得它更明确
forall(s in SOLDIER, d in 1..nDays-2) (roster[s,d] = NIGHT /\ roster[s,d+1] = NIGHT ->
roster[s,d+2] != NIGHT);
每一个士兵都不能傍晚值班后夜晚再值班
forall(s in SOLDIER, d in 1..nDays-1) (roster[s,d] = EVE -> roster[s,d+1] != NIGHT);

逻辑联结词

布尔表达式

  • true
  • false
  • /\ 合取
  • / 析取
  • -> 蕴含
  • <-> 双蕴含或者等价
  • not 非

允许我们更有效地结合约束

  • 但是结合逻辑约束和全局约束时要注意!

排班需求约束

每个夜晚有o个士兵值班
forall(d in DAY) (sum(s in SOLDIER) (roster[s,d] = NIGHT) = o);
每个傍晚有l到u个士兵值班(共同的子表达式)
forall(d in DAY) (sum(s in SOLDIER) (roster[s,d] = EVE) >= l);
forall(d in DAY) (sum(s in SOLDIER) (roster[s,d] = EVE) <= u);

目标函数

傍晚时越多士兵巡逻越好
var int: tOnEve = sum(d in DAY)
(sum(s in SOLDIER)(roster[s,d] = EVE)); solve maximize (tOnEve);

1.3.4全局势约束

中间变量

中间变量

  • 存储会再次用到的表达式的值
  • 依赖于决策变量
array[DAY] of var 0..card(SOLDIER): onEve;
constraint onEve = [sum(s in SOLDIER)(roster[s,d] = EVE)
| d in DAY];
constraint forall(d in DAY)
(onEve[d] >= l /\ onEve[d] <= u);
solve maxmize sum(onEve);

给中间变量选择一个好的界限
或者直接地

array[DAY] of var l..u: onEve;
onEve = [sum(s in SOLDIER)(roster[s,d] = EVE)
| d in DAY];
solve maxmize sum(onEve);

划分问题

很多时候当我们划分一个集合时,我们必须 要限定每个划分类的大小
例如,#NIGHT = o, l ≤ #EVE ≤ u
我们有特殊的约束来限定划分类的大小
global_cardinality(x, v, c)
vc的大小一样
约束 ci = Σj in 1..n(xj = vi)
收集出现次数,限定vi出现的次数
例如,global_cardinality(x,[1,2],[2,1])
x = [1,1,2,3] ✅, [1,2,3,4]

全局势约束

替换

forall(d in DAY)(sum(s in SOLDIER)
(roster[s,d] = NIGHT) = o);
forall(d in DAY)(sum(s in SOLDIER)
(roster[s,d] = EVE) >= l);
forall(d in DAY)(sum(s in SOLDIER)
(roster[s,d] = EVE) <= u);

array[DAY] of var l..u: onEve;
constraint forall(d in DAY)
(global_cardinality([roster[s,d] | s in SOLDIER],
										[NIGHT, EVE], [o, onEve[d]])); 

全局势约束的变种

全局势约束有很多个变种
收集出现次数,要求每个值都出现
global_cardinality_closed(x, v, c)
xi 的值都要从 v 来: 就是 ∀i.∃j. xi = vj
限定出现次数的上限和下限
global_cardinality_low_up(x, v, lo, hi)
约束 loi ≤ (Σj in 1..n (xj = vi)) ≤ hii
限定出现次数的上限和下限,要求每个值都出现
global_cardinality_low_up_closed(x,v,l,u)

小结

很多离散优化问题都涉及到

  • 确定一个函数 f: DOM ➔ COD
  • 这个可以看成是划分DOM

从划分的角度来看问题在我们推理集合F(c)时是很有帮助的 (即 f的伪逆函数)
在势约束下进行划分时,子结构可以由

  • global_cardinality族来表达

共同子表达式:

  • 中间变量

逻辑联结词

1.3.5纯划分

一个聚类问题

给定nSpots个点, 把它们划分到最多k个类 中使得

  • 同一类中任何两点最多相距maxSep
  • 最大化不同类的两点之间的最短距离

纯划分问题

确定一个函数 f: DOM ➔ COD
可以看成是一个划分问题
但如果我们只希望划分DOM

  • DOM = 点 and COD = 类
  • COD无指明意义且未给定

容易的情况: 划分类个数不超过k
COD = 1..k;
更难的情况: 我们不知道有多少个类?
DOM划分为最多k部分
DOM的每一个元素, 用一个变量来指明被
分配到了哪个划分类
简单的数组表示
array[DOM] of var 1..k: x
注意会出现多重表示,因为用哪个数字表示类别是无关紧要的
例如,DOM = 1..4, k = 3
x = [1,2,1,3] ➔ {1,3} {2} {4}
x = [3,1,3,2] ➔ {2} {4} {1,3}
x = [3,2,3,1] ➔ {4} {2} {1,3}

多重表示

添加约束使每种划分只有一个表示
值对称

  • 1..k中的数字是互相对称的
  • 我们不关心类的名称(数字)

这个特点出现在很多离散优化问题中

去除值对称

我们如何促使只有一个表示?
以各自的最小元素对划分集合进行排序
例如,{2}{4}{1,3} 被排序为 {1,3}{2}{4}
唯一地表示成 x = [1,2,1,3]
我们如何用约束来限制?
i个划分类的最小元素比第i+1个划分类的最小 元素小
forall(i in 1..k-1) (min([j | j in DOM where x[j] = i])
<``min([j | j in DOM where x[j] = i+1]));

投石车问题数据

定义聚类实例的数据

int: nSpot; % points to be clustered
set of int: SPOT = 1..nSpot;
array[SPOT,SPOT] of float: dist;
% distance between two points
int: k; % number of clusters
set of int: CLUSTER = 1..k;
float: maxSep;

投石车问题

决策变量
array[SPOT] of var CLUSTER: shot;
值对称
forall(i in 1..k-1) (min([j | j in SPOT where shot[j] = i])
< min([j | j in SPOT where shot[j] = i+1]));
但MiniZinc为去对称提供了更好的工具

value_precede_chain

MiniZinc包含了一个用于去值对称的全局约束
value_precede_chain(array[int] of int: c,
array[int] of var int: x)
强制c[i]x中的第一次出现先于c[i+1]x中 的第一次出现
例如,value_precede_chain([1,2,3], x)
x = [1,1,2,3] ✅, [1,3,1,2] ❌, [1,2,1,2]
我们可以用以下的约束来替换我们的去对称约束
value_precede_chain( [i | i in CLUSTER], shot)

投石车问题

约束: 类中点之间的最大距离
forall(i,j in SPOT where i < j
/\ shot[i] = shot[j])(dist[i,j] <= maxSep);
目标
float: maxdist = max([dist[i,j] | i,j in SPOT]);
var float: obj = min(i,j in SPOT where i < j)
(if shot[i]=shot[j] then
maxdist else dist[i,j] endif); solve maximize obj;
这个模型并不要求正好而只是最多k个类
可以更少
我们可以很容易地添加一条约束来强制每个 类都有成员

  • 下界 = 1
  • 上界 = nSpot - k + 1

global_cardinality_low_up_closed(shot, [i | i in CLUSTER], [1 | i in CLUSTER], [nSpot-k+1 | i in CLUSTER]);

纯划分

如果我们不知道类的数量?
简单: 不会多于nSpots个划分

  • int: k = nSpots;

小结

纯划分问题伴随(其他条件)

  • 不多于k个类
  • 已知k个类
  • 类的数量未知

表示类的数字是无关紧要的

  • 导致值对称

去除值对称

  • value_precede_chain

    聚类问题是一个被充分研究的问题

  • 对于纯聚类问题应选用专门设计的聚类方法
    半监督聚类法会向问题添加约束,例如

  • 某些点必须在相同或不同的类中:用相等=约束或者非等约束表达

  • 类大小的界限:用全局势约束表达

  • 其他种类的约束

离散优化适用于各种半监督聚类问题

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值