【OculusGo】Duffing振動子のカオスな振舞をVR♬

前回、[LSTMでDuffing振動子の未来予測を実施した](https://qiita.com/MuAuan/items/fbb8fa208b88e1fe8473)が、今回はFlask－OculusGo連携で物体の運動にして、カオスな振る舞いをVRしてみた。

そもそもDuffing振動子は以下の参考のように初期値鋭敏性を持つ、有界な非周期運動をする。特に位相空間で見るとより鮮明である。ということでOculusGoでこの位相空間での振る舞いを見ようと思う。
【参考】
・[Duffing oscillator ](http://www.scholarpedia.org/article/Duffing_oscillator)

###やったこと
（１）Flaskで常微分方程式を解いて解を渡す
（２）OculusGoでTextで解をもらって運動に転嫁～二次元データ（ｘ、ｐ）の授受
###（１）Flaskで常微分方程式を解いて解を渡す
ここで以下二つの大きな課題がある。
①常微分方程式は与えた区間に対して解の集合を求められるが、時間経過とともに変化する解を求められるわけではない。
②[前回の時間を生成してそれを渡して運動させたとき](https://qiita.com/MuAuan/items/000c858f71379acdc8e5)は１パラメータしか渡せなかった
①について
Flask側コードは以下のとおり

```app_text.py
from flask import Flask
import time
import numpy as np
from scipy.integrate import odeint

ts=time.time()
app = Flask(__name__)
```
ここまではあまり変わらないが、import odeintしてる。

```py
def duffing(var, t, gamma, a, b, F0, omega, delta):
    x_dot = var[1]
    p_dot = -gamma * var[1] + 2 * a * var[0] - 4 * b * var[0]**3 + F0 * np.cos(omega * t + delta)
    return np.array([x_dot, p_dot])
```
関数定義は常微分方程式と同じ。
常微分方程式の解法もほとんど同じだが、計算が途中で止まることが多いので、幾つか余分なコードで回避している。
一つは、計算が早すぎるのか時刻の進みが遅いのか。。。いずれにしてもodeintのNullエラー多発のため、t1が０でないことを追記。また、何らかの理由でodeintが計算できなかったことを想定して、try-catchを導入して再計算することとした。

```py
def get_sin(t):
    # parameter
    #F0, gamma, omega, delta = 10, 0.1, np.pi/3, 1.5*np.pi
    #a, b = 1/4, 1/2
    F0, gamma, omega, delta = 2.0, 0.1, 2.4, 0            #np.pi/3, 1.5*np.pi
    a, b = 1/5, 1/2
    y0 = [0.500, 0]
    t1 = np.linspace(0, t-ts, 30)
    while len(t1)==0:
        t = time.time()
        t1 = np.linspace(0, t-ts, 30)
    try:
        sol = odeint(duffing, y0, t1, args=(gamma, a, b, F0, omega, delta))
    except:
        print('計算出来ませんでした！')
        print('t1= ',t1)
        print('y0= ',y0)
        sol = odeint(duffing, y0, t1, args=(gamma, a, b, F0, omega, delta))   
    x, p = sol.T[0], sol.T[1]
    return x,p
```
最後は二つのパラメータを渡せるように桁数固定として、
return x1[0:10]+" "+p1[0:10]
とした。
ここで解の渡し方として、x1=str(x[15])
とした。これはtが大きくなる時、真ん中の値も大きくなるということで、これを渡せば全体として運動を表すという仮説である。
   
```py
@app.route("/", methods=['GET'])
def index():
    t=time.time()
    x,p= get_sin(t)  #np.sin(t-ts)
    x1=str(x[15])
    p1=str(p[15])
    return x1[0:10]+" "+p1[0:10]

if __name__ == "__main__":
    #app.debug = True
    app.run(host="192.***.***.***")
```

###（２）OculusGoでTextで解をもらって運動に転嫁～二次元データ（ｘ、ｐ）の授受
ここもほとんど前回の時刻渡しと同じであるが、課題②を解決した。

コードは以下のとおり
まず、GetText.csでTextとして受け取る
工夫した部分は２パラメータをもらうために、桁数制限をせずに全部もらうこととしただけである。
`var strA = www.text.Substring(0, 11);`　⇒　`var strA = www.text;`
全体のコードはおまけのとおり。

一方、TextからStringを得て、ｘ、ｐに数値とするコードはおまけのとおり
ここで特に工夫した点は以下の二行で二つの変数ｘ、ｐをもらっているところ。
`float s = float.Parse(m_MyText.text.Substring(0,10));`
`float s1 = float.Parse(m_MyText.text.Substring(startIndex: 11, length: 10));`　
あとは、これまでの物体の運動と同じようなコードである。
今回は相互作用はなく、一個は位置の基準として置いた。

[OculusGoでカオスな運動を見る♬](https://youtu.be/olirGu-XBxU)
<a href="http://www.youtube.com/watch?feature=player_embedded&v=olirGu-XBxU" target="_blank"><img src="http://img.youtube.com/vi/olirGu-XBxU/0.jpg" 
 </a>
※画像をクリックするとYouTube動画につながります

###まとめ
・Flask側でDuffing振動子の常微分方程式を解いて、その解からOculusGo側で位相空間でのカオスな運動をVRした
・FlaskからUnityへ二つのパラメータ渡しができた

以下のエラー事象があるが根本的な解決に至っていない。
・通信エラー（connot connect...）が多発する
・odeint呼出しで、Nullが多発する

・ゆるい課題としてFlaskでの解をJsonファイルにして丸ごと渡して、OculusGoで再現するとより正確なシミュレーションとなるが、今回は実現できなかった。

###おまけ
```GetText.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO;

public class GetText : MonoBehaviour
{
    public Text m_MyText;
    //通信開始（http://192.***.***.***:5000/）
    public void connectionStart(string name)
    {
        string POST_URL = "http://192.***.***.***:5000/";
        WWW www = new WWW(POST_URL);
        StartCoroutine("WaitForRequest", www);
    }
    //通信の処理待ち
    private IEnumerator WaitForRequest(WWW www)
    {
        yield return www;
        connectionEnd(www);
    }
    //通信終了後の処理
    private void connectionEnd(WWW www)
    {
        //通信結果をLogで出す
        if (www.error != null)
        {
            //Debug.Log(www.error);
            var strB = www.error.Substring(0, 11);
            GetComponent<Text>().text = strB;
            textSave(strB);
        }
        else
        {
            //通信結果 -> www.text
            //Debug.Log(www.text);
            var strA = www.text;  
            GetComponent<Text>().text = strA;
            textSave(strA);
        }
    }
    // Update is called once per frame
    void Update()
    {
        connectionStart(name);
    }
    // 引数でStringを渡してやる
    public void textSave(string txt)
    {
        StreamWriter sw = new StreamWriter("LogData.txt", false); //true=追記 false=上書き
        sw.WriteLine(txt);
        sw.Flush();
        sw.Close();
    }
}
```

```MoveCube.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class MoveCube : MonoBehaviour
{
    [SerializeField]
    private GameObject obj;
    [SerializeField]
    private GameObject obj2;
    
    private float x;
    private float y;
    private float z;
    private float x1;
    private float y1;
    private float z1;
    public Text m_MyText;

    // Use this for initialization
    void Start()
    {
        fc = 1f;
        x1 = 0f;
        y1 = 0f;
        z1 = 10f;
    }
    // Update is called once per frame
    void Update()
    {
        float s = float.Parse(m_MyText.text.Substring(0,10));　
        float s1 = float.Parse(m_MyText.text.Substring(startIndex: 11, length: 10));　
        x = 2f * s;  
        y = 2f * s1;  
        z = 10f;  
        x1 = 0f * Mathf.Cos(s);
        y1 = 0f * Mathf.Sin(s1);
        z1 = 20f;  
        obj.transform.GetComponent<Rigidbody>().position = new Vector3(x, y, z);
        obj2.transform.GetComponent<Rigidbody>().position = new Vector3(x1, y1, z1);
        
    }
}
```