Python(tkinterで平方根の近似解を求めるGUIツール)

今回、平方根の近似解を求めるアルゴリズムとして二分法ニュートン法を用いました。
まずはIDLEでそれぞれをプログラムし、実行すると以下のような結果になります。

アルゴリズムの解説はこちら
ニュートン法で平方根を求める

アルゴリズム選択式、平方根の計算ツール

今回はtkinterを用いたGUIツールとして、アルゴリズムの選択をラジオボタンで行い、表示桁指定と求める平方根をエントリーボックスで入力して、ボタン実行できるようにプログラミングを行いました。

import tkinter as tk
"""
# 二分法での平方根計算
def my_sqrt(x,n=10):
    low,high = 0.0,max(1.0,x)
    for i in range(1,n+1):
        ans=(high+low)/2.0
        print(f"{i}回目の推定値: {ans}")
        if ans**2 < x:
            low = ans
        else:
            high = ans
    return ans

# ニュートン・ラフソン法での平方根計算
def my_sqrt2(x,n=10):
    ans = 1.0
    for i in range(1,n+1):
        ans = ans - (ans**2-x)/(2*ans)
        print(f"{i}回目の推定値: {ans}")
    return ans
"""    

class App(tk.Frame):
    def __init__(self,master=None):
        super().__init__(master)
        self.master.title("平方根の推定値計算機")
        self.master.geometry("350x180")
        self.i = 1   # 処理回数
        self.pre = 0 # エントリーの値(比較用)
        self.low = 0 # 二分法での計算用数1
        self.max = 1 # 二分法での計算用数2 かつ ニュートン法での計算用数
        self.create_widgets()      

    def create_widgets(self):
        tk.Label(self, text="平方根の推定").pack()        

        f1=tk.Frame(self)
        f1.pack()
        # ラジオボタンの設置
        self.val = tk.IntVar()
        self.val.set(0)
        tk.Radiobutton(f1,text = '二分法', variable = self.val, value = 0).pack(side="left")        
        tk.Radiobutton(f1,text = 'ニュートン法', variable = self.val, value = 1).pack(side="left")        

        f2=tk.Frame(self)
        f2.pack()
        tk.Label(f2,text="表示桁数").grid(row=0,column=0)
        self.e1 = tk.Entry(f2)
        self.e1.insert(tk.END,"5")
        self.e1.grid(row=1,column=0,padx=5,)
        tk.Label(f2,text="平方根の数").grid(row=0,column=1)
        self.e2 = tk.Entry(f2)
        self.e2.grid(row=1,column=1,padx=5)

        btn=tk.Button(self,text="推定",command=self.echo)
        btn.pack(ipadx=50,pady=10)
        self.result_label = tk.Label(self,text="正の数を入力して、ボタンを押してください")
        self.result_label.pack()

    def echo(self):
        if self.val.get() == 0:
            func = self.my_sqrt
        else:
            func = self.my_sqrt2
        return func()

    def my_sqrt(self):        
        try:        
            v=int(self.e1.get())
            x=float(self.e2.get())
            if v>0 and x>0:
                if x == self.pre:
                    self.i += 1
                else:
                    self.pre = x
                    self.i = 1
                    self.low,self.high = 0.0,max(1.0,x)
                ans=(self.high+self.low)/2.0
                mess = f"{self.i}"+"回目の推定値: %.*f"%(v,ans)
                if ans**2 < x:
                    self.low = ans
                else:
                    self.high = ans

            elif v<0 or x<0:
                mess = "不正な形式です。"        
        except:
            mess = "不正な形式です。"

        self.result_label.config(text=mess)

    def my_sqrt2(self):        
        try:        
            v=int(self.e1.get())
            x=float(self.e2.get())
            if v>0 and x>0:
                if x == self.pre:
                    self.i += 1
                else:
                    self.pre = x
                    self.i = 1
                    self.max = 1
                self.max = self.max - (self.max**2-x) / (2*self.max)
                mess = f"{self.i}"+"回目の推定値: %.*f"%(v,self.max)

            elif v<0 or x<0:
                mess = "不正な形式です。"        
        except:
            mess = "不正な形式です。"

        self.result_label.config(text=mess)

if __name__ == '__main__':
    app = App()
    app.pack()
    app.mainloop()

ボタンのcommandオプションにラジオボックスのチェックを確認するコールバック関数を設定し、二分法またはニュートン法で実行結果を表示するようにしています。
また、表示桁数で小数点以下は "%.*f" %(v,ans) のように文字列を記述し、タプルで桁数と値を渡すことで設定できます。

内部クラスを設定した方法

import tkinter as tk

class Application(tk.Frame):
    class Info(): # 計算途中の値を貯めておく「構造体」的用法の内部クラス
        def __init__(self,x):
            self.x = x  # 平方根を求めたい値
            self.i = 1  # 回数
            self.low, self.high = 0.0, max(1.0, x)

    def __init__(self, master):
        super().__init__(master)
        self.create_widgets()
        self.pack()
        self.info = None

    def create_widgets(self):
        self.entry = tk.Entry(self)
        self.entry.pack()
        self.btn = tk.Button(self, text='推定', command=self.estimate)
        self.btn.pack()
        self.result_label = tk.Label(self)
        self.result_label.pack()

    def estimate(self):
        s = self.entry.get()
        try:
            x = float(s)
        except:
            self.result_label.configure(text=f'{s}は不正な入力値です。')
            return

        if self.info is None or self.info.x != x:
            self.info = self.Info(x)

        info = self.info

        ans = (info.high+info.low)/2.0
        if ans**2 < x:
            info.low = ans
        else:
            info.high = ans

        self.result_label.configure(text=f'{info.i}回目: {ans}',fg='black')
        info.i += 1

root = tk.Tk()
root.title('平方根の推定')
root.geometry('200x80')
app = Application(root)
app.mainloop()

このプログラムの元ネタはこちら
GUI: 平方根の近似解

計算処理も内部クラス側に定義することで、より洗練されたものになりそうです。

<2019/12/03追記>
計算処理を内部クラスに持たせ、特殊メソッドを定義することで、文字列を返すようにしました。

import tkinter as tk

class Application(tk.Frame):
    class Info(): # 計算途中の値を貯めておく「構造体」的用法の内部クラス
        def __init__(self,x):
            self.x = x  # 平方根を求めたい値
            self.i = 0  # 回数
            self.low, self.high = 0.0, max(1.0, x)

        def __str__(self):
            i,ans = self.my_sqrt()
            return f'{i}回目: {ans}'

        def my_sqrt(self):
            ans = (self.high+self.low)*0.5

            if ans**2 < self.x:
                self.low = ans
            else:
                self.high = ans

            self.i += 1
            return (self.i,ans)

    def __init__(self, master):
        super().__init__(master)
        self.create_widgets()
        self.pack()
        self.info = None

    def create_widgets(self):
        self.entry = tk.Entry(self)
        self.entry.pack()
        self.btn = tk.Button(self, text='推定', command=self.estimate)
        self.btn.pack()
        self.result_label = tk.Label(self)
        self.result_label.pack()

    def estimate(self):
        s = self.entry.get()
        try:
            x = float(s)
            if self.info is None or self.info.x != x:
                self.info = self.Info(x)
            self.result_label.configure(text=str(self.info))
        except:
            self.result_label.configure(text=f'{s}は不正な入力値です。')

root = tk.Tk()
root.title('平方根の推定')
root.geometry('200x80')
app = Application(root)
app.mainloop()
おまけ

Pythonを学習し始めた頃に近似値を用いた二分法での平方根を求めるプログラミングを行いました。
過去の投稿記事に載せていなかったようなので、ここに掲載しておきます。

def CheckPositiveNum(num):
    if num<0:
        raise Exception("Error")

# _関数内処理用のメンバ関数
def _Eq(x,y,prec):
    """num1:比較する数1 と num2:比較する数2が近似かどうかを確認する関数内処理用関数
       prec:精度の小数桁数(デフォルトは1/1000,1e-10より小さい場合は1e-10とする)
       返り値 true:近似している false:近似していない""" 
    return abs(x-y) < prec

def _FixPrec(prec):
    """precが有効かどうかを確認する関数内処理用関数
       prec:精度の小数桁数(デフォルトは1/1000,1e-10より小さい場合は1e-10とする)
       返り値 prec:精度""" 
    if prec < 1e-10:
        print("invalid:",prec)
        prec=1e-10

    elif prec > 1e-3:
        print("invalid:",prec)
        prec=1e-3

    return prec

def CheckEqual(num1,num2,*,prec=1e-3):
    """num1:比較する数1 と num2:比較する数2が近似かどうかを確認する関数
       prec:精度の小数桁数(デフォルトは1/1000,1e-10より小さい場合は1e-10とする)
       返り値 true:近似している false:近似していない"""    
    return _Eq(num1,num2,_FixPrec(prec))

def MySqrt(num1,num2,*,prec=1e-3):
    """num2:求めたい平方根の数 を num1:予測する平方根の値 から計算する関数
       prec:精度の小数桁数(デフォルトは1/1000,1e-10より小さい場合は1e-10とする)
       返り値 i:近似する平方根の値"""

    def Estimate(num1,num2):
        """平方根の近似値を計算する関数
           @param num1 予測する平方根の値
           @param num2 求めたい平方根の数
           @retval     予測する平方根の近似値"""
        return (num1 + num2 / num1) * 0.5

    n=_FixPrec(prec)       

    i = num1    
    while not _Eq(i * i, num2,n):
        i = Estimate(i,num2)
    return i

x = float(input("求めたい平方根の数字を正の数で入力してください x:"))

CheckPositiveNum(x)

y = float(input("予想する平方根の値を入力してください y:"))
precision = float(input("精度の値(1e-3~1e-10)を入力してください prec:"))

print("求める平方根の近似値は:" + str(MySqrt(y,x,prec=precision)))
print('end')

今なら、よりキレイに同様の処理が書けそうですw

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください