├── .gitattributes ├── .gitignore ├── .idea ├── .gitignore ├── misc.xml ├── modules.xml └── vcs.xml ├── BranchAndBound ├── __init__.py ├── bnb_Tree.py └── branch_and_bound.py ├── CuttingPlane ├── __init__.py ├── cutting_plane.py └── linprog │ ├── README.md │ ├── __init__.py │ ├── problem.py │ ├── simplex_method.py │ ├── solve.py │ └── solver.py ├── HungarianAssignment ├── __init__.py └── hungarian_assignment.py ├── IntegerProgExperiment.iml ├── LICENSE ├── MonteCarlo ├── __init__.py └── monte_carlo.py ├── README.md ├── __init__.py ├── docs ├── BranchAndBound.bnb_Tree.html ├── BranchAndBound.branch_and_bound.html ├── BranchAndBound.html ├── CuttingPlane.cutting_plane.html ├── CuttingPlane.html ├── CuttingPlane.linprog.html ├── CuttingPlane.linprog.problem.html ├── CuttingPlane.linprog.simplex_method.html ├── CuttingPlane.linprog.solve.html ├── CuttingPlane.linprog.solver.html ├── HungarianAssignment.html ├── HungarianAssignment.hungarian_assignment.html ├── MonteCarlo.html ├── MonteCarlo.monte_carlo.html ├── experiment.html └── index.html └── experiment.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # celery beat schedule file 95 | celerybeat-schedule 96 | 97 | # SageMath parsed files 98 | *.sage.py 99 | 100 | # Environments 101 | .env 102 | .venv 103 | env/ 104 | venv/ 105 | ENV/ 106 | env.bak/ 107 | venv.bak/ 108 | 109 | # Spyder project settings 110 | .spyderproject 111 | .spyproject 112 | 113 | # Rope project settings 114 | .ropeproject 115 | 116 | # mkdocs documentation 117 | /site 118 | 119 | # mypy 120 | .mypy_cache/ 121 | .dmypy.json 122 | dmypy.json 123 | 124 | # Pyre type checker 125 | .pyre/ 126 | .DS_Store 127 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /BranchAndBound/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | BranchAndBound 提供求解整数线性规划问题的「分支定界法」 3 | 4 | Public 5 | ------ 6 | func branch_and_bound(c, A_ub, b_ub, A_eq, b_eq, bounds, bnbTreeNode=None): 7 | 对整数规划问题使用「分支定界法」进行*递归*求解。 8 | class BnBTree: 表示分枝定界法求整数规划问题过程的树 9 | class BnBTreeNode: BnBTree 的节点 10 | """ 11 | 12 | from BranchAndBound.bnb_Tree import BnBTreeNode, BnBTree 13 | from BranchAndBound.branch_and_bound import branch_and_bound 14 | -------------------------------------------------------------------------------- /BranchAndBound/bnb_Tree.py: -------------------------------------------------------------------------------- 1 | class BnBTreeNode(object): 2 | """ 3 | BnBTreeNode 是 BnBTree 的节点 4 | 5 | Fields 6 | ------ 7 | left : BnBTreeNode, 左子节点,分支定界法里的左枝 8 | right : BnBTreeNode, 右子节点,分支定界法里的右枝 9 | 10 | x_idx : int, 分支定界法里新增条件的变量索引 11 | x_c : str, 分支定界法里新增条件的比较运算符 "<=" 或 ">=" 12 | x_b : float, 分支定界法里新增条件的右端常数 13 | 14 | res_x : numpy array, 分支定界法里这一步的松弛解 15 | res_fun : float, 分支定界法里这一步的目标函数值 16 | 17 | sub_flag: bool, 若节点为*整颗BnBTree*的根节点则为 False,否则 True 18 | """ 19 | 20 | def __init__(self): 21 | super().__init__() 22 | self.left = None 23 | self.right = None 24 | 25 | self.x_idx = None 26 | self.x_c = None 27 | self.x_b = None 28 | 29 | self.res_x = None 30 | self.res_fun = None 31 | 32 | self.sub_flag = True 33 | 34 | def __str__(self): 35 | if self.sub_flag: 36 | return f'x[{self.x_idx}] {self.x_c} {self.x_b}: {self.res_x} -> {self.res_fun}' 37 | else: 38 | return f'Root: {self.res_x} -> {self.res_fun}' 39 | 40 | 41 | class BnBTree(object): 42 | """ 43 | BnBTree 是表示分枝定界法求整数规划问题过程的树 44 | 45 | Fields 46 | ------ 47 | root: 树根节点 48 | """ 49 | 50 | def __init__(self): 51 | super().__init__() 52 | self.root = BnBTreeNode() 53 | self.root.sub_flag = False 54 | self.__str_tree = "" 55 | 56 | def __str__(self): 57 | if self.__str_tree == "": 58 | def walk(node, indentation): 59 | self.__str_tree += "\t|" * indentation + "-- " 60 | # print("\t|" * indentation + "--", end=" ") 61 | self.__str_tree += str(node) + "\n" 62 | # print(node) 63 | if node.left: 64 | walk(node.left, indentation + 1) 65 | if node.right: 66 | walk(node.right, indentation + 1) 67 | 68 | walk(self.root, 0) 69 | return self.__str_tree 70 | 71 | 72 | def _bnb_tree_test(): 73 | tree = BnBTree() 74 | 75 | tree.root.left = BnBTreeNode() 76 | tree.root.left.res_fun = "left" 77 | 78 | tree.root.right = BnBTreeNode() 79 | tree.root.right.res_fun = "right" 80 | 81 | tree.root.right.left = BnBTreeNode() 82 | tree.root.right.left.res_fun = "rl" 83 | 84 | tree.root.right.right = BnBTreeNode() 85 | tree.root.right.right.res_fun = "rr" 86 | 87 | tree.root.right.left.left = BnBTreeNode() 88 | tree.root.right.left.left.res_fun = "rll" 89 | 90 | print(tree) 91 | 92 | 93 | if __name__ == "__main__": 94 | _bnb_tree_test() 95 | -------------------------------------------------------------------------------- /BranchAndBound/branch_and_bound.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | from scipy import optimize 4 | 5 | from BranchAndBound.bnb_Tree import BnBTree, BnBTreeNode 6 | 7 | # epsilon 是算法中判断「零」的临界值,绝对值小于该值的数认为是0 8 | _epsilon = 1e-8 9 | 10 | 11 | def _is_int(n) -> bool: 12 | """ 13 | is_int 是判断给定数字 n 是否为整数, 14 | 在判断中 n 小于epsilon的小数部分将被忽略, 15 | 是则返回 True,否则 False 16 | 17 | :param n: 待判断的数字 18 | :return: True if n is A_ub integer, False else 19 | """ 20 | return (n - math.floor(n) < _epsilon) or (math.ceil(n) - n < _epsilon) 21 | 22 | 23 | def branch_and_bound(c, A_ub, b_ub, A_eq, b_eq, bounds, bnbTreeNode=None): 24 | """ 25 | branch_and_bound 对整数规划问题使用「分支定界法」进行*递归*求解。 26 | 27 | 底层对松弛问题求解使用 scipy.optimize.linprog 完成, 28 | 该算法只是在 scipy.optimize.linprog 求解的基础上加以整数约束, 29 | 所以求解问题的模型、参数中的 c, A_ub, b_ub, A_eq, b_eq, bounds 30 | 与 scipy.optimize.linprog 的完全相同。 31 | 32 | 问题模型: 33 | Minimize: c^T * x 34 | 35 | Subject to: A_ub * x <= b_ub 36 | A_eq * x == b_eq 37 | (x are integers) 38 | 39 | 你可以提供一个 BnBTreeNode 实例作为根节点来记录求解过程,得到一个求解过程的树形图。 40 | 如果需要这样的求解过程的树形图,你可以这样调用 branch_and_bound: 41 | c = [-40, -90] 42 | A_ub = [[9, 7], [7, 20]] 43 | b_ub = [56, 70] 44 | bounds = [(0, None), (0, None)] 45 | tree = BnBTree() 46 | r = branch_and_bound(c, A_ub, b_ub, None, None, bounds, tree.root) 47 | print(r) # 打印求解结果 48 | print(tree) # 打印求解过程的树形图 49 | 50 | Parameters 51 | ---------- 52 | :param c: 系数矩阵。array_like 53 | Coefficients of the linear objective function to be minimized. 54 | :param A_ub: 不等式约束条件矩阵,array_like, 若无则需要传入 None 55 | 2-D array which, when matrix-multiplied by ``x``, gives the values of 56 | the upper-bound inequality constraints at ``x``. 57 | :param b_ub: 不等式约束条件右端常数,array_like, 若无则需要传入 None 58 | 1-D array of values representing the upper-bound of each inequality 59 | constraint (row) in ``A_ub``. 60 | :param A_eq: 等式约束条件矩阵,array_like, 若无则需要传入 None 61 | 2-D array which, when matrix-multiplied by ``x``, gives the values of 62 | the equality constraints at ``x``. 63 | :param b_eq: 等式约束条件右端常数,array_like, 若无则需要传入 None 64 | 1-D array of values representing the RHS of each equality constraint 65 | (row) in ``A_eq``. 66 | :param bounds: 变量取值范围,sequence 67 | ``(min, max)`` pairs for each element in ``x``, defining 68 | the bounds on that parameter. Use None for one of ``min`` or 69 | ``max`` when there is no bound in that direction. By default 70 | bounds are ``(0, None)`` (non-negative) 71 | If a sequence containing a single tuple is provided, then ``min`` and 72 | ``max`` will be applied to all variables in the problem. 73 | :param bnbTreeNode: 该步的 bnbTreeNode 74 | 提供一个 BnBTreeNode 实例作为根节点来记录求解过程,得到一个求解过程的树形图。 75 | 76 | Returns 77 | ------- 78 | :return: {"success": True|False, "x": array([...]), "fun": ...} 79 | - success: 若求解成功则返回 True,否则 False 80 | - x: 最优解 81 | - fun: 最优目标函数值 82 | """ 83 | 84 | # 对松弛问题求解 85 | r = optimize.linprog(c, A_ub, b_ub, A_eq, b_eq, bounds) 86 | 87 | if bnbTreeNode: 88 | bnbTreeNode.res_x = r.x 89 | bnbTreeNode.res_fun = r.fun 90 | 91 | if not r.success: 92 | return {"success": False, "x": None, "fun": None} 93 | 94 | x = r.x 95 | z = sum(np.array(x) * np.array(c)) 96 | 97 | if all([_is_int(i) for i in x]): # 最优解是整数解 98 | return {"success": True, "x": x, "fun": z} 99 | 100 | # 有非整数变量 101 | # 找出第一个非整数变量的索引 102 | opt_idx = [i for i, v in enumerate(x) if not _is_int(v)][0] 103 | 104 | # 构造新的条件、问题 105 | # con1: <= 106 | new_con1 = [1 if i == opt_idx else 0 for i in range(len(A_ub[0]))] 107 | new_A1 = A_ub.copy() 108 | new_A1.append(new_con1) 109 | new_B1 = b_ub.copy() 110 | new_B1.append(math.floor(x[opt_idx])) 111 | 112 | # 构造新问题的 BnBTreeNode 113 | if bnbTreeNode: 114 | bnbTreeNode.left = BnBTreeNode() 115 | bnbTreeNode.left.x_idx = opt_idx 116 | bnbTreeNode.left.x_c = "<=" 117 | bnbTreeNode.left.x_b = math.floor(x[opt_idx]) 118 | 119 | # 递归求解新问题 120 | r1 = branch_and_bound(c, new_A1, new_B1, A_eq, b_eq, bounds, bnbTreeNode.left if bnbTreeNode else None) 121 | 122 | # 构造新的条件 123 | # con2: > 124 | new_con2 = [-1 if i == opt_idx else 0 for i in range(len(A_ub[0]))] 125 | new_A2 = A_ub.copy() 126 | new_A2.append(new_con2) 127 | new_B2 = b_ub.copy() 128 | new_B2.append(-math.ceil(x[opt_idx])) 129 | 130 | # 构造新问题的 BnBTreeNode 131 | if bnbTreeNode: 132 | bnbTreeNode.right = BnBTreeNode() 133 | bnbTreeNode.right.x_idx = opt_idx 134 | bnbTreeNode.right.x_c = ">=" 135 | bnbTreeNode.right.x_b = math.ceil(x[opt_idx]) 136 | 137 | # 递归求解新问题 138 | r2 = branch_and_bound(c, new_A2, new_B2, A_eq, b_eq, bounds, bnbTreeNode.right if bnbTreeNode else None) 139 | 140 | # 子问题返回了,找出其中的最优可行继续向上一层返回 141 | if r1["success"] and r2["success"]: 142 | return min((r1, r2), key=lambda A_ub: A_ub["fun"]) 143 | elif r1["success"]: 144 | return r1 145 | elif r2["success"]: 146 | return r2 147 | else: 148 | return None 149 | 150 | 151 | def _test1(): 152 | c = [3, 4, 1] 153 | A_ub = [[-1, -6, -2], [-2, 0, 0]] 154 | b_ub = [-5, -3] 155 | A_eq = None 156 | b_eq = None 157 | bounds = [(0, None), (0, None), (0, None)] 158 | tree = BnBTree() 159 | r = branch_and_bound(c, A_ub, b_ub, A_eq, b_eq, bounds, tree.root) 160 | print(r) 161 | print(tree) 162 | 163 | 164 | def _test2(): 165 | c = [-40, -90] 166 | A_ub = [[9, 7], [7, 20]] 167 | b_ub = [56, 70] 168 | A_eq = None 169 | b_eq = None 170 | bounds = [(0, None), (0, None)] 171 | tree = BnBTree() 172 | r = branch_and_bound(c, A_ub, b_ub, A_eq, b_eq, bounds, tree.root) 173 | print(r) 174 | print(tree) 175 | 176 | 177 | def _test3(): 178 | c = [-1, -1] 179 | A_ub = [[2, 1], [4, 5]] 180 | b_ub = [6, 20] 181 | bounds = [(0, None), (0, None)] 182 | r = branch_and_bound(c, A_ub, b_ub, None, None, bounds) 183 | print(r) 184 | 185 | 186 | if __name__ == "__main__": 187 | _test1() 188 | _test2() 189 | _test3() 190 | -------------------------------------------------------------------------------- /CuttingPlane/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | CuttingPlane 提供求解整数线性规划问题的「割平面法」 3 | 4 | Public 5 | ------ 6 | func cutting_plane(c, A, b): 对整数规划问题使用「分支定界法」进行*递归*求解。 7 | """ 8 | 9 | from CuttingPlane.cutting_plane import cutting_plane 10 | -------------------------------------------------------------------------------- /CuttingPlane/cutting_plane.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from CuttingPlane.linprog.problem import LpProblem 4 | from CuttingPlane.linprog.simplex_method import SimplexMethod 5 | 6 | # epsilon 是算法中判断「零」的临界值,绝对值小于该值的数认为是0 7 | _epsilon = 1e-8 8 | 9 | 10 | def _is_int(n) -> bool: 11 | """ 12 | is_int 是判断给定数字 n 是否为整数, 13 | 在判断中 n 小于epsilon的小数部分将被忽略, 14 | 是则返回 True,否则 False 15 | 16 | :param n: 待判断的数字 17 | :return: True if n is A_ub integer, False else 18 | """ 19 | return (n - math.floor(n) < _epsilon) or (math.ceil(n) - n < _epsilon) 20 | 21 | 22 | def cutting_plane(c, A, b): 23 | """ 24 | cutting_plane 对整数规划问题使用「割平面法」进行*递归*求解。 25 | 26 | **对于部分问题,该算法无法给出正确的结果,原因未知。不推荐使用** 27 | 28 | 底层对松弛问题求解使用 cdfmlr/SimplexLinprog 完成。 29 | 30 | 问题模型: 31 | Maximize: c^T * x 32 | 33 | Subject to: A * x == b 34 | (x are integers) 35 | 36 | Parameters 37 | ---------- 38 | :param c: 系数矩阵。array_like 39 | Coefficients of the linear objective function to be maximized. 40 | :param A: 等式约束条件矩阵,array_like 41 | 2-D array which, when matrix-multiplied by ``x``, gives the values of 42 | the equality constraints at ``x``. 43 | :param b: 等式约束条件右端常数,array_like 44 | 1-D array of values representing the RHS of each equality constraint 45 | (row) in ``A``. 46 | 47 | Returns 48 | ------- 49 | :return: {"success": True|False, "x": array([...]), "fun": ...} 50 | - success: 若求解成功则返回 True,否则 False 51 | - x: 最优解 52 | - fun: 最优目标函数值 53 | """ 54 | 55 | # 对松弛问题求解 56 | s = SimplexMethod(LpProblem(c, A, b)) 57 | r = s.solve(big_m=True) 58 | 59 | # print(s) 60 | # print(r) 61 | # print(s.pb.a) 62 | 63 | if not r.success: 64 | return {"success": False, "x": None, "fun": None} 65 | 66 | x = r.solve 67 | z = r.target 68 | 69 | if all([_is_int(i) for i in x]): # 最优解是整数解 70 | return {"success": True, "x": x, "fun": z} 71 | 72 | # 有非整数变量 73 | # 找出一个非整数变量的索引 74 | x_b = list(filter(lambda n: n > 0, x)) 75 | opt_idx = [i for i, v in enumerate(x_b) if not _is_int(v)][-1] 76 | opt_tab_inx = s.pb.base_idx[opt_idx] 77 | 78 | # opt 在最终单纯形表中的一行 79 | row = s.pb.a[opt_idx] 80 | bow = x[opt_idx] 81 | 82 | # print(opt_idx, row, bow) 83 | 84 | # 拆分整数和小数 85 | row_int = [] 86 | row_dec = [] 87 | for r in row: 88 | row_int.append(math.floor(r)) 89 | row_dec.append(r - math.floor(r)) 90 | 91 | # print(row_int, row_dec) 92 | 93 | bow_int = math.floor(bow) 94 | bow_dec = bow - math.floor(bow) 95 | 96 | # 构造新的条件、问题 97 | new_con = row_dec + [-1] 98 | new_A = list(s.problem.a.copy()) 99 | for i in range(len(new_A)): 100 | new_A[i] = list(new_A[i]) + [0] 101 | new_A.append(new_con) 102 | new_B = list(s.problem.b.copy()) 103 | new_B.append(bow_dec) 104 | new_C = list(s.pb.c) + [0] 105 | 106 | # print(new_A, new_B, new_C) 107 | 108 | # 递归求解新问题 109 | return cutting_plane(new_C, new_A, new_B) 110 | 111 | 112 | def _test1(): 113 | c = [3, 4, 1, 0, 0] 114 | a = [[1, 6, 2, -1, 0], [2, 0, 0, 0, -1]] 115 | b = [5, 3] 116 | r = cutting_plane(c, a, b) 117 | print(r) 118 | 119 | 120 | def _test2(): 121 | c = [40, 90, 0, 0] 122 | a = [[9, 7, 1, 0], [7, 20, 0, 1]] 123 | b = [56, 70] 124 | r = cutting_plane(c, a, b) 125 | print(r) 126 | 127 | 128 | def _test3(): 129 | c = [1, 1, 0, 0] 130 | a = [[2, 1, 1, 0], [4, 5, 0, 1]] 131 | b = [6, 20] 132 | r = cutting_plane(c, a, b) 133 | print(r) 134 | 135 | 136 | def _test4(): 137 | c = [1, 1, 0, 0] 138 | a = [[-1, 1, 1, 0], [3, 1, 0, 1]] 139 | b = [1, 4] 140 | r = cutting_plane(c, a, b) 141 | print(r) 142 | 143 | 144 | if __name__ == "__main__": 145 | # _test1() # Failed: WA 146 | # _test2() # Failed: WA 147 | _test3() # Pass 148 | _test4() # Pass 149 | -------------------------------------------------------------------------------- /CuttingPlane/linprog/README.md: -------------------------------------------------------------------------------- 1 | # linprog 2 | 3 | > from [cdfmlr/SimplexLinprog](https://github.com/cdfmlr/SimplexLinprog) 4 | 5 | 单纯形算法实现。 6 | -------------------------------------------------------------------------------- /CuttingPlane/linprog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdfmlr/IntegerProgExperiment/bdd21c342c76e2e6cbb01cdb86b37e3f912d4158/CuttingPlane/linprog/__init__.py -------------------------------------------------------------------------------- /CuttingPlane/linprog/problem.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from .solver import LpSolver 3 | 4 | 5 | class LpProblem(object): 6 | """ 7 | LpProblem 描述一个线性规划问题 8 | 9 | :attribute c: 目标函数系数, c = (c_1, c_2, ..., c_n)^T 10 | :attribute a: 系数矩阵, a = (a_{ij})_{m \times n} = (p_1, p_2, ..., p_n) 11 | :attribute b: 右端常数, b = (b_1, b_2, ..., b_m)^T, b > 0 12 | :attribute base_idx: 基变量的下标集合 13 | """ 14 | 15 | def __init__(self, c, a, b): 16 | """ 17 | :param c: 目标函数系数, c = (c_1, c_2, ..., c_n)^T 18 | :param a: 系数矩阵, a = (a_{ij})_{m \times n} = (p_1, p_2, ..., p_n) 19 | :param b: 右端常数, b = (b_1, b_2, ..., b_m)^T, b > 0 20 | """ 21 | self.c = np.array(c, 'float64') 22 | self.a = np.array(a, 'float64') 23 | self.b = np.array(b, 'float64') 24 | 25 | def solve(self, solver: type, **kwargs): 26 | """ 27 | 调用指定算法,求解线性规划问题 28 | 29 | :param solver: 指定算法(LpSolver实例) 30 | """ 31 | assert issubclass(solver, LpSolver) 32 | s = solver(self) 33 | return s.solve(**kwargs) 34 | -------------------------------------------------------------------------------- /CuttingPlane/linprog/simplex_method.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | import numpy as np 4 | 5 | from .problem import LpProblem 6 | from .solve import LpSolve 7 | from .solver import LpSolver 8 | 9 | 10 | class SimplexMethod(LpSolver): 11 | """ 单纯形(表)法 12 | 13 | 待解决应符合标准型,即: 14 | 15 | max z = c^T * x 16 | s.t. a*x = b, x >= 0, b > 0 17 | 18 | 19 | 单纯形算法参考:https://zh.wikipedia.org/zh-hans/单纯形法 20 | """ 21 | 22 | class Problem(LpProblem): 23 | """ 24 | 单纯形(表)法内部的线性规划问题表示 25 | """ 26 | def __init__(self, c, a, b): 27 | super().__init__(c, a, b) 28 | self.base_idx = np.ones(len(b), 'int') * -1 29 | self.entering_idx = -1 30 | self.leaving_idx = -1 31 | self.theta = [] 32 | self.tab = [] 33 | self.cc = copy.deepcopy(c) 34 | 35 | def __init__(self, problem: LpProblem): 36 | super().__init__(problem) 37 | self.problem = self.Problem(problem.c, problem.a, problem.b) 38 | self.pb = None 39 | 40 | def find_idt_base(self): 41 | """ 42 | 尝试找单位阵作初始基 43 | 44 | :return: True if success False else 45 | """ 46 | base_idx = np.ones(len(self.problem.b), 'int') * -1 47 | aT = self.problem.a.T 48 | for i in range(len(self.problem.b)): 49 | e = np.zeros(len(self.problem.b)) 50 | e[i] = 1 51 | for j in range(len(aT)): 52 | if np.all(aT[j] == e): 53 | base_idx[i] = j 54 | 55 | self.problem.base_idx = base_idx 56 | return np.all(base_idx >= 0) 57 | 58 | def big_m(self, **kwargs): 59 | """ 60 | 用大M法得到初始基 61 | 62 | :param kwargs: show_tab=True (default False): 打印运算过程 63 | :return: None 64 | """ 65 | M = ((max(abs(self.problem.c)) + 1) ** 2) * 10 + 10 66 | if kwargs.get("show_tab", False): 67 | print(f"大M法\n\nM = {M}\n") 68 | for i in range(len(self.problem.base_idx)): 69 | if self.problem.base_idx[i] < 0: 70 | self.problem.c = np.insert(self.problem.c, len(self.problem.c), np.array([-M])) 71 | 72 | ap = np.zeros(len(self.problem.b)) 73 | ap[i] = 1 74 | self.problem.a = np.c_[self.problem.a, ap] 75 | 76 | self.problem.base_idx[i] = len(self.problem.c) - 1 77 | 78 | def two_step(self, **kwargs): 79 | """ 80 | 用两阶段法得到初始基,第一阶段在此计算 81 | 82 | :param kwargs: show_tab=True (default False): 打印单纯形表 83 | :return: 第一阶段的解 84 | """ 85 | p = copy.deepcopy(self.problem) 86 | p.c = np.zeros(len(p.c)) 87 | for i in range(len(self.problem.base_idx)): 88 | if self.problem.base_idx[i] < 0: 89 | p.c = np.insert(p.c, len(p.c), np.array([-1])) 90 | 91 | ap = np.zeros(len(p.b)) 92 | ap[i] = 1 93 | p.a = np.c_[p.a, ap] 94 | 95 | self.problem.base_idx[i] = len(p.c) - 1 96 | p.base_idx = self.problem.base_idx 97 | s1 = _simplex_solve(p, tab=kwargs.get("show_tab", False)) 98 | if kwargs.get("show_tab", False): 99 | print("两阶段法\n\nStep1:\n") 100 | print(_simplex_tab_tostring(p)) 101 | print("Step2:\n") 102 | self.problem.c = copy.deepcopy(self.problem.cc) 103 | self.problem.base_idx = p.base_idx 104 | self.problem.b = p.b 105 | self.problem.a = (p.a.T[0: len(self.problem.c)]).T 106 | return s1 107 | 108 | def solve(self, **kwargs) -> LpSolve: 109 | """ 110 | 单纯形算法入口 111 | 112 | :param kwargs: 113 | base_idx=[...] (default []): 指定初始基,缺省则由算法自行确定 114 | show_tab=True (default False): 打印单纯形表 115 | two_step=True (default False): 使用两阶段法 116 | big_m=True (default True): 使用大 M 法 117 | :return: 问题的解 118 | """ 119 | base_idx = kwargs.get("base_idx", []) 120 | if base_idx: # 用户指定了基 121 | self.problem.base_idx = base_idx 122 | else: 123 | if not self.find_idt_base(): # 没有找到单位阵作初始基,用人工变量法(大M法 / 两阶段法) 124 | if kwargs.get("two_step", False): 125 | s1 = self.two_step(**kwargs) 126 | if not s1.success: # 第一阶段确定无可行解 127 | return s1 128 | else: 129 | self.big_m(**kwargs) 130 | 131 | s = None 132 | self.pb = copy.deepcopy(self.problem) 133 | if kwargs.get("show_tab", False): 134 | if s is None: # For nothing, just referent outer s 135 | s = _simplex_solve(self.pb, tab=True) 136 | print(_simplex_tab_tostring(self.pb)) 137 | else: 138 | if s is None: 139 | s = _simplex_solve(self.pb, tab=False) 140 | return s 141 | 142 | 143 | def _simplex_solve(p: SimplexMethod.Problem, tab=False) -> LpSolve: 144 | """ simplex_solve 对给定了初始基的标准型使用单纯形表进行求解 145 | 146 | 可选参数: 147 | tab=True (default False): 计算单纯形表 148 | 149 | :return: 问题的解 150 | """ 151 | 152 | # 初始单纯形表的检验数计算 153 | for i in range(len(p.base_idx)): 154 | p.c -= p.c[p.base_idx[i]] * p.a[i] 155 | 156 | if tab: 157 | _current_simplex_tab(p) 158 | 159 | p.entering_idx = np.argmax(p.c) # 确定入基变量 160 | while p.c[p.entering_idx] > 0: 161 | p.theta = [] 162 | for i in range(len(p.b)): 163 | if p.a[i][p.entering_idx] > 0: 164 | p.theta.append(p.b[i] / p.a[i][p.entering_idx]) 165 | else: 166 | p.theta.append(float("inf")) 167 | 168 | p.leaving_idx = np.argmin(np.array(p.theta)) # 确定出基变量 169 | 170 | if p.theta[p.leaving_idx] == float("inf"): # 出基变量 == inf 171 | return LpSolve(False, "无界解", [None], None) 172 | 173 | _pivot(p) 174 | 175 | if tab: 176 | _current_simplex_tab(p) 177 | p.entering_idx = np.argmax(p.c) # Next 入基变量 178 | 179 | # 迭代结束,分析解的情况 180 | x = np.zeros(len(p.c)) 181 | x[p.base_idx] = p.b 182 | 183 | x_real = x[0: len(p.cc)] 184 | x_presonal = x[len(p.cc):] # 人工变量 185 | 186 | if np.any(x_presonal != 0): 187 | return LpSolve(False, "无可行解", None, None) 188 | 189 | z = np.dot(x_real, p.cc) 190 | 191 | for i in range(len(p.c)): 192 | if (i not in p.base_idx) and (abs(p.c[i]) < 1e-8): # 非基变量检验数为0 193 | return LpSolve(True, "无穷多最优解", x_real, z) 194 | 195 | return LpSolve(True, "唯一最优解", x_real, z) 196 | 197 | 198 | def _pivot(p: SimplexMethod.Problem) -> None: 199 | """ 200 | 对给定问题原址执行转轴操作(基变换) 201 | """ 202 | main_element = p.a[p.leaving_idx][p.entering_idx] 203 | 204 | p.a[p.leaving_idx] /= main_element 205 | p.b[p.leaving_idx] /= main_element 206 | 207 | p.base_idx[p.leaving_idx] = p.entering_idx 208 | 209 | for i in range(len(p.b)): 210 | if i != p.leaving_idx and p.a[i][p.entering_idx] != 0: 211 | p.b[i] -= p.a[i][p.entering_idx] * p.b[p.leaving_idx] 212 | p.a[i] -= p.a[i][p.entering_idx] * p.a[p.leaving_idx] 213 | 214 | p.c -= p.c[p.entering_idx] * p.a[p.leaving_idx] 215 | 216 | 217 | def _current_simplex_tab(p: SimplexMethod.Problem) -> None: 218 | """ 219 | 计算当前单纯形表 220 | :return: None 221 | """ 222 | if len(p.tab) > 0: 223 | main_element = '%.2f' % _float_round4print(p.tab[-1][p.leaving_idx][p.entering_idx + 2]) 224 | p.tab[-1][p.leaving_idx][p.entering_idx + 2] = f'[{main_element}]' 225 | 226 | tab = [] 227 | for i in range(len(p.b)): 228 | if len(p.theta) > 0: 229 | p.tab[-1][i][-1] = p.theta[i] 230 | 231 | tab += [ 232 | [f'x_{p.base_idx[i]}', p.b[i]] + list(p.a[i]) + [" ", ] 233 | ] 234 | 235 | tab += [ 236 | (" ", " ") + tuple(p.c) + (" ",), 237 | ] 238 | 239 | p.tab.append(tab) 240 | 241 | 242 | def _simplex_tab_tostring(p: SimplexMethod.Problem, step=None): 243 | """ 244 | 将给定 SimplexMethod.Problem 中的单纯形表转化为字符串 245 | 246 | :param p: SimplexMethod.Problem 247 | :param step: None 则转化全部,或 int 只转化第几步 248 | :return: 转化后的字符串 249 | """ 250 | s = '' 251 | if step is None: 252 | for step in p.tab: 253 | for row in step: 254 | for i in row: 255 | s += '%6.6s\t' % _float_round4print(i) 256 | s += '\n' 257 | s += '-' * 16 + '\n' 258 | else: 259 | for row in p.tab[step]: 260 | for i in row: 261 | s += '%6.6s\t' % _float_round4print(i) 262 | s += '\n' 263 | s += '-' * 16 + '\n' 264 | return s 265 | 266 | 267 | def _float_round4print(f): 268 | """ 269 | 为方便打印,格式化绝对值趋于0或无穷的浮点值 270 | :param f: 任意值,但只有 float 才会被操作 271 | :return: int(0): 若 f 是 float 且趋于 0; 272 | 格式化的指数表示字符串: 若 f 是 float 且 f >= 1E6; 273 | f: 其他情况 274 | """ 275 | if isinstance(f, float): 276 | if abs(f) <= 1E-6: 277 | return 0 278 | elif abs(f) >= 1E6: 279 | return '%2.2e' % f 280 | return f 281 | -------------------------------------------------------------------------------- /CuttingPlane/linprog/solve.py: -------------------------------------------------------------------------------- 1 | class LpSolve(object): 2 | """ 3 | LpSolve 描述一个线性规划问题的解 4 | 5 | :attributes success: 是否得到了最优解 6 | :attributes description: 解的描述 7 | :attributes solve: 最优解 8 | :attributes target: 最优目标函数值 9 | """ 10 | 11 | def __init__(self, success: bool, description: str, solve: list, target: float): 12 | self.success = success 13 | self.description = description 14 | self.solve = solve 15 | self.target = target 16 | 17 | def __str__(self): 18 | return f'最优化成功\t: {self.success}\n解的描述\t: {self.description}\n最优解\t: {self.solve}\n最优目标函数值\t: {self.target}' 19 | -------------------------------------------------------------------------------- /CuttingPlane/linprog/solver.py: -------------------------------------------------------------------------------- 1 | class LpSolver(object): 2 | """线性规划问题的解法抽象类 3 | 4 | 该项目中的所有解法实现都继承于此类。 5 | """ 6 | def __init__(self, problem): 7 | """ 8 | :param problem: 待解决的问题(LpProblem实例) 9 | """ 10 | pass 11 | 12 | def solve(self, **kwargs): 13 | """ 14 | 求解算法入口,调用此方法开始对该LpSolver实例化时传入的 LpProblem 进行求解 15 | 16 | :return: 求解结果(LpSolve实例) 17 | """ 18 | pass 19 | -------------------------------------------------------------------------------- /HungarianAssignment/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | HungarianAssignment 提供求解指派问题的「匈牙利算法」 3 | 4 | Public 5 | ------ 6 | func hungarian_assignment(cost_matrix): 对指派问题使用「匈牙利算法」进行求解。 7 | """ 8 | from HungarianAssignment.hungarian_assignment import hungarian_assignment 9 | -------------------------------------------------------------------------------- /HungarianAssignment/hungarian_assignment.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy import optimize 3 | 4 | 5 | def hungarian_assignment(cost_matrix): 6 | """ 7 | hungarian_assignment 指派问题的匈牙利解法 8 | 9 | :param cost_matrix: 指派问题的系数矩阵 10 | :return: row_ind, col_ind : array 11 | An array of row indices and one of corresponding column indices giving 12 | the optimal assignment. The cost of the assignment can be computed 13 | as ``cost_matrix[row_ind, col_ind].sum()``. The row indices will be 14 | sorted; in the case of a square cost matrix they will be equal to 15 | ``numpy.arange(cost_matrix.shape[0])``. 16 | """ 17 | 18 | cost_matrix = np.asarray(cost_matrix) 19 | if len(cost_matrix.shape) != 2: 20 | raise ValueError("expected a matrix (2-d array), got a %r array" 21 | % (cost_matrix.shape,)) 22 | 23 | # The algorithm expects more columns than rows in the cost matrix. 24 | if cost_matrix.shape[1] < cost_matrix.shape[0]: 25 | cost_matrix = cost_matrix.T 26 | transposed = True 27 | else: 28 | transposed = False 29 | 30 | state = _Hungarian(cost_matrix) 31 | step = None if 0 in cost_matrix.shape else _simplize4zero 32 | cnt = 0 33 | while step: 34 | step = step(state) 35 | cnt += 1 36 | if cnt > 1000: # 防止意外陷入死循环,把错误的情况交给 optimize.linear_sum_assignment 处理 37 | print("[ERROR] hungarian_assignment Failed, Try optimize.linear_sum_assignment") 38 | return optimize.linear_sum_assignment(cost_matrix) 39 | 40 | if transposed: 41 | assigned = state.assigned.T 42 | else: 43 | assigned = state.assigned 44 | return np.where(assigned == 1) 45 | # return np.array(state.assigned == True, dtype=int) 46 | 47 | 48 | class _Hungarian(object): 49 | """ 50 | State of the Hungarian algorithm. 51 | """ 52 | 53 | def __init__(self, cost_matrix): 54 | self.cost = np.array(cost_matrix) 55 | 56 | r, c = self.cost.shape 57 | self.row_covered = np.zeros(r, dtype=bool) 58 | self.col_covered = np.zeros(c, dtype=bool) 59 | self.assigned = np.zeros((r, c), dtype=int) 60 | 61 | 62 | def _simplize4zero(state: _Hungarian): 63 | """ 64 | step1. 变换指派问题的系数矩阵(c_{ij})为(b_{ij}),使在(bij)的各行各列中都出现0元素,即: 65 | 66 | 1. 从(c_{ij})的每行元素都减去该行的最小元素; 67 | 2. 再从所得新系数矩阵的每列元素中减去该列的最小元素。 68 | 69 | :param state: _Hungarian, State of the Hungarian algorithm. 70 | :return: next step: _try_assign 71 | """ 72 | # 从(c_{ij})的每行元素都减去该行的最小元素 73 | state.cost -= state.cost.min(axis=1)[:, np.newaxis] 74 | # 再从所得新系数矩阵的每列元素中减去该列的最小元素 75 | state.cost -= state.cost.min(axis=0)[np.newaxis, :] 76 | 77 | return _try_assign 78 | 79 | 80 | def _try_assign(state: _Hungarian): 81 | """ 82 | step2. 进行试指派,以寻求最优解。 83 | 在(b_{ij})中找尽可能多的独立0元素, 84 | 若能找出n个独立0元素,就以这n个独立0元素对应解矩阵(x_{ij})中的元素为1,其余为0,这就得到最优解。 85 | 86 | 找独立0元素的步骤为: 87 | 1. __assign_row: 从只有一个0元素的行开始,给该行中的0元素加圈,记作◎。 88 | 然后划去◎所在列的其它0元素,记作Ø ;这表示该列所代表的任务已指派完,不必再考虑别人了。 89 | 依次进行到最后一行。 90 | 2. __assign_col: 从只有一个0元素的列开始(画Ø的不计在内),给该列中的0元素加圈,记作◎; 91 | 然后划去◎所在行的0元素,记作Ø ,表示此人已有任务,不再为其指派其他任务。 92 | 依次进行到最后一列。 93 | 3. __assign_single_zeros: 若仍有没有划圈且未被划掉的0元素,则同行(列)的0元素至少有两个, 94 | 比较这行各0元素所在列中0元素的数目,选择0元素少的这个0元素加圈(表示选择性多的要“礼让”选择性少的)。 95 | 然后划掉同行同列 的其它0元素。 96 | 可反复进行,直到所有0元素都已圈出和划掉为止。 97 | 4. 若◎元素的数目m等于矩阵的阶数n(即 m=n),那么这指派问题的最优解已得到。 98 | 若 m < n, 则转入下一步。 99 | 100 | :param state: _Hungarian, State of the Hungarian algorithm. 101 | :return: next step: None if best reached else _draw_lines 102 | """ 103 | state.assigned = np.zeros(state.cost.shape, dtype=int) 104 | 105 | __assign_row(state) 106 | __assign_col(state) 107 | __assign_single_zeros(state) 108 | 109 | assigned_zeros = np.where(state.assigned == 1)[0] 110 | if len(assigned_zeros) == len(state.cost): 111 | # 若◎元素的数目m等于矩阵的阶数n(即:m=n),那么这指派问题的最优解已得到 112 | return None 113 | elif len(assigned_zeros) < len(state.cost): 114 | return _draw_lines 115 | 116 | raise RuntimeError(assigned_zeros) 117 | 118 | 119 | def __assign_row(state: _Hungarian): 120 | """ 121 | step2.1. (Called by _try_assign) 从只有一个0元素的行开始,给该行中的0元素加圈,记作◎。 122 | 然后划去◎所在列的其它0元素,记作Ø ;这表示该列所代表的任务已指派完,不必再考虑别人了。 123 | 依次进行到最后一行。 124 | 125 | :param state: _Hungarian, State of the Hungarian algorithm. 126 | :return: None 127 | """ 128 | start_flag = True 129 | for i, row in enumerate(state.cost): # 从只有一个0元素的行开始,依次进行到最后一行。 130 | zero_idx = np.where(row == 0)[0] 131 | if not start_flag or len(zero_idx) == 1: # 只有一个0元素的行 132 | start_flag = False 133 | j = zero_idx[np.random.randint(len(zero_idx))] 134 | if state.assigned[i, j] == 0: 135 | for k, _ in enumerate(state.cost.T[j]): 136 | if state.cost[k, j] == 0: 137 | state.assigned[k, j] = -1 # 划去◎所在列的其它0元素,记作Ø,表示该列所代表的任务已 指派完,不必再考虑别人了 138 | state.assigned[i, j] = 1 # 给该行中的0元素加圈,记作◎ 139 | 140 | 141 | def __assign_col(state: _Hungarian): 142 | """ 143 | step2.2. (Called by _try_assign) 从只有一个0元素的列开始(画Ø的不计在内),给该列中的0元素加圈,记作◎; 144 | 然后划去◎所在行的0元素,记作Ø ,表示此人已有任务,不再为其指派其他任务。 145 | 依次进行到最后一列。 146 | 147 | :param state: _Hungarian, State of the Hungarian algorithm. 148 | :return: None 149 | """ 150 | start_flag = True 151 | for i, col in enumerate(state.cost.T): # 从只有一个0元素的列开始(画Ø的不计在内), 依次进行到最后一列。 152 | zero_idx = np.where(col == 0)[0] 153 | zero_idx_except_slashed = np.where(state.assigned.T[i][zero_idx] == 0)[0] 154 | # if not start_flag or (state.assigned[zero_idx[0]][i] == 0 and len(zero_idx_except_slashed) == 1): 155 | if not start_flag or (len(zero_idx_except_slashed) == 1): # 只有一个0元素的列(画Ø的不计在内) 156 | start_flag = False 157 | j = zero_idx[np.random.randint(len(zero_idx))] 158 | if state.assigned[j, i] == 0: 159 | for k, _ in enumerate(state.cost[j]): 160 | if state.cost[j, k] == 0: 161 | state.assigned[j, k] = -1 # 划去◎所在列的其它0元素,记作Ø,表示该列所代表的任务已 指派完,不必再考虑别人了 162 | state.assigned[j, i] = 1 # 给该行中的0元素加圈,记作◎ 163 | 164 | 165 | def __assign_single_zeros(state: _Hungarian): 166 | """ 167 | step2.3. (Called by _try_assign) 若仍有没有划圈且未被划掉的0元素,则同行(列)的0元素至少有两个, 168 | 比较这行各0元素所在列中0元素的数目,选择0元素少的这个0元素加圈(表示选择性多的要“礼让”选择性少的)。 169 | 然后划掉同行同列 的其它0元素。 170 | 可反复进行,直到所有0元素都已圈出和划掉为止。 171 | 172 | :param state: _Hungarian, State of the Hungarian algorithm. 173 | :return: None 174 | """ 175 | cnt = 0 176 | while cnt < 100: 177 | cnt += 1 178 | zx, zy = np.where(state.cost == 0) # 0元素 179 | for i in range(len(zx)): 180 | if state.assigned[zx[i], zy[i]] == 0: # 没有划圈且未被划掉的0元素 181 | zeros_idx_in_row = np.where(state.cost[zx[i]] == 0)[0] # 这行各0元素 182 | if len(zeros_idx_in_row) > 1: 183 | # 比较这行各0元素所在列中0元素的数目 184 | zs_each_col = [(z, len(np.where(state.cost.T[z] == 0)[0])) for z in zeros_idx_in_row] 185 | min_zeros_idx = min(zs_each_col, key=lambda x: x[1])[0] 186 | # 选择0元素少的这个0元素加圈(表示选择性多的要“礼让”选择性少的) 187 | state.assigned[zx[i], zeros_idx_in_row] = -1 188 | for k, _ in enumerate(state.cost.T[min_zeros_idx]): 189 | if state.cost[k, min_zeros_idx] == 0: 190 | state.assigned[k, min_zeros_idx] = -1 # 划去◎所在列的其它0元素,记作Ø,表示该列所代表的任务已 指派完,不必再考虑别人了 191 | state.assigned[zx[i], min_zeros_idx] = 1 192 | continue 193 | zeros_idx_in_col = np.where(state.cost.T[zy[i]] == 0)[0] # 这列各0元素 194 | if len(zeros_idx_in_col) > 1: 195 | # 比较这列各0元素所在行中0元素的数目 196 | zs_each_row = [(z, len(np.where(state.cost[z] == 0)[0])) for z in zeros_idx_in_col] 197 | min_zeros_idx = min(zs_each_row, key=lambda x: x[1])[0] 198 | # 选择0元素少的这个0元素加圈(表示选择性多的要“礼让”选择性少的) 199 | state.assigned[zeros_idx_in_col, zx[i]] = -1 200 | for k, _ in enumerate(state.cost[min_zeros_idx]): 201 | if state.cost[min_zeros_idx, k] == 0 and state.assigned[min_zeros_idx, k] == 0: 202 | state.assigned[min_zeros_idx, k] = -1 # 划去◎所在列的其它0元素,记作Ø,表示该列所代表的任务已 指派完,不必再考虑别人了 203 | state.assigned[min_zeros_idx, zy[i]] = 1 204 | zx, zy = np.where(state.cost == 0) # 0元素 205 | if not any([state.assigned[zx[i], zy[i]] == 0 for i in range(len(zx))]): # 所有0元素都已圈出和划掉 206 | return 207 | 208 | raise RuntimeError("Too many iters:", state.assigned) 209 | 210 | 211 | def _draw_lines(state: _Hungarian): 212 | """ 213 | step3. 用最少的直线通过所有0元素。具体方法为: 214 | 215 | 1. 对没有◎的行打“√”; 216 | 2. 对已打“√” 的行中所有含Ø元素的列打“√” ; 217 | 3. 再对打有“√”的列中含◎ 元素的行打“√” ; 218 | 4. 重复2、 3直到得不出新的打√号的行、列为止; 219 | 5. 对没有打√号的行画横线,有打√号的列画纵线,这就得到覆 盖所有0元素的最少直线数 l 。 220 | 221 | 注: l 应等于 m, 若不相等,说明试指派过程有误,回到第2步,另行试指派; 222 | 若 l = m < n,表示还不能确定最优指派方案,须再变换当前的系数矩阵,以找到n个独立的0元素,为此转第4步。 223 | 224 | :param state: _Hungarian, State of the Hungarian algorithm. 225 | :return: _transform_cost if assignment is correct else _try_assign 226 | """ 227 | state.row_covered[:] = 0 228 | state.col_covered[:] = 0 229 | # 1、对没有◎的行打“√”; 230 | for i, row in enumerate(state.assigned): 231 | assigned_zeros = np.where(row == 1)[0] 232 | if len(assigned_zeros) == 0: 233 | state.row_covered[i] = True 234 | 235 | old_row_covered = np.zeros(state.row_covered.shape) 236 | old_col_covered = np.zeros(state.row_covered.shape) 237 | 238 | while np.any(state.row_covered != old_row_covered) or np.any(state.col_covered != old_col_covered): 239 | # 2、对已打“√” 的行中所有含Ø元素的列打“√” 240 | for i, covered in enumerate(state.row_covered): 241 | if covered: 242 | slashed_zeros = np.where(state.assigned[i, :] == -1)[0] 243 | state.col_covered[slashed_zeros] = True 244 | 245 | # 3、再对打有“√”的列中含◎元素的行打“√” 246 | for i, covered in enumerate(state.col_covered): 247 | if covered: 248 | assigned_zeros = np.where(state.assigned[:, i] == 1)[0] 249 | state.row_covered[assigned_zeros] = True 250 | # 重复2、3直到得不出新的打√号的行、列为止; 251 | old_row_covered = state.row_covered.copy() 252 | old_col_covered = state.col_covered.copy() 253 | 254 | # 对没有打√号的行画横线,有打√号的列画纵线 255 | state.row_covered = (state.row_covered == False) 256 | # ls: 覆盖所有0元素的最少直线数 257 | ls = len(np.where(state.row_covered == True)[0]) + len(np.where(state.col_covered == True)[0]) 258 | assigned_zeros = np.where(state.assigned == 1)[0] 259 | if ls == len(assigned_zeros) and ls < len(state.cost): 260 | return _transform_cost 261 | elif ls == len(assigned_zeros) and ls == len(state.cost): 262 | return None 263 | # 不相等,说明试指派过程有误,回到第2步,另行试指派; 264 | return _try_assign 265 | # raise RuntimeError(ls, len(assigned_zeros), len(state.cost)) 266 | 267 | 268 | def _transform_cost(state: _Hungarian): 269 | """ 270 | step4. 变换矩阵(b_{ij})以增加0元素 271 | 272 | 在没有被直线通过的所有元素中找出最小值, 273 | 没有被直线通过的所有元素减去这个最小元素; 274 | 直线交点处的元素加上这个最小值。 275 | 新系数矩阵的最优解和原问题仍相同。 276 | 转回第2步。 277 | 278 | :param state: _Hungarian, State of the Hungarian algorithm. 279 | :return: _try_assign 280 | """ 281 | # 找出被直线通过的所有元素 282 | row_idx_covered = np.where(state.row_covered == True)[0] 283 | col_idx_covered = np.where(state.col_covered == True)[0] 284 | # 找出没有被直线通过的所有元素 285 | row_idx_not_covered = np.where(state.row_covered == False)[0] 286 | col_idx_not_covered = np.where(state.col_covered == False)[0] 287 | 288 | # 在没有被直线通过的所有元素中找出最小值 289 | min_element = state.cost[row_idx_not_covered].T[col_idx_not_covered].min() 290 | 291 | # 没有被直线通过的所有元素减去这个最小元素 292 | for r in row_idx_not_covered: 293 | for c, _ in enumerate(state.cost[r]): 294 | if c in col_idx_not_covered: 295 | state.cost[r, c] -= min_element 296 | # state.cost[row_idx_not_covered].T[col_idx_not_covered].T -= min_element 297 | 298 | # 直线交点处的元素加上这个最小值 299 | # state.cost[row_idx_covered].T[col_idx_covered].T += min_element 300 | for r in row_idx_covered: 301 | for c, _ in enumerate(state.cost[r]): 302 | if c in col_idx_covered: 303 | state.cost[r][c] += min_element 304 | 305 | return _try_assign 306 | 307 | 308 | def _test_simplize4zero(): 309 | """ 310 | expected: [[ 0 13 7 0] [ 6 0 6 9] [ 0 5 3 2] [ 0 1 0 0]] 311 | """ 312 | c = [[2, 15, 13, 4], [10, 4, 14, 15], [9, 14, 16, 13], [7, 8, 11, 9]] 313 | print("_test_simplize4zero:") 314 | s = _Hungarian(c) 315 | _simplize4zero(s) 316 | print(s.cost) 317 | 318 | 319 | def _test_try_assign(): 320 | """ 321 | expected: [[-1 0 0 1] [ 0 1 0 0] [ 1 0 0 0] [-1 0 1 -1]] 322 | """ 323 | b = [[0, 13, 7, 0], [6, 0, 6, 9], [0, 5, 3, 2], [0, 1, 0, 0]] 324 | s = _Hungarian(b) 325 | print("_test_try_assign:") 326 | _try_assign(s) 327 | print(s.assigned) 328 | 329 | 330 | def _test_draw_lines_transform_cost(): 331 | """ 332 | expected: row: [ True True False True False] 333 | col: [ True False False False False] 334 | transformed cost: [[ 7 0 2 0 2] [ 4 3 0 0 0] [ 0 8 3 5 0] [11 8 0 0 4] [ 0 4 1 4 3]] 335 | """ 336 | print("_test_draw_lines:") 337 | c = [[12, 7, 9, 7, 9], [8, 9, 6, 6, 6], [7, 17, 12, 14, 9], [15, 14, 6, 6, 10], [4, 10, 7, 10, 9]] 338 | s = _Hungarian(c) 339 | _simplize4zero(s) 340 | _try_assign(s) 341 | _draw_lines(s) 342 | print("row:", s.row_covered) 343 | print("col:", s.col_covered) 344 | _transform_cost(s) 345 | print("transformed cost:\n", s.cost) 346 | 347 | 348 | def _test1(): 349 | print("Test1\n" + '-' * 10) 350 | c = [[2, 15, 13, 4], [10, 4, 14, 15], [9, 14, 16, 13], [7, 8, 11, 9]] 351 | r = hungarian_assignment(c) 352 | print(r) 353 | assert np.all(r == np.array([[0, 1, 2, 3], [3, 1, 0, 2]])) 354 | m = np.zeros(np.array(c).shape, dtype=int) 355 | m[r] = 1 356 | print(m) 357 | 358 | 359 | def _test2(): 360 | # from scipy.optimize import linear_sum_assignment 361 | print("\nTest2\n" + '-' * 10) 362 | c = [[12, 7, 9, 7, 9], [8, 9, 6, 6, 6], [7, 17, 12, 14, 9], [15, 14, 6, 6, 10], [4, 10, 7, 10, 9]] 363 | r = hungarian_assignment(c) 364 | print(r) 365 | assert np.all(r == np.array([[0, 1, 2, 3, 4], [1, 3, 4, 2, 0]])) or np.all( 366 | r == np.array([[0, 1, 2, 3, 4], [1, 2, 4, 3, 0]])) 367 | # 两个答案,一个是书上的,一个是 scipy.optimize.linear_sum_assignment 解出来的,总消耗是一样的。 368 | m = np.zeros(np.array(c).shape, dtype=int) 369 | m[r] = 1 370 | print(m) 371 | 372 | 373 | def _test3(): 374 | print("\nTest3\n" + '-' * 10) 375 | c = [[6, 7, 11, 2], [4, 5, 9, 8], [3, 1, 10, 4], [5, 9, 8, 2]] 376 | r = hungarian_assignment(c) 377 | print(r) 378 | m = np.zeros(np.array(c).shape, dtype=int) 379 | m[r] = 1 380 | print(m) 381 | 382 | 383 | def _test4(): 384 | print("\nTest4\n" + '-' * 10) 385 | c = [[7, 5, 9, 8, 11], [9, 12, 7, 11, 9], [8, 5, 4, 6, 9], [7, 3, 6, 9, 6], [4, 6, 7, 5, 11]] 386 | r = hungarian_assignment(c) 387 | print(r) 388 | m = np.zeros(np.array(c).shape, dtype=int) 389 | m[r] = 1 390 | print(m) 391 | 392 | 393 | if __name__ == "__main__": 394 | # _test_simplize4zero() # pass 395 | # _test_try_assign() # pass 396 | # _test_draw_lines_transform_cost() # pass 397 | _test1() 398 | _test2() 399 | _test3() 400 | _test4() 401 | -------------------------------------------------------------------------------- /IntegerProgExperiment.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 cdfmlr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MonteCarlo/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MonteCarlo 提供求解整数规划问题的「蒙特卡洛法」 3 | 4 | Public 5 | ------ 6 | func: monte_carlo(x_nums, fun, cons, bounds, random_times=10 ** 5): 对整数规划问题使用「蒙特卡洛法」进行求解。 7 | """ 8 | 9 | from MonteCarlo.monte_carlo import monte_carlo 10 | -------------------------------------------------------------------------------- /MonteCarlo/monte_carlo.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import random 4 | from multiprocessing import Process, Queue 5 | 6 | 7 | def _monte_carlo_processing(x_nums, fun, cons, bounds, random_times, q: Queue): 8 | """ 9 | monte_carlo 的子进程函数,完成随机试验,向 Queue 传递结果 10 | """ 11 | random.seed(time.time() + os.getpid()) 12 | pb = 0 13 | xb = [] 14 | for i in range(random_times): 15 | x = [random.randint(bounds[i][0], bounds[i][1]) for i in range(x_nums)] # 产生一行x_nums列的区间[0, 99] 上的随机整数 16 | rf = fun(x) 17 | rg = cons(x) 18 | if all((a < 0 for a in rg)): # 若 rg 中所有元素都小于 0,即符合约束条件 19 | if pb < rf: 20 | xb = x 21 | pb = rf 22 | q.put({"fun": pb, "x": xb}) 23 | 24 | 25 | def monte_carlo(x_nums, fun, cons, bounds, random_times=10 ** 5): 26 | """ 27 | monte_carlo 对整数规划问题使用「蒙特卡洛法」求满意解 28 | 29 | 对于线性、非线性的整数规划,在一定计算量下可以考虑用 蒙特卡洛法 得到一个满意解。 30 | 31 | 注意:蒙特卡洛法只能在一定次数的模拟中求一个满意解(通常不是最优的),而且对于每个变量必须给出有明确上下界的取值范围。 32 | 33 | 问题模型: 34 | Minimize: fun(x) 35 | 36 | Subject to: cons(x) <= 0 37 | (x are integers) 38 | 39 | Parameters 40 | ---------- 41 | :param x_nums: `int`, 未知数向量 x 的元素个数 42 | :param fun: `(x: list) -> float`, 要最小化的目标函数 43 | :param cons: `(x: list) -> list`, 小于等于 0 的约束条件 44 | :param bounds: `list`, 各个 x 的取值范围 45 | :param random_times: `int`, 随机模拟次数 46 | 47 | Returns 48 | ------- 49 | :return: {"x": array([...]), "fun": ...} 50 | - x: 最优解 51 | - fun: 最优目标函数值 52 | 53 | Examples 54 | -------- 55 | 试求得如下整数规划问题的一个满意解: 56 | Min x_0 + x_1 57 | s.t. 2 * x_0 + x_1 <= 6 58 | 4 * x_0 + 5 * x_1 <= 20 59 | (x_0、x_1 为整数) 60 | 编写目标函数: 61 | >>> fun = lambda x: x[0] + x[1] 62 | 编写约束条件: 63 | >>> cons = lambda x: [2 * x[0] + x[1] - 6, 4 * x[0] + 5 * x[1] - 20] 64 | 指定取值范围: 65 | >>> bounds = [(0, 100), (0, 100)] 66 | 调用蒙特卡洛法求解: 67 | >>> monte_carlo(2, fun, cons, bounds) 68 | {'fun': 4, 'x': [1, 3]} 69 | 可以看的 monte_carlo 返回了一个满意解(事实上,这是个最优解,但一般情况下不是)。 70 | """ 71 | result_queue = Queue() 72 | processes = [] 73 | 74 | cpus = os.cpu_count() + 2 75 | if cpus < 1: 76 | cpus = 1 77 | sub_times = random_times // cpus 78 | for i in range(cpus): 79 | processes.append(Process(target=_monte_carlo_processing, 80 | args=(x_nums, fun, cons, bounds, sub_times, result_queue))) 81 | for p in processes: 82 | p.start() 83 | 84 | for p in processes: 85 | p.join() 86 | 87 | if not result_queue.empty(): 88 | data = result_queue.get() 89 | best = dict(data) 90 | while not result_queue.empty(): 91 | data = result_queue.get() 92 | if data["fun"] < best["fun"]: 93 | best = dict(data) 94 | return best 95 | return None 96 | 97 | 98 | def _test1(): 99 | def fun(x): 100 | return x[0] + x[1] 101 | 102 | def cons(x): 103 | return [ 104 | 2 * x[0] + x[1] - 6, 105 | 4 * x[0] + 5 * x[1] - 20, 106 | ] 107 | 108 | bounds = [(0, 100), (0, 100)] 109 | r = monte_carlo(2, fun, cons, bounds) 110 | print(r) 111 | 112 | 113 | def _test2(): 114 | def fun(x): 115 | return 40 * x[0] + 90 * x[1] 116 | 117 | def cons(x): 118 | return [ 119 | 9 * x[0] + 7 * x[1] - 56, 120 | 7 * x[0] + 20 * x[1] - 70, 121 | ] 122 | 123 | bounds = [(0, 100), (0, 100)] 124 | r = monte_carlo(2, fun, cons, bounds) 125 | print(r) 126 | 127 | 128 | def _test3(): 129 | def f(x: list) -> int: 130 | return x[0] ** 2 + x[1] ** 2 + 3 * x[2] ** 2 + \ 131 | 4 * x[3] ** 2 + 2 * x[4] ** 2 - 8 * x[0] - 2 * x[1] - \ 132 | 3 * x[2] - x[3] - 2 * x[4] 133 | 134 | def g(x: list) -> list: 135 | return [ 136 | sum(x) - 400, 137 | x[0] + 2 * x[1] + 2 * x[2] + x[3] + 6 * x[4] - 800, 138 | 2 * x[0] + x[1] + 6 * x[2] - 200, 139 | x[2] + x[3] + 5 * x[4] - 200 140 | ] 141 | 142 | bounds = [(0, 99)] * 5 143 | r = monte_carlo(5, f, g, bounds, random_times=10 ** 6) 144 | print(r) 145 | 146 | 147 | if __name__ == "__main__": 148 | _test1() 149 | _test2() 150 | _test3() 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IntegerProgExperiment 2 | 线性规划课程实验——整数规划问题的求解方法 3 | 4 | ## 仓库内容 5 | 6 | ``` 7 | . 8 | ├── BranchAndBound 整数线性规划问题的分支定界法实现 9 | ├── CuttingPlane 整数线性规划问题的割平面法实现 10 | ├── HungarianAssignment 指派问题的匈牙利算法实现 11 | ├── MonteCarlo 整数规划问题的蒙特卡洛法实现 12 | └── experiment.py 各算法实现的使用示例 13 | ``` 14 | 15 | See Docs Here:https://cdfmlr.github.io/IntegerProgExperiment/ 16 | 17 | ## 实现简介 18 | 19 | ### 分支定界法 20 | 21 | > `BranchAndBound`: 整数线性规划问题的「分支定界法」实现。 22 | 23 | #### 问题模型 24 | 25 | ``` 26 | Minimize: c^T * x 27 | 28 | Subject to: A_ub * x <= b_ub 29 | A_eq * x == b_eq 30 | (x are integers) 31 | ``` 32 | 33 | #### 算法思路 34 | 35 | 1. 求整数规划松弛问题的最优解, 若松弛问题的最优解满足整数要求,则得到整数规划的最优解; 否则转下一步; 36 | 37 | 2. 分枝、定界与剪枝 38 | 39 | 选一个非整数解的变量xi,在松弛问题中加上约束: 40 | $x_i≤[x_i] 和 x_i≥[x_i]+1$($[x_i]$: 小于 $x_i$ 的最大整数) 41 | 42 | 构成两个新的松弛问题,称为分枝。 43 | 44 | 检查所有分枝的最优解及最优值,进行定界、剪枝: 45 | 46 | - 若某分枝的最优解是整数并且目标函数值大于其它分枝的最优值 ,则将其它分枝剪去不再计算; 47 | - 若还存在非整数解并且最优值大于整数解的最优值,转 2),需要继续分枝、定界、剪枝,直到得到最优解 $z^*$。 48 | 49 | #### 实现 API 50 | 51 | ```python 52 | BranchAndBound.branch_and_bound(c, A_ub, b_ub, A_eq, b_eq, bounds, bnbTreeNode=None) 53 | ``` 54 | 55 | 该算法实现使用递归调用实现,底层对于松弛问题求解调用 `scipy.optimize.linprog` 完成。 56 | 57 | 参数 `c, A_ub, b_ub, A_eq, b_eq, bounds` 的要求与 `scipy.optimize.linprog` 的同名参数完全相同。 58 | 59 | 在使用过程中,可以提供一个 `BranchAndBound.BnBTreeNode` 实例作为根节点来记录求解过程,得到一个求解过程的树形图。 60 | 61 | #### 使用示例 62 | 63 | 对于问题: 64 | 65 | ``` 66 | min 3 * x_0 + 4 * x_1 + x_2 67 | 68 | s.t. x_0 + 6 * x_1 + 2 * x_2 >= 5 69 | 2 * x_0 >= 3 70 | x_0, x_1, x_2 >= 0, 为整数 71 | ``` 72 | 73 | 编写如下代码使用实现的接口进行求解: 74 | 75 | ```python 76 | import BranchAndBound 77 | 78 | c = [3, 4, 1] 79 | A_ub = [[-1, -6, -2], [-2, 0, 0]] 80 | b_ub = [-5, -3] 81 | bounds = [(0, None), (0, None), (0, None)] 82 | tree = BranchAndBound.BnBTree() 83 | r = BranchAndBound.branch_and_bound(c, A_ub, b_ub, None, None, bounds, tree.root) 84 | print(r) 85 | print(tree) 86 | ``` 87 | 88 | 输出: 89 | 90 | ``` 91 | {'success': True, 'x': array([2., 0., 2.]), 'fun': 8.0} 92 | -- Root: [1.5 0. 1.75] -> 6.250000000000001 93 | |-- x[0] <= 1: nan -> 1.0 94 | |-- x[0] >= 2: [2. 0. 1.5] -> 7.5 95 | | |-- x[2] <= 1: [2. 0.16666667 1.] -> 7.666666666666667 96 | | | |-- x[1] <= 0: [3. 0. 1.] -> 10.0 97 | | | |-- x[1] >= 1: [2. 1. 0.] -> 10.0 98 | | |-- x[2] >= 2: [2. 0. 2.] -> 8.0 99 | ``` 100 | 101 | ### 割平面法 102 | 103 | > `CuttingPlane`: 整数线性规划问题的割平面法实现。 104 | 105 | (这个实现有 Bug,我没改,对于部分问题会求解失败,不推荐使用) 106 | 107 | #### 问题模型 108 | 109 | ``` 110 | Maximize: c^T * x 111 | 112 | Subject to: A * x == b 113 | (x are integers) 114 | ``` 115 | 116 | #### 算法思路 117 | 118 | 先不考虑变量的整数约束,求解相应的线性规划, 然后不断增加线性约束条件(即割平面), 将原可行域割掉不含整数可行解的一部分, 最终得到一个具有整数坐标顶点的可行域, 而该顶点恰好是原整数规划问题的最优解。 119 | 120 | #### 实现 API 121 | 122 | ```python 123 | CuttingPlane.cutting_plane(c, A, b) 124 | ``` 125 | 126 | 该算法实现使用递归调用实现,底层对于松弛问题求解调用 [`cdfmlr/SimplexLinprog`](https://github.com/cdfmlr/SimplexLinprog) 中的 `linprog.simplex_method.SimplexMethod` 完成。 127 | 128 | 参数 `c, A, b` 的要求和 `linprog.simplex_method.SimplexMethod` 完全相同。 129 | 130 | 使用过程中要注意将问题化为标准型。 131 | 132 | #### 使用示例 133 | 134 | 对于问题: 135 | 136 | ``` 137 | max x_0 + x_1 138 | 139 | s.t. 2 * x_0 + x_1 <= 6 140 | 4 * x_0 + 5 * x_1 <= 20 141 | x_0, x_1 >= 0, 为整数 142 | ``` 143 | 144 | 编写如下代码使用实现的接口进行求解: 145 | 146 | ```python 147 | import CuttingPlane 148 | 149 | c = [1, 1, 0, 0] 150 | a = [[2, 1, 1, 0], [4, 5, 0, 1]] 151 | b = [6, 20] 152 | r = CuttingPlane.cutting_plane(c, a, b) 153 | print(r) 154 | ``` 155 | 156 | 输出: 157 | 158 | ```python 159 | {'success': True, 'x': array([2., 2., 0., 2., 0.]), 'fun': -0.33333333333333326} 160 | ``` 161 | 162 | ### 蒙特卡洛法 163 | 164 | > `MonteCarl`: 整数规划问题的蒙特卡洛法实现。 165 | 166 | #### 问题模型 167 | 168 | ``` 169 | Minimize: fun(x) 170 | 171 | Subject to: cons(x) <= 0 172 | (x are integers) 173 | ``` 174 | 175 | #### 算法思路 176 | 177 | 当所求解问题是某种随机事件出现的概率,或者是某个随机变量的期望值时,通过某种“实验”的方法,以这种事件出现的频率估计这一随机事件的概率,或者得到这个随机变量的某些数字特征,并将其作为问题的解。 178 | 179 | 对于线性、非线性的整数规划,在一定计算量下可以用这种思想,通过大量的随机试验,得到一个满意解。 180 | 181 | #### 实现 API 182 | 183 | ```python 184 | MonteCarl.monte_carlo(x_nums, fun, cons, bounds, random_times=10**5) 185 | ``` 186 | 187 | Monte carlo 方法可以求解非线性的整数规划,目标函数、约束条件不在由向量、矩阵给出,而可以直接使用两个方法 `fun`、`cons` 来表达。 188 | 189 | 注意:蒙特卡洛法只能在一定次数的模拟中求一个满意解(通常不是最优的),而且对于每个变量必须给出**有明确上下界的取值范围**。 190 | 191 | #### 使用示例 192 | 193 | 对于问题: 194 | 195 | ``` 196 | max x_0 + x_1 197 | 198 | s.t. 2 * x_0 + x_1 <= 6 199 | 4 * x_0 + 5 * x_1 <= 20 200 | 0 <= x_0, x_1 <= 100, 为整数 201 | ``` 202 | 203 | 编写如下代码使用实现的接口进行求解: 204 | 205 | ```python 206 | import MonteCarlo 207 | 208 | def fun(x): 209 | return x[0] + x[1] 210 | 211 | def cons(x): 212 | return [ 213 | 2 * x[0] + x[1] - 6, 214 | 4 * x[0] + 5 * x[1] - 20, 215 | ] 216 | 217 | bounds = [(0, 100), (0, 100)] 218 | r = MonteCarlo.monte_carlo(2, fun, cons, bounds) 219 | print(r) 220 | ``` 221 | 222 | 输出: 223 | 224 | ``` 225 | {'fun': 4, 'x': [1, 3]} 226 | ``` 227 | 228 | ### 匈牙利算法 229 | 230 | > `HungarianAssignment`: 指派问题的匈牙利算法实现。 231 | 232 | #### 问题模型 233 | 234 | 设 n 个人被分配去做 n 件工作,规定每个人只做一件工作,每 件工作只有一个人去做。已知第i个人去做第j 件工作的效率 ( 时间或费用)为`c_{ij}` (i=1,2,...,n; j=1,2,...,n)并假设 `c_{ij} ≥ 0`。问 应如何分配才能使总效率( 时间或费用)最高? 235 | 236 | 设决策变量: 237 | 238 | ``` 239 | x_{ij} = 1 若指派第i个人做第j件工作 240 | or 0 不指派第i个人做第j件工作 241 | for i, j = 1, 2, ..., n 242 | ``` 243 | 244 | 则问题可表示为: 245 | 246 | ``` 247 | \min \sum_i \sum_j C_{i,j} X_{i,j} 248 | 249 | s.t. each row is assignment to at most one column, and each column to at most one row. 250 | ``` 251 | 252 | #### 算法思路 253 | 254 | step1. 变换指派问题的系数矩阵(`c_{ij}`)为(`b_{ij}`),使在(`b_{ij}`)的各行各列中都出现0元素,即: 255 | 256 | 1. 从(`c_{ij}`)的每行元素都减去该行的最小元素; 257 | 2. 再从所得新系数矩阵的每列元素中减去该列的最小元素。 258 | 259 | step2. 进行试指派,以寻求最优解。 260 | 261 | 在(`b_{ij}`)中找尽可能多的独立0元素,若能找出n个独立0元素,就以这n个独立0元素对应解矩阵(`x_{ij}`)中的元素为1,其余为0,这就得到最优解。 262 | 263 | 找独立0元素的步骤为: 264 | 1. 从只有一个0元素的行开始,给该行中的0元素加圈,记作◎。然后划去◎所在列的其它0元素,记作Ø ;这表示该列所代表的任务已指派完,不必再考虑别人了。依次进行到最后一行。 265 | 2. 从只有一个0元素的列开始(画Ø的不计在内),给该列中的0元素加圈,记作◎;然后划去◎所在行的0元素,记作Ø ,表示此人已有任务,不再为其指派其他任务。依次进行到最后一列。 266 | 3. 若仍有没有划圈且未被划掉的0元素,则同行(列)的0元素至少有两个,比较这行各0元素所在列中0元素的数目,选择0元素少的这个0元素加圈(表示选择性多的要“礼让”选择性少的)。然后划掉同行同列 的其它0元素。可反复进行,直到所有0元素都已圈出和划掉为止。 267 | 4. 若◎元素的数目m等于矩阵的阶数n(即 m=n),那么这指派问题的最优解已得到。若 m < n, 则转入下一步。 268 | 269 | step3. 用最少的直线通过所有0元素。具体方法为: 270 | 271 | 1. 对没有◎的行打“√”; 272 | 2. 对已打“√” 的行中所有含Ø元素的列打“√” ; 273 | 3. 再对打有“√”的列中含◎ 元素的行打“√” ; 274 | 4. 重复2、 3直到得不出新的打√号的行、列为止; 275 | 5. 对没有打√号的行画横线,有打√号的列画纵线,这就得到覆 盖所有0元素的最少直线数 l 。 276 | 277 | 注: l 应等于 m, 若不相等,说明试指派过程有误,回到第2步,另行试指派; 278 | 若 l = m < n,表示还不能确定最优指派方案,须再变换当前的系数矩阵,以找到n个独立的0元素,为此转第4步。 279 | 280 | step4. 变换矩阵(`b_{ij}`)以增加0元素 281 | 282 | 在没有被直线通过的所有元素中找出最小值,没有被直线通过的所有元素减去这个最小元素;直线交点处的元素加上这个最小值。新系数矩阵的最优解和原问题仍相同。转回第2步。 283 | 284 | #### 实现 API 285 | 286 | ```python 287 | HungarianAssignment.hungarian_assignment(cost_matrix) 288 | ``` 289 | 290 | 调用该函数,会对给定的指派问题试使用上述思路的实现求解。由于该函数设计与 `scipy.optimize.linear_sum_assignment` 基本一致,故在出现问题时,会调用 `linear_sum_assignment` 尝试进一步求解。 291 | 292 | #### 使用示例 293 | 294 | 对于问题: 295 | 296 | 有一份中文说明书,需译成英、日、德、俄四种文字。分别记作E、J、G、R。现有甲、乙、丙、丁四人。他们将中文说明书翻译成不同语种的说明书所需时间如下表所示。问应指派何人去完成何工作,使所需总时间最少? 297 | 298 | | 人员 \ 任务 | E | J | G | R | 299 | | ---------- | ---- | ---- | ---- | ---- | 300 | | 甲 | 2 | 15 | 13 | 4 | 301 | | 乙 | 10 | 4 | 14 | 15 | 302 | | 丙 | 9 | 14 | 16 | 13 | 303 | | 丁 | 7 | 8 | 11 | 9 | 304 | 305 | 编写如下代码使用实现的接口进行求解: 306 | 307 | ```python 308 | import HungarianAssignment 309 | 310 | c = [[2, 15, 13, 4], [10, 4, 14, 15], [9, 14, 16, 13], [7, 8, 11, 9]] 311 | r = HungarianAssignment.hungarian_assignment(c) 312 | print(r) 313 | m = np.zeros(np.array(c).shape, dtype=int) 314 | m[r] = 1 315 | print(m) 316 | ``` 317 | 318 | 输出: 319 | 320 | ``` 321 | (array([0, 1, 2, 3]), array([3, 1, 0, 2])) 322 | [[0 0 0 1] 323 | [0 1 0 0] 324 | [1 0 0 0] 325 | [0 0 1 0]] 326 | ``` 327 | 328 | ## 开放源代码 329 | 330 | MIT License 331 | 332 | Copyright (c) 2020 cdfmlr -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdfmlr/IntegerProgExperiment/bdd21c342c76e2e6cbb01cdb86b37e3f912d4158/__init__.py -------------------------------------------------------------------------------- /docs/BranchAndBound.bnb_Tree.html: -------------------------------------------------------------------------------- 1 | 2 | Python: module BranchAndBound.bnb_Tree 3 | 4 | 5 | 6 | 7 | 8 |
 
9 |  
BranchAndBound.bnb_Tree
index
IntegerProgExperiment/BranchAndBound/bnb_Tree.py
12 |

13 |

14 | 15 | 16 | 18 | 19 | 20 |
 
17 | Classes
       
21 |
builtins.object 22 |
23 |
24 |
BnBTree 25 |
BnBTreeNode 26 |
27 |
28 |
29 |

30 | 31 | 32 | 34 | 35 | 36 | 41 | 42 |
 
33 | class BnBTree(builtins.object)
   BnBTree 是表示分枝定界法求整数规划问题过程的树
37 |  
38 | Fields
39 | ------
40 | root: 树根节点
 
 Methods defined here:
43 |
__init__(self)
Initialize self.  See help(type(self)) for accurate signature.
44 | 45 |
__str__(self)
Return str(self).
46 | 47 |
48 | Data descriptors defined here:
49 |
__dict__
50 |
dictionary for instance variables (if defined)
51 |
52 |
__weakref__
53 |
list of weak references to the object (if defined)
54 |
55 |

56 | 57 | 58 | 60 | 61 | 62 | 77 | 78 |
 
59 | class BnBTreeNode(builtins.object)
   BnBTreeNode 是 BnBTree 的节点
63 |  
64 | Fields
65 | ------
66 | left    : BnBTreeNode, 左子节点,分支定界法里的左枝
67 | right   : BnBTreeNode, 右子节点,分支定界法里的右枝
68 |  
69 | x_idx   : int,   分支定界法里新增条件的变量索引
70 | x_c     : str,   分支定界法里新增条件的比较运算符 "<=" 或 ">="
71 | x_b     : float, 分支定界法里新增条件的右端常数
72 |  
73 | res_x   : numpy array, 分支定界法里这一步的松弛解
74 | res_fun : float,      分支定界法里这一步的目标函数值
75 |  
76 | sub_flag: bool, 若节点为*整颗BnBTree*的根节点则为 False,否则 True
 
 Methods defined here:
79 |
__init__(self)
Initialize self.  See help(type(self)) for accurate signature.
80 | 81 |
__str__(self)
Return str(self).
82 | 83 |
84 | Data descriptors defined here:
85 |
__dict__
86 |
dictionary for instance variables (if defined)
87 |
88 |
__weakref__
89 |
list of weak references to the object (if defined)
90 |
91 |

92 | -------------------------------------------------------------------------------- /docs/BranchAndBound.branch_and_bound.html: -------------------------------------------------------------------------------- 1 | 2 | Python: function branch_and_bound 3 | 4 | 5 |

BranchAndBound.branch_and_bound = branch_and_bound(c, A_ub, b_ub, A_eq, b_eq, bounds, bnbTreeNode=None)
branch_and_bound 对整数规划问题使用「分支定界法」进行*递归*求解。
6 |  
7 | 底层对松弛问题求解使用 scipy.optimize.linprog 完成,
8 | 该算法只是在 scipy.optimize.linprog 求解的基础上加以整数约束,
9 | 所以求解问题的模型、参数中的 c, A_ub, b_ub, A_eq, b_eq, bounds
10 | 与 scipy.optimize.linprog 的完全相同。
11 |  
12 | 问题模型:
13 |     Minimize:     c^T * x
14 |  
15 |     Subject to:   A_ub * x <= b_ub
16 |                   A_eq * x == b_eq
17 |                   (x are integers)
18 |  
19 | 你可以提供一个 BnBTreeNode 实例作为根节点来记录求解过程,得到一个求解过程的树形图。
20 | 如果需要这样的求解过程的树形图,你可以这样调用 branch_and_bound:
21 |     c = [-40, -90]
22 |     A_ub = [[9, 7], [7, 20]]
23 |     b_ub = [56, 70]
24 |     bounds = [(0, None), (0, None)]
25 |     tree = BnBTree()
26 |     r = branch_and_bound(c, A_ub, b_ub, None, None, bounds, tree.root)
27 |     print(r)    # 打印求解结果
28 |     print(tree) # 打印求解过程的树形图
29 |  
30 | Parameters
31 | ----------
32 | :param c: 系数矩阵。array_like
33 |     Coefficients of the linear objective function to be minimized.
34 | :param A_ub: 不等式约束条件矩阵,array_like, 若无则需要传入 None
35 |     2-D array which, when matrix-multiplied by ``x``, gives the values of
36 |     the upper-bound inequality constraints at ``x``.
37 | :param b_ub: 不等式约束条件右端常数,array_like, 若无则需要传入 None
38 |     1-D array of values representing the upper-bound of each inequality
39 |     constraint (row) in ``A_ub``.
40 | :param A_eq: 等式约束条件矩阵,array_like, 若无则需要传入 None
41 |     2-D array which, when matrix-multiplied by ``x``, gives the values of
42 |     the equality constraints at ``x``.
43 | :param b_eq: 等式约束条件右端常数,array_like, 若无则需要传入 None
44 |     1-D array of values representing the RHS of each equality constraint
45 |     (row) in ``A_eq``.
46 | :param bounds: 变量取值范围,sequence
47 |     ``(min, max)`` pairs for each element in ``x``, defining
48 |     the bounds on that parameter. Use None for one of ``min`` or
49 |     ``max`` when there is no bound in that direction. By default
50 |     bounds are ``(0, None)`` (non-negative)
51 |     If a sequence containing a single tuple is provided, then ``min`` and
52 |     ``max`` will be applied to all variables in the problem.
53 | :param bnbTreeNode: 该步的 bnbTreeNode
54 |     提供一个 BnBTreeNode 实例作为根节点来记录求解过程,得到一个求解过程的树形图。
55 |  
56 | Returns
57 | -------
58 | :return: {"success": True|False, "x": array([...]), "fun": ...}
59 |             - success: 若求解成功则返回 True,否则 False
60 |             - x: 最优解
61 |             - fun: 最优目标函数值
62 | 63 | -------------------------------------------------------------------------------- /docs/BranchAndBound.html: -------------------------------------------------------------------------------- 1 | 2 | Python: package BranchAndBound 3 | 4 | 5 | 6 | 7 | 8 |
 
9 |  
BranchAndBound
index
IntegerProgExperiment/BranchAndBound/__init__.py
12 |

BranchAndBound 提供求解整数线性规划问题的「分支定界法」
13 |  
14 | Public
15 | ------
16 | func branch_and_bound(c, A_ub, b_ub, A_eq, b_eq, bounds, bnbTreeNode=None):
17 |     对整数规划问题使用「分支定界法」进行*递归*求解。
18 | class BnBTree: 表示分枝定界法求整数规划问题过程的树
19 | class BnBTreeNode: BnBTree 的节点

20 |

21 | 22 | 23 | 25 | 26 | 27 |
 
24 | Package Contents
       
bnb_Tree
28 |
branch_and_bound
29 |
30 | -------------------------------------------------------------------------------- /docs/CuttingPlane.cutting_plane.html: -------------------------------------------------------------------------------- 1 | 2 | Python: function cutting_plane 3 | 4 | 5 |

CuttingPlane.cutting_plane = cutting_plane(c, A, b)
cutting_plane 对整数规划问题使用「割平面法」进行*递归*求解。
6 |  
7 | **对于部分问题,该算法无法给出正确的结果,原因未知。不推荐使用**
8 |  
9 | 底层对松弛问题求解使用 cdfmlr/SimplexLinprog 完成。
10 |  
11 | 问题模型:
12 |     Maximize:     c^T * x
13 |  
14 |     Subject to:   A * x == b
15 |                   (x are integers)
16 |  
17 | Parameters
18 | ----------
19 | :param c: 系数矩阵。array_like
20 |     Coefficients of the linear objective function to be maximized.
21 | :param A: 等式约束条件矩阵,array_like
22 |     2-D array which, when matrix-multiplied by ``x``, gives the values of
23 |     the equality constraints at ``x``.
24 | :param b: 等式约束条件右端常数,array_like
25 |     1-D array of values representing the RHS of each equality constraint
26 |     (row) in ``A``.
27 |  
28 | Returns
29 | -------
30 | :return: {"success": True|False, "x": array([...]), "fun": ...}
31 |             - success: 若求解成功则返回 True,否则 False
32 |             - x: 最优解
33 |             - fun: 最优目标函数值
34 | 35 | -------------------------------------------------------------------------------- /docs/CuttingPlane.html: -------------------------------------------------------------------------------- 1 | 2 | Python: package CuttingPlane 3 | 4 | 5 | 6 | 7 | 8 |
 
9 |  
CuttingPlane
index
IntegerProgExperiment/CuttingPlane/__init__.py
12 |

CuttingPlane 提供求解整数线性规划问题的「割平面法」
13 |  
14 | Public
15 | ------
16 | func cutting_plane(c, A, b): 对整数规划问题使用「分支定界法」进行*递归*求解。

17 |

18 | 19 | 20 | 22 | 23 | 24 |
 
21 | Package Contents
       
cutting_plane
25 |
linprog (package)
26 |
27 | -------------------------------------------------------------------------------- /docs/CuttingPlane.linprog.html: -------------------------------------------------------------------------------- 1 | 2 | Python: package CuttingPlane.linprog 3 | 4 | 5 | 6 | 7 | 8 |
 
9 |  
CuttingPlane.linprog
index
IntegerProgExperiment/CuttingPlane/linprog/__init__.py
12 |

13 |

14 | 15 | 16 | 18 | 19 | 20 |
 
17 | Package Contents
       
problem
21 |
simplex_method
22 |
solve
23 |
solver
24 |
25 | -------------------------------------------------------------------------------- /docs/CuttingPlane.linprog.problem.html: -------------------------------------------------------------------------------- 1 | 2 | Python: module CuttingPlane.linprog.problem 3 | 4 | 5 | 6 | 7 | 8 |
 
9 |  
CuttingPlane.linprog.problem
index
IntegerProgExperiment/CuttingPlane/linprog/problem.py
12 |

13 |

14 | 15 | 16 | 18 | 19 | 20 |
 
17 | Modules
       
numpy
21 |

22 | 23 | 24 | 26 | 27 | 28 |
 
25 | Classes
       
29 |
builtins.object 30 |
31 |
32 |
LpProblem 33 |
34 |
35 |
36 |

37 | 38 | 39 | 41 | 42 | 43 | 51 | 52 |
 
40 | class LpProblem(builtins.object)
   LpProblem(c, a, b)
44 |  
45 | LpProblem 描述一个线性规划问题
46 |  
47 | :attribute c: 目标函数系数,  <math> c = (c_1, c_2, ..., c_n)^T </math>
48 | :attribute a: 系数矩阵,     <math> a = (a_{ij})_{m      imes n} = (p_1, p_2, ..., p_n) </math>
49 | :attribute b: 右端常数,     <math> b = (b_1, b_2, ..., b_m)^T, b > 0 </math>
50 | :attribute base_idx: 基变量的下标集合
 
 Methods defined here:
53 |
__init__(self, c, a, b)
:param c: 目标函数系数,  <math> c = (c_1, c_2, ..., c_n)^T </math>
54 | :param a: 系数矩阵,     <math> a = (a_{ij})_{m      imes n} = (p_1, p_2, ..., p_n) </math>
55 | :param b: 右端常数,     <math> b = (b_1, b_2, ..., b_m)^T, b > 0 </math>
56 | 57 |
solve(self, solver: type, **kwargs)
调用指定算法,求解线性规划问题
58 |  
59 | :param solver: 指定算法(LpSolver实例)
60 | 61 |
62 | Data descriptors defined here:
63 |
__dict__
64 |
dictionary for instance variables (if defined)
65 |
66 |
__weakref__
67 |
list of weak references to the object (if defined)
68 |
69 |

70 | -------------------------------------------------------------------------------- /docs/CuttingPlane.linprog.simplex_method.html: -------------------------------------------------------------------------------- 1 | 2 | Python: module CuttingPlane.linprog.simplex_method 3 | 4 | 5 | 6 | 7 | 8 |
 
9 |  
CuttingPlane.linprog.simplex_method
index
IntegerProgExperiment/CuttingPlane/linprog/simplex_method.py
12 |

13 |

14 | 15 | 16 | 18 | 19 | 20 |
 
17 | Modules
       
copy
21 |
numpy
22 |

23 | 24 | 25 | 27 | 28 | 29 |
 
26 | Classes
       
30 |
CuttingPlane.linprog.solver.LpSolver(builtins.object) 31 |
32 |
33 |
SimplexMethod 34 |
35 |
36 |
37 |

38 | 39 | 40 | 42 | 43 | 44 | 55 | 56 |
 
41 | class SimplexMethod(CuttingPlane.linprog.solver.LpSolver)
   SimplexMethod(problem: CuttingPlane.linprog.problem.LpProblem)
45 |  
46 | 单纯形(表)法
47 |  
48 | 待解决应符合标准型,即:
49 | <math>
50 |     max z = c^T * x
51 |     s.t. a*x = b, x >= 0, b > 0
52 | </math>
53 |  
54 | 单纯形算法参考:https://zh.wikipedia.org/zh-hans/单纯形法
 
 
Method resolution order:
57 |
SimplexMethod
58 |
CuttingPlane.linprog.solver.LpSolver
59 |
builtins.object
60 |
61 |
62 | Methods defined here:
63 |
__init__(self, problem: CuttingPlane.linprog.problem.LpProblem)
:param problem: 待解决的问题(LpProblem实例)
64 | 65 |
big_m(self, **kwargs)
用大M法得到初始基
66 |  
67 | :param kwargs: show_tab=True (default False): 打印运算过程
68 | :return: None
69 | 70 |
find_idt_base(self)
尝试找单位阵作初始基
71 |  
72 | :return: True if success False else
73 | 74 |
solve(self, **kwargs) -> CuttingPlane.linprog.solve.LpSolve
单纯形算法入口
75 |  
76 | :param kwargs:
77 |     base_idx=[...] (default []): 指定初始基,缺省则由算法自行确定
78 |     show_tab=True  (default False): 打印单纯形表
79 |     two_step=True  (default False): 使用两阶段法
80 |     big_m=True     (default True):  使用大 M 法
81 | :return: 问题的解
82 | 83 |
two_step(self, **kwargs)
用两阶段法得到初始基,第一阶段在此计算
84 |  
85 | :param kwargs: show_tab=True (default False): 打印单纯形表
86 | :return: 第一阶段的解
87 | 88 |
89 | Data and other attributes defined here:
90 |
Problem = <class 'CuttingPlane.linprog.simplex_method.SimplexMethod.Problem'>
单纯形(表)法内部的线性规划问题表示
91 | 92 |
93 | Data descriptors inherited from CuttingPlane.linprog.solver.LpSolver:
94 |
__dict__
95 |
dictionary for instance variables (if defined)
96 |
97 |
__weakref__
98 |
list of weak references to the object (if defined)
99 |
100 |

101 | -------------------------------------------------------------------------------- /docs/CuttingPlane.linprog.solve.html: -------------------------------------------------------------------------------- 1 | 2 | Python: module CuttingPlane.linprog.solve 3 | 4 | 5 | 6 | 7 | 8 |
 
9 |  
CuttingPlane.linprog.solve
index
IntegerProgExperiment/CuttingPlane/linprog/solve.py
12 |

13 |

14 | 15 | 16 | 18 | 19 | 20 |
 
17 | Classes
       
21 |
builtins.object 22 |
23 |
24 |
LpSolve 25 |
26 |
27 |
28 |

29 | 30 | 31 | 33 | 34 | 35 | 43 | 44 |
 
32 | class LpSolve(builtins.object)
   LpSolve(success: bool, description: str, solve: list, target: float)
36 |  
37 | LpSolve 描述一个线性规划问题的解
38 |  
39 | :attributes success:     是否得到了最优解
40 | :attributes description: 解的描述
41 | :attributes solve:       最优解
42 | :attributes target:      最优目标函数值
 
 Methods defined here:
45 |
__init__(self, success: bool, description: str, solve: list, target: float)
Initialize self.  See help(type(self)) for accurate signature.
46 | 47 |
__str__(self)
Return str(self).
48 | 49 |
50 | Data descriptors defined here:
51 |
__dict__
52 |
dictionary for instance variables (if defined)
53 |
54 |
__weakref__
55 |
list of weak references to the object (if defined)
56 |
57 |

58 | -------------------------------------------------------------------------------- /docs/CuttingPlane.linprog.solver.html: -------------------------------------------------------------------------------- 1 | 2 | Python: module CuttingPlane.linprog.solver 3 | 4 | 5 | 6 | 7 | 8 |
 
9 |  
CuttingPlane.linprog.solver
index
IntegerProgExperiment/CuttingPlane/linprog/solver.py
12 |

13 |

14 | 15 | 16 | 18 | 19 | 20 |
 
17 | Classes
       
21 |
builtins.object 22 |
23 |
24 |
LpSolver 25 |
26 |
27 |
28 |

29 | 30 | 31 | 33 | 34 | 35 | 40 | 41 |
 
32 | class LpSolver(builtins.object)
   LpSolver(problem)
36 |  
37 | 线性规划问题的解法抽象类
38 |  
39 | 该项目中的所有解法实现都继承于此类。
 
 Methods defined here:
42 |
__init__(self, problem)
:param problem: 待解决的问题(LpProblem实例)
43 | 44 |
solve(self, **kwargs)
求解算法入口,调用此方法开始对该LpSolver实例化时传入的 LpProblem 进行求解
45 |  
46 | :return: 求解结果(LpSolve实例)
47 | 48 |
49 | Data descriptors defined here:
50 |
__dict__
51 |
dictionary for instance variables (if defined)
52 |
53 |
__weakref__
54 |
list of weak references to the object (if defined)
55 |
56 |

57 | -------------------------------------------------------------------------------- /docs/HungarianAssignment.html: -------------------------------------------------------------------------------- 1 | 2 | Python: package HungarianAssignment 3 | 4 | 5 | 6 | 7 | 8 |
 
9 |  
HungarianAssignment
index
IntegerProgExperiment/HungarianAssignment/__init__.py
12 |

HungarianAssignment 提供求解指派问题的「匈牙利算法」
13 |  
14 | Public
15 | ------
16 | func hungarian_assignment(cost_matrix): 对指派问题使用「匈牙利算法」进行求解。

17 |

18 | 19 | 20 | 22 | 23 | 24 |
 
21 | Package Contents
       
hungarian_assignment
25 |
26 | -------------------------------------------------------------------------------- /docs/HungarianAssignment.hungarian_assignment.html: -------------------------------------------------------------------------------- 1 | 2 | Python: function hungarian_assignment 3 | 4 | 5 |

HungarianAssignment.hungarian_assignment = hungarian_assignment(cost_matrix)
hungarian_assignment 指派问题的匈牙利解法
6 |  
7 | :param cost_matrix: 指派问题的系数矩阵
8 | :return: np.where(指派)
9 | 10 | -------------------------------------------------------------------------------- /docs/MonteCarlo.html: -------------------------------------------------------------------------------- 1 | 2 | Python: package MonteCarlo 3 | 4 | 5 | 6 | 7 | 8 |
 
9 |  
MonteCarlo
index
IntegerProgExperiment/MonteCarlo/__init__.py
12 |

MonteCarlo 提供求解整数规划问题的「蒙特卡洛法」
13 |  
14 | Public
15 | ------
16 | func: monte_carlo(x_nums, fun, cons, bounds, random_times=10 ** 5): 对整数规划问题使用「蒙特卡洛法」进行求解。

17 |

18 | 19 | 20 | 22 | 23 | 24 |
 
21 | Package Contents
       
monte_carlo
25 |
26 | -------------------------------------------------------------------------------- /docs/MonteCarlo.monte_carlo.html: -------------------------------------------------------------------------------- 1 | 2 | Python: function monte_carlo 3 | 4 | 5 |

MonteCarlo.monte_carlo = monte_carlo(x_nums, fun, cons, bounds, random_times=100000)
monte_carlo 对整数规划问题使用「蒙特卡洛法」求满意解
6 |  
7 | 对于线性、非线性的整数规划,在一定计算量下可以考虑用 蒙特卡洛法 得到一个满意解。
8 |  
9 | 注意:蒙特卡洛法只能在一定次数的模拟中求一个满意解(通常不是最优的),而且对于每个变量必须给出有明确上下界的取值范围。
10 |  
11 | 问题模型:
12 |     Minimize:   fun(x)
13 |  
14 |     Subject to: cons(x) <= 0
15 |                 (x are integers)
16 |  
17 | Parameters
18 | ----------
19 | :param x_nums: `int`, 未知数向量 x 的元素个数
20 | :param fun:  `(x: list) -> float`, 要最小化的目标函数
21 | :param cons: `(x: list) -> list`, 小于等于 0 的约束条件
22 | :param bounds: `list`, 各个 x 的取值范围
23 | :param random_times: `int`, 随机模拟次数
24 |  
25 | Returns
26 | -------
27 | :return: {"x": array([...]), "fun": ...}
28 |             - x: 最优解
29 |             - fun: 最优目标函数值
30 |  
31 | Examples
32 | --------
33 | 试求得如下整数规划问题的一个满意解:
34 |     Min  x_0 + x_1
35 |     s.t. 2 * x_0 + x_1 <= 6
36 |          4 * x_0 + 5 * x_1 <= 20
37 |          (x_0、x_1 为整数)
38 | 编写目标函数:
39 |     >>> fun = lambda x: x[0] + x[1]
40 | 编写约束条件:
41 |     >>> cons = lambda x: [2 * x[0] + x[1] - 6, 4 * x[0] + 5 * x[1] - 20]
42 | 指定取值范围:
43 |     >>> bounds = [(0, 100), (0, 100)]
44 | 调用蒙特卡洛法求解:
45 |     >>> monte_carlo(2, fun, cons, bounds)
46 |     {'fun': 4, 'x': [1, 3]}
47 | 可以看的 monte_carlo 返回了一个满意解(事实上,这是个最优解,但一般情况下不是)。
48 | 49 | -------------------------------------------------------------------------------- /docs/experiment.html: -------------------------------------------------------------------------------- 1 | 2 | Python: module experiment 3 | 4 | 5 | 6 | 7 | 8 |
 
9 |  
experiment
index
IntegerProgExperiment/experiment.py
12 |

13 |

14 | 15 | 16 | 18 | 19 | 20 |
 
17 | Modules
       
BranchAndBound
21 | CuttingPlane
22 |
HungarianAssignment
23 | MonteCarlo
24 |
numpy
25 |

26 | 27 | 28 | 30 | 31 | 32 |
 
29 | Functions
       
branch_and_bound_test()
33 |
cutting_plane_test()
34 |
hungarian_assignment_test()
35 |
monte_carlo_test()
36 |
37 | -------------------------------------------------------------------------------- /experiment.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import BranchAndBound 4 | import CuttingPlane 5 | import MonteCarlo 6 | import HungarianAssignment 7 | 8 | 9 | def branch_and_bound_test(): 10 | print("\nBranch and Bound Test:\n" + "-" * 22) 11 | c = [3, 4, 1] 12 | A_ub = [[-1, -6, -2], [-2, 0, 0]] 13 | b_ub = [-5, -3] 14 | bounds = [(0, None), (0, None), (0, None)] 15 | tree = BranchAndBound.BnBTree() 16 | r = BranchAndBound.branch_and_bound(c, A_ub, b_ub, None, None, bounds, tree.root) 17 | print(r) 18 | print(tree) 19 | 20 | 21 | def cutting_plane_test(): 22 | print("\nCutting Plane Test:\n" + "-" * 22) 23 | c = [1, 1, 0, 0] 24 | a = [[2, 1, 1, 0], [4, 5, 0, 1]] 25 | b = [6, 20] 26 | r = CuttingPlane.cutting_plane(c, a, b) 27 | print(r) 28 | 29 | 30 | def monte_carlo_test(): 31 | print("\nMonte Carlo Test:\n" + "-" * 22) 32 | 33 | def fun(x): 34 | return x[0] + x[1] 35 | 36 | def cons(x): 37 | return [ 38 | 2 * x[0] + x[1] - 6, 39 | 4 * x[0] + 5 * x[1] - 20, 40 | ] 41 | 42 | bounds = [(0, 100), (0, 100)] 43 | r = MonteCarlo.monte_carlo(2, fun, cons, bounds) 44 | print(r) 45 | 46 | 47 | def hungarian_assignment_test(): 48 | print("\nHungarian Assignment Test:\n" + "-" * 22) 49 | c = [[2, 15, 13, 4], [10, 4, 14, 15], [9, 14, 16, 13], [7, 8, 11, 9]] 50 | r = HungarianAssignment.hungarian_assignment(c) 51 | print(r) 52 | m = np.zeros(np.array(c).shape, dtype=int) 53 | m[r] = 1 54 | print(m) 55 | 56 | 57 | if __name__ == "__main__": 58 | branch_and_bound_test() 59 | cutting_plane_test() 60 | monte_carlo_test() 61 | hungarian_assignment_test() 62 | --------------------------------------------------------------------------------