Helper Functions: Dose chemicals

This vignette assumes a basic understanding of define_water and the S4 water class. See vignette("intro", package = "tidywater") for more information.

Chemical Dosing Setup

To showcase tidywater’s acid-base equilibrium functions, let’s use a common water treatment problem. In this analysis, a hypothetical drinking water utility wants to know how much their pH will be impacted by varying doses of alum. They also want to ensure that their finished water has a pH of 8.

We can create a quick model by manually inputting the utility’s typical water quality. Then we’ll dose the water with their typical alum dose of 30 mg/L, and then a proposed 20mg/L dose. Finally, we’ll see how much caustic is required to raise the pH back to 8.

# Use define_water to prepare for tidywater analysis
no_alum_water <- define_water(ph = 8.3, temp = 18, alk = 150)

# Dose 30 mg/L of alum
alum_30 <- no_alum_water %>%
  chemdose_ph(alum = 30) %>%
  solvedose_ph(target_ph = 8, chemical = "naoh")

alum_30 # Caustic dose required to raise pH to 8 when 30 mg/L of alum is added
># [1] 10.3

# Dose 20 mg/L of alum
alum_20 <- no_alum_water %>%
  chemdose_ph(alum = 20) %>%
  solvedose_ph(target_ph = 8, chemical = "naoh")

alum_20 # Caustic dose required to raise pH to 8 when 20 mg/L of alum is added
># [1] 6.2

As expected, a lower alum dose requires a lower caustic dose to reach the target pH.

But what if the utility wants to test a variety of alum doses on a range of their water quality? We’ll use the power of tidywater’s _chain functions to extend this analysis to a full dataframe. For more information on tidywater’s _chain functions, please see the vignette("help_functions_blend_waters", package = "tidywater").

Multi-Scenario Setup

We’ll use tidywater’s built-in water quality data, water_df, then apply define_water_chain and balance_ions_chain to convert the data to a water object. We’ll also set a range of alum doses to see how they affect each water quality scenario.

# Set a range of alum doses

alum_doses <- tibble(alum_dose = seq(20, 60, 10))

# Use tidywater's built-in synthetic data, water_df, for this example
raw_water <- water_df %>%
  slice_head(n = 2) %>%
  define_water_chain("raw") %>%
  # Join alum doses to create several dosing scenarios.
  cross_join(alum_doses)

chemdose_ph_chain

Now that we’re set up, let’s dose some alum! To do this, we’ll use chemdose_ph_chain, a function whose tidywater base is chemdose_ph. The chemdose_ph_chain function requires dosed chemicals to match the argument’s notation. In this case, our chemical is already properly named. Other chemicals, such as caustic, ferric sulfate, soda ash and more would need to be named naoh, fe2so43, and na2co3, respectively. Most tidywater chemicals are named with their chemical formula, all lowercase and no special characters.

There are two ways to dose chemicals.

  1. You can pass an appropriately named column into the function, or

  2. You can specify the chemical in the function.

Let’s look at both options using the alum doses from before and hydrochloric acid.

dose_water <- raw_water %>%
  mutate(hcl = 10) %>%
  chemdose_ph_chain(input_water = "raw", alum = alum_dose) %>%
  pluck_water(input_water = c("raw", "dosed_chem_water"), parameter = "ph") %>%
  select(-c(raw, dosed_chem_water))

head(dose_water)
>#   alum_dose hcl raw_ph dosed_chem_water_ph
># 1        20  10    7.9                6.36
># 2        30  10    7.9                6.19
># 3        40  10    7.9                6.00
># 4        50  10    7.9                5.78
># 5        60  10    7.9                5.47
># 6        20  10    8.5                6.73

dose_water <- raw_water %>%
  chemdose_ph_chain(input_water = "raw", alum = alum_dose, hcl = 5) %>%
  pluck_water(input_water = c("raw", "dosed_chem_water"), parameter = "ph") %>%
  select(-c(raw, dosed_chem_water))

head(dose_water)
>#   alum_dose hcl raw_ph dosed_chem_water_ph
># 1        20   5    7.9                6.60
># 2        30   5    7.9                6.42
># 3        40   5    7.9                6.25
># 4        50   5    7.9                6.07
># 5        60   5    7.9                5.87
># 6        20   5    8.5                6.94

solvedose_ph_once

Remember, our original task is to see how alum addition affects the pH, but the finished water pH needs to be 8. First, we’ll use caustic to raise the pH to 8. solvedose_ph_once uses solvedose_ph to calculate the required chemical dose (as chemical, not product) based on a target pH.

Note: How can you remember the difference between solvedose_ph vs chemdose_ph? Any function beginning with “solve” is named for what it is solving for based on one input: SolveWhatItReturns_Input. So, solvedose_ph is solving for a dose based on a target pH. Other treatment functions are set up as WhatHappensToTheWater_WhatYouSolveFor. So with chemdose_ph, chemicals are being dosed, and we’re solving for the resulting pH (and other components of acid/base chemistry). chemdose_toc models the resulting TOC after chemicals are added, and dissolve_pb calculates lead solubility in the distribution system.

Let’s get back to our analysis. Similar to chemdose_ph_chain, solvedose_ph_once can handle chemical selection and target pH inputs as a column or function arguments.solvedose_ph_once outputs a numeric dose, not a water object. Thus, solvedose_ph_chain doesn’t exist because the water isn’t changing, so chaining this function to a downstream tidywater function can be done using normal tidywater operations.

solve_ph <- raw_water %>%
  chemdose_ph_chain("raw", alum = alum_dose) %>%
  mutate(target_ph = 8) %>%
  solvedose_ph_once(input_water = "dosed_chem_water", chemical = c("naoh", "mgoh2")) %>%
  select(-c(raw, dosed_chem_water))

head(solve_ph)
>#   alum_dose target_ph chemical dose_required
># 1        20         8     naoh           8.3
># 2        20         8    mgoh2           6.1
># 3        30         8     naoh          12.4
># 4        30         8    mgoh2           9.0
># 5        40         8     naoh          16.5
># 6        40         8    mgoh2          12.0

Now that we have the dose required to raise the pH to 8, let’s dose caustic into the water!

dosed_caustic_water <- raw_water %>%
  chemdose_ph_chain(input_water = "raw", output_water = "alum_dosed", alum = alum_dose) %>%
  solvedose_ph_once(input_water = "alum_dosed", chemical = "naoh", target_ph = 8) %>%
  chemdose_ph_chain(input_water = "alum_dosed", output_water = "caustic_dosed", naoh = dose_required) %>%
  pluck_water(input_water = "caustic_dosed", "ph")

head(dosed_caustic_water)
>#                                                      raw alum_dose
># 1 <S4 class 'water' [package "tidywater"] with 67 slots>        20
># 2 <S4 class 'water' [package "tidywater"] with 67 slots>        30
># 3 <S4 class 'water' [package "tidywater"] with 67 slots>        40
># 4 <S4 class 'water' [package "tidywater"] with 67 slots>        50
># 5 <S4 class 'water' [package "tidywater"] with 67 slots>        60
># 6 <S4 class 'water' [package "tidywater"] with 67 slots>        20
>#                                               alum_dosed target_ph chemical
># 1 <S4 class 'water' [package "tidywater"] with 67 slots>         8     naoh
># 2 <S4 class 'water' [package "tidywater"] with 67 slots>         8     naoh
># 3 <S4 class 'water' [package "tidywater"] with 67 slots>         8     naoh
># 4 <S4 class 'water' [package "tidywater"] with 67 slots>         8     naoh
># 5 <S4 class 'water' [package "tidywater"] with 67 slots>         8     naoh
># 6 <S4 class 'water' [package "tidywater"] with 67 slots>         8     naoh
>#   dose_required                                          caustic_dosed
># 1           8.3 <S4 class 'water' [package "tidywater"] with 67 slots>
># 2          12.4 <S4 class 'water' [package "tidywater"] with 67 slots>
># 3          16.5 <S4 class 'water' [package "tidywater"] with 67 slots>
># 4          20.5 <S4 class 'water' [package "tidywater"] with 67 slots>
># 5          24.4 <S4 class 'water' [package "tidywater"] with 67 slots>
># 6           6.2 <S4 class 'water' [package "tidywater"] with 67 slots>
>#   caustic_dosed_ph
># 1             7.99
># 2             8.02
># 3             8.00
># 4             8.02
># 5             8.01
># 6             7.99

You can see the resulting pH from dosing caustic has raised the pH to 8 +/- 0.02 SU. Doses are rounded to the nearest 0.1 mg/L to make the calculations go a little faster.