【形式手法】PartB:線形計画法(線形計画法)

目次

線形計画

バックパック問題

1.0-1ナップサック問題

2.完全なナップサック問題


一般的に、線形計画問題(以下、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形式化されたコースノート-メッセージを残してプライベートメッセージを交換することを歓迎します

#随手点赞、私はもっと幸せになります~~ ^ _ ^

 

おすすめ

転載: blog.csdn.net/weixin_41950078/article/details/111416573