AMPLPYパッケージの使用法

AMPLをPythonから呼び出すためのAMPLPYパッケージの使用法

AMPLPYライブラリ

AMPLをPythonから呼び出して使うためには amplpy パッケージを使う。AMPLはモデルファイル(.mod)とデータファイル(.dat)とコマンドファイル(.run)から構成される。

マジックコマンド writefile

マジックコマンド writefile の後にファイル名を入れてモデルファイルを出力しておく。

var x >= 0; # 製品1の生産量
var y >= 0; # 製品2の生産量
maximize Profit: 40 * x + 30 * y;
subject to Constraint1: x + 2 * y <= 20; # 資源1の制約
subject to Constraint2: 3 * x + y <= 30; # 資源2の制約
Overwriting production.mod

AMPLクラス

AMPLクラスのインスタンスを作り、readメソッドでモデルファイルを読み込む。

モデルインスタンス m に対するメソッドを用いてソルバー指定、最適化や結果を得ることができる。

from amplpy import AMPL, Environment, tools

env = Environment("/Users/mikiokubo/Documents/ampl/")
ampl = AMPL(env)

# Google Colab.の場合
# from amplpy import ampl_notebook
# m = ampl_notebook(
#     modules=["highs","gurobi","cbc","scip","coin","gecode"],  #coin includes ipopt, couenne, bonmin
#     license_uuid=None)

ampl.read('production.mod')
ampl.option["solver"] = "highs"
ampl.solve()
print("Profit=", ampl.get_value("Profit"))
print("x=", ampl.get_value("x"))
print("y=", ampl.get_value("y"))
HiGHS 1.10.0: optimal solution; objective 500
2 simplex iterations
0 barrier iterations
Profit= 500
x= 8
y= 6

モデルとデータの分離

AMPLの1つの特徴としてモデルとデータの分離があげられる。以下は AMPLコマンドで普通に書いた例を示す。

reset;
# モデルファイル (example.mod)
model;
param a;
param b;
param c;
var x >= 0;
var y >= 0;
maximize Profit: a * x + b * y;
subject to Constraint1: x + y <= c;

# データファイル (example.dat)
data;
param a := 3;
param b := 2;
param c := 5;

# AMPLコマンド
#model example.mod;
#data example.dat;
option solver highs;
solve;
display Profit, x, y;
HiGHS 1.10.0: optimal solution; objective 15
0 simplex iterations
0 barrier iterations
Profit = 15
x = 5
y = 0

amplpyを使うと、データをPythonで入力することができる。これによって、Pythonのデータ分析とAMPLの融合が可能になる。

param a;
param b;
param c;
var x >= 0;
var y >= 0;
maximize Profit: a * x + b * y;
subject to Constraint1: x + y <= c;
Overwriting example.mod
m = AMPL(env)
m.read('example.mod')
m.param["a"] = 3
m.param["b"] = 2
m.param["c"] = 5

m.option["solver"] = "highs"
m.solve()
print("Profit=", m.get_value("Profit"))
print("x=", m.get_value("x"))
print("y=", m.get_value("y"))
HiGHS 1.10.0: optimal solution; objective 15
0 simplex iterations
0 barrier iterations
Profit= 15
x= 5
y= 0

多次元ナップサック問題

多次元ナップサック問題を用いて、データをPythonのコマンドで入力する方法を示す。

# 多制約ナップサック問題のAMPLモデル
set ITEMS;      # アイテムの集合
set RESOURCES;  # リソースの集合

param value{i in ITEMS} >= 0;    # 各アイテムの価値
param weight{r in RESOURCES, i in ITEMS} >= 0;    # 各アイテムの各リソース消費量
param capacity{r in RESOURCES} >= 0;    # 各リソースの容量

var select{i in ITEMS} binary;    # アイテム選択変数

maximize TotalValue: sum{i in ITEMS} value[i] * select[i];

subject to ResourceCapacity{r in RESOURCES}:
    sum{i in ITEMS} weight[r,i] * select[i] <= capacity[r];
Overwriting mkp.mod
import random
random.seed(1)
m = AMPL(env)
m.read('mkp.mod')
m.set['ITEMS'] = [1,2,3]
m.set['RESOURCES'] = ["A","B"]
m.param["value"] ={i:random.randint(1,10) for i in [1,2,3]}
m.param["weight"] ={(r,i):random.randint(5,30) for r in ["A","B"] for i in [1,2,3]}
m.param["capacity"] = {r:50 for r in ["A","B"]}

m.option["solver"] = "highs"
m.solve()

print("TotalValue=", m.get_value("TotalValue"))
m.display("select")
m.var["select"].to_dict() #変数を辞書に変換
HiGHS 1.10.0: optimal solution; objective 13
0 simplex iterations
0 branching nodes
TotalValue= 13
select [*] :=
1  1
2  1
3  0
;
{1: 1, 2: 1, 3: 0}

栄養問題

栄養問題を例として、pandasのデータフレームからデータを生成する方法を示す。

# 栄養問題のAMPLモデル
set FOODS;      # 食品の集合
set NUTRIENTS;  # 栄養素の集合

param cost{f in FOODS} >= 0;    # 各食品の単価
param amount{n in NUTRIENTS, f in FOODS} >= 0;    # 各食品に含まれる栄養素の量
param min_req{n in NUTRIENTS} >= 0;    # 各栄養素の最小必要量
param max_req{n in NUTRIENTS} >= 0;    # 各栄養素の最大許容量

var buy{f in FOODS} >= 0;    # 各食品の購入量

minimize TotalCost: sum{f in FOODS} cost[f] * buy[f];

subject to MinNutrient{n in NUTRIENTS}:
    sum{f in FOODS} amount[n,f] * buy[f] >= min_req[n];

subject to MaxNutrient{n in NUTRIENTS}:
    sum{f in FOODS} amount[n,f] * buy[f] <= max_req[n];
Overwriting diet.mod
import pandas as pd
import numpy as np

food_df = pd.DataFrame(
    [
        ("BEEF", 3.59),
        ("CHK", 2.59),
        ("FISH", 2.29),
        ("HAM", 2.89),
        ("MCH", 1.89),
        ("MTL", 1.99),
        ("SPG", 1.99),
        ("TUR", 2.49),
    ],
    columns=["FOODS", "cost"],
).set_index("FOODS")

# Create a pandas.DataFrame with data for n_min, n_max
nutr_df = pd.DataFrame(
    [
        ("A", 700, 20000),
        ("C", 700, 20000),
        ("B1", 700, 20000),
        ("B2", 700, 20000),
        ("NA", 0, 50000),
        ("CAL", 16000, 24000),
    ],
    columns=["NUTRIENTS", "min_req", "max_req"],
).set_index("NUTRIENTS")

amt_df = pd.DataFrame(
    np.matrix(
        [
            [60, 8, 8, 40, 15, 70, 25, 60],
            [20, 0, 10, 40, 35, 30, 50, 20],
            [10, 20, 15, 35, 15, 15, 25, 15],
            [15, 20, 10, 10, 15, 15, 15, 10],
            [928, 2180, 945, 278, 1182, 896, 1329, 1397],
            [295, 770, 440, 430, 315, 400, 379, 450],
        ]
    ),
    columns=food_df.index.tolist(),
    index=nutr_df.index.tolist(),
)

amt_df
BEEF CHK FISH HAM MCH MTL SPG TUR
A 60 8 8 40 15 70 25 60
C 20 0 10 40 35 30 50 20
B1 10 20 15 35 15 15 25 15
B2 15 20 10 10 15 15 15 10
NA 928 2180 945 278 1182 896 1329 1397
CAL 295 770 440 430 315 400 379 450
m = AMPL(env)
m.read('diet.mod')

m.set_data(food_df, "FOODS")
m.set_data(nutr_df, "NUTRIENTS")
m.get_parameter("amount").set_values(amt_df)

m.option["solver"] = "highs"
m.solve()

print("TotalCost=", m.get_value("TotalCost"))
m.display("buy")
HiGHS 1.10.0: optimal solution; objective 90.0041958
2 simplex iterations
0 barrier iterations
TotalCost= 90.00419580419579
buy [*] :=
BEEF   0
 CHK   0
FISH   0
 HAM   0
 MCH  28.6247
 MTL  18.042
 SPG   0
 TUR   0
;
df = m.get_data("buy").to_pandas()
df
buy
BEEF 0.000000
CHK 0.000000
FISH 0.000000
HAM 0.000000
MCH 28.624709
MTL 18.041958
SPG 0.000000
TUR 0.000000