zoukankan      html  css  js  c++  java
  • 初学OptaPlanner-03- 调包LocalSearch求解N皇后问题

    目前可以公开的一些情报

    什么是运筹排班算法?

    假设一个工厂场景,该工厂下有100个工人需要排班,全排列会有100!约等于 (10^{158})种结果,此类问题也属于NP问题;粗略计算一个地球年不到(10^8)秒,假设每秒可以运算(10^{10})种情况,也需要(10^{140})个地球年来跑完全部的结果集,进而得到全局最优解。
    除此之外,考虑三班倒,有五条生产线,另外需要考虑约束:连上限制、休息限制、工种限制、生产计划限制、熟练度限制、请假换班、加班、公平性考虑等约束考虑;假设为每个个体受限于10种约束,100个人,会有1000个约束考虑,这时,再来人工来手动排班难度极大。

    运筹学是一门通过数学统筹方法来尝试解决此类NP问题的学科,可以通过一些数学手段来缩减求解问题的规模,减少搜索范围;其主要分支有:线性规划、非线性规划、整数规划、几何规划、大型规划、动态规划、图论、网络理论、博弈论、决策论、排队论、存贮论、搜索论等。。

    OptaPlanner的初步作用?

    通过启发式算法,诸如暴力搜索、元启发式算法、进化算法、蚁群算法、粒子群算法、分支界定算法等搜索算法来找到一个局部近似解;使用惩罚项来构建约束,在规定的可以接受的时间范围,通过尽量降低惩罚分数,来得到一个较为最优的解。

    原文地址

    https://docs.optaplanner.org/7.45.0.Final/optaplanner-docs/html_single/index.html#nQueens

    01. N皇后问题是啥?

    约束项:

    • 给出一个N * N的棋盘,问最多可以摆放几个国际象棋中的皇后,他们互不攻击。
    • Queue可以攻击任意横向、纵向、两个对角线方向上的棋子。

    开始照着葫芦画瓢~

    葫芦见上期文章

    02.初始化子状态类、输入输出域的类、约束打分规则类、测试类

    构建最小的子状态类 Queen

    @Data
    @PlanningEntity
    @NoArgsConstructor
    public class Queen {
        /**
         * 固定字段
         */
        private Long id;
    
        /**
         * 列坐标
         */
        @PlanningVariable(valueRangeProviderRefs = "colIdxRange")
        private Integer colIdx;
    
        /**
         * 行坐标
         */
        @PlanningVariable(valueRangeProviderRefs = "rowIdxRange")
        private Integer rowIdx;
    
        public Queen(int i) {
            id = (long) i;
        }
    
        @Override
        public String toString() {
            return String.format("Queen[id=%d: %d,%d]", id, colIdx, rowIdx);
        }
    
        public Integer getLeftDiagonalConflictCheck() {
            return colIdx + rowIdx;
        }
    
        public Integer getRightDiagonalConflictCheck() {
            return colIdx - rowIdx;
        }
    }
    
    

    构建输入输出域

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @PlanningSolution
    public class ChessBoard {
    
        @ValueRangeProvider(id = "colIdxRange")   // 对应 @PlanningVariable下的id
        @ProblemFactCollectionProperty         // 输入 不变
        private List<Integer> colIdxInitList;
    
        @ValueRangeProvider(id = "rowIdxRange")   // 对应 @PlanningVariable下的id
        @ProblemFactCollectionProperty         // 输入 不变
        private List<Integer> rowIdxInitList;
    
        /**
         * 输入时: 无
         * <p>
         * 输出时:
         * 输出结果存储在在 col/row idx
         */
        @PlanningEntityCollectionProperty        // 输出  结果域  (在计算过程中会一直进行尝试,直到尝试到最优解)
        private List<Queen> queenList;
    
        @PlanningScore
        private HardSoftScore score;
    
        @Override
        public String toString() {
            return "ChessBoard{" +
                    ",
     colIdxInitList=" + colIdxInitList +
                    ",
     rowIdxInitList=" + rowIdxInitList +
                    ",
    
     queenList=" + queenList +
                    ",
     score=" + score +
                    '}';
        }
    
        /**
         * 绘制棋盘
         */
        public void paintChessBoard(int n) {
            System.out.println("打印棋盘,最后一行第1个点为(0,0),12点方向为x轴正轴方向
    ");
            for (int i = n - 1; i >= 0; i--) {
                for (int j = 0; j < n; j++) {
                    boolean flag = false;
                    for (Queen queen : queenList) {
                        if (queen.getRowIdx() == i && queen.getColIdx() == j) {
                            System.out.print(" * ");
                            flag = true;
                        }
                    }
                    if (!flag) {
                        System.out.print(" - ");
                    }
                }
                System.out.print("
    ");
            }
        }
    }
    
    

    约束(惩罚项)类 : 四种不合法的类型

    public class QueueConstraintProvider implements ConstraintProvider {
        @Override
        public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
            return new Constraint[]{
                    // Only hard Constraint
    
                    rowConflictCheck(constraintFactory),
                    colConflictCheck(constraintFactory),
                    // 加上左右对角线
                    leftDiagonalConflictCheck(constraintFactory),
                    rightDiagonalConflictCheck(constraintFactory),
            };
        }
    
    
        private Constraint rowConflictCheck(ConstraintFactory constraintFactory) {
            return constraintFactory.from(Queen.class)
                    .join(Queen.class,
                            Joiners.equal(Queen::getRowIdx),
                            Joiners.lessThan(Queen::getId)
                    )
                    .penalize("行约束", HardSoftScore.ONE_HARD);
        }
    
    
        private Constraint colConflictCheck(ConstraintFactory constraintFactory) {
            return constraintFactory.from(Queen.class)
                    .join(Queen.class,
                            Joiners.lessThan(Queen::getId),
                            Joiners.equal(Queen::getColIdx)
                    )
                    .penalize("列约束", HardSoftScore.ONE_HARD);
        }
    
    
        private Constraint leftDiagonalConflictCheck(ConstraintFactory constraintFactory) {
            return constraintFactory.from(Queen.class)
                    .join(Queen.class,
                            Joiners.lessThan(Queen::getId),
                            Joiners.equal(Queen::getLeftDiagonalConflictCheck)
                    )
                    .penalize("右对角线 约束", HardSoftScore.ONE_HARD);
        }
    
        private Constraint rightDiagonalConflictCheck(ConstraintFactory constraintFactory) {
            return constraintFactory.from(Queen.class)
                    .join(Queen.class,
                            Joiners.lessThan(Queen::getId),
                            Joiners.equal(Queen::getRightDiagonalConflictCheck)
                    )
                    .penalize("左对角线 约束", HardSoftScore.ONE_HARD);
        }
    
    }
    
    

    测试类

    /**
     * 记得保持在同一启动类的目录下
     */
    @SpringBootTest
    class NQueenOptaplannerApplicationTests {
    
        @Resource
        private SolverManager<ChessBoard, UUID> nQueensPuzzleSolverManager;
    
        /**
         * 01  N皇后测试
         */
        @Test
        void testNQueensPuzzle() {
            int n = 5;
            int queenNum = 4;
    
            /////////////////////////////////////////////////////////////////////
            List<Integer> colIdxInitList = new ArrayList<>();
            List<Integer> rowIdxInitList = new ArrayList<>();
            List<Queen> queenList = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                colIdxInitList.add(i);
                rowIdxInitList.add(i);
            }
    
            for (int i = 0; i < queenNum; i++) {
                queenList.add(new Queen(i));
            }
    
            ChessBoard problem = new ChessBoard(colIdxInitList, rowIdxInitList, queenList, null);
    
            UUID problemId = UUID.randomUUID();
            // Submit the problem to start solving
            SolverJob<ChessBoard, UUID> solverJob = nQueensPuzzleSolverManager.solve(problemId, problem);
            ChessBoard solution;
            try {
                // Wait until the solving ends
                solution = solverJob.getFinalBestSolution();
            } catch (InterruptedException | ExecutionException e) {
                throw new IllegalStateException(">>>>>Solving failed.", e);
            }
            System.out.println(solution);
            System.out.println("
    
    ");
            solution.paintChessBoard(n);
        }
    }
    
    

    03. 测试

    测试N=5,Queen=4时,是否存在对应的合法局面

    默认使用的LocalSearch算法,跑了35917轮, 找到一个合理解, 返回; 打印如下

    打印棋盘,最后一行第1个点为(0,0),12点方向为x轴正轴方向
    
     -  -  *  -  - 
     -  -  -  -  - 
     -  *  -  -  - 
     -  -  -  *  - 
     *  -  -  -  - 
    

    测试N=5,QueenNum=5时,是否存在对应的合法局面

    默认使用的LocalSearch算法,跑了36689轮, 找到一个合理解, 返回; 打印如下

    ( Solving ended: time spent (5000), best score (0hard/0soft), score calculation speed (14519/sec), phase total (2), environment mode (REPRODUCIBLE).)

    打印棋盘,最后一行第1个点为(0,0),12点方向为x轴正轴方向
    
     -  -  *  -  - 
     -  -  -  -  * 
     -  *  -  -  - 
     -  -  -  *  - 
     *  -  -  -  - 
    

    测试N=10,QueenNum=10时,是否存在对应的合法局面

    Local Search phase (1) ended: time spent (5000ms), best score (-1hard/0soft), score calculation speed (12470/sec), step total (32526).
    这时不存在对应的合法局面, 找到的局部最优解为-1hard/0soft,意思是违反了一个硬约束,打印局部最优解如下:

    打印棋盘,最后一行第1个点为(0,0),12点方向为x轴正轴方向
    
     -  -  -  -  -  -  -  -  -  * 
     -  -  *  -  -  -  -  -  -  - 
     -  -  -  -  -  -  *  -  -  - 
     -  -  -  *  -  -  -  -  -  - 
     -  -  -  -  -  -  -  *  -  - 
     -  -  -  -  -  -  -  *  -  - 
     -  -  -  -  *  -  -  -  -  - 
     *  -  -  -  -  -  -  -  -  - 
     -  -  -  -  -  *  -  -  -  - 
     -  -  -  -  -  -  -  -  *  - 
    

    测试N=10,QueenNum=10时,加大搜索时间到30s,再次尝试:

    更改application.properties即可:

    # The solver runs only for 5 seconds to avoid a HTTP timeout in this simple implementation.
    # It's recommended to run for at least 5 minutes ("5m") otherwise.
    optaplanner.solver.termination.spent-limit=30s
    logging.level.org.optaplanner=debug
    

    再次尝试,找到一个最优解 (0hard/0soft),没有违反任何约束

    DefaultLocalSearchPhase : Local Search phase (1) ended: time spent (30000), best score (0hard/0soft), score calculation speed (14415/sec), step total (236831).

    打印棋盘,最后一行第1个点为(0,0),该点的12点方向为x轴正轴方向————
    *  -  -  -  -  -  -  -  -  - 
     -  -  -  -  -  -  -  -  -  * 
     -  -  -  -  -  -  *  -  -  - 
     -  -  -  -  *  -  -  -  -  - 
     -  -  -  -  -  -  -  *  -  - 
     -  *  -  -  -  -  -  -  -  - 
     -  -  -  -  -  -  -  -  *  - 
     -  -  *  -  -  -  -  -  -  - 
     -  -  -  -  -  *  -  -  -  - 
     -  -  -  *  -  -  -  -  -  - 
    

    04. 最后

    1. N皇后的问题随着棋盘规则的增加,搜索难度也会大幅提升(NP问题),目前除了量子计算外,解决NP问题的一个手段就是数学武器。
    2. 局部搜索(Local Search)是解决最优化问题的一种启发式算法。因为对于很多复杂的问题,求解最优解的时间可能是极其长的。因此诞生了各种启发式算法来退而求其次寻找次优解,局部搜索就是其中一种。它是一种近似算法(Approximate algorithms)。

      局部搜索算法是从爬山法改进而来的。简单来说,局部搜索算法是一种简单的贪心搜索算法,该算法每次从当前解的临近解空间中选择一个最优解作为当前解,直到达到一个局部最优解。局部搜索从一个初始解出发,然后搜索解的邻域,如有更优的解则移动至该解并继续执行搜索,否则返回当前解。

      引用链接: https://www.cnblogs.com/dengfaheng/p/9245559.html
    3. 后续要加快速度,英文文档看着还是比较费劲的(单词都认识、句子可以读通顺,但理解起来就比较困难了),没多时间了,下周就要开始投入生产了;领导说,这个新项目的难点不是缺人力或者缺时间,而是是否存在有效解的问题,如何快速判断我们的算法是否值得继续优化等问题。
    4. 下一步:
      • OPTA怎么选择其他的算法,目前使用的只有LS
      • 怎么快速判断是否存在解(解空间)
      • 怎么优化
      • Boot的多线程计算
      • 是否可以借助GPU加速
  • 相关阅读:
    数据库
    linux
    linux
    python_函数进阶1
    python_函数初识
    python_文件操作
    python_基础数据类型补充
    python 小数据池,代码块总览
    python_基础数据类型二
    python_基础数据类型一
  • 原文地址:https://www.cnblogs.com/zhazhaacmer/p/13900794.html
Copyright © 2011-2022 走看看