Python: Pythonista 3 on iPadでUIを使ってみる

## はじめに
最近出たばかりの、iPad Air 4 を買った。使用目的は主に手書きメモ作成なのだが、計算しながらメモを作成する場合も多い。手書きメモといってもテキストと数字だけなら、わざわざ手書きにしなくても、さっさとテキストエディタで打ち込めばいいのだが、説明図が必要となると、やはり手書きが便利である。計算が必要な場合には、これまでは iMac で Python を立ち上げて計算させながら iPad で手書きスケッチやメモを行っていたが、iPad があるのに iMac を立ち上げるのもなんかかっこ悪い、というかスマートでない。
そこで、最近はご無沙汰であったが、iPad mini 4 用に購入していた、Pythonista 3 を使ってみることにした。

Pythonista 3 は、numpy および matplotlib を含んでおり、これらはほぼ間違いなく動く。scipy や pandas など、その他のライブラリは使えないと思ったほうが良い。このため、複雑な計算でも numpy と matplotlib のみを使っているコードなら実用性がある。また GUI も使えるらしいのだが、これまで自分で使ったことはないので、使用頻度の高い簡単な計算プログラムを、アプリ風にして iPad で使えるようにしてみた。（要はiPad Air 4を買って嬉しいので、これを使っていろいろなことをやろうという趣旨である）

## やっていること
やっていることは Mac に入れている「急勾配水路の等水深を求めるプログラム」を Pythonista 用に書き換えて GUI で動くようにしたものである。
もとのコードは以下の「はてな」に投稿したものである。

[https://damyarou.hatenablog.com/entry/2020/10/23/073643](https://damyarou.hatenablog.com/entry/2020/10/23/073643)

このプログラムは非線形方程式を解くため、Mac 用プログラムでは scipy を使っているが、Pythonista では scipy は使えないため、非線形方程式を解く二分法の部分は自前で書き換えている。
更に、ここからが自分にとって初めての挑戦であるが、アプリ風に GUI を使って仕上げてみた。

実行結果は以下の写真のようになる。上側４個の四角にデータを入力し、```Execute``` というボタンを押すと結果が下側四角の中に示される。

![IMG_0004.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129300/c5f8e467-f426-6ce8-62d2-74785436fd22.jpeg)


プログラム作成に当たり以下のサイトを参考にさせていただいた。

+ [https://jps-code.com/pythonista_ipad](https://jps-code.com/pythonista_ipad)

+ [https://mmm-ssss.com/2020/02/28/%E3%80%90pythonista3%E3%80%91python%E3%81%A7ios%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA2ui%E3%83%9C%E3%82%BF%E3%83%B3%E2%91%A0/](https://mmm-ssss.com/2020/02/28/%E3%80%90pythonista3%E3%80%91python%E3%81%A7ios%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA2ui%E3%83%9C%E3%82%BF%E3%83%B3%E2%91%A0/)

+ [https://omz-software.com/pythonista/docs/ios/ui.html](https://omz-software.com/pythonista/docs/ios/ui.html)

Pythonista 3 では、UI プログラミングを指定すると、２つのファイルができる。下の写真では。```py_uniflow.py``` と ```py_uniflow.pyui``` というファイルが確認できる。「xxx.py」はコードを書き込むプログラムで、「xxx.pyui」は、UI の配置や特性を定義するファイルである。

![IMG_0003.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129300/fa7c0322-185c-747b-9553-1829bcdcd660.jpeg)

配置できるオブジェクトの一覧は、UIの配置と特性を定義する ```py_uniflow.pyui``` 上で、左上の四角で囲まれた＋マークを押すと表示されるので、そこから使いたいものを選択する。

![IMG_0007.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129300/8db6cc02-65bd-f987-bc68-b37fb830ab42.jpeg)

このプログラムでは、以下の４種類のオブジェクトを使っている。

1. label
2. textfield
3. button
4. textview

```label``` と ```buttan``` については、```py_uniflow.pyui``` の中で行っている、特性値指定箇所の写真を示しておく。
```textfield``` と ```textview``` は、位置と大きさを調整しているだけなので、写真は省略。

![IMG_0005.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129300/050b4847-d387-186d-1498-0d8ea9c0e10e.jpeg)

![IMG_0006.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129300/52a2c7b4-adb0-0a9e-2048-3af40521b09b.jpeg)


こんな感じでプログラムをiPad上で仕上げていく。


## コード

```py_uniflow.py``` のコード全文は以下の通り。関数 ```def click_button``` の中に主な処理は全て詰め込んである。


```python
# Calculation of normal depth
# (amended on 2022.05.15)
import ui
import numpy as np


def cal_hc(q,b,cs):
    # critical depth
    g=9.8
    hc=(q**2/g/b**2/cs)**(1/3)
    return hc


def func(h,q,b,n,sn):
    f=q-b*h/n*(b*h/(b+2*h))**(2/3)*sn**(1/2)    
    return f    


def bisection(q,b,n,sn,ha,hb):
    for k in range(100):
        hi=0.5*(ha+hb)
        fa=func(ha,q,b,n,sn)
        fb=func(hb,q,b,n,sn)
        fi=func(hi,q,b,n,sn)
        if fa*fi<0: hb=hi
        if fb*fi<0: ha=hi
        #print(fa,fi,fb)
        if np.abs(hb-ha)<1e-10: break
    return hi


def calc(q,b,n,i):
    theta=np.arctan(i)
    sn=np.sin(theta)
    cs=np.cos(theta)

    ha=0.0 # lower initial value for bisection method
    hb=10.0 # upper initial value for bisection method
    hn=bisection(q,b,n,sn,ha,hb)
    vn=q/b/hn
    hc=cal_hc(q,b,cs)

    # hn: normal depth
    # vn: flow velocity 
    # hc: critical depth
    ss='ang={0:.3f} deg.\n'.format(np.degrees(theta))
    ss=ss+'hn={0:.3f} m\n'.format(hn)
    ss=ss+'vn={0:.3f} m/s\n'.format(vn)   
    ss=ss+'hc={0:.3f} m'.format(hc)
    return ss


def click_button(sender):
    tf1=v['textfield1']
    tf2=v['textfield2']  
    tf3=v['textfield3']
    tf4=v['textfield4']
    q=float(tf1.text) # discharge
    b=float(tf2.text) # channel width
    n=float(tf3.text) # Manning's roughness coefficient
    i=float(tf4.text) # invert gradient
    ss=calc(q,b,n,i)
    v['textview1'].text=ss


v = ui.load_view()
v.name='Uniform flow'
v.present('sheet')
```

以　上