import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Dense, Lambda
from tensorflow.keras.optimizers import Adam
from sklearn.preprocessing import MinMaxScaler
import os
import time
mpl.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'Arial Unicode MS']
mpl.rcParams['axes.unicode_minus'] = False # 关键修复:使用 ASCII 减号
# 设置中文字体支持
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
class PINNModel(tf.keras.Model):
def __init__(self, num_layers=4, hidden_units=32, **kwargs):
super(PINNModel, self).__init__(**kwargs)
self.dense_layers = [Dense(hidden_units, activation='tanh')
for _ in range(num_layers)]
self.final_layer = Dense(1, activation='linear')
# 添加带约束的物理参数
self.k_raw = tf.Variable(0.01, trainable=True, dtype=tf.float32, name='k_raw')
self.k = tf.math.sigmoid(self.k_raw) * 0.5 # 约束在0-0.5之间
def call(self, inputs):
t, h = inputs
x = tf.concat([t, h], axis=1)
for layer in self.dense_layers:
x = layer(x)
return self.final_layer(x)
def physics_loss(self, t, h_current):
"""计算物理损失(基于离散渗流方程)"""
# 预测下一时刻的水位
h_next_pred = self([t, h_current])
# 离散渗流方程: h_{t+1} = h_t - k * h_t (时间步长=1)
residual = h_next_pred - h_current * (1 - self.k)
return tf.reduce_mean(tf.square(residual))
class DamSeepageModel:
def __init__(self, root):
self.root = root
self.root.title("大坝渗流预测模型(PINNs)")
self.root.geometry("1200x800")
# 初始化数据
self.train_df = None #训练集
self.test_df = None #测试集
self.model = None
self.scaler = MinMaxScaler(feature_range=(0, 1))
self.evaluation_metrics = {}
# 创建主界面
self.create_widgets()
def create_widgets(self):
# 创建主框架
main_frame = ttk.Frame(self.root, padding=10)
main_frame.pack(fill=tk.BOTH, expand=True)
# 左侧控制面板
control_frame = ttk.LabelFrame(main_frame, text="模型控制", padding=10)
control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)
# 文件选择部分
file_frame = ttk.LabelFrame(control_frame, text="数据文件", padding=10)
file_frame.pack(fill=tk.X, pady=5)
# 训练集选择
ttk.Label(file_frame, text="训练集:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.train_file_var = tk.StringVar()
ttk.Entry(file_frame, textvariable=self.train_file_var, width=30, state='readonly').grid(row=0, column=1,
padx=5)
ttk.Button(file_frame, text="选择文件", command=lambda: self.select_file("train")).grid(row=0, column=2)
# 测试集选择
ttk.Label(file_frame, text="测试集:").grid(row=1, column=0, sticky=tk.W, pady=5)
self.test_file_var = tk.StringVar()
ttk.Entry(file_frame, textvariable=self.test_file_var, width=30, state='readonly').grid(row=1, column=1, padx=5)
ttk.Button(file_frame, text="选择文件", command=lambda: self.select_file("test")).grid(row=1, column=2)
# PINNs参数设置
param_frame = ttk.LabelFrame(control_frame, text="PINNs参数", padding=10)
param_frame.pack(fill=tk.X, pady=10)
# 验证集切分比例
ttk.Label(param_frame, text="验证集比例:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.split_ratio_var = tk.DoubleVar(value=0.2)
ttk.Spinbox(param_frame, from_=0, to=1, increment=0.05,
textvariable=self.split_ratio_var, width=10).grid(row=0, column=1, padx=5)
# 隐藏层数量
ttk.Label(param_frame, text="网络层数:").grid(row=1, column=0, sticky=tk.W, pady=5)
self.num_layers_var = tk.IntVar(value=4)
ttk.Spinbox(param_frame, from_=2, to=8, increment=1,
textvariable=self.num_layers_var, width=10).grid(row=1, column=1, padx=5)
# 每层神经元数量
ttk.Label(param_frame, text="神经元数/层:").grid(row=2, column=0, sticky=tk.W, pady=5)
self.hidden_units_var = tk.IntVar(value=32)
ttk.Spinbox(param_frame, from_=16, to=128, increment=4,
textvariable=self.hidden_units_var, width=10).grid(row=2, column=1, padx=5)
# 训练轮次
ttk.Label(param_frame, text="训练轮次:").grid(row=3, column=0, sticky=tk.W, pady=5)
self.epochs_var = tk.IntVar(value=500)
ttk.Spinbox(param_frame, from_=100, to=2000, increment=100,
textvariable=self.epochs_var, width=10).grid(row=3, column=1, padx=5)
# 物理损失权重
ttk.Label(param_frame, text="物理损失权重:").grid(row=4, column=0, sticky=tk.W, pady=5)
self.physics_weight_var = tk.DoubleVar(value=0.5)
ttk.Spinbox(param_frame, from_=0.1, to=1.0, increment=0.1,
textvariable=self.physics_weight_var, width=10).grid(row=4, column=1, padx=5)
# 控制按钮
btn_frame = ttk.Frame(control_frame)
btn_frame.pack(fill=tk.X, pady=10)
ttk.Button(btn_frame, text="训练模型", command=self.train_model).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="预测结果", command=self.predict).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="保存结果", command=self.save_results).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="重置", command=self.reset).pack(side=tk.RIGHT, padx=5)
# 状态栏
self.status_var = tk.StringVar(value="就绪")
status_bar = ttk.Label(control_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
status_bar.pack(fill=tk.X, side=tk.BOTTOM)
# 右侧结果显示区域
result_frame = ttk.Frame(main_frame)
result_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)
# 创建标签页
self.notebook = ttk.Notebook(result_frame)
self.notebook.pack(fill=tk.BOTH, expand=True)
# 损失曲线标签页
self.loss_frame = ttk.Frame(self.notebook)
self.notebook.add(self.loss_frame, text="训练损失")
# 预测结果标签页
self.prediction_frame = ttk.Frame(self.notebook)
self.notebook.add(self.prediction_frame, text="预测结果")
# 指标显示
self.metrics_var = tk.StringVar()
metrics_label = ttk.Label(
self.prediction_frame,
textvariable=self.metrics_var,
font=('TkDefaultFont', 10, 'bold'),
relief='ridge',
padding=5
)
metrics_label.pack(fill=tk.X, padx=5, pady=5)
# 初始化绘图区域
self.fig, self.ax = plt.subplots(figsize=(10, 6))
self.canvas = FigureCanvasTkAgg(self.fig, master=self.prediction_frame)
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# 损失曲线画布
self.loss_fig, self.loss_ax = plt.subplots(figsize=(10, 4))
self.loss_canvas = FigureCanvasTkAgg(self.loss_fig, master=self.loss_frame)
self.loss_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
def select_file(self, file_type):
"""选择Excel文件"""
file_path = filedialog.askopenfilename(
title=f"选择{file_type}集Excel文件",
filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
)
if file_path:
try:
df = pd.read_excel(file_path)
# 时间特征处理
time_features = ['year', 'month', 'day']
missing_time_features = [feat for feat in time_features if feat not in df.columns]
if missing_time_features:
messagebox.showerror("列名错误",
f"Excel文件缺少预处理后的时间特征列: {', '.join(missing_time_features)}")
return
# 创建时间戳列 (增强兼容性)
time_cols = ['year', 'month', 'day']
if 'hour' in df.columns: time_cols.append('hour')
if 'minute' in df.columns: time_cols.append('minute')
if 'second' in df.columns: time_cols.append('second')
# 填充缺失的时间单位
for col in ['hour', 'minute', 'second']:
if col not in df.columns:
df[col] = 0
df['datetime'] = pd.to_datetime(df[time_cols])
# 设置时间索引
df = df.set_index('datetime')
# 计算相对时间(天)
df['days'] = (df.index - df.index[0]).days
# 保存数据
if file_type == "train":
self.train_df = df
self.train_file_var.set(os.path.basename(file_path))
self.status_var.set(f"已加载训练集: {len(self.train_df)}条数据")
else:
self.test_df = df
self.test_file_var.set(os.path.basename(file_path))
self.status_var.set(f"已加载测试集: {len(self.test_df)}条数据")
except Exception as e:
messagebox.showerror("文件错误", f"读取文件失败: {str(e)}")
def calculate_metrics(self, y_true, y_pred):
"""计算评估指标"""
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
mse = mean_squared_error(y_true, y_pred)
rmse = np.sqrt(mse)
mae = mean_absolute_error(y_true, y_pred)
non_zero_idx = np.where(y_true != 0)[0]
if len(non_zero_idx) > 0:
mape = np.mean(np.abs((y_true[non_zero_idx] - y_pred[non_zero_idx]) / y_true[non_zero_idx])) * 100
else:
mape = float('nan')
r2 = r2_score(y_true, y_pred)
return {
'MSE': mse,
'RMSE': rmse,
'MAE': mae,
'MAPE': mape,
'R2': r2
}
def train_model(self):
"""训练PINNs模型(带早停机制+训练指标监控,无指标绘图)"""
if self.train_df is None:
messagebox.showwarning("警告", "请先选择训练集文件")
return
try:
self.status_var.set("正在预处理数据...")
self.root.update()
# 从训练集中切分训练子集和验证子集(时间顺序切分)
split_ratio = 1 - self.split_ratio_var.get()
split_idx = int(len(self.train_df) * split_ratio)
train_subset = self.train_df.iloc[:split_idx]
valid_subset = self.train_df.iloc[split_idx:]
# 检查数据量是否足够
if len(train_subset) < 2 or len(valid_subset) < 2:
messagebox.showerror("数据错误", "训练集数据量不足(至少需要2个时间步)")
return
# 数据预处理(训练子集拟合scaler,验证子集用相同scaler)
train_subset_scaled = self.scaler.fit_transform(train_subset[['水位']])
valid_subset_scaled = self.scaler.transform(valid_subset[['水位']])
# 准备训练数据(原始值用于指标计算)
t_train = train_subset['days'].values[1:].reshape(-1, 1).astype(np.float32)
h_train = train_subset_scaled[:-1].astype(np.float32)
h_next_train_scaled = train_subset_scaled[1:].astype(np.float32) # 归一化后的标签
h_next_train_true = train_subset['水位'].values[1:].reshape(-1, 1) # 原始真实值(反归一化前)
# 准备验证数据(原始值用于指标计算)
t_valid = valid_subset['days'].values[1:].reshape(-1, 1).astype(np.float32)
h_valid = valid_subset_scaled[:-1].astype(np.float32)
h_next_valid_scaled = valid_subset_scaled[1:].astype(np.float32) # 归一化后的标签
h_next_valid_true = valid_subset['水位'].values[1:].reshape(-1, 1) # 原始真实值
# 创建模型和优化器
self.model = PINNModel(
num_layers=self.num_layers_var.get(),
hidden_units=self.hidden_units_var.get()
)
optimizer = Adam(learning_rate=0.001)
# 构建训练/验证数据集
train_dataset = tf.data.Dataset.from_tensor_slices(((t_train, h_train), h_next_train_scaled))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(32)
valid_dataset = tf.data.Dataset.from_tensor_slices(((t_valid, h_valid), h_next_valid_scaled))
valid_dataset = valid_dataset.batch(32) # 验证集无需shuffle
# 损失记录(新增指标记录)
train_data_loss_history = []
physics_loss_history = []
valid_data_loss_history = []
# 新增:训练集和验证集的指标历史(MSE, RMSE等)
train_metrics_history = [] # 每个元素是字典(如{'MSE':..., 'RMSE':...})
valid_metrics_history = []
# 早停机制参数
patience = int(self.epochs_var.get() / 3)
min_delta = 1e-4
best_valid_loss = float('inf')
wait = 0
best_epoch = 0
best_weights = None
start_time = time.time()
# 自定义训练循环(新增指标计算)
for epoch in range(self.epochs_var.get()):
# 训练阶段
epoch_train_data_loss = []
epoch_physics_loss = []
# 收集训练预测值(归一化后)
train_pred_scaled = []
for step, ((t_batch, h_batch), h_next_batch) in enumerate(train_dataset):
with tf.GradientTape() as tape:
h_pred = self.model([t_batch, h_batch])
data_loss = tf.reduce_mean(tf.square(h_next_batch - h_pred))
physics_loss = self.model.physics_loss(t_batch, h_batch)
loss = data_loss + self.physics_weight_var.get() * physics_loss
grads = tape.gradient(loss, self.model.trainable_variables)
optimizer.apply_gradients(zip(grads, self.model.trainable_variables))
epoch_train_data_loss.append(data_loss.numpy())
epoch_physics_loss.append(physics_loss.numpy())
train_pred_scaled.append(h_pred.numpy()) # 保存训练预测值(归一化)
# 合并训练预测值(归一化后)
train_pred_scaled = np.concatenate(train_pred_scaled, axis=0)
# 反归一化得到原始预测值
train_pred_true = self.scaler.inverse_transform(train_pred_scaled)
# 计算训练集指标(使用原始真实值和预测值)
train_metrics = self.calculate_metrics(
y_true=h_next_train_true.flatten(),
y_pred=train_pred_true.flatten()
)
train_metrics_history.append(train_metrics)
# 验证阶段
epoch_valid_data_loss = []
valid_pred_scaled = []
for ((t_v_batch, h_v_batch), h_v_next_batch) in valid_dataset:
h_v_pred = self.model([t_v_batch, h_v_batch])
valid_data_loss = tf.reduce_mean(tf.square(h_v_next_batch - h_v_pred))
epoch_valid_data_loss.append(valid_data_loss.numpy())
valid_pred_scaled.append(h_v_pred.numpy()) # 保存验证预测值(归一化)
# 合并验证预测值(归一化后)
valid_pred_scaled = np.concatenate(valid_pred_scaled, axis=0)
# 反归一化得到原始预测值
valid_pred_true = self.scaler.inverse_transform(valid_pred_scaled)
# 计算验证集指标(使用原始真实值和预测值)
valid_metrics = self.calculate_metrics(
y_true=h_next_valid_true.flatten(),
y_pred=valid_pred_true.flatten()
)
valid_metrics_history.append(valid_metrics)
# 计算平均损失
avg_train_data_loss = np.mean(epoch_train_data_loss)
avg_physics_loss = np.mean(epoch_physics_loss)
avg_valid_data_loss = np.mean(epoch_valid_data_loss)
# 记录损失
train_data_loss_history.append(avg_train_data_loss)
physics_loss_history.append(avg_physics_loss)
valid_data_loss_history.append(avg_valid_data_loss)
# 早停机制逻辑(与原代码一致)
current_valid_loss = avg_valid_data_loss
if current_valid_loss < best_valid_loss - min_delta:
best_valid_loss = current_valid_loss
best_epoch = epoch + 1
wait = 0
best_weights = self.model.get_weights()
else:
wait += 1
if wait >= patience:
self.status_var.set(f"触发早停!最佳轮次: {best_epoch},最佳验证损失: {best_valid_loss:.4f}")
if best_weights is not None:
self.model.set_weights(best_weights)
break
# 更新状态(新增指标显示)
if epoch % 10 == 0:
# 提取当前训练/验证的关键指标(如RMSE)
train_rmse = train_metrics['RMSE']
valid_rmse = valid_metrics['RMSE']
train_r2 = train_metrics['R2']
valid_r2 = valid_metrics['R2']
k_value = self.model.k.numpy()
elapsed = time.time() - start_time
self.status_var.set(
f"训练中 | 轮次: {epoch + 1}/{self.epochs_var.get()} | "
f"训练RMSE: {train_rmse:.4f} | 验证RMSE: {valid_rmse:.4f} | "
f"训练R²: {train_r2:.4f} | 验证R²: {valid_r2:.4f} | "
f"k: {k_value:.6f} | 时间: {elapsed:.1f}秒 | 早停等待: {wait}/{patience}"
)
self.root.update()
# 绘制损失曲线(仅保留原始损失曲线)
self.loss_ax.clear()
epochs_range = range(1, len(train_data_loss_history) + 1)
self.loss_ax.plot(epochs_range, train_data_loss_history, 'b-', label='训练数据损失')
self.loss_ax.plot(epochs_range, physics_loss_history, 'r--', label='物理损失')
self.loss_ax.plot(epochs_range, valid_data_loss_history, 'g-.', label='验证数据损失')
self.loss_ax.set_title('PINNs训练与验证损失')
self.loss_ax.set_xlabel('轮次')
self.loss_ax.set_ylabel('损失', rotation=0)
self.loss_ax.legend()
self.loss_ax.grid(True, alpha=0.3)
self.loss_ax.set_yscale('log')
self.loss_canvas.draw()
# 训练完成提示(保留指标总结)
elapsed = time.time() - start_time
if wait >= patience:
completion_msg = (
f"早停触发 | 最佳轮次: {best_epoch} | 最佳验证损失: {best_valid_loss:.4f} | "
f"最佳验证RMSE: {valid_metrics_history[best_epoch - 1]['RMSE']:.4f} | "
f"总时间: {elapsed:.1f}秒"
)
else:
completion_msg = (
f"训练完成 | 总轮次: {self.epochs_var.get()} | "
f"最终训练RMSE: {train_metrics_history[-1]['RMSE']:.4f} | "
f"最终验证RMSE: {valid_metrics_history[-1]['RMSE']:.4f} | "
f"最终训练R²: {train_metrics_history[-1]['R2']:.4f} | "
f"最终验证R²: {valid_metrics_history[-1]['R2']:.4f} | "
f"总时间: {elapsed:.1f}秒"
)
self.status_var.set(completion_msg)
messagebox.showinfo("训练完成", f"PINNs模型训练成功完成!\n{completion_msg}")
except Exception as e:
messagebox.showerror("训练错误", f"模型训练失败:\n{str(e)}")
self.status_var.set("训练失败")
def predict(self):
"""使用PINNs模型进行预测(优化时间轴刻度与网格线)"""
if self.model is None:
messagebox.showwarning("警告", "请先训练模型")
return
if self.test_df is None:
messagebox.showwarning("警告", "请先选择测试集文件")
return
try:
self.status_var.set("正在生成预测...")
self.root.update()
# 预处理测试数据
test_scaled = self.scaler.transform(self.test_df[['水位']])
# 准备时间特征
t_test = self.test_df['days'].values.reshape(-1, 1).astype(np.float32)
# 递归预测
predictions = []
for i in range(len(t_test)):
h_current = np.array([[test_scaled[i][0]]]).astype(np.float32)
h_pred = self.model([t_test[i:i + 1], h_current])
predictions.append(h_pred.numpy()[0][0])
# 反归一化
predictions = np.array(predictions).reshape(-1, 1)
predictions = self.scaler.inverse_transform(predictions)
actual_values = self.scaler.inverse_transform(test_scaled)
# 创建时间索引(确保为DatetimeIndex)
test_time = self.test_df.index # 假设为pandas DatetimeIndex类型
# 清除现有图表
self.ax.clear()
# 绘制结果
self.ax.plot(test_time, actual_values, 'b-', label='真实值')
self.ax.plot(test_time, predictions, 'r--', label='预测值')
self.ax.set_title('大坝渗流水位预测结果(PINNs)')
self.ax.set_xlabel('时间')
self.ax.set_ylabel('测压管水位', rotation=0)
self.ax.legend()
# 添加网格和样式(优化时间轴)
import matplotlib.dates as mdates # 导入日期刻度工具
# 设置x轴刻度:主刻度(年份)和次要刻度(每2个月)
# 主刻度:每年1月1日(或数据起始年的第一个时间点)
self.ax.xaxis.set_major_locator(mdates.YearLocator())
self.ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y')) # 仅显示年份
# 次要刻度:每2个月(如2月、4月、6月...)
self.ax.xaxis.set_minor_locator(mdates.MonthLocator(interval=2))
# 添加次要网格线(每2个月的竖直虚线)
self.ax.grid(which='minor', axis='x', linestyle='--', color='gray', alpha=0.3)
# 主要网格线(可选,保持原有水平网格)
self.ax.grid(which='major', axis='y', linestyle='-', color='lightgray', alpha=0.5)
# 优化刻度标签显示(避免重叠)
self.ax.tick_params(axis='x', which='major', rotation=0, labelsize=10)
self.ax.tick_params(axis='x', which='minor', length=3) # 次要刻度线长度
# 计算并显示评估指标(保持原有逻辑)
self.evaluation_metrics = self.calculate_metrics(
actual_values.flatten(),
predictions.flatten()
)
metrics_text = (
f"MSE: {self.evaluation_metrics['MSE']:.4f} | "
f"RMSE: {self.evaluation_metrics['RMSE']:.4f} | "
f"MAE: {self.evaluation_metrics['MAE']:.4f} | "
f"MAPE: {self.evaluation_metrics['MAPE']:.2f}% | "
f"R²: {self.evaluation_metrics['R2']:.4f}"
)
# 更新文本标签
self.metrics_var.set(metrics_text)
# 在图表上添加指标(位置调整,避免覆盖时间刻度)
self.ax.text(
0.5, 1.08, metrics_text, # 略微上移避免与网格重叠
transform=self.ax.transAxes,
ha='center',
fontsize=10,
bbox=dict(facecolor='white', alpha=0.8)
)
# 调整布局(重点优化时间轴边距)
plt.tight_layout(pad=2.0) # 增加底部边距避免刻度标签被截断
self.canvas.draw()
# 保存预测结果(保持原有逻辑)
self.predictions = predictions
self.actual_values = actual_values
self.test_time = test_time
self.status_var.set("预测完成,结果已显示")
except Exception as e:
messagebox.showerror("预测错误", f"预测失败:\n{str(e)}")
self.status_var.set("预测失败")
def save_results(self):
"""保存预测结果"""
if not hasattr(self, 'predictions'):
messagebox.showwarning("警告", "请先生成预测结果")
return
save_path = filedialog.asksaveasfilename(
defaultextension=".xlsx",
filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")]
)
if not save_path:
return
try:
# 创建结果DataFrame
result_df = pd.DataFrame({
'时间': self.test_time,
'实际水位': self.actual_values.flatten(),
'预测水位': self.predictions.flatten()
})
# 创建评估指标DataFrame
metrics_df = pd.DataFrame([self.evaluation_metrics])
# 保存到Excel
with pd.ExcelWriter(save_path) as writer:
result_df.to_excel(writer, sheet_name='预测结果', index=False)
metrics_df.to_excel(writer, sheet_name='评估指标', index=False)
# 保存图表
chart_path = os.path.splitext(save_path)[0] + "_chart.png"
self.fig.savefig(chart_path, dpi=300)
self.status_var.set(f"结果已保存至: {os.path.basename(save_path)}")
messagebox.showinfo("保存成功", f"预测结果和图表已保存至:\n{save_path}\n{chart_path}")
except Exception as e:
messagebox.showerror("保存错误", f"保存结果失败:\n{str(e)}")
def reset(self):
"""重置程序状态"""
self.train_df = None
self.test_df = None
self.model = None
self.train_file_var.set("")
self.test_file_var.set("")
# 清除图表
if hasattr(self, 'ax'):
self.ax.clear()
if hasattr(self, 'loss_ax'):
self.loss_ax.clear()
# 重绘画布
if hasattr(self, 'canvas'):
self.canvas.draw()
if hasattr(self, 'loss_canvas'):
self.loss_canvas.draw()
# 清除状态
self.status_var.set("已重置,请选择新数据")
# 清除预测结果
if hasattr(self, 'predictions'):
del self.predictions
# 清除指标文本
if hasattr(self, 'metrics_var'):
self.metrics_var.set("")
messagebox.showinfo("重置", "程序已重置,可以开始新的分析")
if __name__ == "__main__":
root = tk.Tk()
app = DamSeepageModel(root)
root.mainloop()
帮我修改文件保存,一个文件保存时间和实际及预测水位,增加一个文件保存轮次和五个评估指标还有三个损失
最新发布