演示平台自定义指标(下拉选择)

2025-12-25 19:09
29

演示平台自定义指标(下拉选择)

下面给出“支持自定义指标(下拉选择)”的完整升级模块:

  • • 教师/学生 下拉选择任意模型输出字段(LAI、TWSO、TAGP、TRA、RD、WUR、…)

  • • 一键 → 蜘蛛图 + 柱状图 展示敏感度

  • • 无需写代码,前端动态生成图表


✅ 前端升级(templates/sensitivity_custom.html)

<!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>

✅ 后端升级(app.py 新增)

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. 1. 打开:http://localhost:5000/sensitivity_custom

  2. 2. 左侧 选择参数(多选)

  3. 3. 右侧 选择指标(多选)

  4. 4. 点击“开始分析”

  5. 5. 等待 10–30 秒 → 看到:

    • • 多色蜘蛛图:每个指标一条颜色

    • • 离圆心越远 → 敏感度越高



✅ 教学提示(可打印)

操作教学目标
换指标理解不同参数对“产量 vs 水分 vs 生物质的权衡”
换参数掌握“哪些参数对某指标几乎无影响”

✅ 后续可拓展

  • • ➕ 下载 PNG / CSV 报告

  • • ➕ 教师后台:查看全班最常选指标

  • • ➕ 支持 全局敏感性(Sobol)(需更多算力)