Git Product home page Git Product logo

maize-toff's Introduction

maize-Toff

DOI

Repository for ecohydrological modeling analysis of maize yield variability and tradeoffs between yield and crop failure. See article by Krell et al. (2021) "Consequences of dryland maize planting decisions under increased seasonal rainfall variability" in Water Resources Research.

Set-up

Create fork of maize-Toff and git clone to local machine.

  1. cd maize-Toff
  2. conda env create -f environment.yml -n maize-Toff
  3. conda activate maize-Toff
  4. jupyter notebook

Note: To update dependecies in an existing environment, use conda env update --file environment.yml after step four.

Directory structure

farm/

  • where models are stored

data/

  • contains CETRAD rainfall data, maize variety info

output/

  • exported figures, results

notebooks/

  • contains notebook that generates figures from the manuscript

How to use this model

Generate a Soil object

The soil object contains all the necessary parameters to specify soil hydraulic properties and texture. The easiest way to generate a soil instance is to use one of the standard USDA soil texture types: Sand, Loamy Sand, Sandy Loam, Silt Loam, Loam, Sandy Clay Loam, Clay Loam, Sandy Clay, Silty Clay, Clay, and Sandy Silty Loamy Clay. Just kidding. That last one is totally not a soil type.

# Creates a sand soil object:
soil = Soil('sand') 

You can also specify your own parameters:

param_dict = {
    'b': 11.4,
    'Psi_S_cm': 40.5,   # saturated water tension, cm
    'Psi_l_cm': 24.3,   # leakage water tension, cm
    'n': 0.482,         # porosity, cm^3/cm^3 (is Psi_S) in C&H,
    'Ks': 0.0077,   # saturated hydraulic conductivity, cm/min
    'S': 0.268      # sorptivity, cm/min^1/2  
}

custom_soil = Soil(params=param_dict)

Of the parameters, only b, Psi_S_cm, Psi_l_cm, n, and Ks are required. The model doesn't use S right now.

Generate a Climate object

The climate object has information on maximum evapotranspiration, as well as a time series of rainfall. The rainfall timeseries is generated stochastically using the parameters of storm depth, frequency, and the length of the season. The storm depth (alpha_r) specifies the average daily storm depth, assuming that daily storm depths are drawn from an exponential distribution. The frequency (lambda_r) is best described as the daily probability of rainfall. The length of the season ends up setting the timescale of the simulation in days.

There are several keyword arguments available:

  • alpha_r Average storm depth [mm]
  • lambda_r Frequency of storms [day^-1]
  • t_seas Length of rainy season [days]
  • ET_max Maximum evapotranspiration [mm/day]

Keyword arguments can be specified when instantiating the object, or the following are the resonable defaults values:

climate = Climate(
    alpha_r=10.0,
    lambda_r=0.3,
    t_seas=180,
    ET_max=6.5)

Generate a Plant

The plant object requires the most parameters to generate, and some of these parameters depend on the soil in which the plant is growing. In addition, the plant object can be subclassed into specific plant types to allow for varying structures and function. In this simulation, we are using the Crop subclass, which is initialized with the minimum following parameters:

  • kc_max The maximum crop coefficient [dimensionless], which is a scale factor applied to ET_max to determine the maximum rate of plant transpiration, T_max.
  • LAI_max The maximum crop leaf area [m^2/m^2].
  • T_max The maximum rate of crop water use in [mm/day]
  • soil A soil object that specifies the soil that this crop is growing in.
crop = Crop(kc_max=1.2, LAI_max=2.0, T_max=4.0, soil=soil)

Additional parameters that should be specified are:

  • Zr Plant rooting depth [mm]. Default is 500mm.
  • sw_MPa Plant wilting point [MPa]. Default is -1.5MPa
  • s_star_MPa Water potential of maximum water use [MPa]. Default is -0.2 MPa.

If these parameters are not provided, default values are inherited from the Plant class.

Initialize and run the model

Combining the prior three steps, we can create a model instance and run the model:

# Import the necessary objects:
from farm.climate import Climate
from farm.soil import Soil
from farm.plant import Crop
from farm.model import CropModel

# Make the things
climate = Climate() # uses default climate values
soil = Soil('sand')
crop = Crop(kc_max=1.2, LAI_max=2.0, T_max=4.0, soil=soil)

# Create the model
model = CropModel(crop=crop,soil=soil,climate=climate)

# RUN IT.
model.run() # TADA!

model.output()

Getting model output

The model.output() function returns all the simulation output structured as a single pandas DataFrame. The frame has the following columns:

  • kc Time series of daily crop coefficients.
  • LAI Time series of crop LAI
  • R Time series of daily rainfall [mm]
  • s Time series of daily relative soil moisture [0-1]
  • I Time series of daily interception [mm]
  • E Time series of daily soil evaporation [mm]
  • T Time series of daily plant transpiration [mm]
  • L Time series of soil leakage loss [mm]
  • Q Time series of surface runoff [mm]
  • dsdt Time series of changing relative soil moisture

To plot any of this data, simply use the .plot() command:

output = model.output()

# Plots a time series of simulated evapotranspiration:
output['ET'].plot()

# Plots a time series of simulated relative soil moisture
output['s'].plot()

Test the model

Basic testing framework

From the root of the directory, run the following in the command line to put farm into your python path in editable mode.

pip install -e .

After that, run tests from the subdirectory farm/tests, for example:

nosetests -vv test_sand

Checking test coverage

Update the coverage for the model before pushing new commits, or in advance of a pull request. You can update the coverage using the following command:

nosetests -vv ./farm/tests --with-coverage --cover-package=farm --cover-html --cover-html-dir=coverage_html_report/

maize-toff's People

Contributors

brynemorgan avatar kcaylor avatar noah-de avatar ntkrell avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

maize-toff's Issues

Calculate leakage terms

Once kinks are sorted for evaporation and relative soil moisture calculations, add correct Leakage equations to calc_L in Soil.

Currently calc_L and calc_Q are the same equations

Write a function for crop choice

Need days to maturity (t_seas) to be a continuous function based on varieties available in Kenya: 70-210 days.

NB: Will also need to think about how our model is a post-germination model and the days to maturity listed on seed packets includes how long it takes to germinate.

Assign reasonable defaults to classes?

@Ntkrell and @noah-de were thinking of taking the kwargs dictionary from runmodel.py and coding them in as default kwargs in the respective models (Crop, Climate & Soil).
Do you have any opinion on this @kcaylor?

In a collaborative coding session, we saw that this enables rapid setup of running multiple simulations.

Should the length of this output be 27?

@kcaylor Maybe you can see the issue more clearly just by looking at it than I can. Why is the output below only 27 days long? I think this has to do with the average_soil_moisture() calculation which is returning a length of 27.

# Part 1. Set conditions for IC runs
n_sim = 500 # change back to 1000 later
planting_date = 1 # Because we want a date with little rainfall

# Part 2. Initialize model with a climate, soil and crop
climate = Climate(station='OL JOGI FARM')
soil = Soil(texture='loam')
crop = Crop(soil=soil, lgp=180)
soil.set_nZr(crop)

model = CropModel(crop=crop, climate=climate, soil=soil)
model.run()
o = model.output() # The output of this length is 208 which is fine.

# Part 3. Get the mean, SD soil moisture and run the simulations to remove IC
s0_mean, s0_std = average_soil_moisture(model, n_sims=n_sim, doy=planting_date)
models = [CropModel(crop=crop, climate=Climate(), soil=soil) for i in np.arange(n_sim)]

# Part 4. Select the planting date we want. 
planting_date = 100

# Part 5: Run the actual simulations
output = [model.run(s0=s0_mean, do_output=True, planting_date=planting_date) for model in models]
output

     kc   LAI    stress          R         s         E        ET         T  \
 0   0.3  0.75  0.211090   0.000000  0.497572  0.881416  1.530082  0.648666   
 1   0.3  0.75  0.231681   0.000000  0.490787  0.846817  1.469218  0.622401   
 2   0.3  0.75  0.252356   0.000000  0.484271  0.814032  1.411212  0.597180   
 3   0.3  0.75  0.273046   0.000000  0.478013  0.782951  1.355905  0.572955   
 4   0.3  0.75  0.293693   0.000000  0.472000  0.753469  1.303148  0.549679   
 5   0.3  0.75  0.314245  34.984921  0.466221  0.725493  1.252802  0.527309   
 6   0.3  0.75  0.006089   4.667907  0.615810  1.553404  2.659763  1.106360   
 7   0.3  0.75  0.002431   0.000000  0.624715  1.608944  2.749776  1.140832   
 8   0.3  0.75  0.007858   0.000000  0.612521  1.533057  2.626686  1.093629   
 9   0.3  0.75  0.015931   1.421261  0.600872  1.461717  2.510255  1.048539   
 10  0.3  0.75  0.020106   0.000000  0.596043  1.432474  2.462318  1.029845   
 11  0.3  0.75  0.031336   0.000000  0.585124  1.367083  2.354660  0.987576   
 12  0.3  0.75  0.044396   0.000000  0.574682  1.305512  2.252668  0.947156   
 13  0.3  0.75  0.059014   0.000000  0.564692  1.247500  2.155986  0.908486   
 14  0.3  0.75  0.074950   0.000000  0.555131  1.192806  2.064283  0.871476   
 15  0.3  0.75  0.091991   0.026773  0.545977  1.141211  1.977251  0.836040   
 16  0.3  0.75  0.109694   0.000000  0.537328  1.093164  1.895722  0.802558   
 17  0.3  0.75  0.128393   0.000000  0.528921  1.047130  1.817146  0.770016   
 18  0.3  0.75  0.147698   0.000000  0.520862  1.003629  1.742451  0.738823   
 19  0.3  0.75  0.167478   0.000000  0.513135  0.962497  1.671409  0.708911   
 20  0.3  0.75  0.187619   0.000000  0.505723  0.923586  1.603806  0.680220   
 21  0.6  0.75  0.208021   0.000000  0.498611  0.886755  1.539443  0.652688   
 22  0.6  0.75  0.228594   0.000000  0.491784  0.851875  1.478137  0.626262   
 23  0.6  0.75  0.249260   0.000000  0.485229  0.818826  1.419714  0.600888   
 24  0.6  0.75  0.269952   0.000000  0.478934  0.787496  1.364013  0.576517   
 25  0.6  0.75  0.290609   0.000000  0.472885  0.757782  1.310884  0.553102   
 26  0.6  0.75  0.311178   0.000000  0.467072  0.729586  1.260186  0.530600   
 27  0.6  0.75  0.331616   0.000000  0.461483  0.702819  1.211786  0.508967   
 
       L       dsdt  dos  doy  
 0   0.0  -1.530082  0.0   79  
 1   0.0  -1.469218  0.0   80  
 2   0.0  -1.411212  0.0   81  
 3   0.0  -1.355905  0.0   82  
 4   0.0  -1.303148  0.0   83  
 5   0.0  33.732119  0.0   84  
 6   0.0   2.008144  0.0   85  
 7   0.0  -2.749776  0.0   86  
 8   0.0  -2.626686  0.0   87  
 9   0.0  -1.088995  0.0   88  
 10  0.0  -2.462318  0.0   89  
 11  0.0  -2.354660  0.0   90  
 12  0.0  -2.252668  0.0   91  
 13  0.0  -2.155986  0.0   92  
 14  0.0  -2.064283  0.0   93  
 15  0.0  -1.950478  0.0   94  
 16  0.0  -1.895722  0.0   95  
 17  0.0  -1.817146  0.0   96  
 18  0.0  -1.742451  0.0   97  
 19  0.0  -1.671409  0.0   98  
 20  0.0  -1.603806  0.0   99  
 21  0.0  -1.539443  1.0  100  
 22  0.0  -1.478137  2.0  101  
 23  0.0  -1.419714  3.0  102  
 24  0.0  -1.364013  4.0  103  
 25  0.0  -1.310884  5.0  104  
 26  0.0  -1.260186  6.0  105  
 27  0.0  -1.211786  7.0  106  ,
      kc   LAI    stress          R         s         E        ET         T  \
 0   0.3  0.75  0.211090   1.964480  0.497572  0.881416  1.530082  0.648666   
 1   0.3  0.75  0.205418   0.000000  0.499498  0.891322  1.547445  0.656123   
 2   0.3  0.75  0.225974  12.188593  0.492636  0.856201  1.485761  0.629559   

...etc

Question: I think the problem is in the average_soil_moisture() function: shouldn't it return a year's worth of soil moisture? Currently line 177 in functions.py prevents us from doing so: output = [ models[i].run(do_output=True, planting_date=doy+1, t_before=t_before, t_after=0) for i in np.arange(n_sims) ]

Validate water potential of maximum transpiration [MPa] in Plant

Originally we used -0.033 for s_star_MPa in Plant(), cf. Laio et al., 2001b, p.713 because that's what they use. However our s_star for loamy soil is different from theirs according to Figure 6 on p. 713. So we'll use a number below that (e.g. -0.05) until we figure out the discrepancy.

Ensemble simulations

The Model object represents a single simulation, and Model.output() returns a single pandas DataFrame with columns for each simulated variable and rows for each day of simulation. We need to be able to run many simulations (with different climate.rainfall realizations) in order to get ensemble information on crop performance and water balance within a stochastic climate.

What is the strategy to aggregate multiple runs of a model with different climates?

Add initial conditions calculator as a subclass of model

We need a function that quickly calculates the initial condition for each soil type and each climatology.

Right now we have functions for this in both the notebook notebooks/results.ipynb and the script scripts/initialconditions.py but it doesn't run very quickly and may be incorrect.

Create notebooks folder

I prefer we put jupyter notebooks in a separate folder, notebooks/, which can be in the project root directory. This will keep them out of our way when working on the python model code.

Make average soil moisture based on the t_before, not doy.

Something for Natasha to think about and can proceed in the mean time without totally fixing this:

Right now there is a disconnect in that the average soil moisture is calculated for the doy and not when the model starts which is determined by t_before. Need IC to be for t_before.

Some options:
* Have some initial function.. model.initialize(โ€ฆ) to globally set t_before, t_after.
* Or can import t_before from model object (this is probably bad).

Write function for t_sfc

Write a Soil function to generate t_sfc for when soil moisture goes above soil field capacity.

runmodel errors with soils other than 'sand'

When running the model with the code of runmodel.py (or similar), there is a value error with 'loam', 'clay' and a few others soil types that I have tried.

The output is helpful:

ValueError: soil field capacity, 0.5057698492600363 is larger than porosity, 0.451

and it even indicates the location of the error:

File "...soil.py", line 190, in __init__ n=self.n

Not sure if this is intentional.

The error message does not appear when using 'sand' as the soil type.

potential for overflow...

When setting up a simulation in an ipython session, I encountered the following:

In [7]: model.run()  
/home/user/Projects/maize-Toff/farm/climate.py:54: RuntimeWarning: overflow encountered in double_scalars
  return pow((s-soil.sh)/(1-soil.sh), q)*E_max

However, on a subsequent simulation, there was no error.

Also. We are noting that on this run:

In [5]: soil.set_nZr(crop)                                                                                               
Out[5]: 225.5

Why do we need planting date in two places in `model.py`?

Currently planting_date is set in two places in model.py. I played around with removing it from init but that didn't seem right. Additionally, as-is model.py has some problems:

  • The model seems happy when you set the planting date as 75 (example 1) but unhappy when planting date is 2 (example 2) or 200 (example 3), for example.
  • It looks like the model always ends on day 208, this should change depending on the planting date.

Here's the code I was playing with:

Start by importing packages and objects

# import packages and set working directory
import numpy as np
import matplotlib.pyplot as plt
import os
from math import exp
import pandas as pd
import sys

# We need to add the module path to our system path so 
# so that our notebook can find our local objects and code:
module_path = os.path.abspath(os.path.join('..'))
if module_path not in sys.path:
    sys.path.append(module_path)
   
# import objects
from farm import Climate
from farm import Soil
from farm import Crop
from farm import CropModel
from farm.make_climate_parameters import make_climate_parameters

Example 1

alpha_r, lambda_r = make_climate_parameters()

climate = Climate(alpha_r, lambda_r)
soil = Soil('loam')
crop = Crop(soil=soil)
soil.set_nZr(crop)  
model = CropModel(crop=crop,soil=soil,climate=climate)

model.run(planting_date=75)

o = model.output()
o['kc'].plot()
o

Here the DOY starts on Julian day 54. This makes sense given the calculation: planting date (75) - t_before (21). The model is ending on day 208 (zero-indexed) whereas it should end on day 262 (calculated by doy_end = 75 + 180 + 7).
image

Example 2

alpha_r, lambda_r = make_climate_parameters()

climate = Climate(alpha_r, lambda_r)
soil = Soil('loam')
crop = Crop(soil=soil)
soil.set_nZr(crop)  
model = CropModel(crop=crop,soil=soil,climate=climate)

model.run(planting_date=2)

Get the error:

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-2-9a2956d9a135> in <module>
      7 model = CropModel(crop=crop,soil=soil,climate=climate)
      8 
----> 9 model.run(planting_date=2)
     10 
     11 o = model.output()

~/Box Sync/waves/maize-Toff/farm/model.py in run(self, s0, planting_date, t_before, t_after)
    123 
    124         for t in range(self.n_days):
--> 125             self.R[t] = self.climate.rainfall[doy[t]-1]
    126 
    127         self.doy = doy

IndexError: index 0 is out of bounds for axis 0 with size 0

Example 3

alpha_r, lambda_r = make_climate_parameters()

climate = Climate(alpha_r, lambda_r)
soil = Soil('loam')
crop = Crop(soil=soil)
soil.set_nZr(crop)  
model = CropModel(crop=crop,soil=soil,climate=climate)

model.run(planting_date=200)

o = model.output()
o['kc'].plot()
o

Here it looks like the plant never starts to grow. dos never switches to "1" and we can see that the crop coefficient is always 0.3.
image

Consider moving Soil variables out of soil.py

These variables are hard coded into the soil.py file.

rho = 1000 # density of water in kg/m^3
g = 9.8 # acceleration of gravity in m/s^2
PRECISION = 2 # Number of decimal places of precision in calculations (default is 2)

Perhaps we could move the declaration to a higher level (the model or parameters of the model).

Dynamic water use parameters

Right now, kc is a constant value throughout the season. Need to edit Crop.calc_kc and Crop.calc_LAI to allow crop coefficients to change in accordance to crop phenology.

Options:

  1. We might want to subclass Crop for different crops to allow for different calc_kc and calc_LAI functions.

Standardize naming conventions across all objects, variables, etc.

A little inspiration from Drew Gower's naming conventions (he thought they were weird but I think it makes sense):

objects are four letters long and in lower case, variables are eight lower case letters separated in the middle by an underscore, and constants are eight upper case letters separated by an underscore

Check out python's style guide.

Once the model is done I can go back and standardize everything. Not a high priority item at the moment though.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.