Spaces:
Sleeping
Sleeping
<html lang="zh-TW"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>營養液反向計算機</title> | |
<style> | |
.container { | |
max-width: 800px; | |
margin: 0 auto; | |
padding: 20px; | |
} | |
.fertilizer-list { | |
margin-top: 20px; | |
border: 1px solid #e0e0e0; | |
padding: 20px; | |
border-radius: 12px; | |
background-color: #ffffff; | |
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); | |
} | |
.fertilizer-list h2 { | |
margin-top: 0; | |
color: #2c3e50; | |
border-bottom: 2px solid #eef2f7; | |
padding-bottom: 12px; | |
margin-bottom: 20px; | |
font-size: 1.4em; | |
} | |
.fertilizer-list ul { | |
list-style: none; | |
padding: 0; | |
margin: 0; | |
} | |
.fertilizer-list li { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
padding: 12px 16px; | |
margin-bottom: 10px; | |
background-color: #f8fafc; | |
border: 1px solid #edf2f7; | |
border-radius: 8px; | |
transition: all 0.2s ease; | |
} | |
.fertilizer-list li:hover { | |
background-color: #edf2f7; | |
transform: translateX(2px); | |
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); | |
} | |
.fertilizer-list .fertilizer-info { | |
flex-grow: 1; | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
padding-right: 24px; | |
min-width: 0; | |
gap: 16px; | |
} | |
.fertilizer-list .formula { | |
font-weight: 500; | |
color: #2d3748; | |
flex: 1; | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
font-size: 0.95em; | |
} | |
.fertilizer-list .amount { | |
display: flex; | |
align-items: center; | |
gap: 8px; | |
min-width: 120px; | |
justify-content: flex-end; | |
} | |
.fertilizer-list .amount-display { | |
cursor: pointer; | |
padding: 4px 8px; | |
border-radius: 4px; | |
background-color: #edf2f7; | |
color: #4a5568; | |
font-family: monospace; | |
transition: all 0.2s ease; | |
} | |
.fertilizer-list .amount-display:hover { | |
background-color: #e2e8f0; | |
} | |
.fertilizer-list .amount-input { | |
width: 100px; | |
padding: 4px 8px; | |
border: 1px solid #cbd5e0; | |
border-radius: 4px; | |
text-align: right; | |
font-family: monospace; | |
} | |
.fertilizer-list .edit-controls { | |
display: flex; | |
gap: 4px; | |
} | |
.fertilizer-list .edit-btn { | |
padding: 4px 8px; | |
border: none; | |
border-radius: 4px; | |
cursor: pointer; | |
font-size: 14px; | |
transition: all 0.2s ease; | |
} | |
.fertilizer-list .save-btn { | |
background-color: #48bb78; | |
color: white; | |
} | |
.fertilizer-list .save-btn:hover { | |
background-color: #38a169; | |
} | |
.fertilizer-list .cancel-btn { | |
background-color: #a0aec0; | |
color: white; | |
} | |
.fertilizer-list .cancel-btn:hover { | |
background-color: #718096; | |
} | |
.fertilizer-list .remove-btn { | |
background-color: #fc8181; | |
color: white; | |
border: none; | |
padding: 6px 12px; | |
border-radius: 6px; | |
cursor: pointer; | |
transition: all 0.2s ease; | |
font-size: 0.9em; | |
} | |
.fertilizer-list .remove-btn:hover { | |
background-color: #f56565; | |
} | |
.no-fertilizers { | |
text-align: center; | |
color: #718096; | |
font-style: italic; | |
padding: 24px; | |
background-color: #f7fafc; | |
border-radius: 8px; | |
border: 1px dashed #cbd5e0; | |
} | |
.result-table { | |
width: 100%; | |
border-collapse: collapse; | |
margin-top: 20px; | |
} | |
.result-table th, .result-table td { | |
border: 1px solid #ccc; | |
padding: 8px; | |
text-align: center; | |
} | |
.fertilizer-selector select { | |
width: 100%; | |
margin: 10px 0; | |
max-height: 200px; | |
} | |
#fertilizer-search { | |
width: 100%; | |
padding: 8px; | |
margin-bottom: 5px; | |
border: 1px solid #ccc; | |
border-radius: 4px; | |
} | |
.test-btn { | |
background-color: #3498db; | |
color: white; | |
border: none; | |
padding: 8px 15px; | |
border-radius: 4px; | |
cursor: pointer; | |
margin-bottom: 10px; | |
transition: background-color 0.2s; | |
} | |
.test-btn:hover { | |
background-color: #2980b9; | |
} | |
/* 新增的樣式 */ | |
.target-input { | |
width: 80px; | |
padding: 4px 8px; | |
border: 1px solid #cbd5e0; | |
border-radius: 4px; | |
text-align: right; | |
font-family: monospace; | |
} | |
.target-row td { | |
padding: 8px; | |
vertical-align: middle; | |
} | |
/* 添加導航按鈕樣式 */ | |
.nav-button { | |
display: inline-block; | |
padding: 8px 16px; | |
background-color: #4299e1; | |
color: white; | |
text-decoration: none; | |
border-radius: 6px; | |
transition: all 0.2s ease; | |
margin-bottom: 20px; | |
} | |
.nav-button:hover { | |
background-color: #3182ce; | |
transform: translateY(-1px); | |
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
} | |
.input-mode-switch { | |
display: flex; | |
gap: 10px; | |
margin-bottom: 15px; | |
justify-content: flex-end; | |
} | |
.mode-btn { | |
padding: 6px 12px; | |
border: 1px solid #4299e1; | |
background-color: white; | |
color: #4299e1; | |
border-radius: 4px; | |
cursor: pointer; | |
transition: all 0.2s ease; | |
} | |
.mode-btn.active { | |
background-color: #4299e1; | |
color: white; | |
} | |
.target-input[disabled] { | |
background-color: #f7fafc; | |
cursor: not-allowed; | |
} | |
.control-panel { | |
margin: 20px 0; | |
text-align: center; | |
} | |
.calculate-btn { | |
background-color: #48bb78; | |
color: white; | |
border: none; | |
padding: 12px 24px; | |
border-radius: 6px; | |
cursor: pointer; | |
font-size: 1.1em; | |
transition: all 0.2s ease; | |
} | |
.calculate-btn:hover { | |
background-color: #38a169; | |
transform: translateY(-1px); | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<h1>營養液反向計算機</h1> | |
<a href="index.html" class="nav-button"> | |
← 返回成分計算機 | |
</a> | |
<!-- 目標養分輸入區域 --> | |
<div class="fertilizer-list"> | |
<h2>目標養分含量</h2> | |
<div class="input-mode-switch"> | |
<button class="mode-btn active" data-mode="ppm">ppm 模式</button> | |
<button class="mode-btn" data-mode="meq">meq/L 模式</button> | |
</div> | |
<table class="result-table"> | |
<thead> | |
<tr> | |
<th>元素</th> | |
<th>目標 ppm</th> | |
<th>目標 meq/L</th> | |
<th>目標 mmol/L</th> | |
</tr> | |
</thead> | |
<tbody id="target-nutrients"> | |
<!-- 將由 JavaScript 動態填充 --> | |
</tbody> | |
</table> | |
<div class="control-panel"> | |
<button onclick="calculateFertilizerAmounts()" class="calculate-btn"> | |
計算肥料用量 | |
</button> | |
</div> | |
</div> | |
<!-- 可用肥料清單 --> | |
<div class="fertilizer-list"> | |
<h2>可用肥料</h2> | |
<div id="available-fertilizers"> | |
<!-- 由 JavaScript 生成 --> | |
</div> | |
</div> | |
</div> | |
<script> | |
// 添加名稱對應表 | |
const fertilizerAliases = { | |
'Fe-EDTA·3H2O': 'C10H12FeN2NaO8·3H2O', | |
'螯合鐵': 'C10H12FeN2NaO8·3H2O' | |
}; | |
// 分子量對照表 | |
const molecularWeights = { | |
// 化合物 | |
'Ca(NO3)2·4H2O': 236.149920, // 40.078 + 2(14.0067 + 3*15.999) + 4(18.015340) | |
'KNO3': 101.102530, // 39.0983 + 14.0067 + 3*15.999 | |
'MgSO4·7H2O': 246.474780, // 24.305 + 32.065 + 4*15.999 + 7(18.015340) | |
'NH4H2PO4': 115.025738, // 14.0067 + 4*1.007940 + 30.973762 + 4*15.999 | |
'Ca(H2PO4)2·H2O': 252.082802, // 40.078 + 2(2*1.007940 + 30.973762 + 4*15.999) + 18.015340 | |
'C10H12FeN2NaO8·3H2O': 412.147420, // Fe-EDTA·3H2O: 10*12.0107 + 12*1.007940 + 55.845 + 2*14.0067 + 22.989770 + 8*15.999 + 3(18.015340) | |
'H3BO3': 61.833220, // 3*1.007940 + 10.811 + 3*15.999 | |
'MnCl2·4H2O': 197.905360, // 54.938045 + 2*35.453 + 4(18.015340) | |
'ZnSO4·7H2O': 287.539780, // 65.38 + 32.065 + 4*15.999 + 7(18.015340) | |
'CuSO4·5H2O': 249.685700, // 63.546 + 32.065 + 4*15.999 + 5(18.015340) | |
'Na2MoO4·2H2O': 241.950680, // 2*22.989770 + 95.96 + 4*15.999 + 2(18.015340) | |
'KH2PO4': 136.085838, // 39.0983 + 2*1.007940 + 30.973762 + 4*15.999 | |
'Ca(NO3)2': 164.088400, // 40.078 + 2(14.0067 + 3*15.999) | |
'MgSO4': 120.366000, // 24.305 + 32.065 + 4*15.999 | |
'(NH4)6Mo7O24·4H2O': 1235.86, // 6(14.0067 + 4*1.007940) + 7*95.96 + 24*15.999 + 4(2*1.007940 + 15.999) | |
'(NH4)2MoO4': 196.0354, // (2 * (14.0067 + 4 * 1.00794)) + 95.96 + (4 * 15.999) | |
// 元素 | |
'N': 14.006700, | |
'P': 30.973762, | |
'K': 39.098300, | |
'Ca': 40.078000, | |
'Mg': 24.305000, | |
'S': 32.065000, | |
'Fe': 55.845000, | |
'Mn': 54.938045, | |
'Zn': 65.380000, | |
'Cu': 63.546000, | |
'B': 10.811000, | |
'Mo': 95.960000, | |
'Cl': 35.453000, | |
// 常用計算值 | |
'H': 1.007940, | |
'O': 15.999000, | |
'Na': 22.989770, | |
'C': 12.010700, | |
'H2O': 18.015340 // 2*1.007940 + 15.999 | |
}; | |
// 肥料成分對照表(以重量比例表示) | |
const fertilizerComposition = { | |
// 大量元素肥料 | |
'Ca(NO3)2·4H2O': { | |
'Ca': 0.169715, // 40.078 / 236.149920 = 16.97% Ca | |
'N': 0.118721 // (2 * 14.0067) / 236.149920 = 11.87% N | |
}, | |
'KNO3': { | |
'K': 0.386716, // 39.0983 / 101.102530 = 38.67% K | |
'N': 0.138486 // 14.0067 / 101.102530 = 13.85% N | |
}, | |
'MgSO4·7H2O': { | |
'Mg': 0.098611, // 24.305 / 246.474780 = 9.86% Mg | |
'S': 0.130095 // 32.065 / 246.474780 = 13.01% S | |
}, | |
'NH4H2PO4': { | |
'N': 0.121773, // 14.0067 / 115.025738 = 12.18% N | |
'P': 0.269278, // 30.973762 / 115.025738 = 26.93% P | |
'H': 0.035023 // (4 * 1.007940) / 115.025738 = 3.50% H | |
}, | |
'Ca(H2PO4)2·H2O': { | |
'Ca': 0.158987, // 40.078 / 252.082802 = 15.90% Ca | |
'P': 0.245848, // (2 * 30.973762) / 252.082802 = 24.58% P | |
'H': 0.008000 // (4 * 1.007940) / 252.082802 = 0.80% H | |
}, | |
'KH2PO4': { | |
'K': 0.287305, // 39.0983 / 136.085838 = 28.73% K | |
'P': 0.227605, // 30.973762 / 136.085838 = 22.76% P | |
'H': 0.014819 // (2 * 1.007940) / 136.085838 = 1.48% H | |
}, | |
'Ca(NO3)2': { | |
'Ca': 0.244497, // 40.078 / 164.088400 = 24.45% Ca | |
'N': 0.170744 // (2 * 14.0067) / 164.088400 = 17.07% N | |
}, | |
'MgSO4': { | |
'Mg': 0.201927, // 24.305 / 120.366000 = 20.19% Mg | |
'S': 0.266393 // 32.065 / 120.366000 = 26.64% S | |
}, | |
// 微量元素肥料 | |
'C10H12FeN2NaO8·3H2O': { | |
'Fe': 0.135498 // 55.845 / 412.147420 = 13.55% Fe | |
}, | |
'H3BO3': { | |
'B': 0.174842 // 10.811 / 61.833220 = 17.48% B | |
}, | |
'MnCl2·4H2O': { | |
'Mn': 0.277595, // 54.938045 / 197.905360 = 27.76% Mn | |
'Cl': 0.358521 // (2 * 35.453) / 197.905360 = 35.85% Cl | |
}, | |
'ZnSO4·7H2O': { | |
'Zn': 0.227379, // 65.38 / 287.539780 = 22.74% Zn | |
'S': 0.111515 // 32.065 / 287.539780 = 11.15% S | |
}, | |
'CuSO4·5H2O': { | |
'Cu': 0.254504, // 63.546 / 249.685700 = 25.45% Cu | |
'S': 0.128421 // 32.065 / 249.685700 = 12.84% S | |
}, | |
'Na2MoO4·2H2O': { | |
'Mo': 0.396610 // 95.96 / 241.950680 = 39.66% Mo | |
}, | |
'(NH4)6Mo7O24·4H2O': { | |
'N': 0.068033, // (6 * 14.0067) / 1235.86 = 6.80% N | |
'Mo': 0.543754 // (7 * 95.96) / 1235.86 = 54.38% Mo | |
}, | |
'(NH4)2MoO4': { | |
'N': 0.142935, // (2 * 14.0067) / 196.0354 = 14.29% N | |
'Mo': 0.489497 // (1 * 95.96) / 196.0354 = 48.95% Mo | |
} | |
}; | |
// 離子價數對照表(用於 meq 計算) | |
const ionicCharges = { | |
'N': -1, // NO3- 形式 | |
'P': -1, // H2PO4- 形式 | |
'K': +1, // K+ 形式 | |
'Ca': +2, // Ca2+ 形式 | |
'Mg': +2, // Mg2+ 形式 | |
'S': -2, // SO4-2 形式 | |
'Fe': +2, // Fe2+ 形式 | |
'Mn': +2, // Mn2+ 形式 | |
'Zn': +2, // Zn2+ 形式 | |
'Cu': +2, // Cu2+ 形式 | |
'B': +3, // B3+ 形式 | |
'Mo': -2, // MoO4-2 形式 | |
'Cl': -1 // Cl- 形式 | |
}; | |
const selectedFertilizers = []; | |
// 定義肥料清單 | |
const fertilizerList = [ | |
{ value: 'Ca(NO3)2·4H2O', label: '硝酸鈣(四水) Ca(NO3)2·4H2O' }, | |
{ value: 'NH4H2PO4', label: '磷酸一銨 NH4H2PO4' }, | |
{ value: 'MgSO4·7H2O', label: '硫酸鎂(七水) MgSO4·7H2O' }, | |
{ value: 'Ca(H2PO4)2·H2O', label: '磷酸一鈣(一水) Ca(H2PO4)2·H2O' }, | |
{ value: 'Ca(NO3)2', label: '硝酸鈣 Ca(NO3)2' }, | |
{ value: 'KNO3', label: '硝酸鉀 KNO3' }, | |
{ value: 'MgSO4', label: '硫酸鎂 MgSO4' }, | |
{ value: 'KH2PO4', label: '磷酸二氫鉀 KH2PO4' }, | |
{ value: 'Fe-EDTA·3H2O', label: '螯合鐵(Fe-EDTA·3H2O)' }, | |
{ value: 'H3BO3', label: '硼酸 H3BO3' }, | |
{ value: 'MnCl2·4H2O', label: '氯化錳(四水) MnCl2·4H2O' }, | |
{ value: 'ZnSO4·7H2O', label: '硫酸鋅(七水) ZnSO4·7H2O' }, | |
{ value: 'CuSO4·5H2O', label: '硫酸銅(五水) CuSO4·5H2O' }, | |
{ value: 'Na2MoO4·2H2O', label: '鉬酸鈉(二水) Na2MoO4·2H2O' }, | |
{ value: '(NH4)6Mo7O24·4H2O', label: '仲鉬酸銨(四水) (NH4)6Mo7O24·4H2O' }, | |
{ value: '(NH4)2MoO4', label: '鉬酸銨 (NH4)2MoO4' } | |
]; | |
// 添加反向計算的特定函數 | |
// ... | |
// 全局變量 | |
let availableFertilizers = []; | |
let targetNutrients = { | |
'N': { ppm: 0, meq: 0, mmol: 0 }, | |
'P': { ppm: 0, meq: 0, mmol: 0 }, | |
'K': { ppm: 0, meq: 0, mmol: 0 }, | |
'Ca': { ppm: 0, meq: 0, mmol: 0 }, | |
'Mg': { ppm: 0, meq: 0, mmol: 0 }, | |
'S': { ppm: 0, meq: 0, mmol: 0 }, | |
'Fe': { ppm: 0, meq: 0, mmol: 0 }, | |
'Mn': { ppm: 0, meq: 0, mmol: 0 }, | |
'Zn': { ppm: 0, meq: 0, mmol: 0 }, | |
'Cu': { ppm: 0, meq: 0, mmol: 0 }, | |
'B': { ppm: 0, meq: 0, mmol: 0 }, | |
'Mo': { ppm: 0, meq: 0, mmol: 0 }, | |
'Cl': { ppm: 0, meq: 0, mmol: 0 } | |
}; | |
let currentInputMode = 'ppm'; // 預設使用 ppm 模式 | |
// 全域常數定義 | |
const SCALE_FACTOR = 1000000; // 放大倍數(100萬倍) | |
const STEP_SIZE = 0.01; // 最小調整單位 (mg/L) | |
const MAX_ITERATIONS = 1000; // 最大迭代次數 | |
const TOLERANCE = 0.05; // 允許誤差 5% | |
const MIN_IMPROVEMENT = 0.001; // 最小改善閾值 | |
// 調整後的步進大小(放大後的值) | |
const SCALED_STEP_SIZE = Math.round(STEP_SIZE * SCALE_FACTOR); // 放大後的最小調整單位 | |
// 初始化頁面 | |
document.addEventListener('DOMContentLoaded', function() { | |
initializePage(); | |
}); | |
// 初始化頁面 | |
function initializePage() { | |
// 初始化目標養分輸入表格 | |
initializeTargetNutrients(); | |
// 初始化所有可用肥料 | |
initializeAllFertilizers(); | |
// 初始化模式切換按鈕 | |
initializeInputModeSwitch(); | |
} | |
// 初始化目標養分輸入表格 | |
function initializeTargetNutrients() { | |
const tbody = document.getElementById('target-nutrients'); | |
tbody.innerHTML = ''; | |
Object.entries(targetNutrients).forEach(([element, values]) => { | |
const tr = document.createElement('tr'); | |
tr.className = 'target-row'; | |
// 在元素符號後添加離子電荷標示 | |
const chargeSymbol = ionicCharges[element] > 0 ? | |
`<sup>${ionicCharges[element]}+</sup>` : | |
`<sup>${Math.abs(ionicCharges[element])}-</sup>`; | |
tr.innerHTML = ` | |
<td>${element}${chargeSymbol}</td> | |
<td><input type="number" class="target-input" data-element="${element}" data-type="ppm" value="0" step="0.0001"></td> | |
<td><input type="number" class="target-input" data-element="${element}" data-type="meq" value="0" step="0.000001"></td> | |
<td><input type="number" class="target-input" data-element="${element}" data-type="mmol" value="0" step="0.000001"></td> | |
`; | |
tbody.appendChild(tr); | |
}); | |
// 添加輸入事件監聽器 | |
document.querySelectorAll('.target-input').forEach(input => { | |
input.addEventListener('input', handleTargetInput); | |
}); | |
} | |
// 處理目標養分輸入 | |
function handleTargetInput(event) { | |
const input = event.target; | |
const element = input.dataset.element; | |
const type = input.dataset.type; | |
const value = parseFloat(input.value) || 0; | |
// 更新目標值 | |
targetNutrients[element][type] = roundNumber(value, type === 'ppm' ? 4 : 6); | |
// 自動計算其他值 | |
updateOtherValues(element, type, value); | |
} | |
// 更新其他相關值 | |
function updateOtherValues(element, changedType, value) { | |
const inputs = document.querySelectorAll(`.target-input[data-element="${element}"]`); | |
const molecularWeight = molecularWeights[element]; | |
const charge = Math.abs(ionicCharges[element]); | |
inputs.forEach(input => { | |
if (input.dataset.type !== changedType) { | |
let newValue = 0; | |
switch (changedType) { | |
case 'ppm': | |
if (input.dataset.type === 'mmol') { | |
newValue = value / molecularWeight; | |
} else if (input.dataset.type === 'meq') { | |
newValue = (value / molecularWeight) * charge; | |
} | |
break; | |
case 'meq': | |
if (input.dataset.type === 'mmol') { | |
newValue = value / charge; | |
} else if (input.dataset.type === 'ppm') { | |
newValue = (value / charge) * molecularWeight; | |
} | |
break; | |
} | |
input.value = formatNumber(newValue, input.dataset.type === 'ppm' ? 4 : 6); | |
targetNutrients[element][input.dataset.type] = roundNumber(newValue, input.dataset.type === 'ppm' ? 4 : 6); | |
} | |
}); | |
} | |
// 添加新的初始化函數 | |
function initializeInputModeSwitch() { | |
const buttons = document.querySelectorAll('.mode-btn'); | |
buttons.forEach(button => { | |
button.addEventListener('click', function() { | |
const mode = this.dataset.mode; | |
setInputMode(mode); | |
// 更新按鈕狀態 | |
buttons.forEach(btn => btn.classList.remove('active')); | |
this.classList.add('active'); | |
}); | |
}); | |
// 初始設置 | |
setInputMode(currentInputMode); | |
} | |
// 設置輸入模式 | |
function setInputMode(mode) { | |
currentInputMode = mode; | |
const inputs = document.querySelectorAll('.target-input'); | |
inputs.forEach(input => { | |
if (input.dataset.type === 'mmol') { | |
// mmol 始終禁用,但保持顯示 | |
input.disabled = true; | |
input.style.backgroundColor = '#f7fafc'; | |
} else if (input.dataset.type === mode) { | |
// 啟用當前模式的輸入 | |
input.disabled = false; | |
input.style.backgroundColor = 'white'; | |
} else { | |
// 禁用其他模式的輸入 | |
input.disabled = true; | |
input.style.backgroundColor = '#f7fafc'; | |
} | |
}); | |
} | |
// 精度處理工具函數 | |
function roundNumber(value, precision = 3) { | |
if (typeof value !== 'number' || isNaN(value)) return 0; | |
const multiplier = Math.pow(10, precision); | |
// 使用四捨六入五成雙規則 | |
const roundedValue = Math.round(value * multiplier) / multiplier; | |
return roundedValue; | |
} | |
// 格式化顯示數值 | |
function formatNumber(value, precision = 3) { | |
const roundedValue = roundNumber(value, precision); | |
// 確保顯示指定的小數位數 | |
return roundedValue.toFixed(precision); | |
} | |
// 初始化所有可用肥料 | |
function initializeAllFertilizers() { | |
availableFertilizers = fertilizerList.map(fertilizer => ({ | |
formula: fertilizer.value, | |
amount: 0 | |
})); | |
updateFertilizerList(); | |
} | |
// 更新肥料列表顯示 | |
function updateFertilizerList() { | |
const container = document.getElementById('available-fertilizers'); | |
const ul = document.createElement('ul'); | |
availableFertilizers.forEach((fertilizer, index) => { | |
const li = document.createElement('li'); | |
const label = fertilizerList.find(f => f.value === fertilizer.formula)?.label || fertilizer.formula; | |
li.innerHTML = ` | |
<div class="fertilizer-info"> | |
<span class="formula">${label}</span> | |
<div class="amount"> | |
<span class="amount-display">${fertilizer.amount.toFixed(2)} g/L</span> | |
</div> | |
</div> | |
`; | |
ul.appendChild(li); | |
}); | |
container.innerHTML = ''; | |
container.appendChild(ul); | |
} | |
// 執行反向計算 | |
async function calculateFertilizerAmounts() { | |
// 1. 準備數據 | |
const targetData = prepareTargetData(); | |
if (Object.keys(targetData).length === 0) { | |
alert('請設定至少一個目標養分含量'); | |
return; | |
} | |
const fertilizerMatrix = prepareFertilizerMatrix(); | |
const constraints = prepareConstraintMatrix(targetData, fertilizerMatrix); | |
try { | |
// 使用動態規劃求解器 | |
const solver = new DPSolver(targetData, fertilizerMatrix, constraints); | |
const solution = solver.solve(); | |
// 更新結果 | |
updateCalculationResults(solution); | |
} catch (error) { | |
alert('無法找到合適的解決方案,請調整目標值'); | |
console.error('計算錯誤:', error); | |
} | |
} | |
// 檢查解是否有效 | |
function isValidSolution(solution) { | |
// 檢查是否所有必需的元素都達到目標值 | |
return Object.entries(targetNutrients).every(([element, values]) => { | |
if (values.ppm <= 0) return true; // 跳過目標值為 0 的元素 | |
const calculated = calculateElementAmount(element, solution); | |
return Math.abs(calculated - values.ppm) / values.ppm < 0.01; // 允許 1% 的誤差 | |
}); | |
} | |
// 計算某個元素的實際含量 | |
function calculateElementAmount(element, solution) { | |
let amount = 0; | |
Object.entries(solution).forEach(([formula, weight]) => { | |
const actualFormula = fertilizerAliases[formula] || formula; | |
const composition = fertilizerComposition[actualFormula]; | |
if (composition && composition[element]) { | |
amount += weight * composition[element]; | |
} | |
}); | |
return amount; | |
} | |
// 更新計算結果 | |
function updateCalculationResults(solution) { | |
availableFertilizers = availableFertilizers.map(fert => ({ | |
...fert, | |
amount: solution[fert.formula] || 0 | |
})); | |
updateFertilizerList(); | |
// 可以添加更詳細的結果顯示,如實際達到的養分含量等 | |
} | |
// 準備目標養分數據 | |
function prepareTargetData() { | |
const targetData = {}; | |
Object.entries(targetNutrients).forEach(([element, values]) => { | |
if (values[currentInputMode] > 0) { | |
targetData[element] = { | |
target: values[currentInputMode], | |
tolerance: values[currentInputMode] * TOLERANCE_PERCENTAGE, // 改為5%誤差 | |
unit: currentInputMode, | |
min: values[currentInputMode] * (1 - TOLERANCE_PERCENTAGE), | |
max: values[currentInputMode] * (1 + TOLERANCE_PERCENTAGE) | |
}; | |
} | |
}); | |
if (Object.keys(targetData).length === 0) { | |
throw new Error('未設定任何目標養分含量'); | |
} | |
return targetData; | |
} | |
// 準備肥料成分矩陣 | |
function prepareFertilizerMatrix() { | |
const matrix = {}; | |
const validFertilizers = availableFertilizers.filter(fert => { | |
const actualFormula = fertilizerAliases[fert.formula] || fert.formula; | |
return fertilizerComposition[actualFormula] !== undefined; | |
}); | |
if (validFertilizers.length === 0) { | |
throw new Error('沒有可用的肥料'); | |
} | |
validFertilizers.forEach(fert => { | |
const actualFormula = fertilizerAliases[fert.formula] || fert.formula; | |
const composition = fertilizerComposition[actualFormula]; | |
matrix[fert.formula] = { | |
composition: {}, | |
price: fertilizerPrices[actualFormula] || 0 // 添加價格信息 | |
}; | |
Object.entries(composition).forEach(([element, content]) => { | |
if (element in targetNutrients && content > 0) { | |
matrix[fert.formula].composition[element] = content; | |
} | |
}); | |
}); | |
return matrix; | |
} | |
// 建立約束條件矩陣 | |
function prepareConstraintMatrix(targetData, fertilizerMatrix) { | |
const constraints = { | |
elements: Object.keys(targetData), | |
fertilizers: Object.keys(fertilizerMatrix), | |
matrix: {}, | |
bounds: targetData, | |
minAmount: 0, | |
maxAmount: 10, | |
totalMaxAmount: 20, | |
prices: {} // 添加價格信息 | |
}; | |
// 建立元素-肥料對應矩陣 | |
constraints.elements.forEach(element => { | |
constraints.matrix[element] = {}; | |
constraints.fertilizers.forEach(fertilizer => { | |
constraints.matrix[element][fertilizer] = | |
fertilizerMatrix[fertilizer].composition[element] || 0; | |
}); | |
}); | |
// 添加價格訊息 | |
constraints.fertilizers.forEach(fertilizer => { | |
constraints.prices[fertilizer] = fertilizerMatrix[fertilizer].price; | |
}); | |
return constraints; | |
} | |
// 添加在全局常數區域 | |
const TOLERANCE_PERCENTAGE = 0.05; // 5% 誤差 | |
const fertilizerPrices = { | |
'Ca(NO3)2·4H2O': 50, // 硝酸鈣(四水),非現況價格 | |
'NH4H2PO4': 80, // 磷酸一銨,非現況價格 | |
'MgSO4·7H2O': 20, // 硫酸鎂(七水),非現況價格 | |
'Ca(H2PO4)2·H2O': 60, // 磷酸一鈣(一水),非現況價格 | |
'Ca(NO3)2': 999, // 硝酸鈣,尚未提供價格 | |
'KNO3': 30, // 硝酸鉀,非現況價格 | |
'MgSO4': 999, // 硫酸鎂,尚未提供價格 | |
'KH2PO4': 999, // 磷酸二氫鉀,尚未提供價格 | |
'Fe-EDTA·3H2O': 500, // 螯合鐵,非現況價格 | |
'H3BO3': 60, // 硼酸,非現況價格 | |
'MnCl2·4H2O': 55, // 氯化錳(四水),非現況價格 | |
'ZnSO4·7H2O': 45, // 硫酸鋅(七水),非現況價格 | |
'CuSO4·5H2O': 40, // 硫酸銅(五水),非現況價格 | |
'Na2MoO4·2H2O': 1400, // 鉬酸鈉(二水),非現況價格 | |
'(NH4)6Mo7O24·4H2O': 999, // 仲鉬酸銨(四水),尚未提供價格 | |
'(NH4)2MoO4': 999 // 鉬酸銨,尚未提供價格 | |
}; | |
// 計算特定解的總成本 | |
function calculateTotalCost(solution, fertilizerMatrix) { | |
let totalCost = 0; | |
Object.entries(solution).forEach(([formula, amount]) => { | |
const actualFormula = fertilizerAliases[formula] || formula; | |
const price = fertilizerMatrix[formula].price; | |
totalCost += amount * price; // amount 是 g/L,需要轉換為 kg | |
}); | |
return totalCost; | |
} | |
// 計算解的有效性(考慮誤差範圍) | |
function isValidSolution(solution, targetData) { | |
return Object.entries(targetData).every(([element, data]) => { | |
const calculated = calculateElementAmount(element, solution); | |
return calculated >= data.min && calculated <= data.max; | |
}); | |
} | |
class FertilizerSolver { | |
constructor(targetData, fertilizerMatrix, constraints) { | |
this.targetData = targetData; | |
this.fertilizerMatrix = fertilizerMatrix; | |
this.constraints = constraints; | |
this.stepSize = STEP_SIZE; | |
this.bestSolution = null; | |
this.bestCost = Infinity; | |
this.currentIteration = 0; | |
// 添加精確度控制 | |
this.precision = { | |
amount: 0.01, // mg/L | |
cost: 0.001, // 成本計算精確度 | |
nutrient: 0.01 // 養分含量計算精確度 | |
}; | |
} | |
// 四捨五入到指定精確度 | |
round(value, precision) { | |
return Math.round(value / precision) * precision; | |
} | |
// 評估當前解的品質(考慮精確度) | |
evaluateSolution(solution) { | |
// 檢查是否滿足所有養分需求 | |
if (!this.isValidSolution(solution)) { | |
return Infinity; | |
} | |
// 計算總成本(考慮精確度) | |
const cost = this.calculateTotalCost(solution); | |
return this.round(cost, this.precision.cost); | |
} | |
} | |
class DPSolver extends FertilizerSolver { | |
constructor(targetData, fertilizerMatrix, constraints) { | |
super(targetData, fertilizerMatrix, constraints); | |
console.log('初始化求解器(放大前):', { | |
targetData, | |
fertilizerMatrix, | |
constraints | |
}); | |
// debugger; // 中斷點:初始化(放大前) | |
// 放大目標數值 | |
this.scaledTargetData = {}; | |
Object.entries(this.targetData).forEach(([element, data]) => { | |
this.scaledTargetData[element] = { | |
min: Math.round(data.min * SCALE_FACTOR), | |
max: Math.round(data.max * SCALE_FACTOR), | |
target: Math.round(data.target * SCALE_FACTOR) | |
}; | |
}); | |
// 放大肥料矩陣 | |
this.scaledMatrix = {}; | |
Object.entries(this.constraints.matrix).forEach(([element, fertilizers]) => { | |
this.scaledMatrix[element] = {}; | |
Object.entries(fertilizers).forEach(([fertilizer, value]) => { | |
this.scaledMatrix[element][fertilizer] = Math.round(value * SCALE_FACTOR); | |
}); | |
}); | |
// 初始化狀態 | |
this.dpStates = new Map(); | |
this.currentState = { | |
nutrients: {}, // 當前養分含量 | |
cost: 0, // 當前成本 | |
solution: {} // 當前解 | |
}; | |
// 初始化當前養分含量(使用放大後的值) | |
this.constraints.elements.forEach(element => { | |
this.currentState.nutrients[element] = 0; | |
}); | |
console.log('初始化完成(放大後):', { | |
scaledTargetData: this.scaledTargetData, | |
scaledMatrix: this.scaledMatrix, | |
currentState: this.currentState | |
}); | |
// debugger; // 中斷點:初始化完成 | |
} | |
// 檢查是否達到目標(使用放大後的值) | |
isTargetReached(state) { | |
console.log('檢查目標達成狀態:', state); | |
// debugger; // 中斷點:目標檢查 | |
return this.constraints.elements.every(element => { | |
const target = this.scaledTargetData[element]; | |
const current = state.nutrients[element]; | |
const reached = current >= target.min && current <= target.max; | |
console.log(`元素 ${element}:`, { | |
current, | |
min: target.min, | |
max: target.max, | |
reached | |
}); | |
return reached; | |
}); | |
} | |
// 計算新狀態(使用放大後的值) | |
calculateNewState(currentState, fertilizer, amount) { | |
console.log('計算新狀態:', { | |
currentState, | |
fertilizer, | |
amount: amount / SCALE_FACTOR // 顯示實際值 | |
}); | |
// debugger; // 中斷點:狀態計算 | |
const newState = { | |
nutrients: {...currentState.nutrients}, | |
cost: currentState.cost, | |
solution: {...currentState.solution} | |
}; | |
// 使用放大後的矩陣計算 | |
this.constraints.elements.forEach(element => { | |
const contribution = this.scaledMatrix[element][fertilizer] * (amount / SCALE_FACTOR); | |
newState.nutrients[element] = Math.round(newState.nutrients[element] + contribution); | |
}); | |
// 更新成本(使用原始價格) | |
const fertilizerPrice = this.constraints.prices[fertilizer]; | |
newState.cost += fertilizerPrice * (amount / SCALE_FACTOR); | |
newState.solution[fertilizer] = (newState.solution[fertilizer] || 0) + (amount / SCALE_FACTOR); | |
return newState; | |
} | |
// 主要求解函數 | |
solve() { | |
console.log('開始求解過程'); | |
// debugger; // 中斷點:求解開始 | |
const initialKey = this.generateStateKey(this.currentState); | |
this.dpStates.set(initialKey, this.currentState); | |
let iteration = 0; | |
let improved = true; | |
while (improved && iteration < MAX_ITERATIONS) { | |
improved = false; | |
iteration++; | |
console.log(`開始第 ${iteration} 次迭代`); | |
// debugger; // 中斷點:迭代開始 | |
// 遍歷當前所有狀態 | |
for (const [stateKey, state] of this.dpStates) { | |
// 如果已達到目標且成本更低,更新最佳解 | |
if (this.isTargetReached(state) && state.cost < this.bestCost) { | |
console.log('找到更好的解:', { | |
oldCost: this.bestCost, | |
newCost: state.cost, | |
solution: state.solution | |
}); | |
// debugger; // 中斷點:找到更好的解 | |
this.bestCost = state.cost; | |
this.bestSolution = {...state.solution}; | |
improved = true; | |
continue; | |
} | |
// 嘗試添加每種肥料 | |
for (const fertilizer of this.constraints.fertilizers) { | |
const newState = this.calculateNewState(state, fertilizer, SCALED_STEP_SIZE); | |
const newKey = this.generateStateKey(newState); | |
if (!this.dpStates.has(newKey) || this.dpStates.get(newKey).cost > newState.cost) { | |
console.log('更新狀態:', { | |
fertilizer, | |
amount: SCALED_STEP_SIZE / SCALE_FACTOR, // 顯示實際值 | |
oldCost: this.dpStates.get(newKey)?.cost, | |
newCost: newState.cost | |
}); | |
// debugger; // 中斷點:狀態更新 | |
this.dpStates.set(newKey, newState); | |
improved = true; | |
} | |
} | |
} | |
console.log(`第 ${iteration} 次迭代結束`, { | |
improved, | |
statesCount: this.dpStates.size, | |
bestCost: this.bestCost | |
}); | |
} | |
if (!this.bestSolution) { | |
throw new Error('無法找到有效解'); | |
} | |
console.log('求解完成', { | |
iterations: iteration, | |
finalCost: this.bestCost, | |
solution: this.bestSolution | |
}); | |
// debugger; // 中斷點:求解完成 | |
return this.bestSolution; | |
} | |
// 生成狀態的唯一鍵值 | |
generateStateKey(state) { | |
console.log('生成狀態鍵值,當前狀態:', state); | |
// debugger; // 中斷點:生成鍵值 | |
// 使用養分含量生成鍵值(放大後的值) | |
const key = this.constraints.elements | |
.map(element => Math.round(state.nutrients[element])) // 確保是整數 | |
.join('|'); | |
console.log('生成的鍵值:', key); | |
return key; | |
} | |
} | |
</script> | |
</body> | |
</html> |