├── .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: 目标函数系数,
10 | :attribute a: 系数矩阵,
11 | :attribute b: 右端常数,
12 | :attribute base_idx: 基变量的下标集合
13 | """
14 |
15 | def __init__(self, c, a, b):
16 | """
17 | :param c: 目标函数系数,
18 | :param a: 系数矩阵,
19 | :param b: 右端常数,
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 |
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 |
12 |
13 |
14 |
15 |
16 |
17 | Classes |
18 |
19 | | | |
20 |
21 | - builtins.object
22 |
-
23 |
24 | - BnBTree
25 |
- BnBTreeNode
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | class BnBTree(builtins.object) |
34 |
35 | | |
36 | BnBTree 是表示分枝定界法求整数规划问题过程的树
37 |
38 | Fields
39 | ------
40 | root: 树根节点 |
41 | | |
42 | 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 |
59 | class BnBTreeNode(builtins.object) |
60 |
61 | | |
62 | 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 |
77 | | |
78 | 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 |
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 |
24 | Package Contents |
25 |
26 | | | |
27 | |
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 |
12 | CuttingPlane 提供求解整数线性规划问题的「割平面法」
13 |
14 | Public
15 | ------
16 | func cutting_plane(c, A, b): 对整数规划问题使用「分支定界法」进行*递归*求解。
17 |
18 |
19 |
20 |
21 | Package Contents |
22 |
23 | | | |
24 | |
27 |
--------------------------------------------------------------------------------
/docs/CuttingPlane.linprog.html:
--------------------------------------------------------------------------------
1 |
2 | Python: package CuttingPlane.linprog
3 |
4 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 | Package Contents |
18 |
19 | | | |
20 | |
25 |
--------------------------------------------------------------------------------
/docs/CuttingPlane.linprog.problem.html:
--------------------------------------------------------------------------------
1 |
2 | Python: module CuttingPlane.linprog.problem
3 |
4 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 | Modules |
18 |
19 | | | |
20 | |
22 |
23 |
24 |
25 | Classes |
26 |
27 | | | |
28 |
29 | - builtins.object
30 |
-
31 |
32 | - LpProblem
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | class LpProblem(builtins.object) |
41 |
42 | | |
43 | 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: 基变量的下标集合 |
51 | | |
52 | 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 |
12 |
13 |
14 |
15 |
16 |
17 | Modules |
18 |
19 | | | |
20 | |
23 |
24 |
25 |
26 | Classes |
27 |
28 | | | |
29 |
30 | - CuttingPlane.linprog.solver.LpSolver(builtins.object)
31 |
-
32 |
33 | - SimplexMethod
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | class SimplexMethod(CuttingPlane.linprog.solver.LpSolver) |
42 |
43 | | |
44 | 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/单纯形法 |
55 | | |
56 | - 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 |
12 |
13 |
14 |
15 |
16 |
17 | Classes |
18 |
19 | | | |
20 |
21 | - builtins.object
22 |
-
23 |
24 | - LpSolve
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | class LpSolve(builtins.object) |
33 |
34 | | |
35 | 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: 最优目标函数值 |
43 | | |
44 | 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 |
12 |
13 |
14 |
15 |
16 |
17 | Classes |
18 |
19 | | | |
20 |
21 | - builtins.object
22 |
-
23 |
24 | - LpSolver
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | class LpSolver(builtins.object) |
33 |
34 | | |
35 | LpSolver(problem)
36 |
37 | 线性规划问题的解法抽象类
38 |
39 | 该项目中的所有解法实现都继承于此类。 |
40 | | |
41 | 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 |
12 | HungarianAssignment 提供求解指派问题的「匈牙利算法」
13 |
14 | Public
15 | ------
16 | func hungarian_assignment(cost_matrix): 对指派问题使用「匈牙利算法」进行求解。
17 |
18 |
19 |
20 |
21 | Package Contents |
22 |
23 | | | |
24 | |
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 |
12 | MonteCarlo 提供求解整数规划问题的「蒙特卡洛法」
13 |
14 | Public
15 | ------
16 | func: monte_carlo(x_nums, fun, cons, bounds, random_times=10 ** 5): 对整数规划问题使用「蒙特卡洛法」进行求解。
17 |
18 |
19 |
20 |
21 | Package Contents |
22 |
23 | | | |
24 | |
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 |
12 |
13 |
14 |
15 |
16 |
17 | Modules |
18 |
19 | | | |
20 | |
26 |
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 |
--------------------------------------------------------------------------------