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

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

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

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

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

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

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

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

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

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

コード

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

# Calculation of normal depth
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 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

    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
    tv=v['textview1']
    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)
    tv.text=ss


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

以 上