第十九届中国研究生数学建模竞赛B题方形件组批优化问题

最近参加了建模大赛,B组题是一个经典的组合优化问题,目前已经过了截止提交时间。由于是第一次参加建模比赛,所以现在写下这篇博文以作纪念和记录方法。

首先声明:方法均为原创,最终子问题1的板材利用率(工件总面积/板材总使用面积)结果在65%左右,子问题2的利用率为45%左右。

赛题具有两个子问题,如下:

子问题1:

子问题1:排样优化问题。要求建立混合整数规划模型,在满足生产订单需求和相关约束条件下,尽可能减少板材用量。

约束:

  1. 在相同栈(stack)里的产品项(item)的宽度(或长度)应该相同;
  2. 最终切割生成的产品项是完整的,非拼接而成。

本子问题要求编程,以数据集A为输入,输出结果要求见第五部分。

下料优化问题(也称排样优化问题):根据同一生产批次内方形件的尺寸与数量,选择原片的规格和数量,进行下料排样优化,最大化板材原片利用率。依据切割工序的工艺要求,排样方案必须满足一刀切(也称齐头切,Guillotine cut)约束(任何一次直线切割都要保证板材可分离,换言之,每次直线切割使得板材分成两块)。下料优化问题属于具有一刀切约束的板型材方形件排样优化问题。

考虑切割工艺的方式不同,分齐头切(guillotine cut)和非齐头切(如图1),齐头切又可以细分精确方式和非精确方式(涉及到切割的阶段数,如图2.2中的三阶段排样方式主要有三种不同的类型:三阶段非精确(3NE)排样方式、三阶段匀质排样方式(3E)、三阶段同质排样方式(3H)。其中 3E 3H 排样方式可在三个阶段内切割出准确尺寸的方形件,因此都属于精确排样方式。3NE 排样方式中,部分方形件还需要额外的第四阶段切割才能得到满足规格尺寸要求。

(a)齐头切guillotine cutting                                 b)非齐头切(non-guillotine cutting

1 切割示意:(a)齐头切(guillotine cutting)和(b)非齐头切(non-guillotine cutting

2 三阶段排样方式

由于涉及到阶段数不同,不同文献对于切割每个阶段的称呼不一样,为了便于理解和统一表述形式,采用英文的方式形容关键阶段模块的描述,具体可参见图3(实际切割过程中,第一刀可能垂直于长边,也可能垂直于短边,图3以垂直于其中一条边为例)。

3 不同切割阶段的形式定义

因为常见的阶段最多为3-4个,因此以3阶段的切割方式为例(如图3),第1阶段的横向切割生成模块称之为stripe(条带),如Stripe1Strip2;第2阶段纵向切割生成模块称之为stack(栈),如Strip1继续被切割分成Stack1Stack2Stack3;第三阶段横向切割生成模块称之为item(产品项),如Stack1继续被切割分成Item1Item2Item3

子问题2:

订单组批问题:在考虑订单交货期、设备产能负荷、仓储容量、材料利用率、生产效率、生产工艺约束等因素下,对生产订单进行组批优化。使具有相同材质、交货期相近、工艺相似的订单安排在同一个生产批次, 通过订单组批优化来保证交货期, 提高原材料的利用率,提高设备生产效率等。为便于统一处理数据和体现问题本质,本次赛题所有订单的交货期均相同,不做区分。批次的定义为完成若干订单全部任务且不含任何不完整订单任务的订单集合。

子问题2:订单组批问题。要求建立混合整数规划模型,对数据集B中全部的订单进行组批,然后对每个批次进行独立排样,在满足订单需求和相关约束条件下,使得板材原片的用量尽可能少。

在满足子问题1约束的基础上进一步要求:

  1. 每份订单当且仅当出现在一个批次中;
  2. 每个批次中的相同材质的产品项(item)才能使用同一块板材原片进行排样;
  3. 为保证加工环节快速流转,每个批次产品项(item)总数不能超过限定值;
  4. 因工厂产能限制,每个批次产品项(item)的面积总和不能超过限定值;

本子题要求编程,以数据集B为输入,输出结果要求见第五部分。

问题假设:

本题假定:

  1. 只考虑齐头切的切割方式(直线切割、切割方向垂直于板材一条边,并保证每次直线切割板材可分离成两块);
  2. 切割阶段数不超过3,同一个阶段切割方向相同;
  3. 排样方式为精确排样;
  4. 假定板材原片仅有一种规格且数量充足
  5. 排样方案不用考虑锯缝宽度(即切割的缝隙宽度)影响。

问题分析:

子问题1要求我们对板材进行排样,并使用齐头切的方式,切割阶段数采用三阶段切割的精确切割方式,如题给出的实例即为横-纵-横的切割方式,因此我们很快地明白Stack中的所有Item均是等宽或等长的(考虑旋转)。

由于本人的研究方向属于运筹学,因此该问题可以使用多级列生成的方式进行精确求解,但比赛时间紧张且算法构建难度很大,求解时间也是指数级别的复杂度,所以为了应对赛题仅考虑启发式算法。

使用C++进行编程,首先是头文件的定义,在子问题2中使用了CPLEX 12100求解器。

TSCS.h

#pragma once
#include<iostream>
#include<fstream>
#include<istream>
#include<sstream>
#include<string>
#include<vector>
#include<algorithm>
#include<set>
#include<map>
#include<ilcplex/ilocplex.h>//Cplex头文件


using namespace std;
//单个工件
struct Item {
	int id;
	string item_material;
	int item_num;
	double item_length;
	double item_width;
	string item_order;
	double item_X, item_Y;
	bool revolve = 0;
	bool alreadyIn = 0;
};
//单个Stack
struct Stack {
	double TW=0, TL=0;
	vector<Item>item_inT;
};
//单个Stripe
struct Stripe {
	double SW=0, SL=0;
	vector<Stack>stack_inS;
};
//单个Pattern
struct Pattern {
	vector<Stripe>stripe_inP;
};
struct sameLengthSet {
	double length;
	vector<int>sameItemId;
};
//three Stage Cutting Stock Problem
class TSCS {
public:
	string fn;//源文件路径
	vector<Item>items;//定义工件
	int max_item_num;//定义最大工件数量
	double max_item_area;//定义最大工件总面积
	double plate_length;//定义板材长度
	double plate_width;//定义板材宽度
public:
	void read(const char* fn) {//读取CSV文件
		this->fn = fn;
		max_item_area = 1000;
		max_item_num = 250;
		plate_length = 2440;
		plate_width = 1220;

		ifstream infile(fn);
		string line;
		getline(infile, line);
		while (getline(infile, line)) {
			replace(line.begin(), line.end(), ',', ' ');
			istringstream sin(line);
			Item it;
			sin >> it.id;
			sin >> it.item_material;
			sin >> it.item_num;
			sin >> it.item_length;
			sin >> it.item_width;
			sin >> it.item_order;

			if (it.item_length < it.item_width) {
				swap(it.item_length, it.item_width);
			}
			it.revolve = 1;
			items.push_back(it);
		}
	};

	TSCS(vector<Item>&items) {//子问题2的分批初始化方式。
		max_item_area = 1000;
		max_item_num = 250;
		plate_length = 2440;
		plate_width = 1220;
		this->items = items;
	}
	TSCS() {

	}
};

void solve(TSCS* tscs);//求解子问题1

void solve2(TSCS* tscs);//求解子问题2

主程序main.cpp:

#include"TSCS.h"

void main() {
	TSCS tscs;
	string problemName;
	cout << "请输入需要求解的问题(输入示例:A1/B1):" << endl;
	cin >> problemName;
	problemName = "data" + problemName + ".csv";
	
	
	if (problemName.find("A")!=string::npos) {
		tscs.read(problemName.c_str());
		cout << "开始求解..." << endl;
		solve(&tscs);
	}
	else if (problemName.find("B") != string::npos) {
		cout << "开始求解..." << endl;
		tscs.read(problemName.c_str());
		solve2(&tscs);
	}
	else {
		cout << "未能找到问题输入文件,请检查输入是否有误" << endl;
	}


	cout << "程序运行结束,请按任意键退出!" << endl;
	system("pause");
}

自此,问题输入和整体方法架构已经完成,我们的方法实现在tscs.cpp文件中,下面介绍以下子问题1的启发式方法:

子问题1启发式方法:

子问题的启发式贪心算法即将整个过程作为一个线性流程,首先排样最长的工件到一个成片中直至无法放置,然后在这些工件生成的Stripes中,添加最长的等宽体Stacks直至无法放置。其算法原理较为简单,但仍具有不小的实现难度。

首先需要处理输入问题,我们在输入过程中就将工件横置,全部工件输入完毕后进行排序,将工件排序完成后开始从头挑出未放置的工件开始制作初始的Stripes,当无法继续放置时,我们将开始统计等宽体,其中要跳过已经被放置的工件,然后对等长体依据长度排序,其中可以进行旋转排样。然后我们将能够放置进初始Stripes的等长体Stacks放入,判断是否无法继续放置。在算法过程中需不断维护当前XY坐标、排样长度宽度等,最终单个成片无法继续放置时,再生成一个新的成片去执行上述流程。

最终我们实现的算法可以在非常短的时间内给出排片方案,从理论上将,子问题1的贪心启发算法可以作为精确算法的初始列输入,这样一来可以大幅度降低生成列的数量,提高求解速度。

子问题1算法流程

1)首先将所有工件横放,然后根据横向长度排序。

2)从头开始取出工件,进行横向堆叠,若两个工件等长,则作为一个stack,而这些stack就形成了初始stripe组,当无法继续放置时转3。

3)寻找等长体并根据长度排序,为每个初始stripe的右侧添加能够放置的最长等长体(多个),直至无法添加(大于成片长度)。

4)放置完毕后,成片数加一,再上述过程中每放置一个块就添加一个已放置标记,然后再从头取出未放置的工件,进行横向堆叠,若两个工件等长,则作为一个stack,而这些stack就形成了初始stripe组,当无法继续放置时转3。

5)全部工件放置完毕后,结束。

 

子问题1实现:

void solve(TSCS* tscs) {
	
    //首先按照工件的长度进行排序。
	sort(tscs->items.begin(), tscs->items.end());
    //寻找等宽体(等长体),使用set数据结构判断。
	set<double>length;//长度集合
	vector<double>sameLength;//相同长度列表
	vector<vector<int>>sameLengthSet;//相同长度下的工件集合
	for (int i = 0; i < tscs->items.size(); i++) {
		if (length.find(tscs->items[i].item_length) == length.end()) {
			length.insert(tscs->items[i].item_length);
			sameLength.push_back(tscs->items[i].item_length);
			vector<int>tmp;
			tmp.push_back(i);
			sameLengthSet.push_back(tmp);
		}
		else {
			for (int j = 0; j < sameLength.size(); j++) {
				if (tscs->items[i].item_length == sameLength[j]) {
					sameLengthSet[j].push_back(i);
					break;
				}
			}
		}
		if (length.find(tscs->items[i].item_width) == length.end()) {
			length.insert(tscs->items[i].item_width);
			sameLength.push_back(tscs->items[i].item_width);
			vector<int>tmp;
			tmp.push_back(i);
			sameLengthSet.push_back(tmp);
		}
	}


	vector<Item>Items(tscs->items);
	
	int index = 0;//定义当前的工件的标引,往后遍历。
	vector<vector<Stripe>> newPatterns;//定义一个新的板材方案

	while (true) {
		double nowX = 0, nowY = 0;//定义方案输出
		bool allin = 1;//是否已经排布完成标志量,1为全部完成。
		for (int i = 0; i < Items.size(); i++) {
			if (Items[i].alreadyIn == 0) {
				allin = 0;
			}
		}
		if (allin == 1)break;

		vector<Stripe> Stripes;//定义板材上的Stripes
        
        //此循环首先进行第一步,生成初始的Stripes,对板材进行横向分层。
		while (index < Items.size() &&  nowY + Items[index].item_width <= tscs->plate_width) {
			if (Items[index].alreadyIn == 1) {//若当前工件已经放入,则看下一个。
				index++;
				continue;
			}
			Stripe newStripe;//定义一个新Stripe
			Stack newStack;//定义一个新Stack
			Item it = Items[index];
			Items[index].alreadyIn = 1;
			Items[index].item_X = nowX;
			Items[index].item_Y = nowY;
			nowY += Items[index].item_width;
			newStack.item_inT.push_back(Items[index]);
			newStack.TL = Items[index].item_length;
			newStack.TW = Items[index].item_width;
			index++;
            //若恰好碰到等长体,则放置在一起作为初始的Stripe。
			while (index < Items.size() && it.item_length == Items[index].item_length && nowY + Items[index].item_width <= tscs->plate_width) {
				if (Items[index].alreadyIn == 1) {
					index++;
					continue;
				}
				Items[index].alreadyIn = 1;
				Items[index].item_X = nowX;
				Items[index].item_Y = nowY;
				nowY += Items[index].item_width;
				newStack.TW += Items[index].item_width;
				newStack.item_inT.push_back(Items[index]);
				index++;
			}

			newStripe.stack_inS.push_back(newStack);
			newStripe.SL += newStack.TL;
			newStripe.SW = newStack.TW;

			Stripes.push_back(newStripe);

		}
        //此时板材上已经被划分为Stripes,并且Stripe中已经放置了一个Stack。
		//cout << Stripes.size() << endl;可输出Stripes的数量
        //对等宽体进行排序,有限放置长的合适的等宽体。
        
		for (int j = 0; j < sameLength.size() - 1; j++) {
			for (int jj = j + 1; jj < sameLength.size(); jj++) {
				if (sameLength[jj] > sameLength[j]) {
					swap(sameLength[j], sameLength[jj]);
					swap(sameLengthSet[j], sameLengthSet[jj]);
				}
			}
		}
		double totalY = nowY;

		for (int i = Stripes.size()-1; i >= 0; i--) {

			nowX = Stripes[i].SL;
			totalY -= Stripes[i].SW;

			while (true) {
				nowY = totalY;
				Stack newStack;
				//寻找最大合适等宽体
				int p = 0;
				double lastL = tscs->plate_length - Stripes[i].SL;
				while (p < sameLength.size()) {//当前等宽体遍历下标志;

					if (sameLength[p] < lastL) {//若等宽体满足长度约束,则添加
						for (int j = 0; j < sameLengthSet[p].size(); j++) {
							Item it = Items[sameLengthSet[p][j]];
							if (it.alreadyIn == 1)continue;//已放置,跳过
							//由于等宽体进行了旋转,则需要恢复旋转。
							
							if (sameLength[p] < it.item_length && newStack.TW + it.item_length > Stripes[i].SW)continue;//横放
							if (sameLength[p] > it.item_width && newStack.TW + it.item_width > Stripes[i].SW)continue;//纵放

							if (sameLength[p] <it.item_length) {
								//旋转回来
								swap(Items[sameLengthSet[p][j]].item_length, Items[sameLengthSet[p][j]].item_width);
								if (Items[sameLengthSet[p][j]].revolve) {
									Items[sameLengthSet[p][j]].revolve = 0;
								}
								else {
									Items[sameLengthSet[p][j]].revolve = 1;
								}
							}


							Items[sameLengthSet[p][j]].alreadyIn = 1;
							Items[sameLengthSet[p][j]].item_X = nowX;
							Items[sameLengthSet[p][j]].item_Y = nowY;
							nowY += Items[sameLengthSet[p][j]].item_width;
							newStack.item_inT.push_back(Items[sameLengthSet[p][j]]);
							newStack.TL = sameLength[p];
							newStack.TW += Items[sameLengthSet[p][j]].item_width;
						}
					}
					if (newStack.item_inT.size() == 0)p++;
					else break;
				}
				if (p == sameLength.size())break;
				else {
					Stripes[i].stack_inS.push_back(newStack);
					Stripes[i].SL += newStack.TL;
				}
			}
		}
		newPatterns.push_back(Stripes);
	}
    //输出结果到csv文件中。
	ofstream outFile;
	outFile.open("cut_program"+tscs->fn,ios::out);
	outFile << "原片材质" << ',' << "原片序号" << ',' << "产品id" << "," << "产品x坐标" << "," << "产品y坐标" << "," << "产品x方向长度" << "," << "产品y方向长度" << endl;

	for (int i = 0; i < newPatterns.size(); i++) {
		for (int j = 0; j < newPatterns[i].size(); j++) {
			for (int k = 0; k < newPatterns[i][j].stack_inS.size(); k++) {
				for (int x = 0; x < newPatterns[i][j].stack_inS[k].item_inT.size(); x++) {
					Item it = newPatterns[i][j].stack_inS[k].item_inT[x];
					outFile << it.item_material << ',' <<i<<',' << it.id << ',' << it.item_X << ',' << it.item_Y << ',' << it.item_length << ',' << it.item_width << endl;

				}
			}
		}
	}
	outFile.close();
	int useNum = newPatterns.size();
	
	double totalArea = 0;
	double totalUseArea = useNum * tscs->plate_length * tscs->plate_width;
	for (int i = 0; i < tscs->items.size(); i++) {
		totalArea += tscs->items[i].item_width * tscs->items[i].item_length;
	}
	cout << "使用板材数量:" << newPatterns.size() << endl;
	cout << "利用率为:" << totalArea / totalUseArea << endl;

};

至此,子问题1被顺利解决。

子问题2:

订单组批问题,订单组批问题为将不同的订单进行组批,划分成一个组来进行排样,实际上这个问题就是在子问题1的基础上考虑多个订单组合的问题,并考虑订单组合中的材质问题,本质上也是一个组合优化问题。

若要精确求解这个问题则难度会更大,达到一个无法估计的程度,因此我们果断放弃了精确建模方法,使用了贪心建模的方式,即最小化组批数量。

建立以下模型,并使用cplex求解:

Subject to

 

最后我们可以得到订单组合,即哪些订单被组批成了一个订单。

然后再将这些订单中的同材质的工件进行归类,送入子问题1的方法中进行求解,我们将子问题1的方法进行了些许改造以适应输出格式,代码如下:

void solveProblem(TSCS* tscs,int bathId) {//子问题1的改造代码,对同一组批中的相同材质的工件进行精确排样。

	sort(tscs->items.begin(), tscs->items.end());

	set<double>length;
	vector<double>sameLength;//相同长度列表
	vector<vector<int>>sameLengthSet;//相同长度下的工件集合
	for (int i = 0; i < tscs->items.size(); i++) {
		if (length.find(tscs->items[i].item_length) == length.end()) {
			length.insert(tscs->items[i].item_length);
			sameLength.push_back(tscs->items[i].item_length);
			vector<int>tmp;
			tmp.push_back(i);
			sameLengthSet.push_back(tmp);
		}
		else {
			for (int j = 0; j < sameLength.size(); j++) {
				if (tscs->items[i].item_length == sameLength[j]) {
					sameLengthSet[j].push_back(i);
					break;
				}
			}
		}
		if (length.find(tscs->items[i].item_width) == length.end()) {
			length.insert(tscs->items[i].item_width);
			sameLength.push_back(tscs->items[i].item_width);
			vector<int>tmp;
			tmp.push_back(i);
			sameLengthSet.push_back(tmp);
		}
	}


	vector<Item>Items(tscs->items);

	int index = 0;
	vector<vector<Stripe>> newPatterns;

	while (true) {
		double nowX = 0, nowY = 0;
		bool allin = 1;
		for (int i = 0; i < Items.size(); i++) {
			if (Items[i].alreadyIn == 0) {
				allin = 0;
			}
		}
		if (allin == 1)break;
		vector<Stripe> Stripes;
		while (index < Items.size() && nowY + Items[index].item_width <= tscs->plate_width) {
			if (Items[index].alreadyIn == 1) {
				index++;
				continue;
			}
			Stripe newStripe;
			Stack newStack;
			Item it = Items[index];
			Items[index].alreadyIn = 1;
			Items[index].item_X = nowX;
			Items[index].item_Y = nowY;
			nowY += Items[index].item_width;
			newStack.item_inT.push_back(Items[index]);
			newStack.TL = Items[index].item_length;
			newStack.TW = Items[index].item_width;
			index++;
			while (index < Items.size() && it.item_length == Items[index].item_length && nowY + Items[index].item_width <= tscs->plate_width) {
				if (Items[index].alreadyIn == 1) {
					index++;
					continue;
				}
				Items[index].alreadyIn = 1;
				Items[index].item_X = nowX;
				Items[index].item_Y = nowY;
				nowY += Items[index].item_width;
				newStack.TW += Items[index].item_width;
				newStack.item_inT.push_back(Items[index]);
				index++;
			}

			newStripe.stack_inS.push_back(newStack);
			newStripe.SL += newStack.TL;
			newStripe.SW = newStack.TW;

			Stripes.push_back(newStripe);

		}

		//cout << Stripes.size() << endl;

		for (int j = 0; j < sameLength.size() - 1; j++) {
			for (int jj = j + 1; jj < sameLength.size(); jj++) {
				if (sameLength[jj] > sameLength[j]) {
					swap(sameLength[j], sameLength[jj]);
					swap(sameLengthSet[j], sameLengthSet[jj]);
				}
			}
		}
		double totalY = nowY;

		for (int i = Stripes.size() - 1; i >= 0; i--) {

			nowX = Stripes[i].SL;
			totalY -= Stripes[i].SW;

			while (true) {
				nowY = totalY;
				Stack newStack;
				//寻找最大合适等宽体
				int p = 0;
				double lastL = tscs->plate_length - Stripes[i].SL;
				while (p < sameLength.size()) {//当前等宽体便利下标志;

					if (sameLength[p] < lastL) {//若等宽体满足长度约束,则添加
						for (int j = 0; j < sameLengthSet[p].size(); j++) {
							Item it = Items[sameLengthSet[p][j]];
							if (it.alreadyIn == 1)continue;//已放置,跳过
							//由于等宽体进行了旋转,则需要恢复旋转。

							if (sameLength[p] < it.item_length && newStack.TW + it.item_length > Stripes[i].SW)continue;//横放
							if (sameLength[p] > it.item_width && newStack.TW + it.item_width > Stripes[i].SW)continue;//纵放

							if (sameLength[p] < it.item_length) {
								//旋转回来
								swap(Items[sameLengthSet[p][j]].item_length, Items[sameLengthSet[p][j]].item_width);
								if (Items[sameLengthSet[p][j]].revolve) {
									Items[sameLengthSet[p][j]].revolve = 0;
								}
								else {
									Items[sameLengthSet[p][j]].revolve = 1;
								}
							}


							Items[sameLengthSet[p][j]].alreadyIn = 1;
							Items[sameLengthSet[p][j]].item_X = nowX;
							Items[sameLengthSet[p][j]].item_Y = nowY;
							nowY += Items[sameLengthSet[p][j]].item_width;
							newStack.item_inT.push_back(Items[sameLengthSet[p][j]]);
							newStack.TL = sameLength[p];
							newStack.TW += Items[sameLengthSet[p][j]].item_width;
						}
					}
					if (newStack.item_inT.size() == 0)p++;
					else break;
				}
				if (p == sameLength.size())break;
				else {
					Stripes[i].stack_inS.push_back(newStack);
					Stripes[i].SL += newStack.TL;
				}
			}
		}
		newPatterns.push_back(Stripes);
	}
	ofstream outFile;
	outFile.open("cut_program" + tscs->fn, ios::app);
	

	for (int i = 0; i < newPatterns.size(); i++) {
		for (int j = 0; j < newPatterns[i].size(); j++) {
			for (int k = 0; k < newPatterns[i][j].stack_inS.size(); k++) {
				for (int x = 0; x < newPatterns[i][j].stack_inS[k].item_inT.size(); x++) {
					Item it = newPatterns[i][j].stack_inS[k].item_inT[x];
					outFile << bathId<<',' << it.item_material << ',' << allUsePatterns << ',' << it.id << ',' << it.item_X << ',' << it.item_Y << ',' << it.item_length << ',' << it.item_width << endl;

				}
			}
		}
		allUsePatterns += 1;
	}
	outFile.close();
	
}

void solve2(TSCS* tscs) {//子问题2的求解函数
    //问题处理,赛题组在这个问题上确实有点不太懂编程的样子。吐槽一下:居然使用字符串当作类型,在程序中可太难判断了,没办法,这次使用vector进行前向逐个判断吧。
	vector<string>sameMaterial;
	vector<string>sameOrder;
	vector<double>OrderArea;
	vector<vector<int>>sameOrderSet;//相同订单集合
	
    //第一步,相同订单的放在一起,并计算订单的工件数和总面积,便于后续建模。
	for (int i = 0; i < tscs->items.size(); i++) {

		if (find(sameMaterial.begin(), sameMaterial.end(), tscs->items[i].item_material) == sameMaterial.end()) {
			sameMaterial.push_back(tscs->items[i].item_material);
		}

		if (find(sameOrder.begin(), sameOrder.end(), tscs->items[i].item_order) == sameOrder.end()) {
			sameOrder.push_back(tscs->items[i].item_order);
			vector<int>tmp = { i };
			sameOrderSet.push_back(tmp);
			OrderArea.push_back(tscs->items[i].item_width * tscs->items[i].item_length);
		}
		else {
			for (int j = 0; j < sameOrder.size(); j++) {
				if (tscs->items[i].item_order == sameOrder[j]) {
					sameOrderSet[j].push_back(i);
					OrderArea[j]+=tscs->items[i].item_width * tscs->items[i].item_length;
					break;
				}
			}
		}
	}
    
	
    //X_i组批数量的最大值,每个组批一个订单。
	int maxBatchNum = sameOrder.size();
	
    //cplex建模过程
	IloEnv env;
	IloModel model(env);
	IloCplex solver(model);

	IloNumVarArray X_i(env,maxBatchNum,0,1,ILOBOOL);
	IloArray<IloNumVarArray>x_ij(env);

	IloExpr expr(env);
	solver.setOut(env.getNullStream());

	for (int i = 0; i < maxBatchNum; i++) {
		expr += X_i[i];
		IloNumVarArray x_i(env, sameOrder.size(),0,1,ILOBOOL);
		x_ij.add(x_i);
	}
	IloObjective obj = IloMinimize(env,expr);
	model.add(obj);
	IloRangeArray con(env);
	//添加数量约束
	int conNum = 0;
	for (int i = 0; i < maxBatchNum; i++) {
		con.add(IloRange(env, 0, 1000));
		IloExpr expr2(env);
		for (int j = 0; j < sameOrder.size(); j++) {
			expr2 += int(sameOrderSet.size()) * x_ij[i][j];
		}
		con[i].setExpr(expr2);
		conNum++;
	}

	for (int i = 0; i < maxBatchNum; i++) {
		con.add(IloRange(env, 0, 250000000));
		IloExpr expr2(env);
		for (int j = 0; j < sameOrder.size(); j++) {
			expr2 +=OrderArea[j] * x_ij[i][j];
		}
		con[i+maxBatchNum].setExpr(expr2);
		conNum++;
	}

	for (int i = 0; i < maxBatchNum; i++) {
		for (int j = 0; j < sameOrder.size(); j++) {
			con.add(IloRange(env,0,IloInfinity));
			IloExpr e(env);
			e = X_i[i] - x_ij[i][j];
			con[conNum].setExpr(e);
			conNum++;
		}
	}

	for (int j = 0; j < sameOrder.size(); j++) {
		IloExpr expr3(env);
		for (int i = 0; i < maxBatchNum; i++) {
			expr3 += x_ij[i][j];
		}
		con.add(IloRange(env, 1, 1));
		con[conNum].setExpr(expr3);
		conNum++;
	}

	model.add(con);
	vector<vector<int>>batchs;
	if (solver.solve()) {
        //求解,输出结果
		cout << "Status=" << solver.getStatus() << endl;
		cout << "Value=" << solver.getObjValue() << endl;

		//记录批次中的订单号
		for (int i = 0; i <int(solver.getObjValue()); i++) {
			vector<int>b;
			for (int j = 0; j < sameOrder.size(); j++) {
				if (solver.getValue(x_ij[i][j]) > 0) {
					b.push_back(j);
				}
			}
			batchs.push_back(b);
		}
	}
	ofstream o;
	o.open("cut_program" + tscs->fn, ios::out);
	o << "批次序号" << ',' << "原片材质" << ',' << "原片序号" << ',' << "产品id" << ", " << "产品x坐标" << ", " << "产品y坐标" << ", " << "产品x方向长度" << ", " << "产品y方向长度" << endl;
	o.close();
    //统计每个批次中的同材料,并调用子问题1的方法进行精确排样。
	for (int i = 0; i < batchs.size(); i++) {
		//第i个组里面的订单
		
		for (int j = 0; j < sameMaterial.size(); j++){
			//多订单中同种类型板材集合
			vector<Item>batch_items;
			
			for (int k = 0; k < batchs[i].size(); k++) {

				for (int z = 0; z < sameOrderSet[batchs[i][k]].size(); z++) {
					//遍历多个订单中的材料,如果发现有这个材料则加入求解组。
					if (tscs->items[sameOrderSet[batchs[i][k]][z]].item_material == sameMaterial[j]) {
						batch_items.push_back(tscs->items[sameOrderSet[batchs[i][k]][z]]);
					}

				}


			}
			if (batch_items.size() == 0) {
				continue;
			}
            //调用子问题1方法进行求解
			TSCS tscs2(batch_items);
			tscs2.fn = tscs->fn;
			solveProblem(&tscs2,i);

		}
		cout << "组批" << i << "完成" << endl;
	}

	//统计输出结果,完成
	double totalArea=0;
	double totalUseArea = allUsePatterns  * tscs->plate_length * tscs->plate_width;
	for (int i = 0; i < tscs->items.size(); i++) {
		totalArea += tscs->items[i].item_width * tscs->items[i].item_length;
	}

	cout << "使用板材数量:" << allUsePatterns << endl;
	cout << "利用率为:" << totalArea / totalUseArea  << endl;
}

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值