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 3 commits into
base: main
Choose a base branch
from
Open
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
208 changes: 137 additions & 71 deletions R/general.R
Original file line number Diff line number Diff line change
Expand Up @@ -247,109 +247,124 @@ plot_ions <- function(water) {
}


#' @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")

# Using a some package local environments to cache some hashmaps for fast lookups
# Create an environment to act as a hashmap:
# See: https://riptutorial.com/r/example/18339/environments-as-hash-maps
convert_units.unit_to_mult <- new.env(parent = emptyenv())
# All the standard units
convert_units.unit_to_mult[["g/L"]] <- 1
convert_units.unit_to_mult[["g/L CaCO3"]] <- 1
convert_units.unit_to_mult[["g/L N"]] <- 1
convert_units.unit_to_mult[["M"]] <- 1
convert_units.unit_to_mult[["eq/L"]] <- 1
# All the milli units
convert_units.unit_to_mult[["mg/L"]] <- 1e-3
convert_units.unit_to_mult[["mg/L CaCO3"]] <- 1e-3
convert_units.unit_to_mult[["mg/L N"]] <- 1e-3
convert_units.unit_to_mult[["mM"]] <- 1e-3
convert_units.unit_to_mult[["meq/L"]] <- 1e-3
# All the mirco units
convert_units.unit_to_mult[["ug/L"]] <- 1e-6
convert_units.unit_to_mult[["ug/L CaCO3"]] <- 1e-6
convert_units.unit_to_mult[["ug/L N"]] <- 1e-6
convert_units.unit_to_mult[["uM"]] <- 1e-6
convert_units.unit_to_mult[["ueq/L"]] <- 1e-6
# All the nano units
convert_units.unit_to_mult[["ng/L"]] <- 1e-9
convert_units.unit_to_mult[["ng/L CaCO3"]] <- 1e-9
convert_units.unit_to_mult[["ng/L N"]] <- 1e-9
convert_units.unit_to_mult[["nM"]] <- 1e-9
convert_units.unit_to_mult[["neq/L"]] <- 1e-9

convert_units.formula_to_charge <- new.env(parent = emptyenv())
convert_units.formula_to_charge[["na"]] <- 1
convert_units.formula_to_charge[["k"]] <- 1
convert_units.formula_to_charge[["cl"]] <- 1
convert_units.formula_to_charge[["hcl"]] <- 1
convert_units.formula_to_charge[["naoh"]] <- 1
convert_units.formula_to_charge[["nahco3"]] <- 1
convert_units.formula_to_charge[["nh4"]] <- 1
convert_units.formula_to_charge[["na"]] <- 1
convert_units.formula_to_charge[["f"]] <- 1
convert_units.formula_to_charge[["br"]] <- 1
convert_units.formula_to_charge[["bro3"]] <- 1
convert_units.formula_to_charge[["so4"]] <- 2
convert_units.formula_to_charge[["caco3"]] <- 2
convert_units.formula_to_charge[["h2so4"]] <- 2
convert_units.formula_to_charge[["na2co3"]] <- 2
convert_units.formula_to_charge[["caoh2"]] <- 2
convert_units.formula_to_charge[["mgoh2"]] <- 2
convert_units.formula_to_charge[["mg"]] <- 2
convert_units.formula_to_charge[["ca"]] <- 2
convert_units.formula_to_charge[["pb"]] <- 2
convert_units.formula_to_charge[["cacl2"]] <- 2
convert_units.formula_to_charge[["mn"]] <- 2
convert_units.formula_to_charge[["h3po4"]] <- 3
convert_units.formula_to_charge[["al"]] <- 3
convert_units.formula_to_charge[["fe"]] <- 3
convert_units.formula_to_charge[["alum"]] <- 3
convert_units.formula_to_charge[["fecl3"]] <- 3
# TODO ASK SIERRA (fe2so43 is not in mweights)
#convert_units.formula_to_charge[["fe2so43"]] <- 3
convert_units.formula_to_charge[["po4"]] <- 3

# Internal conversion function
convert_units_private <- function(value, formula, startunit = "mg/L", endunit = "M") {
gram_list <- c(
"ng/L", "ug/L", "mg/L", "g/L",
"ng/L CaCO3", "ug/L CaCO3", "mg/L CaCO3", "g/L CaCO3",
"ng/L N", "ug/L N", "mg/L N", "g/L N"
)
mole_list <- c("M", "mM", "uM", "nM")
eqvl_list <- c("neq/L", "ueq/L", "meq/L", "eq/L")

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 <- convert_units.unit_to_mult[[startunit]]
end_mult <- convert_units.unit_to_mult[[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"])
n_mw <- as.numeric(tidywater::mweights["n"])

# Determine relevant molar weight
if (formula %in% colnames(tidywater::mweights)) {
if ((startunit %in% caco_list & endunit %in% c(mole_list, eqvl_list)) |
(endunit %in% caco_list & startunit %in% c(mole_list, eqvl_list))) {
(endunit %in% caco_list & startunit %in% c(mole_list, eqvl_list))) {
molar_weight <- caco3_mw
} else if ((startunit %in% n_list & endunit %in% c(mole_list, eqvl_list)) |
(endunit %in% n_list & startunit %in% c(mole_list, eqvl_list))) {
(endunit %in% n_list & startunit %in% c(mole_list, eqvl_list))) {
molar_weight <- n_mw
} else {
molar_weight <- as.numeric(tidywater::mweights[formula])
}
} 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 <- convert_units.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
} else {
stop("Unable to find charge for equivalent conversion")
}

# Unit conversion
# g - mol
if (startunit %in% gram_list & endunit %in% mole_list) {
Expand Down Expand Up @@ -378,14 +393,65 @@ convert_units <- function(value, formula, startunit = "mg/L", endunit = "M") {
value / molar_weight * n_mw
# same lists
} else if ((startunit %in% gram_list & endunit %in% gram_list) |
(startunit %in% mole_list & endunit %in% mole_list) |
(startunit %in% eqvl_list & endunit %in% eqvl_list)) {
(startunit %in% mole_list & endunit %in% mole_list) |
(startunit %in% eqvl_list & endunit %in% eqvl_list)) {
value * multiplier
} else {
stop("Units not supported")
}
}

generate_conversions_table <- function() {
# All units we support
units <- ls(convert_units.unit_to_mult)
# All formulas we support
formulas <- ls(convert_units.formula_to_charge)
env <- new.env(parent = emptyenv())
for (startunit in units) {
for (endunit in units) {
for (formula in formulas) {
name <- paste(formula,startunit,endunit)
env[[name]] <- convert_units_private(1.0, formula, startunit, endunit)
}
}
}
env
}

# Pre-compute all unit conversions and store in env on package load
# Could probably put this in a .rdata file instead?
convert_units.super_table <- generate_conversions_table()


#' @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") {
lookup <- convert_units.super_table[[paste(formula, startunit, endunit)]]
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