前書き
私が大学の卒業研究にてソルバー(多項式や制約条件を入力して解かせるプログラム)を使用したいと思っていろいろ検索していた中で、なかなか説明しているサイトが出てこなかったり情報が古かったりしたため、今回はIBMのCPLEX Student版をダウンロードし、使用するところまでを簡単に紹介します。
なお、Student版は大学の講義や研究でのみ無償で使えるもので、詳しくはダウンロードの際の利用規約をよくお読みください。
また、2017年11月29日現在時点(CPLEX ver.12.7.1)の方法であり、リンクや利用方法が変更されている可能性がありますのでご注意ください。
ではダウンロード、インストール、ターミナルでの使い方、Eclipseを使用したJavaでの使い方を、自分のわかる範囲で書き残しておきます。
※追記 2017年12月2日
Windowsではターミナル等ではなくCPLEX Optimization Studio というEclipseに近いソフトにてOPL言語を用いた計算ができるようです。
使い方もわかることが少ないですが、書き残しておきます。
※追記 2018年2月19日
卒業研究が終わり、一段落したのでさらに追記します。
卒業研究ではJavaでの開発において、複数の最適解をCPLEXに求めさせたのでその方法を書き残します。
CPLEXのダウンロード,インストール
ダウンロード
上記リンクからCPLEXのStudent版をカートに入れます。
この際、サインインしている必要があります(サインインしていない場合は登録ボタンのあるページに飛ぶため、そこで登録を済ませて上記リンクを踏み直してください)。
無料であることを確認し、購入へ進んで完了させましょう。
この際にたしか利用規約への同意画面があり、IBMが使用許可した大学・学生であることという項目があるため、教授等に確認を取った上で同意をしてください。
購入完了後、ダウンロードページに飛びます。
DOWNLOADボタンが複数あると思いますが、OSに合わせて該当するものをダウンロードしましょう。
- Window →
IBM ILOG CPLEX Optimization Studio V12.7.1 for Windows X86-64 Multiplatform Multilingual (.exe)
- Linux →
IBM ILOG CPLEX Optimization Studio 12.7.1 for Linux X86-64 Multilingual (.bin)
- Mac →
IBM ILOG CPLEX Optimization Studio V12.7.1 for OSX Multilingual (.bin)
(その他は各OS該当するものをダウンロード)
Windowsはexeファイルのためファイルを開いてインストールを進められると思います。
私はMacでインストールを行なったためMacの方法を書きたいと思います(おそらくLinuxも同様の方法で行えます)。
インストール(Mac)
ダウンロードしたbinファイルはそのままではインストールが行えないためターミナル(端末)を起動します。
cd /cplex_studio12.7.1.osx.binのあるディレクトリ
chmod 777 cplex_studio12.7.1.osx.bin
./cplex_studio12.7.1.osx.bin
上記の3行のように、cdでダウンロードしたbinファイルのあるディレクトリに移動し、chmodで実行権限を与え、実行します。
インストールするディレクトリは自分で把握しておきましょう(私は /Users/ユーザー名/Applications/
にインストールしました)。
※CPLEXのインストールに関して書かれた個人ブログにてライセンスファイルのダウンロード等が書かれていましたが、バージョンが新しいものは不要?っぽいです。もし詰まったらライセンスファイルを探すと良いでしょう。(参照先)
インストールしたら、ターミナルから
./インストールしたディレクトリ/IBM/ILOG/CPLEX_Studio1271/cplex/bin/x86-64_osx/cplex
を叩いてCPLEXを起動します。
パスを通すとcplexと打つだけで起動できるようになるのでパスを通すことを推奨します(通し方は検索すると出てくると思います)。
>CPLEX
と表示されれば完了です。
ターミナル等での使用方法(主にMacやLinux)
ターミナルで全て打って進める方法もありますが、面倒なのでファイルを作成してそこに解かせる式を書いてCPLEXに読み込ませる方法を書きます。
サンプルとして以下のsample.modファイルを作成してください。
enter example
maximize x1 + 2 x2 + 3 x3
subject to -x1 + x2 + x3 <= 20
x1 - 3 x2 + x3 <= 30
bounds
0 <= x1 <= 40
0 <= x2
0 <= x3
end
optimize
display solution variables x1-x3
quit
これを作成したら、
cplex -f sample.mod
と、-fオプションでファイル読み込みをすると答えまで出ます。
それでは上記を簡単に説明します。
(なお、CPLEXを起動して、>CPEXとなっている状態で上を手入力しても同様のことができます。)
enter sample
1行目では、enterで入力モードに移り、プログラムの名前を付けます(ここではsampleとしています)。
maximize x1 + 2 x2 + 3 x3
2行目では目的関数を入力しています。
ここでは、
L = x_1 + 2x_2 + 3x_3
Lを最大化すると定義しています。
subject to -x1 + x2 + x3 <= 20
x1 - 3 x2 + x3 <= 30
3,4行目では制約条件を定義しています。
-x_1 + x_2 + x_3 ≦ 20 \\
x_1 - 3x_2 + x_3 ≦ 30
bounds
0 <= x1 <= 40
0 <= x2
0 <= x3
end
5~9行目では各変数の境界を定義しています。
\begin{align}
0 &≦ x_1 ≦ 40 \\
0 &≦ x_2 \\
0 &≦ x_3
\end{align}
optimize
display solution variables x1-x3
quit
optimize
で計算を行い、display
で目的関数の最大値であるsolution
と各変数$x_1$~$x_3$を表示させ、quit
でCPLEXを閉じます。
こんな感じに計算させることができます。
maximizeのところは目的関数の最小化をしたいならminimizeに置き換えれば良いはずです。
上のサンプル以外の式の書き方は現状不明なのであとは調べてください。
CPLEX Optimization Studioの使い方(Windows用)
※2017年12月2日追記分
Windowsではインストール後にCPLEX Optimization Studioのソフト起動できるはずです。
そのソフトでは、OPL言語と呼ばれるソルバー等で使える言語を用いた書き方をして計算することが可能なようです。
ソフト起動後に、新規OPLプロジェクト作成をし、プロジェクト名を入力する画面にて、「デフォルトの実行構成の追加」・「モデルの作成」・「設定の作成」・「データの作成」にチェックを入れて終了ボタンを押してプロジェクトを作成します。
作成された実行構成下に「構成1」というファイルがデフォルトで作成されていますが、「構成1」のまま実行するとエラーが起こるため、このファイル名を半角英数字の名前に変更します(ファイル右クリックから名前の変更)。
modファイルには変数や制約条件などの式を書き、datファイルには入力に使いたいデータを書くようです。
一応サンプルコードを書いておきます。
dvar int+ x;
dvar int+ y;
maximize 2*x+3*y;
subject to{
4*x+3*y<=240;
x+2*y<=150;
};
これを書いて、「実行構成」を右クリックして実行を行うと、画面下の「解」というビューにて実行結果が見られると思います。
OPL言語の書き方の場合には配列やfor文が使えるので、複雑な問題を解くのには適していそうです。
OPL言語は調べると書き方等が出てきますので、そちらを参考にしてください。
EclipseでCPLEXを使う
ターミナルのみでは、何かを計算させて結果を知るという簡単な流れのみ可能ですが、結果を使ってさらに何かをすることには向いていません。
そのためJavaプログラムにCPLEXを組み込むことで、ある入力をCPLEXに計算させて、結果を元にさらに何かをするアプリケーションを作ることが出来ます。
Eclipseの使い方はそこまで詳しくないので他のサイトを参照してください。
CPLEXをEclipseで使えるようにする方法と、簡単にですがサンプルコードに関して説明します。
(Macのディレクトリ構成ですので、LinuxやWindowsでは一部構成が異なる可能性があります。適宜置き換えてください)。
下記が手順です。
(1) まずEclipseを起動し、新規Javaプロジェクトを作成します(ここではsampleという名前にします)。
(2)ApplicationというClassを作成します。
(3)プロジェクトを右クリックし、ビルド・パス → 外部アーカイブ
の追加を選択します。
(4)/インストールしたディレクトリ/IBM/ILOG/CPLEX_Studio1271/cplex/lib/cplex.jar
を選択します。
(5)Application.javaに下記のように記述します。
package sample;
import ilog.concert.IloException;
import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumVar;
import ilog.cplex.IloCplex;
public class Application {
static public void main(String[] args) {
try {
IloCplex cplex = new IloCplex();
// create model and solve it
IloNumVar x = cplex.numVar(0, Double.MAX_VALUE, "x");
IloNumVar y = cplex.numVar(0, Double.MAX_VALUE, "y");
IloLinearNumExpr objective = cplex.linearNumExpr();
objective.addTerm(0.12, x);
objective.addTerm(0.15, y);
cplex.addMinimize(objective);
cplex.addGe(cplex.sum(cplex.prod(60, x), cplex.prod(60, y)), 300);
cplex.addGe(cplex.sum(cplex.prod(12, x), cplex.prod(6, y)), 36);
cplex.addGe(cplex.sum(cplex.prod(10, x), cplex.prod(30, y)), 90);
if (cplex.solve()) {
System.out.println("obj = " + cplex.getObjValue());
System.out.println("x = " + cplex.getValue(x));
System.out.println("y = " + cplex.getValue(y));
} else {
System.out.println("Can not solve");
}
} catch (IloException e) {
System.err.println("Concert exception caught: " + e);
}
}
}
(6)実行→実行構成
を開き、
Javaアプリケーション→引数→VM引数
に-Djava.library.path=/cplexファイルのある場所(例:Users/ユーザー名/Applications/IBM/ILOG/CPLEX_Studio1271/cplex/bin/x86-64_osx)
を加えて実行すると計算結果がコンソールに出力されます。
それではApplication.javaを簡単に説明します。
クラスに関しては公式のリファレンスを参照してください。
なお、このサンプルコードは海外の方がYouTubeにアップしていた動画を元にしており、クラスや関数の理解は完全にできていないため推測による説明となります。
誤った情報があればコメント等にてご指摘ください。
IloCplex cplex = new IloCplex();
まずはIloCplexクラスのインスタンスを生成します。
IloNumVar x = cplex.numVar(0, Double.MAX_VALUE, "x");
IloNumVar y = cplex.numVar(0, Double.MAX_VALUE, "y");
そしてIloNumVarクラスの変数を宣言しておきます。
0は最小値、Double.MAX_VALUEは最大値、"x"はただのラベルだと思われます。
つまりはここで境界を同時に定義します。
IloLinearNumExpr objective = cplex.linearNumExpr();
objective.addTerm(0.12, x);
objective.addTerm(0.15, y);
cplex.addMinimize(objective);
ここで目的関数の定義を行います。
addTermにて0.12xと0.15yを追加しています。
addMinimizeで目的関数の最小化を行うことがなんとなくわかりますね。
今回は、
L = 0.12x + 0.15y
Lの最小化ということです。
cplex.addGe(cplex.sum(cplex.prod(60, x), cplex.prod(60, y)), 300);
cplex.addGe(cplex.sum(cplex.prod(12, x), cplex.prod(6, y)), 36);
cplex.addGe(cplex.sum(cplex.prod(10, x), cplex.prod(30, y)), 90);
ここでは3つの制約式を追加しています。
prodが乗算、sumが和算で、addGeはGeが「greater or equal」の略であることから「以上」だと推測できます。
つまり上コードは、
60x + 60y ≧ 300
12x + 6y ≧ 36
10x + 30y ≧ 90
ということがわかります。
これらの制約式を追加したら、
cplex.solve()
solveとして計算します。
あとは読んでなんとなくわかるかと思います。
サンプル問題の説明は以上となります。
出てきていないものに関しては公式のリファレンスを参考にしてください。
※2018年2月19日追記分
上記のsolveメソッドでは最適解を1つ生成できた時点で計算が終わります。
しかし、最適解が複数ある場合に複数個求めたい、もしくは別の最適解がほしいということがあると思います。
その際にはいくつか設定をしてsolveメソッドではなく、populateメソッドを使うことで求めることができます。
この方法はソリューションプールという解を保持する機能を利用するものです。
ソリューションプールに関係する設定のうちの5つを説明します。
① IloCplex.DoubleParam.SolnPoolAGap
ソリューションプール内の解における、目的関数値の絶対許容値となる設定値で小数点型の値を与えます。デフォルトは1.0e+75です。1未満の値を与えることで、生成する解の目的関数値が大きいものはプールに入らないようにできます。今回は0.5を与えれば良いです。
② IloCplex.IntParam.SolnPoolIntensity
生成された解の個数、計算時間、メモリ使用量、これら3つにおけるトレードオフの関係を制御する設定値で、次のいずれかの整数型の値を与えます。0はデフォルトでCPLEXに全て任せます。その他は1~4で、数字が大きい設定であるほど計算時間やメモリ使用量を犠牲にして多くの解を生成しにいきます。私は研究では4を設定しました。
③ IloCplex.IntParam.SolnPoolCapacity
ソリューションプールが保持可能な解の最大個数値で整数型の値を与えます。生成された解の個数がこの値を超えた際には、SolnPoolReplaceの設定による入れ替えを行います。
④ IloCplex.IntParam.PopulateLim
生成する最適解の最大個数値で整数型の値を与えます。
⑤ IloCplex.IntParam.SolnPoolReplace
ソリューションプール内に保持している解の個数がSolnPoolCapacityで設定した値を超えようとするときに、プール内の解とどのように入れ替えを行うかの設定で、次のいずれかの整数型の値を与えます。0はデフォルトで、プールに保持する値のうち、最初に追加した値と入れ替えます(first in, first out)。1は最も良くない目的関数値の解と入れ替えます。2は多様性のある解を求めるための入れ替えをします。おそらく、似た解同士がプールに溜まらないような入れ替えを行うのでしょう。私は研究では2を設定しました。
これらを下記のようにIloCplexクラスのインスタンスメソッドであるsetParam(パラメータ, 値)で設定してあげます。
cplex.setParam(IloCplex.DoubleParam.SolnPoolAGap, 0.5);
cplex.setParam(IloCplex.IntParam.SolnPoolIntensity, 4);
cplex.setParam(IloCplex.IntParam.SolnPoolCapacity, 100);
cplex.setParam(IloCplex.IntParam.PopulateLim, 100);
cplex.setParam(IloCplex.IntParam.SolnPoolReplace, 2);
設定したら、上で書いたサンプルのように制約や目的関数をセットして、solveのときと同様にpupulateにて計算させます。
boolean isPopulatable = cplex.populate();
このようにsolveと同じく、解が求まったかどうかがbooleanで返されます。
また、下のように求まった解が何個かも取得する関数が用意されています。
// ソリューションプール内の解の個数
int nsol = cplex.getSolnPoolNsolns();
上のサンプルでcplex.getObjValue()
として解の目的関数値を取得していましたが、cplex.getObjValue(n)
のように複数解の場合は引数に整数を与えることでn番目の解のものが取得できます。
同様にして、cplex.getValue(x, n)
のように複数解の決定変数の値を取得する場合には第二引数に解の添字を与えればn番目の解の値が取得できます。
あとはこれらのリファレンスドキュメントを読めばいろいろ書いてあります。
英語のリファレンスドキュメントとか読むのは面倒ではありますが、頑張ってソルバーと仲良くしましょう!