| name | comp-code |
| description | 数学建模竞赛编程实现。根据建模报告编写代码、执行计算、收集结果。Use when user says "编程", "写代码", "code implementation". |
| argument-hint | ["modeling-report-or-topic"] |
| allowed-tools | Bash(*), Read, Write, Edit, Grep, Glob, Agent |
竞赛编程实现
根据建模报告编写代码并执行计算:$ARGUMENTS
常量
输入
-
MODELING_REPORT.md — 建模报告(必须存在)
-
PROBLEM_ANALYSIS.md — 赛题分析报告
-
TOPIC_PLAN.md — 选题规划(统计建模,含图表预规划)
-
user_data/ — 赛题附件数据
工作流程
Step 0: 恢复检查
检查 RESULTS.md、code/*.py、figures/*_results.json 是否已存在:
Step 1: 读取建模报告 + 建立实现清单 + 防错审查
从 MODELING_REPORT.md 提取每个子问题的求解算法、数学公式、输入输出要求、所需 Python 库。
⛔ 防错审查(必做): 读取 _references/error_prevention_code.md,根据 MODELING_REPORT.md 末尾标注的题型,对照对应章节的"必须验证"和"常见 Bug"条目。编码过程中逐项检查。
⛔ MANDATORY: 输出实现清单,后续逐项打勾:
IMPLEMENTATION CHECKLIST (from MODELING_REPORT.md):
[ ] 问题1: [算法名] — 输入: [xxx], 输出: [yyy], 库: [zzz]
[ ] 问题2: [算法名] — 输入: [xxx], 输出: [yyy], 库: [zzz]
[ ] 问题3: [算法名] — 输入: [xxx], 输出: [yyy], 库: [zzz]
[ ] 灵敏度分析: [参数列表]
每完成一个子问题,更新清单状态。
Step 1.1: ⛔ 建模-代码一致性契约(禁止偷偷简化)
核心问题:建模报告写了完整模型,但代码实现时偷偷简化(用更简单的方法/忽略约束/降维)。
⛔ 从 MODELING_REPORT.md 提取以下"契约",编码时必须逐条兑现:
⛔ 首先读取建模报告末尾的 5 项必备内容:
-
结果约束清单 → 用于 validate_constraints(),超出即代码有误
-
预期行为描述 → 用于判断结果"形状"是否合理(稳态/瞬态/单调性)
-
异常处理预案 → 遇到异常只能按预案操作,不得自行发明修正方法
-
方法唯一性声明 → 每个步骤只能用指定方法,禁止替代
-
验证检查点 → 代码跑完后逐项 pass/fail 检查
echo "=== 建模-代码一致性契约 ==="
echo "从 MODELING_REPORT.md 提取关键承诺:"
echo ""
echo "--- 算法承诺(建模报告说用什么算法,代码就必须用什么算法)---"
grep -i '算法\|方法\|求解\|使用.*法\|采用' MODELING_REPORT.md | head -20
echo ""
echo "--- 约束承诺(建模报告列的约束,代码必须全部实现)---"
grep -i '约束\|s\.t\.\|subject to\|≤\|≥\|不超过\|至少\|必须满足' MODELING_REPORT.md | head -20
echo ""
echo "--- 物理参数(建模报告定义的参数值,代码必须一致)---"
grep -oE '[A-Za-z_]+\s*=\s*[0-9.]+' MODELING_REPORT.md | head -20
echo ""
echo "--- 结果预期范围表(代码跑完后必须对照验证)---"
sed -n '/结果预期范围/,/^##/p' MODELING_REPORT.md | head -20
⛔ 一致性规则(硬性,不可变通):
-
算法不能降级:建模报告说"遗传算法",代码不能偷偷换成"贪心";说"SAT碰撞检测"不能换成"中心距判断"
-
约束不能遗漏:建模报告列了 N 个约束,代码必须实现 N 个(不能"为了简化"省掉几个)
-
参数不能篡改:建模报告定义 L=2.20m,代码里不能写 L=1.65(用中间量代替)
-
精度不能降低:建模报告说"自适应步长 rtol=1e-8",代码不能改成固定步长 dt=0.1
⛔ 如果编码时发现建模报告的方案确实无法实现(如算法太慢/库不支持),必须:
Step 1.5: 提取图表预规划
⛔ MANDATORY: 读取规划文档的图表预规划,了解下一步 paper-figure 需要生成哪些图表。
comp-code 不生成 PDF 图表,但需要确保输出的 JSON 数据能支撑这些图表。
echo "=== 图表预规划 ==="
for plan in TOPIC_PLAN.md PROBLEM_ANALYSIS.md MODELING_REPORT.md; do
[ -f "$plan" ] || continue
echo "--- $plan ---"
grep -i 'fig_\|图表\|TABLE_\|TikZ\|预规划\|figure' "$plan" | head -30
done
记录规划中的图表清单,确保每个图表对应的数据都会在分析过程中输出到 JSON。
⛔ 图表语言规则: 中文论文(统计建模/数模竞赛)的图表 axis label、legend、annotation 必须用中文。例如 ax.set_xlabel('迭代次数') 而不是 ax.set_xlabel('Iterations')。但这是 paper-figure 的事——comp-code 只需确保 JSON 数据的 key 名有意义即可。
Step 2: 环境准备
检查 Python,安装必要库(numpy, pandas, scipy, matplotlib, scikit-learn, statsmodels, networkx)。
Step 2.5: 数据读取验证(有附件数据时必做)
⛔ 写任何求解代码之前,先写一个独立的数据验证脚本,确认数据读取正确:
import pandas as pd
import os, glob
data_files = glob.glob('user_data/*.csv') + glob.glob('user_data/*.xlsx') + glob.glob('user_data/*.xls')
print(f"找到 {len(data_files)} 个数据文件")
for f in data_files:
print(f"\n=== {os.path.basename(f)} ===")
try:
if f.endswith('.csv'):
for enc in ['utf-8', 'gbk', 'gb2312', 'latin-1']:
try:
df = pd.read_csv(f, encoding=enc)
print(f" 编码: {enc}")
break
except UnicodeDecodeError:
continue
else:
df = pd.read_excel(f)
print(f" 形状: {df.shape}")
print(f" 列名: {list(df.columns)}")
print(f" 数据类型:\n{df.dtypes}")
print(f" 缺失值:\n{df.isnull().sum()[df.isnull().() > ]}")
()
num_cols = df.select_dtypes(include=).columns
(num_cols) > :
()
col num_cols:
df[col].() < col col col:
()
df[col].isnull().() > (df) * :
()
Exception e:
()
执行 data_check.py 后,确认以下几点再继续:
-
所有数据文件都能正确读取(编码、分隔符无误)
-
列名和题目描述一致(不是乱码或错位)
-
数据规模和题目描述一致(行数、列数)
-
缺失值和异常值已识别,后续代码中有处理方案
Step 3: 代码目录结构
code/
main.py # 主程序(串联所有子问题)
problem1.py # 子问题 1
problem2.py # 子问题 2
utils.py # 公共工具
requirements.txt
Step 4: 逐子问题编写和执行
必须按顺序逐问求解:编写 -> 执行 -> 验证 -> 下一问。
⛔ Step 0.5: 上游一致性检查(开始编码前必做):
echo "=== 上游一致性检查 ==="
[ -f MODELING_REPORT.md ] && echo "✅ MODELING_REPORT.md 存在" || { echo "❌ MODELING_REPORT.md 不存在!"; exit 1; }
PROB_COUNT=$(grep -c '问题[一二三四五六七八九十0-9]' PROBLEM_ANALYSIS.md 2>/dev/null || echo 0)
MODEL_COUNT=$(grep -c '问题[一二三四五六七八九十0-9]' MODELING_REPORT.md 2>/dev/null || echo 0)
echo "赛题分析子问题数: $PROB_COUNT, 建模报告子问题数: $MODEL_COUNT"
[ "$MODEL_COUNT" -lt "$PROB_COUNT" ] && echo "⚠ 建模报告覆盖的子问题数少于赛题分析,请检查是否遗漏"
echo "--- 建模报告推荐方法 ---"
grep -i '算法\|方法\|模型.*选择\|求解.*策略' MODELING_REPORT.md 2>/dev/null | head -10
echo "--- 编程实现时必须使用上述方法,或明确说明替代理由 ---"
代码性能要求:
自主判断数据来源:
每个子问题:
-
编写独立 Python 文件
-
执行并检查输出
-
验证结果合理性
-
保存结果到 figures/problem_N_results.json
-
结果异常则修改代码重跑
⛔ 优化类赛题求解策略(调度/选址/路径/分配/规划类必做):
如果当前子问题是优化类(有明确的目标函数需要最大化或最小化),按以下分层策略求解:
层级 1:建立可行解(必做)
先确保能找到一个满足所有约束的可行解,不追求最优。
def solve_problem():
constraints = extract_constraints()
solution = greedy_initial_solution()
for c in constraints:
assert c.check(solution), f"约束违反: {c.name}"
baseline_obj = objective(solution)
print(f"基准可行解: obj={baseline_obj}")
return solution, baseline_obj
约束强制验证(每次求解后必做):
echo "=== 约束验证 ==="
grep -i '约束\|限制\|不超过\|不少于\|上限\|下限\|容量\|预算\|≤\|≥\|<=\|>=' PROBLEM_ANALYSIS.md 2>/dev/null | head -20
echo "--- 逐条核对上述约束是否满足 ---"
读取代码输出的结果,逐条对照题目约束。任何一条不满足 → 必须修改代码重跑,不能跳过。
层级 2:选择求解路线(根据问题规模判断)
| 问题特征 | 推荐路线 | 求解器 |
|---------|---------|-------|
| 线性目标 + 线性约束 | 精确求解 | scipy.optimize.linprog → PuLP → OR-Tools |
| 整数/0-1 变量 | 精确求解(小规模)或启发式(大规模) | PuLP(CBC) → OR-Tools(SCIP) |
| 非线性目标或约束 | 启发式 + 局部搜索 | scipy.optimize.minimize → 遗传算法 → 模拟退火 |
| 组合优化(TSP/VRP/排程) | 启发式 + 改进 | 贪心构造 → 2-opt/3-opt → 遗传/蚁群 |
| 多目标优化 | Pareto 前沿 | NSGA-II (pymoo/deap) |
精确求解路线(能用就用,结果是全局最优):
from scipy.optimize import linprog
import pulp
prob = pulp.LpProblem("name", pulp.LpMinimize)
如果精确求解器返回 Optimal,不需要多轮调优——这已经是全局最优。直接进入层级 4。
启发式求解路线(精确求解不可行时用):
启发式不保证全局最优,必须通过自动调参逼近最优解。不要手动写死参数——用代码自动搜索最优参数组合。
策略:先快速试探,再自动调参,最后多算法对比
第一步:快速试探(30 秒内)
用小参数快速跑一遍,确认代码正确、约束满足、目标函数有值。这一步的目的是验证可行性,不追求最优。
第二步:自动参数搜索(核心,用代码实现)
根据问题规模和单次求解耗时,自动决定搜索策略:
import time, itertools
def auto_tune(solve_func, param_grid, time_budget=None):
"""
自动参数搜索框架。
solve_func(params) -> (objective, feasible, solution)
param_grid: dict, 如 {'pop_size': [50, 100, 200], 'n_gen': [200, 500, 1000], 'cx_rate': [0.7, 0.8, 0.9]}
time_budget: 总时间预算(秒),由 Claude 根据问题规模自行决定
"""
first_params = {k: v[0] for k, v in param_grid.items()}
t0 = time.time()
obj, feasible, sol = solve_func(first_params)
single_time = time.time() - t0
if time_budget is None:
total_combos = 1
for v in param_grid.values():
total_combos *= len(v)
time_budget = max(single_time * total_combos * 1.5, single_time * 20, 120)
print(f"自动设定 time_budget={time_budget:.0f}s")
total_combos = 1
for v in param_grid.values():
total_combos *= len(v)
if single_time * total_combos < time_budget:
strategy = "grid_search"
elif single_time * len(param_grid) * 3 < time_budget:
strategy = "coordinate_search"
:
n_samples = (, (time_budget / single_time / ))
strategy =
()
best_obj, best_params, best_sol = obj, first_params, sol
results_log = [{: first_params, : obj, : feasible}]
start_time = time.time()
():
time.time() - start_time < time_budget
strategy == :
keys = (param_grid.keys())
combo itertools.product(*param_grid.values()):
_time_ok():
()
params = ((keys, combo))
params == first_params:
obj, feasible, sol = solve_func(params)
results_log.append({: params, : obj, : feasible})
feasible obj < best_obj:
best_obj, best_params, best_sol = obj, params, sol
strategy == :
current_best = (first_params)
key param_grid:
_time_ok():
val param_grid[key]:
_time_ok():
params = (current_best)
params[key] = val
obj, feasible, sol = solve_func(params)
results_log.append({: params, : obj, : feasible})
feasible obj < best_obj:
best_obj, best_params, best_sol = obj, params, sol
current_best = (params)
:
random
n = (strategy.split()[-])
_ (n):
_time_ok():
()
params = {k: random.choice(v) k, v param_grid.items()}
obj, feasible, sol = solve_func(params)
results_log.append({: params, : obj, : feasible})
feasible obj < best_obj:
best_obj, best_params, best_sol = obj, params, sol
best_obj, best_params, best_sol, results_log
Claude 根据具体问题自行设计 param_grid,参考:
-
遗传算法:{'pop_size': [50, 100, 200, 500], 'n_gen': [200, 500, 1000], 'cx_rate': [0.7, 0.8, 0.9], 'mut_rate': [0.05, 0.1, 0.2]}
-
模拟退火:{'T0': [500, 1000, 5000], 'alpha': [0.99, 0.995, 0.999], 'n_restarts': [1, 3, 5]}
-
粒子群:{'n_particles': [30, 50, 100], 'n_iter': [200, 500], 'w': [0.5, 0.7, 0.9]}
⛔ 时间预算规则: Claude 根据问题规模和单次求解耗时自行决定 time_budget。外部工作流引擎有总超时保护(步骤级 15 分钟无输出超时 + 总计 4 小时硬限制),不会卡死。Claude 应尽可能充分搜索,在收敛后自行停止。
第三步:多算法对比(可选但推荐)
用最优参数跑完主算法后,再用另一种算法跑一次对比:
⛔ 关键原则:让代码自动决定,不要手动写死参数。 Claude 的任务是写好 solve_func 和 param_grid,搜索框架自动找最优配置。
层级 3:结果改进(启发式专用)
对启发式得到的解做局部搜索改进:
def local_search(solution, max_iter=1000, time_limit=None):
"""对启发式解做局部搜索改进。time_limit 秒后自动停止,不设则由 Claude 自行决定。"""
import time
if time_limit is None:
time_limit = float('inf')
best = solution.copy()
best_obj = objective(best)
t0 = time.time()
improved = 0
for i in range(max_iter):
if time.time() - t0 > time_limit:
print(f"局部搜索时间到 ({time_limit}s),已改进 {improved} 次")
break
neighbor = generate_neighbor(best)
if all(c.check(neighbor) for c in constraints):
obj = objective(neighbor)
if obj < best_obj:
best, best_obj = neighbor, obj
improved += 1
print(f"局部搜索完成: {improved} 次改进, 目标函数 {best_obj}")
return best, best_obj
层级 4:记录求解过程(所有优化类必做)
{
"solve_method": "GA + 2-opt local search",
"solve_route": "heuristic",
"rounds": [
{"round": 1, "params": "pop=50, gen=200", "objective": 12345, "feasible": true, "time_sec": 30},
{"round": 2, "params": "pop=200, gen=1000", "objective": 11890, "feasible": true, "time_sec": 120},
{
这些数据供论文使用:
⛔ 非优化类赛题不需要多轮循环——预测类、评价类、统计类跑一次验证通过即可。
Step 5: 编写主程序
code/main.py 串联所有子问题,汇总结果到 figures/all_results.json。
Step 5.5: 模型检验(根据题型自主判断)
根据题目类型,选择合适的模型检验方式。不是所有题都需要灵敏度分析 — 自己判断:
-
优化类(调度/选址/路径)→ 灵敏度分析:关键参数 ±20% 对目标函数的影响
-
预测类(时间序列/回归)→ 交叉验证 + 残差分析 + 多模型对比
-
评价类(TOPSIS/AHP/熵权法)→ 权重稳定性分析:微调权重看排名是否变化
-
图论/网络类 → 参数灵敏度(边权/容量变化对最优解的影响)
-
统计/实证类 → 稳健性检验(替换变量、子样本、工具变量)
如果判断需要灵敏度分析,执行以下步骤:
Read MODELING_REPORT.md for the sensitivity analysis plan. For each key parameter identified:
-
Write code/sensitivity_analysis.py that varies the parameter across a range (e.g., ±20% in 10 steps)
-
For each parameter value, re-run the model and record the objective function value
-
Save results to figures/sensitivity_results.json:
{
"parameter_name": {"values": [...], "objective": [...]},
"parameter_name2": {"values": [...], "objective": [...]}
}
- Execute the script and verify results are reasonable
This data is required by paper-figure to generate tornado charts and sensitivity curves, and by comp-paper-zh for the 灵敏度分析 chapter.
Step 6: 结果验证 + 实现清单对照
⛔ MANDATORY: 智能自检(代码跑完后,你必须逐项审查):
⛔ 第一步:自动化 sanity check(先跑脚本,不依赖人工判断):
import json, os, sys, re
results = {}
for f in sorted(os.listdir('figures')):
if f.endswith('_results.json') or f == 'all_results.json':
with open(f'figures/{f}', 'r') as fh:
results[f] = json.load(fh)
errors = []
warnings = []
suspicious = []
def check_value(name, val, context=""):
"""通用数值检查"""
if val is None:
errors.append(f"❌ {name} 为 None")
elif isinstance(val, float):
import math
if math.isnan(val):
errors.append(f"❌ {name} 为 NaN")
elif math.isinf(val):
errors.append(f"❌ {name} 为 Inf")
elif abs(val) > 1e15:
warnings.append(f"⚠ {name} = {val},数值异常大")
def check_unrealistic(name, val):
"""⛔ 标记可能不合理的数值(不阻断,只提醒 AI 在 9 问审查中确认)"""
(val, (, )) (val, ):
key = name.lower()
(w key w [, , , , , , , , ]):
val > :
suspicious.append()
val < :
errors.append()
val < (w key w [, , ]):
errors.append()
(w key w [, , , , ]):
val == :
suspicious.append()
val < :
errors.append()
(w key w [, , , , ]):
val > :
suspicious.append()
key key key:
val == :
suspicious.append()
val > :
errors.append()
():
(obj, ):
k, v obj.items():
walk_check(v, )
(obj, ):
i, v (obj):
walk_check(v, )
(obj, (, )):
check_value(path, obj)
check_unrealistic(path, obj)
fname, data results.items():
walk_check(data, fname)
e errors:
(e)
w warnings:
(w)
s suspicious:
(s)
errors warnings suspicious:
()
errors:
()
sys.exit()
suspicious:
()
()
()
()
()
()
:
()
⛔ 第 1.5 步:问题递进性验证(用实际数值检查):
代码跑完所有子问题后,对比各问题的目标函数值,检查递进性:
echo "=== 问题递进性验证 ==="
echo "对比各子问题的核心结果,检查是否符合递进逻辑:"
python3 -c "
import json, os
results = {}
for f in sorted(os.listdir('figures')):
if f.startswith('problem_') and f.endswith('_results.json'):
with open(f'figures/{f}', 'r') as fh:
results[f] = json.load(fh)
# 提取核心指标(目标函数值、最优解等)
data = results[f]
key_vals = []
for k, v in (data.items() if isinstance(data, dict) else []):
if any(kw in k.lower() for kw in ['objective', 'optimal', 'best', 'total', 'cost', 'time', 'profit', 'makespan']):
key_vals.append(f'{k}={v}')
print(f'{f}: {\" | \".join(key_vals) if key_vals else \"(未找到核心指标)\"}')
print()
print('⛔ 人工检查:')
print(' 1. 后续问题的结果是否比前一个有明显变化?')
print(' 2. 新增的变量/资源是否对目标函数有边际效益?')
print(' 3. 如果某个问题的结果和前一个几乎相同 → 假设可能有问题')
" 2>/dev/null
⛔ 第 1.6 步:题目约束对照(检查结果是否违反题目给定约束):
题目通常给具体数值约束(预算/时间/容量/数量等),跑完代码后要回到赛题验证:
echo "=== 题目约束对照 ==="
echo "--- 从赛题中提取数值约束 ---"
for src in PROBLEM_ANALYSIS.md user_data/*_extracted.txt; do
[ -f "$src" ] || continue
echo "来源: $src"
grep -oE '(不超过|最多|至少|不低于|大于|小于|≤|≥|<|>|=)[^。,,.\n]{0,40}[0-9]+[^。,,.\n]*' "$src" 2>/dev/null | head -20
echo ""
done
echo "--- 人工对照清单 ---"
echo "请逐条核对:题目提到的每个数值约束(预算、时间、容量、数量等),"
echo "我的结果是否都满足?如果违反约束(比如超预算、超时间),必须回去"
echo "重新求解,不要硬凑数字让结果看起来满足约束。"
如果发现约束违反:
⛔ 第 1.63 步:自动化约束验证(强制执行,不可跳过)
在每个子问题的代码末尾,必须加入以下约束验证代码块。这不是可选的"人工检查",而是代码的一部分:
def validate_constraints(results: dict, constraints: dict) -> bool:
"""
results: 计算结果字典,如 {'gap': 0.081, 'velocity': -3.5}
constraints: 约束字典,如 {'gap': (0, 0.06, '悬浮间隙'), 'velocity': (-10, 10, '速度')}
格式: {key: (min_val, max_val, description)}
返回: True=全部通过, False=有违反
"""
violations = []
for key, (lo, hi, desc) in constraints.items():
val = results.get(key)
if val is None:
continue
import numpy as np
vals = np.atleast_1d(val)
for v in vals:
if v < lo or v > hi:
violations.append(f"❌ {desc}: {v} 超出范围 [{lo}, {hi}]")
if violations:
print("\n" + "="*60)
print("⛔⛔⛔ 约束验证失败 — 必须修正后才能继续")
print("="*60)
for v in violations:
print(f" {v}")
print("\n修正要求:")
print(" 1. 分析超出原因(数据漂移/模型缺约束/边界效应)")
()
()
()
(* + )
:
()
⛔ 强制规则:
-
每个子问题的代码文件末尾必须有 validate_constraints() 调用
-
约束字典必须从 PROBLEM_ANALYSIS.md 和 MODELING_REPORT.md 的"结果预期范围表"中提取
-
如果验证失败(返回 False),禁止保存 JSON、禁止继续下一个子问题
-
必须修改代码加入物理约束后重跑,直到验证通过
⛔ AI 常见逃避行为(全部禁止):
-
❌ "数学上正确所以不需要约束" → 错!物理约束 > 数学正确性
-
❌ "这是题目数据的特性" → 错!数据有问题就修正数据处理方式
-
❌ "纯数学ODE求解的结果" → 错!ODE 也必须加物理约束(接触、饱和、边界)
-
❌ 删除或注释掉 validate_constraints 代码 → 严重违规
-
❌ 把约束范围改大让结果"通过" → 篡改约束,严重违规
⛔ 第 1.65 步:物理合理性原则(计算正确 ≠ 结果合理)
核心原则:物理/业务约束 > 数据忠实度 > 计算正确性
代码没有 bug、数据是题目给的 ≠ 结果就是对的。每个子问题跑完后必须问自己:
"我的结果在题目描述的物理世界中是否可能发生?"
⛔ 触发条件(同时满足才修正,防止误杀):
-
计算结果超出了题目明确给定的物理边界(白纸黑字写的,不是你猜的)
-
超出幅度显著(不是边界附近的微小浮动,而是明显不可能)
-
你能解释为什么会超出(有具体的物理/数学原因)
⛔ 不触发的情况(保持原始结果):
⛔ 按题型的红旗信号(任何一条触发都必须分析原因):
| 题型 | 红旗信号 | 可能原因 |
|------|---------|---------|
| 物理/工程 | 结果超出题目给定的物理极限 | 数据漂移/截断/模型缺约束 |
| 优化 | 最优解违反约束条件 | 约束未正确加入求解器 |
| 预测 | 预测值超出历史数据范围 5 倍以上 | 模型外推发散/趋势项过强 |
| 预测 | 远期预测单调发散(不收敛不震荡) | 模型不稳定/缺阻尼项 |
| 评价 | 权重之和 ≠ 1(误差 > 0.01) | 归一化步骤遗漏 |
| 评价 | 所有方案得分差异 < 1%(无区分度) | 指标选择不当/权重过于均匀 |
| 评价 | 排名与题目暗示的常识严重矛盾 | 正负向指标处理反了 |
| 图论 | 路径长度/成本为负 | 负权边未处理/算法不适用 |
| 图论 | 流量不守恒(流入≠流出) | 模型遗漏节点或边 |
| 统计 | 回归系数方向与所有文献相反 | 变量编码错误/共线性 |
| 动力学 | 状态变量单调增长不收敛 | 开环积分漂移/缺反馈 |
⛔ 修正流程(触发后):
-
记录原始结果(不删除,论文中需要对比说明)
-
分析超出原因:数据截断?测量误差?净偏差累积?边界效应?模型假设不完整?
-
选择最小修正方案(去漂移/加约束/补偿项),不要大改模型
-
重新计算,验证修正后结果在约束内
-
在 RESULTS.md 中写明:原始结果 → 为什么不合理 → 修正方法 → 修正后结果
-
⛔ JSON 里只保存修正后的结果(原始结果写在 RESULTS.md 的说明文字里,不写进 JSON)
⛔ 绝对禁止的行为:发现结果超出物理约束后"解释原因"但继续使用超出的结果。
解释原因 ≠ 处理完毕。发现超出 → 必须修正 → 用修正后的值。
不能把 0.081m 写进 JSON 然后在旁边注释"这是因为净冲量不平衡"——
必须去漂移/加约束后得到合理值(如 0.049m),把合理值写进 JSON。
⛔ 对照 MODELING_REPORT.md 的"结果预期范围表":
如果建模步骤给出了预期范围表,逐条对照。超出范围的结果必须走上面的修正流程。
⛔ 禁止的做法:
⛔ 第 1.7 步:结合题目背景的全面合理性审查(最关键):
前面的 sanity check 只能抓固定模式。真正的合理性必须结合赛题实际背景来判断——只有你自己(作为 AI)结合常识和题目语境才能发现所有问题。
强制执行以下审查流程:
echo "=== 结合题目背景的合理性审查 ==="
echo "--- 赛题背景 ---"
for src in PROBLEM_ANALYSIS.md; do
[ -f "$src" ] && cat "$src" | head -200
done
echo "--- 建模报告(对照模型设计是否与结果一致)---"
[ -f MODELING_REPORT.md ] && cat MODELING_REPORT.md | head -200
echo "--- 用户上传的赛题附件 ---"
for src in user_data/*_extracted.txt; do
[ -f "$src" ] && echo "来源: $src" && head -100 "$src"
done
echo "--- 所有结果数值 ---"
python3 -c "
import json, os
for f in sorted(os.listdir('figures')):
if not (f.endswith('_results.json') or f == 'all_results.json'): continue
try:
data = json.load(open(f'figures/{f}', 'r', encoding='utf-8'))
print(f'=== {f} ===')
print(json.dumps(data, ensure_ascii=False, indent=2)[:3000])
except: pass
" 2>/dev/null
然后你必须逐条回答以下问题(不能跳过,必须写在 RESULTS.md 末尾的"合理性审查"章节):
⛔ 审查时必须同时对照三个来源:
-
赛题原文(PROBLEM_ANALYSIS.md / user_data/*_extracted.txt)— 题目给的约束和背景
-
建模报告(MODELING_REPORT.md)— 之前设计的模型和预期行为
-
实际结果(figures/all_results.json)— 代码跑出来的数字
如果结果与建模报告的预期不一致(比如建模报告说"用遗传算法求解,预期收敛到全局最优",但实际结果明显不是最优),要么代码实现有 bug,要么模型设计有问题——必须定位原因。
=== 合理性审查(结合题目背景)===
Q1. [数值量级] 每个结果的数值量级是否符合题目实际?
举例:
- 一个城市的年用电量应该是几亿度(10^8-10^9),不是几千度
- 一辆货车的载重应该是几吨到几十吨,不是几克或几千吨
- 一个班级的学生数应该是 30-50 人,不是 3 人或 500 人
- 一天的时间应该是 24 小时内,不是 200 小时
逐条列出每个关键结果的数值,判断量级是否合理。
Q2. [符号方向] 结果的正负/大小方向是否符合直觉?
举例:
- 多给的资源应该让目标函数改善,不是变差
- 距离越近应该运输成本越低,不是越高
- 产品价格上升应该让需求下降(正常商品)
- 投入更多努力应该让产出增加(正相关关系)
逐条检查你的结果方向。
Q3. [约束边界] 结果是否在题目给定的物理/业务边界内?
举例:
- 概率、比例、百分比必须在 [0, 1] 范围内
- 数量、价格、质量、长度等物理量不能为负
- 时间不能倒流
- 几何量(角度、距离)不能超出合理范围
逐条对照题目给的边界条件。
⛔⛔⛔ Q3 判定规则(硬性,不可变通):
- 结果在边界内 → ✅
- 结果超出边界 → ❌(无论你是否"已讨论"、"已解释原因")
- "已在报告中讨论" ≠ ✅,仍然是 ❌
- "数学解超出物理范围" = ❌,不是 ✅
- "纯数学ODE求解结果" = ❌,不是 ✅
- 只要有任何一个结果超出题目明确给定的边界 → 整个 Q3 = ❌
- Q3 = ❌ 时必须:修改代码加入物理约束 → 重跑 → 直到结果在边界内
- ⛔ 禁止:标 ❌ 后写"但这是合理的因为XXX"然后继续 → 这不是通过
Q4. [结果分布] 多个结果之间的比例/差异是否合理?
举例:
- 同类物品的价格差异一般不会超过 10 倍
- 同类设备的效率差异一般不会超过 3 倍
- 不同方法在同一数据集上的精度差异一般 < 30%
- 若相邻问题的结果差 1000 倍,大概率有 bug
Q5. [与赛题相同的场景数据] 如果赛题给了数据样本,我的结果是否与之量级一致?
举例:
- 赛题给了历史销量数据在 100-1000 区间,我的预测不应跳到 10000
- 赛题给了设备产能 50 吨/天,我规划的生产不应超过这个值
逐条对比题目给的样本数据和我的结果。
Q6. [物理/业务常识] 是否违反了常识性的物理/业务规律?
举例:
- 物理:能量守恒、动量守恒、质量守恒
- 经济:帕累托改进、边际递减
- 统计:大数定律、中心极限
- 其他:本题涉及领域的基本定律
列出本题涉及的关键定律,逐条检查结果是否违反。
Q7. [子问题递进] 后续子问题的结果相对前面有明显变化?
- 加约束了应该变差
- 加资源了应该变好
- 更换策略应该有方向性影响
如果结果几乎不变,说明模型对新条件不敏感,大概率有 bug。
Q8. [灵敏度合理] 灵敏度分析显示的敏感性是否合理?
- 关键参数应该敏感
- 无关参数应该不敏感
- 如果所有参数都敏感/都不敏感,模型可能有问题
Q9. [与建模报告一致] 代码实现是否忠实于 MODELING_REPORT.md?(⛔ 最关键的一条)
**自动化对照(必须执行):**
```bash
echo "=== Q9 建模-代码一致性验证 ==="
echo ""
echo "--- 算法对照 ---"
echo "建模报告承诺的算法:"
grep -i '算法\|方法\|求解.*法\|采用.*法' MODELING_REPORT.md | head -10
echo ""
echo "代码中实际使用的算法/库:"
grep -rh 'from\|import\|minimize\|solve_ivp\|linprog\|genetic\|simulated' code/*.py 2>/dev/null | sort -u | head -15
echo ""
echo "--- 约束对照 ---"
echo "建模报告列出的约束数量:"
grep -c '≤\|≥\|约束\|s\.t\.' MODELING_REPORT.md
echo "代码中实现的约束数量:"
grep -rch 'constraint\|bounds\|<=\|>=' code/*.py 2>/dev/null | paste -sd+ | bc 2>/dev/null || echo "(手动检查)"
echo ""
echo "--- 参数对照 ---"
echo "建模报告定义的关键参数:"
grep -oE '[A-Za-z_]+\s*[=::]\s*[0-9]+\.?[0-9]*' MODELING_REPORT.md | head -10
echo "代码中的对应参数值:"
grep -rh '^[A-Z_]*\s*=' code/*.py 2>/dev/null | head -10
逐条对照:
-
建模报告说用 XX 算法 → 代码里确实用了 XX 算法?(不是偷换成更简单的方法)
-
建模报告列了 N 个约束 → 代码里实现了 N 个?(不能省掉"不重要的"约束)
-
建模报告定义的参数值 → 代码里的值完全一致?(不能用中间量代替)
-
建模报告预期目标函数值在 [a, b] 范围 → 实际结果在这个范围内?
-
建模报告的结果预期范围表 → 代码结果全部在范围内?
⛔ Q9 判定规则:
**对每个问题必须明确回答 ✅(通过)、⚠️(需说明)、❌(有问题)。**
**⛔⛔⛔ 判定规则(不可变通):**
- ✅ = 结果完全在约束/合理范围内,无需任何解释
- ⚠️ = 结果在边界附近但未超出,需要说明为什么是合理的极端工况
- ❌ = 结果超出了题目明确给定的约束/边界
**⛔ 禁止的"假通过"行为:**
- ❌ 超出边界但标 ✅ 并写"已在报告中讨论" → 这是 ❌ 不是 ✅
- ❌ 超出边界但标 ✅ 并写"数学解超出物理范围" → 这是 ❌ 不是 ✅
- ❌ 超出边界但标 ⚠️ 并写"物理上可以解释" → 超出就是 ❌,解释不能改变判定
- ❌ 标了 ❌ 但不修改代码,只在文字中"讨论" → 必须改代码重跑
如果任何一项打 ❌:
- **必须修改代码重新跑**,直到结果通过这 9 个问题的审查
- 不允许靠"写作技巧"掩盖结果不合理——用户/评委一眼就能看出
- **特别是 Q3(约束边界):超出 = 代码必须加物理约束后重跑,没有例外**
- 如果修改后某项仍然违反常识(如 R²=1.0 的物理仿真),必须在 RESULTS.md 中**明确说明原因**(如"本题是确定性物理仿真,训练即测试,故 R²=1 正常")
**第二步:人工判断自检(结合题目背景):**
读取 PROBLEM_ANALYSIS.md(或 TOPIC_PLAN.md)和 RESULTS.md,**结合本题的具体背景**逐项自检。以下是参考检查项,不是死规则 — 你需要根据题目实际情况判断每项是否适用、阈值是否合理。如果某项不适用于本题,标注"不适用"并说明原因。
**通用检查(所有题型必做):**
=== 通用自检 ===
-
[题目覆盖] 题目要求解决 N 个子问题,我是否全部给出了数值结果?缺了哪个?
-
[数值合理] 结合题目背景,结果数值是否符合现实常识?
-
[量纲一致] 同一个量在不同子问题中单位是否统一?
-
[NaN/Inf] JSON 结果中是否有 NaN、Inf、null?
-
[数据传递] 问题 N 的输入是否正确引用了问题 N-1 的输出?
-
[灵敏度] 是否做了灵敏度分析?关键参数变化时结果变化是否合理?
**根据题目类型,选做对应检查项(阈值仅供参考,结合题目实际判断):**
=== 预测类 ===
P1. [过拟合] 训练集 vs 测试集精度差距是否异常?(一般 <10%,但物理仿真等确定性问题 R²>0.99 可能正常)
P2. [预测范围] 预测值是否在合理范围内?(结合领域知识判断,不是简单看历史极值)
P3. [多步衰减] 远期预测精度是否合理下降?
P4. [残差检验] 残差是否有明显的模式(自相关/异方差)?
P5. [验证方式] 验证方式是否合理?(时间序列不能随机 split,应该用滚动验证)
P6. [拟合太差] ⛔ R² < 0.5 的曲线拟合/回归模型绝对不能直接用!必须诊断:
- 模型形式是否正确?(是否遗漏基线漂移、非线性项、相位偏移)
- 数据预处理是否正确?(去趋势、归一化、截取有效区间)
- 初始参数是否合理?(非线性拟合对初值敏感)
物理/工程拟合 R² 应 > 0.8,社科截面回归 R² 在 0.2-0.6 可接受
=== 优化类 ===
O1. [约束满足] 最优解是否满足题目所有约束?
O2. [变量类型] 整数/0-1 变量是否满足类型要求?(注意:某些松弛方法允许连续值)
O3. [收敛性] 优化算法是否收敛?
O4. [优于基准] 最优解是否优于简单基准方法?
O5. [解的稳定性] 不同随机种子的结果是否一致?
=== 评价/决策类 ===
E1. [权重归一] 权重之和是否等于 1?
E2. [一致性] AHP 的 CR < 0.1?(如果用了 AHP)
E3. [排名稳定] 权重微调后排名是否稳定?
E4. [指标方向] 正向/负向指标是否正确处理?
=== 统计/实证类 ===
S1. [共线性] 是否检查了 VIF?
S2. [内生性] 是否考虑了内生性问题?
S3. [稳健性] 是否做了稳健性检验?
S4. [显著性] p 值分布是否合理?(全部极显著需要警惕)
S5. [样本量] 样本量是否支撑所用的统计方法?
=== 图论/网络类 ===
G1. [连通性] 图的连通性是否正确处理?
G2. [算法适用] 所用算法是否适合图的特征?(如负权边不能用 Dijkstra)
G3. [守恒性] 网络流的流量守恒是否满足?
**⛔ 自检输出格式:** 对每项回答 ✅ / ❌ / 不适用,如果 ❌ 则说明原因并修改代码重跑。
**常见编程 Bug 排查(跑完代码后快速扫一遍):**
=== 求解器/算法常见坑 ===
B1. [最大化vs最小化] scipy.optimize.minimize 是最小化器 — 如果要最大化,目标函数是否取了负号?
B2. [初始值敏感] 优化结果是否依赖初始值?换一组初始值结果是否一致?(局部最优陷阱)
B3. [约束写反] 不等式约束方向是否正确?scipy 的 'ineq' 约束要求 f(x) >= 0
B4. [整数松弛] 用连续优化器解整数规划后,是否做了取整?取整后是否仍然可行?
B5. [索引越界] 数组索引是否从 0 开始?矩阵维度是否匹配?
B6. [数据泄露] 测试集的数据是否参与了训练/归一化?(StandardScaler 必须只 fit 训练集)
B7. [随机种子] 是否设了 random seed?不同运行结果是否可复现?
=== 数据处理常见坑 ===
D1. [缺失值] 是否处理了 NaN/空值?pandas 的 mean() 默认跳过 NaN 但 numpy 不会
D2. [类型错误] 字符串列是否被当成数值参与了计算?(pandas 读 CSV 可能把数字列读成 str)
D3. [归一化时机] 归一化是在 train/test split 之前还是之后?(应该在之后,只用训练集的统计量)
D4. [时间序列顺序] 时间序列数据是否按时间排序?是否有重复时间戳?
D5. [编码问题] 中文 CSV 是否用了正确的编码读取?(gbk/utf-8/gb2312)
=== 模型选择常见坑 ===
M1. [线性假设] 用线性回归拟合明显非线性的数据?(看残差图是否有弯曲模式)
M2. [样本不平衡] 分类问题中各类样本量是否严重不平衡?是否用了 class_weight 或过采样?
M3. [特征缩放] SVM/KNN/神经网络等距离敏感模型是否做了特征缩放?
M4. [时序交叉验证] 时间序列是否用了普通 k-fold?(应该用 TimeSeriesSplit,不能未来数据预测过去)
M5. [多重比较] 做了多次假设检验是否做了 Bonferroni 校正?
遇到以上任何问题,修改代码重跑。
**⛔⛔⛔ 编码阶段强制修复原则(不可跳过):**
**检测到问题 = 必须修复。解释原因 ≠ 处理完毕。本步骤不允许带着已知问题输出结果。**
无论是 Step 1.65 的物理合理性检查、Step 1.7 的背景审查、还是上面的自检项,只要发现 ❌:
1. **必须立即修改代码** — 不能写"发现XXX问题,但由于时间/复杂度原因暂不处理"
2. **必须重新运行** — 修改后必须重跑代码验证修正有效,不能只改代码不跑
3. **必须验证修正后结果合理** — 修正后的结果必须通过同样的检查,不能引入新问题
4. **必须在 RESULTS.md 中记录** — 写明"原始结果X → 发现问题Y → 修正方法Z → 修正后结果W"
**⛔ 禁止的行为(任何一条都是严重违规):**
- ❌ 发现结果不合理但只在注释/报告中"解释原因"然后继续使用不合理的值
- ❌ 发现 R² < 0.5 但写"由于数据特性,此精度可接受"然后不改进模型
- ❌ 发现超出物理边界但写"这是数学解,物理上意味着XXX"然后把数学解写进 JSON
- ❌ 发现约束不满足但写"后续可以通过XXX改进"然后输出不满足约束的结果
- ❌ 修改了代码但没重跑就声称"已修复"
- ❌ 循环修复超过 3 次仍未解决时放弃修复 — 此时应该换方法/换模型,而不是放弃
**⛔ 修复循环上限:同一个问题最多修 3 轮。如果 3 轮后仍不合理,必须回退到建模阶段重新设计模型(修改 MODELING_REPORT.md),而不是在错误的模型上反复打补丁。**
## ⛔⛔ 编码阶段通用禁止声明
### 禁止一:用中间计算坐标替代物理实体参数
代码中涉及碰撞检测、约束校验、目标函数计算时:
**必须使用 MODELING_REPORT.md 中定义的完整物理参数**(如板凳全长220cm、矩形宽30cm、车身长4.5m),**禁止直接复用上游步骤为其他目的计算的中间量**(如孔中心距165cm、把手坐标间距、质心偏移量)作为物理实体的几何代理。
**强制规则:**
1. 每个约束判定函数的文档字符串中必须注明其引用的物理参数来源及数值
2. 物理参数必须在代码顶部统一定义(从 MODELING_REPORT.md 提取),不能散落在各处硬编码
3. 约束函数使用的参数值与 MODELING_REPORT.md 不一致时,必须抛出异常而非静默执行
4. 禁止"就近取值"——不能因为某个中间变量数值接近就拿来当物理参数用
**典型错误示例:**
- 板凳碰撞检测用"孔中心距165cm"代替"板凳全长220cm" → 错误(少算了两端各27.5cm)
- 矩形放置约束用"中心坐标差"代替"边缘到边缘距离" → 错误(忽略了物体宽度)
- 车辆避障用"质心距离"代替"车身外轮廓最近点距离" → 错误(可能已经碰撞)
**正确做法:**
```python
# ⛔ 物理参数定义(来源:MODELING_REPORT.md 第X节)
BENCH_TOTAL_LENGTH = 2.20 # 板凳全长 220cm(题目原文)
BENCH_WIDTH = 0.30 # 板凳宽度 30cm(题目原文)
# ...
def check_collision(bench_a, bench_b):
"""检测两个板凳是否碰撞。
物理参数来源:MODELING_REPORT.md 符号说明表
- 板凳全长: BENCH_TOTAL_LENGTH = 2.20m
- 板凳宽度: BENCH_WIDTH = 0.30m
使用完整外轮廓(矩形四角)判断,非中心点距离。
"""
# 使用完整矩形碰撞检测(SAT 分离轴定理)
...
禁止二:未经验证的几何简化实现
如果 MODELING_REPORT.md 中标注了"待验证近似"的简化假设,代码中必须:
-
同时实现精确版本和简化版本
-
对比两者结果差异
-
如果差异超过 1~3%,必须使用精确版本
-
在 RESULTS.md 中报告简化误差
⛔ MANDATORY: 对照 Step 1 的实现清单,逐项验证:
echo "=== 实现清单对照 ==="
echo "检查每个子问题的结果文件是否存在且非空:"
for f in figures/problem_*_results.json figures/all_results.json figures/sensitivity_results.json; do
if [ -f "$f" ] && [ -s "$f" ]; then
echo " ✅ $f ($(wc -c < "$f") bytes)"
else
echo " ❌ $f — MISSING or EMPTY"
fi
done
echo ""
echo "检查代码文件是否存在:"
for f in code/*.py; do
[ -f "$f" ] && echo " ✅ $(basename $f)" || echo " ❌ $(basename $f)"
done
如果有 ❌,必须回去补完再继续。 特别注意:
-
figures/all_results.json 必须存在(paper-figure 依赖它画图)
-
figures/sensitivity_results.json 必须存在(灵敏度分析章节依赖它)
-
每个子问题的 figures/problem_N_results.json 必须存在
Step 7: 结果汇总
保存到 RESULTS.md:每个子问题的方法、关键结果、数据文件路径、代码文件清单。
Step 7.5: 数据输出完整性检查(⛔ 必须通过)
确保所有分析结果都保存为 JSON/CSV,供下一步 paper-figure 读取画图:
echo "=== 数据输出完整性检查 ==="
echo ""
echo "JSON 数据文件(paper-figure 的输入):"
ls -la figures/*.json 2>/dev/null || echo " (无)"
echo ""
echo "TABLE 文件:"
ls -la figures/TABLE_*.tex 2>/dev/null || echo " (无)"
⛔ MANDATORY:
MISSING=0
if [ -f figures/all_results.json ] && [ -s figures/all_results.json ]; then
echo " ✅ figures/all_results.json"
else
echo " ❌ figures/all_results.json — MISSING or EMPTY"
MISSING=$((MISSING+1))
fi
if [ -f figures/sensitivity_results.json ]; then
echo " ✅ figures/sensitivity_results.json"
else
echo " ⚠ figures/sensitivity_results.json — not found (required for sensitivity chapter)"
fi
echo "Missing: $MISSING"
如果 ❌,必须回去补完再继续。
⛔ 不要在这一步生成 PDF 图表或 latex_includes.tex——那是 paper-figure 的职责。
关键规则
-
comp-code 只负责数据采集、统计分析、输出结果数据(JSON/CSV)。不画图。
-
⛔ 禁止在分析代码中生成 PDF 图表。 所有 plt.savefig()、save_fig() 调用都不应该出现在 comp-code 的代码里。如果分析过程中需要可视化验证结果,用 plt.show() 看一眼就行,不要保存 PDF。
-
图表 PDF 全部由下一步 paper-figure 生成。 paper-figure 会读取 comp-code 输出的 JSON 数据,按 recipe 系统生成高质量 PDF。
-
⛔ 求解器/优化器超时设置: 不要设太短的超时(如 120 秒)。竞赛数据量可能很大,求解器需要足够时间。推荐设置:
-
小规模问题(变量 <100):timeout=300(5 分钟)
-
中规模问题(变量 100-1000):timeout=600(10 分钟)
-
大规模问题(变量 >1000):timeout=1200(20 分钟)
-
所有求解器都必须打印进度(每 30 秒输出一次当前最优解),防止无输出超时被系统杀掉
-
主输出文件:RESULTS.md + figures/*.json
-
临时文件放 _tmp/ 目录
-
代码必须能运行:写完必须执行验证
-
结果必须保存为 JSON/CSV 文件(供 paper-figure 读取画图)
<data_quality>
Data generation quality (when generating/simulating data without user uploads)
When no user data is available and you need to generate or simulate data:
-
Realistic ranges: values must match the problem domain — e.g., temperature in °C not arbitrary 0-1, population in millions not random integers
-
Meaningful patterns: data should show the trends/relationships the model is designed to capture — e.g., if modeling seasonal demand, the data should have seasonal patterns
-
Visualization-friendly: design data so the resulting figures look informative and professional:
-
Avoid extreme outliers that compress the main data into a tiny range
-
Ensure different methods/groups have visible but not identical differences (5-20% gaps, not 0.1% or 500%)
-
Include enough data points for smooth curves (≥50 for line plots, ≥200 for distributions)
-
For method comparison: the proposed method should be best but not unrealistically dominant — other methods should have their own strengths on some metrics
-
Consistent with problem statement: all generated numbers must be traceable to the problem description — if the problem says "30 provinces", generate 30 data points, not 10
-
Reproducible: set random seeds (np.random.seed(42)) so results are deterministic
</data_quality>
画图配色规则(任何 matplotlib/seaborn 图都必须遵守)
根据论文类型选择配色方案(整篇论文统一一套):
| 论文类型 | 推荐配色 | 调用方式 |
|----------|---------|---------|
| 竞赛论文 / 经管 / 统计建模 | Soft(默认) | setup_style() |
| 多组对比(>6 组) | Tableau | setup_style('tableau') |
| 自然科学 / 生物 / 化学 | NPG | setup_style('npg') |
| 医学 / 统计分析 | NEJM | setup_style('nejm') |
| IEEE / ACM / 工程类 | Science | setup_style('science') |
| 无障碍要求 | Colorblind | setup_style('colorblind') |
在任何画图代码的开头,必须先初始化配色:
import os, sys, shutil
if not os.path.isdir('_utils'):
os.makedirs('_utils', exist_ok=True)
for src in ['plot_utils.py', 'stats_utils.py']:
for search in ['skills/shared-scripts', '../skills/shared-scripts']:
p = os.path.join(search, src)
if os.path.isfile(p):
shutil.copy2(p, f'_utils/{src}'); break
sys.path.insert(0, '.')
try:
from _utils.plot_utils import setup_style, save_fig, PALETTE
setup_style()
except ImportError:
import matplotlib; matplotlib.use('Agg')
import matplotlib.pyplot as plt
PALETTE = ['#5B9BD5','#ED7D7D','#7BC8A4','#B0B0B0','#9B8EC4','#F4A261']
matplotlib.rcParams['axes.prop_cycle'] = matplotlib.cycler(color=PALETTE)
import seaborn as sns
单组柱状图必须显式传颜色:
每个画图脚本写完后自检: 检查是否有硬编码颜色、plt.title()、缺少 setup_style。发现就立即修复。
-
代码要有注释(附录评审加分项)
-
数据路径用相对路径
-
基本异常处理,一个子问题失败不能全崩
-
requirements.txt 必须生成
-
大文件用 Bash heredoc 分块写入