import gradio as gr from assets.knowledge_cards_html import ( TIME_DOMAIN_KNOWLEDGE, FREQUENCY_DOMAIN_KNOWLEDGE, ROOT_LOCUS_KNOWLEDGE, ENGINE_CONTROL_KNOWLEDGE, MOTOR_CONTROL_KNOWLEDGE, GPR_KNOWLEDGE, NN_KNOWLEDGE, EMS_KNOWLEDGE, ) def create_header(): """创建页面顶部的标题、横幅和在线计数器""" gr.HTML("

自动控制理论AI+数智平台

") gr.HTML("

✨ 交互式控制系统分析与设计工具 | 时域·频域·根轨迹·AI问答 ✨

") online_counter = gr.HTML(elem_id="online-counter") gr.HTML("""
📚
课程 Course
自动控制理论
👨‍🏫
负责人 Supervisor
魏鹏飞
📧
联系方式 Contact
pengfeiwei@nwpu.edu.cn
🎓 西北工业大学 Northwestern Polytechnical University
2025年校级本科生建设项目资助
""") return online_counter def create_time_domain_tab(): """创建时域分析选项卡的UI组件""" ui_dict = {} with gr.Row(): with gr.Column(scale=1): with gr.Group(): gr.HTML("
📊 传递函数设定
") ui_dict["num_input"] = gr.Textbox( label="分子系数 (Numerator)", value="1", placeholder="例如: 1 或 1,2,3", info="💡 用逗号分隔,从最高次项到常数项" ) ui_dict["den_input"] = gr.Textbox( label="分母系数 (Denominator)", value="1,6,11,6", placeholder="例如: 1,2,1", info="💡 分母阶数通常 ≥ 分子阶数" ) with gr.Group(): gr.HTML("
🔧 系统模型
") ui_dict["tf_display"] = gr.Markdown(label="当前传递函数", elem_classes="output-display") with gr.Row(): ui_dict["confirm_button"] = gr.Button("✓ 显示传递函数", variant="secondary", scale=1) ui_dict["analyze_button"] = gr.Button("🚀 开始分析", variant="primary", scale=1, elem_classes="primary-btn") with gr.Group(): gr.HTML("
📈 动态性能指标
") ui_dict["output_metrics"] = gr.Textbox( label="Performance Metrics", lines=8, interactive=False, elem_classes="output-metrics" ) with gr.Column(scale=2): ui_dict["output_plot"] = gr.Plot(label="时域响应曲线", elem_classes="plot-container") # 知识卡片 gr.HTML(f"""
{TIME_DOMAIN_KNOWLEDGE}
""") return ui_dict def create_frequency_domain_tab(): """创建频域分析选项卡的UI组件""" ui_dict = {} with gr.Row(): with gr.Column(scale=1): with gr.Group(): gr.HTML("
📊 传递函数设定
") ui_dict["num_input"] = gr.Textbox( label="分子系数 (Numerator)", value="1", placeholder="例如: 1 或 1,2,3", info="💡 用逗号分隔,从最高次项到常数项" ) ui_dict["den_input"] = gr.Textbox( label="分母系数 (Denominator)", value="1,6,11,6", placeholder="例如: 1,2,1", info="💡 分母阶数通常 ≥ 分子阶数" ) with gr.Group(): gr.HTML("
🎚️ 调整系统增益
") ui_dict["log_k_slider"] = gr.Slider(minimum=-4, maximum=4, value=1, step=0.01, label="对数增益 log₁₀(K)", info="💡 拖动滑块查看实时变化") ui_dict["k_number_display"] = gr.Number(value=10.0, label="当前增益 K", interactive=False, elem_classes="gain-display") with gr.Group(): gr.HTML("
🔧 当前系统模型
") ui_dict["tf_display"] = gr.Markdown(label="含增益K的开环传递函数", elem_classes="output-display") with gr.Group(): gr.HTML("
📊 稳定裕度分析
") ui_dict["metrics_display"] = gr.Textbox(label="Stability Margins", lines=4, interactive=False, elem_classes="output-metrics") ui_dict["stability_display"] = gr.Markdown(elem_classes="stability-result") with gr.Row(): ui_dict["analyze_button"] = gr.Button("🚀 开始分析", variant="primary", scale=1, elem_classes="primary-btn") with gr.Column(scale=2): ui_dict["plot_output"] = gr.Plot(label="频域响应图", elem_classes="plot-container") # 知识卡片 gr.HTML(f"""
{FREQUENCY_DOMAIN_KNOWLEDGE}
""") return ui_dict def create_root_locus_tab(): """创建根轨迹分析选项卡的UI组件""" ui_dict = {} with gr.Row(): with gr.Column(scale=1): with gr.Group(): gr.HTML("
📊 传递函数设定
") ui_dict["num_input"] = gr.Textbox( label="分子系数 (Numerator)", value="1", placeholder="例如: 1 或 1,2,3", info="💡 用逗号分隔,从最高次项到常数项" ) ui_dict["den_input"] = gr.Textbox( label="分母系数 (Denominator)", value="1,6,11,6", placeholder="例如: 1,2,1", info="💡 分母阶数通常 ≥ 分子阶数" ) with gr.Group(): gr.HTML("
🎚️ 调整系统增益
") ui_dict["log_k_slider"] = gr.Slider(minimum=-4, maximum=4, value=1, step=0.01, label="对数增益 log₁₀(K)", info="💡 拖动滑块观察极点移动") ui_dict["k_number_display"] = gr.Number(value=10.0, label="当前增益 K", interactive=False, elem_classes="gain-display") with gr.Group(): gr.HTML("
📍 闭环极点位置
") ui_dict["poles_display"] = gr.Textbox(label="Closed-Loop Pole Locations", lines=6, interactive=False, elem_classes="output-metrics") with gr.Row(): ui_dict["analyze_button"] = gr.Button("🚀 开始分析", variant="primary", scale=1, elem_classes="primary-btn") with gr.Column(scale=2): ui_dict["plot_output"] = gr.Plot(label="根轨迹图") gr.HTML(f"""
{ROOT_LOCUS_KNOWLEDGE}
""") return ui_dict def create_case_demo_tab(): """创建算例演示选项卡 — 四阶段交互设计(蒸馏→发动机→电机→能量管理)""" ui_dict = {} # === MathJax re-render helper (reused across tabs) === def _mathjax_script(div_id): return f""" """ with gr.Tabs(): # ========== 阶段零:模型训练(GPR + NN 两个子标签页)========== with gr.TabItem("🧬 模型训练", id="distill_tab"): gr.HTML("""
阶段零:模型训练包含两步——先训练/验证 GPR 高斯过程代理模型, 再将其知识蒸馏为轻量 NN(MLP)用于后续实时控制仿真。
""") with gr.Tabs(): # ----- 子标签页 A: GPR 模型训练 ----- with gr.TabItem("📈 GPR 模型训练", id="gpr_sub_tab"): with gr.Row(): with gr.Column(scale=1): with gr.Group(): gr.HTML("
🔬 GPR 训练设置
") ui_dict["gpr_mode"] = gr.Radio( choices=["load", "train"], value="load", label="运行模式", info="load: 加载已有权重 | train: 从头训练(需 botorch)") ui_dict["gpr_run_button"] = gr.Button( "🚀 运行 GPR 训练 / 加载", variant="primary", elem_classes="primary-btn") with gr.Group(): gr.HTML("
📝 GPR 结果
") ui_dict["gpr_summary"] = gr.Markdown() with gr.Column(scale=2): ui_dict["gpr_plot"] = gr.Plot(label="GPR 模型结果") gr.HTML(f"""
{GPR_KNOWLEDGE}
{_mathjax_script('gpr-knowledge')} """) # ----- 子标签页 B: NN 模型训练 ----- with gr.TabItem("🧠 NN 模型训练", id="nn_sub_tab"): with gr.Row(): with gr.Column(scale=1): with gr.Group(): gr.HTML("
🧪 NN 训练参数
") ui_dict["distill_epochs"] = gr.Slider(minimum=500, maximum=8000, value=3000, step=100, label="训练轮数 (Epochs)", info="越多越精确,但耗时更长") ui_dict["distill_lr"] = gr.Slider(minimum=1e-4, maximum=1e-2, value=3e-3, step=1e-4, label="学习率 (LR)", info="推荐 1e-3 ~ 5e-3") ui_dict["distill_hidden"] = gr.Slider(minimum=16, maximum=256, value=64, step=16, label="隐藏层宽度", info="MLP每层神经元数") ui_dict["distill_run_button"] = gr.Button("🚀 开始 NN 训练", variant="primary", elem_classes="primary-btn") with gr.Group(): gr.HTML("
📝 NN 训练结果
") ui_dict["distill_summary"] = gr.Markdown() with gr.Column(scale=2): ui_dict["distill_plot"] = gr.Plot(label="NN 训练结果 (Loss + Parity)") gr.HTML(f"""
{NN_KNOWLEDGE}
{_mathjax_script('nn-knowledge')} """) # ========== 阶段一:发动机控制器设计 ========== with gr.TabItem("🔧 发动机控制器设计", id="engine_tab"): gr.HTML("""
阶段一:选择 PID 或 MPC 控制器,调整参数,运行阶跃响应测试,观察功率跟踪性能。
""") with gr.Row(): with gr.Column(scale=1): with gr.Group(): gr.HTML("
🎯 控制器选择
") ui_dict["eng_controller_type"] = gr.Radio( choices=["PID", "MPC"], value="PID", label="控制器类型", info="PID: 经典三参数 | MPC: 模型预测控制") with gr.Group(visible=True) as eng_pid_group: gr.HTML("
🎛️ PID 参数
") ui_dict["eng_kp"] = gr.Slider(minimum=0.1, maximum=20, value=4.652, step=0.01, label="比例增益 Kp", info="增大加快响应,过大导致振荡") ui_dict["eng_ki"] = gr.Slider(minimum=0.0, maximum=20, value=7.078, step=0.01, label="积分增益 Ki", info="消除稳态误差,过大导致超调") ui_dict["eng_kd"] = gr.Slider(minimum=0.0, maximum=5, value=0.222, step=0.001, label="微分增益 Kd", info="抑制振荡,改善动态特性") ui_dict["eng_pid_group"] = eng_pid_group with gr.Group(visible=False) as eng_mpc_group: gr.HTML("
🎛️ MPC 参数
") ui_dict["eng_mpc_horizon"] = gr.Slider(minimum=3, maximum=30, value=15, step=1, label="预测时域 (Horizon)", info="MPC前看步数") ui_dict["eng_mpc_W_power"] = gr.Slider(minimum=1, maximum=500, value=200, step=1, label="功率跟踪权重 W_power") ui_dict["eng_mpc_W_dcost"] = gr.Slider(minimum=0.01, maximum=20, value=1.5, step=0.01, label="控制增量权重 W_Δcost") ui_dict["eng_mpc_overshoot"] = gr.Slider(minimum=1, maximum=30, value=5, step=1, label="超调限制 (%)") ui_dict["eng_mpc_group"] = eng_mpc_group with gr.Group(): gr.HTML("
⚙️ 发动机模型参数
") ui_dict["eng_tau_fuel"] = gr.Slider(minimum=0.05, maximum=1.0, value=0.15, step=0.01, label="燃油执行机构时间常数 τ (s)") ui_dict["eng_K_inertia"] = gr.Slider(minimum=10, maximum=500, value=100, step=5, label="转子惯性增益 K") with gr.Group(): gr.HTML("
🧪 仿真设置
") ui_dict["eng_sim_time"] = gr.Slider(minimum=5, maximum=60, value=30, step=1, label="仿真时长 (s)") ui_dict["eng_dt"] = gr.Dropdown(choices=[0.02, 0.05, 0.1], value=0.02, label="仿真步长 (s)") ui_dict["eng_init_power"] = gr.Slider(minimum=20, maximum=250, value=100, step=5, label="初始功率 (kW)") ui_dict["eng_target_power"] = gr.Slider(minimum=20, maximum=300, value=200, step=5, label="目标功率 (kW)") ui_dict["eng_run_button"] = gr.Button("🚀 运行发动机仿真", variant="primary", elem_classes="primary-btn") with gr.Group(): gr.HTML("
📝 设计结果
") ui_dict["eng_summary"] = gr.Markdown() with gr.Column(scale=2): ui_dict["eng_plot"] = gr.Plot(label="发动机控制器阶跃响应") gr.HTML(f"""
{ENGINE_CONTROL_KNOWLEDGE}
{_mathjax_script('engine-knowledge')} """) # Radio toggle PID/MPC visibility ui_dict["eng_controller_type"].change( fn=lambda ct: (gr.update(visible=(ct == "PID")), gr.update(visible=(ct == "MPC"))), inputs=[ui_dict["eng_controller_type"]], outputs=[eng_pid_group, eng_mpc_group], ) # ========== 阶段二:电机控制器设计 ========== with gr.TabItem("⚡ 电机控制器设计", id="motor_tab"): gr.HTML("""
阶段二:选择 PID 或 MPC 控制器,运行转速跟踪 + 负载扰动测试。 在仿真60%时刻自动施加50%负载扰动,检验抗扰能力。
""") with gr.Row(): with gr.Column(scale=1): with gr.Group(): gr.HTML("
🎯 控制器选择
") ui_dict["mot_controller_type"] = gr.Radio( choices=["PID", "MPC"], value="PID", label="控制器类型", info="PID: 经典三参数 | MPC: 模型预测控制") with gr.Group(visible=True) as mot_pid_group: gr.HTML("
🎛️ PID 参数
") ui_dict["mot_kp"] = gr.Slider(minimum=0.1, maximum=50, value=5.0, step=0.1, label="比例增益 Kp", info="增大加快转速响应") ui_dict["mot_ki"] = gr.Slider(minimum=0.0, maximum=100, value=2.0, step=0.1, label="积分增益 Ki", info="消除转速稳态偏差") ui_dict["mot_kd"] = gr.Slider(minimum=0.0, maximum=5, value=0.5, step=0.01, label="微分增益 Kd", info="抑制转速振荡") ui_dict["mot_pid_group"] = mot_pid_group with gr.Group(visible=False) as mot_mpc_group: gr.HTML("
🎛️ MPC 参数
") ui_dict["mot_mpc_W_speed"] = gr.Slider(minimum=1, maximum=500, value=200, step=1, label="转速跟踪权重 W_speed") ui_dict["mot_mpc_W_dcost"] = gr.Slider(minimum=0.01, maximum=20, value=0.3, step=0.01, label="控制增量权重 W_Δcost") ui_dict["mot_mpc_overshoot"] = gr.Slider(minimum=1, maximum=30, value=5, step=1, label="超调限制 (%)") ui_dict["mot_mpc_group"] = mot_mpc_group with gr.Group(): gr.HTML("
⚙️ 电机模型参数
") ui_dict["mot_J"] = gr.Slider(minimum=0.1, maximum=5.0, value=1.0, step=0.1, label="转动惯量 J (kg·m²)", info="越大响应越慢但越平稳") with gr.Group(): gr.HTML("
🧪 仿真设置
") ui_dict["mot_sim_time"] = gr.Slider(minimum=5, maximum=60, value=30, step=1, label="仿真时长 (s)") ui_dict["mot_dt"] = gr.Dropdown(choices=[0.02, 0.05, 0.1], value=0.02, label="仿真步长 (s)") ui_dict["mot_target_rpm"] = gr.Slider(minimum=500, maximum=5000, value=2000, step=50, label="目标转速 (RPM)") ui_dict["mot_load_torque"] = gr.Slider(minimum=10, maximum=400, value=80, step=5, label="负载转矩 (Nm)") ui_dict["mot_run_button"] = gr.Button("🚀 运行电机仿真", variant="primary", elem_classes="primary-btn") with gr.Group(): gr.HTML("
📝 设计结果
") ui_dict["mot_summary"] = gr.Markdown() with gr.Column(scale=2): ui_dict["mot_plot"] = gr.Plot(label="电机控制器阶跃响应") gr.HTML(f"""
{MOTOR_CONTROL_KNOWLEDGE}
{_mathjax_script('motor-knowledge')} """) # Radio toggle PID/MPC visibility ui_dict["mot_controller_type"].change( fn=lambda ct: (gr.update(visible=(ct == "PID")), gr.update(visible=(ct == "MPC"))), inputs=[ui_dict["mot_controller_type"]], outputs=[mot_pid_group, mot_mpc_group], ) # ========== 阶段三:能量管理策略设计 ========== with gr.TabItem("🔋 能量管理策略设计", id="ems_tab"): gr.HTML("""
阶段三:设计基于规则的能量管理策略(自动引用前两阶段的控制器参数)。
策略原理:SOC < 下限阈值 → 进入充电模式; SOC > 上限阈值 → 退出充电,进入功率跟随模式。 下限~上限之间为滞环区间,防止模式频繁切换。
""") with gr.Row(): with gr.Column(scale=1): with gr.Group(): gr.HTML("
📊 SOC规则参数(滞环控制)
") ui_dict["soc_target"] = gr.Slider(minimum=30, maximum=80, value=60, step=1, label="SOC目标值 (%)", info="功率跟随模式下的SOC补偿基准") ui_dict["soc_low"] = gr.Slider(minimum=15, maximum=50, value=30, step=1, label="SOC下限阈值 (%)", info="低于此值→进入充电模式") ui_dict["soc_high"] = gr.Slider(minimum=50, maximum=90, value=70, step=1, label="SOC上限阈值 (%)", info="高于此值→退出充电模式") with gr.Group(): gr.HTML("
⚡ 功率规则参数
") ui_dict["p_eng_min"] = gr.Slider(minimum=10, maximum=100, value=20, step=5, label="发动机最小功率 (kW)") ui_dict["p_eng_max"] = gr.Slider(minimum=100, maximum=350, value=300, step=10, label="发动机最大功率 (kW)") ui_dict["p_charge"] = gr.Slider(minimum=50, maximum=300, value=200, step=10, label="充电模式发动机功率 (kW)", info="进入充电模式后发动机固定输出") ui_dict["k_soc"] = gr.Slider(minimum=0, maximum=200, value=50, step=5, label="SOC补偿增益 (kW/ΔSOC)", info="跟随模式下对SOC偏差的修正力度") ui_dict["power_reserve"] = gr.Slider(minimum=0, maximum=50, value=10, step=1, label="动态功率储备 (%)", info="发动机额外预留功率百分比") with gr.Group(): gr.HTML("
🔧 系统与仿真参数
") ui_dict["battery_capacity"] = gr.Slider(minimum=10, maximum=200, value=50, step=5, label="电池容量 (kWh)") ui_dict["initial_soc"] = gr.Slider(minimum=10, maximum=95, value=60, step=1, label="初始SOC (%)") ui_dict["initial_engine_power"] = gr.Slider(minimum=20, maximum=250, value=50, step=5, label="初始发动机功率 (kW)") ui_dict["profile"] = gr.Dropdown( choices=["起飞-巡航-降落", "高机动阶跃", "经济巡航"], value="起飞-巡航-降落", label="工况模板") ui_dict["sim_time"] = gr.Slider(minimum=20, maximum=180, value=60, step=5, label="仿真时长 (s)") ui_dict["dt"] = gr.Dropdown(choices=[0.02, 0.05, 0.1], value=0.02, label="仿真步长 (s)") ui_dict["hybrid_run_button"] = gr.Button("🚀 运行混动系统仿真", variant="primary", size="lg", elem_classes="primary-btn") with gr.Group(): gr.HTML("
📝 结果摘要
") ui_dict["hybrid_summary"] = gr.Markdown() with gr.Column(scale=2): ui_dict["hybrid_plot"] = gr.Plot(label="混动系统响应图") ui_dict["hybrid_table"] = gr.Dataframe( headers=["时间(s)", "目标转速", "实际转速", "发动机功率(kW)", "电池功率(kW)", "SOC(%)", "EMS模式"], label="关键时刻数据", interactive=False ) gr.HTML(f"""
{EMS_KNOWLEDGE}
{_mathjax_script('ems-knowledge')} """) return ui_dict def create_chatbot_tab(): """创建AI问答选项卡的UI组件""" ui_dict = {} ui_dict["chatbot"] = gr.Chatbot( label="🎓 自控原理AI助教", type="messages", avatar_images=("https://img.icons8.com/fluency/96/user-male-circle.png", "https://img.icons8.com/fluency/96/chatbot.png"), height=650, latex_delimiters=[ {"left": "$$", "right": "$$", "display": True}, {"left": "$", "right": "$", "display": False}, {"left": "\\[", "right": "\\]", "display": True}, {"left": "\\(", "right": "\\)", "display": False} ], elem_classes="modern-chatbot", show_copy_button=True ) with gr.Row(): ui_dict["chat_input"] = gr.Textbox(label="", placeholder="💬 输入您的问题...", scale=4, lines=2, max_lines=4) with gr.Column(scale=1, min_width=120): ui_dict["send_button"] = gr.Button("📤 发送", variant="primary", size="lg") ui_dict["clear_button"] = gr.Button("🗑️ 清空", variant="secondary", size="lg") gr.Examples( examples=["什么是传递函数?", "如何判断系统稳定性?", "解释Bode图的物理意义", "PID控制器各参数的作用"], inputs=ui_dict["chat_input"], label="💡 试试这些问题:" ) return ui_dict