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("""
🎓
西北工业大学
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