根据实测值对模型进行调参
先利用土壤物理参数对土壤的水分特征进行估计,由于气象文件分辨率为0.5度,定义函数使坐标转换为对于气象文件的坐标。
# 参数定义
def soil_data(s, c, om, b=1.5):
s = s # sand %
c = c # clay %
om = om # organic matter %
b = b # bulk g/cm3
# 永久萎蔫点 PWP
pwp = 1.14*(-0.00024*s+0.00487*c+0.006*om+0.00005*s*om-0.00013*c*om+0.0000068*s*c+0.031)-0.02
# 田间持水量 FC
o33 = -0.00251*s+0.00195*c+0.011*om+0.00006*s*om-0.00027*c*om+0.0000452*s*c+0.299
fc = o33+1.283*o33*o33-0.374*o33-0.015
# 饱和含水量-FC
s_33 = 1.636*(0.00278*s+0.00034*c+0.022*om-0.00018*s*om-0.00027*c*om-0.0000584*s*c+0.078)-0.107
# 饱和含水量 容重矫正后
sat = fc+s_33-0.00097*s+0.043
# 校正后田间持水量
pn = 2.65*(1-(fc+s_33-0.00097*s+0.043))
df = b/1.5
if df<0.9:
df=0.9
elif df>1.2:
df=1.2
pdf = pn*df
fc_v = fc-0.2*(fc+s_33-0.00097*s+0.043-(1-pdf/2.65))
# 校正后饱和含水量
sat_v = 1-(pdf/2.65)
return [pwp, fc_v, sat_v]
def st_loc(num):
"""
选取气象数据的时候标准化输入为0.5的分辨率
"""
import math
if num-math.floor(num) <=0.25 or num-math.floor(num)>=0.75:
result = round(num)
elif 0.25 < num-math.floor(num) < 0.75:
result = math.floor(num) + 0.5
return result
导入必要的包
import os
import datetime
import pandas as pd
from pcse.fileinput import CABOFileReader # 读取CABO文件(作物土壤文件)
from pcse.fileinput import YAMLAgroManagementReader # 管理文件读取
from pcse.models import Wofost71_WLP_FD, Wofost71_PP # 导入模型,Wofost71_PP潜在模型
from pcse.base import ParameterProvider # 综合作物、土壤、位点参数
from pcse.fileinput import ExcelWeatherDataProvider # 读取气象文件
import numpy as np
import nlopt
根据Excel中的基础情况初始化模型
site = '邓州' # 位点
variety = 103 # 品种
data_dir = r'C:\Users\Administrator\Desktop\PCSE模型运行\model calibration' # 模型校准文件夹
data_base_info = pd.read_excel('18县生物量LAI整理12.27.xlsx', sheet_name='基本情况')
sub_data = data_base_info.loc[data_base_info['试验地']==site,
['经度', '维度', '品种', '播量(计算)', '播种期',
'sa', 'cl', 'bd', 'som', '灌溉']]
# 基本数据获取
lon = sub_data.loc[sub_data['品种']==variety, ['经度']] # 读取经度
lat = sub_data.loc[sub_data['品种']==variety, ['维度']] # 读取纬度
SAND = sub_data.loc[sub_data['品种']==variety, ['sa']].iloc[0,0] # 砂粒
CLAY = sub_data.loc[sub_data['品种']==variety, ['cl']].iloc[0,0] # 黏粒
OM = sub_data.loc[sub_data['品种']==variety, ['som']].iloc[0,0] # 有机质
BULK_DENSITY = sub_data.loc[sub_data['品种']==variety, ['bd']].iloc[0,0] # 容重
sow_date = sub_data.loc[sub_data['品种']==variety, ['播种期']].iloc[0,0] # 播期
irrigation = sub_data.loc[sub_data['品种']==variety, ['灌溉']].iloc[0,0] # 灌溉条件
weather_dir = r'C:\Users\Administrator\Desktop\2020气象数据' # 气象数据路径
cropdata = CABOFileReader(os.path.join(data_dir,'%d.CAB'%variety)) # 读取作物文件
soildata = CABOFileReader(os.path.join(data_dir,'EC3.NEW')) # 土壤文件
sitedata = {
'SSMAX' : 0.,
'IFUNRN' : 0,
'NOTINF' : 0,
'SSI' : 0,
'WAV' : 30,
'SMLIM' : 0.03,
'RDMSOL' : 120}
parameters = ParameterProvider(cropdata=cropdata, soildata=soildata, sitedata=sitedata) # 参数集合
# 数据替换
parameters['TDWI'] = sub_data.loc[sub_data['品种']==variety, ['播量(计算)']].iloc[0,0] # 播量
parameters['SMW'] = soil_data(SAND, CLAY, OM, BULK_DENSITY)[0] # 萎蔫点
parameters['SMFCF'] = soil_data(SAND, CLAY, OM, BULK_DENSITY)[1] # 田间持水量
parameters['SM0'] = soil_data(SAND, CLAY, OM, BULK_DENSITY)[2] # 饱和含水量
agromanagement = YAMLAgroManagementReader(os.path.join(data_dir,'wheatautoirrigation.agro')) # 管理文件读取
agromanagement[0][datetime.date(2019, 10, 1)]['CropCalendar']['crop_start_date'] = sow_date # 播期替换
irr_condition = agromanagement[0][datetime.date(2019, 10, 1)]['StateEvents'][0]['events_table'][0] # 获取到自动灌溉的字典
irr_calculation = round(irrigation/100*parameters['SMFCF'], 2).item() # 灌溉条件替换, 读取的数据可能是numpy类型
agromanagement[0][datetime.date(2019, 10, 1)]['StateEvents'][0]['events_table'][0][irr_calculation] = \
agromanagement[0][datetime.date(2019, 10, 1)]['StateEvents'][0]['events_table'][0].pop(list(irr_condition.keys())[0])
# 气象数据
weatherdataprovider = ExcelWeatherDataProvider(os.path.join(weather_dir,'NASA天气文件lat={0:.1f},lon={1:.1f}.xlsx'.
format(st_loc(lat.iloc[0,0]), st_loc(lon.iloc[0,0]))))
wf = Wofost71_WLP_FD(parameters, weatherdataprovider, agromanagement) # 定义模型
wf.run_till_terminate() # 运行模型直到终止
output=pd.DataFrame(wf.get_output()).set_index('day')
具体可以参考网址(NLOPT)[https://nlopt.readthedocs.io/en/latest/]
先定义模型runner和代价函数计算函数
parameters每次运行之后都会全部改为默认值因此代价函数运算中也要重新初始化
class ModelRerunner(object):
"""Reruns a given model with different values of parameters TWDI and SPAN.
Returns a pandas DataFrame with simulation results of the model with given
parameter values.
"""
# parameters to calibrate: EFFTB40 CVS
def __init__(self, params, wdp, agro):
self.params = params
self.wdp = wdp
self.agro = agro
def __call__(self, par_values):
# Check if correct number of parameter values were provided
# if len(par_values) != len(self.parameters):
# msg = "Optimizing %i parameters, but only % values were provided!" % (len(self.parameters, len(par_values)))
# raise RuntimeError(msg)
# # Clear any existing overrides
# self.params.clear_override()
# Set overrides for the new parameter values
self.params['SPAN'] = par_values[0]
self.params['KDIFTB'][1] = par_values[1]
self.params['KDIFTB'][3] = par_values[2]
self.params['EFFTB'][1] = par_values[3]
self.params['EFFTB'][3] = par_values[4]
self.params['AMAXTB'][1] = par_values[5]
self.params['AMAXTB'][3] = par_values[6]
self.params['AMAXTB'][5] = par_values[7]
self.params['CVS'] = par_values[8]
self.params['CVO'] = par_values[9]
self.params['CVL'] = par_values[10]
self.params['CVR'] = par_values[11]
# 数据替换
self.params['TDWI'] = sub_data.loc[sub_data['品种']==variety, ['播量(计算)']].iloc[0,0] # 播量
self.params['SMW'] = soil_data(SAND, CLAY, OM, BULK_DENSITY)[0] # 萎蔫点
self.params['SMFCF'] = soil_data(SAND, CLAY, OM, BULK_DENSITY)[1] # 田间持水量
self.params['SM0'] = soil_data(SAND, CLAY, OM, BULK_DENSITY)[2] # 饱和含水量
# Run the model with given parameter values
wofost = Wofost71_WLP_FD(self.params, self.wdp, self.agro)
wofost.run_till_terminate()
df = pd.DataFrame(wofost.get_output()).set_index("day")
return df
class ObjectiveFunctionCalculator(object):
"""Computes the objective function.
This class runs the simulation model with given parameter values and returns the objective
function as the sum of squared difference between observed and simulated LAI.
. """
def __init__(self, params, wdp, agro, observations):
self.modelrerunner = ModelRerunner(params, wdp, agro)
self.df_observations = observations
self.n_calls = 0
def __call__(self, par_values, grad=None):
"""Runs the model and computes the objective function for given par_values.
The input parameter 'grad' must be defined in the function call, but is only
required for optimization methods where analytical gradients can be computed.
"""
self.n_calls += 1
print(".", end="")
# Run the model and collect output
self.df_simulations = self.modelrerunner(par_values)
# compute the differences by subtracting the DataFrames
# Note that the dataframes automatically join on the index (dates) and column names
df_differences_tagp = self.df_observations['生物量kg/ha'] - self.df_simulations['TAGP']
df_differences_twso = self.df_observations['穗生物量kg/ha'] - self.df_simulations['TWSO']
df_differences_lai = self.df_observations['LAI'] - self.df_simulations['LAI']
# Compute the RMSE on the LAI column
obj_func = (np.sqrt(np.mean(df_differences_tagp**2))/abs(np.mean(df_differences_tagp))+
np.sqrt(np.mean(df_differences_twso**2))/abs(np.mean(df_differences_twso))+
np.sqrt(np.mean(df_differences_lai**2))/abs(np.mean(df_differences_lai)))
return obj_func
实测值输入
# 实测值
data_obs = pd.read_excel('18县生物量LAI整理12.27.xlsx', sheet_name='最终实测') # 实测值
sub_obs = data_obs.loc[data_obs['试验地']==site, ['品种', 'day', 'LAI', '生物量kg/ha', '穗生物量kg/ha']]
sub_obs_103 = sub_obs.loc[sub_obs['品种']==variety].set_index('day')
sub_obs_103
品种 LAI 生物量kg/ha 穗生物量kg/ha
day
2020-01-02 103 0.816263 676.718558 0.000000
2020-03-10 103 4.625373 6135.784509 0.000000
2020-04-19 103 5.946694 12810.812963 1810.652288
2020-05-11 103 5.445854 20267.625000 7783.650000
2020-05-26 103 0.000000 21877.584387 9560.547802
运行一次试一下能否计算
# 测试一下能计算不能
objfunc_calculator = ObjectiveFunctionCalculator(parameters, weatherdataprovider, agromanagement, sub_obs_103)
defaults = [31.5, 0.4,0.6,0.5,0.62,45,45,45,0.862,0.9,0.68,0.69]
error = objfunc_calculator(defaults)
print("Objective function value with default parameters (%s): %s" % (defaults, error))
输出:.Objective function value with default parameters ([31.5, 0.4, 0.6, 0.5, 0.62, 45, 45, 45, 0.862, 0.9, 0.68, 0.69]): 8.76644301817127
利用SUBPLEX算法进行自动调参
SPAN_range = [28, 33]
KDIFTB0_range = [0.4, 0.7]
KDIFTB1_range = [0.4, 0.7]
EFFTB0_range = [0.4, 0.5]
EFFTB1_range = [0.4, 0.7]
AMAXTB5_range = [38, 45]
AMAXTB1_range = [38, 45]
AMAXTB13_range = [38, 45]
CVS_range = [0.66, 0.9]
CVO_range = [0.66, 0.9]
CVL_range = [0.66, 0.9]
CVR_range = [0.66, 0.9]
objfunc_calculator = ObjectiveFunctionCalculator(parameters, weatherdataprovider, agromanagement, sub_obs_103)
# Start optimizer with the SUBPLEX algorithm for two parameters
opt = nlopt.opt(nlopt.LN_SBPLX, 12)
# Assign the objective function calculator
opt.set_min_objective(objfunc_calculator)
# lower bounds of parameters values
opt.set_lower_bounds([SPAN_range[0], KDIFTB0_range[0], KDIFTB1_range[0], EFFTB0_range[0], EFFTB1_range[0],AMAXTB5_range[0],
AMAXTB1_range[0],AMAXTB13_range[0],CVS_range[0],CVO_range[0],CVL_range[0],CVR_range[0]])
# upper bounds of parameters values
opt.set_upper_bounds([SPAN_range[1], KDIFTB0_range[1], KDIFTB1_range[1], EFFTB0_range[1], EFFTB1_range[1],AMAXTB5_range[1],
AMAXTB1_range[1],AMAXTB13_range[1],CVS_range[1],CVO_range[1],CVL_range[1],CVR_range[1]])
# the initial step size to compute numerical gradients
opt.set_initial_step([1, 0.1,0.1,0.1,0.1,3,3,3,0.1,0.1,0.1,0.1])
# Maximum number of evaluations allowed
opt.set_maxeval(3000)
# Relative tolerance for convergence
opt.set_stopval(0.5)
# opt.set_ftol_rel(3000)
# Start the optimization with the first guess
firstguess = [31.5, 0.4,0.6,0.5,0.62,45,45,45,0.862,0.9,0.68,0.69]
x = opt.optimize(firstguess)
print("\noptimum at SPAN: %s, KDIFTB0: %s, KDIFTB1: %s, EFFTB0: %s, EFFTB40: %s, \
AMAXTB5: %s, AMAXTB1: %s, AMAXTB13: %s, CVS: %s, CVO: %s, CVL: %s, CVR: %s" %
(x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11]))
print("minimum value = ", opt.last_optimum_value())
print("result code = ", opt.last_optimize_result())
print("With %i function calls" % objfunc_calculator.n_calls)