QUEFTS

Introduction

This chapter describes the QUEFTS model. QUEFTS stands for QUantitative Evaluation of the Fertility of Tropical Soils. It is a simple rule-based model to estimate crop yield given the amount of fertilizer applied, or to estimate the amount of fertilizer needed to reach a particular yield.

To run the model, you need soil and crop parameters, as well as the amount of fertilizer applied, and the attainable crop biomass production. In this context, the attainable production that is either the maximum production in the absence of nutrient limitation, or another (lower) target biomass production.

QUEFTS was first described in: B.H. Janssen, F.C.T. Guiking, D. van der Eijk, E.M.A. Smaling, J. Wolf and H.van Reuler. 1990. A system for quantitative evaluation of the fertility of tropical soils (QUEFTS). Geoderma 46: 299-318.

Requirements

We will use the R package Rquefts. You can install it from CRAN.

if (!require(Rquefts)) { install.packages("Rquefts") }
library(Rquefts)

Technical background

Potential supply of nutrients

The model uses soil properties to estimate the potential soil supply of nitrogen (N), phosphorus (P) and potassium (K). This is done based on relationships between chemical properties of the topsoil and the maximum quantity of those nutrients that can be taken up by the crop, if no other nutrients limiting growth. This amount is crop and soil specific and can be determined through experiments where all other nutrients are in ample supply. The target nutrient thus becomes the most limiting, and the maximum soil supply of that nutrient can then be estimated from the crop’s nutrient uptake, and related to soil properties such as pH, and organic carbon content (Janssen et al., 1990). The equations used by QUEFTS to calculate the potential supplies of nutrients from soil properties were derived from empirical data of field trials in Suriname and Kenya. They are thought to be applicable to well drained, deep soils, that have a pH(H20) in the range 4.5-7.0, and values for organic carbon below 70 g/kg, P-Olsen below 30 mg/kg and exchangeable potassium below 30 mmol/kg.

Let’s have a look at this relationship. Below we compute the nutrient supply for a range of pH values, and given different levels for the other chemical properties.

library(Rquefts)
# range of pH values
ph <- seq(4, 8, 1)
s <- nutSupply(ph, SOC=1.5, Kex=1, Polsen=1)
cbind(ph, s)
##      ph N_base_supply P_base_supply K_base_supply
## [1,]  4          2.55       -0.0250     134.32836
## [2,]  5          5.10        0.7625     104.47761
## [3,]  6          7.65        1.0250      74.62687
## [4,]  7         10.20        0.7625      44.77612
## [5,]  8         12.75       -0.0250      14.92537

With a more detailed range of pH values, to make plots.

ph <- seq(4, 8, 0.25)
# three combinations
s1 <- nutSupply(ph, SOC=15, Kex=5, Polsen=5)
s2 <- nutSupply(ph, SOC=30, Kex=10, Polsen=10)
s3 <- nutSupply(ph, SOC=45, Kex=20, Polsen=20)

Now plots of the effect of pH on N, P and K supply, for three situations (low, medium and high levels of organic matter, exchangeble K, and Olsen-P).

par(mfrow=c(1,3))
for (supply in c("N_base_supply", "P_base_supply", "K_base_supply")) {
  plot(ph, s3[,supply], xlab="pH", ylab=paste(supply, "(kg/ha)"), type="l", ylim=c(0,max(s3[,supply])))
  lines(ph, s2[,supply])
  lines(ph, s1[,supply])
}

image0

This shows that P-supply is highest at a pH=6, for any Olsen-P level. N-supply decreases with pH (given the amount of soil Carbon), and K-supply increases with pH (given the amount of exchangeble K).

Now let’s look at nutrient supply for varying levels of Soil Carbon.

# unit is g/kg
om <- seq(0, 70, 5)
# three combinations
s1 <- nutSupply(pH=5.5, SOC=om, Kex=5, Polsen=5)
s2 <- nutSupply(pH=5.5, SOC=om, Kex=10, Polsen=10)
s3 <- nutSupply(pH=5.5, SOC=om, Kex=20, Polsen=20)
# and plot
par(mfrow=c(1,3))
for (supply in c("N_base_supply", "P_base_supply", "K_base_supply")) {
  plot(om, s3[,supply], xlab="Soil Carbon (g/kg)", ylab=paste(supply, "(kg/ha)"), type="l", ylim=c(0,max(s3[,supply])))
  lines(om, s2[,supply])
  lines(om, s1[,supply])
}

image1

This shows that organic carbon has a strong positive effect on available N and P, but a negative effect on K availability (given the amount of exchangable K).

We can assume that these relationships are robust, at least for the conditions that they were designed for (pH(H20) 4.5-7.0, organic carbon below 70 g/kg, P-Olsen below 30 mg/kg and exchangeable potassium below 30 mmol/kg). But with more data, they could be re-estimated, also for other conditions.

Actual nutrient uptake

The actual uptake of each nutrient is estimated as a function of the potential soil supply of that nutrient, taking into account the potential soil supply of the other two nutrients, and the maximum nutrient concentrations in the vegetative and generative organs of the crop.

The estimated uptake of nitrogen, phosphorus, and potassium is used to estimated biomass production ranges for each nutrient. For each pair of nutrients two yield estimates are calculated. For example the yield for the actual N uptake is computed dependent on the yield range for the P uptake, and that for the actual P uptake in dependent on the yield range for the N uptake. This leads to six combinations describing the uptake of one nutrient given maximum dilution or accumulation of another.

The nutrient-limited yield is then estimated by averaging the six yield estimates for paired nutrients. An estimate based on two nutrients may not exceed the upper limit of the yield range of the third nutrient, that is, the concentration of a nutrient can not be lower than its maximum dilution level. Neither may the yield estimates exceed the spacified maximum (attainable) production.

TO DO: illustrate the procedure numerically and graphically

TO DO: discuss limitations based on the Sattari et al paper

Create and run a model

The first step is to create a model. You can create a model with default parameters like this

library(Rquefts)
q <- quefts()

And then run the model

run(q)
##   leaf_lim   stem_lim  store_lim   N_supply   P_supply   K_supply
## 1928.19667 1928.19667 3756.94929   90.00000   11.00000   90.00000
##   N_uptake   P_uptake   K_uptake      N_gap      P_gap      K_gap
##   81.66872   10.88951   77.38765   60.00000  205.00000    0.00000

You can also initialize a model with parameters of you choice

soiltype <- quefts_soil()
barley <- quefts_crop('Barley')
fertilizer <- list(N=50, P=0, K=0)
biomass <- list(leaf_att=2200, stem_att=2700, store_att=4800, SeasonLength=110)
q <- quefts(soiltype, barley, fertilizer, biomass)

TODO discuss parameters

If the crop has biological nitrogen fixation (in symbiosis with Rhizobium) you can use the NFIX parameter to determine the fraction of the total nitrogen supply that is supplied by biological fixation. Note that NFIX is a constant, while in reality nitrogen fixation is amongst other things dependent on the amount of mineral nitrogen available in the soil.

Explore

library(Rquefts)
rice <- quefts_crop("Rice")
q <- quefts(crop=rice)
q$leaf_att <- 2651
q$stem_att <- 5053
q$store_att <- 8208
fert(q) <- list(N=0, P=0, K=0)
N <- seq(1, 200, 10)
results <- matrix(nrow=length(N), ncol=12)
colnames(results) <- names(run(q))
for (i in 1:length(N)) {
    q["N"] <- N[i]
    results[i,] <- run(q)
}
yield <- results[,"store_lim"]
par(mfrow=c(1,2))
plot(N, yield, type="l", lwd=2)
Efficiency = yield / N
plot(N, Efficiency, type="l", lwd=2)

image2

f <- expand.grid(N=seq(0,400,10), P=seq(0,400,10), K=c(0,200))
x <- rep(NA, nrow(f))
for (i in 1:nrow(f)) {0
    q$N <- f$N[i]
    q$P <- f$P[i]
    q$K <- f$K[i]
    x[i] <- run(q)["store_lim"]
}
x <- cbind(f, x)
# display results
library(raster  )
r0 <- rasterFromXYZ(x[x$K==0, -3])
r200 <- rasterFromXYZ(x[x$K==200, -3])
par(mfrow=c(1,2))
plot(r0, xlab="N fertilizer", ylab="P fertilizer", las=1, main="K=0")
contour(r0, add=TRUE)
plot(r200, xlab="N fertilizer", ylab="P fertilizer", las=1, main="K=200")
contour(r200, add=TRUE)

image3

#plot(stack(r0, r200))

Callibration

Here I illustrate some basic approaches that can be used for callibration. First I generate some data.

set.seed(99)
yldfun <- function(N, noise=TRUE) { 1000 +  500* log(N+1)/3 + noise * runif(length(N), -500, 500) }
N <- seq(0,300,25)
Y <- replicate(10, yldfun(N))
obs <- cbind(N, Y)

We will use Root Mean Square Error (RMSE) to assess model fit.

RMSE <- function(obs, pred) sqrt(mean((obs - pred)^2))

Let’s see how good the default parameters work for us.

q <- quefts()
q$P <- 0
q$K <- 0
results <- matrix(nrow=length(N), ncol=12)
colnames(results) <- names(run(q))
for (i in 1:length(N)) {
    q$N <- N[i]
    results[i,] <- run(q)
}
yield <- results[,'store_lim']
RMSE(obs[,-1], yield)
## [1] 1868.019

The RMSE is quite high.

Create a function to minimize with the optimizer. Here I try to improve the model by altering four parameters.

vars <- c('soil$N_base_supply', 'soil$N_recovery', 'crop$NminStore', 'crop$NmaxStore')
f <- function(p) {
    if (any (p < 0)) return(Inf)
    if (p['crop$NminStore'] >= p['crop$NmaxStore']) return(Inf)
    if (p['soil$N_recovery'] > .6 | p['soil$N_recovery'] < .3) return(Inf)
    q[names(p)] <- p
    pred <- run(q)
    RMSE(obs[,-1], pred['store_lim'])
}
# try the function with some initial values
x <- c(50, 0.5,  0.011, 0.035)
names(x) <- vars
f(x)
## [1] 1573.818

Now optimize

par <- optim(x, f)
# RMSE
par$value
## [1] 1478.384
# optimal parameters
par$par
## soil$N_base_supply    soil$N_recovery     crop$NminStore
##         0.01398939         0.59768399        26.37808069
##     crop$NmaxStore
##        37.05140516