CPLEXで蓄電池をつかったピーク消費電力最小化

電力需要は時間帯によって波があることがよくあります。日中帯は需要が多く、夜間や朝は少ないというようなことです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/e77a1f0c-7a78-e04c-dab0-4ed244c3c691.png)

しかしながら、電力はピークに合わせて契約をするため、ピークが高ければ、割高な契約となります。より割安な契約にするために、蓄電池をつかってピークの前に充電をして置き、ピークを抑えるという方法があります。
では、蓄電池でいつどのくらいの充放電をすることでピークを最小化することができるでしょうか。この最適化問題をCPLEXをつかって解いていきます。

* テスト環境
    * CPLEX Optimization Studio 22.1.0
    * Windows 11 64bit

* サンプルモデルとデータ・ファイル

spsssample/PowerPeakMinimize.mod 

https://github.com/hkwd/spsssample/blob/master/230105%E9%9C%80%E8%A6%81%E4%BA%88%E6%B8%ACCPLEX/PowerPeakMinimize.mod

CPLEX/demand0501.dat 

https://raw.githubusercontent.com/hkwd/spsssample/master/230105%E9%9C%80%E8%A6%81%E4%BA%88%E6%B8%ACCPLEX/demand0501.dat

# 1.問題の記述
問題を以下の４項目の観点でまとめます。

①既知の情報は何か
* １時間毎の電力需要
* 蓄電池の容量：500
* 蓄電池の時間あたりに充放電できる量:100
* 蓄電池充電量初期値:0

②決定係数は何か
* １時間単位の蓄電池の充放電量

③どのような制約があるか
* 電力消費量はマイナスにならない
* 蓄電池の容量を超える電力は充電できない
* 蓄電池に溜まっている電力以上の電力は放電できない
* 蓄電池の単位時間当たりに充放電可能な電力には制限がある

④目的関数は何か
* 電力消費の最小化

# 2.OPLプロジェクトの作成
ILOG CPLEX Optimization Studioを開き、「ファイル」＿「新規」＿「OPLプロジェクト」を選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/6840f755-9178-47f8-ad07-3eebc253e618.png)
「プロジェクト名」と「説明」を入力し、「終了」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/231ca34d-2fa8-c2f6-bdff-573853664aa8.png)

# 3.OPLによるモデルの作成
#1で作った問題の記述をプログラムでモデルとして記述します。
CPLEXではPythonやCなどいくつかのプログラミング言語でモデルを記述可能ですが、ここではOPLという専用言語で記述します。
プロジェクトを右クリックし、「新規」＿「モデル」を選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/198ffb30-5529-f31b-c652-0ea5017a41f8.png)

プロジェクトで作成したフォルダが選ばれていることを確認し、モデルファイル名を書いて「終了」します。拡張子は.modになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/541ac574-c939-81b6-3e53-bd17d5413af3.png)

以下のOPL言語で記述したモデルをファイル内に記述し、保存します。

```c#:PowerPeakMinimize.mod
//①既知の情報の定義///////////////////////////////////////////////////////////////////////////
//１時間毎の電力需要量
tuple TDemands{
  int time;
  float demand;
}

{TDemands}Demands=...;

float BatteryCapacity = 500;    //蓄電池の容量
float ChargeDemandsEfficiency = 100;  //時間あたりに充放電できる量
float BatteryInit = 0;    //蓄電池充電量初期値


//②決定変数と決定式///////////////////////////////////////////////////////////////////////////
//決定変数
dvar float BatteryInput[d in Demands];  //充放電量

//決定式
dexpr float BatteryCharge[d in Demands] = BatteryInit + sum(d2 in Demands:d2.time<=d.time)BatteryInput[d2];  //バッテリー残量
dexpr float Consumption[d in Demands] = d.demand+ BatteryInput[d];      //電力消費量
dexpr float MaxConsumption = max(d in Demands) Consumption[d];  //電力消費の最大値

//③目的関数の定義///////////////////////////////////////////////////////////////////////////
minimize MaxConsumption;//電力消費を最小化する

//④制約条件の定義///////////////////////////////////////////////////////////////////////////
subject to{
  forall(d in Demands){
    Consumption[d] >= 0;    //電力消費量はマイナスにならない
    BatteryCharge[d] <= BatteryCapacity;      //バッテリー容量以上の電力は充電できない
    if(d.time==1){                    //バッテリーからの放電量は充電されている量以下である
      BatteryInput[d] >= -BatteryInit;    
    }else{
      BatteryInput[d] >= -BatteryCharge[item(Demands,d.time-1)];
    }  
    BatteryInput[d] <= ChargeDemandsEfficiency;    //バッテリーへの充電量は時間当たり上限がある
    BatteryInput[d] >= -ChargeDemandsEfficiency;    //バッテリーからの放電量は時間当たり上限がある  
  }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/0e611410-d660-ebda-d067-128de95763ac.png)

# 4.データ・ファイルの作成
モデルPowerPeakMinimize.mod内の「{TDemands}Demands=...;」はデータ・ファイルから読み込むという意味です。
データファイルを作成します。
プロジェクトを右クリックし、「新規」＿「データ」を選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/8f5ebba3-d9b2-8356-96f4-e2f780ca7448.png)

プロジェクトで作成したフォルダが選ばれていることを確認し、データ・ファイル名を書いて「終了」します。拡張子は.datになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/39f21e56-7740-75ff-9a4f-f8ca73683978.png)

以下のデータをファイル内に記述します。
データとして毎時の電力需要が定義されています。
```c#:demand0501.dat
Demands = {
    <1,50.482>,
    <2,35.354>,
    <3,52.048>,
    <4,59.085>,
    <5,73.380>,
    <6,57.665>,
    <7,93.678>,
    <8,110.302>,
    <9,177.242>,
    <10,178.466>,
    <11,165.774>,
    <12,156.503>,
    <13,238.931>,
    <14,259.566>,
    <15,260.461>,
    <16,262.071>,
    <17,247.858>,
    <18,239.442>,
    <19,187.245>,
    <20,95.791>,
    <21,67.990>,
    <22,74.588>,
    <23,70.006>,
    <24,56.901>
};
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/50bb543f-0640-5196-5745-dc1bbc899429.png)

# 5.OPLモデルの解説
#3で作ったOPLモデルを解説します。

## ①既知の情報
まず、①既知の情報を記述しています。

```c#:①既知の情報
//１時間毎の電力需要量
tuple TDemands{
  int time;
  float demand;
}

{TDemands}Demands=...;

float BatteryCapacity = 500;    //蓄電池の容量
float ChargeDemandsEfficiency = 100;  //時間あたりに充放電できる量
float BatteryInit = 0;    //蓄電池充電量初期値
```

電力需要はtimeとdemandからなる配列です。これはモデルの外のデータファイルから与えるようにしています。「{TDemands}Demands=...;」はデータ・ファイルから読み込むという意味です。

#4で作ったデータ・ファイルで毎時の電力需要が定義されています。

データはモデル内に記述することもできるのですが、別ファイルにすることで異なるデータ・ファイルをつかった最適化のシミュレーションを容易にします。


## ②決定係数と決定式
次に、②決定係数とそこから派生した決定式を記述します。

```c#:②決定係数と決定式
//決定変数
dvar float BatteryInput[d in Demands];  //充放電量
```
決定変数をdvarで定義します。
最終的に求めたい決定変数は充放電量であるBatteryInputです。これは時間ごとの需要量の配列分[d in Demands]作成されます。あとで詳しく確認しますが、最適解演算後には以下のようなデータが得られます。
プラスの値は充電、マイナスの値は放電を意味しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/d4438cab-438b-4895-d13a-fd91f75e9628.png)

この決定変数から派生する関数を決定式としてdexprで定義します。このように決定式をつくることで制約や目的関数を読みやすくすることができます。

```c#:②決定係数と決定式
//決定式
dexpr float BatteryCharge[d in Demands] = BatteryInit + sum(d2 in Demands:d2.time<=d.time)BatteryInput[d2];  //バッテリー残量
dexpr float Consumption[d in Demands] = d.demand+ BatteryInput[d];      //電力消費量
dexpr float MaxConsumption = max(d in Demands) Consumption[d];  //電力消費の最大値
```

BatteryChargeはバッテリーの残量です。蓄電池充電量初期値（BatteryInit）にそこまでの充放電量(BatteryInput)の合計値を足したものになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/9c15dc7b-cc6c-f732-80fa-f803a90bf091.png)

Consumptionは電力消費量です。需要量（Demands.demand）と充放電量(BatteryInput)の合計値になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/2d7087f6-d8db-f747-675a-034b90039c23.png)

MaxConsumptionは電力消費量（Consumption）の最大値になります。

## ③目的関数
問題の記述とは順序が前後しますが、次に目的関数を記述します。

```c#:③目的関数の定義
//決定式
minimize MaxConsumption;//電力消費を最小化する
```
電力消費の最大値（MaxConsumption）を最小化することが目的関数になります。

## ④制約条件の定義
最後に制約を、subject toで記述します。
forall(d in Demands)で全ての時間において制約を満たすかをチェックしています。
決定式でConsumptionを作ったことで読みやすくなっています。

```c#:④制約条件の定義
subject to{
  forall(d in Demands){
    Consumption[d] >= 0;    //電力消費量はマイナスにならない
    BatteryCharge[d] <= BatteryCapacity;      //バッテリー容量以上の電力は充電できない
    if(d.time==1){                    //バッテリーからの放電量は充電されている量以下である
      BatteryInput[d] >= -BatteryInit;    
    }else{
      BatteryInput[d] >= -BatteryCharge[item(Demands,d.time-1)];
    }  
    BatteryInput[d] <= ChargeDemandsEfficiency;    //バッテリーへの充電量は時間当たり上限がある
    BatteryInput[d] >= -ChargeDemandsEfficiency;    //バッテリーからの放電量は時間当たり上限がある  
  }
}
```



# 6.最適化実行
モデルとデータ・ファイルができましたが、最適化を実行するためには「実行構成」作る必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/d4a41a53-0b51-5e81-3dea-31619bfe5700.png)

プロジェクトで作成したフォルダが選ばれていることを確認し、実行構成名を書いて「終了」します(バージョン22.1.0ではバグがあるため、ファイル名はAsciiで記述する必要がありました)。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/17e72b3f-5ea6-aed7-ff5b-82e8c63256c4.png)

作成された実行構成にモデルファイルとデータ・ファイルをドラッグアンドドロップします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/e648d115-ce53-bf57-d3f4-7f1ddfd55b95.png)

追加の確認は「OK」で進みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/0edd0193-d582-cfc5-6afb-c2b309b82aca.png)

実行構成の下にモデルファイルとデータ・ファイルが表示されます。
実行構成を右クリックして、「これを実行」で実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/4eeac794-72dd-c94c-23ab-b3da25369559.png)

# 7.実行結果の見方

実行が完了すると「ソリューション」タブと「問題ブラウザー」に結果が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/39a75f0b-e3a5-9de4-521d-8c6c9916a997.png)

どちらも目的関数の電力消費の最大値（MaxConsumption）の値と、その時の決定変数の充放電量（BatteryInput）の配列が表示されています。蓄電池にこの充放電量で動かせば電力量最大で178.25に抑えらえることを意味しています。
「ソリューション」タブのほうがシンプルな表示になります。

「問題ブラウザー」で充放電量（BatteryInput）の配列の「データビューの表示」をクリックするとDemandsの配列の横に充放電量（BatteryInput）が表示されます。
これを見ると13時から19時の電力需要のピークの時間帯に、放電をすることで消費電力のピークを抑えていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/7208d5b0-933c-45d6-5e8f-bce0e1f890e6.png)

また決定式の配列も見ることができます。
バッテリーの残量（BatteryCharge）をみると需要の低い時間帯に充電をしていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/5fa889bb-754c-9774-caf8-6dfe13aae7d0.png)

電力消費量（Consumption）をみると178.253以上に消費を増やさないようにしていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/a0eb25f0-d104-e5a2-4121-9240b2f5bd8d.png)

これをStudioの外でグラフ化すると以下のようになります。
需要の低い時間帯に充電し、需要のピークに蓄電池を使うことで、消費電力のピークを抑えることができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/973a6577-a879-049a-be54-5517cc211fc2.png)