本文介绍基于纯 HTML + JS 打造的的比热数据处理分析工具
将本文附近代码保存为 CPAnalyzer.html,然后双击在浏览器中打开即可,在最左侧导入
将 比热的数据(Sample Temp (Kelvin)Samp HC (J/mole-K) 两列,)复制粘贴到左侧输入框。
在左侧设置好化学式原子数(n)。
勾选启用晶格声子基线扣除,选择需要的模型(推荐复杂体系使用德拜+爱因斯坦模型)。拖动参数滑块,在右上方图表中观察基线是否贴合高温区的无相变顺磁尾巴。
自动双约束寻优工作站
如果不想手动盲猜,点击底部的"高级自动约束寻优"!在弹出的工作站中:
-
输入理论目标磁熵:比如你确信你的体系是S=1/2,直接选择 Rln2。
-
圈定高温对齐区间:比如 150K ~ 200K。
-
点击执行:引擎会瞬间启动百次搜索与局部退火算法!它会尝试成千上万种德拜和爱因斯坦温度的组合,既保证在高温区符合实验测点,又保证最终积分出来的磁熵完美落在 R\ln2 的水平线上。
窗口展示效果如下

不同拟合模型

德拜模型

爱因斯坦模型

双德拜模型,即将体系内轻重原子分离
混合模型


自动搜索窗口

通过自动拟合后的结果

完整代码如下
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>固体比热数据交互分析仪 - 终极修复版</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<style>
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: #f1f5f9; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
input[type=range] { accent-color: #8b5cf6; }
/* 屏蔽只读框的默认灰色 */
input:read-only { background-color: #f8fafc; color: #334155; border-color: #cbd5e1; }
</style>
</head>
<body class="bg-slate-100 text-slate-800 font-sans h-screen w-screen flex overflow-hidden relative">
<!-- 左侧控制面板 (固定宽度) -->
<aside class="w-[350px] bg-white border-r border-slate-300 flex flex-col h-full shadow-lg z-20 shrink-0">
<!-- Header -->
<header class="bg-indigo-600 text-white p-4 shrink-0 shadow-md">
<h1 class="text-base font-bold flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg>
比热数据综合分析站
</h1>
</header>
<!-- 滚动控制区 -->
<div class="flex-1 overflow-y-auto">
<!-- 1. 数据输入 -->
<div class="p-4 border-b border-slate-200">
<h2 class="font-bold text-xs mb-2 text-indigo-700">1. 粘贴比热数据</h2>
<div class="text-[10px] text-slate-500 mb-2 bg-slate-50 p-1.5 rounded border border-slate-200 leading-relaxed">
<strong>要求格式 (支持直接带表头复制):</strong><br/>
<code>Sample Temp (Kelvin) Samp HC (J/mole-K)</code>
</div>
<textarea id="dataInput" class="w-full h-16 p-2 text-[10px] font-mono border border-slate-300 rounded focus:ring-1 focus:ring-indigo-500 outline-none resize-none mb-2 shadow-inner" placeholder="201.07 183.62"></textarea>
<div class="flex items-center justify-between text-xs">
<label class="font-bold text-slate-700">化学式原子数 (n):</label>
<input type="number" id="nAtoms" value="1" step="0.1" class="w-16 p-1 border border-slate-300 rounded text-center shadow-sm">
</div>
</div>
<!-- 2. 数据处理与低温外推 -->
<div class="p-4 border-b border-slate-200 bg-slate-50">
<div class="flex items-center justify-between mb-3">
<h2 class="font-bold text-xs text-indigo-700">2. 插值与极低温外推 (Cp/T vs T²)</h2>
</div>
<div class="flex flex-col gap-2 text-[11px] mb-3">
<label class="flex items-center gap-2">
<input type="checkbox" id="useInterpolation" checked class="rounded text-indigo-600">
<span class="font-bold text-slate-700">线性插值重采样 (步长:</span>
<input type="number" id="interpStep" value="0.5" step="0.1" min="0.01" class="w-12 p-0.5 border rounded text-center"> K)
</label>
<label class="flex items-center gap-2 text-orange-600">
<input type="checkbox" id="forceNonNegative" checked class="rounded text-orange-600">
<span class="font-bold">约束: 限制磁比热 C_mag ≥ 0</span>
</label>
<div class="h-px bg-slate-200 my-1"></div>
<label class="flex items-center gap-2">
<input type="checkbox" id="autoFitLowT" checked class="rounded text-blue-600">
<span class="font-bold text-slate-700">自动拟合低温参数 (T²区:</span>
<input type="number" id="t2Min" value="0" class="w-10 p-0.5 border rounded text-center">-
<input type="number" id="t2Max" value="50" class="w-10 p-0.5 border rounded text-center">)
</label>
<div class="flex items-center gap-2 mt-1">
<span class="font-bold text-slate-700">绘图 T² 上限:</span>
<input type="number" id="t2PlotMax" value="100" class="w-16 p-0.5 border rounded text-center text-blue-700" placeholder="Auto">
</div>
</div>
<div class="bg-white p-2 rounded border border-slate-200 grid grid-cols-2 gap-2 shadow-sm">
<div class="flex flex-col">
<span class="text-[10px] text-slate-500">γ [J/(mol·K²)]</span>
<input type="number" id="gammaInput" value="0" step="0.0001" readonly class="w-full p-1 border rounded text-xs font-mono font-bold">
</div>
<div class="flex flex-col">
<span class="text-[10px] text-slate-500">β [J/(mol·K⁴)]</span>
<input type="number" id="betaInput" value="0" step="0.00001" readonly class="w-full p-1 border rounded text-xs font-mono font-bold">
</div>
<div class="col-span-2 text-[10px] text-slate-500 mt-1 border-t pt-1">
低频参考 Θ_D ≈ <span id="thetaLowVal" class="font-bold text-blue-600">--</span> K
</div>
</div>
</div>
<!-- 3. 晶格声子基线扣除 -->
<div class="p-4 border-b border-slate-200">
<label class="flex items-center gap-2 mb-3 cursor-pointer">
<input type="checkbox" id="enableLattice" checked class="rounded text-orange-600">
<h2 class="font-bold text-xs text-orange-700">3. 晶格声子背景模型扣除</h2>
</label>
<div id="latticeControls" class="space-y-3">
<select id="modelType" class="w-full p-1.5 text-xs border border-slate-300 rounded font-bold text-slate-700 shadow-sm">
<option value="single_debye">单德拜模型 (1 Debye)</option>
<option value="double_debye">双德拜模型 (2 Debye)</option>
<option value="debye_einstein">德拜 + 爱因斯坦模型 (D + E)</option>
</select>
<!-- 动态物理模型展示面板 -->
<div id="modelDescMain" class="p-2 bg-slate-50 rounded border border-slate-200 text-[10px] text-slate-600 shadow-inner transition-all duration-200">
<!-- 动态内容将被注入到这里 -->
</div>
<div class="bg-orange-50/30 p-3 rounded border border-orange-100 shadow-inner">
<!-- P1 -->
<div class="mb-3">
<div class="flex justify-between items-center text-xs mb-1">
<label id="lblParam1" class="font-bold text-slate-700">Θ_D1 (K)</label>
<input type="number" id="numParam1" class="w-16 p-1 border border-orange-300 rounded text-center text-orange-700 font-bold bg-white" value="300" min="10" max="1500" step="0.1">
</div>
<input type="range" id="param1" min="10" max="1500" value="300" step="0.1" class="w-full">
</div>
<!-- P2 -->
<div id="divParam2" class="mb-3" style="display:none;">
<div class="flex justify-between items-center text-xs mb-1">
<label id="lblParam2" class="font-bold text-slate-700">Θ_2 (K)</label>
<input type="number" id="numParam2" class="w-16 p-1 border border-orange-300 rounded text-center text-orange-700 font-bold bg-white" value="150" min="10" max="1500" step="0.1">
</div>
<input type="range" id="param2" min="10" max="1500" value="150" step="0.1" class="w-full">
</div>
<!-- W -->
<div id="divWeight" style="display:none;">
<div class="flex justify-between items-center text-xs mb-1">
<label class="font-bold text-slate-700">模型1权重 (w)</label>
<input type="number" id="numWeight" class="w-16 p-1 border border-orange-300 rounded text-center text-orange-700 font-bold bg-white" value="0.5" min="0" max="1" step="0.01">
</div>
<input type="range" id="weight" min="0" max="1" step="0.01" value="0.5" class="w-full">
</div>
</div>
</div>
</div>
<!-- 4. 磁熵参考标度 -->
<div class="p-4 border-b border-slate-200 bg-red-50/20">
<h2 class="font-bold text-xs mb-2 text-red-700">4. 磁熵绘图理论标度</h2>
<div class="flex flex-col gap-2 text-xs">
<div class="flex items-center justify-between">
<span class="font-bold text-slate-700">目标熵 S:</span>
<select id="sRefType" class="p-1.5 border border-slate-300 rounded shadow-sm w-36">
<option value="none">无(Y轴自适应)</option>
<option value="Rln2">R·ln(2) [S=1/2]</option>
<option value="Rln3">R·ln(3) [S=1]</option>
<option value="Rln4">R·ln(4) [S=3/2]</option>
<option value="Rln5">R·ln(5) [S=2]</option>
<option value="Rln6">R·ln(6) [S=5/2]</option>
<option value="custom">自定义数值</option>
</select>
</div>
<div id="customSRefDiv" class="flex items-center justify-between hidden mt-1">
<span class="font-medium text-slate-500">数值 (J/mol·K):</span>
<input type="number" id="sRefCustom" value="5.76" step="0.1" class="w-20 p-1 border border-slate-300 rounded text-center">
</div>
</div>
</div>
</div>
<!-- 高级寻优入口 (固定在底部) -->
<div class="p-4 bg-indigo-50 shrink-0 border-t border-indigo-100 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)]">
<button id="openAdvancedBtn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded shadow-md transition flex justify-center items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path></svg>
高级自动约束寻优
</button>
</div>
</aside>
<!-- 右侧图表主区 (硬核百分比布局,根除塌陷) -->
<main class="flex-1 h-full p-2 flex flex-col gap-2 relative bg-slate-200">
<!-- 上半部分 2 图 (高 50%) -->
<div class="flex gap-2" style="height: 50%;">
<!-- 图1 -->
<div class="bg-white rounded shadow-sm border border-slate-300 flex-1 relative flex flex-col">
<div class="h-8 flex items-center px-3 border-b border-slate-100 shrink-0">
<div class="w-2 h-2 rounded-full bg-slate-500 mr-2"></div>
<h3 class="font-bold text-slate-700 text-xs">1. 比热总览与声子基线 (Cp vs T)</h3>
</div>
<!-- 图表容器挂载点 (强制绝对定位填充) -->
<div class="flex-1 w-full relative"><div id="chartMain" style="position: absolute; top:0; bottom:0; left:0; right:0;"></div></div>
</div>
<!-- 图2 -->
<div class="bg-white rounded shadow-sm border border-slate-300 flex-1 relative flex flex-col">
<div class="h-8 flex items-center px-3 border-b border-slate-100 shrink-0">
<div class="w-2 h-2 rounded-full bg-orange-500 mr-2"></div>
<h3 class="font-bold text-slate-700 text-xs">2. 提取的磁比热异常 (Cmag vs T)</h3>
</div>
<div class="flex-1 w-full relative"><div id="chartCmag" style="position: absolute; top:0; bottom:0; left:0; right:0;"></div></div>
</div>
</div>
<!-- 下半部分 2 图 (高 50%) -->
<div class="flex gap-2" style="height: 50%;">
<!-- 图3 -->
<div class="bg-white rounded shadow-sm border border-slate-300 flex-1 relative flex flex-col">
<div class="h-8 flex items-center px-3 border-b border-slate-100 shrink-0">
<div class="w-2 h-2 rounded-full bg-blue-500 mr-2"></div>
<h3 class="font-bold text-slate-700 text-xs">3. 极低温外推检验 (Cp/T vs T²)</h3>
</div>
<div class="flex-1 w-full relative"><div id="chartLowT" style="position: absolute; top:0; bottom:0; left:0; right:0;"></div></div>
</div>
<!-- 图4 -->
<div class="bg-white rounded shadow-sm border border-slate-300 flex-1 relative flex flex-col">
<div class="h-8 flex items-center justify-between px-3 border-b border-slate-100 shrink-0">
<div class="flex items-center">
<div class="w-2 h-2 rounded-full bg-red-500 mr-2"></div>
<h3 class="font-bold text-slate-700 text-xs">4. 积分磁熵推演 (Smag vs T)</h3>
</div>
<div class="text-[10px] text-slate-600 bg-red-50 px-2 py-0.5 rounded border border-red-100 z-10">
当前最高 S<sub>mag</sub> = <span id="maxEntropy" class="font-bold text-red-600 text-sm">0.00</span>
</div>
</div>
<div class="flex-1 w-full relative"><div id="chartEntropy" style="position: absolute; top:0; bottom:0; left:0; right:0;"></div></div>
</div>
</div>
</main>
<!-- 高级功能 Modal -->
<div id="advancedModal" class="fixed inset-0 z-50 bg-slate-900/40 backdrop-blur-sm hidden flex items-center justify-center p-6">
<div class="bg-white w-full max-w-6xl h-full max-h-[800px] rounded-xl shadow-2xl flex flex-col overflow-hidden border border-slate-400">
<!-- Modal Header -->
<header class="bg-indigo-800 text-white px-6 py-3 flex items-center justify-between shrink-0">
<div class="flex items-center gap-3">
<svg class="w-6 h-6 text-indigo-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path></svg>
<h2 class="text-lg font-bold">磁熵与比热 - 启发式双约束自动寻优拟合引擎</h2>
</div>
<button id="closeAdvancedBtn" class="text-indigo-200 hover:text-white transition">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
</button>
</header>
<div class="flex flex-1 overflow-hidden">
<!-- Modal Sidebar -->
<div class="w-80 bg-slate-50 border-r border-slate-300 p-5 flex flex-col gap-4 overflow-y-auto">
<div class="bg-white p-3 rounded shadow-sm border border-slate-200">
<label class="block text-xs font-bold text-indigo-800 mb-2 border-b pb-1">① 目标拟合模型</label>
<div class="flex items-center justify-between text-xs mb-2">
<span class="text-slate-600">原子数(n):</span>
<input type="number" id="advNAtoms" step="0.1" class="w-16 p-1 border rounded text-center">
</div>
<select id="advModelType" class="w-full p-1.5 text-xs border rounded font-bold mb-2">
<option value="single_debye">单德拜模型</option>
<option value="double_debye">双德拜模型</option>
<option value="debye_einstein">德拜 + 爱因斯坦模型</option>
</select>
<!-- 高级动态物理模型展示面板 -->
<div id="modelDescAdv" class="p-2 bg-slate-50 rounded border border-slate-200 text-[10px] text-slate-600 shadow-inner">
<!-- 动态内容将被注入 -->
</div>
</div>
<div class="bg-white p-3 rounded shadow-sm border border-slate-200">
<label class="block text-xs font-bold text-red-800 mb-2 border-b pb-1">② 理论磁熵约束 (S_mag)</label>
<div class="flex items-center gap-2 mb-2">
<input type="number" id="advTargetS" step="0.01" class="w-full p-1.5 border rounded font-mono text-center font-bold text-red-700">
</div>
<div class="grid grid-cols-3 gap-1 text-[10px]">
<button class="bg-red-50 hover:bg-red-100 text-red-700 py-1 rounded border" onclick="document.getElementById('advTargetS').value=(8.31446*Math.log(2)).toFixed(3)">Rln2</button>
<button class="bg-red-50 hover:bg-red-100 text-red-700 py-1 rounded border" onclick="document.getElementById('advTargetS').value=(8.31446*Math.log(3)).toFixed(3)">Rln3</button>
<button class="bg-red-50 hover:bg-red-100 text-red-700 py-1 rounded border" onclick="document.getElementById('advTargetS').value=(8.31446*Math.log(4)).toFixed(3)">Rln4</button>
<button class="bg-red-50 hover:bg-red-100 text-red-700 py-1 rounded border" onclick="document.getElementById('advTargetS').value=(8.31446*Math.log(5)).toFixed(3)">Rln5</button>
<button class="bg-red-50 hover:bg-red-100 text-red-700 py-1 rounded border" onclick="document.getElementById('advTargetS').value=(8.31446*Math.log(6)).toFixed(3)">Rln6</button>
</div>
</div>
<div class="bg-white p-3 rounded shadow-sm border border-slate-200">
<label class="block text-xs font-bold text-orange-800 mb-2 border-b pb-1">③ 高温对齐区间 (K)</label>
<div class="flex items-center gap-2">
<input type="number" id="advFitTMin" value="150" class="w-full p-1 border rounded text-center text-sm font-mono">
<span class="text-slate-500">-</span>
<input type="number" id="advFitTMax" value="200" class="w-full p-1 border rounded text-center text-sm font-mono">
</div>
</div>
<div class="bg-white p-3 rounded shadow-sm border border-slate-200">
<label class="block text-xs font-bold text-slate-700 mb-2 border-b pb-1">④ 寻优配置</label>
<div class="flex items-center justify-between text-xs mb-1">
<span class="text-slate-600">迭代代数:</span>
<select id="advIters" class="w-32 p-1.5 border rounded font-medium">
<option value="50">50代 (快速)</option>
<option value="200" selected>200代 (推荐)</option>
<option value="500">500代 (深度)</option>
</select>
</div>
</div>
<div class="mt-auto space-y-2">
<!-- 操作按钮区 -->
<div class="flex gap-2">
<button id="runAutoFitBtn" class="flex-1 bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2.5 rounded shadow transition text-sm">
开始自动寻优
</button>
<button id="stopAutoFitBtn" class="w-20 bg-rose-600 hover:bg-rose-700 text-white font-bold py-2.5 rounded shadow transition text-sm hidden">
⏹ 停止
</button>
</div>
<button id="applyAdvancedBtn" class="w-full bg-emerald-600 hover:bg-emerald-700 text-white font-bold py-2.5 rounded shadow transition hidden text-sm">
应用这套参数并返回主页
</button>
</div>
</div>
<!-- Modal Chart -->
<div class="flex-1 bg-white p-4 relative flex flex-col">
<div class="absolute top-4 left-4 z-10 bg-white/90 p-3 rounded shadow-md border border-slate-200 text-xs">
<h4 class="font-bold text-indigo-800 mb-2 border-b pb-1">寻优状态监视器</h4>
<div class="grid grid-cols-2 gap-x-4 gap-y-1.5 font-mono">
<div class="text-slate-600">适应度(Loss):</div><div id="advLoss" class="font-bold text-slate-800">--</div>
<div class="text-orange-700">Θ_D1:</div><div id="advResP1" class="font-bold">--</div>
<div class="text-orange-700">Θ_2/E:</div><div id="advResP2" class="font-bold">--</div>
<div class="text-orange-700">权重w:</div><div id="advResW" class="font-bold">--</div>
<div class="text-red-700 mt-1 pt-1 border-t">结果S:</div><div id="advResS" class="font-bold mt-1 pt-1 border-t text-red-600">--</div>
</div>
</div>
<div class="flex-1 w-full relative"><div id="chartAdvanced" style="position: absolute; top:0; bottom:0; left:0; right:0;"></div></div>
</div>
</div>
</div>
</div>
<script>
// --- 核心数学计算 ---
const R = 8.314462618;
// --- 物理模型动态文档 (全展开、无省略号公式) ---
const MODEL_DESCRIPTIONS = {
'single_debye': `
<div class="font-bold text-slate-800 mb-1 text-center">【单德拜模型】连续弹性波假设</div>
<div class="font-mono text-indigo-700 bg-indigo-50/80 p-2 rounded mb-1 text-[10px] leading-relaxed border border-indigo-100 text-left">
C<sub>lat</sub> = 9nR(T/Θ<sub>D</sub>)³ ∫<sub class="-ml-1 text-[8px]">0</sub><sup class="text-[8px]">Θ<sub>D</sub>/T</sup> [x⁴eˣ/(eˣ-1)²]dx
</div>
<ul class="list-none space-y-0.5 opacity-90 pl-1 text-[10px] leading-snug">
<li>• <strong>n</strong>: 原胞原子数; <strong>R</strong>: 理想气体常数</li>
<li>• <strong>Θ<sub>D</sub></strong>: 德拜温度,反映声学支最高截断频率</li>
<li>• <strong>适用</strong>: 简单晶体,低频声学支占据绝对主导</li>
</ul>
`,
'double_debye': `
<div class="font-bold text-slate-800 mb-1 text-center">【双德拜模型】双声学支混合假设</div>
<div class="font-mono text-indigo-700 bg-indigo-50/80 p-1.5 rounded mb-1 text-[10px] leading-relaxed border border-indigo-100 text-left">
C<sub>lat</sub> = w · C<sub>D</sub>(Θ<sub>D1</sub>) + (1-w) · C<sub>D</sub>(Θ<sub>D2</sub>)<br/>
<span class="text-[9px] opacity-75 text-slate-500">* C<sub>D</sub> 为上方单德拜标准积分公式</span>
</div>
<ul class="list-none space-y-0.5 opacity-90 pl-1 text-[10px] leading-snug">
<li>• <strong>Θ<sub>D1</sub> / Θ<sub>D2</sub></strong>: 分别对应高低两种声学支色散频率</li>
<li>• <strong>w</strong>: 模型1所占声子态密度的权重比例</li>
<li>• <strong>适用</strong>: 包含质量差异悬殊的原子的复杂化合物</li>
</ul>
`,
'debye_einstein': `
<div class="font-bold text-slate-800 mb-1 text-center">【德拜+爱因斯坦】广义权重展开式</div>
<div class="font-mono text-indigo-700 bg-indigo-50/80 p-1.5 rounded mb-1 text-[9px] leading-relaxed border border-indigo-100 text-left px-2">
C<sub>lat</sub> = <br/>
w · 9nR(T/Θ<sub>D</sub>)³ ∫<sub class="-ml-1 text-[8px]">0</sub><sup class="text-[8px]">Θ<sub>D</sub>/T</sup> [x⁴eˣ/(eˣ-1)²]dx <br/>
+ (1-w) · 3nR(Θ<sub>E</sub>/T)² [e<sup>(Θ<sub>E</sub>/T)</sup>/(e<sup>(Θ<sub>E</sub>/T)</sup>-1)²]
</div>
<ul class="list-none space-y-0.5 opacity-90 pl-1 text-[10px] leading-snug">
<li>• <strong>广义权重 (w)</strong>: 经验调节参数。严格理论下期望值为 1/n,实际分析中允许自由浮动以拟合真实 DOS 混合程度。该公式严格保证高温极限定律。</li>
<li>• <strong>Θ<sub>D</sub></strong>: 德拜温度 (对应低频连续色散声学支)</li>
<li>• <strong>Θ<sub>E</sub></strong>: 爱因斯坦温度 (对应高频局域光学支)</li>
</ul>
`
};
function debyeIntegral(limit) {
if (limit <= 0) return 0;
const nSteps = 50;
const h = limit / nSteps;
let sum = 0;
for (let i = 0; i <= nSteps; i++) {
let x = i * h;
let y = 0;
if (x > 1e-5) {
let expNegX = Math.exp(-x);
y = (Math.pow(x, 4) * expNegX) / Math.pow(1 - expNegX, 2);
}
let weight = (i === 0 || i === nSteps) ? 1 : (i % 2 === 0 ? 2 : 4);
sum += weight * y;
}
return (h / 3) * sum;
}
function calcDebyeCv(T, thetaD, nAtoms) {
if (T <= 0 || thetaD <= 0) return 0;
return 9 * nAtoms * R * Math.pow(T / thetaD, 3) * debyeIntegral(thetaD / T);
}
function calcEinsteinCv(T, thetaE, nAtoms) {
if (T <= 0 || thetaE <= 0) return 0;
const x = thetaE / T;
if (x > 100) return 0;
let expNegX = Math.exp(-x);
return 3 * nAtoms * R * Math.pow(x, 2) * expNegX / Math.pow(1 - expNegX, 2);
}
function linearInterpolate(xArray, yArray, xi) {
let n = xArray.length;
if (xi <= xArray[0]) return yArray[0];
if (xi >= xArray[n - 1]) return yArray[n - 1];
let low = 0, high = n - 1;
while (low <= high) {
let mid = Math.floor((low + high) / 2);
if (xArray[mid] < xi) low = mid + 1;
else high = mid - 1;
}
let i = high;
let x0 = xArray[i], x1 = xArray[i+1];
return yArray[i] + (yArray[i+1] - yArray[i]) * (xi - x0) / (x1 - x0);
}
// --- 安全 DOM 获取 ---
const getEl = (id) => document.getElementById(id);
const bindEvent = (el, type, handler) => {
if (el) el.addEventListener(type, handler);
};
const ui = {
dataInput: getEl('dataInput'), nAtoms: getEl('nAtoms'),
useInterpolation: getEl('useInterpolation'), interpStep: getEl('interpStep'), forceNonNegative: getEl('forceNonNegative'),
autoFitLowT: getEl('autoFitLowT'), t2Min: getEl('t2Min'), t2Max: getEl('t2Max'), t2PlotMax: getEl('t2PlotMax'),
gammaInput: getEl('gammaInput'), betaInput: getEl('betaInput'), thetaLowVal: getEl('thetaLowVal'),
enableLattice: getEl('enableLattice'), latticeControls: getEl('latticeControls'),
modelType: getEl('modelType'), modelDescMain: getEl('modelDescMain'),
param1: getEl('param1'), numParam1: getEl('numParam1'),
param2: getEl('param2'), numParam2: getEl('numParam2'), divParam2: getEl('divParam2'), lblParam2: getEl('lblParam2'),
weight: getEl('weight'), numWeight: getEl('numWeight'), divWeight: getEl('divWeight'),
sRefType: getEl('sRefType'), sRefCustom: getEl('sRefCustom'), customSRefDiv: getEl('customSRefDiv'), maxEntropy: getEl('maxEntropy')
};
const advUI = {
modal: getEl('advancedModal'), openBtn: getEl('openAdvancedBtn'), closeBtn: getEl('closeAdvancedBtn'),
runBtn: getEl('runAutoFitBtn'), stopBtn: getEl('stopAutoFitBtn'), applyBtn: getEl('applyAdvancedBtn'),
nAtoms: getEl('advNAtoms'), modelType: getEl('advModelType'), modelDescAdv: getEl('modelDescAdv'), targetS: getEl('advTargetS'),
fitTMin: getEl('advFitTMin'), fitTMax: getEl('advFitTMax'), iters: getEl('advIters'),
resLoss: getEl('advLoss'), resP1: getEl('advResP1'), resP2: getEl('advResP2'), resW: getEl('advResW'), resS: getEl('advResS')
};
// --- 全局状态 ---
let rawData = [], gridData = [], calcData = [];
let fitParams = { gamma: 0, beta: 0 };
let charts = {};
// 默认数据
const initialData = "Sample Temp (Kelvin)\tSamp HC (J/mole-K)\n201.07122\t183.62491\n177.86576\t169.60842\n154.37558\t150.58813\n130.94854\t129.51363\n130.56572\t131.9599\n128.97674\t128.07355\n127.23678\t127.10045\n125.50972\t125.68587\n123.78234\t124.27806\n122.05464\t123.05592\n120.32494\t121.88481\n118.57919\t121.98924\n116.84722\t123.97255\n115.10291\t138.2341\n113.37812\t141.74983\n111.65512\t132.78091\n109.92332\t126.16805\n108.16925\t121.27112\n106.43654\t117.01644\n104.70779\t113.28561\n102.95869\t110.27529\n101.22849\t107.01971\n99.50132\t103.85109\n97.77832\t100.85733\n96.04843\t97.98861\n94.32259\t95.09093\n92.60597\t92.03379\n90.86414\t89.28305\n89.13783\t86.38211\n87.40416\t83.38169\n85.64937\t80.7099\n83.91135\t77.83788\n82.17027\t75.03278\n80.42113\t72.39602\n80.31531\t72.79102\n70.9147\t57.74955\n62.44569\t45.04394\n54.99743\t34.54643\n48.40993\t25.57195\n42.60544\t18.82431\n37.48575\t13.47575\n32.97917\t9.51555\n29.01615\t6.50666\n25.52791\t4.46807\n22.46445\t2.99481\n19.76773\t2.0205\n17.3907\t1.33152\n15.30542\t0.92472\n13.46876\t0.62927\n11.85473\t0.45551\n10.43623\t0.30392\n9.09842\t0.24276\n7.98989\t0.17838\n7.01319\t0.14296\n6.15107\t0.10356\n5.40679\t0.08005\n4.74876\t0.06143\n4.17084\t0.04768\n3.66435\t0.03808\n3.22184\t0.03032\n2.83505\t0.02473\n2.50231\t0.01974\n2.21449\t0.01801\n1.96926\t0.01332";
// 核心求值引擎
function quickEvaluate(p1, p2, w, useLattice, overrideNAtoms=null, overrideModel=null) {
const nAtoms = overrideNAtoms !== null ? overrideNAtoms : (parseFloat(ui.nAtoms?.value) || 1);
const modelType = overrideModel !== null ? overrideModel : (ui.modelType?.value || 'single_debye');
const forcePositive = ui.forceNonNegative?.checked || false;
let cumulativeEntropy = 0;
let tmpCalcData = [];
for (let i = 0; i < gridData.length; i++) {
const d = gridData[i];
let c_lat = 0;
if (useLattice) {
if (modelType === 'single_debye') c_lat = calcDebyeCv(d.t, p1, nAtoms);
else if (modelType === 'double_debye') c_lat = w * calcDebyeCv(d.t, p1, nAtoms) + (1 - w) * calcDebyeCv(d.t, p2, nAtoms);
else if (modelType === 'debye_einstein') c_lat = w * calcDebyeCv(d.t, p1, nAtoms) + (1 - w) * calcEinsteinCv(d.t, p2, nAtoms);
}
let c_mag = d.cp - c_lat - fitParams.gamma * d.t;
if (forcePositive) c_mag = Math.max(0, c_mag);
if (i > 0) {
const prev_d = gridData[i - 1];
const prev_c_mag = tmpCalcData[i - 1].c_mag;
cumulativeEntropy += 0.5 * ((c_mag / d.t) + (prev_c_mag / prev_d.t)) * (d.t - prev_d.t);
} else {
cumulativeEntropy += (c_mag / d.t) * d.t;
}
tmpCalcData.push({ t: d.t, c_lat, c_mag, s_mag: cumulativeEntropy, cp: d.cp });
}
return tmpCalcData;
}
// CSV 导出
function exportToCSV(dataArray) {
const targetData = (dataArray && Array.isArray(dataArray)) ? dataArray : calcData;
if (!targetData || targetData.length === 0) { alert("暂无数据可导出!"); return; }
let csvContent = "data:text/csv;charset=utf-8,\uFEFF";
csvContent += "T (K),Cp_Total (J/mol.K),C_Lattice (J/mol.K),C_Mag (J/mol.K),S_Mag (J/mol.K)\n";
targetData.forEach(d => {
csvContent += `${(d.t||0).toFixed(4)},${(d.cp||0).toFixed(4)},${(d.c_lat||0).toFixed(4)},${(d.c_mag||0).toFixed(4)},${(d.s_mag||0).toFixed(4)}\n`;
});
const link = document.createElement("a");
link.setAttribute("href", encodeURI(csvContent));
link.setAttribute("download", "HeatCapacity_Analysis.csv");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// 初始化 ECharts
function initCharts() {
['chartMain', 'chartCmag', 'chartLowT', 'chartEntropy', 'chartAdvanced'].forEach(id => {
const el = document.getElementById(id);
if(el) {
charts[id] = echarts.init(el);
if(id !== 'chartAdvanced') {
new ResizeObserver(() => { if(charts[id]) charts[id].resize(); }).observe(el.parentElement);
}
}
});
const elAdv = document.getElementById('chartAdvanced');
if(elAdv) {
new ResizeObserver(() => { if(charts.chartAdvanced && !advUI.modal.classList.contains('hidden')) charts.chartAdvanced.resize(); }).observe(elAdv.parentElement);
}
}
// 数据解析
function processData() {
if(!ui.dataInput) return;
const lines = ui.dataInput.value.split('\n');
let data = [];
lines.forEach(line => {
const trimmed = line.trim();
if (!trimmed || !/^[0-9.-]/.test(trimmed)) return;
const parts = trimmed.split(/[\t\s,]+/).filter(Boolean);
if (parts.length >= 2) {
const t = parseFloat(parts[0]), cp = parseFloat(parts[1]);
if (!isNaN(t) && !isNaN(cp) && t > 0) data.push({ t, cp, t2: t * t, cp_t: cp / t });
}
});
data.sort((a, b) => a.t - b.t);
rawData = data.filter((item, index, arr) => index === 0 || item.t > arr[index-1].t);
if(rawData.length === 0) return;
gridData = [];
if (ui.useInterpolation && ui.useInterpolation.checked) {
const step = Math.max(0.01, parseFloat(ui.interpStep?.value) || 0.5);
const tMin = rawData[0].t, tMax = rawData[rawData.length - 1].t;
const xArr = rawData.map(d => d.t), yArr = rawData.map(d => d.cp);
for (let T = tMin; T <= tMax; T += step) {
let cp = linearInterpolate(xArr, yArr, T);
gridData.push({ t: T, cp: cp, t2: T*T, cp_t: cp/T });
}
if (gridData[gridData.length-1].t < tMax) {
gridData.push({ t: tMax, cp: rawData[rawData.length-1].cp, t2: tMax*tMax, cp_t: rawData[rawData.length-1].cp/tMax });
}
} else { gridData = [...rawData]; }
}
function processLowT() {
let gamma = 0, beta = 0;
if (ui.autoFitLowT && ui.autoFitLowT.checked) {
const tMin2 = parseFloat(ui.t2Min?.value) || 0, tMax2 = parseFloat(ui.t2Max?.value) || 50;
const fitData = rawData.filter(d => d.t2 >= tMin2 && d.t2 <= tMax2);
if (fitData.length > 2) {
const n = fitData.length;
let sx=0, sy=0, sxy=0, sxx=0;
fitData.forEach(d => { sx+=d.t2; sy+=d.cp_t; sxy+=d.t2*d.cp_t; sxx+=d.t2*d.t2; });
const denom = n * sxx - sx * sx;
if(denom !== 0) { beta = (n * sxy - sx * sy) / denom; gamma = (sy - beta * sx) / n; }
}
if(ui.gammaInput) ui.gammaInput.value = gamma.toFixed(4);
if(ui.betaInput) ui.betaInput.value = beta.toFixed(5);
} else {
gamma = parseFloat(ui.gammaInput?.value) || 0;
beta = parseFloat(ui.betaInput?.value) || 0;
}
fitParams = { gamma, beta };
if(ui.thetaLowVal) ui.thetaLowVal.innerText = beta > 0 ? Math.pow((1944 * (parseFloat(ui.nAtoms?.value)||1)) / beta, 1/3).toFixed(1) : '--';
}
function calculateMain() {
calcData = quickEvaluate(
parseFloat(ui.numParam1?.value)||300,
parseFloat(ui.numParam2?.value)||150,
parseFloat(ui.numWeight?.value)||0.5,
ui.enableLattice?.checked
);
if(ui.maxEntropy) ui.maxEntropy.innerText = calcData.length ? calcData[calcData.length-1].s_mag.toFixed(3) : '0.00';
}
// --- 制图核心 ---
function renderMainCharts() {
if(rawData.length === 0) return;
const tbConfig = {
show: true, feature: {
dataZoom: { yAxisIndex: 'none', title:{zoom:'框选缩放',back:'还原'} },
restore: { title:'复位' },
myExportCsv: {
show: true, title: '📥 导出全套分析数据 (CSV)',
icon: 'path://M14,2L20,8V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V4A2,2 0 0,1 6,2H14M18,20V9H13V4H6V20H18M12,19L8,15H10.5V12H13.5V15H16L12,19Z',
onclick: () => exportToCSV(calcData)
}
},
iconStyle: { borderColor: '#475569' }, itemSize: 13, top: 0, right: 10
};
const gridConfig = { left: '10%', right: '8%', bottom: '15%', top: '15%' };
const axSty = { fontSize: 10 };
if(charts.chartMain) charts.chartMain.setOption({ toolbox: tbConfig, tooltip: { trigger: 'axis' }, legend: { data: ['测点', '插值', '基线'], top: 2, itemWidth: 10, itemHeight: 6, textStyle: {fontSize: 10} }, grid: gridConfig, xAxis: { type: 'value', name: 'T', nameGap: 10, axisLabel: axSty }, yAxis: { type: 'value', name: 'Cp', axisLabel: axSty }, series: [ { name: '测点', type: 'scatter', symbolSize: 3, data: rawData.map(d=>[d.t, d.cp]), itemStyle: { color: '#0f172a' } }, { name: '插值', type: 'line', data: calcData.map(d=>[d.t, d.cp]), symbol: 'none', lineStyle: { color: '#94a3b8', width: 1 } }, { name: '基线', type: 'line', data: calcData.map(d=>[d.t, d.c_lat]), symbol: 'none', lineStyle: { color: '#f97316', width: 2, type: 'dashed' } } ] }, true);
if(charts.chartCmag) charts.chartCmag.setOption({ toolbox: tbConfig, tooltip: { trigger: 'axis' }, legend: { data: ['Cmag'], top: 2, itemWidth: 10, itemHeight: 6, textStyle: {fontSize: 10} }, grid: gridConfig, xAxis: { type: 'value', name: 'T', nameGap: 10, axisLabel: axSty }, yAxis: { type: 'value', name: 'Cmag', axisLabel: axSty }, series: [ { name: 'Cmag', type: 'line', data: calcData.map(d=>[d.t, d.c_mag]), symbol: 'none', areaStyle: { color: 'rgba(249, 115, 22, 0.2)' }, lineStyle: { color: '#f97316', width: 2 }, markLine: { data: [{ yAxis: 0, lineStyle: {color: '#cbd5e1'} }], symbol: ['none','none'] } } ] }, true);
const pMax = parseFloat(ui.t2PlotMax?.value), aMax = isNaN(pMax) ? Infinity : pMax;
const lMaxX = Math.min(aMax, (parseFloat(ui.t2Max?.value)||50)*1.5);
if(charts.chartLowT) charts.chartLowT.setOption({ toolbox: tbConfig, tooltip: { trigger: 'axis' }, legend: { data: ['实验', '拟合'], top: 2, itemWidth: 10, itemHeight: 6, textStyle: {fontSize: 10} }, grid: gridConfig, xAxis: { type: 'value', name: 'T²', nameGap: 10, axisLabel: axSty }, yAxis: { type: 'value', name: 'Cp/T', scale: true, axisLabel: axSty }, series: [ { name: '实验', type: 'scatter', symbolSize: 3, data: rawData.filter(d=>d.t2<=aMax).map(d=>[d.t2, d.cp_t]), itemStyle: { color: '#3b82f6' } }, { name: '拟合', type: 'line', data: [[0,fitParams.gamma],[lMaxX,fitParams.gamma+fitParams.beta*lMaxX]], symbol: 'none', lineStyle: { color: '#1e3a8a', width: 2 } } ] }, true);
let refVal = null, mOpt = undefined;
if(ui.sRefType && ui.sRefType.value !== 'none') {
refVal = ui.sRefType.value === 'custom' ? parseFloat(ui.sRefCustom?.value||0) : R * Math.log(parseInt(ui.sRefType.value.replace('Rln','')));
mOpt = { symbol: ['none','none'], data: [{ yAxis: refVal, lineStyle: {color: '#b91c1c', type:'dashed'}, label: {formatter:`${refVal.toFixed(2)}`, position:'insideStartTop', color:'#b91c1c', fontSize:10} }] };
}
let sMax = 0;
for(let i=0; i<calcData.length; i++) if(calcData[i].s_mag > sMax) sMax = calcData[i].s_mag;
const yM = refVal !== null ? Math.max(sMax*1.1, refVal*1.2) : null;
if(charts.chartEntropy) charts.chartEntropy.setOption({ toolbox: tbConfig, tooltip: { trigger: 'axis' }, grid: gridConfig, xAxis: { type: 'value', name: 'T', nameGap: 10, axisLabel: axSty }, yAxis: { type: 'value', name: 'Smag', max: yM, axisLabel: axSty }, series: [ { name: 'Smag', type: 'line', data: calcData.map(d=>[d.t, d.s_mag]), symbol: 'none', areaStyle: { color: 'rgba(220, 38, 38, 0.2)' }, lineStyle: { color: '#dc2626', width: 2 }, markLine: mOpt } ] }, true);
}
// --- 全局驱动器 ---
function triggerUpdate() {
try {
if(ui.numParam1 && ui.param1) ui.numParam1.value = ui.param1.value;
if(ui.numParam2 && ui.param2) ui.numParam2.value = ui.param2.value;
if(ui.numWeight && ui.weight) ui.numWeight.value = ui.weight.value;
if(ui.divParam2 && ui.modelType) ui.divParam2.style.display = ui.modelType.value === 'single_debye' ? 'none' : 'block';
if(ui.divWeight && ui.modelType) ui.divWeight.style.display = ui.modelType.value === 'single_debye' ? 'none' : 'block';
if(ui.customSRefDiv && ui.sRefType) ui.customSRefDiv.style.display = ui.sRefType.value === 'custom' ? 'flex' : 'none';
// 动态更新展示卡片
if (ui.modelDescMain && ui.modelType) {
ui.modelDescMain.innerHTML = MODEL_DESCRIPTIONS[ui.modelType.value];
}
processData();
if(rawData.length === 0) return;
processLowT();
calculateMain();
renderMainCharts();
} catch(e) { console.error("Update Error:", e); }
}
// UI 绑定
const sync1 = (e)=>{ if(ui.param1) ui.param1.value=e.target.value; if(ui.numParam1) ui.numParam1.value=e.target.value; triggerUpdate(); };
const sync2 = (e)=>{ if(ui.param2) ui.param2.value=e.target.value; if(ui.numParam2) ui.numParam2.value=e.target.value; triggerUpdate(); };
const syncW = (e)=>{ if(ui.weight) ui.weight.value=e.target.value; if(ui.numWeight) ui.numWeight.value=e.target.value; triggerUpdate(); };
bindEvent(ui.param1, 'input', sync1); bindEvent(ui.numParam1, 'change', sync1);
bindEvent(ui.param2, 'input', sync2); bindEvent(ui.numParam2, 'change', sync2);
bindEvent(ui.weight, 'input', syncW); bindEvent(ui.numWeight, 'change', syncW);
['dataInput', 'nAtoms', 'useInterpolation', 'interpStep', 'forceNonNegative', 't2Min', 't2Max', 't2PlotMax', 'modelType', 'sRefType', 'sRefCustom', 'gammaInput', 'betaInput'].forEach(id => {
bindEvent(ui[id], 'change', triggerUpdate);
if(['dataInput', 't2Min', 't2Max', 't2PlotMax'].includes(id)) bindEvent(ui[id], 'input', triggerUpdate);
});
bindEvent(ui.autoFitLowT, 'change', e => {
if(ui.gammaInput) ui.gammaInput.readOnly = e.target.checked;
if(ui.betaInput) ui.betaInput.readOnly = e.target.checked;
triggerUpdate();
});
bindEvent(ui.enableLattice, 'change', e => {
if(ui.latticeControls) {
ui.latticeControls.style.opacity = e.target.checked ? '1' : '0.4';
ui.latticeControls.style.pointerEvents = e.target.checked ? 'auto' : 'none';
}
triggerUpdate();
});
// --- 高级寻优模块 ---
let advActiveData = [];
let advBestParams = {p1:300, p2:150, w:0.5, loss: Infinity};
let isSearching = false, shouldStop = false;
// 高级窗口物理模型同步更新
bindEvent(advUI.modelType, 'change', () => {
if(advUI.modelDescAdv) advUI.modelDescAdv.innerHTML = MODEL_DESCRIPTIONS[advUI.modelType.value];
});
bindEvent(advUI.openBtn, 'click', () => {
if(advUI.nAtoms && ui.nAtoms) advUI.nAtoms.value = ui.nAtoms.value;
if(advUI.modelType && ui.modelType) {
advUI.modelType.value = ui.modelType.value;
if(advUI.modelDescAdv) advUI.modelDescAdv.innerHTML = MODEL_DESCRIPTIONS[ui.modelType.value];
}
if(ui.sRefType && ui.sRefType.value !== 'none' && ui.sRefType.value !== 'custom') {
if(advUI.targetS) advUI.targetS.value = (R * Math.log(parseInt(ui.sRefType.value.replace('Rln', '')))).toFixed(3);
} else if (ui.sRefType && ui.sRefType.value === 'custom') {
if(advUI.targetS && ui.sRefCustom) advUI.targetS.value = ui.sRefCustom.value;
}
if(advUI.modal) advUI.modal.classList.remove('hidden');
});
bindEvent(advUI.closeBtn, 'click', () => { shouldStop = true; if(advUI.modal) advUI.modal.classList.add('hidden'); });
bindEvent(advUI.stopBtn, 'click', () => { shouldStop = true; });
bindEvent(advUI.runBtn, 'click', async () => {
if(isSearching) return;
isSearching = true; shouldStop = false;
if(advUI.runBtn) { advUI.runBtn.classList.add('hidden'); }
if(advUI.stopBtn) { advUI.stopBtn.classList.remove('hidden'); }
if(advUI.applyBtn) { advUI.applyBtn.classList.add('hidden'); }
const targetS = Math.max(parseFloat(advUI.targetS?.value) || 5.76, 1e-5);
const tMin = parseFloat(advUI.fitTMin?.value) || 150;
const tMax = parseFloat(advUI.fitTMax?.value) || 200;
const iters = parseInt(advUI.iters?.value) || 200;
const nAtoms = parseFloat(advUI.nAtoms?.value) || 1;
const mType = advUI.modelType?.value || 'single_debye';
advBestParams = { p1: parseFloat(ui.numParam1?.value)||300, p2: parseFloat(ui.numParam2?.value)||150, w: parseFloat(ui.numWeight?.value)||0.5, loss: Infinity };
let tempRadius = mType === 'single_debye' ? 200 : 300;
for(let i=0; i<iters; i++) {
if(shouldStop) break;
let candidates = [];
for(let j=0; j<25; j++) {
let cp1 = Math.max(50, Math.min(1500, advBestParams.p1 + (Math.random()*2-1)*tempRadius));
let cp2 = Math.max(50, Math.min(1500, advBestParams.p2 + (Math.random()*2-1)*tempRadius));
let cw = mType==='single_debye' ? 1.0 : Math.max(0, Math.min(1, advBestParams.w + (Math.random()*2-1)*0.2));
if(mType==='debye_einstein' && cp2 < 50) cp2 = 50;
candidates.push({p1:cp1, p2:cp2, w:cw});
}
for(let c of candidates) {
let res = quickEvaluate(c.p1, c.p2, c.w, true, nAtoms, mType);
let cpLoss = 0, count = 0;
for(let d of res) {
if(d.t >= tMin && d.t <= tMax) {
const tc = Math.max(d.cp - fitParams.gamma*d.t, 1e-5);
cpLoss += Math.pow((d.c_lat - tc)/tc, 2); count++;
}
}
cpLoss = count > 0 ? Math.sqrt(cpLoss/count) : 1000;
const finalS = res.length > 0 ? res[res.length-1].s_mag : 0;
const sLoss = Math.abs(finalS - targetS) / targetS;
const loss = cpLoss * 5.0 + sLoss * 1.0;
if(loss < advBestParams.loss) { advBestParams = { p1: c.p1, p2: c.p2, w: c.w, loss: loss }; }
}
tempRadius *= 0.95;
if(i % 5 === 0 || i === (iters - 1)) {
if(advUI.resLoss) advUI.resLoss.innerText = advBestParams.loss.toFixed(4);
if(advUI.resP1) advUI.resP1.innerText = advBestParams.p1.toFixed(1);
if(advUI.resP2) advUI.resP2.innerText = mType==='single_debye'? '--': advBestParams.p2.toFixed(1);
if(advUI.resW) advUI.resW.innerText = mType==='single_debye'? '--': advBestParams.w.toFixed(2);
advActiveData = quickEvaluate(advBestParams.p1, advBestParams.p2, advBestParams.w, true, nAtoms, mType);
if(advUI.resS) advUI.resS.innerText = advActiveData[advActiveData.length-1].s_mag.toFixed(3);
if(charts.chartAdvanced) {
charts.chartAdvanced.setOption({
toolbox: { show: true, feature: { myExportCsv: { show: true, title: '📥 导出此优解CSV', icon: 'path://M14,2L20,8V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V4A2,2 0 0,1 6,2H14M18,20V9H13V4H6V20H18M12,19L8,15H10.5V12H13.5V15H16L12,19Z', onclick: ()=>exportToCSV(advActiveData) } }, top: 0, right: 10 },
tooltip: { trigger: 'axis', textStyle: {fontSize: 10} }, legend: { data: ['总比热 Cp', '拟合基线 Clat', '拟合过程磁熵'], bottom: 0, textStyle: {fontSize: 10} },
grid: [ { left: '10%', right: '10%', top: '10%', height: '40%' }, { left: '10%', right: '10%', top: '60%', height: '30%' } ],
xAxis: [ { type: 'value', gridIndex: 0, axisLabel:{show:false} }, { type: 'value', name:'T(K)', nameLocation:'middle', nameGap:20, gridIndex: 1, axisLabel:{fontSize:10} } ],
yAxis: [ { type: 'value', name: 'Cp', gridIndex: 0, axisLabel:{fontSize:10} }, { type: 'value', name: 'Smag', gridIndex: 1, axisLabel:{fontSize:10} } ],
series: [
{ name: '总比热 Cp', type: 'scatter', symbolSize: 3, data: advActiveData.map(d=>[d.t, d.cp]), xAxisIndex: 0, yAxisIndex: 0, itemStyle:{color:'#94a3b8'} },
{ name: '拟合基线 Clat', type: 'line', data: advActiveData.map(d=>[d.t, d.c_lat]), xAxisIndex: 0, yAxisIndex: 0, lineStyle:{color:'#f97316', width:2}, markArea: { itemStyle:{color:'rgba(249,115,22,0.1)'}, data:[ [{xAxis: tMin}, {xAxis: tMax}] ] } },
{ name: '拟合过程磁熵', type: 'line', data: advActiveData.map(d=>[d.t, d.s_mag]), xAxisIndex: 1, yAxisIndex: 1, lineStyle:{color:'#ef4444'}, markLine: { data: [{yAxis: targetS, lineStyle:{color:'#b91c1c', type:'dashed'}}], symbol: ['none','none'] } }
]
}, true);
}
await new Promise(r => setTimeout(r, 20));
}
}
if(advUI.runBtn) { advUI.runBtn.innerHTML = `重新开始寻优`; advUI.runBtn.classList.remove('hidden'); }
if(advUI.stopBtn) { advUI.stopBtn.classList.add('hidden'); }
if(advUI.applyBtn) { advUI.applyBtn.classList.remove('hidden'); }
isSearching = false;
});
bindEvent(advUI.applyBtn, 'click', () => {
if(advBestParams.loss !== Infinity) {
if(ui.nAtoms && advUI.nAtoms) ui.nAtoms.value = advUI.nAtoms.value;
if(ui.modelType && advUI.modelType) ui.modelType.value = advUI.modelType.value;
if(ui.param1) ui.param1.value = advBestParams.p1;
if(ui.numParam1) ui.numParam1.value = advBestParams.p1.toFixed(1);
if(ui.modelType && ui.modelType.value !== 'single_debye') {
if(ui.param2) ui.param2.value = advBestParams.p2;
if(ui.numParam2) ui.numParam2.value = advBestParams.p2.toFixed(1);
if(ui.weight) ui.weight.value = advBestParams.w;
if(ui.numWeight) ui.numWeight.value = advBestParams.w.toFixed(2);
}
triggerUpdate();
}
if(advUI.modal) advUI.modal.classList.add('hidden');
});
// 启动
window.onload = () => {
if(ui.dataInput) ui.dataInput.value = initialData;
initCharts();
setTimeout(triggerUpdate, 50);
};
</script>
</body>
</html>