Prechádzať zdrojové kódy

二维装箱禁忌搜索算法

24282 1 rok pred
rodič
commit
10a278e485

+ 92 - 0
sd-framework/src/main/java/com/sd/framework/util/packing/Application.java

@@ -0,0 +1,92 @@
+package com.sd.framework.util.packing;
+
+
+import com.sd.framework.util.packing.entity.Instance;
+import com.sd.framework.util.packing.entity.PlaceSquare;
+import com.sd.framework.util.packing.entity.Solution;
+import com.sd.framework.util.packing.model.TabuSearch;
+import javafx.scene.Scene;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.paint.Color;
+import javafx.stage.Stage;
+
+import java.util.Random;
+
+/**
+ * 二维装箱禁忌搜索算法
+ */
+public class Application extends javafx.application.Application {
+
+    private int counter = 0;
+
+    public static void main(String[] args) {
+        launch(args);
+    }
+
+    @Override
+    public void start(Stage primaryStage) throws Exception {
+
+        String path = "src/main/resources/data.txt";
+
+        TabuSearch model = new TabuSearch(ReadDataUtil.getInstance(path));
+        Solution solution = model.search();
+
+        Instance instance = solution.getInstance();
+        AnchorPane pane = new AnchorPane();
+        plot(solution);
+        Canvas canvas = new Canvas(instance.getLength(), instance.getWidth());
+        pane.getChildren().add(canvas);
+        canvas.relocate(100, 100);
+        // 绘制最外层的矩形
+        draw(canvas, 0, 0, instance.getLength(), instance.getWidth());
+        // 添加按钮
+        Button nextButton = getButton(solution, canvas);
+        pane.getChildren().add(nextButton);
+        primaryStage.setTitle("二维下料可视化");
+        primaryStage.setScene(new Scene(pane, 1000, 1000, Color.AQUA));
+        primaryStage.show();
+    }
+
+    private Button getButton(Solution solution, Canvas canvas) {
+        Button nextButton = new Button("Next");
+        nextButton.setOnAction(actionEvent -> {
+            try {
+                PlaceSquare placeSquare = solution.getPlaceSquareList().get(counter);
+                draw(canvas, placeSquare.getX(), placeSquare.getY(), placeSquare.getL(), placeSquare.getW());
+                counter++;
+            } catch (Exception e) {
+                Alert alert = new Alert(Alert.AlertType.WARNING);
+                alert.setContentText("已经没有可以放置的矩形了!");
+                alert.showAndWait();
+            }
+        });
+        return nextButton;
+    }
+
+    private void plot(Solution solution) {
+        System.out.println(solution);
+
+        // 绘制里面的矩形
+//        List<PlaceSquare> placeSquareList = solution.getPlaceSquareList();
+//        System.out.println(placeSquareList.size());
+//        for (PlaceSquare placeSquare : placeSquareList) {
+//            canvas = draw(canvas,placeSquare.getX(),placeSquare.getY(),placeSquare.getL(),placeSquare.getW());
+//        }
+    }
+
+    private void draw(Canvas canvas, double x, double y, double l, double w) {
+        GraphicsContext gc = canvas.getGraphicsContext2D();
+        // 边框
+        gc.setStroke(Color.BLACK);
+        gc.setLineWidth(2);
+        gc.strokeRect(x, y, l, w);
+        // 填充
+        gc.setFill(new Color(new Random().nextDouble(), new Random().nextDouble(), new Random().nextDouble(), new Random().nextDouble()));
+        gc.fillRect(x, y, l, w);
+    }
+}
+

+ 34 - 0
sd-framework/src/main/java/com/sd/framework/util/packing/ReadDataUtil.java

@@ -0,0 +1,34 @@
+package com.sd.framework.util.packing;
+
+import cn.hutool.core.util.IdUtil;
+import com.sd.framework.util.packing.entity.Instance;
+import com.sd.framework.util.packing.entity.Square;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ReadDataUtil {
+    public static Instance getInstance(String path) throws IOException {
+        BufferedReader bufferedReader = new BufferedReader(new FileReader(path));
+        String input;
+        Instance instance = new Instance();
+        List<Square> squareList = new ArrayList<>();
+        boolean isFirstLine = true;
+        while ((input = bufferedReader.readLine()) != null) {
+            String[] split = input.split(" ");
+            if (isFirstLine) {
+                instance.setLength(Double.parseDouble(split[0]));
+                instance.setWidth(Double.parseDouble(split[1]));
+                instance.setRotateEnable(split[2].equals("1"));
+                isFirstLine = false;
+            } else {
+                squareList.add(new Square(IdUtil.getSnowflakeNextId(), Double.parseDouble(split[0]), Double.parseDouble(split[1])));
+            }
+        }
+        instance.setSquareList(squareList);
+        return instance;
+    }
+}

+ 24 - 0
sd-framework/src/main/java/com/sd/framework/util/packing/entity/Instance.java

@@ -0,0 +1,24 @@
+package com.sd.framework.util.packing.entity;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class Instance {
+    /**
+     * 长
+     */
+    double length;
+    /**
+     * 宽
+     */
+    double width;
+    /**
+     * 是否可以旋转
+     */
+    private boolean isRotateEnable = true;
+
+    private List<Square> squareList;
+
+}

+ 28 - 0
sd-framework/src/main/java/com/sd/framework/util/packing/entity/PlacePoint.java

@@ -0,0 +1,28 @@
+package com.sd.framework.util.packing.entity;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+public class PlacePoint implements Comparable<PlacePoint> {
+    private double x, y, length;
+
+    public PlacePoint(double x, double y, double length) {
+        this.x = x;
+        this.y = y;
+        this.length = length;
+    }
+
+    @Override
+    public int compareTo(PlacePoint placePoint) {
+        // 优先往下排 然后优先往左排
+        int compare_y = Double.compare(y, placePoint.y);
+        if (compare_y != 0) {
+            return compare_y;
+        } else {
+            return Double.compare(x, placePoint.x);
+        }
+    }
+
+}

+ 13 - 0
sd-framework/src/main/java/com/sd/framework/util/packing/entity/PlaceSquare.java

@@ -0,0 +1,13 @@
+package com.sd.framework.util.packing.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class PlaceSquare {
+    private Long id;
+    private double x, y, l, w;
+}

+ 17 - 0
sd-framework/src/main/java/com/sd/framework/util/packing/entity/Solution.java

@@ -0,0 +1,17 @@
+package com.sd.framework.util.packing.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class Solution {
+    private double rate;
+    private Instance instance;
+    private List<Square> squareList;
+    private List<PlaceSquare> placeSquareList;
+}

+ 13 - 0
sd-framework/src/main/java/com/sd/framework/util/packing/entity/Square.java

@@ -0,0 +1,13 @@
+package com.sd.framework.util.packing.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Square {
+    private Long id;
+    private double l, w;
+}

+ 50 - 0
sd-framework/src/main/java/com/sd/framework/util/packing/model/TabuMapTree.java

@@ -0,0 +1,50 @@
+package com.sd.framework.util.packing.model;
+
+import com.sd.framework.util.packing.entity.Square;
+import lombok.Data;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class TabuMapTree {
+
+    Map<Long, TabuMapTree> sonTreeMap = new HashMap<>();
+
+    Square nodeSquare;
+
+    public void add(List<Square> squareList, int index) {
+        if (index >= squareList.size()) {
+            return;
+        }
+        if (nodeSquare == null) {
+            nodeSquare = squareList.get(index);
+            index++;
+        }
+        Square square = squareList.get(index);
+        Long id = square.getId();
+        if (sonTreeMap.containsKey(id)) {
+            sonTreeMap.get(id).add(squareList, index + 1);
+        } else {
+            TabuMapTree tabuMapTree = new TabuMapTree();
+            tabuMapTree.setNodeSquare(square);
+            sonTreeMap.put(id, tabuMapTree);
+            sonTreeMap.get(id).add(squareList, index + 1);
+        }
+    }
+
+    public boolean contains(List<Square> squareList, int index) {
+        if (index >= squareList.size()) {
+            return true;
+        }
+        Square square = squareList.get(index);
+        Long id = square.getId();
+        if (sonTreeMap.containsKey(id)) {
+            return sonTreeMap.get(id).contains(squareList, index + 1);
+        } else {
+            return false;
+        }
+    }
+
+}

+ 328 - 0
sd-framework/src/main/java/com/sd/framework/util/packing/model/TabuSearch.java

@@ -0,0 +1,328 @@
+package com.sd.framework.util.packing.model;
+
+import com.sd.framework.util.packing.entity.*;
+import lombok.Data;
+
+import java.util.*;
+
+@Data
+public class TabuSearch {
+
+    /**
+     * 最大的迭代次数(提高这个值可以稳定地提高解质量,但是会增加求解时间)
+     */
+    public final int MAX_GEN = 10;
+    /**
+     * 每次搜索领域的个数(这个值不要太大,太大的话搜索效率会降低)
+     */
+    public final int N = 200;
+    /**
+     * 禁忌队列
+     */
+    public HashMap<Long, TabuMapTree> tabuTreeMap = new HashMap<>();
+    /**
+     * 矩形数量
+     */
+    public int sqNum;
+    /**
+     * 初始顺序
+     */
+    public List<Square> initGhh;
+    /**
+     * 最佳顺序
+     */
+    public List<Square> bestGh;
+    /**
+     * 当前最好顺序
+     */
+    public List<Square> LocalGh;
+    /**
+     * 存放临时顺序
+     */
+    public List<Square> tempGh;
+    /**
+     * 最佳的迭代次数
+     */
+    public int bestT;
+    /**
+     * 最优解
+     */
+    public Solution bestSolution;
+    /**
+     * 每次领域搜索的最优解(领域最优解)
+     */
+    public Solution LocalSolution;
+    /**
+     * 临时解
+     */
+    public Solution tempSolution;
+    /**
+     * 当前迭代
+     */
+    public int t;
+    /**
+     * 随机函数对象
+     */
+    public Random random;
+    /**
+     * 问题实例
+     */
+    public Instance instance;
+    /**
+     * 长
+     */
+    double length;
+    /**
+     * 宽
+     */
+    double width;
+
+    public TabuSearch(Instance instance) {
+        this.instance = instance;
+        this.initGhh = new ArrayList<>(instance.getSquareList());
+        // 初始化变量
+        random = new Random(System.currentTimeMillis());
+        length = instance.getLength();
+        width = instance.getWidth();
+        sqNum = initGhh.size();
+    }
+
+    public Solution search() {
+        long start = System.currentTimeMillis();
+        // 获取初始解
+        getInitSolution();
+        System.out.println(bestSolution.getRate());
+        //开始迭代,停止条件为达到指定迭代次数
+        while (t < MAX_GEN) {
+            //当前领域搜索次数
+            int n = 0;
+            LocalSolution = new Solution();
+            LocalSolution.setRate(0);
+            while (n < N) {
+                // 随机打乱顺序 得到当前编码Ghh的邻居编码tempGh
+                tempGh = generateNewGh(new ArrayList<>(initGhh));
+                // 判断其是否在禁忌表中
+                if (!judge(tempGh)) {
+                    //加入禁忌表
+                    enterTabooList(tempGh);
+                    tempSolution = evaluate(new ArrayList<>(tempGh));
+                    if (tempSolution.getRate() > LocalSolution.getRate()) {
+                        // 如果临时解优于本次领域搜索的最优解
+                        // 那么就将临时解替换本次领域搜索的最优解
+                        LocalGh = new ArrayList<>(tempGh);
+                        LocalSolution = tempSolution;
+                    }
+                }
+                n++;
+            }
+            if (LocalSolution.getRate() > bestSolution.getRate()) {
+                //如果本次搜索的最优解优于全局最优解
+                //那么领域最优解替换全局最优解
+                bestT = t;
+                bestGh = new ArrayList<>(LocalGh);
+                bestSolution = LocalSolution;
+            }
+            initGhh = new ArrayList<>(LocalGh);
+            t++;
+            System.out.println("当前迭代次数为:" + t + ",当前最佳利用率为:" + bestSolution.getRate());
+        }
+        //求解完毕
+        System.out.println("最佳迭代次数:" + bestT);
+        System.out.println("最佳利用率为:" + bestSolution.getRate());
+        System.out.println("用时:" + (System.currentTimeMillis() - start) + "ms");
+        return bestSolution;
+    }
+
+    //评价函数
+    public Solution evaluate(List<Square> squareList) {
+        Solution solution = new Solution();
+        solution.setInstance(instance);
+        solution.setSquareList(new ArrayList<>(squareList));
+        List<PlaceSquare> placeSquareList = new ArrayList<>();
+        // 创建初始可放置角点
+        List<PlacePoint> placePointList = new ArrayList<>();
+        placePointList.add(new PlacePoint(0, 0, length));
+
+        // 开始按照顺序和规则放置
+        for (int i = 0; i < placePointList.size(); ) {
+            PlacePoint placePoint = placePointList.get(i);
+            double maxMark = -1.0d;
+            int maxIndex = -1;
+            double isRotate = -1, curMarks = -1;
+            for (int j = 0; j < squareList.size(); j++) {
+                Square square = squareList.get(j);
+                double[] arr = getMarks(placePoint, square, placeSquareList);
+                double is_rotate = arr[0];
+                curMarks = arr[1];
+                if (curMarks > 0 && curMarks > maxMark) {
+                    maxMark = curMarks;
+                    maxIndex = j;
+                    isRotate = is_rotate;
+                }
+            }
+            if (maxIndex < 0 && i < placePointList.size()) {
+                i++;
+            } else if (maxIndex < 0) {
+                break;
+            } else {
+                Square square = squareList.remove(maxIndex);
+                double l = square.getL();
+                double w = square.getW();
+                if (isRotate > 0) {
+                    // 表示进行了旋转
+                    square.setL(w);
+                    square.setW(l);
+                }
+                // 移除当前角点
+                placePointList.remove(i);
+                //新增已放置的square
+                placeSquareList.add(new PlaceSquare(square.getId(), placePoint.getX(), placePoint.getY(), square.getL(), square.getW()));
+                // 新增两个可行角点
+                double surplus = placePoint.getLength() - square.getL(); // 剩余长度
+                if (surplus > 0) {
+                    placePointList.add(new PlacePoint(placePoint.getX() + square.getL(), placePoint.getY(), surplus));
+                }
+                placePointList.add(new PlacePoint(placePoint.getX(), placePoint.getY() + square.getW(), square.getL()));
+                // 重新排序
+                Collections.sort(placePointList);
+                i = 0;
+                // 还原矩形
+                if (isRotate > 0) {
+                    // 表示进行了旋转
+                    square.setL(l);
+                    square.setW(w);
+                }
+            }
+        }
+        // 设置已经放置的矩形列表
+        solution.setPlaceSquareList(new ArrayList<>(placeSquareList));
+        // 计算利用率
+        double rate;
+        double s = 0.0f;
+        for (PlaceSquare placeSquare : placeSquareList) {
+            s += (placeSquare.getL() * placeSquare.getW());
+        }
+        rate = s / (length * width);
+        solution.setRate(rate);
+        return solution;
+    }
+
+    // 评价该点的得分
+    private double[] getMarks(PlacePoint placePoint, Square square, List<PlaceSquare> placeSquareList) {
+        // 返回{是否旋转,分数}
+        double delta, mark1, mark2;
+        PlaceSquare placeSquare = new PlaceSquare(square.getId(), placePoint.getX(), placePoint.getY(), square.getL(), square.getW());
+        if (isOverlap(placeSquareList, placeSquare)) {
+            mark1 = -1.0d;
+        } else {
+            delta = Math.abs(placePoint.getLength() - square.getL());
+            mark1 = 1 - delta / placePoint.getLength();
+        }
+        mark2 = -1.0d;
+        if (instance.isRotateEnable()) {
+            placeSquare = new PlaceSquare(square.getId(), placePoint.getX(), placePoint.getY(), square.getW(), square.getL());
+            if (!isOverlap(placeSquareList, placeSquare)) {
+                delta = Math.abs(placePoint.getLength() - square.getW());
+                mark2 = 1 - delta / placePoint.getLength();
+            }
+        }
+        if (mark1 >= mark2) {
+            return new double[]{-1d, (int) (mark1 * 10)};
+        }
+        return new double[]{1d, (int) (mark2 * 10)};
+    }
+
+    // 判断放置在该位置是否超出边界或者和其他矩形重叠
+    public boolean isOverlap(List<PlaceSquare> placeSquareList, PlaceSquare tempPlaceSquare) {
+        // 出界
+        if (tempPlaceSquare.getL() > length || tempPlaceSquare.getW() > width) {
+            return true;
+        }
+        // 出界
+        if (tempPlaceSquare.getX() + tempPlaceSquare.getL() > length || tempPlaceSquare.getY() + tempPlaceSquare.getW() > width) {
+            return true;
+        }
+        for (PlaceSquare placeSquare : placeSquareList) {
+            // 角点重合
+            if (placeSquare.getX() == tempPlaceSquare.getX() && placeSquare.getY() == tempPlaceSquare.getY()) {
+                placeSquareList.remove(placeSquare);
+                return true;
+            }
+            // 判断即将要放置的块是否与之前放置的块有重叠
+            if (isOverlap2(placeSquare, tempPlaceSquare)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // 判断即将要放置的块是否与之前放置的块有重叠
+    public boolean isOverlap2(PlaceSquare placeSquare, PlaceSquare tempPlaceSquare) {
+        double x1 = Math.max(placeSquare.getX(), tempPlaceSquare.getX());
+        double y1 = Math.max(placeSquare.getY(), tempPlaceSquare.getY());
+        double x2 = Math.min(placeSquare.getX() + placeSquare.getL(), tempPlaceSquare.getX() + tempPlaceSquare.getL());
+        double y2 = Math.min(placeSquare.getY() + placeSquare.getW(), tempPlaceSquare.getY() + tempPlaceSquare.getW());
+        return !(x1 >= x2) && !(y1 >= y2);
+    }
+
+    // 生成初始解
+    public void getInitSolution() {
+        Collections.shuffle(initGhh);
+        bestSolution = evaluate(new ArrayList<>(initGhh));
+        tempSolution = bestSolution;
+        bestGh = new ArrayList<>(initGhh);
+        tempGh = new ArrayList<>(initGhh);
+        LocalGh = new ArrayList<>(initGhh);
+    }
+
+    //加入禁忌队列
+    public void enterTabooList(List<Square> squareList) {
+        if (tabuTreeMap == null) {
+            tabuTreeMap = new HashMap<>();
+        }
+        Square square = squareList.get(0);
+        Long id = square.getId();
+        if (tabuTreeMap.containsKey(id)) {
+            tabuTreeMap.get(id).add(new ArrayList<>(squareList), 1);
+        } else {
+            TabuMapTree tabuMapTree = new TabuMapTree();
+            tabuMapTree.setNodeSquare(square);
+            tabuMapTree.add(new ArrayList<>(squareList), 1);
+            tabuTreeMap.put(id, tabuMapTree);
+        }
+    }
+
+    //生成新解
+    public List<Square> generateNewGh(List<Square> localGh) {
+        Square temp;
+        //将Gh复制到tempGh
+        List<Square> tempGh = new ArrayList<>(localGh);
+
+        for (int i = 0; i < 6; i++) {
+            int r1 = 0;
+            int r2 = 0;
+
+            while (r1 == r2) {
+                r1 = random.nextInt(tempGh.size());
+                r2 = random.nextInt(tempGh.size());
+            }
+            //交换
+            temp = tempGh.get(r1);
+            tempGh.set(r1, tempGh.get(r2));
+            tempGh.set(r2, temp);
+        }
+
+        return new ArrayList<>(tempGh);
+    }
+
+    //判断路径编码是否存在于禁忌表中
+    public boolean judge(List<Square> Gh) {
+        Square square = Gh.get(0);
+        if (tabuTreeMap.containsKey(square.getId())) {
+            return tabuTreeMap.get(square.getId()).contains(Gh, 1);
+        } else {
+            return false;
+        }
+    }
+
+}