Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
477 lines (411 sloc) 16.7 KB
---
title: "Carbonate Chemistry Examples"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Carbonate Chemistry Examples}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
This vignette provides examples of the carbonate chemistry tools implemented by the **mediachemtools** package to explore carbonate solubility and pH control in open and closed media systems. See the [carbonate chemistry equations vignette](carbonate_chemistry_equations.html) for conceptual background.
```{r, echo=FALSE, message=FALSE, warning=FALSE}
library(tidyverse) # load tidyverse tools
library(plotly) # load interactive plotting
library(MediaChemTools) # load mediachemtools library
```
# CO2 solubility
[See equations](carbonate_chemistry_equations.html#co_2-solubility).
```{r "henry", fig.width=8, fig.height=6}
solubilities <- data_frame(
temperature = seq(from = 0, to = 40, by = 5) %>% qty("C"),
solubility = calculate_solubility("CO2", temperature)
)
ggplot(solubilities) +
aes(get_qty_value(temperature, "C"), solubility) +
geom_line(size = 2) +
labs(
x = "T [C]",
y = get_qty_units_with_label(solubilities$solubility, "Henry's law solubility")) +
theme_bw()
```
# Speciation
[See equations](carbonate_chemistry_equations.html#speciation).
```{r "speciation", fig.width=8, fig.height=6}
# calculate
df_speciation <-
data_frame(
pH = seq(3, 14, by = 0.1),
DIC = qty(10, "mM"),
`H2CO3*` = calculate_carbonic_acid(pH, DIC = DIC),
HCO3 = calculate_bicarbonate(pH, DIC = DIC),
CO3 = calculate_carbonate(pH, DIC = DIC)
)
# visualize
df_speciation %>%
mutate_if(is_molarity, get_qty_value, "mM") %>%
gather(Species, Concentration, -pH, -DIC) %>%
ggplot() +
aes(pH, Concentration, color = Species) +
geom_line() +
scale_x_continuous(expand = c(0, 0), breaks = c(1:14)) +
scale_y_log10(
breaks = 10^(-3:1),
labels = make_qty_text_labeller(qty(1, "mM"))) +
coord_cartesian(ylim = c(10^-3, 10)) +
theme_bw()
```
# Open system
[See equations](carbonate_chemistry_equations.html#open-system).
## CO2 only
```{r}
# range of pCO2s
pCO2s <- data_frame(pCO2 = c(seq(0, 5, by=0.1), seq(5, 20, by=1), seq(20, 200, by=5)) %>% qty("mbar"))
# range of temperature
temperatures <- data_frame(temperature = c(10, 25, 37) %>% qty("C"))
# calculate for all combinations
df_pH_vs_pCO2 <-
crossing(pCO2s, temperatures) %>%
mutate(
pH = calculate_open_system_pH(pCO2, temp = temperature),
`DIC [mM]` = calculate_DIC(pH, pCO2, temp = temperature) %>%
get_qty_value("mM"),
`CO2 [mM]` = calculate_ideal_gas_molarity(pCO2, temp = temperature) %>%
get_qty_value("mM")
)
```
```{r "pH vs pCO2", fig.width = 8, fig.height = 6}
df_pH_vs_pCO2 %>%
gather(var, val, pH, `DIC [mM]`, `CO2 [mM]`) %>%
filter(!(var == "pH" & val > 5.5)) %>%
ggplot() +
aes(pCO2, val, color = factor(temperature)) +
geom_line() +
scale_x_continuous(
expand = c(0,0),
labels = make_qty_text_labeller(df_pH_vs_pCO2$pCO2, c("mbar", "% SP"))) +
scale_color_brewer(
"Temperature", palette = "Set1",
labels = make_qty_text_labeller(df_pH_vs_pCO2$temperature, "C")
) +
facet_grid(var~., scales = "free_y") +
expand_limits(x = 0) +
theme_bw() + labs(y = "")
```
## Adjusting pH with alkalinity
```{r}
# to calculate how much base (NaOH or NaHCO3) to add
calculate_open_system_alkalinity(pH = 6.8, pCO2 = qty(0.4, "mbar"))
calculate_open_system_alkalinity(pH = 6.8, pCO2 = qty(50, "mbar"))
calculate_open_system_alkalinity(pH = 6.8, pCO2 = qty(200, "mbar"))
# to calculate pH with addition of a specific amount of base
calculate_open_system_pH(pCO2 = qty(0.4, "mbar"), alkalinity = qty(5, "mM"))
calculate_open_system_pH(pCO2 = qty(50, "mbar"), alkalinity = qty(5, "mM"))
calculate_open_system_pH(pCO2 = qty(200, "mbar"), alkalinity = qty(5, "mM"))
# to calculate pH with addition of a specific amount of acid
calculate_open_system_pH(pCO2 = qty(0.4, "mbar"), alkalinity = qty(-1, "mM"))
calculate_open_system_pH(pCO2 = qty(50, "mbar"), alkalinity = qty(-1, "mM"))
calculate_open_system_pH(pCO2 = qty(200, "mbar"), alkalinity = qty(-1, "mM"))
```
### Visualization
```{r "unbalanced ions for pH", fig.width = 9, fig.height = 6}
# range of pCO2s
pCO2s <- data_frame(pCO2 = c(0.4, 50, 200) %>% qty("mbar"))
# range of temperature
temperatures <- data_frame(temperature = c(25, 37) %>% qty("C"))
# calculate for all combinations
df_base_vs_pH <-
crossing(pCO2s, temperatures, pH = seq(3, 7.5, by = 0.05)) %>%
mutate(
ions = calculate_open_system_alkalinity(pH, pCO2, temp = temperature),
`Add base (e.g. NaOH) [mM]` = ifelse(ions > 0, get_qty_value(ions, "mM"), 0),
`Add acid (e.g. HCl) [mM]` = ifelse(ions < 0, -get_qty_value(ions, "mM"), 0),
`DIC [mM]` = calculate_DIC(pH, pCO2, temp = temperature) %>%
get_qty_value("mM")
)
p <- df_base_vs_pH %>%
gather(var, val, `Add base (e.g. NaOH) [mM]`, `Add acid (e.g. HCl) [mM]`, `DIC [mM]`) %>%
mutate(panel = get_qty_text(pCO2, "mbar") %>% paste("CO2") %>% as_factor()) %>%
filter(val > 0) %>%
ggplot() +
aes(pH, val, color = var, linetype = get_qty_text(temperature, "C")) +
geom_vline(xintercept = 7, color = "black", size = 1) +
geom_line() +
scale_x_continuous(breaks = 1:14, expand = c(0, 0)) +
scale_color_brewer("Component", palette = "Set1") +
facet_wrap(~panel, nrow = 1, scales = "free_y") +
theme_bw() +
labs(y = "Concentration [mM]", linetype = "Temperature")
p
```
### Log scale
```{r "unbalanced ions for pH - log scale", fig.width = 9, fig.height = 6}
# easier to see in log space and equal y axis + a vertical line
p_log <- p +
scale_y_log10("Concentration", breaks = 10^(-3:3),
labels = function(x) map(x, qty, "mM") %>% map_chr(get_qty_text)) +
facet_wrap(~panel, nrow = 1)
p_log
```
## Adjusting pH with a buffer
For simplicty keeping temperature constant at the default (25C).
```{r}
buffer <- qty(c(1, 10, 50), "mM")
# to calculate how much base (NaOH or NaHCO3) to add
df <- data_frame(
buffer = buffer,
add = calculate_open_system_alkalinity(
pH = 6.8,
pCO2 = qty(50, "mbar"),
buffer = buffer,
buffer_pKa = 7.5
)
)
df
# what if buffer is mono-sodium? (negative means HCl or other strong acid required instead of base)
df %>% mutate(add_if_salt_buffer = add - buffer)
# to calculate pH with addition of a specific buffer
df %>%
mutate(
pH = calculate_open_system_pH(
pCO2 = qty(50, "mbar"),
buffer = buffer,
alkalinity = add,
buffer_pKa = 7.5
)
)
```
### Different concentrations of weak acid (same pKa)
```{r "unbalanced ions for pH with buffer", fig.width = 9, fig.height = 6}
# range of pCO2s
pCO2s <- data_frame(pCO2 = c(0.4, 50, 200) %>% qty("mbar"))
# range of buffers
buffers <- data_frame(
buffer = c(0, 20, 20, 20, 50) %>% qty("mM"),
buffer_pKa = c(7.5, 7.5, 9, 6, 7.5))
# calculate for all combinations
df_base_w_buffer_vs_pH <-
crossing(pCO2s, buffers, pH = seq(3, 7.5, by = 0.05)) %>%
mutate(
ions = calculate_open_system_alkalinity(pH, pCO2, buffer = buffer, buffer_pKa = buffer_pKa),
`Add base (e.g. NaOH) [mM]` = ifelse(ions > 0, get_qty_value(ions, "mM"), 0),
`Add acid (e.g. HCl) [mM]` = ifelse(ions < 0, -get_qty_value(ions, "mM"), 0),
`DIC [mM]` = calculate_DIC(pH, pCO2) %>% get_qty_value("mM")
)
# visualize
plot_df <- df_base_w_buffer_vs_pH %>%
gather(var, val, `Add base (e.g. NaOH) [mM]`, `Add acid (e.g. HCl) [mM]`, `DIC [mM]`) %>%
mutate(panel = get_qty_text(pCO2, "mbar") %>% paste("CO2") %>% as_factor(),
Buffer = get_qty_text(buffer, "mM") %>% paste0(" (pKa=", buffer_pKa, ")")) %>%
filter(val > 0)
p_log %+% filter(plot_df, buffer_pKa == 7.5) %+%
aes(linetype = Buffer) +
labs(title = "Different concentrations of a weak acid buffer")
```
#### Interactive
```{r "unbalanced ions for pH with buffer interactive", fig.width = 9, fig.height = 6}
ggplotly()
```
### Same concentration of weak acid (different pKa)
```{r "unbalanced ions for pH with buffer difference pKa", fig.width = 9, fig.height = 6}
p_log %+% filter(plot_df, buffer == qty(20, "mM")) %+%
aes(linetype = Buffer) +
labs(title = "Same concentration of a weak acid buffer with different pKa")
```
### Different concentration of salt buffer (same pKa)
If the buffer is a salt instead of an acid, it contributes to the alkalinity.
```{r "unbalanced ions for pH with different buffer concs", fig.width = 9, fig.height = 6}
plot_df2 <-
crossing(pCO2s, buffers, pH = seq(3, 7.5, by = 0.05)) %>%
mutate(
ions = calculate_open_system_alkalinity(pH, pCO2, buffer = buffer, buffer_pKa = buffer_pKa) - buffer,
`Add base (e.g. NaOH) [mM]` = ifelse(ions > 0, get_qty_value(ions, "mM"), 0),
`Add acid (e.g. HCl) [mM]` = ifelse(ions < 0, -get_qty_value(ions, "mM"), 0),
`DIC [mM]` = calculate_DIC(pH, pCO2) %>% get_qty_value("mM")
) %>%
gather(var, val, `Add base (e.g. NaOH) [mM]`, `Add acid (e.g. HCl) [mM]`, `DIC [mM]`) %>%
mutate(panel = get_qty_text(pCO2, "mbar") %>% paste("CO2") %>% as_factor(),
Buffer = get_qty_text(buffer, "mM") %>% paste0(" (pKa=", buffer_pKa, ")")) %>%
filter(val > 0)
p_log %+% filter(plot_df2, buffer_pKa == 7.5) %+%
aes(linetype = Buffer) +
labs(title = "Different concentrations of a mono-valent salt buffer")
```
## pH difference upon atmosphere switching
A rare environmental but common laboratory scenario is the sometimes unexpected change in pH upon shifting from standard atmospheric CO2 (400ppm, let's face reality...) to some artifical higher CO2 atmosphere without adjusting anything else.
```{r, "pH change vs pCO2 and acid base with buffer", fig.width = 8, fig.height = 7, warning=FALSE}
df_pH_switch <-
df_base_w_buffer_vs_pH %>%
filter(pCO2 == qty(0.4, "mbar")) %>%
rename(init_pCO2 = pCO2) %>%
crossing(data_frame(pCO2 = qty(c(50, 200), "mbar"))) %>%
mutate(
`new pH` = calculate_open_system_pH(
pCO2 = pCO2, buffer = buffer, buffer_pKa = buffer_pKa, alkalinity = ions
),
`pH difference` = `new pH` - pH,
panel = get_qty_text(pCO2, "mbar") %>% paste("CO2") %>% as_factor()
) %>%
gather(var, val, `new pH`, `pH difference`)
p %+% df_pH_switch %+%
aes(color = get_qty_text(buffer, "mM"), linetype = factor(buffer_pKa)) +
facet_grid(var~panel, scales = "free_y") +
scale_color_brewer("Buffer", palette = "Set1") +
scale_linetype_manual(values = c(2, 1, 3)) +
labs(linetype = "pKa", y = "")
```
# Closed system
[See equations](carbonate_chemistry_equations.html#closed-system).
## CO2 consumption
Scenario: media is equilibrated with an initial CO2 pressure and adjusted to a specific starting pH (which requires the appropriate addition of alkalinity) in presence or absence of a pH buffer. Then the vessel is closed off and has a starting total inorganic carbon from its headspace and liquid that can then be consumed. Here a few parameters are explored but additional variation will arise from changes in buffer concentration, pKa and the proportion of headspace to liquid.
```{r "CO2 consumption", fig.width = 9, fig.height=8}
df_CO2_consumption <-
crossing(
data_frame(
# initial medium
pH_start = 7,
pCO2_start = qty(c(0.4, 50, 200), "mbar")
),
data_frame(
# buffer
buffer = qty(c(0, 50), "mM"),
pKa = 7.5
)
) %>%
mutate(
# required alkalinity for pH
pH_start = 7,
alkalinity = calculate_open_system_alkalinity(
pH = pH_start, pCO2 = pCO2_start,
buffer = buffer, buffer_pKa = pKa),
# close off
V_liquid = qty(10, "mL"),
V_gas = qty(10, "mL"),
TIC_start = calculate_closed_system_TIC(pH = pH_start, pCO2 = pCO2_start, V_liquid = V_liquid, V_gas = V_gas)
) %>%
# range of TIC used up (%)
crossing(TIC_used = seq(0, 1, by = 0.01)) %>%
# calculation of system at each point
mutate(
TIC = TIC_start * (1-TIC_used),
pH = calculate_closed_system_pH(
TIC = TIC, V_liquid = V_liquid, V_gas = V_gas,
alkalinity = alkalinity, buffer = buffer, buffer_pKa = pKa),
pCO2 = calculate_closed_system_pCO2(pH = pH, TIC = TIC, V_liquid = V_liquid, V_gas = V_gas),
DIC = calculate_DIC(pH, pCO2 = pCO2),
`H2CO3*` = calculate_carbonic_acid(pH, DIC = DIC),
HCO3 = calculate_bicarbonate(pH, DIC = DIC),
CO3 = calculate_carbonate(pH, DIC = DIC)
)
# visualize
df_CO2_consumption %>%
# convert units in preparation for gathering
mutate(
pCO2_start = get_qty_text(pCO2_start, "mbar") %>% as_factor(),
buffer = get_qty_text(buffer, "mM") %>% paste0(" (pKa=", pKa, ")") %>% as_factor()
) %>%
mutate_if(is_molarity, get_qty_value, "M", log10) %>%
mutate_if(is_pressure, get_qty_value, "bar", log10) %>%
gather(var, val, pH, pCO2, DIC, `H2CO3*`, HCO3, CO3) %>%
# avoid extremes
filter(!(var == "pCO2" & val < -7), !(var == "H2CO3*" & val < -8)) %>%
# make sensible panels
mutate(panel = case_when(var %in% c("pH", "pCO2") ~ var, TRUE ~ "inorganic carbon")) %>%
ggplot() +
aes(TIC_used, val, color = var, linetype = buffer) +
geom_line() +
scale_x_continuous("TIC consumed", labels = scales::percent, expand = c(0, 0)) +
scale_y_continuous(
breaks = c(-12:0, 7:14),
labels = function(labels,...) {
if (max(labels, na.rm = TRUE) == -1) { # pCO2
return(make_qty_text_labeller(qty(1, "bar"))(10^labels))
} else if (min(labels, na.rm = TRUE) == 7) { # pH
return(labels)
} else { # inorganic carbon
return(make_qty_text_labeller(qty(1, "M"))(10^labels))
}
labels
}, expand = c(0, 0)) +
facet_grid(panel ~ pCO2_start, scales = "free_y") +
theme_bw() + theme(panel.grid.minor = element_blank()) +
labs(y = "", color = "", title = "CO2 consumption in closed system")
```
## CO2 production
Scenario: media with organic substrate (e.g. a sugar) is adjusted to an initial starting pH (which requires the appropriate addition of alkalinity) in presence or absence of a pH buffer. Then the vessel is closed off and CO2 is produced from the organic substrate (assuming the entire organic pool is respirable).
```{r "CO2 production", fig.width = 9, fig.height=8}
df_CO2_production <-
crossing(
data_frame(
# initial medium
pH_start = 7,
Corg = qty(c(1, 10, 100), "mM") # respirable carbon molarity
),
data_frame(
# buffer
buffer = qty(c(0, 50), "mM"),
pKa = 7.5
)
) %>%
mutate(
# required alkalinity for pH adjustment
pH_start = 7,
pCO2_start = qty(0.4, "mbar"), # atmospheric
alkalinity = calculate_open_system_alkalinity(
pH = pH_start, pCO2 = pCO2_start,
buffer = buffer, buffer_pKa = pKa),
# close off
V_liquid = qty(10, "mL"),
V_gas = qty(10, "mL"),
TIC_start = calculate_closed_system_TIC(pH = pH_start, pCO2 = pCO2_start, V_liquid = V_liquid, V_gas = V_gas)
) %>%
# range of organics respired (%)
crossing(Corg_used = seq(0, 1, by = 0.01)) %>%
# calculation of system at each point
mutate(
TIC = TIC_start + Corg * V_liquid * Corg_used,
pH = calculate_closed_system_pH(
TIC = TIC, V_liquid = V_liquid, V_gas = V_gas,
alkalinity = alkalinity, buffer = buffer, buffer_pKa = pKa),
pCO2 = calculate_closed_system_pCO2(pH = pH, TIC = TIC, V_liquid = V_liquid, V_gas = V_gas),
DIC = calculate_DIC(pH, pCO2 = pCO2),
`H2CO3*` = calculate_carbonic_acid(pH, DIC = DIC),
HCO3 = calculate_bicarbonate(pH, DIC = DIC),
CO3 = calculate_carbonate(pH, DIC = DIC)
)
# visualize
df_CO2_production %>%
# convert units in preparation for gathering
mutate(
Corg = get_qty_text(Corg, "mM") %>% paste("organic carbon") %>% as_factor(),
buffer = get_qty_text(buffer, "mM") %>% paste0(" (pKa=", pKa, ")") %>% as_factor()
) %>%
mutate_if(is_molarity, get_qty_value, "M", log10) %>%
mutate_if(is_pressure, get_qty_value, "bar", log10) %>%
gather(var, val, pH, pCO2, `H2CO3*`, HCO3, CO3) %>%
# avoid extremes
filter(!(var == "pCO2" & val < -7), !(var == "H2CO3*" & val < -8)) %>%
# make sensible panels
mutate(panel = case_when(var %in% c("pH", "pCO2") ~ var, TRUE ~ "inorganic carbon")) %>%
ggplot() +
aes(Corg_used, val, color = var, linetype = buffer) +
geom_line() +
scale_x_continuous("Organic C consumed", labels = scales::percent, expand = c(0, 0)) +
scale_y_continuous(
breaks = c(-12:0, 1:7),
labels = function(labels,...) {
if (max(labels, na.rm = TRUE) == 0) { # pCO2
return(make_qty_text_labeller(qty(1, "bar"))(10^labels))
} else if (max(labels, na.rm = TRUE) == 7) { # pH
return(labels)
} else { # inorganic carbon
return(make_qty_text_labeller(qty(1, "M"))(10^labels))
}
}, expand = c(0, 0)) +
facet_grid(panel ~ Corg, scales = "free_y") +
theme_bw() + theme(panel.grid.minor = element_blank()) +
labs(y = "", color = "", title = "CO2 production in closed system")
```
## CO2 loss through headspace exchange
Coming soon...
## CO2 gain through headspace exchange
Coming soon...
You can’t perform that action at this time.