cvxpyを使ってモンスターハンターライズの防具の最適化してみた

# はじめに
　モンスターハンターライズを購入して一か月ほど経ち、システムを理解し始めると、「めっちゃ爆発する装備作りたい」や「当たらなければどうということはないがしたい」とかを考えるようになったのですが、4500万通り以上（下位も含むと14億通り以上）もある防具の組み合わせから、必要なスキルが最大LVになる防具を考えるのは結構な時間がかかる。
　防具の構成を考えるのも楽しみの一つなのだが、ある程度当てを付けてくれるような物があったらいいなと思い、pythonのcvxpyを用いて、必要なスキルが最大LVになる防具の組み合わせを探すプログラムを作成した。
　コードは以下のgithubに置いておきます。
- [防具最適化プログラム](https://github.com/kaitaryu/mhrise_armor)

# 目次
<!-- タイトルとアンカー名を編集 -->
1. [制約条件と最適化条件の決定](#実装_制約条件と最適化条件の決定)
2. [使用データについて](#実装_使用データについて)
3. [コード説明](#実装_コード説明)
4. [結果](#結果)
5. [感想](#感想)

<!-- 各チャプター -->
<a id="#実装"></a>
# 実装

<a id="#実装_制約条件と最適化条件の決定"></a>
##制約条件と最適化条件の決定
 「数値最適化」というからには制約条件や最適化条件をきめないといけません。
　モンハンやってる人にとっては当たり前のようなことしか書いていませんが、当たり前のことをコンピュータは理解してくれませんので、ここをちゃんと定義しておかないと、後のデバッグで苦労します。（ちなみに、ガバガバ設定であることが、後でわかります。）

**制約条件**
1. 防具は「頭」「胴」「腕」「腰」「足」の5つあり、それぞれ1つしか装備できない。
2. スキルには最大値があり、それ以上は上がらない。
3. 装飾品は防具についているスロット以上よりも多くしてはならな。
4. 装飾品は負の数個つけられない。
**最適化条件**
1. 選択したスキルが最大LVに近くなる。

<a id="#実装_使用データについて"></a>
##使用データについて
　まず必要なものは3つ、「防具ごとのスキルレベル」「スキルごとの重み」「スキルの最大LV」「装飾品の詳細」を用意しなければなりません。
　「防具ごとのスキルレベル」は([https://altema.jp/mhrize/boguichiran](https://altema.jp/mhrize/boguichiran))こちらのサイトから情報をお借りして、横軸にスキル縦軸に防具が並んでいるスキルレベルが書かれた数列[armor.csv](https://github.com/kaitaryu/mhrise_armor/blob/main/data/armor.csv)を作成します。(装飾品Lvは正確にはスキルではないですが、のちの計算を楽にするため、今回はスキルとして計算します。)
　「スキルごとの重み」は正直わからないので、とりあえず、スキルのMAXに到達すれば価値が1となるように、[weight.csv](https://github.com/kaitaryu/mhrise_armor/blob/main/data/weight.csv)を作成。
　重みを付けないと、取りやすく最大lvの大きいスキル(攻撃や防御など)ばかりついて、ほかのスキルがつかないなんてことになりますので、この重みはかなり重要です。
　「スキルの最大LV」は([https://game8.jp/mhrise/363848](https://game8.jp/mhrise/363848))こちらのサイトから情報をお借りして、[maxlv.csv](https://github.com/kaitaryu/mhrise_armor/blob/main/data/maxlv.csv)を作成。
　「装飾品の詳細」は([https://game8.jp/mhrise/363846](https://game8.jp/mhrise/363846))こちらのサイトから情報をお借りして、[ornaments.csv](https://github.com/kaitaryu/mhrise_armor/blob/main/data/ornaments.csv)を作成。
　後は[cvxpy](https://www.cvxpy.org/)の説明を見ながら、pythonでコードを書いていく。
<a id="#実装_コード説明"></a>
##コード説明
　[github](https://github.com/kaitaryu/mhrise_armor)に上げているコードの計算部分(armor_saiteki)について説明します。

```ruby:main.py
    #どの防具を装備するかを決定する変数
    atama_variable = cp.Variable(atama_size[1],boolean = True)
    dou_variable = cp.Variable(dou_size[1],boolean = True)
    ude_variable = cp.Variable(ude_size[1],boolean = True)
    kosi_variable = cp.Variable(kosi_size[1],boolean = True)
    asi_variable = cp.Variable(asi_size[1],boolean = True)
    #装飾品を装備する個数を表す変数
    ornaments_variable = cp.Variable(ornaments_list.shape[1], integer=True)
```

　不要な範囲が表現できないように、防具は装備したかしていないかを1ビットで表現するため、 booleanの設定をTrueとしておき、装飾品は複数装備可能にするため、整数が設定できるようにintegerの設定をTrueとしておきます。
　設定しないと、デフォルトではfloatが設定されて、「装飾品を0.5個装備する」みたいな変な結果になります。

```ruby:main.py
    #装備したことによって増える、装備ごとのスキル
    atama_ski = atama_list@atama_variable
    dou_ski = dou_list@dou_variable
    ude_ski = ude_list@ude_variable
    kosi_ski = kosi_list@kosi_variable
    asi_ski = asi_list@asi_variable
    #装飾品を装備したことによって増えたスキル
    ornamentslv1_ski = cp.multiply(ornaments_list[0],ornaments_variable)
    ornamentslv2_ski = cp.multiply(ornaments_list[1],ornaments_variable)
    ornamentslv3_ski = cp.multiply(ornaments_list[2],ornaments_variable)
    ornamentslv1_count = ornaments_list[0] @ ornaments_variable
    ornamentslv2_count = ornaments_list[1] @ ornaments_variable
    ornamentslv3_count = ornaments_list[2] @ ornaments_variable
```

　スキルlvの計算を行っている部分です。
　ここで書かれている「@」は行列同士の積を計算しており、「cp.multiply」は「要素の乗算」を計算しています。
　私の赤ちゃん語彙力で説明しても混乱を招くだけなので、([https://www.cvxpy.org/tutorial/functions/index.html](https://www.cvxpy.org/tutorial/functions/index.html))こちらを参照してください。

```ruby:main.py
    #装備したことによって増えるスキルの合計
    tmp_1 = (atama_ski + dou_ski + ude_ski + kosi_ski + asi_ski)
    sum_ski = tmp_1[3:]
    sum_ski = sum_ski+ornamentslv1_ski + ornamentslv2_ski + ornamentslv3_ski
    #装備につけられる装飾品の数を計算する。
    max_ornaments = tmp_1[:3]
    
    weight_tmp1 =  sum_ski @ weight_list
    
```

　防具と装飾品を装備したことによって、取得したスキルの合計を計算しているところです。
　装飾品Lvをスキルレベルと一緒に計算しているので、前3つの装飾品Lv部分は、計算に入れません。
　計算したスキルの合計に[weight.csv](https://github.com/kaitaryu/mhrise_armor/blob/main/data/weight.csv)から読み込んだ重みをつけてあげます。

```ruby:main.py
    #選択したスキルを最大化する
    for i in select_skill_list:
        if tmp_2 is None:
            
            tmp_2 = sum_ski[i] * weight_list[i]

        else:
            tmp_2 += sum_ski[i] * weight_list[i]
            

    if not(tmp_2 is None):
        objective = cp.Maximize(cp.sum(tmp_2))
    else:
        objective = cp.Maximize(cp.sum(weight_tmp1))

    constraints = [
        #防具は複数装備できない。
        cp.sum(atama_variable) <= 1,
        cp.sum(dou_variable) <= 1,
        cp.sum(ude_variable) <= 1,
        cp.sum(kosi_variable) <= 1,
        cp.sum(asi_variable) <= 1,
        cp.sum(atama_variable) >= 1,
        cp.sum(dou_variable) >= 1,
        cp.sum(ude_variable) >= 1,
        cp.sum(kosi_variable) >= 1,
        cp.sum(asi_variable) >= 1,
        #スキルの最大値を超えないようにする
        sum_ski <= maxlv_list,
        #装飾品は装備よりも多くしてはならな
        ornamentslv1_count <= max_ornaments[0],
        ornamentslv2_count <= max_ornaments[1],
        ornamentslv3_count <= max_ornaments[2],
        #装飾品は負の数個つけられない
        ornaments_variable >= 0
        ]
    prob = cp.Problem(objective, constraints)

    # The optimal objective value is returned by `prob.solve()`.
    result = prob.solve()
    
    return atama_variable.value,dou_variable.value,ude_variable.value,kosi_variable.value,asi_variable.value,ornaments_variable.value

```

　cp.Maximizeが最適化条件であり、コード内では、スキルを選択していた時と、選択していなかったときで分岐しています。
　constraintsに代入しているlistが制約条件です。
　"cp.sum(atama_variable) == 1"とすればいいと思うのですが、なぜかerrorが出て止まるので、こんなみっともないプログラムになっています。
　prob.solve()を実行することで、設定した制約条件と最適化条件に適した結果を計算してくれて、結果は最初にcp.Variableを使って設定した変数に書き込まれているはずです。(複雑だと解が求まらない場合もありますが、今回はうまくいきました。)


<a id="#結果"></a>
# 結果
　とりあえず「防御,ガード強化,ガード性能,回復速度」のスキルを最大化して絶対に乙しない装備を探す。
　実際にランスと一緒に装備すると素の防御力541で危険度3モンスター程度の攻撃ならほぼ無傷、ダメージ食らえば赤部分は即回復し、盾で毒ガスを防御する超人が誕生。(ただし、火力がないので討伐に時間がかかる模様）
　回復笛の方とご一緒させていただいたときは、異能生存体になった気分を味わえました。
　でも、防御力を考慮していないし、たぶん、装飾の空きを付けるような装備も、ちゃんと条件を設定したらできるはずなので、ほかにさらに強い装備があるかも？
![カチカチやぞ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1033231/fac7343a-591c-b53e-2ef9-99330642ea76.png)
　
<a id="#感想"></a>
# 感想

　今回はこんなことにcvxpyを使いましたが、やろうと思えば「勤務表の自動作成」「スーパーの仕入れの最適化」など世の中の役に立つことが、式さえちゃんと考えれば、後は勝手に計算してくれるので、便利になったなと思う反面、内部でやってることを理解しないまま、使い続けるのも嫌なので、本格的に、最適化問題あたりの勉強を始めたい。
　「防御が入ってない」など、かなりガバガバ設定なので、時間があれば修正したいと思います。（時間がなければやらないということ）
　「Githubに上げとったら誰かがやってくれるやろ」という軽い気持ちでGithubを使ったが、Githubのリポジトリも初めて作成するので、設定方法やGithubを用いた今後の開発方法も全く知らない。
　まだまだ勉強することは多いなと感じさせられました。

<a id="#reference"></a>
# 参考文献
- [Welcome to CVXPY 1.1](https://www.cvxpy.org/)
- [Atomic Functions](https://www.cvxpy.org/tutorial/functions/index.html)
- [マクドナルドで一日分の栄養を取れる組み合わせを計算したら衝撃の結果に](https://qiita.com/takobaya391076/items/49b15c1fa36734b3fa53)
- [ナース・スケジューリング](https://www.amazon.co.jp/dp/B07B4S2GMG/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1) 
- [防具一覧](https://altema.jp/mhrize/boguichiran)
- [【モンハンライズ】スキルの一覧と変更点｜仕様解説【MHRise】](https://game8.jp/mhrise/363848)