You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

455 lines
17 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import sys
import numpy as np
import data as gl_data
import tkinter as tk
from tkinter import *
import tkinter.filedialog # 注意次数要将文件对话框导入
import re
import inspect
from X1 import *
from X2 import *
from X3 import *
import function
import input
global Q_root,F_root
global root_window
global label1,label2,label3
def window():
global root_window
global label1, label2, label3
root_window = Tk()
root_window.title('函数拟合')
root_window.geometry('900x600') # 设置窗口大小:宽x高,注,此处不能为 "*",必须使用 "x"
# 设置主窗口的背景颜色,颜色值可以是英文单词或者颜色值的16进制数,除此之外还可以使用Tk内置的颜色常量
root_window["background"] = "white"
root_window.resizable(0, 0) # 防止用户调整尺寸
label1 = tk.Label(root_window, text="样本数据\n集文件", font=('Times', 8), bg="white",
width=13, height=3, # 设置标签内容区大小
padx=0, pady=0, borderwidth=0, )
label1.place(x=10, y=4) # 设置填充区距离、边框宽度和其样式(凹陷式)
label3 = tk.Label(root_window, text="", font=('Times', 8), bg="white", fg="black",
width=39, height=2, padx=0, pady=0, borderwidth=0, relief="ridge", highlightcolor="blue")
label3.place(x=122, y=10)
# 使用按钮控件调用函数
tk.Button(root_window, text="装载", relief=RAISED, command=lambda: askfile(label3)).place(x=370, y=12)
label2 = tk.Label(root_window, text="拟合曲线类型", font=('Times', 12), bg="white",
width=20, height=3, # 设置标签内容区大小
padx=0, pady=0, borderwidth=0, )
# 设置填充区距离、边框宽度和其样式(凹陷式)
label2.place(x=450, y=4)
gl_data.Canvas2 = tk.Canvas(root_window, bg='white', width=450, height=330)
gl_data.Canvas2.place(x=4, y=60)
# 定义一个处理文件的相关函数
def askfile(label3):
# 从本地选择一个文件,并返回文件的路径
filename = tkinter.filedialog.askopenfilename()
if filename != '':#若选中了一个文件,则对文件进行读取
label3.config(text=filename)#显示文件路径
read_sample_data(filename)#将文件读取并存到sampleData中
# print_sample_data(filename)#将文件读取并逐行输出
selfdata_show(gl_data.SampleData, gl_data.LOW, gl_data.HIGH)
else:#若未选择文件,则显示为空
label3.config(text='')
def print_sample_data(file_path):#打开对应文件
with open(file_path, 'r') as file:
for line in file:#逐行读取文件
line = line.strip('\n')#移除换行符
sx, sy = line.split(' ')#以空格分割xy
print(f'sx: {float(sx)}, sy: {float(sy)}')#将sx、sy转换为浮点数并打印
def read_sample_data(file_path):
x, y = [], []#初始化xy
with open(file_path, 'r') as file:
for line in file:#逐行读文件
line = line.strip('\n')#移除换行符
sx, sy = line.split(' ')#以空格分割xy
x.append(float(sx))#将sx转换为浮点数并加入数组
y.append(float(sy))#将sy转换为浮点数并加入数组
gl_data.SampleData = np.array(list(zip(x, y)))#xy数组转为array并赋值给全局变量
# x=np.array(x) # 列表转数组
# y=np.array(y)
# gl_data.SampleData = np.array(list(zip(x, y)))
# #################################拟合优度R^2的计算######################################
def __sst(y_no_fitting):
"""
计算SST(total sum of squares) 总平方和
:param y_no_predicted: List[int] or array[int] 待拟合的y
:return: 总平方和SST
"""
y_mean = sum(y_no_fitting) / len(y_no_fitting)
s_list =[(y - y_mean)**2 for y in y_no_fitting]
sst = sum(s_list)
return sst
def __ssr(y_fitting, y_no_fitting):
"""
计算SSR(regression sum of squares) 回归平方和
:param y_fitting: List[int] or array[int] 拟合好的y值
:param y_no_fitting: List[int] or array[int] 待拟合y值
:return: 回归平方和SSR
"""
y_mean = sum(y_no_fitting) / len(y_no_fitting)
s_list =[(y - y_mean)**2 for y in y_fitting]
ssr = sum(s_list)
return ssr
def __sse(y_fitting, y_no_fitting):
"""
计算SSE(error sum of squares) 残差平方和
:param y_fitting: List[int] or array[int] 拟合好的y值
:param y_no_fitting: List[int] or array[int] 待拟合y值
:return: 残差平方和SSE
"""
s_list = [(y_fitting[i] - y_no_fitting[i])**2 for i in range(len(y_fitting))]
sse = sum(s_list)
return sse
def goodness_of_fit(y_fitting, y_no_fitting):
"""
计算拟合优度R^2
:param y_fitting: List[int] or array[int] 拟合好的y值
:param y_no_fitting: List[int] or array[int] 待拟合y值
:return: 拟合优度R^2
"""
sse = __sse(y_fitting, y_no_fitting)
sst = __sst(y_no_fitting)
rr = 1 - sse /sst
return rr
# 简化版本的goodness_of_fit
def goodness_of_fit_easy(y_fitting, y_no_fitting):
"""
计算拟合优度R^2
:param y_fitting: List[int] or array[int] 拟合好的y值
:param y_no_fitting: List[int] or array[int] 待拟合y值
:return: 拟合优度R^2
"""
y_mean = sum(y_no_fitting) / len(y_no_fitting)
# 计算SST
sst = sum((y - y_mean) ** 2 for y in y_no_fitting)
# 计算SSE
sse = sum((y_fitting[i] - y_no_fitting[i]) ** 2 for i in range(len(y_fitting)))
# 计算R^2
r_squared = 1 - sse / sst
return r_squared
# 由于之前的函数修改了,这里需要对四个按钮的函数进行编程
def BF_generate_plot_dada(low, high):
num_samples = 25 # 设置样本点数量
sampleData = random_points(num_samples, low, high) # 生成随机样本数据25个
generate_and_plot_sample_data(sampleData, low, high)
def BF_plot_data(sampleData,low,high):
selfdata_show(sampleData,low,high)
def BF_plot_data_and_curve(curveData, sampleData,low,high):
# curveData = compute_curveData2(low,high,1,theta,m, x.mean(), x.std())
draw_dots_and_line(curveData,sampleData,low,high)
# # X4版本
def BF_fit_X4(xian_index, sampleData):
# gl_data.yvals_pow = []
print(xian_index)
cur = function.Fun[xian_index] # 装载正在选择的函数
func = cur.get_fun()#获取当前函数func
sx = sampleData[:,0]
sy = sampleData[:,1]
popt, pcov = curve_fit(func, sx, sy)# 用curve_fit来对点进行拟合
yvals_pow = func(sx, *popt)
# 这里直接套用会没有CurveData
x_values = np.arange(gl_data.LOW, gl_data.HIGH, 1)
y_values = func(x_values, *popt)
gl_data.CurveData = np.column_stack((x_values, y_values))
rr = goodness_of_fit(yvals_pow, sy)# 计算本次拟合的R*R值用于表示拟合优度
# 输出拟合后的各个参数值
ans = '\n函数系数F(x) = '
for i in range(cur.variable):
if i == 4:
ans += '\n'
if i != 0:
ans += ', '
ans += chr(ord('a') + i) + '=' + '{:.2e}'.format(popt[i]) # str(round(gl_data.popt[i], 3))
gl_data.Out = '函数形式:' + cur.name + ' '
gl_data.Out += cur.demo
gl_data.Out += ans
gl_data.Out += '\n拟合优度(R\u00b2)' + str(round(rr, 5))
show_fit_X4()# 显示函数信息到输出框
# # X4版本
# 这里是梯度下降法更新后的新的按钮函数
def BF_fit_X4_testnew(xian_index, sampleData):
# # print(xian_index)
#
# cur = function.Fun[xian_index] # 装载正在选择的函数
# func = cur.get_fun()#获取当前函数func
#
# sx = sampleData[:,0]
# sy = sampleData[:,1]
#
# popt, pcov = gradient_descent_method_X5(func, sx, sy)# 用curve_fit来对点进行拟合
#
# x_normalized = (sx - np.mean(sx)) / np.std(sx)
# yvals_pow = func(x_normalized, *popt)
#
# # print("yvals_pow", yvals_pow)
# # print("sy", sy)
#
# # 这里直接套用会没有CurveData
# x_values = np.arange(gl_data.LOW, gl_data.HIGH, 1)
# x_values_normalized = (x_values - np.mean(sx)) / np.std(sx)
# y_values = func(x_values_normalized, *popt)
# gl_data.CurveData = np.column_stack((x_values, y_values))
#
# rr = goodness_of_fit(yvals_pow, sy)# 计算本次拟合的R*R值用于表示拟合优度
# # 输出拟合后的各个参数值
# ans = '\n函数系数F(x) = '
# for i in range(cur.variable):
# if i == 4:
# ans += '\n'
# if i != 0:
# ans += ', '
# ans += chr(ord('a') + i) + '=' + '{:.2e}'.format(popt[i]) # str(round(gl_data.popt[i], 3))
# gl_data.Out = '函数形式:' + cur.name + ' '
# gl_data.Out += cur.demo
# gl_data.Out += ans
# gl_data.Out += '\n拟合优度(R\u00b2)' + str(round(rr, 5))
fit_X4(xian_index, sampleData)
show_fit_X4()# 显示函数信息到输出框
# 编程4.18 -----------------------------------------
def fitting(letters,result):
code_str = '''
def func({}):
return {}
'''.format(letters,result)
print("code_str",code_str)
return code_str
def create_func(code_str):
# 创建一个空的命名空间
namespace = {}
# 使用exec函数执行字符串代码并指定命名空间为locals
exec(code_str, globals(), namespace)
# 返回命名空间中的函数对象
return namespace['func']
# 这里由于拓展的各种函数因此使用的curve_fit
def BF_fit(root_window, screen_size, xian_index, sampleData):
fit_XX(xian_index, sampleData)
show_fit(root_window, screen_size) # 显示函数信息到输出框,包括上述信息
# 这里是因为自己的梯度下降法可以使用了所以整一个新的同时按照X4中的工作将BF_Fit修改为简单的函数调用形式而不是一大片
def BF_fit_X5_new(root_window, screen_size, xian_index, sampleData):
fit_X5(xian_index, sampleData)
show_fit(root_window, screen_size) # 显示函数信息到输出框,包括上述信息
# 编程4.18 END-----------------------------------------
def buttons():
# 随机生成样本点并显示
b_dot = tk.Button(root_window, text="生成\n数据集", relief=RAISED, bd=4, bg="white", font=("微软雅黑", 10),
command=lambda: BF_generate_plot_dada(gl_data.LOW, gl_data.HIGH))
b_dot.place(x=455, y=410)
# 显示当前样本点
b_show = tk.Button(root_window, text="显示\n数据集", relief=RAISED, bd=4, bg="white", font=("微软雅黑", 10),
command=lambda: BF_plot_data(gl_data.SampleData, gl_data.LOW, gl_data.HIGH))
b_show.place(x=510, y=410)
# 显示数据集与曲线
b_line = tk.Button(root_window, text="显示数据\n集与曲线", relief=RAISED, bd=4, bg="white", font=("微软雅黑", 10),
command=lambda: BF_plot_data_and_curve(gl_data.CurveData, gl_data.SampleData, gl_data.LOW, gl_data.HIGH))
b_line.place(x=565, y=410)
# 手动输入数据集
b_input = tk.Button(root_window, text="手动输入数据集", relief=RAISED, bd=4, bg="white", pady=7, font=("微软雅黑", 13),
command=lambda: input.input_num(root_window))
b_input.place(x=633, y=410)
# 拟合并输出拟合结果
b_fit = tk.Button(root_window, text="拟合", relief=RAISED, bd=4, bg="white", pady=7, font=("微软雅黑", 13),
command=lambda: BF_fit_X4_testnew(gl_data.Xian_index, gl_data.SampleData))
b_fit.place(x=771, y=410)
# 这里是X4版本的代码X5修改了界面
def show_fit_X4():
L = tk.Label(root_window, text='结果输出:', bg='white', font=("微软雅黑", 16)
, anchor=W)
L.place(x=20, y=480, width=100, height=30)
ans = tk.Label(root_window, text=gl_data.Out, font=("微软雅黑", 14)
, anchor=W, justify='left')
ans.place(x=120, y=480, width=760, height=100)
print(gl_data.Out)
def show_fit(root_window, screen_size):
sout = str(gl_data.Out)
ans = tk.Label(root_window, text=sout, font=("微软雅黑", 14)
, anchor=W, justify='left')
ans.place(x=int(120*screen_size), y=int(480*screen_size), width=int(760*screen_size), height=100)
print(sout)
# 拟合按钮所对应的函数
def fit_data(xian_index, sampleData):
# gl_data.yvals_pow = []
cur = function.Fun[xian_index] # 装载正在选择的函数
func = cur.get_fun()#获取当前函数func
sx, sy = sampleData[:, 0], sampleData[:, 1]
# 使用 inspect.signature 获取函数的签名信息
sig = inspect.signature(func)
# 获取所有参数的名称
params = sig.parameters
print(params)
popt, pcov = curve_fit(func, sx, sy)# 用curve_fit来对点进行拟合
yvals_pow = func(sx, *popt)
rr = goodness_of_fit(yvals_pow, sy)# 计算本次拟合的R*R值用于表示拟合优度
# 输出拟合后的各个参数值
ans = '\n函数系数:'
for i in range(cur.variable):
if i == 4:
ans += '\n'
if i != 0:
ans += ', '
ans += chr(ord('a') + i) + '=' + '{:.2e}'.format(popt[i]) # str(round(gl_data.popt[i], 3))
gl_data.Out = '函数形式:' + cur.name + ' '
gl_data.Out += cur.demo
gl_data.Out += ans
gl_data.Out += '\n拟合优度(R\u00b2)' + str(round(rr, 5))
show_fit_X4() # 显示函数信息到输出框
def change_Q_X4(no):
gl_data.Quadrant = no #更改全局变量的象限显示
if no:#若为一象限则修改显示下限为0
gl_data.LOW = 0
else:#若为四象限,则修改显示下限为-gl_data.maxV
gl_data.LOW = -gl_data.MAXV
q_button_X4()#更新象限显示面板
def q_button_X4():
r = 7.5
rr = 2.5
for widget in Q_root.winfo_children():
widget.destroy()
q_cv = tk.Canvas(Q_root, width=400, height=50)
q_cv.place(x=0, y=0)
l = tk.Label(Q_root, text='坐标轴', bd=0, font=("微软雅黑", 16)
, anchor=W)
l.place(x=20, y=0, width=80, height=50,)
# 四象限按钮
b1 = tk.Button(Q_root, text='四象限', bd=0, font=("微软雅黑", 16)
, command=lambda: change_Q(0), anchor=W)
b1.place(x=170, y=0, width=80, height=50,)
# 一象限按钮
b2 = tk.Button(Q_root, text='一象限', bd=0, font=("微软雅黑", 16)
, command=lambda: change_Q(1), anchor=W)
b2.place(x=320, y=0, width=80, height=50,)
# 绘制标记框
q_cv.create_oval(140 - r, 25 - r, 140 + r, 25 + r
, fill="white", width=1, outline="black")
q_cv.create_oval(290 - r, 25 - r, 290 + r, 25 + r
, fill="white", width=1, outline="black")
# 根据当前的象限选择值来填充标记框
if gl_data.Quadrant == 0:
q_cv.create_oval(140 - rr, 25 - rr, 140 + rr, 25 + rr
, fill="black", width=1, outline="black")
else:
q_cv.create_oval(290 - rr, 25 - rr, 290 + rr, 25 + rr
, fill="black", width=1, outline="black")
def change_f_X4(no):
gl_data.Xian_index = no#设置全局函数编号为该函数
f_button_X4()#重新绘制函数显示界面
def f_button_X4():
r = 7.5#设置按钮大小
rr = 2.5#设置按钮大小
for widget in F_root.winfo_children():
widget.destroy()#清空原有按钮
f_cv = tk.Canvas(F_root, width=400, height=330)#新建画布
f_cv.place(x=0, y=0)#放置画布
f_cv.create_rectangle(2, 2, 398, 328, fill='white', outline="#0099ff")#设置画布边框及底色
cur = 0#函数计数
for fun in function.Fit_type_library:#遍历函数库
f = function.Fit_type_library.get(fun)#获取函数具体信息
if function.Show[f[0]] == 1:# 如果show为1则显示
f_cv.create_oval(20 - r, 20 + 15 + cur * 30 - r, 20 + r, 20 + 15 + cur * 30 + r
, fill="white", width=1, outline="black")# 绘制标记框
if f[0] == gl_data.Xian_index:# 若选择为当前函数则标记
f_cv.create_oval(20 - rr, 20 + 15 + cur * 30 - rr, 20 + rr, 20 + 15 + cur * 30 + rr
, fill="black", width=1, outline="black")
# 绘制切换按钮,单击则将当前使用函数切换为该函数
button = tk.Button(F_root, text=f[2] + ' ' + f[1], bd=0, bg="white", font=("微软雅黑", 12)
, command=lambda x=f[0]: change_f_X4(x), anchor=W)
button.place(x=40, y=20 + cur * 30, width=300, height=30)
cur += 1#计数加一
def close_window():
sys.exit()
def main():
global Q_root, F_root, root_window
gl_data.X = []
gl_data.X = []
window()
function.f_read()
# 函数选择相关界面
F_root = tk.Frame(root_window, width=400, height=330, bg='white', )
F_root.place(x=490, y=60)
# 象限选择相关界面
Q_root = tk.Frame(root_window, width=400, height=50, bg='white', )
Q_root.place(x=20, y=410)
buttons()
f_button_X4()
q_button_X4()
show_fit_X4()
root_window.protocol("WM_DELETE_WINDOW", close_window)
root_window.mainloop()
if __name__ == '__main__':
main()
# X4除了没有使用自己定义的方法以外基本差不多了