目次
一般的に、線形計画問題(以下、LPと呼びます)は、制約Cと目的関数Fの2つの部分で構成されます。私たちの目標は、Cの制約の下でFの最小値または最大値を見つけることです。制約と目的関数はどちらも線形形式です。このパートでは、Z3の助けを借りて、いくつかのLP問題を研究します。
たとえば、与えられた制約:計算したい:
Z3のLP問題を解決するために、最適化モジュールを使用します。具体的には、maximize()APIも使用します。モジュールとAPIの詳細については、公式ドキュメントを参照してください。
上記の例では、次のZ3コードを使用できます。
x, y = Ints("x y")
solver = Optimize()
cons = [x < 2, (y - x) < 1]
solver.add(cons)
solver.maximize(x + 2*y)
if solver.check() == sat:
print(solver.model())
Z3の出力は次のとおりです。[y = 1、x = 1]
演習14:linear_programming.pyPythonファイルのコードを読み取ります。例を実行して、コードを理解していることを確認してください。不足しているコードを入力して制約を追加することにより、演習を完了します
def lp_exercise():
opt = Optimize()
# exercise 14: Given the constraints:
# x - y > 2.1
# x + z < 5.5
# y - z < 1.1
#
# try to calculate the maximal value of
# x + y + z
# by using LP in Z3
# Your code here:
# raise Todo("exercise 14: please fill in the missing code.")
# add the constraints is same with LA
x, y , z = Reals("x y z")
cons = [(x - y) > 2.1, (x + z) < 5.5, (y - z) < 1.1]
opt.add(cons)
# use maximize() and minimize() method to add the target function
opt.maximize(x + y + z)
# check the Optimizer as we do with Solver
if opt.check() == sat:
print(opt.model())
if __name__ == '__main__':
lp_examples()
# should output: [z = 9/10, y = 3/2, x = 41/10]
lp_exercise()
出力結果:
バックパック問題
1.0-1ナップサック問題
ナップサック問題は、何百年にもわたる研究の歴史を持つ典型的な最適化問題です。問題は次のとおりです。アイテムのセットが与えられた場合、各アイテムには重みと値があります。合計重量が指定された制限より小さく、合計値ができるだけ大きくなるようにアイテムの数を決定します。ナップサック問題にはいくつかのサブ問題があります:0-1ナップサック問題、完全ナップサック問題、複数ナップサック問題、多次元ナップサック問題など。詳細については、バックパックの質問を参照してください。
各アイテムの数量が1つだけであると仮定して、最初に0-1ナップサック問題について説明しましょう。以下では、次の記号を使用します。
フラグFを使用して各アイテムを表します 。0はアイテムiが選択されていないことを意味し、1はアイテムiが選択されていることを意味します。
総重量がバッグの容量Cを超えないようにするために、次のようになります。ここで、Nはアイテムの総数です。
私たちの最適化の目標は、選択したアイテムの価値を最大化することです。
演習15:knapsack.py Pythonファイルのコードを読み取り、zero_one_knapsack_lp()メソッドコードを完成させ、0-1ILPを使用して0-1バックパック問題を解決します。コードをテストすることを忘れないでください。
#DP算法解决背包问题
def zero_one_knapsack_dp(weights, values, cap):
def knapsack_dp_do(rest_cap, index):
if rest_cap <= 0 or index <= 0:
return 0
if weights[index - 1] > rest_cap:
return knapsack_dp_do(rest_cap, index - 1)
value_1 = knapsack_dp_do(rest_cap, index - 1)
value_2 = knapsack_dp_do(rest_cap - weights[index - 1], index - 1)
if value_1 >= (value_2 + values[index - 1]):
return value_1
return value_2 + values[index-1]
start = time.time()
result = knapsack_dp_do(cap, len(weights))
print(f"zero_one_knapsack_dp solve {len(weights)} items by time {(time.time() - start):.6f}s")
return result
#0-1线性规划解决背包问题
def zero_one_knapsack_lp(weights, values, cap, verbose=False):
# create a new solver, but
solver = Optimize()
# the decision variables
flags = [Int(f"x_{i}") for i in range(len(weights))]
# print(flags)
# flags are 0-1
for flag in flags:
solver.add(Or(flag == 0, flag == 1))
# @exercise 15: solve the 0-1 knapsack problem by using 0-1 ILP
# construct the constraint
# \sum_i weights[i] * flags[i] <= cap
# and the the target
# \sum_i values[i] * flags[i]
# Your code here:
# @begin
# raise Todo("exercise 15: please fill in the missing code.")
wf_exp = []
i = 0
for w in weights:
wf_exp.append(w*flags[i])
i = i + 1
solver.add(sum(wf_exp) <= cap) # 约束条件:\sum_i weights[i] * flags[i] <= cap
j = 0
vf_exp = []
for v in values:
vf_exp.append(v*flags[j])
j = j + 1
solver.maximize(sum(vf_exp))
# @end
start = time.time()
result = solver.check()
print(f"zero_one_knapsack_lp solve {len(weights)} items by time {(time.time() - start):.6f}s")
if result == sat:
model = solver.model()
verbose = True
# print the chosen items
if verbose:
print("\n".join([f"Index: {index}, Weight: {weights[index]}, Value: {values[index]}"
for index, flag in enumerate(flags) if model[flag] == 1]))
return True, sum([values[index] for index, flag in enumerate(flags) if model[flag] == 1])
return False, result
# 测试用例
if __name__ == '__main__':
# a small test case
W = [4, 6, 2, 2, 5, 1]
V = [8, 10, 6, 3, 7, 2]
C = 12
print(zero_one_knapsack_dp(W, V, C))
print(zero_one_knapsack_lp(W, V, C))
# another test case
W = [23, 26, 20, 18, 32, 27, 29, 26, 30, 27]
V = [505, 352, 458, 220, 354, 414, 498, 545, 473, 543]
C = 67
print(zero_one_knapsack_dp(W, V, C))
print(zero_one_knapsack_lp(W, V, C))
出力結果:
2.完全なナップサック問題
ナップサック問題のもう1つのサブ問題は、完全なナップサック問題です。さまざまなアイテムの数に制限がなく、任意のアイテムを選択できることを前提としています。
したがって、変数を宣言し、選択した各アイテムに数量Aを設定する必要がありますF [i]: 0≤i<N、0≤A
したがって、総重量の制約条件は次のとおりです。
ここで、Nはアイテムタイプの総数です。選択したアイテムの最大値は次のとおりです。
演習16:knapsack_py Pythonファイルのコードを読み取り、complete_knapsack_lp()メソッドにコードを入力して、完全なknapsack_lp問題を解決します。コードをテストすることを忘れないでください。
def complete_knapsack_lp(weights, values, cap, verbose=False):
solver = Optimize()
# @exercise 16: solve the complete knapsack problem by using LP
# note that flags[i] will be a integer and flags[i] >= 0
# construct the constraint
# \sum_i weights[i] * flags[i] <= cap
# and the the target
# \sum_i values[i] * flags[i]
# Your code here:
# @begin
# raise Todo("exercise 16: please fill in the missing code.")
flags = [Int(f"x_{i}") for i in range(len(weights))]
for flag in flags:
solver.add(flag>=0)
wf_exp = []
i = 0
for w in weights:
wf_exp.append(w * flags[i])
i = i + 1
solver.add(sum(wf_exp) <= cap) # 约束条件:\sum_i weights[i] * flags[i] <= cap
j = 0
vf_exp = []
for v in values:
vf_exp.append(v * flags[j])
j = j + 1
solver.maximize(sum(vf_exp))
start = time.time()
result = solver.check()
print(f"zero_one_knapsack_lp solve {len(weights)} items by time {(time.time() - start):.6f}s")
if result == sat:
model = solver.model()
verbose = True
# print the chosen items
if verbose:
print("\n".join([f"Index: {index}, Weight: {weights[index]}, Value: {values[index]},Amount:{model[flag].as_long()}"
for index, flag in enumerate(flags) if model[flag].as_long() > 0]))
return True, sum([values[index]*model[flag].as_long() for index, flag in enumerate(flags) if model[flag].as_long() > 0])
return False, result
# @end
テスト:
if __name__ == '__main__':
W = [23, 26, 20, 18, 32, 27, 29, 26, 30, 27]
V = [505, 352, 458, 220, 354, 414, 498, 545, 473, 543]
C = 133
print("Maximal Value: ", complete_knapsack_lp(W, V, C, verbose=True))
出力結果:
主な難しさ:z3整数をPythonの大きな数に変換する関数as_long()を見つける
model [flag]が0より大きいと判断した後、長い間スタックしていて、model [flag]> 0を書き込むとエラーが報告され続けます。model [flag]はz3の整数であることがわかります。pyセマンティクスに変換するときは、次のように記述する必要があります。model[flag] .as_long()> 0
z3.py库中对as_long()函数的解释
def as_long(self):
"""Return a Z3 integer numeral as a Python long (bignum) numeral.
>>> v = IntVal(1)
>>> v + 1
1 + 1
>>> v.as_long() + 1
2
"""
if z3_debug():
_z3_assert(self.is_int(), "Integer value expected")
return int(self.as_string())
ナップサック問題は、動的計画法によっても解決できます。
演習17:knapsack_py Pythonファイルのコードを読み取ります。zero_one_knapsack_dp()メソッドは、動的計画法(DP)に基づく0-1ナップサック問題の解決策を提供しました。get_large_test()メソッドによって生成されたデータを使用して、DPアルゴリズムとLPアルゴリズムの効率を比較してみてください。あなたの観察は何ですか?結果からどのような結論を引き出すことができますか?
# 读取较大数值文件的函数
def get_large_test():
# this test data is fetched from:
# https://people.sc.fsu.edu/~jburkardt/datasets/knapsack_01/knapsack_01.html
# the expect maximum value should be: 13549094
def read_numbers_from_file(file_path):
with Path(file_path).open(mode="r") as fp:
content = fp.readlines()
return [int(x.strip()) for x in content]
file_folder = Path(__file__).parent.resolve()
return (read_numbers_from_file(file_folder / "p08_w.txt"),
read_numbers_from_file(file_folder / "p08_p.txt"))
テストケース:
if __name__ == '__main__':
# a large test case
W, V = get_large_test()
C = 6404180
# @exercise 17: compare the efficiency of the DP and the
# LP algorithm, by running your program on a larger data.
# what's your observation? What conclusion you can draw from these data?
# Your code here:
# @begin
# raise Todo("exercise 17: please fill in the missing code.")
print(zero_one_knapsack_dp(W, V, C))
print(zero_one_knapsack_lp(W, V, C))
# @end
出力結果:
(追記:LPアルゴリズムは、結果がなくなるのを待たずに長時間実行されており、結果をマップします)
結論:動的計画法は、多数のナップサック問題を処理する上で、LPアルゴリズムよりもはるかに効率的です。
#中科大软院-hbj形式化されたコースノート-メッセージを残してプライベートメッセージを交換することを歓迎します
#随手点赞、私はもっと幸せになります~~ ^ _ ^