CPLEX的OPL语言学习

9 篇文章 1 订阅

OPL语言学习

参考B站视频:cplex入门到精通

相关下载资源放在公众号【诸葛小猿】(现在已经更名【角色棱镜】,换了写作方向,因为下载的人多,还是保留了关键字“cplex”下载)上,关注即可获得下载链接。

创建OPL建模语言,是为了简化对数学规划问题的解算。 许多数学规划问题都可以使用计算机语言表达,其语法与这些问题在教科书和科学论文中的标准表示法相似。

1.注释

注释的作用:提高代码的可读性。

注释是给人看的,程序执行时会忽略注释。

1.1.单行注释

快捷键: ctrl + /

// 单行注释 独占一行
int a = 4;  // 单行注释 在代码的结尾

1.2.多行注释

/*
 这个是0-1背包问题的模型。
 使用注意:......
 说明:....
 作者:.....
*/

2.OPL说明

OPL 的基本构建块是整数、浮点数、字符串、标识和关键字。

OPL 中的标识只能包含字母、数字和下划线,并且不能以数字开始。

OPL 中的字母区分大小写。

OPL 模型包括:

  • 变量声明
  • 可选预处理指令
  • 模型/问题定义
  • 可选处理后指令
  • 可选流控制(main 块)

3.基本数据类型

基本数据类型包括:整数、浮点数、字符串、分段线性函数和分步函数。

3.1.整数int

整数是数字序列,可能带有减号前缀。

整数常量 maxint,它表示可用的最大正整数。

OPL 提供范围整数范围: -maxintmaxint

int a; // 定义  未初始化 默认为0
int b = -3; // 定义并初始化
int c = a*a; // 通过表达式初始化整数   c 初始化为a 的平方
int d = maxint; // 最大整数  2147483647
int e = -maxint;// 最小整数 -2147483647


execute{
	writeln(a," ",b," ",c," ",d," ",e);  // 0 -3 0 2147483647 -2147483647
}
3.2.浮点数float

OPL 中的数据类型 float 使用双精度浮点数。

预定义的浮点常数 infinity,用于表示无穷大符号。

浮点数可以用小数符号(3.4-2.5) 或使用科学记数法(3.5e-3-3.4e10)进行描述。

float a; // 只定义 不初始化  默认为0
float b = 3.14; // 定义并初始化
float c = -3.1415926e8; // 科学计数法
float d = infinity;  // 正无穷大
float e = -infinity; // 负无穷大
float f = 2.5*a + b;  // 通过表达式初始化float

execute{
	writeln(a," ", b," ",c," ",d," ",e," ",f); // 0 3.14 -314159260 Infinity -Infinity 3.14
}
3.3.字符串string

转义字符\

string a;
string b = "";
string c = "cplex";
string d = "sss\"dddd\n"; // 转义字符 \"
string e = "id\tname\tage\taddr\n1\twxl\t30\tshanghai\n"; // 转义字符 \t  \n

execute{
	writeln(a," ",b," ",c," ",d,"",e);  
}
执行结果:
  cplex sss"dddd
id	name	age	addr
1	wxl	30	shanghai


4.数据结构

使用整数、浮点数、字符串等基本类型构建的更复杂的数据结构。

范围(range)、数组(array)、元组(tuple)和集合(set)

4.1范围range

范围运算符:…。

上界小于下界,范围为空。空范围自动规范化为 0..-1。所有空范围都相等。

4.1.1 整数range

要指定整数范围,可以给出其下界和上界,上界和下界必须都为整数。步长为1。

作用:

1.数组初始化时,作为数组索引;arr[R]

2.用作迭代范围。for(i in R) forall (i in R)

3.用作整数决策变量的定义域。dvar int i in R;

4.1.2.浮点型range

定义:range float X = 1.0…100.0;

作用:通常用作浮点决策变量的定义域: dvar float x in X;

range a; // 定义 不初始化  默认为范围为空  0..-1
range b = 1..5; // 整数范围,下界和上界
range c = -5..6;
range d = 9..3;  // 空范围 0..-1
//range f = 1.0..5.0;// 语法错误 上下界必须为整数
int n = 8;
range e = n+1..2*n+1; // 通过表达式来给出下界和上界   

range R = 1..100;
int arr[R]; // 数组arr有100个元素,元素的索引为1到100

range float g = -2.1..3.8;   // 浮点数类型的range

execute{
   writeln(a);
   writeln(b);
   writeln(c);
   writeln(d);
   writeln(e);
   for(i in b){
   	writeln(i);
   }
//	for (j in g){   // 浮点数类型 不能循环打印
//		writeln(j);
//	}
}

执行结果:
0..-1
1..5
-5..6
0..-1
9..17
1
2
3
4
5
4.2.数组array

分一维数组和多维数组。

符号:[ ]

4.1.一维数组

一维数组在 OPL 中时最简单的数组,根据元素和索引集的类型而有所不同。

OPL 中的数组的索引可以是整数range或其他任意有限集合。

int a[1..4]; // 只定义 不初始化  默认[0,0,0,0]  // 使用整形range作为数组的索引
int b[1..4] = [10, 20, 30, 40];   // 整数数组

range r = 1..4;
float c[r] = [1.2, 2.3, 3.4, 4.5]; // 将range定义和数组定义分开   浮点数数组

string d[1..2] = ["Monday", "Wednesday"]; // 字符串数组

{string} name = {"zhangsan","lisi","wangwu"}; // 定义一个字符串集合
float salary[name] = [1000.0,2000,8000]; // 以字符串集合为索引   非常有用

execute{
	writeln(a);
	writeln(b);
	writeln(c);
	writeln(d); 
	writeln(salary); 
	writeln(salary["zhangsan"]); 
//	writeln(salary["zhaoliu"]);  // 查询不存在的索引对应的元素  报错

	for(i in r){
		writeln(c[i]) // 根据索引访问每一个元素
	}
} 

结果:
 [1.2 2.3 3.4 4.5]
 ["Monday" "Wednesday"]
 [1000 2000 8000]
1000
1.2
2.3
3.4
4.5

4.2.多维数组

声明:

int a[1..2][1..3] = ...; 

元素表示:

元素形式为 a[index1][index2] 的二维数组
4.3.元组tuple
4.3.1.元组的定义和使用

元组可以将紧密相关的数据聚集在一起。类似于面向对象的类class。

符号:< >

声明及初始化:

tuple Point {
		key int id;
       int x;
       int y;
    }; // 定义元组
Point dot = <1,2>; // 初始化一个元组  
Point point[i in 1..3] = <i, i+1>; // 初始化多个元组
{Point} set ={<1,1,2>,<2,3,4>}

一旦声明了元组类型 T,便可以定义元组数组、元组集合、元组的元组。元组中不支持多维数组。

传统方式访问元组的各个字段,可以在元组名称中添加点和字段名称后缀。

元组结构可以与关联。 元组键能够访问元组中使用一组唯一标识来组织的数据。

使用关键字:key

单个键: 只有一个key字段。key是唯一键。

多个键:有多个key字段。多个key字段合起来后是唯一键

在元组声明key的作用:

  • 键字段可以用作元组的唯一标识。元组集合中的元素的key不能重复,否则 OPL 会报错。
  • 定义key后,可以仅使用key字段的值来访问元组集合的元素。 可以仅使用键字段来对元组集合进行分类。
tuple Point {
       int x;
       int y;
} // 声明一个元组
    
Point dot = <1,2>; // 初始化一个元组对象    
Point point[i in 1..3] = <i, i+1>; // 初始化一个元组数组,包含3个元组对象
{Point} points = {<1,2>, <2,3>};   // 初始化一个元组集合,包含2个元组对象

tuple Rectangle {
    Point ll; 
    Point ur; 
}// 定义一个元组的元组

Rectangle rect = <<3,4>,<5,6>>; // 初始化一个元组为元素的元组


tuple KeyPoint1 {
  	   key int id;
       int x;
       int y;
    }; // 声明含有单个key的元组

tuple Point {
       int x;
       int y;
} // 声明一个元组
    
Point dot = <1,2>; // 初始化一个元组对象    
Point point[i in 1..3] = <i, i+1>; // 初始化一个元组数组,包含3个元组对象
{Point} points = {<1,2>, <2,3>};   // 初始化一个元组集合,包含2个元组对象

tuple Rectangle {
    Point ll; 
    Point ur; 
}// 定义一个元组的元组

Rectangle rect = <<3,4>,<5,6>>; // 初始化一个元组为元素的元组


tuple KeyPoint1 {
  	   key int id;
       int x;
       int y;
    }; // 声明含有单个key的元组

KeyPoint1 tup1 = <11,1,1>;
KeyPoint1 tup2 = <22,2,2>;
KeyPoint1 tup3 = <33,3,3>;
KeyPoint1 tup11 = <11,1,1>;
{KeyPoint1} kpSet = {tup1,tup2,tup3}; // 元组集合中的元素的key如果重复则下面arr运行时报错
int arr[kpSet] = [100,200,300];  // 元组作为数组的索引


tuple KeyPoint2 {
  	   key int id;
  	   key string name;
       int x;
       int y;
    }; // 声明含有两个key的元组    

//dvar int x;
//dexpr int y[t in kpSet] = 3*x; // 元组索引
//subject to {
//   forall(<a,b,c> in kpSet){ //  不能元组模式        也要用元组索引 forall(t in kpSet){
//   		y[<a,b,c>]==4;
//   }
//}; 


execute{
	writeln(dot);
	writeln(point);
	writeln(points);
	writeln(rect);
	writeln(dot.x);  // 访问单个字段
	writeln(point[1].x);
	writeln(rect.ll.x);
	
	writeln(kpSet.get(33)) // 只通过key=33获取元组集合的元素
	
	for(n in kpSet){ // n是元组索引
		writeln(arr[n]);
	}
	
	writeln(arr[tup1]); // 可以只使用元组的key获取元组数组的值
	writeln(arr[tup11]); // 可以只使用元组的key获取元组数组的值
//	writeln(arr[<11,1,1>]); // 不能使用arr[<11,1,1>]
//	writeln(arr[<11>]); // 不能使用arr[<11>]
}

// 结果:
 <1 2>
 [ <1 2>  <2 3>  <3 4>]
 {<1 2> <2 3>}
 <<3 4> <5 6>>
1
1
3
 <33 3 3>
100
200
300
100
100

4.3.2元组中的数据类型限制

1.元组中允许的数据类型

  • 原语(int、float 和 string)
  • 元组(也称为子元组)
  • 具有原语项的数组(非字符串),即:整数或浮动数组
  • 具有原语项的集合,即:整数、浮点数或字符串集

2.元组中不允许的数据类型

  • 元组集
  • 字符串、元组和元组集的数组
  • 多维数组

不能将声明中的元组索引和模式与决策表达式dexpr 混合使用:

dexpr float y[i in t] = ...; // 元组索引
subject to {
   forall(<a,b,c> in t) y[<a,b,c>]==...; }; // 元组模式

或

dexpr float y[<a,b,c> in t] = ...; // 元组模式
subject to {
   forall(i in t) y[i]==...; // 元组索引
}; 

如果选择在大型模型中标注约束,请使用元组索引来代替元组模式,从而避免增加性能和内存开销。

4.4.集合set
4.4.1.集合定义和初始化

集合set:没有重复元素(重复元素只保留一份),没有索引,默认已排序,顺序是创建顺序。

支持使用任意类型的集。

符号:{ }

定义:{type} 或 setof(type)

默认情况下,集合是已排序状态(ordered),意味着:

  • 其元素顺序被视为创建顺序。
  • 应用于有序集的函数和运算保留此顺序。
/*
  1.集合的定义和初始化
*/
{string} nameSet;  // 定义方式一: {type}   默认是空集{}
setof(string) citySet; // 定义方式二: setof(type) 默认是空集{}

{string} nameSet1 = {"zhangsan","lisi","wangwu"};  // 定义并初始化 
setof(string) citySet1 = {"shanghai","beijing","shengzhen"}; // 定义并初始化

ordered {string} nameSet2 = {"zhangsan","lisi","wangwu"};  // 定义并初始化  上面默认都是 ordered: 默认集合是已排序状态,元素顺序为创建顺序
ordered setof(string) citySet2 = {"shanghai","beijing","shengzhen"}; 


{string} nameSet3 = {"zhangsan","wangwu","zhangsan","lisi","wangwu"};  // 定义并初始化  重复元素只保留一次

tuple Point {
      int before;
      int after;
}
{Point} points = {<1,2>, <1,3>, <3,4>}; // 定义并初始化  集合内部可以是任意类型的元素

execute{
	writeln(nameSet);
	writeln(citySet);
	writeln(nameSet1);
	writeln(citySet1);
	writeln(nameSet2);
	writeln(citySet2);
	writeln(nameSet3);
	writeln(points);
	for(i in points){
		writeln(i); // 查看每一个集合的元素
	}
}
4.4.2.集合运算

在集合上允许以下运算:union、inter、diff 和 symdiff、first 和 last、next 和 prev、nextc 和 prevc、item、ord。 对于集上的函数,索引从 0 开始。

/*
2.集合的运算
*/
{string} nameSet1 = {"zhangsan","lisi","wangwu"}; 
setof(string) citySet3 = {"beijing","shengzhen"};
setof(string) citySet4 = {"shanghai","beijing"};

{string} unionset = citySet3 union citySet4; // 并集 {"beijing" "shengzhen" "shanghai"}
{string} interset = citySet3 inter citySet4; // 交集 {"beijing"}
{string} diffset = citySet3 diff  citySet4; // 差集:第一个集合独有的元素   {"shengzhen"}
{string} symdiffset = citySet3 symdiff citySet4; // 两个系统中都单独存在的元素 {"shengzhen" "shanghai"}
string firstStr = first(citySet3); // beijing
string lastStr = last(citySet3);  // shengzhen
string ele = next(nameSet1,"zhangsan",2); // 在集合中找zhangsan后面的第二个  集合中如果有重复元素会报错
string ele1 = item(nameSet1,1); // 找到索引值为1的元素
int ele2 = ord(nameSet1,"zhangsan"); // 找到zhangsan的索引值:0

execute{
	writeln(unionset);
	writeln(interset);
	writeln(diffset);
	writeln(symdiffset);
	writeln(firstStr);
	writeln(lastStr);
	writeln(ele);
	writeln(ele1);
	writeln(ele2);
}
4.4.3.集合的顺序

集合可以是已排序状态、已排列状态或逆序状态。默认是已排序状态,默认是输入顺序。

可以使用sorted或reversed对集合排序。

元组集合如果有key,则根据每个key排序。如果没有key,则所有字段(有例外)参与排序。

ordered {string} orderedSet     = {"shanghai","beijing","shengzhen","123cc","2frsd"}; // 已排序集合  默认情况   输入的顺序
sorted {string} sortedSet       = {"shanghai","beijing","shengzhen","123cc","2frsd"}; // 已排列集合   元素按照自然升序(或降序)顺序排列。 字符串是词典序。 
reversed {string} reversedSet   = {"shanghai","beijing","shengzhen","123cc","2frsd"}; // 逆序排列集合

tuple Point {
  	  key string name;
      int x;
      int y;
}
         {Point} tupSet1 = {<"bcd",1,2>, <"abd",1,3>, <"abc",3,4>};       
reversed {Point} tupSet2 = {<"bcd",1,2>, <"abd",1,3>, <"abc",3,4>}; // 逆序元组集合  比较元组的所有key,先比较第一个key然后比较第二个key。元组集合不使用键,那么会将整个元组(除了固定字段和数组字段)都考虑到排列操作中
sorted {Point} sortedTupSet2 = {<i,j,k> | <i,j,k> in tupSet1};  

execute{
	writeln(orderedSet); //  {"shanghai" "beijing" "shengzhen" "123cc" "2frsd"}
	writeln(sortedSet);  //  {"123cc" "2frsd" "beijing" "shanghai" "shengzhen"}
	writeln(reversedSet);//  {"shengzhen" "shanghai" "beijing" "2frsd" "123cc"}
	writeln(tupSet1); // {<"bcd" 1 2> <"abd" 1 3> <"abc" 3 4>}
	writeln(tupSet2); // {<"bcd" 1 2> <"abd" 1 3> <"abc" 3 4>}
	writeln(sortedTupSet2);//  {<"abc" 3 4> <"abd" 1 3> <"bcd" 1 2>}
}
4.5.通过数据结构解决稀疏性问题

就矩阵(或二维数组)而言,稀疏性指包含许多零值。

比如有100个仓库,给50个人发货,要算出这50个人的订单,应该最多只有50种仓库到用户的距离不为0,其余全为0;通过以下形式的声明可以利用稀疏性:

{string} Warehouses = ...;
{string} Customers = ...;
tuple Route {
       string w;
       string c;
}

{Route} routes = ...;
int transp[routes] = ... ; 

该声明指定集 routes,该集仅包含相关对 (warehouse, customer)。 然后,数组 transp 可以按此集编制索引,从而利用应用程序中存在的稀疏性。 很明显,对于大规模应用程序,这种方法会大幅降低内存消耗量,计算时间。

可以通过列出其值来初始化数组,就像目前为止提供的大部分示例一样。

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值