独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数字孪生与模型预测控制 (MPC) 原理动画</title>
<style>
:root {
--bg-base: #050914;
--bg-grid: rgba(0, 255, 255, 0.03);
--color-physical: #64748b;
--color-fluid: #3b82f6;
--color-twin: #06b6d4;
--color-predict-danger: #ef4444;
--color-predict-safe: #10b981;
--color-control: #8b5cf6;
--color-data: #38bdf8;
--text-main: #f8fafc;
--text-dim: #94a3b8;
--font-tech: 'SF Mono', 'Fira Code', 'JetBrains Mono', Consolas, monospace;
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: var(--bg-base);
display: flex;
justify-content: center;
align-items: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
overflow: hidden;
background-image:
linear-gradient(var(--bg-grid) 1px, transparent 1px),
linear-gradient(90deg, var(--bg-grid) 1px, transparent 1px);
background-size: 40px 40px;
background-position: center center;
}
.animation-container {
width: 100%;
max-width: 1200px;
aspect-ratio: 16 / 9;
position: relative;
box-shadow: 0 0 100px rgba(6, 182, 212, 0.05);
border-radius: 12px;
overflow: hidden;
background: radial-gradient(circle at center, rgba(15, 23, 42, 0.8) 0%, var(--bg-base) 100%);
}
svg {
width: 100%;
height: 100%;
display: block;
}
/* 文字样式 */
.text-title {
font-family: var(--font-tech);
font-size: 14px;
fill: var(--color-twin);
font-weight: bold;
letter-spacing: 1px;
}
.text-label {
font-size: 11px;
fill: var(--text-dim);
letter-spacing: 0.5px;
}
.text-data {
font-family: var(--font-tech);
font-size: 10px;
fill: var(--text-main);
}
/* 动画类 */
.flow-line {
stroke-dasharray: 6 6;
animation: flowAnim 1s linear infinite;
}
.flow-line.reverse {
animation: flowAnimRev 1s linear infinite;
}
.flow-line.fast {
animation-duration: 0.5s;
}
@keyframes flowAnim {
to { stroke-dashoffset: -12; }
}
@keyframes flowAnimRev {
to { stroke-dashoffset: 12; }
}
/* 振动筛动画 */
.vibrating {
transform-origin: center;
animation: vibrate 0.1s linear infinite alternate;
}
@keyframes vibrate {
0% { transform: translate(-1px, -1px) rotate(-0.5deg); }
100% { transform: translate(1px, 1px) rotate(0.5deg); }
}
/* 液位波动 */
.fluid-level {
transform-origin: bottom;
animation: levelFluctuate 4s ease-in-out infinite alternate;
}
.fluid-level-twin {
transform-origin: bottom;
animation: levelFluctuateTwin 4s ease-in-out infinite alternate;
}
@keyframes levelFluctuate {
0% { transform: scaleY(0.6); }
50% { transform: scaleY(0.9); }
100% { transform: scaleY(0.5); }
}
@keyframes levelFluctuateTwin {
0% { transform: scaleY(0.6); }
50% { transform: scaleY(0.9); }
100% { transform: scaleY(0.5); }
}
/* 扫描线 */
.scan-line {
animation: scan 3s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
@keyframes scan {
0% { transform: translateY(-50px); opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { transform: translateY(200px); opacity: 0; }
}
/* 预测曲线绘制 */
.draw-curve {
stroke-dasharray: 200;
stroke-dashoffset: 200;
animation: drawCurve 4s ease-in-out infinite;
}
@keyframes drawCurve {
0%, 10% { stroke-dashoffset: 200; opacity: 0; }
20% { opacity: 1; }
70%, 100% { stroke-dashoffset: 0; opacity: 1; }
}
/* 脉冲光晕 */
.pulse-glow {
animation: pulseGlow 2s ease-in-out infinite alternate;
}
@keyframes pulseGlow {
0% { filter: drop-shadow(0 0 2px var(--color-twin)); }
100% { filter: drop-shadow(0 0 10px var(--color-twin)); }
}
.pulse-control {
animation: pulseControl 4s ease-in-out infinite;
}
@keyframes pulseControl {
0%, 60% { filter: drop-shadow(0 0 2px rgba(139, 92, 246, 0.2)); opacity: 0.5; }
70%, 80% { filter: drop-shadow(0 0 15px var(--color-control)); opacity: 1; }
100% { filter: drop-shadow(0 0 2px rgba(139, 92, 246, 0.2)); opacity: 0.5; }
}
/* 数据流粒子 */
.data-particle {
animation: moveParticle 2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
@keyframes moveParticle {
0% { offset-distance: 0%; opacity: 0; transform: scale(0.5); }
10% { opacity: 1; transform: scale(1); }
90% { opacity: 1; transform: scale(1); }
100% { offset-distance: 100%; opacity: 0; transform: scale(0.5); }
}
.data-particle-delay {
animation: moveParticle 2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
animation-delay: 1s;
}
/* 控制阀门旋转 */
.valve-wheel {
transform-origin: center;
animation: valveSpin 4s ease-in-out infinite;
}
@keyframes valveSpin {
0%, 60% { transform: rotate(0deg); }
70%, 80% { transform: rotate(90deg); stroke: var(--color-control); }
100% { transform: rotate(0deg); }
}
/* 齿轮旋转 */
.gear-spin {
transform-origin: center;
animation: spin 6s linear infinite;
}
.gear-spin-reverse {
transform-origin: center;
animation: spinRev 4s linear infinite;
}
@keyframes spin { 100% { transform: rotate(360deg); } }
@keyframes spinRev { 100% { transform: rotate(-360deg); } }
</style>
</head>
<body>
<div class="animation-container">
<svg viewBox="0 0 1200 675" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 滤镜与渐变 -->
<filter id="glow-twin" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glow-control" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glow-predict" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<linearGradient id="fluid-grad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#60a5fa" />
<stop offset="100%" stop-color="#2563eb" />
</linearGradient>
<linearGradient id="twin-fluid-grad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(6, 182, 212, 0.8)" />
<stop offset="100%" stop-color="rgba(6, 182, 212, 0.2)" />
</linearGradient>
<linearGradient id="grid-grad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(6, 182, 212, 0.1)" />
<stop offset="100%" stop-color="transparent" />
</linearGradient>
<marker id="arrow-data" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,2 L8,5 L0,8 Z" fill="var(--color-data)" />
</marker>
<marker id="arrow-control" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,2 L8,5 L0,8 Z" fill="var(--color-control)" />
</marker>
<!-- 路径定义供粒子运动使用 -->
<path id="path-sensor1" d="M 320 400 C 320 280, 480 320, 500 200" fill="none" />
<path id="path-sensor2" d="M 230 520 C 230 380, 450 400, 500 200" fill="none" />
<path id="path-control" d="M 700 200 C 750 350, 400 480, 360 480" fill="none" />
</defs>
<!-- 背景网格辅助线 -->
<g opacity="0.2">
<line x1="600" y1="50" x2="600" y2="625" stroke="var(--color-twin)" stroke-width="1" stroke-dasharray="2 4" />
<line x1="100" y1="337.5" x2="1100" y2="337.5" stroke="var(--color-twin)" stroke-width="1" stroke-dasharray="2 4" />
<circle cx="600" cy="337.5" r="150" fill="none" stroke="var(--color-twin)" stroke-width="0.5" stroke-dasharray="4 8" />
</g>
<!-- ========================================== -->
<!-- 左侧/下方:物理实体系统 (Physical System) -->
<!-- ========================================== -->
<g transform="translate(150, 320)">
<!-- 标题说明 -->
<text x="0" y="-120" class="text-title" fill="var(--color-physical)">PHYSICAL LAYER</text>
<text x="0" y="-100" class="text-label">现实工况与设备</text>
<!-- 进液管 -->
<path d="M -80 -60 L 20 -60 L 20 -20" fill="none" stroke="var(--color-physical)" stroke-width="12" stroke-linejoin="round"/>
<path d="M -80 -60 L 20 -60 L 20 -20" fill="none" stroke="var(--color-fluid)" stroke-width="6" class="flow-line" stroke-linejoin="round"/>
<!-- 振动筛 (带振动动画) -->
<g class="vibrating">
<rect x="10" y="-20" width="120" height="15" rx="2" fill="#475569" stroke="#94a3b8" stroke-width="2" transform="rotate(15)" />
<line x1="20" y1="-15" x2="120" y2="10" stroke="#0f172a" stroke-width="2" stroke-dasharray="4 4" transform="rotate(15)" />
<!-- 筛分后的液体滴落 -->
<line x1="50" y1="0" x2="50" y2="40" stroke="var(--color-fluid)" stroke-width="2" class="flow-line fast" opacity="0.7"/>
<line x1="70" y1="5" x2="70" y2="40" stroke="var(--color-fluid)" stroke-width="2" class="flow-line fast" opacity="0.6"/>
<line x1="90" y1="10" x2="90" y2="40" stroke="var(--color-fluid)" stroke-width="2" class="flow-line fast" opacity="0.8"/>
</g>
<text x="130" y="-10" class="text-label">振动筛 (固液分离)</text>
<!-- 缓冲罐 -->
<path d="M 30 40 L 30 180 A 10 10 0 0 0 40 190 L 140 190 A 10 10 0 0 0 150 180 L 150 40" fill="rgba(30, 41, 59, 0.8)" stroke="var(--color-physical)" stroke-width="3" />
<!-- 内部液体 (带液位波动动画) -->
<g transform="translate(33, 187)">
<rect x="0" y="-140" width="114" height="140" fill="url(#fluid-grad)" class="fluid-level" />
</g>
<text x="90" y="210" class="text-label" text-anchor="middle">缓冲罐 (Buffer Tank)</text>
<!-- 出液管与控制阀 (PLC) -->
<path d="M 150 160 L 220 160 L 220 200" fill="none" stroke="var(--color-physical)" stroke-width="12" stroke-linejoin="round"/>
<path d="M 150 160 L 220 160 L 220 200" fill="none" stroke="var(--color-fluid)" stroke-width="6" class="flow-line" stroke-linejoin="round"/>
<!-- PLC 控制阀门 -->
<g transform="translate(185, 160)">
<rect x="-10" y="-15" width="20" height="30" fill="#334155" />
<circle cx="0" cy="0" r="12" fill="#1e293b" stroke="var(--color-physical)" stroke-width="2" />
<path d="M -8 0 L 8 0 M 0 -8 L 0 8" stroke="var(--color-physical)" stroke-width="2" class="valve-wheel" />
<text x="0" y="30" class="text-label" text-anchor="middle" fill="var(--color-control)">PLC 控制执行</text>
</g>
<!-- 物理层传感器节点 -->
<circle cx="140" cy="80" r="4" fill="var(--color-data)" /> <!-- 液位传感器 -->
<circle cx="50" cy="-20" r="4" fill="var(--color-data)" /> <!-- 流量/振动传感器 -->
<polyline points="140,80 170,80" fill="none" stroke="var(--color-data)" stroke-width="1" opacity="0.5" />
<text x="175" y="83" class="text-data">LVL_SENS</text>
</g>
<!-- ========================================== -->
<!-- 右侧/上方:数字孪生模型 (Digital Twin) -->
<!-- ========================================== -->
<g transform="translate(750, 150)">
<text x="0" y="-60" class="text-title" filter="url(#glow-twin)">DIGITAL TWIN MODEL</text>
<text x="0" y="-40" class="text-label">实时流体力学与机械耦合仿真</text>
<!-- 孪生坐标系基座 -->
<path d="M -50 250 L 250 250 L 300 200 L 0 200 Z" fill="url(#grid-grad)" stroke="var(--color-twin)" stroke-width="1" opacity="0.3" />
<!-- 孪生缓冲罐 (线框发光风格) -->
<g transform="translate(50, 50)" filter="url(#glow-twin)">
<!-- 扫描线效果 -->
<rect x="30" y="40" width="120" height="2" fill="var(--color-twin)" class="scan-line" />
<path d="M 30 40 L 30 180 A 10 10 0 0 0 40 190 L 140 190 A 10 10 0 0 0 150 180 L 150 40" fill="rgba(6, 182, 212, 0.05)" stroke="var(--color-twin)" stroke-width="1.5" stroke-dasharray="4 2" />
<!-- 孪生液体 -->
<g transform="translate(33, 187)">
<rect x="0" y="-140" width="114" height="140" fill="url(#twin-fluid-grad)" class="fluid-level-twin" />
</g>
<!-- 孪生振动筛 -->
<rect x="10" y="-30" width="120" height="15" rx="2" fill="none" stroke="var(--color-twin)" stroke-width="1" transform="rotate(15)" stroke-dasharray="2 2"/>
<!-- 孪生数据标注 -->
<g opacity="0.7">
<line x1="150" y1="110" x2="200" y2="110" stroke="var(--color-twin)" stroke-width="1" />
<circle cx="200" cy="110" r="2" fill="var(--color-twin)" />
<text x="210" y="108" class="text-data">Vol: 78.4%</text>
<text x="210" y="120" class="text-data">Rho: 1.2g/cm³</text>
</g>
</g>
<!-- 模型计算节点旋转特效 -->
<g transform="translate(250, -10)" class="pulse-glow" opacity="0.8">
<circle cx="0" cy="0" r="30" fill="none" stroke="var(--color-twin)" stroke-width="1" stroke-dasharray="10 5" class="gear-spin" />
<circle cx="0" cy="0" r="20" fill="none" stroke="rgba(6,182,212,0.5)" stroke-width="2" stroke-dasharray="5 5" class="gear-spin-reverse" />
<polygon points="0,-8 7,4 -7,4" fill="var(--color-twin)" />
<text x="0" y="45" class="text-data" text-anchor="middle">EDGE CPU/GPU</text>
</g>
</g>
<!-- ========================================== -->
<!-- 中央核心:模型预测控制 (MPC) 推演区域 -->
<!-- ========================================== -->
<g transform="translate(420, 80)">
<rect x="0" y="0" width="280" height="160" fill="rgba(15, 23, 42, 0.9)" stroke="#334155" stroke-width="1" rx="8" />
<text x="140" y="25" class="text-title" text-anchor="middle" fill="#f8fafc">MPC 模型预测控制序列</text>
<text x="140" y="40" class="text-label" text-anchor="middle">预测时域: 20s | 控制周期: 2s</text>
<!-- 坐标系 -->
<g transform="translate(30, 130)">
<!-- X/Y 轴 -->
<line x1="0" y1="0" x2="230" y2="0" stroke="#475569" stroke-width="1.5" />
<line x1="0" y1="0" x2="0" y2="-70" stroke="#475569" stroke-width="1.5" />
<!-- 当前时间线 (T=0) -->
<line x1="40" y1="0" x2="40" y2="-70" stroke="#94a3b8" stroke-width="1" stroke-dasharray="2 2" />
<text x="40" y="15" class="text-data" text-anchor="middle">Now</text>
<!-- 预测终点线 (T=20s) -->
<line x1="200" y1="0" x2="200" y2="-70" stroke="#475569" stroke-width="1" stroke-dasharray="2 2" />
<text x="200" y="15" class="text-data" text-anchor="middle">+20s</text>
<!-- 安全上限阈值线 -->
<line x1="0" y1="-60" x2="230" y2="-60" stroke="rgba(239, 68, 68, 0.4)" stroke-width="1" stroke-dasharray="4 4" />
<text x="235" y="-57" class="text-data" fill="var(--color-predict-danger)">上限</text>
<!-- 曲线1:无控制的未来推演 (走向失控) -->
<path d="M 0 -30 C 20 -30, 30 -35, 40 -40 C 80 -60, 120 -80, 200 -85" fill="none" stroke="var(--color-predict-danger)" stroke-width="2" stroke-dasharray="4 2" opacity="0.6" class="draw-curve" />
<!-- 曲线2:MPC最优控制下的推演 (平稳安全) -->
<path d="M 0 -30 C 20 -30, 30 -35, 40 -40 C 60 -50, 80 -45, 120 -40 C 160 -35, 180 -38, 200 -40" fill="none" stroke="var(--color-predict-safe)" stroke-width="2.5" filter="url(#glow-predict)" class="draw-curve" />
<!-- 交汇点动画 -->
<circle cx="40" cy="-40" r="3" fill="#fff" />
</g>
<!-- 最优控制设定下发提示 -->
<g transform="translate(140, 150)" class="pulse-control">
<rect x="-60" y="-10" width="120" height="20" rx="10" fill="rgba(139, 92, 246, 0.2)" stroke="var(--color-control)" stroke-width="1" />
<text x="0" y="4" class="text-data" fill="var(--color-control)" text-anchor="middle" font-weight="bold">↓ 最优设定下发</text>
</g>
</g>
<!-- ========================================== -->
<!-- 数据链路与通信流向 -->
<!-- ========================================== -->
<!-- 1. 物理层 -> 数据采集网关 -> 孪生模型 (上传数据) -->
<g opacity="0.7">
<!-- 连线路径 (不可见,用作参考线) -->
<path d="M 290 400 C 290 250, 480 320, 560 160" fill="none" stroke="rgba(56, 189, 248, 0.2)" stroke-width="2" stroke-dasharray="4 4" />
<!-- 运动粒子 -->
<circle r="4" fill="var(--color-data)" filter="url(#glow-predict)">
<animateMotion dur="2.5s" repeatCount="indefinite" path="M 290 400 C 290 250, 480 320, 560 160" keyPoints="0;1" keyTimes="0;1" calcMode="linear" />
</circle>
<circle r="3" fill="#fff">
<animateMotion dur="2.5s" begin="1.2s" repeatCount="indefinite" path="M 290 400 C 290 250, 480 320, 560 160" keyPoints="0;1" keyTimes="0;1" calcMode="linear" />
</circle>
<path d="M 200 300 C 200 150, 400 200, 420 160" fill="none" stroke="rgba(56, 189, 248, 0.2)" stroke-width="2" stroke-dasharray="4 4" />
<circle r="3.5" fill="var(--color-data)">
<animateMotion dur="2s" begin="0.5s" repeatCount="indefinite" path="M 200 300 C 200 150, 400 200, 420 160" keyPoints="0;1" keyTimes="0;1" calcMode="linear" />
</circle>
</g>
<!-- 采集网关节点 -->
<g transform="translate(320, 320)">
<rect x="-30" y="-15" width="60" height="30" rx="4" fill="#0f172a" stroke="var(--color-data)" stroke-width="1.5" />
<text x="0" y="4" class="text-data" text-anchor="middle" fill="var(--color-data)">网关</text>
<text x="-40" y="30" class="text-label" fill="var(--color-data)">在线状态更新</text>
</g>
<!-- 2. MPC 模型 -> 物理层 PLC (下发控制) -->
<g class="pulse-control">
<path d="M 560 240 C 560 380, 450 480, 365 480" fill="none" stroke="var(--color-control)" stroke-width="2" marker-end="url(#arrow-control)" stroke-dasharray="8 4" class="flow-line reverse" />
<text x="480" y="380" class="text-data" fill="var(--color-control)" font-weight="bold">调整阀门开度 & 频率</text>
<!-- 下发粒子 -->
<circle r="5" fill="var(--color-control)" filter="url(#glow-control)">
<animateMotion dur="4s" repeatCount="indefinite" path="M 560 240 C 560 380, 450 480, 365 480" keyPoints="0;1" keyTimes="0;1" calcMode="linear" />
</circle>
</g>
<!-- 图例说明 (边缘,不遮挡主体) -->
<g transform="translate(40, 600)" opacity="0.8">
<rect x="0" y="0" width="12" height="12" fill="none" stroke="var(--color-data)" stroke-width="2" stroke-dasharray="2 2" />
<text x="20" y="10" class="text-label">实时状态数据上报</text>
<rect x="130" y="0" width="12" height="12" fill="none" stroke="var(--color-control)" stroke-width="2" />
<text x="150" y="10" class="text-label">MPC 预测控制序列下发</text>
<rect x="290" y="0" width="12" height="12" fill="none" stroke="var(--color-twin)" stroke-width="2" />
<text x="310" y="10" class="text-label">理想解:无需增加硬件,仅靠算力预判消除波动矛盾</text>
</g>
</svg>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数字孪生实时预测与 MPC 控制系统</title>
<style>
:root {
/* 主题色板 - 赛博数字孪生美学 */
--bg-color: #050b14;
--panel-bg: #0b1320;
--text-main: #f8fafc;
--text-muted: #64748b;
--accent-blue: #0ea5e9;
--accent-cyan: #22d3ee;
--accent-orange: #f97316;
--accent-green: #10b981;
--physical-color: #475569;
--grid-color: rgba(14, 165, 233, 0.05);
}
body {
margin: 0;
padding: 0;
background-color: var(--bg-color);
color: var(--text-main);
font-family: 'PingFang SC', 'Microsoft YaHei', 'Segoe UI', system-ui, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
overflow: hidden;
background-image: radial-gradient(circle at 50% 0%, #0f2440 0%, transparent 70%);
}
.container {
width: 100%;
max-width: 1280px;
display: flex;
flex-direction: column;
gap: 24px;
padding: 24px;
box-sizing: border-box;
}
.header {
display: flex;
justify-content: space-between;
align-items: flex-end;
border-bottom: 1px solid rgba(14, 165, 233, 0.15);
padding-bottom: 16px;
}
.title-block h1 {
margin: 0 0 8px 0;
font-size: 26px;
font-weight: 600;
letter-spacing: 2px;
background: linear-gradient(90deg, var(--accent-cyan), var(--accent-blue));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 20px rgba(34, 211, 238, 0.2);
}
.title-block p {
margin: 0;
font-size: 14px;
color: var(--text-muted);
letter-spacing: 1px;
}
.controls {
display: flex;
gap: 24px;
background: rgba(11, 19, 32, 0.8);
padding: 16px 28px;
border-radius: 8px;
border: 1px solid rgba(14, 165, 233, 0.2);
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.control-group {
display: flex;
flex-direction: column;
gap: 10px;
}
.control-group label {
font-size: 12px;
color: var(--accent-cyan);
letter-spacing: 1px;
font-weight: 500;
}
/* 自定义滑块样式 */
input[type="range"] {
-webkit-appearance: none;
width: 160px;
background: transparent;
}
input[type="range"]:focus {
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: var(--accent-orange);
cursor: pointer;
margin-top: -6px;
box-shadow: 0 0 12px rgba(249, 115, 22, 0.6);
border: 2px solid #fff;
transition: transform 0.1s;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2);
}
input[type="range"]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
}
.svg-container {
width: 100%;
background: var(--panel-bg);
border-radius: 12px;
border: 1px solid rgba(14, 165, 233, 0.1);
box-shadow: inset 0 0 60px rgba(0,0,0,0.8), 0 20px 40px rgba(0,0,0,0.4);
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
/* SVG 内核动画定义 */
@keyframes flowDash {
from { stroke-dashoffset: 20; }
to { stroke-dashoffset: 0; }
}
@keyframes flowDashReverse {
from { stroke-dashoffset: 0; }
to { stroke-dashoffset: 20; }
}
.flow-data {
stroke-dasharray: 4 6;
animation: flowDashReverse 0.6s linear infinite;
}
.flow-control {
stroke-dasharray: 4 6;
animation: flowDash 0.6s linear infinite;
}
.flow-liquid {
stroke-dasharray: 6 8;
animation: flowDashReverse 0.4s linear infinite;
}
@keyframes dataPulse {
0% { opacity: 0.3; transform: scale(0.9); }
50% { opacity: 1; transform: scale(1.1); filter: drop-shadow(0 0 8px var(--accent-cyan)); }
100% { opacity: 0.3; transform: scale(0.9); }
}
.data-node {
transform-origin: center;
animation: dataPulse 1.5s ease-in-out infinite;
}
@keyframes scanline {
0% { transform: translateY(-10px); opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { transform: translateY(220px); opacity: 0; }
}
.scan-line {
animation: scanline 2.5s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
@keyframes shake {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(-1.5px, 1.5px) rotate(-0.5deg); }
50% { transform: translate(1.5px, -1.5px) rotate(0.5deg); }
75% { transform: translate(-1.5px, -1.5px) rotate(0deg); }
}
.vibrating {
animation: shake 0.08s linear infinite;
transform-origin: center;
}
/* 辉光特效类 */
.glow-blue { filter: drop-shadow(0 0 10px rgba(14, 165, 233, 0.6)); }
.glow-cyan { filter: drop-shadow(0 0 12px rgba(34, 211, 238, 0.8)); }
.glow-orange { filter: drop-shadow(0 0 12px rgba(249, 115, 22, 0.8)); }
.glow-green { filter: drop-shadow(0 0 10px rgba(16, 185, 129, 0.6)); }
.tech-text {
font-family: 'Consolas', 'Courier New', monospace;
letter-spacing: 0.5px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="title-block">
<h1>数字孪生智能预测与 MPC 控制系统</h1>
<p>基于最终理想解 (IFR):利用在线异构数据驱动水力/机械耦合模型,实现自主寻优与零滞后控制</p>
</div>
<div class="controls">
<div class="control-group">
<label>来液工况扰动模拟 (流入率)</label>
<div style="display: flex; align-items: center; gap: 12px;">
<input type="range" id="inflowSlider" min="10" max="90" value="50">
<span id="inflowVal" style="font-size: 14px; color: var(--accent-cyan); font-weight: bold; width: 36px; text-align: right;">50%</span>
</div>
</div>
<div class="control-group" style="border-left: 1px solid rgba(255,255,255,0.1); padding-left: 20px;">
<label>系统关键参数</label>
<div style="font-size: 13px; color: var(--text-main); padding-top: 4px; font-family: monospace;">
Tp=20s | Tc=2s
</div>
</div>
</div>
</div>
<div class="svg-container">
<!-- 使用 viewBox 确保响应式缩放,居中对齐 -->
<svg id="mainSvg" viewBox="0 0 1150 560" width="100%" height="100%">
<defs>
<!-- 液体渐变 -->
<linearGradient id="liquidGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0ea5e9" />
<stop offset="100%" stop-color="#0284c7" />
</linearGradient>
<linearGradient id="twinLiquidGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(34, 211, 238, 0.5)" />
<stop offset="100%" stop-color="rgba(14, 165, 233, 0.2)" />
</linearGradient>
<!-- 背景网格图案 -->
<pattern id="bgGrid" width="30" height="30" patternUnits="userSpaceOnUse">
<path d="M 30 0 L 0 0 0 30" fill="none" stroke="var(--grid-color)" stroke-width="1"/>
</pattern>
<!-- 箭头标记 -->
<marker id="arrowData" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
<path d="M0,1 L7,4 L0,7 Z" fill="var(--accent-cyan)" />
</marker>
<marker id="arrowControl" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
<path d="M0,1 L7,4 L0,7 Z" fill="var(--accent-orange)" />
</marker>
</defs>
<!-- 渲染背景网格 -->
<rect width="1150" height="560" fill="url(#bgGrid)" />
<!-- ==================== 左侧:物理实体世界 (Physical Assets) ==================== -->
<g id="physical-world" transform="translate(60, 100)">
<!-- 区域标题 -->
<rect x="0" y="-45" width="220" height="24" fill="rgba(71, 85, 105, 0.2)" rx="4"/>
<text x="10" y="-28" fill="#cbd5e1" font-size="14" font-weight="bold" letter-spacing="1">PHYSICAL ASSETS 物理实体</text>
<!-- 进液管道 -->
<path d="M 0 30 L 60 30 L 60 70" fill="none" stroke="#334155" stroke-width="14" stroke-linejoin="round"/>
<path d="M 0 30 L 60 30 L 60 70" fill="none" stroke="#0ea5e9" stroke-width="6" stroke-linejoin="round" class="flow-liquid" id="inflowLine"/>
<!-- 振动筛 (Vibrating Screen) -->
<g class="vibrating" id="screenMotor">
<!-- 机械外壳 -->
<rect x="25" y="70" width="90" height="45" fill="#1e293b" rx="4" stroke="#64748b" stroke-width="2"/>
<!-- 内部筛网结构 -->
<path d="M 35 92 L 45 82 L 55 102 L 65 82 L 75 102 L 85 82 L 95 92" fill="none" stroke="#94a3b8" stroke-width="3"/>
<circle cx="115" cy="92.5" r="10" fill="#334155" stroke="#cbd5e1" stroke-width="2"/>
<circle cx="115" cy="92.5" r="3" fill="#cbd5e1"/>
<text x="35" y="132" font-size="12" fill="#94a3b8">振动筛结构</text>
</g>
<!-- 筛分后流入缓冲罐 -->
<path d="M 70 120 L 70 160" fill="none" stroke="#0ea5e9" stroke-width="10" stroke-dasharray="4 4" class="flow-liquid" id="middleFlow"/>
<!-- 缓冲罐 (Buffer Tank) -->
<path d="M 20 160 L 20 320 A 50 12 0 0 0 120 320 L 120 160" fill="none" stroke="#475569" stroke-width="4"/>
<ellipse cx="70" cy="160" rx="50" ry="12" fill="none" stroke="#475569" stroke-width="4"/>
<!-- 物理液位渲染 -->
<g clip-path="url(#tankClip)">
<clipPath id="tankClip">
<path d="M 22 160 L 22 320 A 48 10 0 0 0 118 320 L 118 160 Z" />
</clipPath>
<!-- 动态液体矩形 -->
<rect id="physLiquid" x="20" y="240" width="100" height="100" fill="url(#liquidGrad)" />
<!-- 动态液体表面椭圆 -->
<ellipse id="physLiquidSurface" cx="70" cy="240" rx="48" ry="10" fill="#38bdf8" />
</g>
<text x="45" y="350" font-size="12" fill="#94a3b8">缓冲罐</text>
<!-- 液位传感器 -->
<rect x="130" y="200" width="18" height="18" fill="#0f172a" stroke="var(--accent-cyan)" stroke-width="2" class="glow-cyan"/>
<line x1="120" y1="209" x2="130" y2="209" stroke="var(--accent-cyan)" stroke-width="2"/>
<text x="155" y="213" font-size="11" fill="var(--accent-cyan)">高精度液位变送器</text>
<circle cx="139" cy="209" r="3" fill="var(--accent-cyan)" class="data-node"/>
<!-- 出液管道与 PLC 阀门 -->
<path d="M 70 332 L 70 380 L 190 380" fill="none" stroke="#334155" stroke-width="14" stroke-linejoin="round"/>
<path d="M 70 332 L 70 380 L 190 380" fill="none" stroke="#0ea5e9" stroke-width="6" stroke-linejoin="round" class="flow-liquid" id="outflowLine"/>
<!-- PLC 控制器组件 -->
<g transform="translate(130, 380)">
<circle cx="0" cy="0" r="16" fill="#1e293b" stroke="var(--accent-orange)" stroke-width="2.5" class="glow-orange"/>
<path d="M -8 -8 L 8 8 M -8 8 L 8 -8" stroke="var(--accent-orange)" stroke-width="2.5"/>
<rect x="-20" y="35" width="40" height="24" fill="#0f172a" stroke="#f97316" rx="4"/>
<text x="-12" y="52" font-size="12" fill="var(--accent-orange)" font-weight="bold" class="tech-text">PLC</text>
<line x1="0" y1="35" x2="0" y2="16" stroke="var(--accent-orange)" stroke-width="2" stroke-dasharray="3 3"/>
</g>
</g>
<!-- ==================== 中间:边缘计算与数据网关 ==================== -->
<g id="edge-compute" transform="translate(420, 150)">
<!-- 核心计算节点外壳 -->
<rect x="0" y="0" width="120" height="170" fill="rgba(15, 23, 42, 0.8)" stroke="var(--accent-cyan)" stroke-width="1.5" rx="8" class="glow-blue"/>
<!-- 装饰条纹与节点指示灯 -->
<rect x="15" y="15" width="90" height="24" fill="#1e293b" rx="3"/>
<circle cx="28" cy="27" r="4" fill="var(--accent-green)" class="data-node"/>
<circle cx="45" cy="27" r="4" fill="var(--accent-green)" />
<circle cx="62" cy="27" r="4" fill="var(--text-muted)" />
<!-- i7 / GPU 模块 -->
<rect x="15" y="50" width="90" height="55" fill="rgba(14, 165, 233, 0.1)" stroke="rgba(14, 165, 233, 0.3)" rx="3"/>
<path d="M 30 77 L 45 62 L 60 77 L 75 62" fill="none" stroke="var(--accent-cyan)" stroke-width="1.5" opacity="0.5"/>
<text x="25" y="70" font-size="10" fill="var(--text-main)" class="tech-text">Intel i7+GPU</text>
<text x="22" y="90" font-size="11" fill="var(--accent-cyan)">边缘计算工控机</text>
<!-- 数据采集网关 -->
<rect x="15" y="120" width="90" height="35" fill="#1e293b" rx="3"/>
<text x="26" y="142" font-size="12" fill="var(--accent-blue)" font-weight="bold">数据采集网关</text>
<!-- 【核心动作】上传链路 (传感器 -> 网关) -->
<path d="M -160 150 L -90 150 L -90 137 L -10 137" fill="none" stroke="var(--accent-cyan)" stroke-width="2" class="flow-data" marker-end="url(#arrowData)"/>
<text x="-120" y="130" font-size="12" fill="var(--accent-cyan)" class="glow-cyan">历史与状态数据</text>
<!-- 【核心动作】下发链路 (网关 -> PLC) -->
<path d="M 60 170 L 60 330 L -215 330" fill="none" stroke="var(--accent-orange)" stroke-width="2.5" class="flow-control" marker-end="url(#arrowControl)"/>
<text x="-100" y="320" font-size="12" fill="var(--accent-orange)" font-weight="bold" class="glow-orange">最优控制设定下发 (消除矛盾)</text>
</g>
<!-- ==================== 右侧:数字孪生与预测世界 (Digital Twin) ==================== -->
<g id="digital-twin" transform="translate(640, 100)">
<!-- 孪生区域外框 -->
<rect x="0" y="-45" width="460" height="465" fill="rgba(14, 165, 233, 0.02)" stroke="rgba(34, 211, 238, 0.3)" stroke-width="1" rx="12" stroke-dasharray="10 5"/>
<text x="20" y="-28" fill="var(--accent-cyan)" font-size="14" font-weight="bold" letter-spacing="1" class="glow-cyan">DIGITAL TWIN 实时数字孪生模型</text>
<!-- 数据中继:Edge 到 Twin -->
<path d="M -100 180 L 20 180" fill="none" stroke="var(--accent-cyan)" stroke-width="2" class="flow-data" marker-end="url(#arrowData)"/>
<!-- 控制中继:Twin 到 Edge -->
<path d="M 20 220 L -100 220" fill="none" stroke="var(--accent-orange)" stroke-width="2.5" class="flow-control" marker-end="url(#arrowControl)"/>
<!-- 1. 虚拟映射模块 (线框全息风格) -->
<g transform="translate(30, 40)">
<!-- 全息扫描线特效 -->
<rect x="-10" y="0" width="140" height="2" fill="var(--accent-cyan)" class="scan-line glow-cyan"/>
<!-- 孪生振动筛 -->
<rect x="15" y="10" width="90" height="45" fill="none" rx="4" stroke="var(--accent-cyan)" stroke-width="1.5" stroke-dasharray="3 3"/>
<path d="M 25 32 L 35 22 L 45 42 L 55 22 L 65 42 L 75 22 L 85 32" fill="none" stroke="var(--accent-cyan)" stroke-width="1.5" stroke-dasharray="3 3"/>
<!-- 孪生液流 -->
<path d="M 60 60 L 60 100" fill="none" stroke="var(--accent-cyan)" stroke-width="4" stroke-dasharray="4 6" class="flow-liquid"/>
<!-- 孪生缓冲罐 -->
<path d="M 10 100 L 10 260 A 50 12 0 0 0 110 260 L 110 100" fill="none" stroke="var(--accent-cyan)" stroke-width="2" stroke-dasharray="6 3"/>
<ellipse cx="60" cy="100" rx="50" ry="12" fill="none" stroke="var(--accent-cyan)" stroke-width="2" stroke-dasharray="6 3"/>
<!-- 孪生液位投影 -->
<g clip-path="url(#twinTankClip)">
<clipPath id="twinTankClip">
<path d="M 12 100 L 12 260 A 48 10 0 0 0 108 260 L 108 100 Z" />
</clipPath>
<rect id="twinLiquid" x="10" y="180" width="100" height="100" fill="url(#twinLiquidGrad)" />
<ellipse id="twinLiquidSurface" cx="60" cy="180" rx="48" ry="10" fill="rgba(34, 211, 238, 0.7)" />
</g>
<text x="12" y="295" font-size="12" fill="var(--accent-cyan)" class="glow-cyan">水力/机械耦合全真模型</text>
</g>
<!-- 2. MPC 预测与图表区 (核心机理展示) -->
<g transform="translate(180, 20)">
<rect x="0" y="0" width="260" height="220" fill="rgba(15, 23, 42, 0.6)" stroke="rgba(255,255,255,0.05)" stroke-width="1" rx="6"/>
<rect x="0" y="0" width="260" height="30" fill="rgba(249, 115, 22, 0.1)" rx="6"/>
<text x="12" y="20" font-size="13" fill="var(--text-main)" font-weight="bold">MPC 模型预测控制寻优推演</text>
<!-- 图表坐标轴 -->
<line x1="30" y1="180" x2="240" y2="180" stroke="var(--text-muted)" stroke-width="1"/>
<line x1="30" y1="50" x2="30" y2="180" stroke="var(--text-muted)" stroke-width="1"/>
<text x="5" y="60" font-size="10" fill="var(--text-muted)">100%</text>
<text x="12" y="180" font-size="10" fill="var(--text-muted)">0%</text>
<text x="225" y="195" font-size="10" fill="var(--text-muted)">t(s)</text>
<!-- 当前时间分割线 -->
<line x1="100" y1="50" x2="100" y2="180" stroke="var(--accent-cyan)" stroke-width="1.5" stroke-dasharray="3 3"/>
<rect x="85" y="185" width="30" height="16" fill="var(--accent-cyan)" rx="2"/>
<text x="90" y="196" font-size="10" fill="#000" font-weight="bold">NOW</text>
<!-- 预测时域结束线 -->
<line x1="220" y1="50" x2="220" y2="180" stroke="var(--accent-orange)" stroke-width="1" stroke-dasharray="2 2"/>
<text x="210" y="195" font-size="10" fill="var(--accent-orange)">+20s</text>
<!-- 动态曲线绘制路径 -->
<!-- 历史轨迹 -->
<path id="historyPath" d="" fill="none" stroke="var(--accent-cyan)" stroke-width="2.5" class="glow-cyan"/>
<!-- 未来预测推演轨迹 (虚线) -->
<path id="predictedPath" d="" fill="none" stroke="var(--accent-orange)" stroke-width="2.5" stroke-dasharray="5 4" class="glow-orange"/>
<!-- 目标点闪烁圆 -->
<circle id="predictionTarget" cx="220" cy="115" r="4" fill="var(--accent-orange)" class="glow-orange">
<animate attributeName="r" values="3;6;3" dur="1s" repeatCount="indefinite" />
</circle>
<!-- 最终理想解标尺 -->
<line x1="30" y1="115" x2="240" y2="115" stroke="var(--accent-green)" stroke-width="1" stroke-dasharray="4 4"/>
<text x="125" y="110" font-size="10" fill="var(--accent-green)" font-weight="bold">理想液位目标带 (IFR)</text>
</g>
<!-- 3. 动态机制与参数面板 HUD -->
<g transform="translate(180, 260)">
<rect x="0" y="0" width="260" height="130" fill="rgba(0, 0, 0, 0.3)" border="1px solid #333" rx="6"/>
<text x="12" y="24" font-size="13" fill="var(--accent-green)" font-weight="bold" letter-spacing="1">>> 在线演化计算矩阵</text>
<g transform="translate(12, 45)" font-size="11" fill="#cbd5e1" class="tech-text" spacing="16">
<text y="0">1.读取工况,融合时序特征</text>
<text y="18">2.推演未来 10-20s 演化边界</text>
<text y="36">3.MPC生成全局最优控制序列</text>
<text y="54" fill="var(--accent-orange)">4.取当前最优步下发防滞后</text>
</g>
<!-- 动态读数 -->
<text id="hudSimTime" x="15" y="115" font-size="11" fill="var(--accent-cyan)" class="tech-text">Sim_Time: 00:00.00</text>
<text id="hudCost" x="140" y="115" font-size="11" fill="var(--accent-orange)" class="tech-text">Cost_Func: MIN</text>
</g>
</g>
</svg>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
// --- 交互与 DOM 元素获取 ---
const inflowSlider = document.getElementById("inflowSlider");
const inflowValText = document.getElementById("inflowVal");
const physLiquid = document.getElementById("physLiquid");
const physLiquidSurface = document.getElementById("physLiquidSurface");
const twinLiquid = document.getElementById("twinLiquid");
const twinLiquidSurface = document.getElementById("twinLiquidSurface");
const outflowLine = document.getElementById("outflowLine");
const historyPath = document.getElementById("historyPath");
const predictedPath = document.getElementById("predictedPath");
const predictionTarget = document.getElementById("predictionTarget");
const hudSimTime = document.getElementById("hudSimTime");
// --- 容器与坐标常量 ---
const TANK_Y_TOP = 160;
const TANK_Y_BOTTOM = 320;
const TANK_HEIGHT = TANK_Y_BOTTOM - TANK_Y_TOP;
const TWIN_Y_TOP = 100;
const TWIN_Y_BOTTOM = 260;
const TWIN_HEIGHT = TWIN_Y_BOTTOM - TWIN_Y_TOP;
const TARGET_LEVEL = 0.5; // 理想解:液位保持在 50%
// 图表坐标映射
const CHART_X_START = 30;
const CHART_X_NOW = 100;
const CHART_X_END = 220;
const CHART_Y_MIN = 50; // 100%液位
const CHART_Y_MAX = 180; // 0%液位
const CHART_HEIGHT = CHART_Y_MAX - CHART_Y_MIN;
const CHART_TARGET_Y = CHART_Y_MAX - (TARGET_LEVEL * CHART_HEIGHT);
// --- 仿真状态数据 ---
let currentInflowPct = 50; // 滑块输入值 10-90
let liquidLevel = 0.5; // 当前系统真实液位 0.0 - 1.0
let valveOpenLevel = 0.5; // PLC 控制下发的阀门开度
let simTime = 0;
// 历史缓冲区,用于绘制 NOW 左侧真实的曲线
const HISTORY_SIZE = 30;
let historyBuffer = new Array(HISTORY_SIZE).fill(TARGET_LEVEL);
// 监听滑块事件(扰动注入)
inflowSlider.addEventListener("input", (e) => {
currentInflowPct = parseInt(e.target.value);
inflowValText.textContent = currentInflowPct + "%";
});
// 转换液位比例至 SVG 图表 Y 坐标
function getChartY(level) {
// 钳制在 0-1 之间
level = Math.max(0, Math.min(1, level));
return CHART_Y_MAX - (level * CHART_HEIGHT);
}
// --- 核心更新逻辑:物理模拟与孪生 MPC 算法推演 ---
function simulationStep() {
simTime += 1/60; // 模拟基于 60FPS
// 1. 物理机理模拟 (水力学简化)
// 流入率基础基数,流出率受阀门控制。添加少许非线性让动画自然。
const inflowRate = (currentInflowPct / 100) * 0.015;
// 阀门流出与当前液位有轻微水头压力关系,简化为正相关
const outflowRate = valveOpenLevel * 0.02 * (0.5 + liquidLevel * 0.5);
liquidLevel += inflowRate - outflowRate;
liquidLevel = Math.max(0.05, Math.min(0.95, liquidLevel));
// 2. 数字孪生 MPC 控制逻辑 (模型预测控制)
// 孪生平台获知当前偏差,并计算未来控制序列消除矛盾
const error = liquidLevel - TARGET_LEVEL;
const disturbance = (currentInflowPct - 50) / 100;
// MPC 输出当前最优步开度 (简化版的 P+前馈 表现机制)
// 当液位过高(error>0)或预见大流入(disturbance>0)时,大幅开阀
let optimalValve = 0.5 + (error * 3.0) + (disturbance * 1.5);
valveOpenLevel += (optimalValve - valveOpenLevel) * 0.1; // 平滑下发执行
valveOpenLevel = Math.max(0.1, Math.min(1.0, valveOpenLevel));
// 3. 更新 UI SVG 属性
renderPhysicalAndTwinTanks();
renderDynamicFlows();
renderHUD();
requestAnimationFrame(simulationStep);
}
// --- 渲染逻辑分组 ---
function renderPhysicalAndTwinTanks() {
// 物理罐
const phH = TANK_HEIGHT * liquidLevel;
const phY = TANK_Y_BOTTOM - phH;
physLiquid.setAttribute("y", phY);
physLiquid.setAttribute("height", phH + 20);
physLiquidSurface.setAttribute("cy", phY);
// 孪生罐 (高度同步映射)
const twH = TWIN_HEIGHT * liquidLevel;
const twY = TWIN_Y_BOTTOM - twH;
twinLiquid.setAttribute("y", twY);
twinLiquid.setAttribute("height", twH + 20);
twinLiquidSurface.setAttribute("cy", twY);
}
function renderDynamicFlows() {
// 根据阀门开度调整下放水流的粗细与流速动画
const thickness = 4 + (valveOpenLevel * 8);
outflowLine.setAttribute("stroke-width", thickness);
const duration = Math.max(0.15, 1.2 - valveOpenLevel);
outflowLine.style.animationDuration = `${duration}s`;
}
function renderHUD() {
// 格式化时间
const minutes = Math.floor(simTime / 60).toString().padStart(2, '0');
const seconds = (simTime % 60).toFixed(2).padStart(5, '0');
hudSimTime.textContent = `Sim_Time: ${minutes}:${seconds}`;
}
// --- 降频刷新图表逻辑 (大约每秒 10 次更新轨迹,增加视觉科技感) ---
setInterval(() => {
// 更新历史记录队列
historyBuffer.shift();
historyBuffer.push(liquidLevel);
// 绘制历史轨迹曲线 (NOW的左侧)
let dHist = `M ${CHART_X_START} ${getChartY(historyBuffer[0])}`;
const stepX = (CHART_X_NOW - CHART_X_START) / (HISTORY_SIZE - 1);
for(let i = 1; i < HISTORY_SIZE; i++) {
dHist += ` L ${CHART_X_START + i * stepX} ${getChartY(historyBuffer[i])}`;
}
historyPath.setAttribute("d", dHist);
// 构建未来 20s 的推演轨迹 (体现 MPC 的控制目标收敛能力)
// 在虚拟空间中,MPC 会将最终液位平滑拉回到 TARGET_LEVEL
// 我们通过贝塞尔曲线模拟这个前瞻预测过程
const currentY = getChartY(liquidLevel);
const targetY = getChartY(TARGET_LEVEL);
// 动态计算控制点以产生 S 型收敛曲线
// 初始斜率受当前变化趋势影响 (差分)
const trend = historyBuffer[HISTORY_SIZE-1] - historyBuffer[HISTORY_SIZE-3];
const cp1Y = currentY - (trend * CHART_HEIGHT * 5);
const cp1X = CHART_X_NOW + (CHART_X_END - CHART_X_NOW) * 0.3;
const cp2X = CHART_X_NOW + (CHART_X_END - CHART_X_NOW) * 0.7;
const cp2Y = targetY; // 接近末端时斜率趋平
const dPred = `M ${CHART_X_NOW} ${currentY} C ${cp1X} ${cp1Y}, ${cp2X} ${cp2Y}, ${CHART_X_END} ${targetY}`;
predictedPath.setAttribute("d", dPred);
// 移动末端目标点
predictionTarget.setAttribute("cy", targetY);
}, 100);
// 启动主循环
simulationStep();
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数字孪生预测控制系统 (MPC) 原理动画</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Noto+Sans+SC:wght@400;500;700;900&display=swap');
:root {
--bg-dark: #040914;
--cyan: #00F0FF;
--purple: #B026FF;
--orange: #FF6B00;
--blue-grey: #1C2A47;
--text-dim: #88B0C0;
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: #02050A;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Noto Sans SC', sans-serif;
overflow: hidden;
}
.animation-container {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
svg {
max-width: 100%;
max-height: 100%;
box-shadow: 0 0 50px rgba(0, 240, 255, 0.05);
background: radial-gradient(circle at center, #0A152A 0%, #02050A 100%);
}
/* --- Typography --- */
.title { font-weight: 900; font-size: 24px; fill: #FFFFFF; letter-spacing: 2px; }
.subtitle { font-weight: 500; font-size: 14px; fill: var(--text-dim); }
.mono { font-family: 'JetBrains Mono', monospace; }
.label { font-size: 12px; fill: var(--text-dim); }
.label-glow { font-size: 12px; fill: var(--cyan); font-weight: bold; }
/* --- CSS Animations --- */
@keyframes scan {
0% { transform: translateY(0); opacity: 0; }
5% { opacity: 0.6; }
95% { opacity: 0.6; }
100% { transform: translateY(400px); opacity: 0; }
}
.scanline { animation: scan 4s linear infinite; }
@keyframes stream-flow {
from { stroke-dashoffset: 16; }
to { stroke-dashoffset: 0; }
}
@keyframes stream-flow-reverse {
from { stroke-dashoffset: 0; }
to { stroke-dashoffset: 16; }
}
.data-up { animation: stream-flow 0.5s linear infinite; filter: drop-shadow(0 0 4px var(--cyan)); }
.data-down { animation: stream-flow-reverse 0.5s linear infinite; filter: drop-shadow(0 0 4px var(--purple)); }
@keyframes shake-screen {
0% { transform: translate(0, 0) rotate(-5deg); }
25% { transform: translate(-2px, 1px) rotate(-5.5deg); }
50% { transform: translate(1px, -1px) rotate(-4.5deg); }
75% { transform: translate(-1px, 2px) rotate(-5.2deg); }
100% { transform: translate(0, 0) rotate(-5deg); }
}
.vibrating-mesh { animation: shake-screen 0.1s infinite; transform-origin: center; }
@keyframes radar-ping {
from { stroke-dashoffset: 8; opacity: 1; }
to { stroke-dashoffset: 0; opacity: 0.2; }
}
.radar-wave { animation: radar-ping 0.8s linear infinite; }
@keyframes flow-liquid {
to { stroke-dashoffset: -12; }
}
.liquid-stream { animation: flow-liquid 0.4s linear infinite; }
/* Interactive Pulse Effect */
.pulse-active {
filter: drop-shadow(0 0 25px var(--orange)) brightness(1.5) !important;
transform: scale(1.05);
transition: all 0.1s;
}
#mpc-core { transition: all 0.3s ease-out; transform-origin: 630px 250px; }
/* --- UI Panel Styling inside foreignObject --- */
.ui-panel {
background: rgba(4, 12, 26, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
padding: 16px;
border: 1px solid rgba(0, 240, 255, 0.3);
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6), inset 0 0 20px rgba(0, 240, 255, 0.05);
display: flex;
flex-direction: column;
gap: 12px;
color: var(--cyan);
height: 100%;
box-sizing: border-box;
}
.ui-panel h3 {
margin: 0;
font-size: 16px;
font-weight: 700;
text-shadow: 0 0 8px rgba(0, 240, 255, 0.6);
display: flex;
align-items: center;
gap: 8px;
letter-spacing: 1px;
}
.ui-panel h3::before {
content: '';
display: block;
width: 4px;
height: 14px;
background: var(--orange);
box-shadow: 0 0 8px var(--orange);
}
.slider-container { display: flex; flex-direction: column; gap: 8px; }
.slider-header { display: flex; justify-content: space-between; font-size: 13px; }
.val-text { font-family: 'JetBrains Mono', monospace; font-weight: bold; color: var(--orange); text-shadow: 0 0 5px var(--orange); }
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
margin: 8px 0;
}
input[type=range]:focus { outline: none; }
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 18px;
width: 18px;
border-radius: 50%;
background: var(--orange);
cursor: grab;
box-shadow: 0 0 10px var(--orange), inset 0 0 4px rgba(255,255,255,0.5);
margin-top: -7px;
transition: transform 0.1s;
}
input[type=range]::-webkit-slider-thumb:active { cursor: grabbing; transform: scale(1.2); }
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: rgba(28, 42, 71, 0.8);
border-radius: 2px;
border: 1px solid rgba(0, 240, 255, 0.2);
}
.desc {
font-size: 11px;
color: #5C7C99;
line-height: 1.5;
margin-top: auto;
}
</style>
</head>
<body>
<div class="animation-container">
<svg id="system-svg" viewBox="0 0 1200 800" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- Gradients -->
<linearGradient id="tank-glass" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#11223A" stop-opacity="0.8"/>
<stop offset="15%" stop-color="#1A3355" stop-opacity="0.3"/>
<stop offset="85%" stop-color="#1A3355" stop-opacity="0.3"/>
<stop offset="100%" stop-color="#0C172A" stop-opacity="0.9"/>
</linearGradient>
<linearGradient id="liquid-gradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#00F0FF" stop-opacity="0.8"/>
<stop offset="100%" stop-color="#0066AA" stop-opacity="0.9"/>
</linearGradient>
<!-- Filters -->
<filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
<filter id="glow-orange" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
<filter id="glow-purple" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
<!-- Patterns -->
<pattern id="dot-grid" width="20" height="20" patternUnits="userSpaceOnUse">
<circle cx="2" cy="2" r="1" fill="#1C2A47" opacity="0.6"/>
</pattern>
<pattern id="diagonal-stripe" width="8" height="8" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="8" stroke="#00F0FF" stroke-width="1" opacity="0.2" />
</pattern>
<clipPath id="twin-clip">
<rect x="40" y="50" width="1120" height="420" rx="16" />
</clipPath>
<clipPath id="physical-tank-clip">
<rect x="282" y="552" width="156" height="176" rx="8" />
</clipPath>
</defs>
<!-- Background -->
<rect width="100%" height="100%" fill="url(#dot-grid)" />
<!-- ==================== DATA STREAMS LAYER ==================== -->
<g id="data-streams" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
<!-- Sensor to Gateway (Up) -->
<path d="M 440 640 L 520 640 L 520 570" stroke="#00F0FF" stroke-dasharray="8 8" class="data-up" />
<!-- Gateway to Twin (Up) -->
<path d="M 520 530 L 520 460" stroke="#00F0FF" stroke-dasharray="8 8" class="data-up" />
<!-- Twin Tank to MPC (Internal) -->
<path d="M 440 290 L 590 290" stroke="#00F0FF" stroke-dasharray="4 4" opacity="0.5" />
<!-- MPC to Graph (Internal) -->
<path d="M 670 290 L 760 290" stroke="#FF6B00" stroke-dasharray="4 4" opacity="0.5" />
<!-- MPC to PLC (Down) -->
<path d="M 630 320 L 630 460 L 740 460 L 740 600" stroke="#B026FF" stroke-dasharray="8 8" class="data-down" />
<!-- PLC to Pump (Down) -->
<path d="M 740 700 L 740 750 L 560 750 L 560 740" stroke="#B026FF" stroke-dasharray="8 8" class="data-down" />
</g>
<!-- ==================== DIGITAL TWIN LAYER ==================== -->
<g id="digital-twin" clip-path="url(#twin-clip)">
<!-- Twin Panel Background -->
<rect x="40" y="50" width="1120" height="420" rx="16" fill="#0A1128" opacity="0.7" stroke="#00F0FF" stroke-width="1.5" />
<rect x="40" y="50" width="1120" height="420" rx="16" fill="url(#diagonal-stripe)" />
<!-- Scanline -->
<line x1="40" y1="50" x2="1160" y2="50" stroke="#00F0FF" stroke-width="2" class="scanline" filter="url(#glow-cyan)" />
<!-- Headers -->
<text x="70" y="100" class="title" filter="url(#glow-cyan)">实时数字孪生与 MPC 控制台</text>
<text x="70" y="125" class="subtitle">边缘算力驱动 / 预测时域 20s / 闭环控制周期 2s</text>
<!-- Twin Assets (Wireframes) -->
<g id="wireframes" transform="translate(0, -260)" stroke="#00F0FF" fill="none" opacity="0.8">
<!-- Twin Screen -->
<path d="M 140 600 L 260 600" stroke-dasharray="4 4" transform="rotate(-5, 200, 600)" />
<!-- Twin Tank -->
<rect x="280" y="550" width="160" height="180" rx="10" stroke-dasharray="4 4" />
<!-- Twin Liquid -->
<rect id="twin-liquid" x="282" y="640" width="156" height="88" fill="rgba(0, 240, 255, 0.15)" stroke="none" />
<text x="360" y="535" class="label-glow" text-anchor="middle">水力/机械耦合模型状态同步</text>
</g>
<!-- MPC Core Processor -->
<g id="mpc-core-group" transform="translate(630, 250)">
<text x="0" y="-55" fill="#FF6B00" font-size="13" font-weight="bold" text-anchor="middle" filter="url(#glow-orange)">破除系统响应滞后</text>
<text x="0" y="-35" fill="#B026FF" font-size="12" font-weight="bold" text-anchor="middle">生成最优前馈控制</text>
<g id="mpc-core">
<polygon points="0,-40 35,-20 35,20 0,40 -35,20 -35,-20" fill="#180A30" stroke="#B026FF" stroke-width="2" filter="url(#glow-purple)"/>
<circle cx="0" cy="0" r="15" fill="none" stroke="#00F0FF" stroke-dasharray="2 4" stroke-width="3">
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="4s" repeatCount="indefinite" />
</circle>
<text x="0" y="4" class="mono" font-size="12" fill="#FFF" text-anchor="middle" font-weight="bold">MPC</text>
</g>
</g>
<!-- Trend Graph Panel -->
<g id="graph-panel" transform="translate(760, 150)">
<rect x="0" y="0" width="340" height="250" rx="8" fill="#040914" stroke="#1C2A47" opacity="0.9"/>
<!-- Graph Grids (Static) -->
<g stroke="#1C2A47" stroke-width="1">
<line x1="0" y1="40" x2="340" y2="40"/>
<line x1="0" y1="95" x2="340" y2="95"/>
<line x1="0" y1="150" x2="340" y2="150" stroke="#4A6080" stroke-dasharray="4 4" stroke-width="2"/> <!-- 50% target -->
<line x1="0" y1="205" x2="340" y2="205"/>
<line x1="85" y1="0" x2="85" y2="250"/>
<line x1="170" y1="0" x2="170" y2="250" stroke="#00F0FF" stroke-dasharray="2 2"/> <!-- Current time -->
<line x1="255" y1="0" x2="255" y2="250"/>
</g>
<!-- Dynamic Paths -->
<path id="history-area" fill="rgba(0, 240, 255, 0.1)" stroke="none" />
<path id="history-path" fill="none" stroke="#00F0FF" stroke-width="2" filter="url(#glow-cyan)"/>
<path id="prediction-path" fill="none" stroke="#FF6B00" stroke-width="2" stroke-dasharray="6 4" filter="url(#glow-orange)"/>
<!-- Axis Labels -->
<text x="-10" y="45" class="mono label" text-anchor="end">100%</text>
<text x="-10" y="155" class="mono label" text-anchor="end">50%</text>
<text x="-10" y="245" class="mono label" text-anchor="end">0%</text>
<rect x="120" y="250" width="100" height="24" rx="12" fill="#0A1128" stroke="#00F0FF"/>
<text x="170" y="266" class="label-glow" text-anchor="middle">当前时刻 (t)</text>
<text x="85" y="270" class="label" fill="#00F0FF" text-anchor="middle">历史观测数据</text>
<text x="255" y="270" class="label" fill="#FF6B00" text-anchor="middle">MPC 未来推演</text>
</g>
</g> <!-- End Digital Twin Layer -->
<!-- ==================== PHYSICAL LAYER ==================== -->
<g id="physical-layer">
<!-- Inflow Pipe -->
<g id="inflow-group">
<path d="M -10 520 L 150 520 L 150 560" fill="none" stroke="#111A2C" stroke-width="16" stroke-linejoin="round" />
<path d="M -10 520 L 150 520 L 150 560" fill="none" stroke="#3A5078" stroke-width="18" stroke-linejoin="round" opacity="0.5" />
<text x="70" y="495" class="label" fill="#FF6B00" font-weight="bold">不可控扰动:来液流量</text>
<!-- Liquid Flowing -->
<path d="M 0 520 L 150 520 L 150 565" fill="none" stroke="#00E5FF" stroke-width="6" stroke-dasharray="10 10" class="liquid-stream" stroke-linejoin="round" />
</g>
<!-- Vibrating Screen -->
<g id="vibrating-screen" transform="translate(200, 600)">
<!-- Mechanical Base/Springs -->
<path d="M -40 0 L -40 40 M 40 0 L 40 40" stroke="#4A6080" stroke-width="4" stroke-dasharray="2 4"/>
<g class="vibrating-mesh">
<rect x="-60" y="-10" width="120" height="20" rx="4" fill="#FF6B00" />
<path d="M -50 -10 L 50 -10" stroke="#FFF" stroke-width="2" stroke-dasharray="2 2" />
<text x="0" y="30" class="label" text-anchor="middle" font-weight="bold" fill="#FF6B00">振动筛 (水力分离)</text>
</g>
<!-- Dropping Liquid -->
<path d="M 60 10 L 110 30 L 110 100" fill="none" stroke="#00E5FF" stroke-width="4" stroke-dasharray="6 6" class="liquid-stream" />
</g>
<!-- Buffer Tank -->
<g id="buffer-tank">
<!-- Tank Background/Back wall -->
<rect x="280" y="550" width="160" height="180" rx="10" fill="#0A1128" stroke="#1C2A47" stroke-width="2" />
<!-- Physical Liquid with clipping -->
<g clip-path="url(#physical-tank-clip)">
<rect id="physical-liquid" x="282" y="640" width="156" height="88" fill="url(#liquid-gradient)" />
<path d="M 282 640 Q 320 635 360 640 T 438 640 L 438 645 L 282 645 Z" fill="#4DFFFF" opacity="0.5">
<animate attributeName="d"
values="M 282 640 Q 320 635 360 640 T 438 640 L 438 645 L 282 645 Z;
M 282 640 Q 320 645 360 640 T 438 640 L 438 645 L 282 645 Z;
M 282 640 Q 320 635 360 640 T 438 640 L 438 645 L 282 645 Z"
dur="2s" repeatCount="indefinite" />
</path>
</g>
<!-- Glass overlay for 3D effect -->
<rect x="280" y="550" width="160" height="180" rx="10" fill="url(#tank-glass)" stroke="#3A5078" stroke-width="2" />
<text x="360" y="755" class="label" text-anchor="middle" font-weight="bold">缓冲罐</text>
</g>
<!-- Liquid Radar Sensor -->
<g id="sensor" transform="translate(360, 550)">
<rect x="-15" y="-20" width="30" height="20" rx="3" fill="#111A2C" stroke="#00F0FF" stroke-width="2"/>
<path d="M 0 0 L -30 60 M 0 0 L 0 60 M 0 0 L 30 60" stroke="#00F0FF" stroke-width="1.5" stroke-dasharray="4 4" class="radar-wave" />
<text x="0" y="-28" class="label-glow" text-anchor="middle">高频在线数据采集</text>
</g>
<!-- Top Edge Hardware (Gateway, Sensor Node) -->
<g id="gateway" transform="translate(520, 550)">
<rect x="-25" y="-20" width="50" height="40" rx="6" fill="#111A2C" stroke="#3A5078" stroke-width="2" />
<circle cx="0" cy="0" r="8" fill="#00F0FF">
<animate attributeName="opacity" values="1;0.2;1" dur="1s" repeatCount="indefinite" />
</circle>
<line x1="0" y1="-20" x2="0" y2="-40" stroke="#3A5078" stroke-width="2" />
<circle cx="0" cy="-40" r="3" fill="#00F0FF" />
<text x="0" y="35" class="label" text-anchor="middle">边缘采集网关</text>
</g>
<!-- Outlet Pump & Pipes -->
<g id="outlet-group">
<!-- Outflow Pipe -->
<path d="M 440 710 L 560 710" fill="none" stroke="#3A5078" stroke-width="14" />
<path d="M 440 710 L 560 710" fill="none" stroke="#00E5FF" stroke-width="6" stroke-dasharray="10 10" class="liquid-stream" />
<path d="M 560 710 L 700 710" fill="none" stroke="#3A5078" stroke-width="14" />
<path d="M 560 710 L 700 710" fill="none" stroke="#00E5FF" stroke-width="6" stroke-dasharray="10 10" class="liquid-stream" />
<!-- Pump Unit -->
<g transform="translate(560, 710)">
<circle cx="0" cy="0" r="24" fill="#0A1128" stroke="#FF6B00" stroke-width="3" />
<g id="pump-impeller">
<path d="M -16 0 L 16 0 M 0 -16 L 0 16" stroke="#FF6B00" stroke-width="5" stroke-linecap="round" />
</g>
<text x="0" y="-35" class="label" fill="#FF6B00" text-anchor="middle" font-weight="bold">排出泵(执行机构)</text>
<text id="valve-outflow-text" x="0" y="40" class="mono" fill="#FF6B00" font-size="14" font-weight="bold" text-anchor="middle" filter="url(#glow-orange)">50.0%</text>
</g>
</g>
<!-- PLC Controller -->
<g id="plc" transform="translate(740, 650)">
<rect x="-40" y="-50" width="80" height="100" rx="8" fill="#111A2C" stroke="#3A5078" stroke-width="2" />
<rect x="-25" y="-30" width="10" height="4" fill="#B026FF" class="pulse-active">
<animate attributeName="opacity" values="1;0;1" dur="0.2s" repeatCount="indefinite"/>
</rect>
<rect x="-25" y="-15" width="10" height="4" fill="#00F0FF" />
<rect x="-25" y="0" width="10" height="4" fill="#00F0FF" />
<text x="0" y="70" class="label" text-anchor="middle" font-weight="bold">现场 PLC 控制柜</text>
</g>
</g> <!-- End Physical Layer -->
<!-- ==================== INTERACTIVE UI OVERLAY ==================== -->
<foreignObject x="30" y="580" width="240" height="180" style="overflow: visible;">
<div xmlns="http://www.w3.org/1999/xhtml" class="ui-panel">
<h3>工况扰动模拟</h3>
<div class="desc">
滑动修改来液流量,观察滞后系统中 MPC 如何前馈推演并快速稳定液位。
</div>
<div class="slider-container">
<div class="slider-header">
<span>瞬时来液流量</span>
<span id="slider-value" class="val-text">50.0 L/s</span>
</div>
<input type="range" id="inflow-slider" min="10" max="90" value="50" />
</div>
</div>
</foreignObject>
</svg>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// ---- DOM Elements ----
const slider = document.getElementById('inflow-slider');
const sliderValueText = document.getElementById('slider-value');
const valveOutflowText = document.getElementById('valve-outflow-text');
const physicalLiquid = document.getElementById('physical-liquid');
const twinLiquid = document.getElementById('twin-liquid');
const pumpImpeller = document.getElementById('pump-impeller');
const mpcCore = document.getElementById('mpc-core');
const historyPath = document.getElementById('history-path');
const historyArea = document.getElementById('history-area');
const predictionPath = document.getElementById('prediction-path');
// ---- System Variables ----
let inflow = 50;
let outflow = 50;
let level = 50; // 0 to 100 %
const targetLevel = 50;
// Graph data arrays
const historyLength = 100;
const predictionLength = 80;
let historyData = new Array(historyLength).fill(50);
let predictionData = new Array(predictionLength).fill(50);
// Animation states
let impellerAngle = 0;
let mpcPulseTimeout = null;
// ---- MPC & Physics Emulation Logic ----
// This perfectly emulates the Real-Time Twin computing optimal control
function updateSimulation() {
// 1. Read disturbance
inflow = parseFloat(slider.value);
sliderValueText.textContent = inflow.toFixed(1) + ' L/s';
// 2. MPC Controller Logic (Emulated via Proportional control with predictive feed-forward)
// Goal is to calculate an outflow that brings level to targetLevel smoothly
let error = targetLevel - level;
let kp = 0.8;
let computedIdealOutflow = inflow - (error * kp);
// Smooth the actuator response (PLC/Pump physical limits)
outflow += (computedIdealOutflow - outflow) * 0.15;
outflow = Math.max(0, Math.min(100, outflow));
valveOutflowText.textContent = outflow.toFixed(1) + '%';
// 3. Physical Plant Model (Update real level)
// Level changes based on mass balance (in - out)
let netFlow = inflow - outflow;
level += netFlow * 0.05; // System capacity multiplier
level = Math.max(0, Math.min(100, level));
// 4. Update History Array
historyData.shift();
historyData.push(level);
// 5. MPC Predicts Future 20s trajectory
// The twin models the exact physics to plot the future
let simLevel = level;
let simOutflow = outflow;
for(let i = 0; i < predictionLength; i++) {
let simError = targetLevel - simLevel;
let reqOutflow = inflow - (simError * kp);
simOutflow += (reqOutflow - simOutflow) * 0.15;
simOutflow = Math.max(0, Math.min(100, simOutflow));
simLevel += (inflow - simOutflow) * 0.05;
predictionData[i] = simLevel;
}
// 6. Visual Updates
updateVisuals();
requestAnimationFrame(updateSimulation);
}
// ---- Draw Updates to SVG ----
function updateVisuals() {
// A. Hardware Animation (Pump spin based on outflow)
impellerAngle += (outflow / 100) * 15;
pumpImpeller.setAttribute('transform', `rotate(${impellerAngle})`);
// B. Physical Tank Level
// Base Y = 728 (empty), Height max = 176. Base Y = 552 (full)
let physH = (level / 100) * 176;
let physY = 728 - physH;
physicalLiquid.setAttribute('height', physH);
physicalLiquid.setAttribute('y', physY);
// C. Digital Twin Tank Level
// Base Y = 728, Height max = 176 in twin coordinate system => mapped to twin dimensions
// Twin tank internal max height = 176, Y base = 728 (wireframe coords)
twinLiquid.setAttribute('height', physH);
twinLiquid.setAttribute('y', physY);
// D. Draw Graphs
// Graph bounding box inside group: Width=340, Height=250.
// Y range: 0 (100%) to 250 (0%). Target is 150.
// History X: 0 to 170. Prediction X: 170 to 340.
const drawPath = (data, startX, width) => {
let step = width / (data.length - 1);
let d = '';
for(let i=0; i<data.length; i++) {
let x = startX + i * step;
let y = 250 - (data[i] / 100) * 250;
d += (i === 0 ? `M ${x} ${y} ` : `L ${x} ${y} `);
}
return d;
};
const drawAreaPath = (data, startX, width) => {
let path = drawPath(data, startX, width);
// close path to bottom
path += ` L ${startX + width} 250 L ${startX} 250 Z`;
return path;
};
historyPath.setAttribute('d', drawPath(historyData, 0, 170));
historyArea.setAttribute('d', drawAreaPath(historyData, 0, 170));
predictionPath.setAttribute('d', drawPath(predictionData, 170, 170));
}
// ---- Interactions ----
slider.addEventListener('input', () => {
// Visual cue for MPC processing disturbance
mpcCore.classList.add('pulse-active');
if(mpcPulseTimeout) clearTimeout(mpcPulseTimeout);
mpcPulseTimeout = setTimeout(() => {
mpcCore.classList.remove('pulse-active');
}, 300);
});
// Start simulation loop
requestAnimationFrame(updateSimulation);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高保真数字孪生及预测控制原理动画</title>
<style>
:root {
--cyan: #00E5FF;
--magenta: #FF00FF;
--orange: #FFAA00;
--dark-bg: #0B1021;
--panel-bg: rgba(11, 20, 35, 0.85);
}
body {
margin: 0;
padding: 0;
background-color: #050811;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
overflow: hidden;
color: #FFF;
}
.wrapper {
position: relative;
width: 100%;
max-width: 1200px;
max-height: 90vh;
aspect-ratio: 12 / 8;
box-shadow: 0 0 60px rgba(0, 229, 255, 0.1);
background: var(--dark-bg);
border: 1px solid rgba(0, 229, 255, 0.2);
border-radius: 12px;
overflow: hidden;
}
svg {
width: 100%;
height: 100%;
display: block;
}
/* ----- SVG Animations ----- */
.flow-anim {
animation: flowForward 1s linear infinite;
}
.flow-anim-fast {
animation: flowForward 0.5s linear infinite;
}
.flow-anim-slow {
animation: flowForward 2s linear infinite;
}
@keyframes flowForward {
from { stroke-dashoffset: 40; }
to { stroke-dashoffset: 0; }
}
#shakerGroup {
animation: vibrate 0.05s infinite;
}
@keyframes vibrate {
0% { transform: translate(180px, 275px) rotate(-8deg); }
33% { transform: translate(179px, 274px) rotate(-9deg); }
66% { transform: translate(181px, 276px) rotate(-7deg); }
100% { transform: translate(180px, 275px) rotate(-8deg); }
}
.blink {
animation: blinkAnim 1.5s infinite alternate;
}
.blink-fast {
animation: blinkAnim 0.3s infinite alternate;
}
@keyframes blinkAnim {
from { opacity: 0.3; filter: drop-shadow(0 0 0px var(--cyan)); }
to { opacity: 1; filter: drop-shadow(0 0 8px var(--cyan)); }
}
/* ----- HTML Overlay UI ----- */
.title-bar {
position: absolute;
top: 30px;
left: 50%;
transform: translateX(-50%);
text-align: center;
pointer-events: none;
}
.title-bar h1 {
margin: 0;
font-size: 28px;
letter-spacing: 2px;
color: #FFF;
text-shadow: 0 0 15px var(--cyan);
}
.title-bar p {
margin: 5px 0 0 0;
font-size: 14px;
color: var(--cyan);
opacity: 0.8;
}
.control-panel {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
background: var(--panel-bg);
border: 1px solid var(--cyan);
border-radius: 8px;
padding: 15px 25px;
display: flex;
gap: 15px;
align-items: center;
box-shadow: 0 0 20px rgba(0, 229, 255, 0.2);
backdrop-filter: blur(10px);
z-index: 10;
}
.control-panel h3 {
margin: 0 15px 0 0;
font-size: 14px;
color: var(--cyan);
text-transform: uppercase;
}
.btn {
background: rgba(0, 229, 255, 0.1);
color: #FFF;
border: 1px solid var(--cyan);
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
font-size: 13px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
outline: none;
}
.btn:hover {
background: var(--cyan);
color: var(--dark-bg);
box-shadow: 0 0 15px var(--cyan);
transform: translateY(-2px);
}
.btn.danger {
border-color: var(--magenta);
background: rgba(255, 0, 255, 0.1);
color: var(--magenta);
}
.btn.danger:hover {
background: var(--magenta);
color: #FFF;
box-shadow: 0 0 15px var(--magenta);
}
#statusText {
font-family: monospace;
font-size: 14px;
margin-left: 15px;
min-width: 280px;
color: var(--cyan);
}
</style>
</head>
<body>
<div class="wrapper">
<div class="title-bar">
<h1>振动筛与缓冲罐实时数字孪生预测控制系统</h1>
<p>基于数字空间 20s 提前推演,实现零新增硬件的最优闭环控制</p>
</div>
<svg viewBox="0 0 1200 800" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- Grid Pattern -->
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<rect width="40" height="40" fill="none" stroke="rgba(255,255,255,0.02)" />
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(255,255,255,0.04)" stroke-width="1" />
</pattern>
<!-- Gradients -->
<linearGradient id="liquidGradient" x1="0" y1="450" x2="0" y2="650" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#00E5FF" />
<stop offset="100%" stop-color="#003366" />
</linearGradient>
<linearGradient id="dtLiquidGradient" x1="0" y1="450" x2="0" y2="650" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="rgba(0, 229, 255, 0.7)" />
<stop offset="100%" stop-color="rgba(0, 229, 255, 0.1)" />
</linearGradient>
<linearGradient id="predAreaGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(255, 0, 255, 0.4)" />
<stop offset="100%" stop-color="rgba(255, 0, 255, 0)" />
</linearGradient>
<!-- Filters -->
<filter id="glowCyan" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<!-- Background Grid -->
<rect width="1200" height="800" fill="url(#grid)" />
<!-- ================= TRIZ IFR CALLOUT ================= -->
<g transform="translate(40, 40)">
<rect width="390" height="40" fill="rgba(0, 229, 255, 0.05)" stroke="#00E5FF" stroke-width="1" rx="4"/>
<text x="15" y="25" fill="#00E5FF" font-size="14" font-weight="bold">TRIZ 最终理想解 (IFR):</text>
<text x="175" y="25" fill="#FFF" font-size="12">仅利用现有在线历史数据闭环,零复杂硬件新增</text>
</g>
<!-- ================= LAYER BACKGROUNDS ================= -->
<!-- Physical Layer -->
<rect x="40" y="110" width="380" height="620" fill="rgba(255,255,255,0.01)" stroke="#334455" stroke-dasharray="4,4" rx="10"/>
<text x="60" y="140" fill="#8899AA" font-size="16" font-weight="bold">物理现场空间 (Physical Space)</text>
<!-- Edge Layer -->
<rect x="440" y="110" width="300" height="620" fill="rgba(255,255,255,0.01)" stroke="#334455" stroke-dasharray="4,4" rx="10"/>
<text x="460" y="140" fill="#8899AA" font-size="16" font-weight="bold">边缘计算网络层 (Edge IoT)</text>
<!-- Digital Twin Layer -->
<rect x="760" y="110" width="400" height="620" fill="rgba(0, 229, 255, 0.03)" stroke="#00E5FF" stroke-width="1" rx="10"/>
<text x="780" y="140" fill="#00E5FF" font-size="16" font-weight="bold">数字空间 (Digital Twin & MPC)</text>
<!-- ================= PHYSICAL COMPONENTS ================= -->
<!-- Inlet Pipe & Fluid -->
<path d="M 150 180 L 150 250" stroke="#223344" stroke-width="14" fill="none"/>
<path id="inletFluid" d="M 150 180 L 150 250" stroke="#00E5FF" stroke-width="6" stroke-dasharray="10,10" fill="none" class="flow-anim-fast"/>
<text x="85" y="200" fill="#AAA" font-size="12">未处理来液</text>
<!-- Shaker (Animated via CSS) -->
<g id="shakerGroup">
<g transform="translate(-80, -15)">
<rect x="0" y="0" width="160" height="25" fill="#1A2233" stroke="#556677" stroke-width="2"/>
<!-- Shaker Mesh -->
<path d="M 10 6 L 150 6 M 10 12 L 150 12 M 10 18 L 150 18" stroke="#00E5FF" stroke-width="1" stroke-dasharray="2,2" opacity="0.5"/>
<text x="80" y="17" fill="#FFF" font-size="13" text-anchor="middle" font-weight="bold" letter-spacing="1">高频振动筛</text>
</g>
</g>
<!-- Shaker Drip -->
<path id="dripFluid" d="M 230 280 L 230 450" stroke="#00E5FF" stroke-width="4" stroke-dasharray="8,8" fill="none" class="flow-anim"/>
<!-- Buffer Tank -->
<path d="M 120 450 L 120 650 L 280 650 L 280 450" fill="rgba(0,0,0,0.6)" stroke="#556677" stroke-width="4"/>
<path id="liquidLevel" fill="url(#liquidGradient)" opacity="0.85"/>
<text x="200" y="680" fill="#CCC" font-size="14" text-anchor="middle" font-weight="bold">缓冲液罐</text>
<!-- Outlet Pipe & Pump -->
<path d="M 280 630 L 370 630 L 370 690" fill="none" stroke="#223344" stroke-width="14"/>
<path id="outletFluid" d="M 280 630 L 370 630 L 370 690" fill="none" stroke="#00E5FF" stroke-width="6" stroke-dasharray="10,10" class="flow-anim-fast"/>
<circle cx="330" cy="630" r="22" fill="#1A2233" stroke="#8899AA" stroke-width="2"/>
<!-- Spinning Impeller -->
<g id="pumpImpellerGroup" style="transform-origin: 330px 630px;">
<circle cx="330" cy="630" r="14" fill="none" stroke="#00E5FF" stroke-width="4" stroke-dasharray="8,8"/>
<line x1="316" y1="630" x2="344" y2="630" stroke="#00E5FF" stroke-width="2"/>
<line x1="330" y1="616" x2="330" y2="644" stroke="#00E5FF" stroke-width="2"/>
</g>
<text x="330" y="600" fill="#CCC" font-size="12" text-anchor="middle">变频排出泵</text>
<!-- Sensors & PLC -->
<rect x="280" y="470" width="24" height="24" fill="#FFAA00" rx="4"/>
<circle cx="292" cy="482" r="4" fill="#FFF" class="blink-fast"/>
<text x="315" y="487" fill="#FFAA00" font-size="12">液位传感</text>
<rect x="70" y="260" width="24" height="24" fill="#FFAA00" rx="4"/>
<circle cx="82" cy="272" r="4" fill="#FFF" class="blink-fast"/>
<text x="95" y="245" fill="#FFAA00" font-size="12">工况采集</text>
<rect x="60" y="550" width="60" height="70" fill="#1A1A25" stroke="#FFAA00" stroke-width="2" rx="6"/>
<text x="90" y="590" fill="#FFAA00" font-size="16" font-weight="bold" text-anchor="middle">PLC</text>
<text x="135" y="590" fill="#FFAA00" font-size="11">执行控制指令</text>
<!-- ================= EDGE NETWORK COMPONENTS ================= -->
<!-- Gateway -->
<rect x="520" y="420" width="160" height="60" fill="#0B1525" stroke="#00E5FF" stroke-width="2" rx="8" filter="url(#glowCyan)"/>
<circle cx="540" cy="450" r="6" fill="#00E5FF" class="blink"/>
<text x="610" y="455" fill="#00E5FF" font-size="16" text-anchor="middle" font-weight="bold">数据采集网关</text>
<!-- Edge IPC -->
<rect x="520" y="240" width="160" height="80" fill="#0B1525" stroke="#FF00FF" stroke-width="2" rx="8"/>
<circle cx="540" cy="265" r="6" fill="#FF00FF" class="blink-fast"/>
<text x="610" y="275" fill="#FF00FF" font-size="16" text-anchor="middle" font-weight="bold">边缘计算工控机</text>
<text x="610" y="295" fill="#AAA" font-size="11" text-anchor="middle">Intel i7 / GPU 加速算力</text>
<!-- ================= DIGITAL TWIN & MPC COMPONENTS ================= -->
<!-- DT Tank Wireframe -->
<path d="M 820 450 L 820 650 L 980 650 L 980 450" fill="rgba(0,255,255,0.02)" stroke="#00E5FF" stroke-width="2" stroke-dasharray="6,4"/>
<path id="dtLiquidLevel" fill="url(#dtLiquidGradient)"/>
<!-- Hologram Scanline -->
<rect id="dtScanline" x="820" y="450" width="160" height="3" fill="#00E5FF" opacity="0.6">
<animate attributeName="y" values="450;650;450" dur="2.5s" repeatCount="indefinite" />
</rect>
<!-- 3D visual lines inside DT tank -->
<line x1="820" y1="516" x2="980" y2="516" stroke="#00E5FF" stroke-width="1" stroke-dasharray="2,4" opacity="0.3"/>
<line x1="820" y1="583" x2="980" y2="583" stroke="#00E5FF" stroke-width="1" stroke-dasharray="2,4" opacity="0.3"/>
<line x1="900" y1="450" x2="900" y2="650" stroke="#00E5FF" stroke-width="1" stroke-dasharray="2,4" opacity="0.3"/>
<text x="900" y="680" fill="#00E5FF" font-size="14" text-anchor="middle" font-weight="bold" filter="url(#glowCyan)">实时孪生模型 (水力/机械耦合)</text>
<!-- MPC Chart Dashboard -->
<rect x="780" y="160" width="360" height="190" fill="#080C16" stroke="#223344" stroke-width="2" rx="6"/>
<text x="795" y="185" fill="#FFF" font-size="14" font-weight="bold">MPC 模型预测控制寻优轨迹</text>
<!-- Parameters Box -->
<rect x="1005" y="170" width="125" height="50" fill="rgba(255,0,255,0.1)" stroke="#FF00FF" stroke-width="1" rx="4"/>
<text x="1015" y="188" fill="#FF00FF" font-size="11" font-weight="bold">预测时域: 20s (Np)</text>
<text x="1015" y="208" fill="#FF00FF" font-size="11" font-weight="bold">控制周期: 2s (Tc)</text>
<!-- Chart Grids -->
<!-- Target Line (50%) -->
<line x1="780" y1="265" x2="1140" y2="265" stroke="#00E5FF" stroke-width="1.5" stroke-dasharray="6,4" opacity="0.4"/>
<text x="1110" y="258" fill="#00E5FF" font-size="10" opacity="0.8">最优设定目标</text>
<!-- Current Time Separator -->
<line x1="910" y1="190" x2="910" y2="340" stroke="#FFF" stroke-width="1.5" stroke-dasharray="4,4"/>
<circle cx="910" cy="340" r="3" fill="#FFF"/>
<text x="910" y="355" fill="#FFF" font-size="11" text-anchor="middle" font-weight="bold">当前时刻 t</text>
<text x="845" y="355" fill="#8899AA" font-size="11" text-anchor="middle">在线历史数据</text>
<text x="1025" y="355" fill="#FF00FF" font-size="11" text-anchor="middle">闭环推演未来液位趋势</text>
<!-- Dynamic Paths for Chart -->
<!-- History -->
<path id="historyPath" fill="none" stroke="#00E5FF" stroke-width="2.5"/>
<!-- Prediction -->
<path id="predictionPath" fill="none" stroke="#FF00FF" stroke-width="3" stroke-dasharray="6,4"/>
<path id="predictionArea" fill="url(#predAreaGradient)"/>
<!-- ================= DATA PATHS & ROUTES ================= -->
<!-- 1. Sensors -> Gateway -->
<path d="M 304 482 L 460 482 L 460 450 L 520 450" fill="none" stroke="#00E5FF" stroke-width="2" opacity="0.2"/>
<path d="M 304 482 L 460 482 L 460 450 L 520 450" fill="none" stroke="#00E5FF" stroke-width="3" stroke-dasharray="10,12" class="flow-anim"/>
<path d="M 94 272 L 460 272 L 460 440 L 520 440" fill="none" stroke="#00E5FF" stroke-width="2" opacity="0.2"/>
<path d="M 94 272 L 460 272 L 460 440 L 520 440" fill="none" stroke="#00E5FF" stroke-width="3" stroke-dasharray="10,12" class="flow-anim"/>
<!-- 2. Gateway -> Edge IPC -->
<path d="M 600 420 L 600 320" fill="none" stroke="#00E5FF" stroke-width="2" opacity="0.2"/>
<path d="M 600 420 L 600 320" fill="none" stroke="#00E5FF" stroke-width="4" stroke-dasharray="15,15" class="flow-anim-fast"/>
<text x="615" y="375" fill="#00E5FF" font-size="11">实时同步</text>
<!-- 3. Edge IPC -> Digital Twin Update -->
<path d="M 680 290 L 730 290 L 730 550 L 820 550" fill="none" stroke="#00E5FF" stroke-width="2" opacity="0.2"/>
<path d="M 680 290 L 730 290 L 730 550 L 820 550" fill="none" stroke="#00E5FF" stroke-width="4" stroke-dasharray="12,12" class="flow-anim-fast"/>
<path d="M 680 270 L 780 270" fill="none" stroke="#00E5FF" stroke-width="2" opacity="0.2"/>
<path d="M 680 270 L 780 270" fill="none" stroke="#00E5FF" stroke-width="4" stroke-dasharray="12,12" class="flow-anim-fast"/>
<text x="735" y="260" fill="#00E5FF" font-size="11">驱动孪生更新</text>
<!-- 4. Edge IPC -> PLC (Optimal Control Signal Downstream) -->
<path d="M 520 280 L 170 280 L 170 560 L 120 560" fill="none" stroke="#FF00FF" stroke-width="2" opacity="0.2"/>
<path id="controlFlowPath" d="M 520 280 L 170 280 L 170 560 L 120 560" fill="none" stroke="#FF00FF" stroke-width="4" stroke-dasharray="15,15" class="flow-anim-fast"/>
<rect x="250" y="265" width="130" height="28" fill="#110022" stroke="#FF00FF" stroke-width="1" rx="4"/>
<text x="315" y="284" fill="#FF00FF" font-size="13" font-weight="bold" text-anchor="middle">最优控制序列下发</text>
<!-- 5. PLC -> Pump (Execution) -->
<path d="M 90 620 L 90 655 L 310 655 L 310 640" fill="none" stroke="#FFAA00" stroke-width="2" stroke-dasharray="6,6" class="flow-anim"/>
<text x="210" y="645" fill="#FFAA00" font-size="11">动态调节转速指令</text>
</svg>
<!-- Interactive Control Panel -->
<div class="control-panel">
<h3>工况突变模拟器</h3>
<button class="btn" onclick="triggerSurge()">模拟:突发高流量来液</button>
<button class="btn danger" onclick="triggerDrop()">模拟:来液突然中断</button>
<div id="statusText">系统运行:[稳态] MPC闭环维持最优工况</div>
</div>
</div>
<script>
// --- Core Simulation State ---
let timeTicks = 0;
const HISTORY_LEN = 150;
const PRED_LEN = 200;
let historyData = new Array(HISTORY_LEN).fill(50);
let predictionData = new Array(PRED_LEN).fill(50);
let currentLevel = 50; // Tank level: 0 to 100%
let baseFlow = 15;
let targetInletFlow = baseFlow;
let currentInletFlow = baseFlow;
let currentPumpFlow = baseFlow;
let isUserIntervening = false;
// UI Element References
const pathHistory = document.getElementById('historyPath');
const pathPrediction = document.getElementById('predictionPath');
const pathPredArea = document.getElementById('predictionArea');
const liquidLevel = document.getElementById('liquidLevel');
const dtLiquidLevel = document.getElementById('dtLiquidLevel');
const pumpImpeller = document.getElementById('pumpImpellerGroup');
const statusText = document.getElementById('statusText');
const controlFlowPath = document.getElementById('controlFlowPath');
const inletFluid = document.getElementById('inletFluid');
const dripFluid = document.getElementById('dripFluid');
const outletFluid = document.getElementById('outletFluid');
function simulateTick() {
timeTicks++;
// 1. Natural drift to show system autonomy if user isn't clicking buttons
if(!isUserIntervening && timeTicks % 120 === 0) {
targetInletFlow = baseFlow + (Math.random() - 0.5) * 8; // Slight fluctuations
}
// 2. Smooth physical inlet transition
currentInletFlow += (targetInletFlow - currentInletFlow) * 0.04;
// 3. MPC Control Law (Proportional controller simulating the first step of MPC horizon)
// Error > 0 means level is too high, pump needs to extract more than inlet.
let error = currentLevel - 50;
// Kp = 0.8. Optimal flow to bring error to zero
let optimalPumpFlow = currentInletFlow + error * 0.8;
// 4. PLC Execution (Physical pump responds with slight delay)
currentPumpFlow += (optimalPumpFlow - currentPumpFlow) * 0.1;
currentPumpFlow = Math.max(0, currentPumpFlow); // Cannot pump negative
// 5. Physics Update: Tank Level Integrator
let netFlow = currentInletFlow - currentPumpFlow;
currentLevel += netFlow * 0.08;
currentLevel = Math.max(0, Math.min(100, currentLevel)); // Clamp 0-100%
// 6. Record History
historyData.push(currentLevel);
historyData.shift();
// 7. Digital Twin / MPC Prediction Generation (Rollout model for next 20s)
let simLevel = currentLevel;
let simInlet = currentInletFlow;
let simPump = currentPumpFlow;
predictionData = [];
for(let i = 0; i < PRED_LEN; i++) {
// The internal predictive model assumes disturbance will eventually decay towards base
simInlet += (baseFlow - simInlet) * 0.015;
// Planned control sequence for the future
let simOptPump = simInlet + (simLevel - 50) * 0.8;
simPump += (simOptPump - simPump) * 0.1;
// Predicted physical response
simLevel += (simInlet - simPump) * 0.08;
predictionData.push(simLevel);
}
// 8. Visual Updates
updateRender();
// Loop
requestAnimationFrame(simulateTick);
}
function updateRender() {
// -- Chart Update --
// Chart X maps: History (780 to 910), Prediction (910 to 1140)
// Chart Y maps: 0% -> 340, 100% -> 190 (Height 150)
let dH = "M ";
for(let i = 0; i < HISTORY_LEN; i++) {
let x = 780 + (i / (HISTORY_LEN-1)) * 130;
let y = 340 - (historyData[i] / 100) * 150;
dH += `${x},${y} `;
}
pathHistory.setAttribute('d', dH);
let dP = "M ";
let dPArea = "";
for(let i = 0; i < PRED_LEN; i++) {
let x = 910 + (i / (PRED_LEN-1)) * 230;
let y = 340 - (predictionData[i] / 100) * 150;
dP += `${x},${y} `;
}
pathPrediction.setAttribute('d', dP);
// Fill area under prediction curve
dPArea = dP + `L 1140 340 L 910 340 Z`;
pathPredArea.setAttribute('d', dPArea);
// -- Physical Space Tank Update --
// Tank Box Y: 450 to 650. Range = 200.
let physY = 650 - (currentLevel / 100) * 200;
// Generate a subtle dynamic wave effect based on time
let t = Date.now() / 250;
let waveOffset = Math.sin(t) * 4;
let waveOffset2 = Math.cos(t * 1.3) * 4;
let physD = `M 120 ${physY + waveOffset} Q 200 ${physY + waveOffset2 - 15}, 280 ${physY + waveOffset} L 280 650 L 120 650 Z`;
liquidLevel.setAttribute('d', physD);
// -- Digital Twin Tank Update --
let dtY = 650 - (currentLevel / 100) * 200;
let dtD = `M 820 ${dtY} L 980 ${dtY} L 980 650 L 820 650 Z`;
dtLiquidLevel.setAttribute('d', dtD);
// -- Hardware Animation Speeds & Thickness --
// Pump Rotation Speed
let pumpRotSpeed = Math.max(20, 250 - currentPumpFlow * 5); // ms per frame approx mapping
let currentRot = (Date.now() / pumpRotSpeed) % 360;
pumpImpeller.style.transform = `rotate(${currentRot}deg)`;
// Pipe Fluid Stroke Widths mapping to flow volume
let inThick = Math.max(2, currentInletFlow * 0.35);
let outThick = Math.max(2, currentPumpFlow * 0.35);
inletFluid.setAttribute('stroke-width', inThick);
dripFluid.setAttribute('stroke-width', inThick * 0.8);
outletFluid.setAttribute('stroke-width', outThick);
// Dynamic Control Path Styling based on Error Severity
let errorMagnitude = Math.abs(currentLevel - 50);
if(errorMagnitude > 15) {
controlFlowPath.style.stroke = "#FF3333";
controlFlowPath.style.filter = "drop-shadow(0 0 8px #FF3333)";
} else {
controlFlowPath.style.stroke = "#FF00FF";
controlFlowPath.style.filter = "none";
}
}
// --- Interaction Handlers ---
let interventionTimeout;
function triggerSurge() {
isUserIntervening = true;
targetInletFlow = 45; // Massive spike
statusText.innerHTML = "<span style='color:#FF3333;'>[预警] 检测到突发激增来液!MPC输出高频强排指令...</span>";
clearTimeout(interventionTimeout);
interventionTimeout = setTimeout(() => {
targetInletFlow = baseFlow;
statusText.innerHTML = "<span style='color:#00E5FF;'>[恢复] 激增消除,液位平滑回归 50% 最优点</span>";
setTimeout(() => { isUserIntervening = false; }, 3000);
}, 6000);
}
function triggerDrop() {
isUserIntervening = true;
targetInletFlow = 1; // Almost stops
statusText.innerHTML = "<span style='color:#FFAA00;'>[预警] 来液中断!MPC紧急抑制泵排量以防抽空损坏...</span>";
clearTimeout(interventionTimeout);
interventionTimeout = setTimeout(() => {
targetInletFlow = baseFlow;
statusText.innerHTML = "<span style='color:#00E5FF;'>[恢复] 来液恢复正常,系统自动重新寻优</span>";
setTimeout(() => { isUserIntervening = false; }, 3000);
}, 6000);
}
// --- Boot Sequence ---
document.addEventListener('DOMContentLoaded', () => {
// Kick off the simulation automatically on load as requested
requestAnimationFrame(simulateTick);
});
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
