flask_inference_api_g / calculate.html
DmitrMakeev's picture
Update calculate.html
436a2c0 verified
raw
history blame
42.8 kB
<!DOCTYPE html>
<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>