Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some slight speedups of convert units #7

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions R/empty.R

This file was deleted.

113 changes: 51 additions & 62 deletions R/general.R
Original file line number Diff line number Diff line change
Expand Up @@ -246,32 +246,13 @@ plot_ions <- function(water) {
guides(fill = "none")
}


#' @title Calculate unit conversions for common compounds
#'
#' @description This function takes a value and converts units based on compound name.
#'
#' @param value Value to be converted
#' @param formula Chemical formula of compound. Accepts compounds in mweights for conversions between g and mol or eq
#' @param startunit Units of current value, currently accepts g/L; g/L CaCO3; g/L N; M; eq/L;
#' and the same units with "m", "u", "n" prefixes
#' @param endunit Desired units, currently accepts same as start units
#'
#' @examples
#' convert_units(50, "ca") # converts from mg/L to M by default
#' convert_units(50, "ca", "mg/L", "mg/L CaCO3")
#' convert_units(50, "ca", startunit = "mg/L", endunit = "eq/L")
#'
#' @export
#'
#' @returns A numeric value for the converted parameter.
#'
convert_units <- function(value, formula, startunit = "mg/L", endunit = "M") {
milli_list <- c("mg/L", "mg/L CaCO3", "mg/L N", "mM", "meq/L")
mcro_list <- c("ug/L", "ug/L CaCO3", "ug/L N", "uM", "ueq/L")
nano_list <- c("ng/L", "ng/L CaCO3", "ng/L N", "nM", "neq/L")
stand_list <- c("g/L", "g/L CaCO3", "g/L N", "M", "eq/L")

# Internal conversion function
Carter12s marked this conversation as resolved.
Show resolved Hide resolved
# This function generates a cached table of unit conversions with every combo of formula and units in the compile_tidywater_data file.
# If we fail to lookup a unit conversion in our cached table we look here
# This function is ~20x slower than the cache lookup
convert_units_private <- function(value, formula, startunit = "mg/L", endunit = "M") {
unit_multipliers <- get("unit_multipliers")
formula_to_charge <- get("formula_to_charge")
gram_list <- c(
"ng/L", "ug/L", "mg/L", "g/L",
"ng/L CaCO3", "ug/L CaCO3", "mg/L CaCO3", "g/L CaCO3",
Expand All @@ -283,37 +264,15 @@ convert_units <- function(value, formula, startunit = "mg/L", endunit = "M") {
caco_list <- c("mg/L CaCO3", "g/L CaCO3", "ug/L CaCO3", "ng/L CaCO3")
n_list <- c("mg/L N", "g/L N", "ug/L N", "ng/L N")

# Determine multiplier for order of magnitude conversion
# In the same list, no multiplier needed
if ((startunit %in% milli_list & endunit %in% milli_list) |
(startunit %in% stand_list & endunit %in% stand_list) |
(startunit %in% nano_list & endunit %in% nano_list) |
(startunit %in% mcro_list & endunit %in% mcro_list)) {
multiplier <- 1
# m - standard, n-u, u-n
} else if ((startunit %in% milli_list & endunit %in% stand_list) |
(startunit %in% mcro_list & endunit %in% milli_list) |
(startunit %in% nano_list & endunit %in% mcro_list)) {
multiplier <- 1e-3
} else if ((startunit %in% stand_list & endunit %in% milli_list) |
(startunit %in% milli_list & endunit %in% mcro_list) |
(startunit %in% mcro_list & endunit %in% nano_list)) {
multiplier <- 1e3
# u - standard
} else if ((startunit %in% mcro_list & endunit %in% stand_list) |
(startunit %in% nano_list & endunit %in% milli_list)) {
multiplier <- 1e-6
} else if ((startunit %in% stand_list & endunit %in% mcro_list) |
(startunit %in% milli_list & endunit %in% nano_list)) {
multiplier <- 1e6
# n - standard
} else if (startunit %in% nano_list & endunit %in% stand_list) {
multiplier <- 1e-9
} else if (startunit %in% stand_list & endunit %in% nano_list) {
multiplier <- 1e9
} else {
# Look up the unit multipliers for starting and end units
start_mult <- unit_multipliers[[startunit]]
end_mult <- unit_multipliers[[endunit]]
if (is.null(start_mult) || is.null(end_mult)) {
# If we didn't find multipliers these units are not supported
stop("Units not supported")
}
# Calculate the net multiplier
multiplier <- start_mult / end_mult

# Need molar mass of CaCO3 and N
caco3_mw <- as.numeric(tidywater::mweights["caco3"])
Expand All @@ -333,16 +292,15 @@ convert_units <- function(value, formula, startunit = "mg/L", endunit = "M") {
} else if (!(startunit %in% gram_list) & !(endunit %in% gram_list)) {
molar_weight <- 0
} else {
stop("Chemical formula not supported")
stop(paste("Chemical formula not supported: ", formula))
}

# Determine charge for equivalents
if (formula %in% c("na", "k", "cl", "hcl", "naoh", "nahco3", "na", "nh4", "f", "br", "bro3")) {
charge <- 1
} else if (formula %in% c("so4", "caco3", "h2so4", "na2co3", "caoh2", "mgoh2", "mg", "ca", "pb", "cacl2", "mn")) {
charge <- 2
} else if (formula %in% c("h3po4", "al", "fe", "alum", "fecl3", "fe2so43", "po4")) {
charge <- 3
# Look up our known charges in our hashtable
table_charge <- formula_to_charge[[formula]]
if (!is.null(table_charge)) {
# If we found a charge in the hash table use that
charge <- table_charge
} else if (!(startunit %in% eqvl_list) & !(endunit %in% eqvl_list)) {
# This is included so that charge can be in equations later without impacting results
charge <- 1
Expand Down Expand Up @@ -386,6 +344,37 @@ convert_units <- function(value, formula, startunit = "mg/L", endunit = "M") {
}
}

#' @title Calculate unit conversions for common compounds
#'
#' @description This function takes a value and converts units based on compound name.
#'
#' @param value Value to be converted
#' @param formula Chemical formula of compound. Accepts compounds in mweights for conversions between g and mol or eq
#' @param startunit Units of current value, currently accepts g/L; g/L CaCO3; g/L N; M; eq/L;
#' and the same units with "m", "u", "n" prefixes
#' @param endunit Desired units, currently accepts same as start units
#'
#' @examples
#' convert_units(50, "ca") # converts from mg/L to M by default
#' convert_units(50, "ca", "mg/L", "mg/L CaCO3")
#' convert_units(50, "ca", startunit = "mg/L", endunit = "eq/L")
#'
#' @export
#'
#' @returns A numeric value for the converted parameter.
#'
convert_units <- function(value, formula, startunit = "mg/L", endunit = "M") {
convert_units_cache <- get("convert_units_cache")
# Start with pre-generated lookup table (which has most combinations of formula and units) for speed.
lookup <- convert_units_cache[[paste(formula, startunit, endunit)]]
Carter12s marked this conversation as resolved.
Show resolved Hide resolved
if (is.null(lookup)) {
# Fallback to full implementation
convert_units_private(value, formula, startunit, endunit)
} else {
value * lookup
}
}


#' @title Calculate hardness from calcium and magnesium
#'
Expand Down
Binary file added R/sysdata.rda
Binary file not shown.
95 changes: 95 additions & 0 deletions data-raw/compile_tidywater_data.R
Original file line number Diff line number Diff line change
Expand Up @@ -785,3 +785,98 @@ bromatecoeffs <- data.frame(
I = 1 # temp not in exponent
)
usethis::use_data(bromatecoeffs, overwrite = TRUE)

# For all units accepted by the convert_units function
# provide their SI base multipliers
unit_multipliers <- data.frame(
# base
"g/L" = 1,
"g/L CaCO3" = 1,
"g/L N" = 1,
"M" = 1,
"eq/L" = 1,
# milli
"mg/L" = 1e-3,
"mg/L CaCO3" = 1e-3,
"mg/L N" = 1e-3,
"mM" = 1e-3,
"meq/L" = 1e-3,
# micro
"ug/L" = 1e-6,
"ug/L CaCO3" = 1e-6,
"ug/L N" = 1e-6,
"uM" = 1e-6,
"ueq/L" = 1e-6,
# nano
"ng/L" = 1e-9,
"ng/L CaCO3" = 1e-9,
"ng/L N" = 1e-9,
"nM" = 1e-9,
"neq/L" = 1e-9,
# required to allow names with slashes
check.names = FALSE
)

# Used as part of convert units
# Provides a quick lookup of the charge of a given formula
formula_to_charge <- data.frame(
"na" = 1,
"k" = 1,
"cl" = 1,
"hcl" = 1,
"naoh" = 1,
"nahco3" = 1,
"nh4" = 1,
"f" = 1,
"br" = 1,
"bro3" = 1,
"so4" = 2,
"caco3" = 2,
"h2so4" = 2,
"na2co3" = 2,
"caoh2" = 2,
"mgoh2" = 2,
"mg" = 2,
"ca" = 2,
"pb" = 2,
"cacl2" = 2,
"mn" = 2,
"h3po4" = 3,
"al" = 3,
"fe" = 3,
"alum" = 3,
"fecl3" = 3,
# TODO ASK SIERRA (fe2so43 is not in mweights)
# "fe2so43"= 3,
"po4" = 3
)

# This function is used to generate a fast lookup table to speed up unit conversions.
# We precompute all permutations of our normal conversions and store them in a hash map.
generate_unit_conversions_cache <- function() {
# All units we support
units <- ls(unit_multipliers)
# All formulas we support are in mweights, note this is more than formula_to_charge
formulas <- ls(mweights)
env <- new.env(parent = emptyenv())
for (startunit in units) {
for (endunit in units) {
for (formula in formulas) {
name <- paste(formula, startunit, endunit)
# Not all unit conversions will be valid
# Try them all and we won't store any that fail
try(
{
env[[name]] <- convert_units_private(1.0, formula, startunit, endunit)
},
silent = TRUE
)
}
}
}
env
}

convert_units_cache <- generate_unit_conversions_cache()

usethis::use_data(unit_multipliers, formula_to_charge, convert_units_cache, overwrite = TRUE, internal = TRUE)
11 changes: 0 additions & 11 deletions man/empty.Rd

This file was deleted.

Loading