end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

python-mip による制約充足問題

pulp for python 以外に python-mip の存在を知った為、メモ

参考url

写経結果

#!python3
# -*- coding: utf-8 -*-
# cf https://magazine.techacademy.jp/magazine/45423
# pip install mip

from mip import Model, xsum, minimize, maximize, BINARY
import numpy  as np
import pandas as pd
import sys

def main():
    problem_1()    # 100x + 100y の最大化を求める問題
    problem_2_1()  # ナップザック問題
    problem_2_2()  # ナップザック問題
    problem_3_1()
    problem_3_2()
    
# 100x + 100y の最大化を求める問題
# cf https://qiita.com/SaitoTsutomu/items/c7b43c2e02710749d117
def problem_1():
    print("## start",sys._getframe().f_code.co_name)

    m = Model()  # 数理モデル
    # 変数
    x = m.add_var("x")
    y = m.add_var("y")
    # 目的関数 (objective)
    m.objective = maximize(100 * x + 100 * y)
    # 制約条件 (subject to)
    m += x + 2 * y<=16  # 材料Aの上限
    m += 3 * x + y<=18  # 材料Bの上限

    m.verbose = 0       # ログの非表示化
    m.optimize()        # ソルバーの実行
    print("x=",x.x, "y=",y.x)
    
# ナップザック問題
# cf https://magazine.techacademy.jp/magazine/45423
def problem_2_1():
    print("## start",sys._getframe().f_code.co_name)
    
    w = [2, 1, 3, 2, 1, 4]  # 重さ
    v = [3, 2, 6, 1, 3, 8]  # 価値
    W = 10                  # 重さの限度:10kg

    r = range(len(w))

    m = Model()
    x = [ m.add_var(var_type=BINARY) for i in r ]

    # 目的関数 (objective)
    m.objective = maximize(xsum(v[i] * x[i] for i in r))

    # 制約条件 (subject to)
    m += xsum(w[i] * x[i] for i in r) <= W

    m.verbose = 0       # ログの非表示化
    m.optimize()
    selected = [i for i in r if x[i].x >= 0.99]

    max_value = sum([v[i] for i in selected])
    weights   = [w[i] for i in selected]
    print("max value=",max_value, "wights=",weights )


# ナップザック問題.
# 変数生成にadd_var_tensor()を使う場合
# cf https://kamekokamekame.net/2021/2021-12-24-article.html
def problem_2_2():
    print("## start",sys._getframe().f_code.co_name)
    
    w = [2, 1, 3, 2, 1, 4]  # 重さ
    v = [3, 2, 6, 1, 3, 8]  # 価値
    W = 10                  # 重さの限度:10kg
    r = range(len(w))

    m = Model()
    # 1次元の変数. 内容は有/無の為、type=BINARY
    x = m.add_var_tensor((len(w),),"x", var_type=BINARY)
    # 目的関数 (objective)
    m.objective = maximize(xsum(v[i] * x[i] for i in r))
    # 制約条件 (subject to)
    m += xsum(w[i] * x[i] for i in r) <= W
    
    m.verbose = 0       # ログの非表示化
    m.optimize()

    selected = [i for i in r if x[i].x >= 0.99]

    max_value = sum([v[i] for i in selected])
    weights   = [w[i] for i in selected]
    print("max value=",max_value, "wights=",weights )

# cf https://qiita.com/SaitoTsutomu/items/c7b43c2e02710749d117
# - 倉庫群から工場群への輸送量を決めたい → 変数
# - 輸送コストを最小化したい             → 目的関数
# - 各倉庫からの搬出は、供給可能量以下   → 制約
# - 各工場への搬入は、需要量以上         → 制約
def problem_3_1():
    print("## start",sys._getframe().f_code.co_name)
    
    nw = 3  # 倉庫数
    nf = 4  # 工場数
    rnd = np.random.default_rng(0)      # 乱数generator
    supply = rnd.integers(30,50, nw)    # 供給 乱数
    demand = rnd.integers(20,40, nf)    # 需要  〃
    cost   = rnd.integers(10,20,(nw,nf))# 輸送費〃

    m1 = Model()
    v1 = m1.add_var_tensor((nw, nf), "v1")
    
    expr = xsum(xsum(v) for v in cost * v1)
    m1.objective = minimize(expr)
    for i in range(nw):
        m1 += xsum(v1[i]) <= supply[i]
    for j in range(nf):
        m1 += xsum(v1[:, j]) >= demand[j]
    m1.verbose = 0
    m1.optimize()
    # 変数の値を多次元配列で取得
    print( v1.astype(float) )

def problem_3_2():
    print("## start",sys._getframe().f_code.co_name)

    nw = 3  # 倉庫数
    nf = 4  # 工場数
    rnd = np.random.default_rng(0)              # 乱数generator
    supply   = rnd.integers(30,50, nw)          # 供給 乱数
    demand   = rnd.integers(20,40, nf)          # 需要  〃
    cost = rnd.integers(10,20,(nw,nf))# 輸送費〃

    dfw = pd.DataFrame({"倉庫":["W1","W2","W3"],      "supply": supply})
    dff = pd.DataFrame({"工場":["F1","F2","F3", "F4"],"demand": demand})
    df  = pd.merge(dfw, dff, "cross").assign( cost=cost.flatten() )
    # print( df )

    m2 = Model()
    df["Var"] = m2.add_var_tensor((len(df),), "v2")
    print( df["Var"] )

    
    m2.objective = minimize(xsum(df.cost * df.Var))
    for _, gr in df.groupby('倉庫'):
        m2 += xsum(gr.Var) <= gr.supply.iloc[0]
    for _, gr in df.groupby('工場'):
        m2 += xsum(gr.Var) >= gr.demand.iloc[0]
        
    m2.verbose = 0
    m2.optimize()
    # 変数の多次元配列を取得
    df["Val"] = df.Var.astype(float)
    # 更にその中から >0 のものを選択
    print( df[df.Val > 0] )

if __name__ == '__main__':
    main()