分享图
动画工坊
引擎就绪
<!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分