# ロットサイズ最適化システム OptLot


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## 簡易システムの紹介ビデオ

ロットサイズ最適化システム OptLot

![](11lotsize_files/figure-commonmark/cell-2-output-1.jpeg)

## はじめに

ここで論じるのは，需要量が期によって変動するときの
各期の生産量（もしくは発注量）ならびに在庫量を決定するためのモデル（動的ロットサイズ決定モデル）である．
発注量を決める古典モデルである経済発注量モデルは，サプライ・チェイン基本分析システム
SCBASの，安全在庫、ロットサイズ、目標在庫（基在庫レベル）の設定関数
inventory_analysisで計算できる．
ここで考えるモデルは，経済発注量モデルにおける需要が一定という仮定を拡張し，期によって変動することを許したものである．

計画期間は有限であるとし，発注を行う際の段取り費用（もしくは生産費用）と
在庫費用のトレードオフをとることがモデルの主目的になる．

ロットサイズ決定は，タクティカルレベルの意思決定モデルであり，
与えられた資源（機械や人）の下で，活動をどの程度まとめて行うかを決定する．

一般に，生産や輸送は規模の経済性をもつ．
これをモデル化する際には，生産や輸送のための諸活動を行うためには「段取り」とよばれる準備活動が必要になると考える．
ロットサイズ決定とは，段取り活動を行う期を決定し，
生産・輸送を表す諸活動をまとめて行うときの「量」を決定するモデルである．

ロットサイズ決定問題は，古くから多くの研究が行われている問題であるが，
国内での（特に実務家の間での）認知度は今ひとつのようである．
適用可能な実務は，ERP（Enterprise Resource Planning）やAPS（Advanced
Planning and Scheduling）などの処理的情報技術を導入しており，かつ
段取りの意思決定が比較的重要な分野である． そのような分野においては，
ERPやAPSで単純なルールで自動化されていた部分に最適化を持ち込むことによって，
より現実的かつ効率的な解を得ることができる．
特に，装置産業においては，ロットサイズ決定モデルは，生産計画の中核を担う．

現在の我が国で販売されている生産スケジューラは，すべてルールに基づいたヒューリスティクスであり，いわゆる処理的情報技術に過ぎない．
ロットまとめの意思決定はタクティカルレベルであり，オペレーショナルレベルのスケジューリング最適化と同時に行うべきではない．
個々の意思決定レベルの最適化に基づき，情報のやりとりによって全体最適化を目指す必要があり，そのためには，広範囲の問題が解けるロットサイズ決定モデルが必須である．

## データ

### 読み込みと生成

``` python
prod_df = pd.read_csv(folder + "lotprod.csv",index_col=0)
prod_df.set_index("name", inplace=True)
production_df = pd.read_csv(folder + "production.csv",index_col=0)
bom_df = pd.read_csv(folder + "bomodel.csv", index_col =0)
resource_df = pd.read_csv(folder + "resource.csv", index_col=0)
plnt_demand_df = pd.read_csv(folder+"plnt-demand.csv")
demand = pd.pivot_table(plnt_demand_df, index= "prod", columns ="period",  values="demand", aggfunc=sum)
```

### 品目データ

製品や半製品や部品や原材料をあわせて品目とよぶ．
データ項目は以下の通り．

- name: 名称
- inv_cost: （工場における）在庫費用
- safety_inventory: 安全在庫量（在庫量の下限値）
- initial_inventory: 初期在庫量
- target_inventory: 目標在庫量（在庫量の上限値）

``` python
prod_df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">inv_cost</th>
<th data-quarto-table-cell-role="th">safety_inventory</th>
<th data-quarto-table-cell-role="th">initial_inventory</th>
<th data-quarto-table-cell-role="th">target_inventory</th>
</tr>
<tr>
<th data-quarto-table-cell-role="th">name</th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">A</td>
<td>0.005769</td>
<td>38.636429</td>
<td>401.0</td>
<td>763.398459</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">B</td>
<td>0.005769</td>
<td>37.812572</td>
<td>395.0</td>
<td>753.503381</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">C</td>
<td>0.005769</td>
<td>22.971393</td>
<td>373.0</td>
<td>723.295128</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">D</td>
<td>0.005769</td>
<td>17.463825</td>
<td>323.0</td>
<td>630.222614</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">E</td>
<td>0.005769</td>
<td>200.161342</td>
<td>934.0</td>
<td>1668.384758</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">F</td>
<td>0.005769</td>
<td>33.532552</td>
<td>385.0</td>
<td>737.729494</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">G</td>
<td>0.005769</td>
<td>41.214374</td>
<td>450.0</td>
<td>859.171178</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">H</td>
<td>0.005769</td>
<td>96.322978</td>
<td>498.0</td>
<td>901.348857</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">I</td>
<td>0.005769</td>
<td>20.535360</td>
<td>333.0</td>
<td>645.835288</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">J</td>
<td>0.005769</td>
<td>133.584150</td>
<td>654.0</td>
<td>1175.499053</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">ABCDE</td>
<td>0.002000</td>
<td>317.045562</td>
<td>2426.0</td>
<td>4538.804340</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">FGHIJ</td>
<td>0.002000</td>
<td>325.189413</td>
<td>2320.0</td>
<td>4319.583871</td>
</tr>
</tbody>
</table>

</div>

### 生産データ

工場における各品目の生産データを保管する． データ項目は以下の通り．

- name: 品目名
- ProdTime: 品目1単位の生産時間
- SetupTime: 品目の段取り時間
- ProdCost: 品目1単位の生産費用
- SetupCost: 品目の段取り費用

``` python
production_df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">name</th>
<th data-quarto-table-cell-role="th">ProdTime</th>
<th data-quarto-table-cell-role="th">SetupTime</th>
<th data-quarto-table-cell-role="th">ProdCost</th>
<th data-quarto-table-cell-role="th">SetupCost</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>A</td>
<td>1</td>
<td>3752</td>
<td>218</td>
<td>18873</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>B</td>
<td>1</td>
<td>6193</td>
<td>153</td>
<td>19454</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>C</td>
<td>1</td>
<td>5206</td>
<td>203</td>
<td>18008</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>D</td>
<td>1</td>
<td>6628</td>
<td>144</td>
<td>10289</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>E</td>
<td>1</td>
<td>5539</td>
<td>213</td>
<td>10470</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">5</td>
<td>F</td>
<td>1</td>
<td>4028</td>
<td>170</td>
<td>12393</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">6</td>
<td>G</td>
<td>1</td>
<td>4395</td>
<td>208</td>
<td>10508</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">7</td>
<td>H</td>
<td>1</td>
<td>5289</td>
<td>171</td>
<td>11959</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">8</td>
<td>I</td>
<td>1</td>
<td>6635</td>
<td>284</td>
<td>11938</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">9</td>
<td>J</td>
<td>1</td>
<td>4275</td>
<td>282</td>
<td>12590</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">10</td>
<td>ABCDE</td>
<td>1</td>
<td>4016</td>
<td>221</td>
<td>18107</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">11</td>
<td>FGHIJ</td>
<td>1</td>
<td>6045</td>
<td>134</td>
<td>14345</td>
</tr>
</tbody>
</table>

</div>

### 部品展開表（枝）データ

どの品目（親品目）がどの品目（子品目）から製造されるかを表すデータ．データ項目は，以下の通り．

- child: 子品目（枝の出発点）
- parent: 親品目（枝の到着点）
- units: 親品目1単位を製造するために必要な子品目の数

``` python
bom_df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">child</th>
<th data-quarto-table-cell-role="th">parent</th>
<th data-quarto-table-cell-role="th">units</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>ABCDE</td>
<td>A</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>ABCDE</td>
<td>B</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>ABCDE</td>
<td>C</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>ABCDE</td>
<td>D</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>ABCDE</td>
<td>E</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">5</td>
<td>FGHIJ</td>
<td>F</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">6</td>
<td>FGHIJ</td>
<td>G</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">7</td>
<td>FGHIJ</td>
<td>H</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">8</td>
<td>FGHIJ</td>
<td>I</td>
<td>1</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">9</td>
<td>FGHIJ</td>
<td>J</td>
<td>1</td>
</tr>
</tbody>
</table>

</div>

### 需要データ

各（最終）品目の期ごとの需要量を保管する．

``` python
demand
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th">period</th>
<th data-quarto-table-cell-role="th">0</th>
<th data-quarto-table-cell-role="th">1</th>
<th data-quarto-table-cell-role="th">2</th>
<th data-quarto-table-cell-role="th">3</th>
<th data-quarto-table-cell-role="th">4</th>
<th data-quarto-table-cell-role="th">5</th>
<th data-quarto-table-cell-role="th">6</th>
<th data-quarto-table-cell-role="th">7</th>
<th data-quarto-table-cell-role="th">8</th>
<th data-quarto-table-cell-role="th">9</th>
<th data-quarto-table-cell-role="th">...</th>
<th data-quarto-table-cell-role="th">14</th>
<th data-quarto-table-cell-role="th">15</th>
<th data-quarto-table-cell-role="th">16</th>
<th data-quarto-table-cell-role="th">17</th>
<th data-quarto-table-cell-role="th">18</th>
<th data-quarto-table-cell-role="th">19</th>
<th data-quarto-table-cell-role="th">20</th>
<th data-quarto-table-cell-role="th">21</th>
<th data-quarto-table-cell-role="th">22</th>
<th data-quarto-table-cell-role="th">23</th>
</tr>
<tr>
<th data-quarto-table-cell-role="th">prod</th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th"></th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">A</td>
<td>12607</td>
<td>9700</td>
<td>10743</td>
<td>12361</td>
<td>10971</td>
<td>11835</td>
<td>11457</td>
<td>12331</td>
<td>10895</td>
<td>11739</td>
<td>...</td>
<td>16728</td>
<td>16814</td>
<td>16545</td>
<td>17139</td>
<td>16092</td>
<td>15550</td>
<td>16085</td>
<td>17367</td>
<td>16447</td>
<td>15288</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">B</td>
<td>11615</td>
<td>11448</td>
<td>10511</td>
<td>10698</td>
<td>11532</td>
<td>11694</td>
<td>10784</td>
<td>12906</td>
<td>11695</td>
<td>11695</td>
<td>...</td>
<td>13776</td>
<td>15588</td>
<td>16452</td>
<td>16163</td>
<td>14370</td>
<td>17795</td>
<td>14495</td>
<td>14654</td>
<td>13622</td>
<td>16490</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">C</td>
<td>4673</td>
<td>4545</td>
<td>3978</td>
<td>4551</td>
<td>3376</td>
<td>4471</td>
<td>4128</td>
<td>4299</td>
<td>4731</td>
<td>4072</td>
<td>...</td>
<td>6362</td>
<td>5667</td>
<td>6067</td>
<td>5630</td>
<td>5553</td>
<td>6329</td>
<td>6384</td>
<td>6816</td>
<td>6113</td>
<td>6151</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">D</td>
<td>523</td>
<td>490</td>
<td>491</td>
<td>480</td>
<td>538</td>
<td>489</td>
<td>529</td>
<td>526</td>
<td>534</td>
<td>568</td>
<td>...</td>
<td>756</td>
<td>716</td>
<td>696</td>
<td>761</td>
<td>760</td>
<td>682</td>
<td>771</td>
<td>733</td>
<td>694</td>
<td>750</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">E</td>
<td>47705</td>
<td>49913</td>
<td>50755</td>
<td>50712</td>
<td>47705</td>
<td>43462</td>
<td>49057</td>
<td>46509</td>
<td>55423</td>
<td>52030</td>
<td>...</td>
<td>67193</td>
<td>60003</td>
<td>65447</td>
<td>73008</td>
<td>74761</td>
<td>69651</td>
<td>75255</td>
<td>70322</td>
<td>70567</td>
<td>71030</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">F</td>
<td>8026</td>
<td>7661</td>
<td>8048</td>
<td>7967</td>
<td>7177</td>
<td>7946</td>
<td>8141</td>
<td>7524</td>
<td>7801</td>
<td>8261</td>
<td>...</td>
<td>9664</td>
<td>10244</td>
<td>10250</td>
<td>10294</td>
<td>9653</td>
<td>10567</td>
<td>11465</td>
<td>11614</td>
<td>10023</td>
<td>11288</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">G</td>
<td>10293</td>
<td>9586</td>
<td>10591</td>
<td>8613</td>
<td>9825</td>
<td>10961</td>
<td>10291</td>
<td>10687</td>
<td>10027</td>
<td>9467</td>
<td>...</td>
<td>15540</td>
<td>13173</td>
<td>14380</td>
<td>14110</td>
<td>15172</td>
<td>16715</td>
<td>15323</td>
<td>14382</td>
<td>16271</td>
<td>15819</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">H</td>
<td>25189</td>
<td>24967</td>
<td>24478</td>
<td>23431</td>
<td>26105</td>
<td>23759</td>
<td>24443</td>
<td>22857</td>
<td>24239</td>
<td>23478</td>
<td>...</td>
<td>36093</td>
<td>35980</td>
<td>36019</td>
<td>37020</td>
<td>34913</td>
<td>34591</td>
<td>37014</td>
<td>31564</td>
<td>35839</td>
<td>37686</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">I</td>
<td>1755</td>
<td>1664</td>
<td>1579</td>
<td>1582</td>
<td>1618</td>
<td>1591</td>
<td>1729</td>
<td>1678</td>
<td>1582</td>
<td>1585</td>
<td>...</td>
<td>2097</td>
<td>2179</td>
<td>1984</td>
<td>2268</td>
<td>2382</td>
<td>2331</td>
<td>2296</td>
<td>1808</td>
<td>2036</td>
<td>2171</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">J</td>
<td>36139</td>
<td>37966</td>
<td>36192</td>
<td>31019</td>
<td>29571</td>
<td>30102</td>
<td>33486</td>
<td>31354</td>
<td>30218</td>
<td>32938</td>
<td>...</td>
<td>50071</td>
<td>49455</td>
<td>47130</td>
<td>46465</td>
<td>46361</td>
<td>41844</td>
<td>46982</td>
<td>54395</td>
<td>47368</td>
<td>44730</td>
</tr>
</tbody>
</table>

<p>10 rows × 24 columns</p>
</div>

### 資源データ

資源の使用可能量上限（容量；使用可能時間）を規定する．データ項目は，以下の通り．

- name: 資源名
- period: 期
- capacity: 使用可能量上限

``` python
resource_df.head()
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">name</th>
<th data-quarto-table-cell-role="th">period</th>
<th data-quarto-table-cell-role="th">capacity</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>Res0</td>
<td>0</td>
<td>102239.854167</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>Res0</td>
<td>1</td>
<td>102239.854167</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>Res0</td>
<td>2</td>
<td>102239.854167</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>Res0</td>
<td>3</td>
<td>102239.854167</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>Res0</td>
<td>4</td>
<td>102239.854167</td>
</tr>
</tbody>
</table>

</div>

## 標準モデルの定式化

\#hide \## 多段階動的ロットサイズ決定モデル Pochet–Wolsey 1991
Management Science Vol. 37 53–67 参照

ここでは，多段階にわたって製造を行うときのロットサイズ決定問題を考える．

### 標準定式化

集合

- {1..*T*}: 期間の集合
- *P* :
  品目の集合（完成品と部品や原材料を合わせたものを「品目」と定義する。）
- *K* :
  生産を行うのに必要な資源（機械，生産ライン，工程などを表す）の集合
- *P*<sub>*k*</sub> : 資源 *k* で生産される品目の集合
- *P**a**r**e**n**t*<sub>*p*</sub> :
  部品展開表における品目（部品または材料）*p*
  の親品目の集合．言い換えれば，品目 *p* から製造される品目の集合

パラメータ

- *T*: 計画期間数；期を表す添え字を 1, 2, ⋯, *t*, ⋯, *T* と記す．
- *f*<sub>*t*</sub><sup>*p*</sup> : 期 *t* に品目 *p*
  に対する段取り替え（生産準備）を行うときの費用（段取り費用）
- *g*<sub>*t*</sub><sup>*p*</sup> : 期 *t* に品目 *p*
  に対する段取り替え（生産準備）を行うときの時間（段取り時間）
- *c*<sub>*t*</sub><sup>*p*</sup> : 期 *t* における品目 *p*
  の生産変動費用
- *h*<sub>*t*</sub><sup>*p*</sup> : 期 *t* から期 *t* + 1 に品目 *p*
  を持ち越すときの単位あたりの在庫費用
- *d*<sub>*t*</sub><sup>*p*</sup> : 期 *t* における品目 *p* の需要量
- *ϕ*<sub>*p**q*</sub> : *q* ∈ *P**a**r**e**n**t*<sub>*p*</sub> のとき，
  品目 *q* を 1 単位製造するのに必要な品目 *p* の数 （*p*-units）；
  ここで， *p*-unitsとは，品目 *q* の
  1単位と混同しないために導入された単位であり， 品目 *p* の
  1単位を表す．*ϕ*<sub>*p**q*</sub>
  は，部品展開表を有向グラフ表現したときには，枝の重みを表す．
- *M*<sub>*t*</sub><sup>*k*</sup> : 期 *t* における資源 *k*
  の使用可能な生産時間の上限． 定式化では，品目 1単位の生産時間を
  1単位時間になるようにスケーリングしてあるものと仮定しているが，
  プログラム内では単位生産量あたりの生産時間を定義している．
- *U**B*<sub>*t*</sub><sup>*p*</sup> : 期 *t* における品目 *p*
  の生産時間の上限． 品目 *p* を生産する資源が *k*
  のとき，資源の使用可能時間の上限 *M*<sub>*t*</sub><sup>*k*</sup>
  から段取り替え時間 *g*<sub>*t*</sub><sup>*p*</sup>
  を減じたものと定義される．

変数

- *x*<sub>*t*</sub><sup>*p*</sup>（x）: 期 *t* における品目 *p* の生産量
- *I*<sub>*t*</sub><sup>*p*</sup>（inv） : 期 *t* における品目 *p*
  の在庫量
- *y*<sub>*t*</sub><sup>*p*</sup>（y）: 期 *t* に品目 *p*
  に対する段取りを行うとき 1， それ以外のとき 0 を表す 0-1 変数

上の記号を用いると、多段階ロットサイズ決定モデルは，以下のように定式化できる．

$$
\begin{array}{ l l l }
minimize & \sum\_{t=1}^T \sum\_{p \in P} \left( f_t^p y_t^p + c_t^p x_t^p + h_t^p I_t^p \right) &           \\
s.t. &  \\\\I\_{t-1}^p +x_t^p  = d_t^p+ \sum\_{q \in Parent_p} \phi\_{pq} x_t^q  +I_t^p & \forall p \in P, t=1,\cdots,T  \\
            &  \sum\_{p \in P_k} x_t^p  +\sum\_{p \in P_k} g_t^p y_t^p \leq M_t^k    & \forall k \in K, t=1,\cdots,T \\
            &  x_t^p  \leq UB_t^p y_t^p     & \forall p \in P, t=1,\cdots,T  \\
            &  I_0^p =0               & \forall p \in P                          \\
            &  x_t^p,I_t^p \geq 0         & \forall  p \in P, t=1,\cdots,T  \\
            &  y_t^p \in \\0,1\\   & \forall p \in P, t=1,\cdots,T 
\end{array}
$$

上の定式化で，最初の制約式は，各期および各品目に対する在庫の保存式を表す．
より具体的には，品目 *p* の期 *t* − 1 からの在庫量
*I*<sub>*t* − 1</sub><sup>*p*</sup> と生産量
*x*<sub>*t*</sub><sup>*p*</sup> を加えたものが， 期 *t* における需要量
*d*<sub>*t*</sub><sup>*p*</sup>，次期への在庫量
*I*<sub>*t*</sub><sup>*p*</sup>， および他の品目を生産するときに必要な量
∑<sub>*q* ∈ *P**a**r**e**n**t*<sub>*p*</sub></sub>*ϕ*<sub>*p**q*</sub>*x*<sub>*t*</sub><sup>*q*</sup>
の和に等しいことを表す．

2番目の制約は， 各期の生産時間の上限制約を表す．
定式化ではすべての品目の生産時間は， 1 単位あたり1
時間になるようにスケーリングしてあると仮定していたが，実際問題のモデル化の際には，
品目 *p* を 1 単位生産されるときに，資源 *k* を使用する時間
*a*<sub>*k*</sub><sup>*p*</sup> を用いた方が汎用性がある．

3番目の式は，段取り替えをしない期は生産できないことを表す．

また，以下の強化式を追加すると，高速化する可能性がある．
*x*<sub>*t*</sub><sup>*p*</sup> ≤ *d*<sub>*t*</sub><sup>*p*</sup>*y*<sub>*t*</sub><sup>*p*</sup> + *I*<sub>*y*</sub><sup>*p*</sup>   ∀*p* ∈ *P*, *t* = 1, ⋯, *T*∀*p* ∈ *P*, *t* = 1, ⋯, *T*

### エシェロン在庫を用いた定式化

品目間の親子関係だけでなく，先祖（部品展開表を表す有向グラフを辿って到達可能な品目の集合）を導入しておく．

集合

- *A**n**c**e**s**t**o**r*<sub>*p*</sub>: 品目 *p* の先祖の集合．
  親子関係を表す有向グラフを辿って到達可能な点に対応する品目から構成される集合．
  品目 *p* 自身は含まないものとする．

パラメータ

- *ρ*<sub>*p**q*</sub>: *q* ∈ *A**n**c**e**s**t**o**r*<sub>*p*</sub>
  のとき，品目 *q* を 1 単位生産するのに必要な品目 *p* の量
- *H*<sub>*t*</sub><sup>*p*</sup>: 期 *t* における品目 *p*
  のエシェロン在庫費用； 品目 *p*
  を生産することによって得られた付加価値に対する在庫費用を表す。品目 *p*
  を製造するのに必要な品目の集合を *C**h**i**l**d*<sub>*p*</sub>
  としたとき，以下のように定義される．
  *H*<sub>*t*</sub><sup>*p*</sup> = *h*<sub>*t*</sub><sup>*p*</sup> − ∑<sub>*q* ∈ *C**h**i**l**d*<sub>*p*</sub></sub>*h*<sub>*t*</sub><sup>*q*</sup>*ϕ*<sub>*q**p*</sub>

変数 - *E*<sub>*t*</sub><sup>*p*</sup>: 期 *t* における品目 *p*
のエシェロン在庫量；自分と自分の先祖の品目の在庫量を合わせたものであり，
以下のように定義される．
*E*<sub>*t*</sub><sup>*p*</sup> = *I*<sub>*t*</sub><sup>*p*</sup> + ∑<sub>*q* ∈ *A**n**c**e**s**t**o**r*<sub>*p*</sub></sub>*ρ*<sub>*p**q*</sub>*I*<sub>*t*</sub><sup>*q*</sup>

上の記号を用いると，エシェロン在庫を用いた多段階ロットサイズ決定モデルの定式化は，以下のようになる．
$$
\begin{array}{ l l l }
minimize & \sum\_{t=1}^T \sum\_{p \in P} \left( f_t^p y_t^p + c_t^p x_t^p + H_t^p E_t^p \right) &           \\
s.t. & \\\\E\_{t-1}^p +x_t^p - E_t^p = d_t^p + \sum\_{q \in Ansestor_p} \rho\_{pq} d_t^q  & \forall p \in P, t=1,\cdots,T  \\
            &  \sum\_{p \in P_k} x_t^p  +\sum\_{p \in P_k} g_t^p y_t^p \leq M_t^k    & \forall  k \in K, t=1,\cdots,T  \\
            & E_t^p \geq   \sum\_{q \in Parent_p} \phi\_{pq} E_t^q    & \forall p \in P, t=1,\cdots,T  \\
            &  x_t^p  \leq UB_t^p  y_t^p     & \forall p \in P, t=1,\cdots,T  \\
            &  E_0^p =0               & \forall p \in P                          \\
            &  x_t^p,E_t^p \geq 0         & \forall  p \in P, t=1,\cdots,T  \\
            &  y_t^p \in \\0,1\\   & \forall p \in P, t=1,\cdots,T 
\end{array}
$$

上の定式化で，最初の制約は，各期および各品目に対するエシェロン在庫の保存式を表す．
より具体的には，品目 *p* の期 *t* − 1 からのエシェロン在庫量
*E*<sub>*t* − 1</sub><sup>*p*</sup> と生産量
*x*<sub>*t*</sub><sup>*p*</sup> を加えたものが， 期 *t* における品目 *p*
の先祖 *q* の需要量を品目 *p* の必要量に換算したものの合計
∑<sub>*q* ∈ *A**n**s**e**s**t**o**r*<sub>*p*</sub></sub>*ρ*<sub>*p**q*</sub>*d*<sub>*t*</sub><sup>*q*</sup>
と、 自身の需要量 *d*<sub>*t*</sub><sup>*p*</sup>
と、次期へのエシェロン在庫量 *E*<sub>*t*</sub><sup>*p*</sup>
の和に等しいことを表す． 2番目の制約は，各期の生産時間の上限制約を表す．
3番目の制約は，各品目のエシェロン在庫量が，その親集合の品目のエシェロン在庫量の合計以上であること（言い換えれば実需要量が負にならないこと）を規定する．
4番目の制約は，段取り替えをしない期は生産できないことを表す．

この定式化は1段階モデルと同じ構造をもつので、強化式を加えたり、施設配置定式化のような強い定式化に変形することが可能である。

以下のプログラムでは、実在庫量の上下限制約を付加している。エシェロン在庫モデルにおける3番目の制約のスラック変数が、実在庫量になっているので、
制約を以下のように書き換えた後で、実在庫量に対する上下限制約を付加すれば良い。

*E*<sub>*t*</sub><sup>*p*</sup> + *I*<sub>*t*</sub><sup>*p*</sup> = ∑<sub>*q* ∈ *P**a**r**e**n**t*<sub>*p*</sub></sub>*ϕ*<sub>*p**q*</sub>*E*<sub>*t*</sub><sup>*q*</sup>   ∀*p* ∈ *P*, *t* = 1, ⋯, *T*

### 施設配置定式化

ここでは，オリジナルと定式化と同じ *O*(*T*) 個の 0-1
変数を用いて，より強い定式化を導く．
簡単のため1段階モデルを考える。品目を表す添え字 *p*
を付加し、在庫をエシェロン在庫に置き換えれば、多段階にも使うことができる。

我々の想定しているロットサイズ決定問題では品切れは許さないので， 期 *t*
の需要は，期 *t*
もしくはそれ以前の期で生産された量でまかなわれなければならない．
そこで，期 *t* の需要のうち，期 *s*(*s* ≤ *t*)
で生産した量によってまかなわれた比率を表す変数 *X*<sub>*s**t*</sub>
を導入する．

*X*<sub>*s**t*</sub> が 1 のとき，期　*t*の需要量 *d*<sub>*t*</sub>
が，期 *s* で生産され，期 *t* まで在庫される． 需要
1　単位あたりの変動費用は，生産変動費用 *c*<sub>*s*</sub> と在庫費用
$\sum\_{\ell=s}^{t-1} h\_{\ell}$ の和になるので， *X*<sub>*s**t*</sub>
の係数 *C**H*<sub>*s**t*</sub> は，以下のように定義される．
$$
 CH\_{st}= \left( c_s +\sum\_{\ell=s}^{t-1} h\_{\ell} \right) d_t 
$$

すべての需要は満たされなければならず， また *X*<sub>*s**t*</sub> \> 0
のときには期 *s* で生産しなければならないので， *y*<sub>*s*</sub> = 1
となる． ここで，*y*<sub>*t*</sub> はオリジナルの定式化における 0-1
変数で， 期 *t* に生産をするときに 1，それ以外のとき 0
を表すことを思い起こされたい． よって，以下の定式化を得る．

$$
 \begin{array}{ l l l }
  minimize & \sum\_{s t: s \leq t} CH\_{st} X\_{st} + \sum\_{t} f_t y_t &        \\
  s.t.   & \\\\\sum\_{s=1}^t X\_{st}=1    & \forall  t=1,\cdots,T  \\
                &  X\_{st} \leq y_s         & \forall s \leq t, t=1,\cdots,T  \\
                &  X\_{st} \geq 0           & \forall s \leq t, t=1,\cdots,T \\
                & y_t \in \\0,1\\          & \forall t=1,\cdots,T  
 \end{array}
$$

この定式化は，期を表す点を需要地点（マーケット）ならびに施設（倉庫）の配置可能地点ととらえると，
施設配置問題に類似した問題になるので，施設配置定式化（facility location
formulation）とよばれる．

オリジナルの定式化の変数との関係は以下のようになる。

各期の生産量：
$$
x_s = \sum\_{t=s}^T d_t X\_{st} 
$$

在庫量：
$$
 I_t = \sum\_{i=1}^t ( x_i -d_i ) 
$$

この関係を用いれば、オリジナルの定式化に含まれている付加制約を記述することができる。

他にも、生産をする際のロットサイズが固定されている場合（フル容量生産）に対しては、別の強化された定式化が可能である。
また、順序依存の段取りや複数の生産資源制約や生産ラインへの割り当てを考慮した定式化も可能であるが、問題の規模が大きくなると計算が困難になる。
実際問題の制約に応じて、カスタマイズされた定式化を行うことが望ましい。

## 多モードロットサイズ決定モデルの定式化

\#hide

### 大バケットモデル

### 小バケットモデル

各期の各モード（ラインを想定）では，1つの品目を生産できると仮定した場合を考える．これは，高々2つの品目を生産可能な場合に拡張できる．

集合

- {1..*T*}: 期間の集合
- *P* :
  品目の集合（完成品と部品や原材料を合わせたものを「品目」と定義する。）
- *K* :
  生産を行うのに必要な資源（機械，生産ライン，工程などを表す）の集合
- *M*<sub>*p*</sub>: 品目 *p* のモードの集合（すべてのモードの集合は
  *M*）
- *P**M*<sub>*k*</sub> : 資源 *k* で生産される品目とモードの対の集合

パラメータ

- *T*: 計画期間数；期を表す添え字を 1, 2, ⋯, *t*, ⋯, *T* と記す．
- *f*<sub>*t**m*</sub><sup>*p*</sup> : 期 *t* に品目 *p* をモード *m*
  で段取り替え（生産準備）を行うときの費用（段取り費用）
- *g*<sub>*t**m*</sub><sup>*p*</sup> : 期 *t* に品目 *p* をモード *m*
  で段取り替え（生産準備）を行うときの時間（段取り時間）
- *c*<sub>*t**m*</sub><sup>*p*</sup> : 期 *t* における品目 *p* のモード
  *m* での生産変動費用
- *τ*<sub>*t**m*</sub><sup>*p*</sup>: 期 *t* における品目 *p* のモード
  *m* での単位生産時間
- *h*<sub>*t*</sub><sup>*p*</sup> : 期 *t* から期 *t* + 1 に品目 *p*
  を持ち越すときの単位あたりの在庫費用
- *d*<sub>*t*</sub><sup>*p*</sup> : 期 *t* における品目 *p* の需要量
- *U*<sub>*t*</sub><sup>*k*</sup> : 期 *t* における資源 *k*
  の使用可能な生産時間の上限．
- *U**B*<sub>*t*</sub><sup>*k**p*</sup> : 期 *t* における品目 *p*
  の生産時間の上限． 品目 *p* を生産する資源が *k*
  のとき，資源の使用可能時間の上限 *U*<sub>*t*</sub><sup>*k*</sup>
  から段取り替え時間 *g*<sub>*t*</sub><sup>*p*</sup>
  を減じたものと定義される．

変数

- *x*<sub>*t**m*</sub><sup>*p*</sup>: 期 *t* における品目 *p* のモード
  *m* における生産量
- *I*<sub>*t*</sub><sup>*p*</sup> : 期 *t* における品目 *p*
  の在庫量（ただし *t* = 0 のときには初期在庫，*t* = *T*
  のときには最終在庫を表すパラメータとする．）
- *y*<sub>*t**m*</sub><sup>*p*</sup>: 期 *t* に品目 *p* をモード *m*
  で生産を行うとき 1， それ以外のとき 0 を表す 0-1 変数（ただし *t* = 0
  のときには初期における段取り状態を表すパラメータとする． また，*t* = 0
  のときには，1つの品目の段取り状態になっていないといけない．）
- *z*<sub>*t**m*</sub><sup>*p*</sup>: 期 *t* に品目 *p* をモード *m*
  で段取りを開始するとき 1， それ以外のとき 0 を表す 0-1 変数
- *w*<sub>*t**m*</sub><sup>*p*</sup>: 期 *t* に品目 *p* をモード *m*
  で段取りを終了するとき 1， それ以外のとき 0 を表す 0-1 変数

上の記号を用いると、小バケットロットサイズ決定モデルは，以下のように定式化できる．

$$
\begin{array}{ l l l }
minimize & \sum\_{t=1}^T \sum\_{p \in P} \sum\_{m \in M_p} \left( f\_{tm}^p z\_{tm}^p + c\_{tm}^p x\_{tm}^p + h_t^p I_t^p \right) &           \\
s.t. &  \\\\I\_{t-1}^p + \sum\_{m \in M_p} x\_{tm}^p  = d_t^p+ I_t^p & \forall p \in P, t=1,\cdots,T  \\
            &  \sum\_{(p,m) \in PM_k}  ( \tau\_{tm}^p x_t^p + g\_{tm}^p z\_{tm}^p )  \leq U_t^k    & \forall k \in K, t=1,\cdots,T \\
            &  x\_{tm}^p  \leq UB_t^p y\_{tm}^p     & \forall p \in P, t=1,\cdots,T  \\
            & \sum\_{p} y\_{tm}^p = 1  & \forall m \in M, t=1,\cdots,T \\
            & z\_{tm}^p \leq y\_{tm}^p   & \forall  p \in P, m \in M_p, t=1,\cdots,T  \\
            & z\_{tm}^p- w\_{t-1,m}^p = y\_{tm}^p - y\_{t-1,m}^p  & \forall  p \in P, m \in M_p, t=1,\cdots,T  \\
            &  I_0^p =0               & \forall p \in P                          \\
            &  x\_{tm}^p \geq 0         & \forall  p \in P, m \in M_p, t=1,\cdots,T  \\
            &  I_t^p \geq 0             & \forall  p \in P, t=1,\cdots,T  \\
            &  y\_{tm}^p, z\_{tm}^p, w\_{tm}^p   \in \\0,1\\   & \forall  p \in P, m \in M_p, t=1,\cdots,T
\end{array}
$$

また，以下の強化式を追加すると，高速化する可能性がある．
*y*<sub>*t* − 1, *m*</sub><sup>*p*</sup> + *z*<sub>*t**m*</sub><sup>*p*</sup> + ∑<sub>*q* ≠ *p* : *m* ∈ *M*<sub>*q*</sub></sub>(*y*<sub>*t**m*</sub><sup>*q*</sup> − *z*<sub>*t*, *m*</sub><sup>*q*</sup>) ≤ 1   ∀*p* ∈ *P*, *m* ∈ *M*<sub>*p*</sub>, *t* = 1, ⋯, *T*

これは， 以下の3つが同時に発生しないことを表す．

1.  期 *t* − 1 に品目 *p* が段取り状態である．
2.  期 *t* に品目 *p* が段取りを開始する．
3.  *p* 以外の品目 *q* が期 *t*
    に段取りを開始する以外で段取り状態にある．（*y*<sub>*t**m*</sub><sup>*q*</sup> = 1
    でかつ *z*<sub>*t*, *m*</sub><sup>*q*</sup> = 0 である．）

### 高々2つの品目が生産可能と仮定した小バケットモデル

段取りに関する部分を以下を変更する．
$$
\begin{array}{ l l l }
            & \sum\_{p} (y\_{tm}^p -z\_{tm}^p ) = 1  & \forall m \in M, t=1,\cdots,T \\
            & z\_{tm}^p +z\_{t-1,n}^p \leq y\_{tm}^p   & \forall  p \in P, m \in M_p, t=1,\cdots,T  
\end{array}
$$

最初の式は，段取り状態で段取り開始でない品目（前の期からの段取りを引き継いでいる品目）は，1つであることを表す．
次の式は，期 *t* もしくは *t* − 1 に段取り開始をしたならば，期 *t*
は段取り状態になっていることを表し，同時に2期連続で段取り開始できないことを表す．

また，以下の強化式を追加すると，高速化する可能性がある．
*y*<sub>*t**m*</sub><sup>*p*</sup> + ∑<sub>*q* ≠ *p* : *m* ∈ *M*<sub>*q*</sub></sub>(*y*<sub>*t* + 1, *m*</sub><sup>*q*</sup> − *z*<sub>*t* + 1, *m*</sub><sup>*q*</sup> − *z*<sub>*t**m*</sub><sup>*q*</sup>) ≤ 1   ∀*p* ∈ *P*, *m* ∈ *M*<sub>*p*</sub>, *t* = 1, ⋯, *T*

これは，品目 *p* が期 *t* に段取り状態になっている場合は，それ以外の品目
*q* が期 *t* + 1 に段取り状態で，かつ期 *t* + 1 でも *t*
でも段取り開始をしていないことが不可能であることを規程する．

``` python
#需要の生成 
T = 24 
yearly_ratio = [1.0 + np.sin(i)*0.5 for i in range(13)] 
dem, prod, month = [], [], [] 
try:
    prod_df.reset_index(inplace=True)
except:
    pass
for row in prod_df.itertuples():
    mu, sigma  = row.average_demand, row.standard_deviation  #monthly demand and std (original average is weekly) 
    for t in range(T):
        dem.append( int(yearly_ratio[t//12]* random.gauss(mu,sigma)) )
        prod.append(row.name)
        month.append(t)
plnt_demand_df = pd.DataFrame({"prod":prod, "period":month, "demand": dem})
plnt_demand_df.to_csv(folder+"plnt-demand.csv")
plnt_demand_df.head()
#print("T=",T)
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">prod</th>
<th data-quarto-table-cell-role="th">period</th>
<th data-quarto-table-cell-role="th">demand</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>A</td>
<td>0</td>
<td>11790</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>A</td>
<td>1</td>
<td>12295</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>A</td>
<td>2</td>
<td>12417</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>A</td>
<td>3</td>
<td>12506</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>A</td>
<td>4</td>
<td>11648</td>
</tr>
</tbody>
</table>

</div>

``` python
try:
    prod_df.set_index("name",inplace=True)
except:
    pass

#２段階モデルを仮定；各段階には１つの資源制約と仮定
num_stages = 2
maintenance_cycle = 100 #資源が使用不可になる周期；この数で割り切れると休止
#num_resources = [2,3]
#assert num_stages == len(num_resources) #段階数と資源数のリストの長さは同じであることの確認

prod_time_bound = (1,1)
setup_time_bound =(3600,7200)
prod_cost_bound = (100,300)
setup_cost_bound =(10000,20000)

units_bound =(1,1)

num_parents = 5  # 1つの子品目から生成される親品目の数；distribution型の生産工程を仮定
products = list(prod_df.index)
num_prod = len(products)

#原材料リストを生成
raw_material_name=""
raw_materials = [] 
for i,p in enumerate(prod_df.index):
    raw_material_name += str(p)
    if (i+1)%num_parents == 0 or i==len(prod_df)-1:
        #print(i,p, raw_material_name)
        raw_materials.append(raw_material_name)
        raw_material_name =""
#親子関係を定義
parent = defaultdict(list)
child = {}
for r in raw_materials:
    parent[r] = list(r)
    for p in parent[r]:
        child[p] = r

#生産工程の容量を計算
average_demand = plnt_demand_df.demand.sum()/T
print(average_demand)
```

    188868.375

``` python
cycle_time= 4
capacity = (average_demand*prod_time_bound[1]+setup_time_bound[1]*num_prod/cycle_time)

print("capacity=",capacity)
#資源データフレーム
#期ごとに容量が変化するモデル
#print(capacity)
name_, period_, capacity_ = [], [], []
for s in range(num_stages):
    for t in range(T):
        name_.append( f"Res{s}")
        period_.append(t)
        if (t+1) % maintenance_cycle == 0:
            capacity_.append( 0 )
        else:
            if s ==0:
                capacity_.append( capacity/2 )
            else:
                capacity_.append( capacity)
resource_df = pd.DataFrame(data={"name": name_, "period": period_, "capacity": capacity_})

#部品展開表のデータフレーム生成
bom_df = pd.DataFrame(data={"child": [child[p] for p in products],
                         "parent": products,
                         "units": [random.randint(units_bound[0], units_bound[1]) for i in range(num_prod)]
                        })

#生産情報データフレーム生成
items = products+raw_materials
num_item = len(items)
production_df = pd.DataFrame(data={"name": items,
                             "ProdTime": [random.randint(prod_time_bound[0], prod_time_bound[1]) for i in range(num_item)],
                             "SetupTime": [random.randint(setup_time_bound[0], setup_time_bound[1]) for i in range(num_item)],
                             "ProdCost": [random.randint(prod_cost_bound[0], prod_cost_bound[1]) for i in range(num_item)],
                             "SetupCost": [random.randint(setup_cost_bound[0], setup_cost_bound[1]) for i in range(num_item)]
                             })
production_df.set_index("name", inplace=True)
production_df.reset_index(inplace=True)
production_df.to_csv(folder+"production.csv")
bom_df.to_csv(folder+"bomodel.csv")
resource_df.to_csv(folder+"resource.csv")
```

    capacity= 206868.375

``` python
#lotsize用のprodデータ作成
#inv_cost: Optional[float] = Field(description="工場における在庫費用")
#safety_inventory: Optional[float] = Field(description="安全在庫量（最終期の目標在庫量）")
#initial_inventory: Optional[float] = Field(description="初期在庫量")
#target_inventory

prod_df.reset_index(inplace=True)
name = prod_df.name.to_list()
inv_cost = prod_df.inv_cost.to_list()
safety_inventory = prod_df.safety_inventory.to_list()
initial_inventory = prod_df.initial_inventory.to_list()
target_inventory = prod_df.target_inventory.to_list()

name.extend(raw_materials)
inv_cost.extend( [0.002 for i in range(len(raw_materials))])
safety_inventory.extend([sum(safety_inventory[:num_parents]),sum(safety_inventory[num_parents:])] )
initial_inventory.extend([sum(initial_inventory[:num_parents]),sum(initial_inventory[num_parents:])] )
target_inventory.extend([sum(target_inventory[:num_parents]),sum(target_inventory[num_parents:])] )

prod_df = pd.DataFrame({"name":name, "inv_cost":inv_cost,"safety_inventory":safety_inventory,"initial_inventory":initial_inventory,"target_inventory":target_inventory})
prod_df.to_csv(folder+"lotprod.csv")
```

## ロットサイズ決定問題を解く関数 lotsizing

2段階モデルを仮定し、最初の段階（原材料生成工程）における資源名を
Res0、次の段階（完成品生産工程）における資源名をRes1とする。

原材料の在庫は許さないものとする。

親品目は子品目１単位から生成される。

引数：

- prod_df : 品目データフレーム
- production_df : 生産情報データフレーム
- bom_df : 部品展開表データフレーム
- demand : （期別・品目別の）需要を入れた配列（行が品目，列が期）
- resource_df : 資源データフレーム

返値： - model : ロットサイズ決定モデルのオブジェクト（最適解の情報も
mode.\_\_data に含む）; 最適化の状態は model.Status - T : 計画期間数

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L52"
target="_blank" style="float:right; font-size:smaller">source</a>

### lotsizing

``` python

def lotsizing(
    prod_df, production_df, bom_df, demand, resource_df, max_cpu:int=10, solver:str='CBC'
):

```

*ロットサイズ決定問題を解く関数*

### lotsizing関数の使用例

``` python
#prod_df = pd.read_csv(folder + "Prod_with_inventory.csv",index_col="name")
prod_df = pd.read_csv(folder + "lotprod.csv",index_col=0)
prod_df.set_index("name", inplace=True)
production_df = pd.read_csv(folder + "production.csv",index_col="name")
bom_df = pd.read_csv(folder + "bom.csv")

#需要量の計算
#demand = demand_df.iloc[:,3:].values
plnt_demand_df = pd.read_csv(folder+"plnt-demand.csv")

demand_ = pd.pivot_table(plnt_demand_df, index= "prod", columns ="period",  values="demand", aggfunc=sum)
demand = demand_.values
_, T = demand.shape 
resource_df = pd.read_csv(folder + "resource.csv")

model, T = lotsizing(prod_df, production_df, bom_df, demand = demand, resource_df=resource_df, max_cpu= 10, solver="SCIP")
```

### 最適化結果から図とデータフレームを生成する関数 show_result_for_lotsizing

引数：

- model : ロットサイズ決定モデル
- T : 計画期間
- production_df : 生産情報データフレーム
- bom_df : 部品展開表データフレーム
- resource_df : 資源データフレーム

返値：

- violated :
  需要満足条件を逸脱した品目と期と逸脱量を保存したデータフレーム
- production : 生産量を保管したデータフレーム
- inventory : 在庫量を保管したデータフレーム
- fig_inv : 在庫量の推移を表した図オブジェクト
- fig_capacity : 容量制約を表した図オブジェクト

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L189"
target="_blank" style="float:right; font-size:smaller">source</a>

### show_result_for_lotsizing

``` python

def show_result_for_lotsizing(
    model, T, prod_df, production_df, bom_df, resource_df
):

```

*最適化結果から図とデータフレームを生成する関数*

``` python
violated, production, inventory, fig_inv, fig_capacity = show_result_for_lotsizing(model, T, prod_df, production_df, bom_df, resource_df=resource_df)
plotly.offline.plot(fig_inv);
```

``` python
plotly.offline.plot(fig_capacity);
```

``` python
production.columns = [str(i) for i in list(production.columns)]
production
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">0</th>
<th data-quarto-table-cell-role="th">1</th>
<th data-quarto-table-cell-role="th">2</th>
<th data-quarto-table-cell-role="th">3</th>
<th data-quarto-table-cell-role="th">4</th>
<th data-quarto-table-cell-role="th">5</th>
<th data-quarto-table-cell-role="th">6</th>
<th data-quarto-table-cell-role="th">7</th>
<th data-quarto-table-cell-role="th">8</th>
<th data-quarto-table-cell-role="th">9</th>
<th data-quarto-table-cell-role="th">...</th>
<th data-quarto-table-cell-role="th">14</th>
<th data-quarto-table-cell-role="th">15</th>
<th data-quarto-table-cell-role="th">16</th>
<th data-quarto-table-cell-role="th">17</th>
<th data-quarto-table-cell-role="th">18</th>
<th data-quarto-table-cell-role="th">19</th>
<th data-quarto-table-cell-role="th">20</th>
<th data-quarto-table-cell-role="th">21</th>
<th data-quarto-table-cell-role="th">22</th>
<th data-quarto-table-cell-role="th">23</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">FGHIJ</td>
<td>0.000000</td>
<td>0.00000</td>
<td>6282.38280</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>7298.43600</td>
<td>0.0</td>
<td>5242.57507</td>
<td>...</td>
<td>0.0000</td>
<td>5787.49000</td>
<td>0.000000</td>
<td>5515.05310</td>
<td>0.00000</td>
<td>5059.50944</td>
<td>0.0000</td>
<td>5928.4375</td>
<td>0.0</td>
<td>4581.18941</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">B</td>
<td>0.000000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>7857.47797</td>
<td>0.00000</td>
<td>0.0</td>
<td>0.00000</td>
<td>...</td>
<td>0.0000</td>
<td>0.00000</td>
<td>0.000000</td>
<td>0.00000</td>
<td>8014.69081</td>
<td>0.00000</td>
<td>0.0000</td>
<td>0.0000</td>
<td>0.0</td>
<td>0.00000</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">I</td>
<td>0.000000</td>
<td>0.00000</td>
<td>6713.83529</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.0</td>
<td>6792.29993</td>
<td>...</td>
<td>0.0000</td>
<td>6871.29993</td>
<td>0.000000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.0000</td>
<td>0.0000</td>
<td>6433.0</td>
<td>0.00000</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">ABCDE</td>
<td>0.000000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>4456.00576</td>
<td>5295.47240</td>
<td>5963.55650</td>
<td>0.00000</td>
<td>0.0</td>
<td>0.00000</td>
<td>...</td>
<td>6325.9182</td>
<td>0.00000</td>
<td>0.000000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>7208.4375</td>
<td>0.0000</td>
<td>0.0</td>
<td>4166.04556</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">C</td>
<td>0.000000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>4533.29513</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.0</td>
<td>0.00000</td>
<td>...</td>
<td>0.0000</td>
<td>0.00000</td>
<td>0.000000</td>
<td>4533.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.0000</td>
<td>0.0000</td>
<td>0.0</td>
<td>0.00000</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">E</td>
<td>0.000000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>7964.16130</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.0</td>
<td>0.00000</td>
<td>...</td>
<td>0.0000</td>
<td>0.00000</td>
<td>0.000000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.0000</td>
<td>0.0000</td>
<td>0.0</td>
<td>0.00000</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">A</td>
<td>0.000000</td>
<td>5666.50830</td>
<td>5173.89016</td>
<td>5053.15945</td>
<td>0.00000</td>
<td>0.00000</td>
<td>5876.84050</td>
<td>5162.23797</td>
<td>0.0</td>
<td>0.00000</td>
<td>...</td>
<td>5500.0000</td>
<td>0.00000</td>
<td>6163.000000</td>
<td>0.00000</td>
<td>5261.23797</td>
<td>0.00000</td>
<td>6298.0000</td>
<td>0.0000</td>
<td>5772.0</td>
<td>0.00000</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">F</td>
<td>0.000000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>7796.72949</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.0</td>
<td>7663.00000</td>
<td>...</td>
<td>0.0000</td>
<td>0.00000</td>
<td>0.000000</td>
<td>7978.19694</td>
<td>0.00000</td>
<td>7268.80306</td>
<td>0.0000</td>
<td>0.0000</td>
<td>0.0</td>
<td>0.00000</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">J</td>
<td>6851.499050</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>7015.0</td>
<td>0.00000</td>
<td>...</td>
<td>0.0000</td>
<td>7039.08510</td>
<td>0.000000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.0000</td>
<td>0.0000</td>
<td>6500.0</td>
<td>0.00000</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">D</td>
<td>5873.027089</td>
<td>6513.19552</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>6438.24121</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.0</td>
<td>0.00000</td>
<td>...</td>
<td>0.0000</td>
<td>0.00000</td>
<td>6473.831800</td>
<td>0.00000</td>
<td>5966.97191</td>
<td>0.00000</td>
<td>6191.4375</td>
<td>6462.0000</td>
<td>0.0</td>
<td>0.00000</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">G</td>
<td>0.000000</td>
<td>7518.17118</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.0</td>
<td>0.00000</td>
<td>...</td>
<td>7871.9568</td>
<td>0.00000</td>
<td>7061.043196</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.0000</td>
<td>0.0000</td>
<td>0.0</td>
<td>0.00000</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">H</td>
<td>6973.348860</td>
<td>0.00000</td>
<td>0.00000</td>
<td>6847.98605</td>
<td>6894.81287</td>
<td>0.00000</td>
<td>0.00000</td>
<td>7237.20107</td>
<td>0.0</td>
<td>0.00000</td>
<td>...</td>
<td>0.0000</td>
<td>0.00000</td>
<td>0.000000</td>
<td>0.00000</td>
<td>0.00000</td>
<td>7369.56250</td>
<td>0.0000</td>
<td>7307.4375</td>
<td>0.0</td>
<td>0.00000</td>
</tr>
</tbody>
</table>

<p>12 rows × 24 columns</p>
</div>

``` python
production.head()
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">0</th>
<th data-quarto-table-cell-role="th">1</th>
<th data-quarto-table-cell-role="th">2</th>
<th data-quarto-table-cell-role="th">3</th>
<th data-quarto-table-cell-role="th">4</th>
<th data-quarto-table-cell-role="th">5</th>
<th data-quarto-table-cell-role="th">6</th>
<th data-quarto-table-cell-role="th">7</th>
<th data-quarto-table-cell-role="th">8</th>
<th data-quarto-table-cell-role="th">9</th>
<th data-quarto-table-cell-role="th">...</th>
<th data-quarto-table-cell-role="th">14</th>
<th data-quarto-table-cell-role="th">15</th>
<th data-quarto-table-cell-role="th">16</th>
<th data-quarto-table-cell-role="th">17</th>
<th data-quarto-table-cell-role="th">18</th>
<th data-quarto-table-cell-role="th">19</th>
<th data-quarto-table-cell-role="th">20</th>
<th data-quarto-table-cell-role="th">21</th>
<th data-quarto-table-cell-role="th">22</th>
<th data-quarto-table-cell-role="th">23</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">D</td>
<td>0.0</td>
<td>0.0</td>
<td>5915.46383</td>
<td>0.00000</td>
<td>6511.0</td>
<td>0.000000</td>
<td>0.00000</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>...</td>
<td>0.0</td>
<td>6448.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>6501.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">F</td>
<td>0.0</td>
<td>0.0</td>
<td>0.00000</td>
<td>7668.53255</td>
<td>0.0</td>
<td>0.000000</td>
<td>0.00000</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>...</td>
<td>0.0</td>
<td>0.0</td>
<td>7532.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>7567.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">H</td>
<td>0.0</td>
<td>0.0</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.0</td>
<td>4007.322978</td>
<td>4807.00000</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>...</td>
<td>0.0</td>
<td>0.0</td>
<td>4840.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">G</td>
<td>0.0</td>
<td>0.0</td>
<td>0.00000</td>
<td>0.00000</td>
<td>0.0</td>
<td>3841.000000</td>
<td>4537.21437</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>...</td>
<td>0.0</td>
<td>0.0</td>
<td>4598.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">I</td>
<td>0.0</td>
<td>0.0</td>
<td>0.00000</td>
<td>7388.53536</td>
<td>0.0</td>
<td>0.000000</td>
<td>0.00000</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>...</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
<td>7410.0</td>
<td>0.0</td>
<td>0.0</td>
<td>0.0</td>
</tr>
</tbody>
</table>

<p>5 rows × 24 columns</p>
</div>

![](11lotsize_files/figure-commonmark/cell-21-output-1.png)

![](11lotsize_files/figure-commonmark/cell-22-output-1.png)

## 一般的なモデルのExcelデータテンプレートの生成

単に最適化を行うだけでなく，実際の運用で使うためのWebアプリとExcel連携

Webアプリで以下のものを入力

- 単位期間： 日や週や月などの計画単位期間を事前に決める．
- 計画期間の開始日と終了日

### 品目データ（マスタ）生成関数 generate_item_master

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L305"
target="_blank" style="float:right; font-size:smaller">source</a>

### generate_item_master

``` python

def generate_item_master(
    wb
):

```

*Call self as a function.*

``` python
wb = Workbook()
ws = wb.active
wb.remove(ws)
wb = generate_item_master(wb)
```

### 工程データ（マスタ） 生成関数 generate_process_master

生産モードの概念を導入し，子品目数と資源数を入力する．
このデータを元に部品展開表テンプレートと，資源必要量テンプレートを生成する．

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L327"
target="_blank" style="float:right; font-size:smaller">source</a>

### generate_process_master

``` python

def generate_process_master(
    wb
):

```

*Call self as a function.*

### ロットサイズ決定システムのマスターExcel Workbookを生成する関数 generate_lotsize_master

品目と工程シートの他に，
資源名と基本容量を入れたシートをもつWorkbookを生成する．
これがロットサイズ決定システムのマスタデータになる．

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L352"
target="_blank" style="float:right; font-size:smaller">source</a>

### generate_lotsize_master

``` python

def generate_lotsize_master(
    
):

```

*Call self as a function.*

``` python
wb = generate_lotsize_master()
wb.save("optlot-master.xlsx")
```

### 部品展開表と資源必要量のシートを追加する関数 add_bom_resource_sheets

引数: - wb: シート追加前のWorkbook；
工程シートに必要な子品目数と資源数を入れておく．

返り値： - wb: 部品展開表と必要資源量のシートを追加したWorkbook

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L374"
target="_blank" style="float:right; font-size:smaller">source</a>

### add_bom_resource_sheets

``` python

def add_bom_resource_sheets(
    wb
):

```

*Call self as a function.*

``` python
wb = load_workbook("lotsize-ex1.xlsx")
wb = add_bom_resource_sheets(wb)
#wb.save("lotsize-ex1.xlsx")
```

### 資源データ入力Excelテンプレートの生成関数

基本データをマスタで入力した後，
Webアプリで開始日と終了日と期間(pandasの頻度(freq)パラメータ）を入力し，生成した期別の入力シートをダウンロードしてもらい，
期別の容量（稼働時間上限）をExcelで入力し，アップロードする．これが，
オペレーショナルに用いる資源データになる．

引数： - wb: ロットサイズ決定のためのExcel Workbook - start:
計画期間の開始日（日付時刻を表す文字列もしくは日付時刻型） - finish:
計画期間の終了日（日付時刻を表す文字列もしくは日付時刻型） - period:
期を構成する単位期間の数；既定値は 1 - period_unit: 期の単位
（時，日，週，月から選択； 既定値は日；
periodとあわせて期間の生成に用いる．
たとえば，既定値だと１日が1期となる．

返値： - wb:
資源の期別の容量（稼働時間上限）のシートを追加したWorkbook；
シート名は「期別上限 開始日 終了日」となる．

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L440"
target="_blank" style="float:right; font-size:smaller">source</a>

### add_detailed_resource_sheet

``` python

def add_detailed_resource_sheet(
    wb, start, finish, period:int=1, period_unit:str='日'
):

```

*Call self as a function.*

``` python
wb = load_workbook("lotsize-sample-new.xlsx")
start = '2021-01-01' #開始日
finish = '2021-01-5'
period=1
period_unit="日"
wb = add_detailed_resource_sheet(wb, start, finish, period, period_unit)
wb.save("lotsize-sample-new.xlsx")
```

``` python
#開始日を設定し，需要を生成
wb = load_workbook("optlot-master-at.xlsx")
start = '2021-01-5' #開始日
finish = '2021-12-17'
period=1
period_unit="日"
wb = add_detailed_resource_sheet(wb, start, finish, period, period_unit)
wb.save("optlot-master-at2.xlsx")
```

### 注文データ（品目の納期と数量） generate_order_excel

注文データ（品目の納期と数量）のテンプレートExcel Workbookを生成する．
これは，オペレーショナルデータである． これから需要量データを生成する．

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L464"
target="_blank" style="float:right; font-size:smaller">source</a>

### generate_order_master

``` python

def generate_order_master(
    
):

```

*Call self as a function.*

``` python
wb = generate_order_master()
wb.save("order.xlsx")
```

### Excelブックの読み込みとデータ変換のための関数 read_dfs_from_excel_lot

引数 - wb: Excel Workbook

返値： - item_df: 品目データフレーム - process_df: 工程データフレーム -
resource_df: 資源データフレーム - bom_df: 部品展開表データフレーム -
usage_df: 資源使用量データフレーム

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L488"
target="_blank" style="float:right; font-size:smaller">source</a>

### read_dfs_from_excel_lot

``` python

def read_dfs_from_excel_lot(
    wb
):

```

*Call self as a function.*

### read_dfs_from_excel_lotの使用例

``` python
wb = load_workbook("optlot-master2-ex1.xlsx")
item_df, process_df, resource_df, bom_df, usage_df = read_dfs_from_excel_lot(wb)
print(item_df)
#print(process_df)
#print(resource_df)
```

               名称  在庫費用（円/期/unit)  在庫量下限  在庫量上限  初期在庫量  最終在庫量
    0     Bottle6              54      0   1000      0      0
    1        Can6              74      0   1000      0      0
    2     Bottle1               8      0   1000      0      0
    3        Can1               9      0   1000      0      0
    4       Apple               1      0   1000      0      0
    5  Watermelon               2      0   1000      0      0

### 注文Workbookから需要を生成する関数 generate_demand_from_order

引数 - wb: 注文情報を入れたExcel Workbook（シート名は注文） - start:
開始日；日単位でない（たとえば1週単位の）場合には，開始日から1週間前からその開始日までが最初の期になる． -
finish:
終了日：日単位でない（たとえば1週単位の）場合には，生成したい週の最後の日が終了日以前である必要がある． -
period: 期を構成する単位期間の数；既定値は 1 - period_unit: 期の単位
（時，日，週，月から選択； 既定値は日；
periodとあわせて期間の生成に用いる．
たとえば，既定値だと１日が1期となる．

返値 - demand: 品目 *p* の期 *t* の需要量 demand\[t,p\] を入れた辞書 -
T: 計画期間数

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L520"
target="_blank" style="float:right; font-size:smaller">source</a>

### generate_demand_from_order

``` python

def generate_demand_from_order(
    wb, start, finish, period:int=1, period_unit:str='日'
):

```

*Call self as a function.*

### generate_demand_from_orderの使用例

``` python
wb = load_workbook("order-sample.xlsx")
#開始日を設定し，需要を生成
start = '2021-01-10' #開始日
finish = '2021-1-20' #終了日
period=1
period_unit="日"
demand, T = generate_demand_from_order(wb, start, finish, period, period_unit)
print("Demand=",demand)
print("T=",T)
```

    Demand= defaultdict(<class 'float'>, {(0, 'A1'): 100.0, (1, 'A1'): 0.0, (2, 'A1'): 0.0, (3, 'A1'): 0.0, (4, 'A1'): 0.0, (5, 'A1'): 200.0, (6, 'A1'): 0.0, (7, 'A1'): 0.0, (8, 'A1'): 400.0, (9, 'A1'): 0.0, (10, 'A1'): 300.0, (0, 'B1'): 100.0})
    T= 11

### 期別資源使用量上限をもつWorkbookから情報を抽出する関数 get_resource_ub

引数 - wb: 注文情報を入れたExcel
Workbook（期別資源使用量上限のシート名は「期別上限 {開始日}
{終了日}」である必要がある．） - start:
開始日；日単位でない（たとえば1週単位の）場合には，開始日から1週間前からその開始日までが最初の期になる． -
finish:
終了日：日単位でない（たとえば1週単位の）場合には，生成したい週の最後の日が終了日以前である必要がある． -
period: 期を構成する単位期間の数；既定値は 1 - period_unit: 期の単位
（時，日，週，月から選択； 既定値は日；
periodとあわせて期間の生成に用いる．
たとえば，既定値だと１日が1期となる．

返値： - M: 期別の資源量上限を表す辞書； 資源 *r* の期 *t* の上限が
M\[t,r\] になる．

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L551"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_resource_ub

``` python

def get_resource_ub(
    wb, start, finish, period:int=1, period_unit:str='日'
):

```

*Call self as a function.*

``` python
start = '2021-01-01' #開始日
finish = '2021-01-25'
period=1
period_unit="日"
wb = load_workbook("optlot-master2-ex1.xlsx")
M = get_resource_ub(wb, start, finish, period, period_unit)
print(M)
```

    {(0, 'Production'): 2880, (1, 'Production'): 2880, (2, 'Production'): 2880, (3, 'Production'): 2880, (4, 'Production'): 2880, (5, 'Production'): 2880, (6, 'Production'): 2880, (7, 'Production'): 2880, (8, 'Production'): 2880, (9, 'Production'): 2880, (10, 'Production'): 2880, (11, 'Production'): 2880, (12, 'Production'): 2880, (13, 'Production'): 2880, (14, 'Production'): 2880, (15, 'Production'): 2880, (16, 'Production'): 2880, (17, 'Production'): 2880, (18, 'Production'): 2880, (19, 'Production'): 2880, (20, 'Production'): 2880, (21, 'Production'): 2880, (22, 'Production'): 2880, (23, 'Production'): 2880, (24, 'Production'): 2880, (0, 'Packaging'): 1440, (1, 'Packaging'): 1440, (2, 'Packaging'): 1440, (3, 'Packaging'): 1440, (4, 'Packaging'): 1440, (5, 'Packaging'): 1440, (6, 'Packaging'): 1440, (7, 'Packaging'): 1440, (8, 'Packaging'): 1440, (9, 'Packaging'): 1440, (10, 'Packaging'): 1440, (11, 'Packaging'): 1440, (12, 'Packaging'): 1440, (13, 'Packaging'): 1440, (14, 'Packaging'): 1440, (15, 'Packaging'): 1440, (16, 'Packaging'): 1440, (17, 'Packaging'): 1440, (18, 'Packaging'): 1440, (19, 'Packaging'): 1440, (20, 'Packaging'): 1440, (21, 'Packaging'): 1440, (22, 'Packaging'): 1440, (23, 'Packaging'): 1440, (24, 'Packaging'): 1440, (0, 'Dummy'): 6000000, (1, 'Dummy'): 6000000, (2, 'Dummy'): 6000000, (3, 'Dummy'): 6000000, (4, 'Dummy'): 6000000, (5, 'Dummy'): 6000000, (6, 'Dummy'): 6000000, (7, 'Dummy'): 6000000, (8, 'Dummy'): 6000000, (9, 'Dummy'): 6000000, (10, 'Dummy'): 6000000, (11, 'Dummy'): 6000000, (12, 'Dummy'): 6000000, (13, 'Dummy'): 6000000, (14, 'Dummy'): 6000000, (15, 'Dummy'): 6000000, (16, 'Dummy'): 6000000, (17, 'Dummy'): 6000000, (18, 'Dummy'): 6000000, (19, 'Dummy'): 6000000, (20, 'Dummy'): 6000000, (21, 'Dummy'): 6000000, (22, 'Dummy'): 6000000, (23, 'Dummy'): 6000000, (24, 'Dummy'): 6000000}

### 最適化結果Workbookの色情報を元に変数の固定情報を抽出する関数 extract_fix_info

出力のExcelシートに色を塗った箇所の生産（発注）量を固定する情報を抽出する．

引数 - wb: 最適化結果を入れた Excel Workbook - start:
開始日；日単位でない（たとえば1週単位の）場合には，開始日から1週間前からその開始日までが最初の期になる． -
finish:
終了日：日単位でない（たとえば1週単位の）場合には，生成したい週の最後の日が終了日以前である必要がある． -
period: 期を構成する単位期間の数；既定値は 1 - period_unit: 期の単位
（時，日，週，月から選択； 既定値は日；
periodとあわせて期間の生成に用いる．
たとえば，既定値だと１日が1期となる．

返値： - fix_x: 変数 *x*
の固定情報；キーは「期番号，モード,アイテム」で値は固定したい数値を入れた辞書

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L588"
target="_blank" style="float:right; font-size:smaller">source</a>

### extract_fix_info

``` python

def extract_fix_info(
    wb, start, finish, period:int=1, period_unit:str='日'
):

```

*Call self as a function.*

#### extract_fix_info関数の使用例

``` python
wb = load_workbook("lotsize-out.xlsx")
start = '2021-01-01' #開始日
finish = '2021-01-25'
period=1
period_unit="日"
fix_x = extract_fix_info(wb, start, finish, period=1, period_unit="日")
fix_x
```

    {}

## 多モードロットサイズ決定問題を解く関数 multi_mode_lotsizing

引数： - item_df: 品目データフレーム - process_df: 工程データフレーム -
resource_df: 資源データフレーム - bom_df: 部品展開表データフレーム -
usage_df: 資源使用量データフレーム - demand: 需要量を入れた辞書 -
capacity: 資源量上限を入れた辞書 - T: 計画期間数（既定値は 1） - fix_x:
変数固定情報を入れた辞書（既定値は None）

返値： - model: モデルオブジェクト

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L618"
target="_blank" style="float:right; font-size:smaller">source</a>

### multi_mode_lotsizing

``` python

def multi_mode_lotsizing(
    item_df, resource_df, process_df, bom_df, usage_df, demand, capacity, T:int=1, fix_x:NoneType=None
):

```

*Call self as a function.*

### multi_mode_lotsizing関数の使用例

``` python
#from mypulp import GRB, quicksum, Model
#from pulp import PULP_CBC_CMD
from gurobipy import GRB, quicksum, Model

#開始日を設定し，需要を生成
start = '2021-01-8' #開始日
finish = '2021-02-26'
period=1
period_unit="週"

wb = load_workbook("optlot-master-at.xlsx")
item_df, process_df, resource_df, bom_df, usage_df = read_dfs_from_excel_lot(wb)

M = get_resource_ub(wb, start, finish, period, period_unit)

wb = load_workbook("optlot-order-at.xlsx")

demand, T = generate_demand_from_order(wb, start, finish, period, period_unit)
# print(demand,T)
    
model = multi_mode_lotsizing(item_df, resource_df, process_df, bom_df, usage_df, demand, M, T, fix_x=None)

model.optimize()
#PULPの場合
#solver = PULP_CBC_CMD(timeLimit=10, presolve=False)
#model.optimize(solver)
#SCIPの場合
#solver = SCIP(timeLimit=10)
#model.optimize(solver=solver)

x, I, y, slack, surplus, inv_slack, inv_surplus, cost, items, modes, item_modes, setup_time, prod_time, parent, resources = model.__data

print(model.Status)
```

    Gurobi Optimizer version 10.0.0 build v10.0.0rc2 (mac64[x86])

    CPU model: Intel(R) Xeon(R) W-2140B CPU @ 3.20GHz
    Thread count: 8 physical cores, 16 logical processors, using up to 16 threads

    Optimize a model with 3845 rows, 6109 columns and 16822 nonzeros
    Model fingerprint: 0x8f6d8ff7
    Variable types: 5237 continuous, 872 integer (872 binary)
    Coefficient statistics:
      Matrix range     [5e-01, 6e+05]
      Objective range  [1e+00, 1e+07]
      Bounds range     [1e+00, 1e+00]
      RHS range        [4e+01, 6e+05]
    Found heuristic solution: objective 2.066083e+12
    Presolve removed 2096 rows and 2295 columns
    Presolve time: 0.02s
    Presolved: 1749 rows, 3814 columns, 8612 nonzeros
    Variable types: 3090 continuous, 724 integer (724 binary)

    Root relaxation: objective 1.565244e+11, 759 iterations, 0.00 seconds (0.00 work units)

        Nodes    |    Current Node    |     Objective Bounds      |     Work
     Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time

         0     0 1.5652e+11    0  108 2.0661e+12 1.5652e+11  92.4%     -    0s
    H    0     0                    3.277442e+11 1.5652e+11  52.2%     -    0s
    H    0     0                    2.684641e+11 1.5652e+11  41.7%     -    0s
         0     0 1.5682e+11    0  103 2.6846e+11 1.5682e+11  41.6%     -    0s
    H    0     0                    2.673409e+11 1.5682e+11  41.3%     -    0s
         0     0 1.5682e+11    0  107 2.6734e+11 1.5682e+11  41.3%     -    0s
         0     0 1.5682e+11    0  107 2.6734e+11 1.5682e+11  41.3%     -    0s
         0     0 1.5685e+11    0  103 2.6734e+11 1.5685e+11  41.3%     -    0s
    H    0     0                    1.922286e+11 1.5685e+11  18.4%     -    0s
         0     0 1.5692e+11    0  102 1.9223e+11 1.5692e+11  18.4%     -    0s
         0     0 1.5692e+11    0  105 1.9223e+11 1.5692e+11  18.4%     -    0s
         0     0 1.5692e+11    0   78 1.9223e+11 1.5692e+11  18.4%     -    0s
         0     0 1.5693e+11    0   82 1.9223e+11 1.5693e+11  18.4%     -    0s
         0     0 1.5693e+11    0   82 1.9223e+11 1.5693e+11  18.4%     -    0s
         0     0 1.5698e+11    0   77 1.9223e+11 1.5698e+11  18.3%     -    0s
         0     0 1.5698e+11    0   58 1.9223e+11 1.5698e+11  18.3%     -    0s
         0     2 1.5698e+11    0   58 1.9223e+11 1.5698e+11  18.3%     -    0s
    H   72    80                    1.914588e+11 1.5699e+11  18.0%   6.4    0s
    H   74    80                    1.869687e+11 1.5699e+11  16.0%   6.3    0s
    H  122   144                    1.772177e+11 1.5699e+11  11.4%   4.9    0s
    H  575   632                    1.769165e+11 1.5699e+11  11.3%   4.0    0s
    H  581   632                    1.765848e+11 1.5699e+11  11.1%   4.1    0s
    H  619   632                    1.745040e+11 1.5699e+11  10.0%   4.1    0s
    H  625   632                    1.721484e+11 1.5699e+11  8.81%   4.1    0s
    H  658   567                    1.569882e+11 1.5699e+11  0.00%   4.1    0s

    Cutting planes:
      Gomory: 34
      MIR: 115
      Flow cover: 222
      Flow path: 66
      RLT: 2
      Relax-and-lift: 2

    Explored 763 nodes (5325 simplex iterations) in 0.66 seconds (0.37 work units)
    Thread count was 16 (of 16 available processors)

    Solution count 10: 1.56988e+11 1.72148e+11 1.74504e+11 ... 2.67341e+11

    Optimal solution found (tolerance 1.00e-04)
    Best objective 1.569882242028e+11, best bound 1.569881671758e+11, gap 0.0000%
    2

``` python
# #hide
# INF = 9999999999.
# fix_x=None
# h = {} #在庫費用
# IUB, ILB = {},{} #在庫量の下限と上限
# for row in item_df.itertuples():
#     h[row[1]] = row[2]
#     if pd.isnull(row[3]):
#         ILB[row[1]] = 0.
#     else:
#         ILB[row[1]] = row[3]
#     if pd.isnull(row[4]):
#         IUB[row[1]] = INF # 在庫量上限がない場合
#     else:
#         IUB[row[1]] = row[4]

# #親子関係や資源必要量の辞書を作成
# parent = defaultdict(set) #子品目pを必要とする親品目とモードの組の集合
# phi = defaultdict(float) #親品目qをモードmで１単位生産するために必要な子品目pのunit数
# modes = defaultdict(set) #親品目qのモード集合
# resources = defaultdict(set) #親品目qをモードmで生産するときに必要な資源の集合
# setup_time = defaultdict(float)
# setup_cost = defaultdict(float)
# prod_time = defaultdict(float)
# prod_cost = defaultdict(float)

# items = item_df.iloc[:,0] #品目のリストを準備
# item_set = set(items)
# resource_set = set( resource_df.iloc[:,0])

# #resource内にない場合にエラーを起こす
# #品目にモードがない場合にもエラー
# for row in process_df.itertuples():
#     if row[1] is not None:
#         q = row[1]            
#     if row[2] is not None: #モード
#         m = row[2]
#         modes[q].add(m)
#     #費用
#     if row[3] is None or np.isnan(row[3]):
#         setup_cost[q,m] = 0.
#     else:
#         setup_cost[q,m] = row[3]
#     if row[4] is None or np.isnan(row[4]):
#         prod_cost[q,m] = 0.
#     else:
#         prod_cost[q,m] = row[4] 
        
# for q in items:        
#     if len(modes[q])==0:
#         raise ValueError(f"品目{q}にモードがありません．")
# #BOM
# for row in bom_df.itertuples():
#     if row[1] is not None:
#         q = row[1]            
#     if row[2] is not None: #モード
#         m = row[2]
#     if row[3] is not None:
#         p = row[3]
#         phi[p,q,m] = row[4]
#         parent[p].add( (q,m) )
#     if q not in item_set:
#         raise ValueError(f"品目{q}が品目シートにありません．")
#     if p not in item_set:
#         raise ValueError(f"品目{p}が品目シートにありません．")

# #usage
# for row in usage_df.itertuples():
#     if row[1] is not None:
#         q = row[1]            
#     if row[2] is not None: #モード
#         m = row[2]
#     #資源と時間
#     if row[3] is not None:
#         if r in resource_set and q in item_set:
#             r = row[3]
#             resources[q,m].add(r) 
#             if row[4] is None or np.isnan(row[4]):
#                 setup_time[q,m,r] = 0.
#             else:
#                 setup_time[q,m,r] = row[4]
#             if row[5] is None or np.isnan(row[5]):
#                 prod_time[q,m,r] = 0.
#             else:
#                 prod_time[q,m,r] = row[5]

# #     if r not in resource_set:
# #         raise ValueError(f"資源{r}が資源シートにありません．")
# #     if q not in item_set:
# #         raise ValueError(f"品目{q}が品目シートにありません．")

# item_modes = defaultdict(set) #資源rを使用する品目とモードの組の集合（resourcesの逆写像）
# for key in resources:
#     for r in resources[key]:
#         item_modes[r].add(key)

# model = Model()
# x, I, y = {}, {}, {}
# slack, surplus = {}, {}
# inv_slack, inv_surplus = {}, {}
# Ts = range(0, T)

# for row in item_df.itertuples():
#     p = row[1]
#     for m in modes[p]:
#         for t in Ts:
#             x[t, m, p] = model.addVar(name=f"x({p},{m},{t})")
#             I[t, p] = model.addVar(name=f"I({p},{t})") 
#             y[t, m, p] = model.addVar(name=f"y({p},{m},{t})", vtype="B")
#             slack[t, p] = model.addVar(name=f"slack({p},{t})")
#             surplus[t, p] = model.addVar(name=f"surplus({p},{t})")
#             inv_slack[t, p] = model.addVar(name=f"inv_slack({p},{t})")
#             inv_surplus[t, p] = model.addVar(name=f"inv_surplus({p},{t})")
#         if pd.isnull(row[5]):
#             I[-1, p] = 0.
#         else:
#             I[-1, p] =  row[5]  # 初期在庫  

#         if pd.isnull(row[6]):
#             I[T-1,p] = 0.
#         else:
#             I[T-1,p] =  row[6]  # 最終期の在庫量            
# #各費用項目を別途合計する
# cost ={}
# for i in range(5):
#     cost[i] = model.addVar(vtype="C",name=f"cost[{i}]")

# model.update()

# #変数の固定
# if fix_x is not None:
#     for (t,m,p) in fix_x:
#         model.addConstr( x[t,m,p] == fix_x[t,m,p] )

# #在庫量の上下限の逸脱の計算
# for t in Ts:
#     for p in items:  
#         model.addConstr( I[t, p] <= IUB[p] + inv_surplus[t,p], f"IUB({t},{p})" ) 
#         model.addConstr( ILB[p]  <= I[t, p]+ inv_slack[t,p], f"ILB({t},{p})" ) 


# for row in resource_df.itertuples():
#     r = row[1]
#     for t in Ts:
#         # time capacity constraints
#         model.addConstr(quicksum(prod_time[p,m,r]*x[t,m,p] + setup_time[p,m,r]*y[t,m,p] for (p,m) in item_modes[r]) <= M[t,r], 
#                         f"TimeConstraint1({r},{t})")

# for t in Ts:
#     for p in items:
#         # flow conservation constraints（ソフト制約）
#         model.addConstr(I[t-1, p] + quicksum(x[t, m, p] for m in modes[p]) + slack[t, p] - surplus[t, p] == I[t, p] +demand[t,p]+
#                      quicksum( phi[p,q,m]*x[t, m, q] for (q,m) in parent[p]), f"FlowCons({t},{p})" ) 

# for t in Ts:
#     for p in items:  
#         # capacity connection constraints
#         for m in modes[p]:
#             for r in resources[p,m]:
#                 model.addConstr(prod_time[p,m,r]*x[t,m,p]
#                             <=  (M[t,r]-setup_time[p,m,r])*y[t,m,p], f"ConstrUB({t},{m},{r},{p})")

# model.addConstr( quicksum( slack[t, p]+surplus[t, p] for t in Ts for p in items) == cost[0] )
# model.addConstr( quicksum( inv_slack[t, p]+inv_surplus[t, p] for t in Ts for p in items) == cost[1] )
# model.addConstr( quicksum( setup_cost[p,m]*y[t,m,p] for t in Ts for p in items for m in modes[p] for r in resources[p,m]) == cost[2])
# model.addConstr( quicksum( prod_cost[p,m]*x[t,m,p] for t in Ts  for p in items for m in modes[p] for r in resources[p,m]) == cost[3])
# model.addConstr( quicksum( h[p]*I[t, p] for t in Ts for p in items) == cost[4] )

# model.setObjective(99999999.*cost[0] + 999999.*cost[1] + quicksum(cost[i] for i in range(2,5)) , GRB.MINIMIZE)
# model.__data = x, I, y, slack, surplus, inv_slack, inv_surplus, cost, items, modes, item_modes, setup_time, prod_time, parent, resources

# model.optimize()
```

``` python
# #開始日を設定し，需要を生成
# from pulp import PULP_CBC_CMD
# from mypulp import GRB, quicksum, Model

# order_wb = load_workbook("optlot-order-ex1.xlsx")
# start = '2021-01-1' #開始日
# finish = '2021-01-5'
# period=1
# period_unit="日"
# demand, T = generate_demand_from_order(order_wb, start, finish, period, period_unit)

# #資源量設定
# wb = load_workbook("optlot-master2-ex1.xlsx")
# item_df, process_df, resource_df, bom_df, usage_df = read_dfs_from_excel_lot(wb)
# M = get_resource_ub(wb, start, finish, period, period_unit)

# #変数固定情報
# result_wb = load_workbook("lotsize-out.xlsx")
# fix_x = extract_fix_info(result_wb, start, finish, period=1, period_unit="日")

# model = multi_mode_lotsizing(item_df, resource_df, process_df, bom_df, usage_df, demand, M, T, fix_x)
# model.Params.OutputFlag=False
# solver = PULP_CBC_CMD(timeLimit=100, presolve=True)
# model.optimize(solver)
# print(model.Status, model.ObjVal)
# #     class Status:
# #         OPTIMAL = 2
# #         INFEASIBLE = 3
# #         INF_OR_UNBD = 4
# #         UNBOUNDED = 5
# #         UNDEFINED = None
# x, I, y, slack, surplus, inv_slack, inv_surplus, cost, items, modes, item_modes, setup_time, prod_time, parent, resources = model.__data
# for v in y:
#     if y[v].X is not None and y[v].X > 0:
#         print(v,y[v].X, x[v].X)
# for i in cost:
#     print(cost[i].X)
```

### 最終期の在庫量を最適化するための方法

将来の不確実性を考慮するために，過去の需要量からブートストラップ（繰り返しを許したサンプリング）して未来の需要のシナリオを生成する．
シナリオごとに確定的な最適化を行い，それらの多数決もしくは期待値で行動を決める．

### 費用内訳のデータフレームを生成する関数 make_cost_df

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L793"
target="_blank" style="float:right; font-size:smaller">source</a>

### make_cost_df

``` python

def make_cost_df(
    cost
):

```

*Call self as a function.*

### make_cost_dfの使用例

``` python
cost_df = make_cost_df(cost)
cost_df
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">費用</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">需要逸脱ペナルティ</td>
<td>0.000000e+00</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">在庫上下限逸脱ペナルティ</td>
<td>1.569742e+05</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">段取り費用</td>
<td>1.609179e+06</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">生産変動費用</td>
<td>1.371843e+06</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">在庫費用</td>
<td>1.122441e+07</td>
</tr>
</tbody>
</table>

</div>

### 最適化結果のExcelファイルを出力する関数 lot_output_excel

在庫・生産，需要量とシミュレーション用の関数を埋め込み

次の計画の初期在庫量は，このテンプレートの該当列になる．
ローリングホライズンで行うために，以前のWorkbookを入れてそれに追加

引数 - model: ロットサイズ最適化のモデルオブジェクト - start:
開始日；日単位でない（たとえば1週単位の）場合には，開始日から1週間前からその開始日までが最初の期になる． -
finish:
終了日：日単位でない（たとえば1週単位の）場合には，生成したい週の最後の日が終了日以前である必要がある． -
period: 期を構成する単位期間の数；既定値は 1 - period_unit: 期の単位
（時，日，週，月から選択； 既定値は日；
periodとあわせて期間の生成に用いる．
たとえば，既定値だと１日が1期となる． - demand: 需要を入れた辞書 - cost:
費用の変数を入れた辞書

返値： - wb:
品目別の在庫量・生産量（モードごと）・需要量・品切れ量・超過量を入れたExcel
Workbook

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L798"
target="_blank" style="float:right; font-size:smaller">source</a>

### lot_output_excel

``` python

def lot_output_excel(
    model, start, finish, period, period_unit, demand, cost_df
):

```

*Call self as a function.*

### lot_output_excel関数の使用例

``` python
start = '2021-01-1' #開始日
finish = '2021-01-5'
period=1
period_unit="日"
cost_df = make_cost_df(cost)
wb = lot_output_excel(model, start, finish, period, period_unit, demand, cost_df)
wb.save("lotsize-out.xlsx")
```

### 多モードモデルの最適化結果の図を生成する関数 show_result_for_multimode_lotsizing

引数 - model: ロットサイズ最適化のモデルオブジェクト - start:
開始日；日単位でない（たとえば1週単位の）場合には，開始日から1週間前からその開始日までが最初の期になる． -
finish:
終了日：日単位でない（たとえば1週単位の）場合には，生成したい週の最後の日が終了日以前である必要がある． -
period: 期を構成する単位期間の数；既定値は 1 - period_unit: 期の単位
（時，日，週，月から選択； 既定値は日；
periodとあわせて期間の生成に用いる．
たとえば，既定値だと１日が1期となる． - capacity: 資源量上限を入れた辞書

返値： - fig_inv : 在庫量の推移を表した図オブジェクト - fig_capacity :
容量制約を表した図オブジェクト

------------------------------------------------------------------------

<a
href="https://github.com/scmopt/manual/tree/master/blob/master/scmopt/lotsizing.py#L837"
target="_blank" style="float:right; font-size:smaller">source</a>

### show_result_for_multimode_lotsizing

``` python

def show_result_for_multimode_lotsizing(
    model, start, finish, period, period_unit, capacity
):

```

*Call self as a function.*

``` python
period=1
period_unit="週"
fig_inv, fig = show_result_for_multimode_lotsizing(model, start, finish, period, period_unit, M)

plotly.offline.plot(fig);
plotly.offline.plot(fig_inv);
fig.write_html("resource.html")
fig_inv.write_html("inv.html")
```

#### show_result_for_multimode_lotsizing関数の使用例

``` python
start = '2021-01-1' #開始日
finish = '2021-01-5' #終了日

period=1
period_unit="日"
fig_inv, fig = show_result_for_multimode_lotsizing(model, start, finish, period, period_unit, M)

plotly.offline.plot(fig);
plotly.offline.plot(fig_inv);
```

![](11lotsize_files/figure-commonmark/cell-60-output-1.png)

![](11lotsize_files/figure-commonmark/cell-61-output-1.png)

\#hide \## 生産計画・スケジューリングのためのヒヤリングメモ

- 見込み生産か受注生産か: 需要予測を行っている場合には見込み生産，
  顧客からの注文が生産計画の始点となっている場合には受注生産とみなす．
  もちろん， 中間製品（部品）は見込みで生産し，
  その後の完成品の生産は注文があってから開始する場合もある．
  その場合には，受注生産（引っ張り）と見込み（押し出し）の境界の工程（push-pull
  boundary)をヒヤリングしておく．
  中間製品（部品）の安全在庫がたくさんある地点がこれに該当する．
  ここで安全在庫とは，予測誤差に対処するための緩衝用の在庫である．
  通常のサイクル（仕掛り）在庫ではないことに注意されたい．
  工程ごとのロットまとめの基準が異なる場合には，
  工程間に比較的多くの在庫が滞留することがある（製鉄が代表的である）．
  このような中間在庫はロットサイズ在庫とみなす．

- ロットまとめが重要か：
  生産を行う際に，まとめて生産を行っている場合には，ロットサイズ決定モデルを用いる必要がある．
  ロット生産する際のおおよその段取り時間と段取り費用もヒヤリングしておく．
  小さな生産ロットごとに必要な簡易な段取りは，生産時間に含めて考えることができるので，無視して良い．
  また，段取り時間（費用）が順序依存なのか，順序に依存しないかもヒヤリングしておく．

- 部品展開表（BOM）情報：
  主要な完成品とその生産に必要な主要な中間製品（部品）と原材料についてヒヤリングする．

- 工程情報：
  製品製造のおおよその流れと工程数，ならびにボトルネックになっている工程があるかをヒヤリングする．

- 資源情報：
  生産を行う際にボトルネックになっている製造設備（人員）についてヒヤリングしておく．

- 計画期間：
  スケジューリング最適化の対象とする計画期間は，すべての情報が集まっていて最適化が可能な期間のことである．
  将来を不確実な需要予測をもとにした生産最適化の計画期間と区別してヒヤリングする．
  現場では，大日程と小日程と言った方が通じるかもしれない．

- 最適化の対象とする範囲の決定：
  はじめは重要だが小規模の問題に絞って検証実験をした方が良い．
  クライアントにPoCの対象範囲を決めてもらうことが望ましい．

## 安全在庫配置ならびに在庫最適化のためのヒヤリングメモ

- 複数拠点での在庫最適化なのか1拠点だけなのか
- 計画期間（タクティカルかオペレーショナルか）：
  長期にわたるサプライ・チェイン全体でのリード時間決定なら安全在庫配置モデル，短期の少数拠点の在庫方策決定なら
  確率的在庫モデル
- サプライ・チェイン全体での在庫最適化の場合：
  グラフとして描いてもらう．
- 拠点間のリード時間：
  ある在庫地点が商品の供給地点に発注してから商品が到着するまでの時間（保証リード時間，サービス時間）を指す．
  商品が到着してから出荷可能になるまでの時間（生産時間）と区別する必要がある．
- 各拠点での商品の（おおよその）価値もしくは在庫費用，もしくは原材料の調達費用と各地点での付加価値（かかった費用）
- 最終顧客への保証リード（サービス）時間
- 需要データの有無
- 需要の季節変動の有無

## 配送最適化のためのヒヤリングメモ

- デポ数， トラックの車種別の数， 顧客数，
  区別すべき荷（常温，冷蔵，冷凍など）の数
- デポならびに積み込み地点と積み下ろし地点（顧客）の情報：
  住所，緯度経度，入庫可能なトラックの種類など
- 荷の分割の可否
- 高速利用の可否
- 時間枠（指定）の数と種類
- 休憩のとりかたについてのコンセンサス
- トラックの車種別の時速設定についてのコンセンサス
- 中継地点での荷のリレーの可否
- 運転ルールの確認（なるべく右折を避ける，Uターン禁止など）

## ネットワーク設計のためのヒヤリングメモ

- サプライ・チェイン全体でのモノの移動をグラフとして描いてもらう
- 製品数，拠点（工場，倉庫，顧客）の数
- 最適化する範囲の決定
- 顧客や製品が多い場合には，集約の可否
- 地点間の費用（料率表）の確認，もしくはトンキロで近似するための過去の実績の有無を聞く
- 地点の住所，緯度経度の有無
- トラック以外の輸送手段の料率表と運行スケジュール
- リスク要因を組み込むか否か
