增加混电算例功能

This commit is contained in:
2026-04-07 19:23:12 +08:00
parent 57ba7b5fee
commit cf9a1a2654
16 changed files with 4338 additions and 838 deletions
+331 -28
View File
@@ -1,8 +1,13 @@
import gradio as gr
import gradio as gr
from assets.knowledge_cards_html import (
TIME_DOMAIN_KNOWLEDGE,
FREQUENCY_DOMAIN_KNOWLEDGE,
ROOT_LOCUS_KNOWLEDGE
ROOT_LOCUS_KNOWLEDGE,
ENGINE_CONTROL_KNOWLEDGE,
MOTOR_CONTROL_KNOWLEDGE,
GPR_KNOWLEDGE,
NN_KNOWLEDGE,
EMS_KNOWLEDGE,
)
def create_header():
@@ -66,6 +71,20 @@ def create_time_domain_tab():
ui_dict = {}
with gr.Row():
with gr.Column(scale=1):
with gr.Group():
gr.HTML("<div class='card-title'>📊 传递函数设定</div>")
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("<div class='card-title'>🔧 系统模型</div>")
ui_dict["tf_display"] = gr.Markdown(label="当前传递函数", elem_classes="output-display")
@@ -116,6 +135,20 @@ def create_frequency_domain_tab():
ui_dict = {}
with gr.Row():
with gr.Column(scale=1):
with gr.Group():
gr.HTML("<div class='card-title'>📊 传递函数设定</div>")
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("<div class='card-title'>🎚️ 调整系统增益</div>")
ui_dict["log_k_slider"] = gr.Slider(minimum=-4, maximum=4, value=1, step=0.01, label="对数增益 log₁₀(K)", info="💡 拖动滑块查看实时变化")
@@ -127,6 +160,8 @@ def create_frequency_domain_tab():
gr.HTML("<div class='card-title'>📊 稳定裕度分析</div>")
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")
# 知识卡片
@@ -163,6 +198,20 @@ def create_root_locus_tab():
ui_dict = {}
with gr.Row():
with gr.Column(scale=1):
with gr.Group():
gr.HTML("<div class='card-title'>📊 传递函数设定</div>")
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("<div class='card-title'>🎚️ 调整系统增益</div>")
ui_dict["log_k_slider"] = gr.Slider(minimum=-4, maximum=4, value=1, step=0.01, label="对数增益 log₁₀(K)", info="💡 拖动滑块观察极点移动")
@@ -170,6 +219,8 @@ def create_root_locus_tab():
with gr.Group():
gr.HTML("<div class='card-title'>📍 闭环极点位置</div>")
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"""
@@ -201,34 +252,285 @@ def create_root_locus_tab():
return ui_dict
def create_case_demo_tab():
"""创建算例演示选项卡的UI组件"""
"""创建算例演示选项卡 — 四阶段交互设计(蒸馏→发动机→电机→能量管理)"""
ui_dict = {}
with gr.Row():
with gr.Column(scale=1):
with gr.Group():
gr.HTML("<div class='card-title'>🧪 算例参数设置</div>")
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["initial_soc"] = gr.Slider(minimum=20, maximum=90, 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["rpm_scale"] = gr.Slider(minimum=0.6, maximum=1.4, value=1.0, step=0.05, label="目标转速缩放系数")
ui_dict["load_scale"] = gr.Slider(minimum=0.6, maximum=1.4, value=1.0, step=0.05, label="负载转矩缩放系数")
ui_dict["run_button"] = gr.Button("🚀 运行混动算例", variant="primary", elem_classes="primary-btn")
with gr.Group():
gr.HTML("<div class='card-title'>📝 结果解读</div>")
ui_dict["summary"] = gr.Markdown()
with gr.Column(scale=2):
ui_dict["plot"] = gr.Plot(label="混动系统响应图")
ui_dict["table"] = gr.Dataframe(
headers=["时间(s)", "目标转速", "实际转速", "发动机功率(kW)", "电池功率(kW)", "SOC(%)"],
label="关键时刻数据",
interactive=False
# === MathJax re-render helper (reused across tabs) ===
def _mathjax_script(div_id):
return f"""
<script>
(function() {{
function renderMath() {{
setTimeout(function() {{
if (typeof MathJax !== 'undefined' && MathJax.typesetPromise) {{
MathJax.typesetPromise([document.getElementById('{div_id}')])
.catch(function(err) {{ console.log('MathJax error:', err); }});
}} else {{ setTimeout(renderMath, 500); }}
}}, 300);
}}
if (document.readyState === 'loading') {{
document.addEventListener('DOMContentLoaded', renderMath);
}} else {{ renderMath(); }}
}})();
</script>"""
with gr.Tabs():
# ========== 阶段零:模型训练(GPR + NN 两个子标签页)==========
with gr.TabItem("🧬 模型训练", id="distill_tab"):
gr.HTML("""<div style='background:linear-gradient(135deg,#f3e5f5,#e1bee7);padding:10px 16px;
border-radius:8px;margin-bottom:10px;font-size:0.92em;color:#6a1b9a;'>
<b>阶段零</b>:模型训练包含两步——先训练/验证 GPR 高斯过程代理模型,
再将其知识蒸馏为轻量 NN(MLP)用于后续实时控制仿真。</div>""")
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("<div class='card-title'>🔬 GPR 训练设置</div>")
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("<div class='card-title'>📝 GPR 结果</div>")
ui_dict["gpr_summary"] = gr.Markdown()
with gr.Column(scale=2):
ui_dict["gpr_plot"] = gr.Plot(label="GPR 模型结果")
gr.HTML(f"""
<div id="gpr-knowledge" style="max-height:600px;overflow-y:auto;padding-right:8px;">
{GPR_KNOWLEDGE}
</div>
{_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("<div class='card-title'>🧪 NN 训练参数</div>")
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("<div class='card-title'>📝 NN 训练结果</div>")
ui_dict["distill_summary"] = gr.Markdown()
with gr.Column(scale=2):
ui_dict["distill_plot"] = gr.Plot(label="NN 训练结果 (Loss + Parity)")
gr.HTML(f"""
<div id="nn-knowledge" style="max-height:600px;overflow-y:auto;padding-right:8px;">
{NN_KNOWLEDGE}
</div>
{_mathjax_script('nn-knowledge')}
""")
# ========== 阶段一:发动机控制器设计 ==========
with gr.TabItem("🔧 发动机控制器设计", id="engine_tab"):
gr.HTML("""<div style='background:linear-gradient(135deg,#fff3e0,#ffe0b2);padding:10px 16px;
border-radius:8px;margin-bottom:10px;font-size:0.92em;color:#e65100;'>
<b>阶段一</b>:选择 PID 或 MPC 控制器,调整参数,运行阶跃响应测试,观察功率跟踪性能。</div>""")
with gr.Row():
with gr.Column(scale=1):
with gr.Group():
gr.HTML("<div class='card-title'>🎯 控制器选择</div>")
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("<div class='card-title'>🎛️ PID 参数</div>")
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("<div class='card-title'>🎛️ MPC 参数</div>")
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("<div class='card-title'>⚙️ 发动机模型参数</div>")
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("<div class='card-title'>🧪 仿真设置</div>")
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("<div class='card-title'>📝 设计结果</div>")
ui_dict["eng_summary"] = gr.Markdown()
with gr.Column(scale=2):
ui_dict["eng_plot"] = gr.Plot(label="发动机控制器阶跃响应")
gr.HTML(f"""
<div id="engine-knowledge" style="max-height:600px;overflow-y:auto;padding-right:8px;">
{ENGINE_CONTROL_KNOWLEDGE}
</div>
{_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("""<div style='background:linear-gradient(135deg,#e3f2fd,#bbdefb);padding:10px 16px;
border-radius:8px;margin-bottom:10px;font-size:0.92em;color:#1565c0;'>
<b>阶段二</b>:选择 PID 或 MPC 控制器,运行转速跟踪 + 负载扰动测试。
在仿真60%时刻自动施加50%负载扰动,检验抗扰能力。</div>""")
with gr.Row():
with gr.Column(scale=1):
with gr.Group():
gr.HTML("<div class='card-title'>🎯 控制器选择</div>")
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("<div class='card-title'>🎛️ PID 参数</div>")
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("<div class='card-title'>🎛️ MPC 参数</div>")
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("<div class='card-title'>⚙️ 电机模型参数</div>")
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("<div class='card-title'>🧪 仿真设置</div>")
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("<div class='card-title'>📝 设计结果</div>")
ui_dict["mot_summary"] = gr.Markdown()
with gr.Column(scale=2):
ui_dict["mot_plot"] = gr.Plot(label="电机控制器阶跃响应")
gr.HTML(f"""
<div id="motor-knowledge" style="max-height:600px;overflow-y:auto;padding-right:8px;">
{MOTOR_CONTROL_KNOWLEDGE}
</div>
{_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("""<div style='background:linear-gradient(135deg,#e8f5e9,#c8e6c9);padding:10px 16px;
border-radius:8px;margin-bottom:10px;font-size:0.92em;color:#2e7d32;'>
<b>阶段三</b>:设计基于规则的能量管理策略(自动引用前两阶段的控制器参数)。<br>
<b>策略原理</b>SOC &lt; 下限阈值 → 进入<b>充电模式</b>;
SOC &gt; 上限阈值 → 退出充电,进入<b>功率跟随模式</b>。
下限~上限之间为<b>滞环区间</b>,防止模式频繁切换。</div>""")
with gr.Row():
with gr.Column(scale=1):
with gr.Group():
gr.HTML("<div class='card-title'>📊 SOC规则参数(滞环控制)</div>")
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("<div class='card-title'>⚡ 功率规则参数</div>")
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("<div class='card-title'>🔧 系统与仿真参数</div>")
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("<div class='card-title'>📝 结果摘要</div>")
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"""
<div id="ems-knowledge" style="max-height:600px;overflow-y:auto;padding-right:8px;">
{EMS_KNOWLEDGE}
</div>
{_mathjax_script('ems-knowledge')}
""")
return ui_dict
def create_chatbot_tab():
@@ -260,3 +562,4 @@ def create_chatbot_tab():
label="💡 试试这些问题:"
)
return ui_dict