566 lines
35 KiB
Python
566 lines
35 KiB
Python
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("<h1 class='main-title'> 自动控制理论AI+数智平台</h1>")
|
||
gr.HTML("<p class='subtitle'>✨ 交互式控制系统分析与设计工具 | 时域·频域·根轨迹·AI问答 ✨</p>")
|
||
|
||
online_counter = gr.HTML(elem_id="online-counter")
|
||
|
||
gr.HTML("""
|
||
<div class='project-info-banner'>
|
||
<div style='display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 25px;'>
|
||
<div style='display: flex; align-items: center; gap: 10px; background: rgba(255,255,255,0.1); padding: 12px 18px; border-radius: 12px; backdrop-filter: blur(10px);'>
|
||
<span style='font-size: 1.8em; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));'>📚</span>
|
||
<div>
|
||
<div style='font-size: 0.8em; color: rgba(255,255,255,0.85); font-weight: 500; letter-spacing: 0.5px;'>课程 Course</div>
|
||
<div style='font-weight: 700; font-size: 1.15em; color: white; margin-top: 2px;'>自动控制理论</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style='display: flex; align-items: center; gap: 10px; background: rgba(255,255,255,0.1); padding: 12px 18px; border-radius: 12px; backdrop-filter: blur(10px);'>
|
||
<span style='font-size: 1.8em; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));'>👨🏫</span>
|
||
<div>
|
||
<div style='font-size: 0.8em; color: rgba(255,255,255,0.85); font-weight: 500; letter-spacing: 0.5px;'>负责人 Supervisor</div>
|
||
<div style='font-weight: 700; font-size: 1.15em; color: white; margin-top: 2px;'>魏鹏飞</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style='display: flex; align-items: center; gap: 10px; background: rgba(255,255,255,0.1); padding: 12px 18px; border-radius: 12px; backdrop-filter: blur(10px);'>
|
||
<span style='font-size: 1.8em; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));'>📧</span>
|
||
<div>
|
||
<div style='font-size: 0.8em; color: rgba(255,255,255,0.85); font-weight: 500; letter-spacing: 0.5px;'>联系方式 Contact</div>
|
||
<a href='mailto:pengfeiwei@nwpu.edu.cn'
|
||
style='font-weight: 700; font-size: 1.15em; color: white; text-decoration: none;
|
||
border-bottom: 2px solid rgba(255,255,255,0.5); padding-bottom: 2px; margin-top: 2px; display: inline-block;
|
||
transition: all 0.3s ease;'
|
||
onmouseover='this.style.borderColor="white"; this.style.transform="translateY(-1px)";'
|
||
onmouseout='this.style.borderColor="rgba(255,255,255,0.5)"; this.style.transform="translateY(0)";'>
|
||
pengfeiwei@nwpu.edu.cn
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style='text-align: center; margin-top: 20px; padding-top: 18px; border-top: 2px solid rgba(255,255,255,0.2);'>
|
||
<div style='display: inline-flex; align-items: center; gap: 8px; background: rgba(255,255,255,0.08); padding: 10px 20px; border-radius: 25px; backdrop-filter: blur(5px);'>
|
||
<span style='font-size: 1.3em; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));'>🎓</span>
|
||
<span style='font-weight: 600; font-size: 1.05em; color: white;'>西北工业大学</span>
|
||
<span style='color: white; opacity: 0.9; font-size: 1.05em;'>Northwestern Polytechnical University</span>
|
||
</div>
|
||
<div style='margin-top: 10px;'>
|
||
<span style='color: white; opacity: 0.9; font-size: 0.95em; font-weight: 500;'>2025年校级本科生建设项目资助</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
""")
|
||
return online_counter
|
||
|
||
def create_time_domain_tab():
|
||
"""创建时域分析选项卡的UI组件"""
|
||
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")
|
||
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("<div class='card-title'>📈 动态性能指标</div>")
|
||
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"""
|
||
<div id="time-domain-knowledge" style="max-height: 600px; overflow-y: auto; padding-right: 8px;">
|
||
{TIME_DOMAIN_KNOWLEDGE}
|
||
</div>
|
||
<script>
|
||
(function() {{
|
||
// 等待DOM加载完成
|
||
if (document.readyState === 'loading') {{
|
||
document.addEventListener('DOMContentLoaded', renderMath);
|
||
}} else {{
|
||
renderMath();
|
||
}}
|
||
|
||
function renderMath() {{
|
||
// 延迟执行以确保MathJax已加载
|
||
setTimeout(function() {{
|
||
if (typeof MathJax !== 'undefined' && MathJax.typesetPromise) {{
|
||
MathJax.typesetPromise([document.getElementById('time-domain-knowledge')])
|
||
.catch((err) => console.log('MathJax渲染错误:', err));
|
||
}} else {{
|
||
console.log('MathJax未加载,将在500ms后重试');
|
||
setTimeout(renderMath, 500);
|
||
}}
|
||
}}, 300);
|
||
}}
|
||
}})();
|
||
</script>
|
||
""")
|
||
return ui_dict
|
||
|
||
def create_frequency_domain_tab():
|
||
"""创建频域分析选项卡的UI组件"""
|
||
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="💡 拖动滑块查看实时变化")
|
||
ui_dict["k_number_display"] = gr.Number(value=10.0, label="当前增益 K", interactive=False, elem_classes="gain-display")
|
||
with gr.Group():
|
||
gr.HTML("<div class='card-title'>🔧 当前系统模型</div>")
|
||
ui_dict["tf_display"] = gr.Markdown(label="含增益K的开环传递函数", elem_classes="output-display")
|
||
with gr.Group():
|
||
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")
|
||
# 知识卡片
|
||
gr.HTML(f"""
|
||
<div id="freq-domain-knowledge" style="max-height: 600px; overflow-y: auto; padding-right: 8px;">
|
||
{FREQUENCY_DOMAIN_KNOWLEDGE}
|
||
</div>
|
||
<script>
|
||
(function() {{
|
||
if (document.readyState === 'loading') {{
|
||
document.addEventListener('DOMContentLoaded', renderMath);
|
||
}} else {{
|
||
renderMath();
|
||
}}
|
||
|
||
function renderMath() {{
|
||
setTimeout(function() {{
|
||
if (typeof MathJax !== 'undefined' && MathJax.typesetPromise) {{
|
||
MathJax.typesetPromise([document.getElementById('freq-domain-knowledge')])
|
||
.catch((err) => console.log('MathJax渲染错误:', err));
|
||
}} else {{
|
||
console.log('MathJax未加载,将在500ms后重试');
|
||
setTimeout(renderMath, 500);
|
||
}}
|
||
}}, 300);
|
||
}}
|
||
}})();
|
||
</script>
|
||
""")
|
||
return ui_dict
|
||
|
||
def create_root_locus_tab():
|
||
"""创建根轨迹分析选项卡的UI组件"""
|
||
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="💡 拖动滑块观察极点移动")
|
||
ui_dict["k_number_display"] = gr.Number(value=10.0, label="当前增益 K", interactive=False, elem_classes="gain-display")
|
||
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"""
|
||
<div id="root-locus-knowledge" style="max-height: 600px; overflow-y: auto; padding-right: 8px;">
|
||
{ROOT_LOCUS_KNOWLEDGE}
|
||
</div>
|
||
<script>
|
||
(function() {{
|
||
if (document.readyState === 'loading') {{
|
||
document.addEventListener('DOMContentLoaded', renderMath);
|
||
}} else {{
|
||
renderMath();
|
||
}}
|
||
|
||
function renderMath() {{
|
||
setTimeout(function() {{
|
||
if (typeof MathJax !== 'undefined' && MathJax.typesetPromise) {{
|
||
MathJax.typesetPromise([document.getElementById('root-locus-knowledge')])
|
||
.catch((err) => console.log('MathJax渲染错误:', err));
|
||
}} else {{
|
||
console.log('MathJax未加载,将在500ms后重试');
|
||
setTimeout(renderMath, 500);
|
||
}}
|
||
}}, 300);
|
||
}}
|
||
}})();
|
||
</script>
|
||
""")
|
||
return ui_dict
|
||
|
||
def create_case_demo_tab():
|
||
"""创建算例演示选项卡 — 四阶段交互设计(蒸馏→发动机→电机→能量管理)"""
|
||
ui_dict = {}
|
||
|
||
# === 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 < 下限阈值 → 进入<b>充电模式</b>;
|
||
SOC > 上限阈值 → 退出充电,进入<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():
|
||
"""创建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
|
||
|