基于Python开发临界二阶阻尼传递函数模型仿真工具

一、小程序功能设计

1、临界二阶阻尼传递函数模型回顾

(1)二阶系统的结构:

(2)不同阻尼的二阶系统响应:

(3)根据以上的内容回顾,临界阻尼二阶系统有如下特点:

(4)基于临界阻尼二阶系统的特点,可以设计一个响应调节器,对某些变化剧烈的信号进行响应调节,使其变化平滑。不过请求信号往往是通过控制器发出的,而要想算法在控制器中运行,它必须是离散的,因此对于连续的响应调节器,要想应用于软件控制中,必须将其离散化,离散化后得到的状态空间表达式如下所示。

2、CSV信号配置

(1)小程序支持导入CSV文件,用户可以选择直接配置CSV数据的原始采样周期,如果CSV数据中有时间轴,也可直接选择时间列的数据作为横轴,然后选择输入跟踪微分器的信号列。

(2)功能定义:

①点击“导入CSV”,选择需要导入的文件,即可将CSV数据导入小程序。

②在右侧下拉框可以配置横轴的时间列和纵轴的原始信号列,如果选择使用CSV的原始采样周期,则不配置横轴的时间列。

3、仿真曲线绘制

(1)在参数配置区可配置参数模型(自然频率不要太大,需要小于调度频率,否则系统会振荡),然后点击“添加本组”,即可加入一组参数(支持添加多组参数);选中列表中的一组参数,点击“删除选中”,即可将参数组移除。

(2)完成参数配置后,点击“批量绘制对比曲线”,小程序会把原始数据、经过各组参数模型处理过的平滑数据共轴展示。

二、小程序开发

1、开发环境

(1)需要确认安装的Python是否自带tkinter库(图形依赖),有则无需单独安装。

(2)安装pyinstaller库,该库主要用于将.py打包成可执行文件.exe,使小程序运行能够脱离Python环境。

(3)安装numpy库,该库用于支持复杂的科学计算。

2、程序源码

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import pandas as pd
import numpy as np
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
matplotlib.rcParams['axes.unicode_minus'] = False
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

class CritDamp2ndSimGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("临界阻尼二阶系统仿真工具 ζ=1")
        self.root.geometry("1360x880")
        self.df = None
        self.csv_columns = []
        self.file_text = tk.StringVar(value="未加载CSV文件")
        # CSV原始采样周期
        self.var_csv_ts = tk.StringVar(value="0.001")
        self.param_list = []
        self.line_colors = ["#d62728", "#2ca02c", "#ff7f0e", "#9467bd", "#17becf", "#bcbd22"]

        # ========== CSV信号配置区(带时间、信号下拉框) ==========
        csv_frame = ttk.LabelFrame(root, text="CSV信号配置(输入临界阻尼二阶系统)")
        csv_frame.pack(fill="x", padx=8, pady=4)
        ttk.Button(csv_frame, text="导入CSV", command=self.load_csv).grid(row=0, column=0, padx=5, pady=6)
        ttk.Label(csv_frame, textvariable=self.file_text).grid(row=0, column=1, padx=10)
        ttk.Label(csv_frame, text="CSV原始采样周期(s):").grid(row=0, column=2, padx=10)
        self.ent_csv_ts = ttk.Entry(csv_frame, textvariable=self.var_csv_ts, width=12)
        self.ent_csv_ts.grid(row=0, column=3)
        ttk.Label(csv_frame, text="X轴时间列:").grid(row=0, column=4, padx=10)
        self.var_time = tk.StringVar()
        self.cbx_time = ttk.Combobox(csv_frame, textvariable=self.var_time, state="readonly", width=14)
        self.cbx_time.grid(row=0, column=5)
        self.cbx_time.bind("<<ComboboxSelected>>", self._on_time_col_change)
        ttk.Label(csv_frame, text="x1输入信号列:").grid(row=0, column=6, padx=10)
        self.var_x1 = tk.StringVar()
        self.cbx_x1 = ttk.Combobox(csv_frame, textvariable=self.var_x1, state="readonly", width=14)
        self.cbx_x1.grid(row=0, column=7)

        # ========== 参数配置区:wn自然频率 / Ts仿真周期 ==========
        param_frame = ttk.LabelFrame(root, text="临界阻尼二阶参数组:wn自然频率(rad/s) / Ts调度周期(s)  ζ固定=1")
        param_frame.pack(fill="x", padx=8, pady=4)
        ttk.Label(param_frame, text="wn:").grid(row=0, column=0, padx=4, pady=5)
        self.var_wn_in = tk.StringVar(value="10.0")
        ttk.Entry(param_frame, textvariable=self.var_wn_in, width=10).grid(row=0, column=1)
        ttk.Label(param_frame, text="Ts调度周期(s):").grid(row=0, column=2, padx=8)
        self.var_ts_in = tk.StringVar(value="0.001")
        ttk.Entry(param_frame, textvariable=self.var_ts_in, width=10).grid(row=0, column=3)
        ttk.Button(param_frame, text="添加本组", command=self.add_param).grid(row=0, column=4, padx=6)
        ttk.Button(param_frame, text="删除选中", command=self.del_param).grid(row=0, column=5, padx=6)
        ttk.Button(param_frame, text="清空全部", command=self.clear_param).grid(row=0, column=6, padx=4)
        self.param_tree = ttk.Treeview(param_frame, columns=("wn", "Ts"), show="headings", height=4)
        self.param_tree.heading("wn", text="wn自然频率(rad/s)")
        self.param_tree.heading("Ts", text="Ts调度周期(s)")
        self.param_tree.grid(row=1, column=0, columnspan=11, sticky="ew", pady=6)
        param_frame.columnconfigure(10, weight=1)

        # 绘图按钮
        btn_frame = ttk.Frame(root)
        btn_frame.pack(fill="x", padx=8, pady=3)
        ttk.Button(btn_frame, text="批量绘制对比曲线", command=self.draw_all).pack()

        # 绘图画布
        plot_frame = ttk.LabelFrame(root, text="输入输出同轴对比曲线")
        plot_frame.pack(fill="both", expand=True, padx=8, pady=6)
        self.fig, self.ax = plt.subplots(figsize=(11, 6.8), dpi=96)
        self.canvas = FigureCanvasTkAgg(self.fig, master=plot_frame)
        self.canvas_widget = self.canvas.get_tk_widget()
        self.canvas_widget.pack(fill="both", expand=True)
        self.canvas.draw()

    def _on_time_col_change(self, event=None):
        # 选择样本序号时启用采样周期输入,选真实时间列则禁用
        if self.var_time.get() == "样本序号":
            self.ent_csv_ts.config(state="normal")
        else:
            self.ent_csv_ts.config(state="disabled")

    def load_csv(self):
        path = filedialog.askopenfilename(filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")])
        if not path:
            return
        try:
            self.df = pd.read_csv(path)
            self.csv_columns = list(self.df.columns)
            time_list = ["样本序号"] + self.csv_columns
            self.cbx_time["values"] = time_list
            self.var_time.set("样本序号")
            self.cbx_x1["values"] = self.csv_columns
            if self.csv_columns:
                self.var_x1.set(self.csv_columns[0])
            self.ent_csv_ts.config(state="normal")
            self.file_text.set(f"已加载:{path.split('/')[-1]}")
            messagebox.showinfo("提示", "CSV加载完成,每组参数按各自Ts自动重采样仿真")
        except Exception as e:
            messagebox.showerror("读取失败", str(e))
            self.df = None
            self.csv_columns = []

    def add_param(self):
        try:
            wn = float(self.var_wn_in.get())
            ts = float(self.var_ts_in.get())
            if wn <= 0 or ts <= 0:
                messagebox.showerror("错误", "wn、Ts必须大于0!")
                return
        except:
            messagebox.showerror("参数错误", "wn/Ts必须为数字!")
            return
        self.param_list.append((wn, ts))
        self.param_tree.insert("", "end", values=(wn, ts))

    def del_param(self):
        sel = self.param_tree.selection()
        if not sel:
            messagebox.showwarning("提示", "请先选中一行参数")
            return
        idx = self.param_tree.index(sel[0])
        del self.param_list[idx]
        self.param_tree.delete(sel[0])

    def clear_param(self):
        self.param_list.clear()
        for item in self.param_tree.get_children():
            self.param_tree.delete(item)

    def _get_orig_time(self):
        """获取CSV原始时间轴"""
        time_sel = self.var_time.get()
        if time_sel == "样本序号":
            try:
                t_csv = float(self.var_csv_ts.get())
            except:
                messagebox.showerror("错误", "CSV原始采样周期必须是数字!")
                return None
            if t_csv <= 0:
                messagebox.showerror("错误", "CSV原始采样周期必须大于0!")
                return None
            return np.arange(len(self.df)) * t_csv
        else:
            return self.df[time_sel].to_numpy(dtype=float)

    def sim_crit_damp_2nd(self, u_seq, Ts, wn):
        """
        临界阻尼二阶系统 ζ=1
        G(s) = wn² / (s² + 2*wn*s + wn²)
        欧拉离散迭代仿真
        """
        n = len(u_seq)
        y = np.zeros(n)
        dy = np.zeros(n)
        for k in range(1, n):
            u_k = u_seq[k]
            # 二阶微分方程离散
            ddy = (wn**2) * u_k - 2 * wn * dy[k-1] - (wn**2) * y[k-1]
            dy[k] = dy[k-1] + ddy * Ts
            y[k] = y[k-1] + dy[k-1] * Ts
        return y

    def draw_all(self):
        if self.df is None:
            messagebox.showwarning("提示", "请先导入CSV文件")
            return
        if len(self.param_list) == 0:
            messagebox.showwarning("提示", "至少添加一组仿真参数")
            return
        t_orig = self._get_orig_time()
        if t_orig is None:
            return
        x1_orig = self.df[self.var_x1.get()].to_numpy(dtype=float)
        self.ax.clear()
        # 绘制原始输入信号
        self.ax.plot(t_orig, x1_orig, color="#1f77b4", linewidth=1.4, label="原始输入x1")
        # 遍历多组参数仿真绘图
        for idx, (wn, ts) in enumerate(self.param_list):
            t_new = np.arange(t_orig[0], t_orig[-1] + ts * 0.5, ts)
            v_seq = np.interp(t_new, t_orig, x1_orig)
            y_out = self.sim_crit_damp_2nd(v_seq, ts, wn)
            c = self.line_colors[idx % len(self.line_colors)]
            self.ax.plot(t_new, y_out, color=c, linewidth=1.6, label=f"临界阻尼输出 wn={wn} Ts={ts}s")
        self.ax.legend(loc="best")
        self.ax.grid(True, alpha=0.3)
        self.ax.set_xlabel("时间 (s)")
        self.ax.set_ylabel("信号幅值")
        self.ax.set_title("临界阻尼二阶系统(ζ=1)多参数同轴仿真对比")
        self.canvas.draw()

if __name__ == "__main__":
    root = tk.Tk()
    app = CritDamp2ndSimGUI(root)
    root.mainloop()

3、可执行程序生成及注意事项

(1)在程序源码文件crit_damp_2nd_analyze.py所在目录下打开终端,输入命令“pyinstaller -w -F crit_damp_2nd_analyze.py”,即可生成可执行文件crit_damp_2nd_analyze.exe。

(2)当前小程序仅支持曲线图像生成,不支持曲线图像保存,如有需求,可基于以上程序源码二次开发。

(3)传统控制算法往往关注很小的时间单位(毫秒级别),如果希望仿真生成的图像具有参考意义,建议导入CSV数据的时间范围不要过长(当然,也可基于以上程序源码二次开发,增加仿真时间范围配置项)。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zevalin爱灰灰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值