#%%writefile production.mod
>= 0; # 製品1の生産量
var x >= 0; # 製品2の生産量
var y 40 * x + 30 * y;
maximize Profit: + 2 * y <= 20; # 資源1の制約
subject to Constraint1: x 3 * x + y <= 30; # 資源2の制約 subject to Constraint2:
Overwriting production.mod
AMPLをPythonから呼び出して使うためには amplpy(アンプルパイと読む)パッケージを使う。AMPLはモデルファイル(.mod)とデータファイル(.dat)とコマンドファイル(.run)から構成される。
マジックコマンド %%writefile
の後にファイル名を入れてモデルファイルを出力しておく。
AMPLの環境クラス Environmentのインスタンスenv
を作り、それを用いてAMPLのモデルクラスのインスタンスampl
を生成する。
モデルインスタンス ampl
に対しては、以下のメソッドが使える。
read
: モデルファイルを読み込む。read_data
: データファイルを読み込む。eval
: AMPLコマンドを実行する。solve
: ソルバーを用いて求解する。option
: オプションを設定する。get_value
: 値を得る。これらのメソッドを用いてソルバー指定、最適化や結果を得ることができる。
from amplpy import AMPL, Environment
env = Environment("/Users/mikiokubo/Documents/ampl/")
ampl = AMPL(env)
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コマンドで普通に書いた例を示す。
セルの先頭でマジックコマンド%%ampl_eval
と書いた後に、AMPLのコマンドを記述する。
#%%ampl_eval
reset;
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;
data;
param a := 3;
param b := 2;
param c := 5;
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の融合が可能になる。
まず、マジックコマンド %%writefile
で例題をexample.mod
に保存しておく。
#%%writefile example.mod
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
amplをインストールした環境はenv
に保存されているので、amplのモデルインスタンスm
を作り、ファイルexample.mod
から読み込んで モデルインスタンスを生成する。パラメータはparam[パラメータ名]
に代入することによって設定できる。
また、最適化はsolve
メソッドで起動し、最適値やスカラーの変数はget_value
で得ることができる。
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のコマンドで入力する方法を示す。
まず、マジックコマンド %%writefile
でモデルをmkp.mod
に保存しておく。
#%%writefile mkp.mod
# 多制約ナップサック問題の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
amplインスタンスm
を生成し、モデルを読み込み、集合、パラメータオプションを設定する。
集合はset
にリストを代入し、パラメータはparam
に辞書を代入し、 ソルバーオプションoption「"solver"]
にソルバー名を代入することによって設定できる。
最適化した後の値(最適値などのスカラー)はget_value
で、変数の表示はdisplay
で表示できる。
変数インスタンスはvar
で得ることができ、 to_dict
(to_list
, to_pandas
, to_string
) で辞書(リスト、データフレーム、文字列)に 変換できる。
import random
random.seed(1)
m = AMPL(env)
m.read("mkp.mod")
m.set["ITEMS"] = [1,2,3]
m.set["RESOURCES"] = ["A","B"]
value = {i:random.randint(1,10) for i in [1,2,3]}
print("value=",value)
m.param["value"] = value
weight = {(r,i):random.randint(5,30)
for r in ["A","B"] for i in [1,2,3]}
print("weight=",weight)
m.param["weight"] = weight
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() #変数を辞書に変換
value= {1: 3, 2: 10, 3: 2}
weight= {('A', 1): 13, ('A', 2): 8, ('A', 3): 20, ('B', 1): 29, ('B', 2): 19, ('B', 3): 20}
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のデータフレームからデータを生成する方法を示す。
#%%writefile diet.mod
# 栄養問題の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
のset_data
メソッドでデータフレームからデータとインデックス集合を同時に設定できる。 1番目の引数data
がデータフレームであり、2番目の引数set_name
がモデルのインデックス集合の名前である。 たとえば、set_data(data=food_df, set_name= "FOODS")
は、データフレームfood_df
で定義されるパラメータcost
のデータとそのインデックス集合FOODS
を設定している。
パラメータamount
はデータだけを代入すれば良いので、データフレームamt_df
をパラメータインスタンスm.param["amount"]
に 代入するだけで良い。
m = AMPL(env)
m.read("diet.mod")
m.set_data(data=food_df, set_name= "FOODS")
m.set_data(nutr_df, "NUTRIENTS")
m.param["amount"] = 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
;