在之前的两篇文章中我们建立了JSP问题的数学模型(机器调度(JSP)问题数学模型)和使用java调用cplex求解JSP问题(JSP(机器调度问题)使用java进行数学建模并调用cplex求解)。本篇文章将使用java编程语言给出更一般的JSP模型的代码,使得可以从键盘输入任意数据进行求解。
首先我们给出JSP问题的数学模型:
我们将从键盘上依次输入以下信息:工件的个数,机器的个数,每个工件的工序(即在哪些机器上面加工),每个工件的加工时间。
首先定义三个类:Vertex,Edge,Graph
Vertex类:
package MethodOfJSP;
import java.util.ArrayList;
import java.util.List;
import ilog.concert.IloNumVar;
public class Vertex {
public int id;
public int processedTime;//使用时记得转换成int
public IloNumVar completionTime;
public List<Edge> flowOutEdges = new ArrayList<>();
public List<Edge> flowInEdges = new ArrayList<>();
public int machine;//使用时记得转换成int
public int workpiece;
}
Edge类:
package MethodOfJSP;
import ilog.concert.IloIntVar;
public class Edge {
public int id;
public Vertex head;//弧的头顶点
public Vertex tail;//弧的尾顶点
public IloIntVar X;//根据每一条弧是否被选择定义的0-1变量,如果被选择取1,否则取0
}
Graph类:
package MethodOfJSP;
import java.util.ArrayList;
import java.util.List;
public class Graph {
List<Vertex> V = new ArrayList<>();
List<Edge> E = new ArrayList<>();
//定义E_{I}:实线弧集合
public List<Edge> EI = new ArrayList<>();
//定义A1:虚线弧集合(不带虚拟的顶点和终点)
public List<Edge> A1 = new ArrayList<>();
//定义A1:虚线弧集合(带虚拟的顶点和终点)
public List<Edge> A2 = new ArrayList<>();
//定义UJOUT:虚拟的开始节点
public List<Vertex> UJOUT = new ArrayList<>();
//定义UJIN:虚拟的结束节点
public List<Vertex> UJIN = new ArrayList<>();
//定义UJMid:中间节点(实际节点)
public List<Vertex> UJMid = new ArrayList<>();
//定义机器对应的点集合
public List<List<Vertex>> MachineSet = new ArrayList<>();
}
现在来创建我们运行的主类:
package MethodOfJSP;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import ilog.concert.IloException;
import ilog.concert.IloNumExpr;
import ilog.concert.IloNumVar;
import ilog.cplex.IloCplex;
public class 输入数据 {
public static void main(String[] args) {
//后面的代码是在这里进行编写的
}
}
先创建一个Graph对象:
Graph graph = new Graph();
因为需要从键盘输入,所以我们需要创建一个Scanner对象:
//创建一个sc对象
Scanner sc = new Scanner(System.in);
现在编写从键盘输入工件数量和机器数量的代码:
//输入工件数量capacity
System.out.println("请输入工件的数量:");
int capacity = sc.nextInt();
//输入机器数量numberOfMachine
System.out.println("请输入机器的数量:");
int numberOfMachine = sc.nextInt();
然后我们需要从键盘输入每个工件的工序。为了使JSP的数学模型转换成图的模型(每一个工序代表图上的一个顶点),以及方便后续边的集合创建,所以我们准备将所有的工序按照工件来划分。由于数组在定义的时候需要确定数组的长度,而JSP问题的一般模型可能是不确定长度的,因此我们使用List,这里需要使用List嵌套。我们来定义与工序相关的一些集合,如每道工序的加工机器,每道工序的加工时间,以及按照每个工件来进行划分的工序集合。
//将工序按机器进行划分
List<List<Integer>> machine = new ArrayList<>();
//定义加工时间,从键盘输入
List<List<Integer>> processedTime = new ArrayList<>();
//定义按照工件划分的点集合
List<List<Vertex>> vertexSetOfAll = new ArrayList<>();
现在开始编写从键盘上开始输入数据的代码,由于从键盘上面输入的是字符串类型的数据,我们要将其转换成为int整数型的数据,以方便后续的调用以及使用。为确保输入数据的正确性,我们将每一组数据输入后进行打印,方便我们确认数据输入的正确性。
//输入每个工件的工序,即输入工件在各个工序上加工的机器
int i = 1;
while(i <= capacity) {
System.out.println("请以数组的形式输入第"+i+"个工件的流程,并用逗号隔开:");
String str = sc.next();
String arrProcess[] = str.split(",");//将输入的数组按逗号分隔开
int strArrLen = arrProcess.length;
int[] intArrProcess = new int[strArrLen];//定义intArrProcess数组,方便后续取出元素
//定义按照工件划分的点集合vertexSet
List<Vertex> vertexSet = new ArrayList<>();//创建按工件划分的工序(点)集合
for(int a = 0; a < strArrLen; a++) {
Vertex v = new Vertex();
vertexSet.add(v);//向vertexSet中添加元素
graph.UJMid.add(v);//向UJMid中添加元素
}
vertexSetOfAll.add(vertexSet);//向vertexSetOfAll中添加元素
for(int a = 0; a < strArrLen; a++) {
try {
intArrProcess[a] = Integer.parseInt(arrProcess[a]);
}catch (NumberFormatException e) {
e.printStackTrace();
return;
}
}
//将数组转换为List类型
ArrayList<Integer> list = new ArrayList<>();
for(int j = 0;j < intArrProcess.length; j++) {
list.add(intArrProcess[j]);
}
System.out.println(" ");
i++;
System.out.println(list);
machine.add(list);
}
System.out.println(machine);//工件1从0开始,依次为0,1,2,3……
类似的,我们输入各个工件的加工时间,代码如下:
//输入每个工件的加工时间
int j = 1;
while(j <= capacity) {
System.out.println("请按顺序以数组的形式输入第"+j+"个工件的加工时间,并用逗号隔开:");
String str = sc.next();
String arrProcessedTime[] = str.split(",");//将输入的数组按逗号分隔开
int strArrLen = arrProcessedTime.length;
int[] intArrProcessTime = new int[strArrLen];//定义intArrProcess数组,方便后续取出元素
for(int a = 0; a < strArrLen; a++) {
try {
intArrProcessTime[a] = Integer.parseInt(arrProcessedTime[a]);
}catch (NumberFormatException e) {
e.printStackTrace();
return;
}
}
//将数组转换为List类型
ArrayList<Integer> list = new ArrayList<>();
for(int k = 0;k < arrProcessedTime.length; k++) {
list.add(intArrProcessTime[k]);
}
j++;
System.out.println(list);
processedTime.add(list);
}
System.out.println(processedTime);//工件1从0开始,依次为0,1,2,3……
然后我们按照各个工件的加工工序,来定义实线弧集合。为确保我们输入数据的正确性,我们在本代码段结尾处打印实线弧集合的大小:
//定义实现弧集合EI
for(List<Vertex> vertexOfSet : vertexSetOfAll) {
for(int a = 0; a < vertexOfSet.size()-1; a++) { //注意:边的数目是顶点的数目减1
Edge e = new Edge();//定义实线边
e.head = vertexOfSet.get(a+1);//定义边的头顶点
e.tail = vertexOfSet.get(a);//定义边的尾顶点
graph.EI.add(e);//向EI中添加实线边
}
}
System.out.println("实线弧集合的大小为"+graph.EI.size());//输出EI的大小
现在我们给每一道工序(即工序对应的顶点)添加加工时间和所使用的机器:
//给每个顶点赋予machine和processedTime两个值
for(int a = 0; a < vertexSetOfAll.size(); a++) {
for(int b = 0; b < vertexSetOfAll.get(a).size(); b++) {
vertexSetOfAll.get(a).get(b).machine = machine.get(a).get(b);
vertexSetOfAll.get(a).get(b).processedTime = processedTime.get(a).get(b);
}
}
为定义对于每一台机器所有加工工序的之间待求的弧,我们需要将在同一台机器上加工的工序(点)组成新的集合,使得新的集合是按照机器来划分的。同样的,我们使用List嵌套来创建集合。
//定义MachineSetOfAll,将在同一台机器上完成的工序组成新的集合
List<List<Vertex>> MachineSetOfAll = new ArrayList<>();//使用List嵌套创建新的集合
for(int a = 1; a <= numberOfMachine; a++) {
List<Vertex> MachineSet = new ArrayList<>();//创建在同一台机器上加工工序的集合
for(Vertex v : graph.UJMid) {
if(v.machine == a) {
MachineSet.add(v);//将工序(点)添加到集合中
}
}
MachineSetOfAll.add(MachineSet);
}
现在我们向属于同一个机器里的工序添加虚线弧A1:
//向属于同一个机器里的工序添加虚线弧A1
for(int a = 0 ; a < MachineSetOfAll.size(); a++) {
for(int b = 0 ; b < MachineSetOfAll.get(a).size(); b++) {
for(int c = 0 ; c < MachineSetOfAll.get(a).size(); c++) {
while(!(b == c)) {
Edge e = new Edge();
e.head = MachineSetOfAll.get(a).get(b);
e.tail = MachineSetOfAll.get(a).get(c);
graph.A1.add(e);
graph.A2.add(e);
break;
}
}
}
}
System.out.println("虚线弧集合A1的大小为"+graph.A1.size());//输出虚线弧集合A的大小
开始向属于同一个机器里的工序添加UJIN和UJOUT:
//向属于同一个机器里的工序添加UJIN和UJOUT
for(int a = 0 ; a < MachineSetOfAll.size(); a++) {
Vertex v0 = new Vertex();
Vertex vM = new Vertex();
for(int b = 0 ; b < MachineSetOfAll.get(a).size(); b++) {
Edge e = new Edge();//虚拟的开始弧
Edge w = new Edge();//虚拟的结束弧
e.head = MachineSetOfAll.get(a).get(b);
e.tail = v0;
w.head = vM;
w.tail = MachineSetOfAll.get(a).get(b);
v0.flowOutEdges.add(e);
vM.flowInEdges.add(w);
graph.A2.add(e);
graph.A2.add(w);
}
graph.UJIN.add(vM);
graph.UJOUT.add(v0);
}
System.out.println("加入虚拟开始和结束顶点后虚线弧集合A2的大小为"+graph.A2.size());//输出虚线弧集合A的大小
给所有实际工序(实际的点)添加流入边集合和流出边集合:
//给所有中间点添加流入边集合
for(Edge e : graph.A2) {
for(Vertex v : graph.UJMid) {
if(e.head == v) {
v.flowInEdges.add(e);
}
}
}
//给所有中间点添加流出边集合
for(Edge e : graph.A2) {
for(Vertex v : graph.UJMid) {
if(e.tail == v) {
v.flowOutEdges.add(e);
}
}
}
现在我们调用cplex进行求解
下面的链接是:使用java进行对JSP问题数学建模,分析并调用cplex求解的文章
JSP(机器调度问题)使用java进行数学建模并调用cplex求解https://blog.csdn.net/whq002/article/details/126050391
//求解步骤
try {
//定义cplex环境变量
IloCplex cplex = new IloCplex();
//初始化决策变量completionTime
for(Vertex v:graph.UJMid) {
v.completionTime = cplex.numVar(0, Double.MAX_VALUE);
}
//初始化决策变量X
for(Edge e:graph.A2) {
e.X = cplex.intVar(0, 1);
}
for(Edge e:graph.EI) {
e.X = cplex.intVar(0, 1);
}
for(Vertex v:graph.UJIN) {
for(int a = 0; a < v.flowInEdges.size(); a++) {
v.flowInEdges.get(a).X = cplex.intVar(0, 1);
}
}
for(Vertex v:graph.UJOUT) {
for(int a = 0; a < v.flowOutEdges.size(); a++) {
v.flowOutEdges.get(a).X = cplex.intVar(0, 1);
}
}
for(Vertex v:graph.UJMid) {
for(int a = 0; a < v.flowOutEdges.size(); a++) {
v.flowOutEdges.get(a).X = cplex.intVar(0, 1);
}
}
for(Vertex v:graph.UJMid) {
for(int a = 0; a < v.flowInEdges.size(); a++) {
v.flowInEdges.get(a).X = cplex.intVar(0, 1);
}
}
//定义目标函数
IloNumExpr obj = cplex.numExpr();
IloNumVar c = cplex.intVar(0, (int) Double.MAX_VALUE);
for(Vertex v: graph.UJMid) {
cplex.addGe(c,v.completionTime);
}
obj = cplex.sum(obj,cplex.prod(c, 1));
cplex.addMinimize(obj);
//约束条件1:
for(Vertex v : graph.UJMid) {
IloNumExpr cs1 = cplex.numExpr();
cs1 = cplex.sum(cs1,cplex.prod(v.completionTime,1));
cplex.addGe(cs1,v.processedTime);
}
//约束条件2:
for(Edge edge: graph.EI) {
IloNumExpr cs2 = cplex.numExpr();
Vertex v = edge.head;
Vertex u = edge.tail;
cs2 = cplex.sum(cs2,cplex.prod(v.completionTime,1));
cs2 = cplex.sum(cs2,cplex.prod(u.completionTime,-1));
cplex.addGe(cs2,v.processedTime);
}
//约束条件3:流出来的弧的数量之和等于1
for(Vertex vertexOut:graph.UJOUT) {
IloNumExpr cs3 = cplex.numExpr();
for(int a = 0; a < vertexOut.flowOutEdges.size(); a++) {
Edge outEdge = vertexOut.flowOutEdges.get(a);
cs3 = cplex.sum(cs3,cplex.prod(outEdge.X, 1));
}
cplex.addEq(cs3,1);
}
//约束条件4:流进来的弧的数量之和等于1
for(Vertex vertexIn:graph.UJIN) {
IloNumExpr cs4 = cplex.numExpr();
for(int a = 0; a < vertexIn.flowInEdges.size(); a++) {
Edge inEdge = vertexIn.flowInEdges.get(a);
cs4 = cplex.sum(cs4,cplex.prod(inEdge.X, 1));
}
cplex.addEq(cs4,1);
}
//约束条件5:每个中间节点指向该节点的边数等于指出该节点的边数且等于1
for(Vertex vertexMid:graph.UJMid) {
IloNumExpr cs5 = cplex.numExpr();
IloNumExpr cs6 = cplex.numExpr();
for(int a = 0; a < vertexMid.flowInEdges.size(); a++) {
Edge inEdge = vertexMid.flowInEdges.get(a);
cs5 = cplex.sum(cs5,cplex.prod(inEdge.X, 1));
}
for(int a = 0; a < vertexMid.flowOutEdges.size(); a++) {
Edge outEdge = vertexMid.flowOutEdges.get(a);
cs6 = cplex.sum(cs6,cplex.prod(outEdge.X, 1));
}
cplex.addEq(cs5,cs6);
cplex.addEq(cs6,1);
}
//约束条件6:在同一台机器上面加工的零件,紧前工序的完工时刻加上紧后工序的加工时间小于等于紧后工序的完工时刻
for(Edge edge: graph.A1) {
IloNumExpr cs7 = cplex.numExpr();
cs7 = cplex.sum(cs7,cplex.prod(edge.head.completionTime,1));
cs7 = cplex.sum(cs7,cplex.prod(edge.tail.completionTime,-1));
cs7 = cplex.sum(cs7,cplex.prod(edge.X,-10000));
cs7 = cplex.sum(cs7,10000);
cplex.addGe(cs7,edge.head.processedTime);
}
//求解
if(cplex.solve()) {
cplex.output().println("Solution status = " + cplex.getStatus());
cplex.output().println("Solution value = " + cplex.getObjValue());
}
}catch (IloException e) {
e.printStackTrace();
}
我们仍使用上次给的案例,下图为案例的图的模型和加工时间,详细的图的模型的构建文章在:
机器调度(JSP)问题数学模型https://mp.csdn.net/mp_blog/creation/editor/125901441
数据输入:
求解结果主要部分截图: