CPLEXサンプル(warehouse)をWatson StudioのDecision Optimization上で動かす

# はじめに
IBMのSAAS版クラウドサービスである、「IBM Decision Optimization on Cloud」は近々利用できなくなります。
現在このサービスを利用している場合、移行先はWatson Studio / Watson Machine Learningになります。
わかりやすい移行ガイドが、あまりないため、参考情報としてこの記事を記載します。

# 前提

## IBM クラウドへのユーザー登録・Watson Studioの準備
これからの操作をするためには、IBM Cloudにアカウント登録を行い、Watson Studioのインスタンスを作成してWatson Studioのプロジェクトを作る必要があります。また、Watson Machine Learningとの関連づけも必要です。
一連の手順は、別記事に記載してありますので、こちらを参照して下さい。

[無料でなんでも試せる! Watson Studioセットアップガイド](https://qiita.com/makaishi2/items/b666f847a98c2e42dfbb)

# 対象アプリケーション
下記リンク先に記載されている「Warehouse location」を元アプリケーションとします。

https://www.ibm.com/support/knowledgecenter/SSSA5P_12.10.0/ilog.odms.ide.help/OPL_Studio/opllanguser/topics/opl_languser_app_areas_IP_warehse.html

同じコードは、下記にもアップしておきました。
https://github.com/makaishi2/cplex-samples/tree/master/warehouse-org

そんなに分量もないので、コード自体もアップしておきます。

warehouse_cloud.mod

```CPLEX
// --------------------------------------------------------------------------
// Licensed Materials - Property of IBM
//
// 5725-A06 5725-A29 5724-Y48 5724-Y49 5724-Y54 5724-Y55
// Copyright IBM Corporation 1998, 2013. All Rights Reserved.
//
// Note to U.S. Government Users Restricted Rights:
// Use, duplication or disclosure restricted by GSA ADP Schedule
// Contract with IBM Corp.
// --------------------------------------------------------------------------

include "warehouse_data.mod";


range stores = 1..plan.nbStores;

dvar boolean Open[ warehouses ];
dvar boolean Supply[ stores ][ warehouses ];

// expression
dexpr float totalOpeningCost = sum( w in warehouses ) w.fixedCost * Open[w];
dexpr float totalSupplyCost  = sum( w in warehouses , s in stores, k in supplyCosts : k.storeId == s && k.warehouseName == w.name ) 
    Supply[s][w] * k.cost;

minimize
  totalOpeningCost + totalSupplyCost;

subject to {
  forall( s in stores )
    ctEachStoreHasOneWarehouse:
      sum( w in  warehouses ) Supply[s][w] == 1;
      
  forall( w in warehouses, s in stores )
    ctUseOpenWarehouses:
      Supply[s][w] <= Open[w];
      
  forall( w in warehouses )
    ctMaxUseOfWarehouse:         
      sum( s in stores) Supply[s][w] <= w.capacity;
}


{int} StoresSupplied[w in warehouses] = { s | s in stores : Supply[s][w] == 1 };
{string} OpenWarehouses = { w.name | w in warehouses : Open[w] == 1 };
tuple TSuppliedStore {
  string warehouseName;
  int storeId;
}
{TSuppliedStore} network;

execute DISPLAY_RESULTS{
  network.clear();
  writeln("* Open Warehouses=", OpenWarehouses);
  for ( var w in warehouses) {
     if ( Open[w] ==1)   {
        writeln("* stores supplied by ", w.name, ": ", StoresSupplied[w]);
	for (var s in stores) {
	  if (Supply[s][w] == 1) {
	    network.addOnly(w.name, s);
	  }
	}
     }
  }
}
```

warehouse_data.mod

```cplex

tuple TWarehouse {
  key string name;
  int capacity;
  float fixedCost;
}

tuple TSupplyCost {
   key string warehouseName;
   key int storeId;
   float cost;
}

tuple TPlan {
   int nbStores;
}

TPlan plan = ...;
{TWarehouse} warehouses = ...;
{TSupplyCost} supplyCosts = ...;
```

warehouse_cloud.dat

```cplex
// --------------------------------------------------------------------------
// Licensed Materials - Property of IBM
//
// 5725-A06 5725-A29 5724-Y48 5724-Y49 5724-Y54 5724-Y55
// Copyright IBM Corporation 1998, 2013. All Rights Reserved.
//
// Note to U.S. Government Users Restricted Rights:
// Use, duplication or disclosure restricted by GSA ADP Schedule
// Contract with IBM Corp.
// --------------------------------------------------------------------------

// 10 stores to be opened.
plan = <10>;

warehouses =
{
    <Bonn    , 1, 30>
  , <Bordeaux, 4, 30>
  , <London  , 2, 30>
  , <Paris   , 1, 30>
  , <Rome    , 3, 30>
};

supplyCosts =
{
    <Bonn,1,20>
  , <Bonn,2,28>
  , <Bonn,3,74>
  , <Bonn, 4,2>
  , <Bonn,5,46>
  , <Bonn,6,42>
  , <Bonn,7,1>
  , <Bonn,8,10>
  , <Bonn,9,93>
  , <Bonn,10,47>
  //
  , <Bordeaux,1,24>
  , <Bordeaux,2,27>
  , <Bordeaux,3,97>
  , <Bordeaux,4,55>
  , <Bordeaux,5,96>
  , <Bordeaux,6,22>
  , <Bordeaux,7,5>
  , <Bordeaux,8,73>
  , <Bordeaux,9,35>
  , <Bordeaux,10,65>
  //
   , <London,1,11>
  , <London,2,82>
  , <London,3,71>
  , <London,4,73>
  , <London,5,59>
  , <London,6,29>
  , <London,7,73>
  , <London,8,13>
  , <London,9,63>
  , <London,10,55>
  //
    , <Paris,1,25>
  , <Paris,2,83>
  , <Paris,3,96>
  , <Paris,4,69>
  , <Paris,5,83>
  , <Paris,6,67>
  , <Paris,7,59>
  , <Paris,8,43>
  , <Paris,9,85>
  , <Paris,10,71>
  //
    , <Rome,1,30>
  , <Rome,2,74>
  , <Rome,3,70>
  , <Rome,4,61>
  , <Rome,5,4>
  , <Rome,6,59>
  , <Rome,7,56>
  , <Rome,8,96>
  , <Rome,9,46>
  , <Rome,10,95>
  
};
```

実行結果例は、以下のとおりです。

![スクリーンショット 2020-07-20 15.20.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/3355a100-f722-0f74-da53-693c43ad035a.png)


# 対象アプリケーションのDropSolve化
Saas版のDropSolveの便利な点は、UI機能を持っていて、CPLEXコードとデータをDrag and Dropすると、そのまま解が得られた点です。
更に、この時、入力データはExcelで作成可能でした。

下の画面にこの時のExcelを示します。
``warehouses``と``supplyCosts``という2枚のシートがあることがわかります。

![スクリーンショット 2020-07-20 15.27.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/1b4447b8-9663-5d7e-e13b-387f8d5e9487.png)

![スクリーンショット 2020-07-20 15.28.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/06802e50-4b28-b787-3426-d662899778e6.png)

このシートは、下記のOPLコードでいうと、
``{TWarehouse} warehouses = ...;``と
``{TSupplyCost} supplyCosts = ...;``に対応している形になります。

```opl
// --------------------------------------------------------------------------
// Licensed Materials - Property of IBM
//
// 5725-A06 5725-A29 5724-Y48 5724-Y49 5724-Y54 5724-Y55
// Copyright IBM Corporation 1998, 2013. All Rights Reserved.
//
// Note to U.S. Government Users Restricted Rights:
// Use, duplication or disclosure restricted by GSA ADP Schedule
// Contract with IBM Corp.
// --------------------------------------------------------------------------

tuple TWarehouse {
  key string name;
  int capacity;
  float fixedCost;
}
{TWarehouse} warehouses = ...;

tuple TSupplyCost {
   key string warehouseName;
   key int storeId;
   float cost;
}
{TSupplyCost} supplyCosts = ...;

tuple TPlan {
   int nbStores;
}

// Excel化できないための一時対応
TPlan plan = <10>;

range stores = 1..plan.nbStores;

dvar boolean Open[ warehouses ];
dvar boolean Supply[ stores ][ warehouses ];

// expression
dexpr float totalOpeningCost = sum( w in warehouses ) w.fixedCost * Open[w];
dexpr float totalSupplyCost  = sum( w in warehouses , s in stores, k in supplyCosts : k.storeId == s && k.warehouseName == w.name ) 
    Supply[s][w] * k.cost;

minimize
  totalOpeningCost + totalSupplyCost;

subject to {
  forall( s in stores )
    ctEachStoreHasOneWarehouse:
      sum( w in  warehouses ) Supply[s][w] == 1;
      
  forall( w in warehouses, s in stores )
    ctUseOpenWarehouses:
      Supply[s][w] <= Open[w];
      
  forall( w in warehouses )
    ctMaxUseOfWarehouse:         
      sum( s in stores) Supply[s][w] <= w.capacity;
}


{int} StoresSupplied[w in warehouses] = { s | s in stores : Supply[s][w] == 1 };
{string} OpenWarehouses = { w.name | w in warehouses : Open[w] == 1 };
tuple TSuppliedStore {
  string warehouseName;
  int storeId;
}
{TSuppliedStore} network;

execute DISPLAY_RESULTS{
  network.clear();
  writeln("* Open Warehouses=", OpenWarehouses);
  for ( var w in warehouses) {
     if ( Open[w] ==1)   {
        writeln("* stores supplied by ", w.name, ": ", StoresSupplied[w]);
	for (var s in stores) {
	  if (Supply[s][w] == 1) {
	    network.addOnly(w.name, s);
	  }
	}
     }
  }
}
 ```

コードとExcelは、下記のgithubにアップしておきました。

https://github.com/makaishi2/cplex-samples/tree/master/warehouse-excel

# DropSolveアプリケーションのWatson Studio / Watson Machine Learningへの移行
ここまでが、問題設定としての話の前置きで、ここからがこの記事の本題です。
これから、上記のDrop SolveサンプルアプリをWatson Stuido / Watson Machine Learningで動かすための手順について説明します。

## データ準備
最初のステップとしてデータ準備を行う必要があります。
具体的には、入力Excelの各シートから、``<シート名>.csv``という名前のCSVファイルをExportします。
すべてのシートについて同じ操作をして下さい。
上記のサンプルの場合、``warehouses.csv``と``spplyCosts.csv``の2つのファイルを準備することになります。

## モデルビルダーの起動
次の手順でモデルビルダーを起動します。
資産管理の画面から、画面右上の「プロジェクトに追加」をクリック

![スクリーンショット 2020-07-20 15.41.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/b611f665-b0a5-66df-b8bd-9bebee90140f.png)

メニューから「意思決定最適化の実験」を選択

![スクリーンショット 2020-07-20 15.42.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/a090a90f-f637-6489-3578-b255c3a932e8.png)

①下の画面で「warehouse-do」などと、名称を入力
②画面右下の「Create」ボタンをクリック

![スクリーンショット 2020-07-20 15.46.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/aa9efd41-ee7e-2fec-7da9-188a024f66ba.png)

これで、下のようなモデルビルダーの初期画面が表示されます。

![スクリーンショット 2020-07-20 15.49.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/8460adc7-9652-68da-4763-68755970cbe4.png)

## CSVデータのロード

モデル構築の最初のステップは事前準備したCSVデータのロードです。上の画面からdrag and dropで2つのCSVデータをアップロードして下さい。

アップロードが終わると、下の画面のように2つのCSVファイルが選択された状態になるはずです。この状態で、黄色の枠で示した、``Import``のリンクをクリックして下さい。

![スクリーンショット 2020-07-20 15.51.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/58f09494-b4d9-0c90-409f-ca91aaefdc86.png)

すると、下の画面のように2つのCSVデータがモデルビルダーに取り込まれた形になるはずです。これで、データの準備は完了となります。

![スクリーンショット 2020-07-20 15.54.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/f4cd1601-4b5a-1bb9-4e74-1b1101bc686a.png)

## Modelの定義

データの準備が完了したら、次はモデルの定義です。上の画面で赤枠で囲んだ、「**Run model**」をクリックして下さい。すると、下の画面になります。
今回は、実装済みのOPLコード(DropSolveで動いていたもの)を取り込めばいいので、一番右の「**Import**」を選びます。

![スクリーンショット 2020-07-20 15.57.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/de4b613d-6a3a-3d00-6b9c-9eb2e64ca4ab.png)

下のような画面になります。今回の実装はOPLなので、「**OPL**」タブを選択します。

![スクリーンショット 2020-07-20 16.00.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/31c7eca5-3fcf-a53e-c949-446521e88042.png)

「**Browse**」ボタンをクリックして事前準備済みのOPLコード``warehouse_ml.mod``を指定します。
その後で、画面右下の「**Import**」ボタンをクリックして下さい。

![スクリーンショット 2020-07-20 16.01.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/f6c88603-6b0e-fa29-8056-f9a94cf762db.png)

下のような画面に遷移したら、コードの準備も完了です。

![スクリーンショット 2020-07-20 16.01.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/e41322e1-2f5d-ea22-f02c-3eb9a84ddf21.png)

## モデルの実行
これで、Watson StudioでDOモデルを実行するためのすべての準備は整いました。あとは画面右上の「**Run model**」ボタンをクリックすると、モデルが実行されます。
しばらく待って、下のような画面になれば、モデルの実行に成功しています。

![スクリーンショット 2020-07-20 16.08.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/12494a8e-fb9b-6706-c110-d860c825640d.png)

「**Solution Tables**」のタブをクリックすると、下のような最適解が表示されます。

![スクリーンショット 2020-07-20 16.08.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/780ffb4e-19a3-3629-0237-2f001d08b11c.png)

こちらもデータ一式は、下記githubにアップしておきました。

https://github.com/makaishi2/cplex-samples/tree/master/warehouse-do

## 別のデータで最適化する場合
いったんModel Builderで登録したデータを削除します。それから、新しいCSVデータを同じファイル名でクラウド上にアップロードしなおして下さい。
あとは、先ほどと同じ手順で、Model Builder上にデータ登録し、モデルを実行すれば結果が得られます。

# おまけ
以上で説明したように、Watson StudioのModel Builderで動くようになった最適化モデルは、Watson Machine Learingにモデルを登録することで、Webサービスとして呼び出すことも可能です。
その手順について別記事 [CPLEXサンプル(warehouse)をWatson MLのWebサービス化する](https://qiita.com/makaishi2/items/51942a303034ba664f7c) で解説しましたので、あわせて参照してください。