
AquaCrop-OSPy这玩意儿官方给的示例跑起来往往很顺一旦换成自己站点的数据各种报错和离谱结果就全来了。我当初从能跑的Demo到跑通自己场地的数据卡了整整一个晚上最后发现全是输入设置的问题。所以这篇学习笔记02专门把模型输入这块拆开揉碎讲清楚覆盖气象数据、作物参数、土壤剖面、灌溉管理、初始条件这些必须在建模前准备好的东西适合正好学到这一步、准备喂自己数据的同学。1. 动手前先理清AquaCropModel到底要喂哪些输入1.1 输入清单和它们在模拟中的角色在第一次调用AquaCropModel之前建议先把输入项在纸上列一遍。别嫌这一步啰嗦磨刀不误砍柴工后面所有报错排查都依赖你对自己输入内容的熟悉程度。AquaCrop-OSPy构建一个模型实例大致需要下面这些块输入块必需程度在模型里的作用simulation_start_date / simulation_end_date必需定义模拟窗口决定整个模拟的起止边界weather_file必需提供逐日气象序列温度、降水、参考蒸散量ETo或ETo计算所需的辅助变量soil必需土壤剖面参数包括水文特性、径流曲线数、土层分层crop必需作物参数包括生育期、冠层生长、根系深度、收获指数等irrigation_management可选没有就是雨养有就按规则或按事件灌水field_management可选地表覆盖、覆膜、地表粗糙度等田间管理措施initial_water_content可选模拟开始时土壤剖面中的初始含水量分布groundwater可选浅层地下水对根区的补给一般平原地块才用得上这份清单里前四项是跑起来的底线后面几项是跑得像样的关键。我见过不少用户直接不传initial_water_content就跑结果模型默认了一个含水量最后产量和实测差很多还以为是作物参数出了问题其实就是初始条件不对。1.2 哪些可以缺省哪些必须较真有一类输入是缺省能跑但结果无意义的最典型的就是灌溉管理。你要是把irrigation_management整个不传模型会按雨养作物处理如果你实际是滴灌玉米那模拟出来的水分胁迫曲线肯定跟你田里对不上。反过来如果你研究的就是雨养农业那这一步反而应该明确传一个IrrigationManagement(irrigation_method1)标明无灌溉而不是靠默认值蒙混过关。模拟起止时间也容易引起误解。很多初学者以为simulation_start_date必须等于播种日期其实不是。AquaCropModel允许模拟窗口比作物生长周期长模型内部会用作物文件中设置的播种日期或你额外指定的种植时间来决定什么时候出苗。但反过来如果模拟窗口在你设定的播种日期之前就结束那模型等于什么都没跑。我习惯把模拟开始日期设在前一年的冬季或者至少播种前一个月这样能给土壤水分一个预平衡的时间后面看初始含水量也更合理。还有一点想提醒Ospy的weather_file参数传的是文件路径不是DataFrame。早期版本有人直接塞pandas DataFrame进去会直接类型报错。这个细节后面气象文件部分还会展开。2. 气象文件是第一道坎格式、列名和ETo的坑2.1 我常用的气象文件模板气象文件看起来就是纯文本但它恰恰是OSPy里最容易卡住的地方因为内部的ClimateFile解析类对格式要求非常固定。我踩过很多次坑之后现在固定用下面这个模板基本一次通过Locality: 山东青岛试验站 Latitude 36.07 Longitude 120.38 Elevation 12.5 day month year Tmin Tmax Precipitation ETo 1 1 2000 -1.2 5.6 0.0 0.4 2 1 2000 -0.8 6.1 0.2 0.5注意几个细节。第一前几行的站点信息顺序不强制但推荐这么写解析时它会自动找Locality、Latitude、Longitude、Elevation这些关键字。第二分隔符建议用Tab用空格也能读但空格个数不统一时容易出幺蛾子数据列越多越是如此。第三表头行就是普通的列名不需要加引号列名不区分大小写Tmin和tmin都能认但拼写不能错。我在一次试验里把Precipitation写成了Precip结果整个文件解析出来降水全是NaN模型初始化倒没报错最后产量结果离了大谱。日期部分我最推荐用day、month、year三列分别放年月日一年365天一行不落全给全。有些教程会让你只用一列day表示从1开始的年积日这样OSPy会按连续日期内部推算并补全但我实际试下来发现不同版本对只有day列的解析逻辑有差异有的会从这里开始累计有的会把它当成年份处理非常容易踩雷。所以老老实实用三列最省心。2.2 ETo直接给还是让模型算参考蒸散量ETo是AquaCrop的重要输入它本质上代表了气象条件对作物耗水的需求侧驱动。OSPy给ETo留了两种路径。第一种气象文件里有ETo列模型直接用不做任何计算。这是最稳妥的方式前提是你对ETo的计算方法有把握。ETo单位是mm/day必须是逐日值。我一般用气象站的完整数据在外部用FAO-56 Penman-Monteith公式算好再塞进文件。如果你手头只有温度降水数据没有辐射风速湿度那ETo列就必须靠外部来源补齐不能靠模型变魔术。第二种气象文件里不提供ETo列但提供相对湿度RH、风速Wind、太阳辐射Rs或者日照时数Sunshine这些辅助变量OSPy会在读文件时调用pyet库按FAO-56推算ETo。这个方式适合你手头有完整气象观测要素的情况。我的建议是如果条件允许尽量走自己算好ETo再喂进去这条路。原因是模型内置的ETo计算对输入变量的单位比较敏感比如风速必须保证是2米高度处的m/s不是10米处的太阳辐射必须是MJ/m2/day而不是W/m2一旦单位不对算出来的ETo会系统性偏移整个模拟的耗水量和产量全都跟着歪。2.3 日期、单位、缺测值三个最常见翻车点日期连续性这个问题是最隐蔽的。OSPy读气象文件后内部会按日期序列去索引逐日气象如果你的数据中间漏了几天它不一定立刻报错而是会把缺失日期当成NaN处理或者干脆把序列错位往下串实际效果就是用的数据根本不连续。这种情况最难排查因为报错信息往往很模糊甚至不报错。我的办法是在喂给模型之前先用脚本检查一遍日期完整性from datetime import date import pandas as pd df pd.read_csv(weather.txt, sep\t) dates pd.to_datetime(df[[day, month, year]]) expected pd.date_range(dates.iloc[0], dates.iloc[-1], freqD) print(len(dates), len(expected), dates.dt.date.tolist() expected.date.tolist())如果长度对不上或顺序不对趁早修复别让它进模型。单位问题上温度是摄氏度降水是mm这俩基本不会错。真正会翻车的是风速和辐射。遇到过有人把风速填成km/h结果ETo整体放大好几倍整个生长季蒸散量爆表。缺测值处理是另一个大头。气象观测数据总会有缺测但OSPy不认空值和-999这类占位符出现了基本就是NaN初始化时大概率直接报non-finite输入错误。我常用的做法是缺测天数不多时用前后两天线性插值缺测多的时候就用多年平均或附近站点插补同时在文件外面留一份插补记录方便后面审稿或者写报告时溯源。3. 作物参数和土壤剖面从原版文件到OSPy对象3.1 用内置参数还是读原版.CRO文件OSPy做得很贴心的一点是它既支持直接调用内置作物参数也支持读取AquaCrop桌面版导出或者手动整理的.CRO作物文件。内置作物参数适合快速验证模型流程比如学习阶段、跑通Demo、对比测试这时候直接用Crop(Maize)或Crop(Wheat)简单省事省了去翻上百行参数的时间。但一旦进入正儿八经的研究我强烈建议读原版.CRO文件。因为实际用到的作物品种、播期、密度、收获方式都不一样你肯定要改参数而内置参数是写死在包里的虽然也能改对象属性但因为看不到完整上下文很容易漏改某几个关联项。反而是从桌面版AquaCrop导出的.CRO文件打开能看到完整的参数逻辑[GDD_Info] Method 0 GDD_season 1000 [Crop_Info] Crop_type 1 CCx 0.85 CDC 0.08具体字段名以你手里的文件为准关键点是文件用方括号分节读进来后OSPy会默认所有参数都是合理范围内的有效值。所以我拿到一份新的作物文件后第一件事是把里面的数字都过一遍特别是CCx最大冠层覆盖度、CDC冠层衰退系数、HI收获指数这几个直接影响产量的关键参数看它们和文献、实测是否吻合。3.2 土壤剖面文件的关键参数土壤参数在AquaCrop里的地位比很多人以为的重要得多它直接决定了根系能抽到多少水、降水能不能入渗、地表径流怎么产生对水分平衡的影响是全链条的。用OSPy读土壤文件下面几个参数无论如何都要检查清楚。一是CNSCS径流曲线数它控制地表产流同样的降水CN设65和75径流比例能差出几个百分点累计整个生长季就是不小的水分差额。二是REW表层土壤可蒸发水量它直接影响土壤蒸发阶段的前期蒸发速率。三是Zem最大有效蒸发深度这个参数默认值偶尔会和某些研究区的实际土壤深度冲突我吃过亏当时没改结果裸间蒸发被高估旱季模拟的土壤水消耗明显偏大。土壤剖面分层的写法OSPy和原版AquaCrop思路一致从上到下逐行写各土层每行包括土层厚度、含水量特征等参数。需要特别注意的有两点。第一土层厚度之和必须能覆盖到作物的最大有效根深不然模型里一部分根系会悬空。第二每层的参数个数必须完全一样少一个数都不会报第几行第几列缺失而是直接解析失败或者用默认值补位后者更隐蔽。我自己的习惯是把完整的土壤剖面文件按照实验站的实际土壤调查数据做出来后另外用小脚本做一个参数范围合法性检查比如FC在0和1之间、WP小于FC、SAT大于FC这些基本物理约束任何一条不满足都说明数据录入有错该先改数据而不是硬跑模型。3.3 参数修改的两个路径和版本差异参数文件读进来之后OSPy允许在Python里直接修改对象属性这是第二条修改路径。比如你想把某个作物品种的CCx从0.85改成0.90crop Crop(D:/projects/station/maize.cro, Maize) crop.CCx 0.90 crop.HI 0.48这种改法的好处是不用去动原始文件方便做参数敏感性分析和情景模拟。坏处是如果你改了对象属性又忘记录过几天再回来看脚本会搞不清楚基线参数到底是多少。我建议每个改动都写注释并且把修改后的完整参数导出存档。OSPy也提供了把对象保存为文件的方法具体导出接口不同版本可能有差异建议查一下当前版本的帮助文档。版本差异这里单独提醒一句。AquaCrop桌面版从6.x升级到7.x之后CRO和SOL文件的字段有一些调整OSPy对应版本也不是完全兼容读取所有老格式文件。如果你在读文件的时候遇到KeyError或者解析出来的参数明显异常优先怀疑版本问题解决思路是找旧版文件或者在桌面版里重新打开再另存为新格式。千万别急着改代码去适配那样反而容易引入更多问题。4. 灌溉与田间管理不传也能跑但结果可能不是你要的4.1 五种灌溉方式的取舍OSPy的IrrigationManagement把灌溉方式大致分了五类对应不同模拟目标irrigation_method含义适用场景1无灌溉/雨养旱地农业、对照情景2按固定事件灌溉有实际灌水记录、灌水日期已知3按根区可耗水量RAW阈值自动灌溉模拟水分亏缺到一定程度就补水的优化情景4按固定时间间隔自动灌溉轮灌制度、固定周期灌水的设计情景5按土壤含水量阈值自动灌溉需要精确控制土壤水分的试验模拟我自己的经验方法2最常用也最实用因为它对应真实观测数据。只要你能拿到每次灌水的日期和灌水量mm直接按事件灌进去模型就能和实测田间水分动态对上。方法3到5更适合做情景设计比如你想回答如果我在苗期不灌水拔节后再按40%RAW灌产量会差多少这种问题。4.2 手动灌溉事件的日期陷阱方法2写手动灌溉事件时最典型的坑是日期类型写成字符串。OSPy要求灌水日期用datetime.date对象写成2020-06-10这种字符串看起来好像没什么问题但模型内部做时间索引时类型对不上轻则忽略事件重则直接类型报错。正确写法是from datetime import date from aquacrop import IrrigationManagement irr IrrigationManagement( irrigation_method2, scheduled_irrigation[ (date(2020, 6, 10), 40.0), (date(2020, 6, 20), 35.0), ], )还有两个细节比较容易忽略。第一个是灌溉日期必须落在模拟窗口内落在窗口外的会被忽略更糟的是有时候不报错结果你在结果里怎么都找不到那笔灌水还以为模型算错了。第二个是灌水量单位是mm不是m3/亩也不是方/公顷。单位折算错了一次灌水算成10倍量水分平衡能彻底乱掉植株泡在水里模拟产量直接掉到零。4.3 田间管理覆膜、地表覆盖和土壤质地关联田间管理是一个常被忽视的输入块但它对模拟结果的影响不能低估。FieldManagement可以设置地表是否覆膜、是否有作物残留物覆盖、田间表面是否平整等。这些参数影响的是土壤蒸发和地表径流尤其在干旱半干旱区覆膜带来的蒸发减少会直观反映在土壤水含量和最终产量上。比如你模拟的是西北旱区的玉米覆膜与否在现实里产量差异很大如果不把field_management里的地表覆盖考虑进去模型里就不存在覆膜这个机制最后结果自然对不上观测。这里特别想强调做的设置要和你的实际田间管理一致而不是随意选一个默认值。田间地表参数还与土壤质地有关联。同样是地表覆盖黏土和砂土上的入渗、蒸发行为差异很大OSPy在做田间管理时会和土壤剖面参数一起参与计算。所以如果你发现改了field_management里的某个参数但结果毫无变化大概率不是参数没生效而是被土壤参数里的某些限制条件卡住了需要两个输入块一起检查才能定位。5. 初始含水量、模拟时间与GDD最容易被忽略的三个变量5.1 初始含水量三种给法对应不同场景初始含水量决定模拟第一天土壤剖面里有多少水它的影响其实会持续很长一段时间尤其如果到播种期的降水不多初始含水量差异能一直影响到作物根区水分消耗的中后段。OSPy的InitialWaterContent支持多种方式传入核心就是指定value和method。我用的最多的是按每层体积含水量直接给值因为田间实测的土壤含水量通常就是体积含水量from aquacrop import InitialWaterContent init_wc InitialWaterContent( value[0.30, 0.28, 0.25, 0.22], methodvolumetric, )注意这里的列表长度要和土壤文件里的土层数对应。如果列表比土层少后续层会用默认值补全如果列表比土层多多出来的会被忽略。这个按需补全的机制让人又爱又恨爱的是方便恨的是如果你不知道它有这个逻辑补全出来的默认值可能不是你想要的模拟结果就带着隐性误差跑了。除了体积含水量还可以按占田间持水量的百分比给或者按占有效水比例给。这个到底选哪种取决于你手头实测数据的形式。如果你只有土壤水分占田间持水量70%左右这么个田间判断就可以直接按百分比给比自己估算体积含水量更贴近实际。5.2 模拟起止与播种日期怎么配合模拟起止时间的选择说简单也简单说讲究也讲究。基本要求是初始化日期在作物生长季开始之前结束日期在收获之后。但具体提前多久我倾向于留出至少一个月的预热期让土壤水分在真实气象驱动下先演化一段时间这样到播种时土壤水库的状态才比较真实。如果你从播种当天才开始模拟初始含水量只能靠InitialWaterContent硬给对边界条件的依赖就大多了。如果你用的是读取的原版CRO文件作物文件里往往带有种子日期和收获日期的信息但这些只是默认值在实际建模时常常要按当年的农事日历修改。OSPy的模型初始化时会在控制输出里打印出一个时间线显示模拟开始、播种、出苗、收获这些关键节点。我每次跑新站点都会认真看这段输出确认时间线和农事记录一致再往下走。5.3 GDD计算方法影响出苗和生育期长度积温是AquaCrop模拟生育期推进的核心引擎。温度数据从气象文件进来模型会根据每日温度计算生长度日再用生长度日驱动出苗、冠层生长、物候推进。GDD的计算方法不同同一份温度数据算出来的积温会有差别。OSPy默认采用的GDD计算方法基于日平均温度就是Tavg (Tmin Tmax) / 2然后把Tavg减去作物基点温度得到当天的度日贡献。这种折算法在温度日较差大的地方会有偏差尤其是高温或低温被截断时。如果你发现模拟的物候期和观测比总是差几天检查一下GDD方法是不是需要换成正弦曲线法会更合理。GDD相关的参数大多在作物文件里包括基点温度、生育期总积温需求等。总积温需求量尤其关键它是决定生育期长度的核心参数不同品种差异很大。曾经我帮人查一个问题冬小麦模拟成熟期比实际晚了十天查到最后发现是CRO文件里GDD_season填的是杂交种的积温需求和当地主栽品种差了200度日。6. 输入设好之后正确性检查与排错实战6.1 先跑一个基准算例验证设置是否自洽输入都准备到位后强烈建议不要直接上正式方案而是先跑一个基准算例验证所有输入是否自洽。基准算例的套路是用同一份气象数据创建雨养方案irrigation_method1和完全灌溉方案irrigation_method4且灌水充足各跑一遍。理论上完全灌溉的产量应该不低于雨养蒸散量应该不低于雨养。如果出现完全灌溉产量低于雨养这种违背基本物理直觉的情况说明输入肯定有系统性错误。跑完之后一定要检查水量平衡。OSPy在模型对象里有水量平衡信息initialize之后和模拟结束之后都可以查看重点关注总水量是否闭合差太多就说明某个环节的水分输入输出设置有问题。水量平衡是AquaCrop这种过程模型的生命线水量闭合不了后面所有结果都不可信。结果数据的读取也要养成好习惯model.initialize() model.step(until_terminal_conditionTrue) model.terminate() results model.get_simulation_results() df_water results.Water df_final results.Final先看Final里的最终产量和季节性ET如果这两个量级和文献或实测对不上就不要往下分析逐日序列了先把输入定位问题。6.2 常见报错与排查对照表这里把我实际遇到过的报错和排查思路整理成一张表省得大家再走弯路现象可能原因排查方向初始化报No valid weather data气象文件列名不识别、日期解析失败检查列名是否包含Tmin/Tmax/Precipitation等关键字查看文件前几行格式初始化报Non-finite values气象数据含NaN或-999占位符逐列检查缺失值采用插补或删除处理读CRO文件报KeyError作物文件版本与OSPy不兼容换对应版本的CRO文件或在桌面版AquaCrop中重新导出Soil文件解析后剖面层数不对行数、列数不齐或有空行检查每行参数个数一致去掉多余空行和分隔符模拟结果产量为0播种日期不在模拟窗口内、或作物未出苗就结束查看initialize打印的时间线确认模拟窗口与生育期重合完全灌溉产量反而更低ETo计算或灌水单位有系统偏差核对气象文件中的ETo和灌水事件的水量单位结果对灌溉事件毫无反应灌溉日期类型错误、日期不在窗口内检查scheduled_irrigation里的日期类型和区间6.3 我的三步定位法如果跑出来结果就是不对报错信息又不给力我一般按下面三步排查效率很高。第一步是砍到最小可复现。把灌溉、田间管理、初始含水量全去掉只用内置Soil和内置Crop配上你的气象数据跑一遍。如果这一遍跑下来结果合理说明气象文件没问题问题就出在你后来加的那几块自定义输入里。如果连这都跑不对那气象文件或者模型安装本身有问题先解决基础问题再说。第二步是逐块替换。从最小可复现出发先把Crop换成你自定义的CRO文件看结果是否异常再把Soil换成自定义的再看最后加IrrigationManagement和InitialWaterContent。每替换一块就重新跑一遍并记录结果哪一步开始不对劲问题就锁定在那块输入上。这个办法看着笨但实际定位快得很而且能避免同时改多个变量导致没法归因。第三步是看初始化输出。OSPy的initialize()方法可以打印出非常详细的检查报告包括气象数据起止、作物生育期时间线、土壤分层汇总、初始含水量计算值等。我见过很多人忽略这些输出其实它们就是模型替你把输入翻译成了内部数据结构里面任何一行异常都是重要的排查线索。养成每次初始化都认真读一遍输出的习惯能省下大量反复试错的时间。还有一个经验每次调试时保持一个干净的输入文件版本管理习惯。我在本地会用站点名称日期建文件夹气象、土壤、作物各放一个子目录改参数之前先复制一份原版这样出了问题随时能回滚也方便对照不同参数方案的差异。这套流程跑顺之后准备一份新站点的模型输入大概就是20分钟的活大部分时间花在气象数据清洗和作物参数校准上。输入设置这块确实繁琐但它是整个AquaCrop-OSPy模拟中最值得花心思的部分后面所有分析、出图、写结论都建立在它的基础之上。