PCSE.WOFOST模型调参

根据实测值对模型进行调参
先利用土壤物理参数对土壤的水分特征进行估计,由于气象文件分辨率为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中的 SUBPLEX optimization algorithm

具体可以参考网址(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)

你可能感兴趣的:(wofost相关,python)