pythonのプログラムをrubyに書き換えてWebアプリケーションに拡張してみた（ガソリン代シミュレータ））



## 1. はじめに

今日は僕のアウトプット仲間の@YukihiroNISHIYAMAくんが面白いことしていたので、それ関連のアウトプットです。　
（GWのミニミニ開発）

@YukihiroNISHIYAMAくんが、1年間に使用するガソリン代のシミュレーションをpythonでしています。
[ケチサイエンティストへの道〜Python3で自分のバイクの最適給油タイミングを求めよう〜 ①『自分の給油行動をモデリングしてみた』](https://qiita.com/YukihiroNISHIYAMA/items/2ed5154ee23260a603ee)

僕自身、現在はWebエンジニアとして働き始めたので、プライベートでもWeb開発の勉強をしてます。そこでガソリン代のシミュレーションプログラムをWebアプリケーションとして拡張してみました。


## 2. シミュレーションプログラムからアプリケーション開発へ
上記のシミュレーションプログラムで十分面白いのですが、

- @YukihiroNISHIYAMAくん自身のバイクの使い方におけるシミュレーションである
- 人によってタンク容量や出かける確率、距離は異なる

という点がWebアプリケーション的には不十分と感じたので、それを含め勉強がてら拡張してみました。

## 3. 成果物

### 開発環境

- Ruby on Rails
    - Ruby 2.5.3
    - Rails 5.2.3
- Heroku
    

作成したアプリケーションはこちらです。
[ガソリン代シミュレーションアプリ](https://gas-simulater.herokuapp.com)

コードはこちらに。
https://github.com/MoriTomo7315/gasorinsim


## 4. アプリケーション全体像

ブラックボックス的にはこんな感じ。

![gasorin_bbox.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335866/749babf9-e586-7742-b98b-6b22fcccb830.png)

Ruby on Railsで開発したので、簡単にMVC(Model View Controller)モデルを説明します。

![MVC_gasorin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335866/f329613f-0ef4-dd51-68ac-1ce27ec152a7.png)

### Model
今回はシミュレーションに必要なデータを担ってもらっています。

Simsettingモデルが持つ属性は、

- 車体の情報
    - タンク容量
    - 燃費
    - メータがどれくらいになったらガソリンを給油するか
- 乗り方の情報
    - 平日の平均走行距離
    - 休日の平均走行距離
    - 休日に出かける確率

としています。

### View
Viewは自分が使っている車体や乗り方の情報を入力したり、シミュレーション結果を確認するためのものです。

### Controller
ControllerはViewで入力されたModelに関するデータを扱います。
今回はシミュレーションで使用するために、データを変換したり、実際にシミュレーションの計算をおこなうために用います。


## 5. rubyによるガソリン給油のモデリング
モデリング自体は、@YukihiroNISHIYAMAくんのものをそのまま使います。
変更点としては、

- 上記６つの情報を自分の状況に合わせて入力できること
- ガソリンの価格設定の変更（140.0〜148.0円）
    - 最近の感覚。140円を下回ることもなければ、150円を超えることもない気がする
    
上記二つ以外の、価格の変動モデル等は特に変更はしていません。

シミュレーションのプログラムは`pages_controller.rb`の`simulate_result`アクションが担っています。

**simulate.htmlからのページ遷移と同時に、数値計算して結果をsimulate_result.htmlに渡してしまおうというシンプルな作戦です。**

pythonプログラムをrubyで書き換えたものは次の通りです。
クラス変数（@@）でがんばってデータを保存している理由はフォームで入力した情報を二つ先の遷移先画面でも使いたかったからです。
**他にいい方法があるならご教授お願いします。**

```ruby:pages_controller.rb
class PagesController < ApplicationController

  # タンク容量、給油するときのメータ位置、燃費
  @@params_temp_vehicle = {fuel_capa: 0, fuel_per1km: 0 ,fuel_threshold: 0}
  # 平日の走行距離、休日の走行距離
  @@params_temp_distance = {dist_univ: 0, dist_holiday: 0}
  # 休日にお出かけする確率（:probability属性に格納予定）
  @@params_temp_probability = 0

  def index
    @simsetting = Simsetting.new
  end

  def simulate

    case params[:simsetting][:fuel_threshold]
    when "半分以上" then
      @@params_temp_vehicle[:fuel_threshold] = 0.6
    when "半分切ったら" then
      @@params_temp_vehicle[:fuel_threshold] = 0.4
    when "少なくなったら" then
      @@params_temp_vehicle[:fuel_threshold] = 0.3
    else
      @@params_temp_vehicle[:fuel_threshold] = 0.1
    end

    case params[:simsetting][:probability]
    when "ほぼほぼ" then
      @@params_temp_probability = 0.8
    when "半々" then
      @@params_temp_probability = 0.5
    when "気が向いたら" then
      @@params_temp_probability = 0.3
    else
      @@params_temp_probability = 0.1
    end


    @@params_temp_vehicle = {fuel_capa: params[:simsetting][:fuel_capa], 
                            fuel_per1km: params[:simsetting][:fuel_per1km]}
    
    @@params_temp_distance = {dist_univ: params[:simsetting][:dist_univ],
                              dist_holiday: params[:simsetting][:dist_holiday]}

    @simsetting = Simsetting.new(fuel_capa: params[:simsetting][:fuel_capa], 
                                fuel_per1km: params[:simsetting][:fuel_per1km], 
                                fuel_threshold: params[:simsetting][:fuel_threshold],
                                dist_univ: params[:simsetting][:dist_univ],
                                dist_holiday: params[:simsetting][:dist_holiday],
                                probability: params[:simsetting][:probability]
                                )
  end

  def simulate_result
    simulate_time        = (1..365).to_a
    normal_random        = RandomBell.new
    distance_dist_normal = @@params_temp_distance[:dist_univ].to_f
    distance_dist_holi   = RandomBell.new(mu: @@params_temp_distance[:dist_holiday].to_f, sigma: (@@params_temp_distance[:dist_holiday].to_f)/2)
    fuel_tank            = 0
    fuel_capa            = @@params_temp_vehicle[:fuel_capa].to_f
    fuel_per1km          = @@params_temp_vehicle[:fuel_per1km].to_f
    fuel_threshold       = fuel_tank * @@params_temp_vehicle[:fuel_threshold].to_f
    fueling_litter       = 0 # 給油量
    total_amountOfrefuel = 0 # 総給油量
    refueling_times      = 0 # 給油回数
    running_distance     = 0 # お出かけ距離
    total_running        = 0 # 総距離
    total_cost           = 0 # 総コスト


    gas_price_array = [ 140.0, 142.0, 144.0, 146.0, 148.0 ]

    simulate_time.each do |i|

      # - - - - 値段設定 - - - - - - - 
      if i == 0
        gas_price = gas_price_array[Random.rand(5)]
      else
        if Random.rand(100)/100 < 0.2
          gas_price = gas_price_array[Random.rand(5)]
        end
      end

      gas_price += normal_random.rand
      # - - - - - - - -  - - - - - - -

      # - - 車両のメータチェック - - - -
      if fuel_tank < fuel_threshold
        fueling_litter = fuel_capa - fuel_tank
        total_amountOfrefuel += fueling_litter
        total_cost += gas_price * fueling_litter
        fuel_tank = fuel_capa
        refueling_times += 1
      end
      # - - - - - - - - - - - - - - - 
      
      # iが6,7ならば，土曜日, 日曜日であると仮定
      if  i % 6 == 0 || i % 7 == 0
        if Random.rand(100)/100 < @@params_temp_probability
          # お出かけの距離
          running_distance = distance_dist_holi.rand
          # 距離が正になるよう調整
          while running_distance <= 0
              running_distance = distance_dist_holi.rand
          end

        total_running += 2 * running_distance
        fuel_tank -= (1.0/fuel_per1km) * 2 * running_distance
        end
      else # 平日
        total_running += distance_dist_normal
        fuel_tank -= (1.0/fuel_per1km) * distance_dist_normal
      end
    end
    @totalCost = total_cost
    @totalRunning = total_running
    @refuelingTimes = refueling_times
    @totalAmountRefuel = total_amountOfrefuel
  end
end

```

### 給油タイミングのモデリング
車を想定してもらうとわかりやすいと思いますが、シミュレーションの際にはガソリンのメータがどこらへんの位置にきたら給油するか選択をしてもらいます。

そしてそのメータの目安に応じて、残油がタンクの何割になったら給油するか決めることにしています。

|選択項目|タンクの割合|
|:-----:|--------:|
|半分以上|0.6|
|半分切ったら|0.4|
|少なくなったら|0.3|
|ランプがついたら|0.1|

### 休日行動のモデリング
単純なモデリングになってしまいましたが、みんながみんな休日におでかけをするとは限らないです。

そのため、給油タイミングと同様に目安の頻度をシミュレーションの際に選択してもらいます。

|選択項目|タンクの割合|
|:-----:|--------:|
|ほぼほぼ|0.8|
|半々|0.5|
|気が向いたら|0.3|
|全然出かけない|0.1|

休日のおでかけする距離に関しては、@YukihiroNISHIYAMAくん同様に変動をさせるため正規分布に従うものとさせています。（平日は固定としています。）


## 6. シミュレーション結果

僕が学生の頃、通学に使っていたデミオちゃんで試してみます。

<img width="778" alt="Screen Shot 2019-05-04 at 23.14.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335866/905ec6e6-b113-2e6a-b187-f2a6a97a0d54.png">


1ヶ月に2万円ほどガソリン代を使用していたので、まあまあ妥当な結果が得られました。

<img width="749" alt="Screen Shot 2019-05-04 at 23.24.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335866/13df54f1-4058-4a9b-7f27-bc9b81f2a806.png">



## 7. おわりに

今回はGWのミニミニ開発の一環でRuby on Railsの勉強がてらガソリン代のシミュレーションアプリケーションを作成してみました。

Ruby on Rails である必要はあるか？？と言われると否めない点はあります。

ですが、さくっと作れてしまうことは確かです。

@YukihiroNISHIYAMAくんのようにグラフで可視化等はまだ試みていないのでまたやってみたいと思います。

またガソリン代についても地域によってやガソリンスタンドの店舗ごとで値段も変わってくると思うので、

NAVITIMEがいい感じのガソリンスタンドAPIを提供しているので、より現実的なシミュレータにしていけたらと思います。
http://ntts.navitime.jp/api/v3/detail/gasstation

( gogo.gsもあったがapiの提供は終了しているらしい。）