Example Object-Oriented Access to the PEST Control File

The pst_handler module with pyemu.pst contains the Pst class for dealing with pest control files. It relies heavily on pandas to deal with tabular sections, such as parameters, observations, and prior information. This jupyter notebook shows how to create a control-file object (instantiate the class or make an instance of the class), how to access attributes of the class, and how to call an instance method.


In [13]:
import os
import numpy as np
import pyemu
from pyemu import Pst

A PEST control file is required to make the object, and we need to pass the name of the PEST control file as a parameter to the init method for the class. The class instance (or object) is assigned to the variable p.


In [14]:
pst_name = os.path.join("..", "..", "examples", "henry","pest.pst")
p = Pst(pst_name)

Now all of the relevant parts of the pest control file are attributes of the object. For example, the parameter_data, observation data, and prior information are available as pandas dataframes.


In [15]:
p.parameter_data.head()


Out[15]:
parnme partrans parchglim parval1 parlbnd parubnd pargp scale offset dercom extra
parnme
global_k global_k log factor 200.0 150.00 250.00 m 1.0 0.0 1 NaN
mult1 mult1 log factor 1.0 0.75 1.25 m 1.0 0.0 1 NaN
mult2 mult2 log factor 1.0 0.50 2.00 m 1.0 0.0 1 NaN
kr01c01 kr01c01 log factor 1.0 0.10 10.00 p 1.0 0.0 1 NaN
kr01c02 kr01c02 log factor 1.0 0.10 10.00 p 1.0 0.0 1 NaN

In [16]:
p.observation_data.head()


Out[16]:
obsnme obsval weight obgnme extra
obsnme
h_obs01_1 h_obs01_1 0.051396 152.1458 head NaN
h_obs01_2 h_obs01_2 0.022156 0.0000 head NaN
h_obs02_1 h_obs02_1 0.046879 152.1458 head NaN
h_obs02_2 h_obs02_2 0.020853 0.0000 head NaN
h_obs03_1 h_obs03_1 0.036584 152.1458 head NaN

In [17]:
p.prior_information.head()


Out[17]:
equation obgnme pilbl weight
pilbl
mult1 1.0 * log(mult1) = 0.000000 regul_m mult1 1.0
kr01c01 1.0 * log(kr01c01) = 0.0 regul_p kr01c01 1.0
kr01c02 1.0 * log(kr01c02) = 0.0 regul_p kr01c02 1.0
kr01c03 1.0 * log(kr01c03) = 0.0 regul_p kr01c03 1.0
kr01c04 1.0 * log(kr01c04) = 0.0 regul_p kr01c04 1.0

The client-code can be used to change values in the dataframes that can be written to a new or updated control file using the write() method as shown at the end of the notebook.


In [18]:
p.parameter_data.loc['global_k', 'parval1'] = 225
p.parameter_data.head()


Out[18]:
parnme partrans parchglim parval1 parlbnd parubnd pargp scale offset dercom extra
parnme
global_k global_k log factor 225.0 150.00 250.00 m 1.0 0.0 1 NaN
mult1 mult1 log factor 1.0 0.75 1.25 m 1.0 0.0 1 NaN
mult2 mult2 log factor 1.0 0.50 2.00 m 1.0 0.0 1 NaN
kr01c01 kr01c01 log factor 1.0 0.10 10.00 p 1.0 0.0 1 NaN
kr01c02 kr01c02 log factor 1.0 0.10 10.00 p 1.0 0.0 1 NaN

A residual file (.rei or res) can also be passed to the resfile argument at instantiation to enable some simple residual analysis and evaluate if weight adjustments are needed. If resfile = False, or not supplied, and if the residual file is in the same directory as the pest control file and has the same base name, it will be accessed automatically:


In [19]:
p.res.head()


Out[19]:
name group measured modelled residual weight
name
h_obs01_1 h_obs01_1 head 0.051396 0.080402 -0.029006 152.1458
h_obs01_2 h_obs01_2 head 0.022156 0.036898 -0.014742 0.0000
h_obs02_1 h_obs02_1 head 0.046879 0.069121 -0.022241 152.1458
h_obs02_2 h_obs02_2 head 0.020853 0.034311 -0.013458 0.0000
h_obs03_1 h_obs03_1 head 0.036584 0.057722 -0.021138 152.1458

The weights can be updated by changing values in the observation dataframe.


In [20]:
p.observation_data.loc['h_obs01_1', 'weight'] = 25.0
p.observation_data.head()


Out[20]:
obsnme obsval weight obgnme extra
obsnme
h_obs01_1 h_obs01_1 0.051396 25.0000 head NaN
h_obs01_2 h_obs01_2 0.022156 0.0000 head NaN
h_obs02_1 h_obs02_1 0.046879 152.1458 head NaN
h_obs02_2 h_obs02_2 0.020853 0.0000 head NaN
h_obs03_1 h_obs03_1 0.036584 152.1458 head NaN

The Pst class exposes a method, get(), to create a new Pst instance with a subset of parameters and or observations. For example, make a new PEST control-file object using the first 10 entries from the parameter and observation dataframes. Note this method does not propogate prior information to the new instance:


In [21]:
pnew = p.get(p.par_names[:10],p.obs_names[:10])
pnew.prior_information.head()


Out[21]:
equation obgnme pilbl weight names
pilbl
mult1 1.0 * log(mult1) = 0.000000 regul_m mult1 1.0 [mult1]
kr01c01 1.0 * log(kr01c01) = 0.0 regul_p kr01c01 1.0 [kr01c01]
kr01c02 1.0 * log(kr01c02) = 0.0 regul_p kr01c02 1.0 [kr01c02]
kr01c03 1.0 * log(kr01c03) = 0.0 regul_p kr01c03 1.0 [kr01c03]
kr01c04 1.0 * log(kr01c04) = 0.0 regul_p kr01c04 1.0 [kr01c04]

Check the parameter_data and observation_data dataframes for the new object, note that the updated values for global_k parval1 and h_obs01_1 weight are in these dataframes.


In [22]:
pnew.parameter_data.head()


Out[22]:
parnme partrans parchglim parval1 parlbnd parubnd pargp scale offset dercom extra
parnme
global_k global_k log factor 225.0 150.00 250.00 m 1.0 0.0 1 NaN
mult1 mult1 log factor 1.0 0.75 1.25 m 1.0 0.0 1 NaN
mult2 mult2 log factor 1.0 0.50 2.00 m 1.0 0.0 1 NaN
kr01c01 kr01c01 log factor 1.0 0.10 10.00 p 1.0 0.0 1 NaN
kr01c02 kr01c02 log factor 1.0 0.10 10.00 p 1.0 0.0 1 NaN

In [23]:
pnew.observation_data.head()


Out[23]:
obsnme obsval weight obgnme extra
obsnme
h_obs01_1 h_obs01_1 0.051396 25.0000 head NaN
h_obs01_2 h_obs01_2 0.022156 0.0000 head NaN
h_obs02_1 h_obs02_1 0.046879 152.1458 head NaN
h_obs02_2 h_obs02_2 0.020853 0.0000 head NaN
h_obs03_1 h_obs03_1 0.036584 152.1458 head NaN

The write(filename) method allows you to write a PEST control file with the current state of the object: that is, make a new PEST control file with the current information contained in an object.


In [24]:
pnew.write("test.pst")