tests/testthat/test-chemdose_ph.R

# chemdose_ph ----

test_that("chemdose ph returns the same pH/alkalinity when no chemical is added.", {
  water1 <- define_water(ph = 7, temp = 25, alk = 100, 0, 0, 0, 0, 0, 0, 0, cond = 100, toc = 5, doc = 4.8, uv254 = .1)
  water2 <- chemdose_ph(water1, h2so4 = 0, h3po4 = 0)

  water3 <- define_water(ph = 7, temp = 20, alk = 100, tot_po4 = 2, tds = 200)
  water4 <- chemdose_ph(water3, naocl = 0, alum = 0, naoh = 0)

  expect_equal(water1@ph, water2@ph)
  expect_equal(water1@alk, water2@alk)
  expect_equal(water3@ph, water4@ph)
  expect_equal(water3@alk, water4@alk)
})

test_that("chemdose ph corrects ph when softening", {
  water1 <- suppressWarnings(define_water(ph = 7, temp = 25, alk = 100, tot_hard = 350))
  water2 <- chemdose_ph(water1, caco3 = -100)
  water3 <- chemdose_ph(water1, caco3 = -100, softening_correction = TRUE)
  water4 <- chemdose_ph(water1, caco3 = 10, softening_correction = TRUE)
  water5 <- chemdose_ph(water1, caco3 = 10)

  expect_equal(round(water3@ph, 2), 3.86) # softening correction works
  expect_error(expect_equal(water2@ph, water3@ph)) # ph with/without softening correction are different
  expect_equal(water4@ph, water5@ph) # softening_correction should not affect pH without caco3 <0
})


# To do: subdivide for each chemical?
test_that("chemdose ph works", {
  water1 <- define_water(6.7, 20, 20, 70, 10, 10, 10, 10, 10, toc = 5, doc = 4.8, uv254 = .1)
  water2 <- define_water(7.5, 20, 100, 70, 10, 10, 10, 10, 10, toc = 5, doc = 4.8, uv254 = .1)
  water3 <- define_water(7.5, 20, 20, 70, 10, 10, 10, 10, 10, toc = 5, doc = 4.8, uv254 = .1)
  water4 <- define_water(8, 20, 20, 70, 10, 10, 10, 10, 10, toc = 5, doc = 4.8, uv254 = .1)

  test1 <- suppressWarnings(chemdose_ph(water1, alum = 30))
  test2 <- suppressWarnings(chemdose_ph(water2, alum = 30))
  test3 <- suppressWarnings(chemdose_ph(water2, alum = 50, h2so4 = 20))
  test4 <- suppressWarnings(chemdose_ph(water3, alum = 50, naoh = 10))
  test5 <- suppressWarnings(chemdose_ph(water4, alum = 50))
  test6 <- suppressWarnings(chemdose_ph(water4, naoh = 80))
  test7 <- suppressWarnings(chemdose_ph(water1, nh42so4 = 5))
  test8 <- suppressWarnings(chemdose_ph(water4, nh4oh = 5))

  # Rounded values from waterpro and WTP spot check
  expect_equal(round(test1@ph, 1), 5.7)
  expect_equal(round(test1@alk, 0), 5)
  expect_equal(round(test2@ph, 1), 6.9)
  expect_equal(round(test2@alk, 0), 85)
  expect_equal(round(test3@ph, 1), 6.4)
  expect_equal(round(test3@alk, 0), 55)
  expect_equal(round(test4@ph, 1), 6.1)
  expect_equal(round(test4@alk, 0), 7)
  expect_equal(round(test5@ph, 1), 4.0)
  expect_equal(round(test5@alk, 0), -5)
  expect_equal(round(test6@ph, 1), 11.4)
  expect_equal(round(test6@alk, 0), 119)
  expect_equal(round(test7@ph, 1), 6.7)
  expect_equal(round(test7@alk, 0), 20)
  expect_equal(round(test8@ph, 1), 9.7)
  expect_equal(round(test8@alk, 0), 26)
})

test_that("Starting phosphate residual does not affect starting pH.", {
  water1 <- suppressWarnings(define_water(ph = 7, alk = 10, tot_po4 = 5) %>%
    chemdose_ph())

  water2 <- water1 %>%
    chemdose_ph()

  water3 <- water2 %>%
    chemdose_ph()

  expect_equal(water1@ph, 7)
  expect_equal(water2@ph, 7)
  expect_equal(water3@ph, 7)
})

test_that("Phosphate dose works as expected.", {
  water1 <- suppressWarnings(define_water(ph = 7, alk = 50, tot_po4 = 0, temp = 25))

  water2 <- chemdose_ph(water1, h3po4 = 1) # 0.969 as PO4
  water3 <- chemdose_ph(water1, h3po4 = 5) # 4.84 as PO4
  water4 <- chemdose_ph(water1, h3po4 = 10) # 9.69 as PO4

  expect_equal(round(water2@ph, 1), 7.0)
  expect_equal(round(water3@ph, 1), 6.9)
  expect_equal(round(water4@ph, 1), 6.7)
})

test_that("Starting chlorine residual does not affect starting pH.", {
  water1 <- suppressWarnings(define_water(ph = 7, alk = 10, free_chlorine = 1) %>%
    chemdose_ph())

  water2 <- water1 %>%
    chemdose_ph()

  water3 <- water2 %>%
    chemdose_ph()

  expect_equal(water1@ph, 7)
  expect_equal(water2@ph, 7)
  expect_equal(water3@ph, 7)
})

test_that("Starting ammonia does not affect starting pH.", {
  water1 <- suppressWarnings(define_water(ph = 7, alk = 10, tot_nh3 = 1) %>%
    chemdose_ph())

  water2 <- water1 %>%
    chemdose_ph()

  water3 <- water2 %>%
    chemdose_ph()

  expect_equal(water1@ph, 7)
  expect_equal(water2@ph, 7)
  expect_equal(water3@ph, 7)
})

test_that("Warning when both chlorine- and ammonia-based chemical are dosed.", {
  water1 <- suppressWarnings(define_water(ph = 7, alk = 10, na = 2, cl = 2, so4 = 2))

  expect_warning(chemdose_ph(water1, cl2 = 30, nh42so4 = 20))
  expect_warning(chemdose_ph(water1, naocl = 30, nh42so4 = 20))
  expect_warning(chemdose_ph(water1, cl2 = 30, nh4oh = 20))
  expect_no_warning(chemdose_ph(water1, cl2 = 30, naocl = 20))
  expect_no_warning(chemdose_ph(water1, nh4oh = 10, nh42so4 = 12))
  expect_no_warning(chemdose_ph(water1, hcl = 20))
})

test_that("Warning when chlorine-based chemical is dosed into water containing ammonia", {
  water1 <- suppressWarnings(define_water(ph = 7, alk = 10, tot_nh3 = 3, na = 2, cl = 2, so4 = 2))
  water2 <- suppressWarnings(define_water(ph = 7, alk = 10, na = 2, cl = 2, so4 = 2)) %>%
    chemdose_ph(nh4oh = 4)

  expect_warning(chemdose_ph(water1, cl2 = 30))
  expect_warning(chemdose_ph(water2, naocl = 30))
  expect_no_warning(chemdose_ph(water1, nh4oh = 20))
  expect_no_warning(chemdose_ph(water1, hcl = 20))
})

test_that("Warning when ammonia-based chemical is dosed into water containing chlorine", {
  water1 <- suppressWarnings(define_water(ph = 7, alk = 10, free_chlorine = 3, na = 2, cl = 2, so4 = 2))
  water2 <- suppressWarnings(define_water(ph = 7, alk = 10, na = 2, cl = 2, so4 = 2)) %>%
    chemdose_ph(naocl = 4)

  expect_warning(chemdose_ph(water1, nh42so4 = 30))
  expect_warning(chemdose_ph(water2, nh4oh = 30))
  expect_no_warning(chemdose_ph(water1, cl2 = 20))
  expect_no_warning(chemdose_ph(water1, hcl = 20))
})

test_that("Warning when water slot is NA", {
  water1 <- suppressWarnings(define_water(ph = 7, alk = 10))
  water2 <- suppressWarnings(chemdose_ph(water1, naoh = 30))

  expect_warning(chemdose_ph(water1, naoh = 30), "Sodium")
  expect_equal(water2@na, NA_real_)

  expect_warning(chemdose_ph(water1, caoh = 30), "Calcium")
  expect_warning(chemdose_ph(water1, mgoh = 30), "Magne")
  expect_warning(chemdose_ph(water1, cl2 = 30), "Chloride")
  expect_warning(chemdose_ph(water1, h2so4 = 30), "Sulf")
  # expect_warning(chemdose_ph(water1, kmno4 = 30), "Potas")
})


################################################################################*
################################################################################*
# chemdose_ph helpers ----
# Check chemdose_ph_once outputs are the same as base function, chemdose_ph
# Check that output is a data frame

test_that("chemdose_ph_once outputs are the same as base function, chemdose_ph", {
  water1 <- suppressWarnings(define_water(
    ph = 7.9, temp = 20, alk = 50, tot_hard = 50, ca = 13, mg = 4, na = 20, k = 20,
    cl = 30, so4 = 20, tds = 200, cond = 100, toc = 2, doc = 1.8, uv254 = 0.05
  )) %>%
    chemdose_ph(naoh = 5)

  water2 <- suppressWarnings(water_df %>%
    slice(1) %>%
    define_water_chain() %>%
    chemdose_ph_once(input_water = "defined_water", naoh = 5))

  expect_equal(water1@ph, water2$ph)
  expect_equal(water1@alk, water2$alk)
})

# Check that output is a data frame

test_that("chemdose_ph_once is a data frame", {
  water1 <- suppressWarnings(water_df %>%
    slice(1) %>%
    define_water_chain() %>%
    chemdose_ph_once(input_water = "defined_water", naoh = 5))


  expect_true(is.data.frame(water1))
  expect_equal(colnames(water1), c("defined_water", "naoh", "ph", "alk"))
})

# Check chemdose_ph_once can use a column or function argument for chemical dose

test_that("chemdose_ph_once can use a column and/or function argument for chemical dose", {
  water0 <- water_df %>%
    define_water_once()

  water1 <- suppressWarnings(water_df %>%
    define_water_chain() %>%
    chemdose_ph_once(input_water = "defined_water", naoh = 5))

  water2 <- suppressWarnings(water_df %>%
    define_water_chain() %>%
    mutate(naoh = 5) %>%
    chemdose_ph_once(input_water = "defined_water"))

  water3 <- water_df %>%
    define_water_chain() %>%
    mutate(naoh = seq(0, 11, 1)) %>%
    chemdose_ph_once(hcl = c(5, 8))

  water4 <- water3 %>%
    slice(11) # same starting wq as water 5

  water5 <- water1 %>%
    slice(6) # same starting wq as water 4

  expect_equal(water1$ph, water2$ph) # test different ways to input chemical
  expect_equal(water1$alk, water2$alk)
  expect_equal(ncol(water3), ncol(water0) - 30) # both naoh and hcl dosed, but excess columns removed
  expect_equal(nrow(water3), 24) # joined correctly
  expect_error(expect_equal(water4$ph, water5$ph)) # since HCl added to water3, pH should be different
})

# Test that chemdose_ph_chain outputs are the same as base function, chemdose_ph.
test_that("chemdose_ph_chain outputs the same as base, chemdose_ph", {
  testthat::skip_on_cran()
  water0 <- define_water(
    ph = 7.9, temp = 20, alk = 50, tot_hard = 50, ca = 13, mg = 4, na = 20, k = 20,
    cl = 30, so4 = 20, tds = 200, cond = 100, toc = 2, doc = 1.8, uv254 = 0.05
  )

  water1 <- chemdose_ph(water0, naoh = 10)

  water2 <- water_df %>%
    slice(1) %>%
    define_water_chain() %>%
    chemdose_ph_chain(input_water = "defined_water", naoh = 10) %>%
    pluck_water(c("dosed_chem_water"), c("ph", "alk"))

  coag_doses <- tibble(alum = seq(0, 100, 10))
  softening <- tibble(softening_correction = c(T, F))
  water3 <- water_df %>%
    slice(1) %>%
    define_water_chain("raw") %>%
    cross_join(coag_doses) %>%
    cross_join(softening) %>%
    chemdose_ph_chain("raw", "dose") %>%
    pluck_water(c("dose"), c("ph", "alk"))

  water4 <- chemdose_ph(water0, alum = 20)
  water5 <- chemdose_ph(water0, alum = 100, softening_correction = FALSE)

  water6 <- water_df %>%
    slice(1) %>%
    define_water_chain("raw") %>%
    mutate(naoh = 10) %>%
    cross_join(coag_doses) %>%
    rename(NewName = alum) %>%
    chemdose_ph_chain("raw", "dose", alum = NewName, naocl = c(0, 2)) %>%
    pluck_water(c("dose"), c("ph", "alk"))

  water7 <- chemdose_ph(water0, alum = 20, naocl = 2, naoh = 10)

  expect_equal(water2$dosed_chem_water_alk[1], water1@alk)
  expect_equal(water3$dose_ph[6], water4@ph)
  expect_equal(water3$dose_ph[22], water5@ph)
  expect_equal(water6$dose_ph[6], water7@ph)
})

# Test that output is a column of water class lists, and changing the output column name works

test_that("chemdose_ph_chain output is list of water class objects, and can handle an ouput_water arg", {
  testthat::skip_on_cran()
  water1 <- suppressWarnings(water_df %>%
    slice(1) %>%
    define_water_chain() %>%
    balance_ions_chain() %>%
    chemdose_ph_chain(input_water = "balanced_water", naoh = 10))

  water2 <- purrr::pluck(water1, 4, 1)

  water3 <- suppressWarnings(water_df %>%
    define_water_chain() %>%
    mutate(naoh = 10) %>%
    balance_ions_chain() %>%
    chemdose_ph_chain(output_water = "diff_name"))

  expect_s4_class(water2, "water") # check class
  expect_equal(names(water3[4]), "diff_name") # check if output_water arg works
})

# Check that this function can be piped to the next one
test_that("chemdose_ph_chain works", {
  testthat::skip_on_cran()
  water1 <- suppressWarnings(water_df %>%
    define_water_chain() %>%
    mutate(naoh = 10) %>%
    balance_ions_chain() %>%
    chemdose_ph_chain(input_water = "balanced_water"))

  expect_equal(ncol(water1), 4) # check if pipe worked
})

# Check that variety of ways to input chemicals work
test_that("chemdose_ph_chain can handle different ways to input chem doses", {
  testthat::skip_on_cran()
  water1 <- suppressWarnings(water_df %>%
    define_water_chain() %>%
    balance_ions_chain() %>%
    chemdose_ph_chain(input_water = "balanced_water", naoh = 10, output_water = "dosed_chem"))

  water2 <- suppressWarnings(water_df %>%
    define_water_chain() %>%
    mutate(naoh = 10) %>%
    balance_ions_chain() %>%
    chemdose_ph_chain(input_water = "balanced_water"))

  water3 <- suppressWarnings(water_df %>%
    define_water_chain() %>%
    mutate(naoh = seq(0, 11, 1)) %>%
    balance_ions_chain() %>%
    chemdose_ph_chain(hcl = c(5, 8)))

  water4 <- water3 %>%
    slice(21) # same starting wq as water 5

  water5 <- water1 %>%
    slice(11) # same starting wq as water 4

  expect_equal(
    pluck_water(water1, "dosed_chem", "toc")$dosed_chem_toc,
    pluck_water(water2, "dosed_chem_water", "toc")$dosed_chem_water_toc
  ) # test different ways to input chemical
  expect_equal(ncol(water3), 5) # both naoh and hcl dosed
  expect_equal(nrow(water3), 24) # joined correctly
  expect_error(expect_equal(
    pluck_water(water4, "dosed_chem_water", "ph")$dosed_chem_water_ph,
    pluck_water(water5, "dosed_chem", "ph")$dosed_chem_ph
  )) # since HCl added to water3, pH should be different
})

# Check that na_to_zero implementation works
test_that("chemdose_ph_chain na_to_zero argument works", {
  testthat::skip_on_cran()
  water <- suppressWarnings(water_df %>%
    define_water_chain() %>%
    balance_ions_chain() %>%
    chemdose_ph_chain(input_water = "balanced_water", naoh = c(1, 2, 3, 4, NA, 6, 7, 8, NA, 10, 11, 12)))
  expect_equal(water$naoh[5], 0)
  expect_equal(water$naoh[9], 0)
})

Try the tidywater package in your browser

Any scripts or data that you put into this service are public.

tidywater documentation built on Aug. 8, 2025, 7:15 p.m.