Thermodynamic Modeling with CHNOSZ

Are you curious what sorts of metabolic processes might be occurring at your study site? Using in situ geochemical data, you can model the Gibbs free energy available from metabolic reactions and predict which metabolisms are energetically favorable! Gibbs free energy can be modeled using the following equation:

\[\Delta G_r - RT ln \frac{K_r}{Q_r }\]

where \(\Delta G_r\) is the Gibbs energy of reaction r under in situ conditions, \(K_r\) and \(Q_r\) are the equillibrium constant and activity product of the rth reaction, R is the gas constant, and T is temperature in Kelvin.

\(Q_r\) can be calculated using the following equation:

\[Q_r = \prod a_i ^{v_{i,r}}\]

where \(a_i\) is the activity of the ith species and \(v_{i,r}\) is the stoichiometric coefficient of the ith species in the rth reaction.

For this tutorial I’m using data and code from my publication in Geobiology. We calculated activity coefficients for each species of interest using Spece8 in Geochemist’s Workbench using geochemical data collected from our field site. Then we used the CHNOSZ package to calculate Gibbs free energy of various metabolic reactions. First, let’s check out what the activity data looks like.

#load tidyverse library
pacman::p_load(tidyverse)

activities <- read_csv("https://raw.githubusercontent.com/CaitlinCasar/Casar2020_DeMMO_MineralHostedBiofilms/master/orig_data/DeMMO_SpecE8_aqueousGas.csv")

You can see we calculated activites for Ca2+, acetate, CH4, Fe2+, H+, HCO3-, HS-, Mn2+, NH4+, NO2-, NO3-, and SO42- at six different sites called DeMMO1-6.

Once you have your activity coefficients, then you need to decide what reactions you want to model. I was interested in metabolic reactions with minerals, so I created a spreadsheet of balanced reactions with minerals and aqueous species available in situ. Let’s load this data and preview it here.

#import DeMMO mineral reactions
reactions <- read_csv("https://raw.githubusercontent.com/CaitlinCasar/Casar2020_DeMMO_MineralHostedBiofilms/master/orig_data/reactions_aq_gas.csv") %>%
  filter(!product.f == "manganite") #removing reactions with manganite because we don't have equation of state data for this 

The formatting of this data is important for compatability with CHNOSZ, so make sure you include the details I’m showing here when you balance your reactions. Notice that the reactant stoichiometric coefficients are negative because they’re on the left side of the reaction, while products are positive because they’re on the right side of the reaction. Now we’re ready to model Gibbs free energy from these reactions in CHNOSZ! First we’ll load the CHNOSZ library.

#load the CHNOSZ library
pacman::p_load(CHNOSZ)

Let’s take a look at the available species for modeling reactions in the CHNOSZ database:

thermo_db <- thermo()$obigt %>% as_tibble()

A few minerals that I was interested in were missing in the database, but that’s ok! If you have the Gibbs free energy available at stadard conditions and the equation of state data for your reactant, you can add it to the CHNOSZ database. Let’s add pyrolusite and ferrihydrite with the mod.obigt function!

#add pyrolusite to database
pyrolusite <- mod.obigt("pyrolusite", G=-111100, H=-124283, S=12.61, V=17.3, formula="MnO2", state="cr", a1.a=12.55, a2.b=9.761, a3.c=-2.105)

#add ferrihydrite to database
ferrihydrite <- mod.obigt("ferrihydrite", G=-111200, H=-127800, S=16.7, V=20.88, formula="FeOOH", state="cr", a1.a=8.70, a2.b=36.71, a3.c=-1.0146)

You can set your temperature and energy units like this:

#set temperature units to Kelvin
T.units("K")

#set energy units to joules
E.units("J")

Let’s calculate logK:

logK <- reactions %>%
  pivot_longer(reactant.a:state.i,
               names_to = c(".value", "set"),
               names_pattern = "(.+).(.+)") %>% #pivot from wide to long dataframe
  unite("react_prod", reactant:product, na.rm = TRUE, remove = F) %>% #unite the reactants and products into one column 
  filter(!react_prod == "") %>% #remove any rows with missing react_prod values
  group_by(rxn.number) %>% #group by reaction number for calculations
  summarise(DeMMO1 = subcrt(react_prod, coeff, state, T=283.45)$out$logK,#calculate logK using in situ DeMMO1 temperature for all other reactions 
         DeMMO2 = subcrt(react_prod, coeff, state, T=285.55)$out$logK,
         DeMMO3 = subcrt(react_prod, coeff, state, T=289.35)$out$logK,
         DeMMO4 = subcrt(react_prod, coeff, state, T=295.65)$out$logK,
         DeMMO5= subcrt(react_prod, coeff, state, T=304.85)$out$logK,
         DeMMO6 = subcrt(react_prod, coeff, state, T=294.65)$out$logK) %>%
  pivot_longer(cols = DeMMO1:DeMMO6, names_to = "Site", values_to = "LogK") %>% #pivot from wide to long for merging later 
  distinct() #remove the duplicate rows 

Now we need to calculate logQ:

activities <- activities %>%
  pivot_longer(`Ca+2`:CO, names_to = "react_prod", values_to = "activity") #pivot from wide to long for joining

logQ <- reactions %>%
  pivot_longer(reactant.a:state.i,
               names_to = c(".value", "set"),
               names_pattern = "(.+).(.+)") %>% #pivot from wide to long 
  unite("react_prod", reactant:product, na.rm = TRUE, remove = F) %>% #unite the reactant and product columns into one column called react_prod 
  left_join(activities) %>% #join with the activities data 
  filter(!is.na(activity)) %>% #remove any activities with NA values 
  mutate(logQ = if_else(!is.na(reactant), -abs(coeff)*activity, abs(coeff)*activity)) %>% #multiply each species by its stoichiometric coefficient 
  group_by(rxn.number, Site) %>% #group on the reaction number and site 
  summarise(logQ = sum(logQ)) #calculate logQ 

Finally, it’s time to calculate Gibbs free energy yield from each reaction!

deltaG <- logK %>%
  left_join(logQ) %>% #join the logK and logQ tables 
  left_join(reactions %>% select(rxn.number, e.transfer, reactant.a)) %>% #add the reaction number, number of electrons transferred, and minerals from each reaction 
  rename(mineral = "reactant.a") %>% #rename reactant.a to mineral for clarity 
  mutate(deltaG = (-2.303*8.314*283.45*(LogK-logQ))/(e.transfer*1000)) #calculate deltaG for each reaction at each site 

Lets’s take a look at the deltaG table:

Now, let’s plot the data and see which metabolic reactions with minerals are energetically favorable under in situ conditions at DeMMO:

deltaG %>%
  ggplot(aes(deltaG, reorder(rxn.number, -deltaG), shape=Site, group=rxn.number)) +
  theme_gray() +
  geom_line(aes(color=mineral), size=2.5, alpha=0.6) + #color each line spanning the deltaG values for the six sites by the mineral in the reaction
  geom_point() + 
  scale_shape_manual(values = c(0,1,2,15,16,17)) + #manually set the shapes for each point to denote the six different sites 
  scale_x_reverse() + #reverse the x-axis to show exergonic values on the right, this is standard for this kind of data
  labs(x=expression(Delta~G[r]~'kJ/mol'~e^{textstyle("-")})) + #generate the axis labels
  ylab("Reaction #") +
  geom_vline(xintercept = 0, linetype="dotted", color = "black") + #add a vertical line at zero for reference 
  theme(legend.position = c(.1, .6), legend.text=element_text(size=8), legend.title = element_text(size=8, face="bold")) + #position the legend on the left 
  theme(legend.key.size =  unit(0.2, "in")) #resize the legend to make it fit 

Everything that plots to the right of the dotted line is exergonic, while everything to the left of the dotted line is endergonic. The most exergonic and therefore the most energetically favorable reactions occur with the mineral pyrolusite, followed by pyrite. If you’re curious which reactant was coupled to these minerals for each reaction, refer back to the reactions table above! 👩‍🔬

Fork Me