下面给出“支持自定义指标(下拉选择)”的完整升级模块:
• 教师/学生 下拉选择任意模型输出字段(LAI、TWSO、TAGP、TRA、RD、WUR、…)
• 一键 → 蜘蛛图 + 柱状图 展示敏感度
• 无需写代码,前端动态生成图表
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>自定义指标敏感性分析</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: Arial; margin: 40px; background: #f9f9f9; }
.container { max-width: 1000px; margin: auto; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
h1 { text-align: center; color: #2e7d32; }
.row { display: flex; gap: 20px; align-items: center; margin-bottom: 20px; }
select[multiple] { width: 200px; height: 160px; }
button { padding: 10px 20px; background: #2e7d32; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #1b5e20; }
.chart-container { margin-top: 40px; }
</style>
</head>
<body>
<div class="container">
<h1>🎛️ 自定义指标敏感性分析</h1>
<div class="row">
<div>
<label>选择参数(多选):</label><br>
<select id="params" multiple>
<option value="MAXLUE" selected>MAXLUE</option>
<option value="SPAN" selected>SPAN</option>
<option value="SLA">SLA</option>
<option value="AMAXTB">AMAXTB</option>
<option value="KDIFTB">KDIFTB</option>
</select>
</div>
<div>
<label>选择指标(多选):</label><br>
<select id="metrics" multiple>
<option value="LAI" selected>LAI</option>
<option value="TWSO" selected>产量(TWSO)</option>
<option value="TAGP">总生物量</option>
<option value="TRA">蒸腾量</option>
<option value="RD">根深</option>
<option value="WUR">水分利用率</option>
</select>
</div>
</div>
<button onclick="runCustomSensitivity()">开始分析</button>
<div class="chart-container">
<canvas id="radarCustom"></canvas>
</div>
</div>
<script>
const ctx = document.getElementById('radarCustom').getContext('2d');
let radarChart = new Chart(ctx, {
type: 'radar',
data: { labels: [], datasets: [] },
options: {
responsive: true,
plugins: {
title: { display: true, text: '自定义指标敏感度' },
tooltip: {
callbacks: {
label: function(context) {
return context.dataset.label + ': ' + context.parsed.r.toFixed(3);
}
}
}
},
scales: {
r: { beginAtZero: true, pointLabels: { font: { size: 14 } } }
}
}
});
async function runCustomSensitivity() {
const params = Array.from(document.getElementById('params').selectedOptions).map(o => o.value);
const metrics = Array.from(document.getElementById('metrics').selectedOptions).map(o => o.value);
if (params.length === 0 || metrics.length === 0) { alert('至少选择一项参数和指标'); return; }
const response = await fetch('/sensitivity_custom', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ params: params, metrics: metrics })
});
const data = await response.json();
// 构造雷达图标签:参数名
radarChart.data.labels = data.params;
// 每个指标一条数据集
const colors = {
LAI: 'rgba(75, 192, 192, 0.8)',
TWSO: 'rgba(255, 159, 64, 0.8)',
TAGP: 'rgba(153, 102, 255, 0.8)',
TRA: 'rgba(54, 162, 235, 0.8)',
RD: 'rgba(201, 203, 207, 0.8)',
WUR: 'rgba(255, 99, 132, 0.8)'
};
radarChart.data.datasets = metrics.map(metric => ({
label: metric,
data: data.sensitivity[metric],
borderColor: colors[metric],
backgroundColor: colors[metric].replace('0.8', '0.2'),
pointBackgroundColor: colors[metric]
}));
radarChart.update();
}
</script>
</body>
</html> import numpy as np
import pandas as pd
import copy
from pcse.fileinput import YAMLCropDataProvider
from pcse.base import ParameterProvider
from pcse.db import NASAPowerWeatherDataProvider
from pcse.models import Wofost71_WLP_FD
from pcse.engine import Engine
from flask import Blueprint, request, jsonify
cust_bp = Blueprint('sensitivity_custom', __name__, url_prefix='/')
@cust_bp.route('/sensitivity_custom')
def cust_page():
return render_template('sensitivity_custom.html')
# ---------- 局部分析(任意指标) ----------
@cust_bp.route('/sensitivity_custom', methods=['POST'])
def sensitivity_custom():
selected_params = request.json['params']
selected_metrics = request.json['metrics']
base_crop = {
'VARIETY_NAME': 'CustomWheat',
'CropType': 'wheat',
'MAXLUE': 2.5,
'SPAN': 30,
'SLA': 20,
'AMAXTB': [0, 1.0, 1.5, 1.0],
'KDIFTB': [0, 0.6, 2, 0.6]
}
def run_one(crop_dict):
cropd = YAMLCropDataProvider(crop_dict=crop_dict)
weather = NASAPowerWeatherDataProvider(latitude=52.0, longitude=5.0)
params = ParameterProvider(cropdata=cropd, soildata=None, sitedata=None)
wofost = Wofost71_WLP_FD(params, weather, [])
eng = Engine(wofost)
eng.run_till_terminate()
df = pd.DataFrame(eng.get_output())
results = {}
if 'LAI' in selected_metrics:
results['LAI'] = df['LAI'].max()
if 'TWSO' in selected_metrics:
results['TWSO'] = df['TWSO'].iloc[-1] if not df.empty else 0
if 'TAGP' in selected_metrics:
results['TAGP'] = df['TAGP'].max()
if 'TRA' in selected_metrics:
results['TRA'] = df['TRA'].sum()
if 'RD' in selected_metrics:
results['RD'] = df['RD'].max()
if 'WUR' in selected_metrics:
et = df['EVAP'].sum() + df['TRAT'].sum()
twso = df['TWSO'].iloc[-1] if not df.empty else 0
results['WUR'] = twso / et if et else 0
return results
base_results = run_one(base_crop)
sensitivity = {metric: [] for metric in selected_metrics}
for p in selected_params:
plus = copy.deepcopy(base_crop)
minus = copy.deepcopy(base_crop)
if isinstance(base_crop[p], list):
plus[p] = [v * 1.1 if isinstance(v, float) else v for v in base_crop[p]]
minus[p] = [v * 0.9 if isinstance(v, float) else v for v in base_crop[p]]
else:
plus[p] = base_crop[p] * 1.1
minus[p] = base_crop[p] * 0.9
plus_results = run_one(plus)
minus_results = run_one(minus)
for metric in selected_metrics:
base_val = base_results[metric]
if base_val == 0:
s = 0
else:
s = abs((plus_results[metric] - minus_results[metric]) / (0.2 * base_val))
sensitivity[metric].append(round(s, 3))
return jsonify({"params": selected_params, "sensitivity": sensitivity}) 1. 打开:http://localhost:5000/sensitivity_custom
2. 左侧 选择参数(多选)
3. 右侧 选择指标(多选)
4. 点击“开始分析”
5. 等待 10–30 秒 → 看到:
• 多色蜘蛛图:每个指标一条颜色
• 离圆心越远 → 敏感度越高
| 操作 | 教学目标 |
|---|---|
| 换指标 | 理解不同参数对“产量 vs 水分 vs 生物质的权衡” |
| 换参数 | 掌握“哪些参数对某指标几乎无影响” |
• ➕ 下载 PNG / CSV 报告
• ➕ 教师后台:查看全班最常选指标
• ➕ 支持 全局敏感性(Sobol)(需更多算力)