#knitr::opts_chunk$set(fig.width=8, fig.height=6)
#options(width = 85)

1. Overview

The ABM model predicts conversion of organic material in animal manure or other (high-moisture) organic wastes to methane (CH~4~), carbon dioxide (CO~2~) and ammonia (NH~3~) under anaerobic conditions. The name comes from anaerobic biodegradation model. With multiple methanogen groups and a single sulfate reducer group and group-specific parameters describing kinetics and yield, the model can predict realistic short- and long-term responses to temperature change and other perturbations. Although it was prediction of CH~4~ emission from stored animal slurry (liquid manure) in unheated channels or tanks that prompted development of the model, the model can be used to simulate CH~4~ emission or biogas production from other organic waste under a range of conditions, including in anaerobic digesters. The purpose of this document is to demonstrate the use of the ABM R package, which is a flexible implementation of the model. Details on the model will be available in publications in the near future.

2. Installation

The ABM package is available on GitHub and so can be installed with the install_github() function from the devtools package, which must be installed first. These steps should be carried out once to install both packages:

install.packages('devtools')
devtools::install_github('AU-BCE-EE/ABM', build_vignettes = T)

And to use the ABM model, the package must be loaded.

library(ABM)

And to view this vignette, use:

vignette('ABM_start')

3. A demonstration of pig farm emission simulation

A demonstration is presented in this section to show what the abm() function can do. For a more incremental introduction to the function, see the following sections.

First we will start with a demonstration using the default input parameters, which reflect manure production in a pig-house with 1000 grower-finisher pigs. In-house slurry pits are 0.6 m deep and the slurry is flushed out of the pig-house every 6 weeks, leaving 5 cm of residual slurry inside the in-house slurry pits. The slurry temperature is 20&degC. The amount of degradable VS content of raw pig manure is set to 75 gCOD/kg manure (feces + urine).

To predict in-house manure methane emission we can make the following call, specifying that we want to run the model for 365 days. We start using parameter sets (v1.0) from Dalby et al. 2023, where substrate input is degradable VS.

out0 <- abm(days = 365, man_pars = man_pars1.0, grp_pars = grp_pars1.0)

Lets take a look at the output of microbial groups methanogens m0, m1, m2 and sulfate reducer group sr1.

line_colors <- c('red', 'blue', 'green','orange')
matplot(out0$time, out0[, nn <- c('m0','m1','m2','sr1')]/1000, 
        type = 'l', lty = c(1:length(nn)), col = line_colors, xlab = 'Time (days from 1. Jan)', 
        ylab = 'Microbial biomass (kg)')
legend("topright", legend = nn, lty = c(1:length(nn)), col = line_colors, lwd = 1,
       title = "Microbial biomass", cex = 0.8)

matplot(out0$time, out0[, nn <- c('CH4_emis_rate', 'CO2_emis_rate', 'NH3_emis_rate')]/1000, 
        type = 'l', lty = c(1:length(nn)), col = line_colors, xlab = 'Time (days from 1. Jan)', 
        ylab = 'Emission rate (kg/day)')
legend("topright", legend = nn, lty = c(1:length(nn)), col = line_colors, lwd = 1,
       title = "Emission rates", cex = 0.8)

Methane and carbon dioxide emission rates follow a clear pattern correlated with the amount of slurry in the slurry pits, which is flushed out every 28 days.

So how much methane is produced per pig per year in-house? We can use the cumulative methane output and the fact that slurry production rate default is for 1000 pigs. For ammonia we might want to have emissions per production area. Since we assume that the floor is fully slatted, we can assume that the production area is equal to the slurry surface area.

pigs <- 1000
CH4_kg_pr_pig_pr_year <- max(out0$CH4_emis_cum)/1000/pigs 
CH4_kg_pr_pig_pr_year
CO2_kg_pr_pig_pr_year <- max(out0$CO2_emis_cum)/1000/pigs 
CO2_kg_pr_pig_pr_year
NH3_kg_pr_pig_pr_year <- max(out0$NH3_emis_cum)/1000/out0$area[1]
NH3_kg_pr_pig_pr_year
C_ratio <- (CH4_kg_pr_pig_pr_year * 12/16) / (CO2_kg_pr_pig_pr_year * 12/44 + CH4_kg_pr_pig_pr_year * 12/16)
C_ratio

around 34% of carbon emission is methane, suggesting that a lot of carbon dioxide comes from non-methanogenic processes (urea hydrolysis and surface respiration).

We can continue our calculations by doing another simulation of emissions coming from the outside slurry tank, by using the effluents from the in-house simulation as input for our storage simulation. To do this we extract the effluent slurry masses and pass to the new simulation as a data frame using the add_pars argument. First, while we could extract the rows of effluent data from out0 by finding those that have slurry_mass_eff > 0 it is easier to just run the simulation again and specify that we want effluent results with value = "eff":

out0e <- abm(days = 365, man_pars = man_pars1.0, grp_pars = grp_pars1.0, value = 'eff')

With add_pars we can change the default input parameters. In the outside storage, the temperature will also changes considerably over the year, so temperature will also be passed as a data frame. We will get our temperature data from another file. Since some VS was degraded in the barn we have to change the VS in the manure going to the outside storage also.

slurry_mass_dat <- data.frame(time = out0e$time, slurry_mass = out0e$slurry_mass_eff_cum)
slurry_mass_dat
VSd_fresh <- mean(out0e$VSd_eff_conc)
VSd_fresh
temp_dat <- as.data.frame(outside_slurry_temp_NIR)
temp_dat

Lets look at temperature and slurry data before we continue.

plot(slurry_mass_dat$time, slurry_mass_dat$slurry_mass, ylab = 'Slurry_mass (kg)', 
     xlab = 'Time (days from 1. Jan)')
plot(temp_dat$time, temp_dat$temp_C, ylab = 'Temperature (deg C)', 
     xlab = 'Time (days from 1. Jan)')

We need to adjust dimensions of outside slurry storage (height = 4 m, surface area = 1000), which we can do with add_pars.

out1 <- abm(365, man_pars = man_pars1.0, grp_pars = grp_pars1.0, 
            add_pars = list(storage_depth = 4, area = 1000, floor_area = 0, 
                            slurry_mass = slurry_mass_dat, temp_C = temp_dat, 
                            conc_fresh.VSd = VSd_fresh, conc_fresh.urea = 0,
                            wash_water = NA))

The addition of wash_water = NA is only to avoid a warning. Alternatively, the complete mng_pars argument can be specified. When a variable slurry mass is used as input, the wash_water needs to be passed through the slurry_mass data frame as well, but here, in outside storage, there is no washing after emptying.

Clearly emissions peak during the summer period, due to higher methanogenesis activity.

plot(CH4_emis_rate ~ time, data = out1, type = 'l', xlab = 'Time (d)', 
     ylab = 'CH4 emission rate (g/d)')
plot(CH4_emis_cum ~ time, data = out1, type = 'l', xlab = 'Time (d)', 
     ylab = 'CH4 cumulative emission (g)')

We can even look at the VFA dynamics during the year.

plot(VFA_conc ~ time, data = out1, type = 'l', xlab = 'Time (d)', 
     ylab = 'VFA conc. (g/kg)')

Because we would ideally like a stabilization period, we can run the model for three years and take a look only at the last year by using the startup argument. Furthermore, we can also focus on total emission in the last year (value = "summ").

out1a <- abm(365, man_pars = man_pars1.0, grp_pars = grp_pars1.0, 
             add_pars = list(storage_depth = 4, area = 1000, 
                             floor_area = 0, slurry_mass = slurry_mass_dat, 
                             temp_C = temp_dat, conc_fresh.VSd = VSd_fresh, 
                             conc_fresh.urea = 0, wash_water = NA), 
             startup = 2, value = 'summ')
out1a

Results show total annual emission from the outside storage of about 5.5 kg CH~4 per pig (CH4_emis_cum / 1000 pigs / 1000 g pr kg).

How much might acidification reduce emission? We can easily make a comparison to a scenario with pH reduced to 5.5, with everything else the same. Slurry pH indirectly affects methane by increasing the concentration of toxic H~2~S and protonated (uncharged) VFAs.

out1b <- abm(365, man_pars = man_pars1.0, grp_pars = grp_pars1.0, 
             add_pars = list(storage_depth = 4, area = 1000, 
                             floor_area = 0, slurry_mass = slurry_mass_dat, 
                             temp_C = temp_dat, conc_fresh.VSd = VSd_fresh, 
                             conc_fresh.urea = 0, wash_water = NA, pH = 5.5), 
             startup = 2, value = 'summ')
out1b

The predicted reduction is 91%.

1 - out1b['CH4_emis_cum'] / out1a['CH4_emis_cum']

4. Setting parameter values

Although the ABM model is relatively simple, explicitly simulating the activity of multiple microbial groups means there are many parameters. For convenience in setting up simulations and to facilitate reproducibility, parameters are organized in groups and saved as numbered versions as data objects that are included with the package. A look at the help files or argument list shows how parameters are grouped and shows default sets.

?abm
args(abm)

Here it is also seen what parameter sets are used. Currently v1.0 and v2.0 are available and the default parameter set is v2.0. Simply display parameter objects to check values. Microbial parameters are in grp_pars and mic_pars.

grp_pars2.0
mic_pars2.0

Parameters are grouped to make changes easier (and to prevent input mistakes) and to limit the number of parameter names that are needed. The mng_pars argument contains parameters related to management; man_pars describes the incoming manure or feed; grp_pars, the most extensive argument, is used to define the microbial groups; mic_pars contains other microbial parameters that do not vary among groups; chem_pars sets some chemical/physical parameters; arrh_pars sets kinetic parameters controlling hydrolysis rates of different organic matter components. Values in arrh_pars are used when instead of specifying substrate input as degradable volatile solids, we can specify: crude protein that is quickly degraded (CPf), crude protein that is slowly degraded, crude fats (Cfat), residual degradable fiber (RFd), starch and sugars (starch), and even more components. In that case VSd should be set to 0. Values in init_pars are the initial concentrations of organic matter components in the storage, which by default is equal to the concentration in the excreted manure, but it can be changed here.

There are also some built-in shortcuts to make small tweaks simpler than defining complete parameter objects. In particular, the add_pars argument makes life easy (and was used in the demonstration above). The argument can be used to add or change variables normally given in specific parameter arguments. For example, perhaps all the elements in the default mng_pars argument are correct for a particular case except for the slurry production rate, which should be 8000 kg/d.

args(abm)

We can use a different value with add_pars.

out2a <- abm(365)
out2b <- abm(365, add_pars = list(slurry_prod_rate = 10000))

plot(CH4_emis_rate ~ time, data = out2a, type = 'l', xlab = 'Time (d)', ylab = 'CH4 emis rate (g/day)', ylim = c(0,30000))
lines(CH4_emis_rate ~ time, data = out2b, col = "red")

Of course this approach also works if we have explicitly defined our own mng_pars values and then want to modify it in a later call.

mng_pars2c <- list(slurry_prod_rate = 5700, slurry_mass = 39000,
                   storage_depth = 0.6, resid_depth = 0.05, floor_area = 650,
                   area = 715, empty_int = 42, temp_C = 20, wash_water = 75000,
                   wash_int = NA, rest_d = 5, cover = 'none', resid_enrich = 0.9,
                   slopes = c(urea = NA, slurry_prod_rate = NA), 
                   graze = c(start = "May", duration = 0, hours_day = 0), 
                   scale = c(ks_coefficient = 1, qhat_opt = 1, xa_fresh = 1, 
                             yield = 1, alpha_opt = 1))
out2c <- abm(365, mng_pars = mng_pars2c, add_pars = list(slurry_prod_rate = 10000))
all.equal(out2c, out2b)

Or to change a value in one of the default parameter sets.

out2d <- abm(365, add_pars = list(pH = 6))

As in the above example out1 multiple parameters can be combined in a list for use with add_pars. But for parameter values that come from sets with a nested structure, we have to provide more details on what value we are trying to change. To change VFA_concentration in fresh manure, use conc_fresh.VFA, for example. All the other values from the default parameter set would be used.

man_pars2.0
out2e <- abm(365, add_pars = list(conc_fresh.VFA = 10))

plot(VFA_conc ~ time, data = out2a, type = 'l', xlab = 'Time (d)', ylab = 'VFA conc. (g/kg)', ylim = c(0,20))
lines(VFA_conc ~ time, data = out2e, col = "red")

We can also change any OM concentrations in the influent manure over time by passing it to conc_fresh as a data frame. Below example with changed VFA (which increase methane production), sulfide concentrations (which inhibit methanogens) and total ammonia nitrogen (TAN).

man_pars <- list(conc_fresh = data.frame(time = c(0, 100, 101, 365), sulfide = c(0.01, 0.01, 0.1, 0.1), 
                                        urea = 0, 
                                        sulfate = 0.2, TAN = c(0, 3, 5, 6), starch = 1, 
                                        VFA = c(2.83, 2.83, 6, 6), 
                                        xa_dead = 0, xa_bac = 0, xa_aer = 0, 
                                        Cfat = 12, CPs = 10, CPf = 10, RFd = 29, iNDF = 10, VSd = 0, 
                                        VSd_A = 44.4, VSnd_A = 20, ash = 15), pH = 7, dens = 1000)

man_pars

out2f <- abm(365, man_pars = man_pars)

plot(CH4_emis_rate ~ time, data = out2a, type = 'l', xlab = 'Time (d)', ylab = 'CH4 emis rate (g/day)')
lines(CH4_emis_rate ~ time, data = out2f, col = "red")

plot(VFA_conc ~ time, data = out2f, type = 'l', col = "red", xlab = 'Time (d)', ylab = 'VFA conc. (g/kg)')
lines(VFA_conc ~ time, data = out2a)

plot(H2S_inhib_m0 ~ time, data = out2a, type = 'l', xlab = 'Time (d)', ylab = 'inhibition', ylim = c(0,1))
lines(H2S_inhib_m0 ~ time, data = out2f,  col = "red")
lines(cum_inhib_m0 ~ time, data = out2f, col = "red", lty = 2)

The H2S inhibition constant is plotted for the methanogen "m0", as by default inhibition kinetics are similar for all groups of methanogens. Due to the TAN added, the cumulative inhibition constant is even lower than just inhibition from H2S.

A similar change in the concentration of microbial populations in the fresh manure can be simulated:

xa_fresh <- data.frame(time = c(0, 100, 101, 365), m0 = c(2, 0.0628, 0.0628, 0.0628), 
                       m1 = c(0.0628, 1, 2, 0.01), m2 = c(0.0628, 0.01, 0.01, 0.1), 
                       sr1 = c(0.0628, 1, 2, 0.01))

out2g <- abm(365, man_pars = man_pars, add_pars = list(xa_fresh = xa_fresh))

plot(CH4_emis_rate ~ time, data = out2a, type = 'l', xlab = 'Time (d)', 
     ylab = 'CH4 emis rate (g/day)', ylim = c(0, 30000))
lines(CH4_emis_rate ~ time, data = out2g, col = "red")

matplot(out2g$time, out2g[, nn <- c('m0_conc','m1_conc','m2_conc','sr1_conc')]/1000, 
        type = 'l', lty = c(1:length(nn)), col = line_colors, xlab = 'Time (d)', 
        ylab = 'Microbial biomass (gCOD/kg)')
legend("topright", legend = nn, lty = c(1:length(nn)), col = line_colors, lwd = 1,
       title = "Microbial biomass", cex = 0.8)


sashahafner/ATM99 documentation built on June 14, 2025, 5:34 p.m.