MRobot/polynomial.py
2025-05-24 23:49:21 +08:00

286 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
import numpy as np
import pandas as pd
from PyQt5.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QSpinBox,
QLabel, QTableWidget, QTableWidgetItem, QFileDialog, QTextEdit,
QComboBox, QMessageBox, QHeaderView
)
from PyQt5.QtGui import QFont
from PyQt5.QtCore import Qt
import matplotlib
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
class PolyFitApp(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("MRobot 多项式拟合工具")
self.resize(1440, 1280)
self.setFont(QFont("微软雅黑", 11))
self.center()
self.data_x = []
self.data_y = []
self.last_coeffs = None
self.last_xmin = None
self.last_xmax = None
# 主布局
main_layout = QHBoxLayout(self)
main_layout.setContentsMargins(20, 20, 20, 20)
main_layout.setSpacing(20)
left_layout = QVBoxLayout()
left_layout.setSpacing(12)
right_layout = QVBoxLayout()
right_layout.setSpacing(12)
main_layout.addLayout(left_layout, 0)
main_layout.addLayout(right_layout, 1)
# 数据输入区
self.table = QTableWidget(0, 2)
self.table.setFont(QFont("Consolas", 11))
self.table.setHorizontalHeaderLabels(["x", "y"])
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.table.setSelectionBehavior(QTableWidget.SelectRows)
left_layout.addWidget(self.table)
btn_row = QHBoxLayout()
self.add_row_btn = QPushButton("添加数据")
self.add_row_btn.setStyleSheet("color: #333;")
self.add_row_btn.clicked.connect(self.add_point_row)
btn_row.addWidget(self.add_row_btn)
self.del_row_btn = QPushButton("删除选中行")
self.del_row_btn.setStyleSheet("color: #333;")
self.del_row_btn.clicked.connect(self.delete_selected_rows)
btn_row.addWidget(self.del_row_btn)
left_layout.addLayout(btn_row)
# 导入导出按钮区
file_btn_row = QHBoxLayout()
self.import_btn = QPushButton("导入Excel文件")
self.import_btn.setStyleSheet("font-weight: bold; color: #333;")
self.import_btn.clicked.connect(self.load_excel)
file_btn_row.addWidget(self.import_btn)
self.export_btn = QPushButton("导出Excel文件")
self.export_btn.setStyleSheet("font-weight: bold; color: #333;")
self.export_btn.clicked.connect(self.export_excel_and_plot)
file_btn_row.addWidget(self.export_btn)
left_layout.addLayout(file_btn_row)
# 拟合参数区
param_layout = QHBoxLayout()
param_layout.addWidget(QLabel("多项式阶数:"))
self.order_spin = QSpinBox()
self.order_spin.setRange(1, 10)
self.order_spin.setValue(2)
param_layout.addWidget(self.order_spin)
left_layout.addLayout(param_layout)
self.fit_btn = QPushButton("拟合并显示")
self.fit_btn.setStyleSheet("font-weight: bold; color: #333;")
self.fit_btn.clicked.connect(self.fit_and_plot)
left_layout.addWidget(self.fit_btn)
# 输出区
self.output = QTextEdit()
self.output.setReadOnly(False)
self.output.setFont(QFont("Consolas", 10))
self.output.setMaximumHeight(150)
left_layout.addWidget(self.output)
code_layout = QHBoxLayout()
code_layout.addWidget(QLabel("输出代码格式:"))
self.code_type = QComboBox()
self.code_type.addItems(["C", "C++", "Python"])
code_layout.addWidget(self.code_type)
self.gen_code_btn = QPushButton("生成函数代码")
self.gen_code_btn.setStyleSheet("color: #333;")
self.gen_code_btn.clicked.connect(self.generate_code)
code_layout.addWidget(self.gen_code_btn)
left_layout.addLayout(code_layout)
# 拟合曲线区
self.figure = Figure(figsize=(5, 4))
self.canvas = FigureCanvas(self.figure)
right_layout.addWidget(self.canvas)
def center(self):
qr = self.frameGeometry()
cp = QApplication.desktop().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def add_point_row(self, x_val="", y_val=""):
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setItem(row, 0, QTableWidgetItem(str(x_val)))
self.table.setItem(row, 1, QTableWidgetItem(str(y_val)))
def delete_selected_rows(self):
selected = self.table.selectionModel().selectedRows()
for idx in sorted(selected, reverse=True):
self.table.removeRow(idx.row())
def load_excel(self):
file, _ = QFileDialog.getOpenFileName(self, "选择Excel文件", "", "Excel Files (*.xlsx *.xls)")
if file:
try:
data = pd.read_excel(file, usecols=[0, 1])
new_x = data.iloc[:, 0].values.tolist()
new_y = data.iloc[:, 1].values.tolist()
for x, y in zip(new_x, new_y):
self.add_point_row(x, y)
QMessageBox.information(self, "成功", "数据导入成功!")
except Exception as e:
QMessageBox.critical(self, "错误", f"读取Excel失败: {e}")
def export_excel_and_plot(self):
file, _ = QFileDialog.getSaveFileName(self, "导出Excel文件", "", "Excel Files (*.xlsx *.xls)")
if file:
x_list, y_list = [], []
for row in range(self.table.rowCount()):
try:
x = float(self.table.item(row, 0).text())
y = float(self.table.item(row, 1).text())
x_list.append(x)
y_list.append(y)
except Exception:
continue
if not x_list or not y_list:
QMessageBox.warning(self, "导出失败", "没有可导出的数据!")
return
df = pd.DataFrame({'x': x_list, 'y': y_list})
try:
df.to_excel(file, index=False)
# 导出同名png图像
png_file = file
if png_file.lower().endswith('.xlsx') or png_file.lower().endswith('.xls'):
png_file = png_file.rsplit('.', 1)[0] + '.png'
else:
png_file = png_file + '.png'
self.figure.savefig(png_file, dpi=150, bbox_inches='tight')
QMessageBox.information(self, "导出成功", f"数据已成功导出到Excel文件\n图像已导出为:{png_file}")
except Exception as e:
QMessageBox.critical(self, "导出错误", f"导出Excel或图像失败: {e}")
def get_manual_points(self):
x_list, y_list = [], []
for row in range(self.table.rowCount()):
try:
x = float(self.table.item(row, 0).text())
y = float(self.table.item(row, 1).text())
x_list.append(x)
y_list.append(y)
except Exception:
continue
return x_list, y_list
def fit_and_plot(self):
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
matplotlib.rcParams['axes.unicode_minus'] = False
matplotlib.rcParams['font.size'] = 14
self.data_x, self.data_y = self.get_manual_points()
try:
order = int(self.order_spin.value())
except ValueError:
QMessageBox.warning(self, "输入错误", "阶数必须为整数!")
return
n_points = len(self.data_x)
if n_points < order + 1:
QMessageBox.warning(self, "数据不足", "数据点数量不足以拟合该阶多项式!")
return
x = np.array(self.data_x, dtype=np.float64)
y = np.array(self.data_y, dtype=np.float64)
x_min, x_max = x.min(), x.max()
if x_max - x_min == 0:
QMessageBox.warning(self, "数据错误", "所有x值都相同无法拟合")
return
try:
coeffs = np.polyfit(x, y, order)
except Exception as e:
QMessageBox.critical(self, "拟合错误", f"多项式拟合失败:{e}")
return
poly = np.poly1d(coeffs)
expr = "y = " + " + ".join([f"{c:.6g}*x^{order-i}" for i, c in enumerate(coeffs)])
self.output.setPlainText(f"{expr}\n")
self.figure.clear()
ax = self.figure.add_subplot(111)
ax.scatter(x, y, color='red', label='数据点')
x_fit = np.linspace(x_min, x_max, 200)
y_fit = poly(x_fit)
ax.plot(x_fit, y_fit, label='拟合曲线')
ax.legend()
self.canvas.draw()
self.last_coeffs = coeffs
self.last_xmin = x_min
self.last_xmax = x_max
def generate_code(self):
if self.last_coeffs is None:
QMessageBox.warning(self, "未拟合", "请先拟合数据!")
return
coeffs = self.last_coeffs
code_type = self.code_type.currentText()
if code_type == "C":
code = self.create_c_function(coeffs)
elif code_type == "C++":
code = self.create_cpp_function(coeffs)
else:
code = self.create_py_function(coeffs)
self.output.setPlainText(code)
def create_c_function(self, coeffs):
lines = ["#include <math.h>", "double polynomial(double x) {", " return "]
n = len(coeffs)
terms = []
for i, c in enumerate(coeffs):
exp = n - i - 1
if exp == 0:
terms.append(f"{c:.8g}")
elif exp == 1:
terms.append(f"{c:.8g}*x")
else:
terms.append(f"{c:.8g}*pow(x,{exp})")
lines[-1] += " + ".join(terms) + ";"
lines.append("}")
return "\n".join(lines)
def create_cpp_function(self, coeffs):
lines = ["#include <cmath>", "double polynomial(double x) {", " return "]
n = len(coeffs)
terms = []
for i, c in enumerate(coeffs):
exp = n - i - 1
if exp == 0:
terms.append(f"{c:.8g}")
elif exp == 1:
terms.append(f"{c:.8g}*x")
else:
terms.append(f"{c:.8g}*pow(x,{exp})")
lines[-1] += " + ".join(terms) + ";"
lines.append("}")
return "\n".join(lines)
def create_py_function(self, coeffs):
n = len(coeffs)
lines = ["def polynomial(x):", " return "]
terms = []
for i, c in enumerate(coeffs):
exp = n - i - 1
if exp == 0:
terms.append(f"{c:.8g}")
elif exp == 1:
terms.append(f"{c:.8g}*x")
else:
terms.append(f"{c:.8g}*x**{exp}")
lines[-1] += " + ".join(terms)
return "\n".join(lines)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = PolyFitApp()
win.show()
sys.exit(app.exec_())