diff --git a/R/chemistry-tools.R b/R/chemistry-tools.R index b660ea9..cf91b49 100644 --- a/R/chemistry-tools.R +++ b/R/chemistry-tools.R @@ -16,140 +16,3 @@ molarity2mass <- function(formulamass, volume, molarity) { # [g * mol-1] * [liter] * [mole * liter-1] = [g] return(mass) } - - - -#' Vapour pressure of water -#' -#' Vapour pressure of water as a function of temperature -#' This function returns the vapour pressure of water at the given -#' temperature(s) from the common::vapourwater dataset. -#' -#' @param temperature numeric vector, in degrees Celsius -#' -#' @return vapour pressure of water, in kilopascal -#' @export -#' -#' @examples -#' \dontrun{ -#' VapourPressureWater(45) -#' VapourPressureWater(c(20, 25, 45, 60)) -#' } -VapourPressureWater <- function(temperature) { - data <- common::vapourwater - # if T outside range in data, warn the user - if (any(temperature < min(data$temperature)) | any(temperature > max(data$temperature))) { - warning("At least one supplied temperature is outside the data range (", - paste(range(data$temperature), collapse=" - "), - " Celsius). Returning NAs for those values.") - } - # The vapourwater dataset only contains data for every degree celsius (or less), - # so we use the interpolation function to fill in the rest. - # Note that approx(rule = 1) returns NAs for input outside the data range. - pressure <- - stats::approx(x = data$temperature, y = data$pressure, - method = "linear", rule = 1, - xout = temperature)$y - return(pressure) -} - - - -#' Oxygen solubility in water -#' -#' Oxygen solubility in water which is in contact with -#' air saturated with water vapour, as a function of -#' temperature and at a total pressure of 760 torr. -#' -#' Some background: as the temperature of a gasesous solution is raised the -#' gas is driven off until complete degassing occurs at the boiling point -#' of the solvent. This variation of solubility with temperature can be -#' derived from thermodynamic first principles. -#' But the variation of oxygen solubility in water cannot be represented by a -#' simple relationship (derived from thermodynamic first principles), and so -#' more complicated expressions which are fitted to empirical data have -#' to be used. -#' -#' Hitchman, Measurement of Dissolved Oxygen, 1978 reproduce a table by -#' Battino and Clever (1966) that presents experimental values of the -#' so-called Bunsen absorption coefficient (this is the volume of gas, at 0 C -#' and 760 torr, that, at the temperature of measurement, is dissolved in one -#' volume of the solvent when the partial pressure of the gas is 760 torr) -#' recorded by eleven research groups up until 1965. The standard error of the -#' mean value is never greater +-0.5%. The mean values from this table are -#' probably accurate enough for most applications. -#' Hitchman notes that the data in this table can be fitted by two forms of -#' equations: one form obtained from Henry's law (under the restriction that -#' the partial pressure of the gas remains constant), and another form by -#' describing the variation with temperature by fitting a general power series. -#' The latter approach is used in this function. -#' -#' Hitchman chooses to fit a fourth degree polynomial, and found that the -#' square of the correlation coefficient was 0.999996. -#' -#' For more background and detailed derivation of the formula used here, -#' see section 2.2 (pp. 11) in Hitchman. -#' -#' This formula is strictly speaking only valid for 0 < T < 50 celsius. -#' The function will return values outside this range, but with a warning. -#' -#' @param temperature numeric, vector. In degrees Celsius. -#' -#' @return a dataframe with the following columns: -#' + "temperature" same as the supplied temperature -#' + "g/cm-3" oxygen solubility expressed as gram per cubic cm -#' + "mg/L" ditto expressed as milligram per litre -#' + "mol/L" ditto expressed as moles per litre (molarity) -#' + "permoleculewater" number of O2 molecules per molecule of water -#' Note: mg/L is equivalent to ppm by weight (since water has approx -#' unit density in the temperature range 0-50 Celsius). -#' @export -#' @examples -#' \dontrun{ -#' OxygenSolubilityWater(22) -#' OxygenSolubilityWater(c(2, 7, 12, 30)) -#' } -OxygenSolubilityWater <- function(temperature) { - if (any(temperature < 0) | any(temperature > 50)) { - warning("This function is fitted to data within the range 0 - 50 Celsius. ", - "You are now extrapolating.") - } - # formula weight of oxygen - oxygen.fw <- 15.9994 # gram per mole - # formula weight of water - water.fw <- 18.02 - # oxygen ratio in dry air (20.95%) - oxygen.dryair <- 20.95 / 100 - # coefficients (from Hitchman) - A <- 4.9E1 - B <- -1.335 - C <- 2.759E-2 - D <- -3.235E-4 - E <- 1.614E-6 - # conversion factor from Bunsen to g/cm-3 - conv.factor <- (2 * oxygen.fw) / 22.414E3 - # Bunsen absorption coefficient, commonly denoted as Greek alpha - alpha <- - 1E-3 * (A + B * temperature + C * temperature^2 + - D * temperature^3 + E * temperature^4) - # solubility of oxygen, in gram per cm-3 - oxygen.solubility <- - data.frame(temperature = temperature, - grampercm3 = - (conv.factor * oxygen.dryair / 760) * alpha * - # keep in mind that VapourPressureWater() returns values in kilopascal - (760 - pascal2torr(1E3 * common::VapourPressureWater(temperature))), - mgperlitre = - 1E6 * (conv.factor * oxygen.dryair / 760) * alpha * - (760 - pascal2torr(1E3 * common::VapourPressureWater(temperature))), - molperlitre = - 1E3 * (conv.factor * oxygen.dryair / 760) * alpha * - (760 - pascal2torr(1E3 * common::VapourPressureWater(temperature))) / - (2 * oxygen.fw)) - # Number of O2 molecules per water molecule - oxygen.solubility$permoleculewater <- - # note: using a water density value that's approx the average in the - # temperature range 0 - 50 Celsius - ((1E3 * oxygen.solubility$grampercm3) / oxygen.fw) / (995.00 / water.fw) - return(oxygen.solubility) -} diff --git a/R/data.R b/R/data.R deleted file mode 100644 index 7780215..0000000 --- a/R/data.R +++ /dev/null @@ -1,18 +0,0 @@ -#' @name vapourwater -#' @title Vapour pressure and other saturation properties of water -#' @description A dataset summarising vapour pressure, enthalpy of vapourisation, -#' and surface tension of water from 0.01 Celsius to 373.95 Celsius. -#' Data as accepted by the International Association for the Properties -#' of Water and Steam for general scientific use. -#' Source: CRC handbook, 94th ed., table 6-10-90, Eric W. Lemmon. -#' @docType data -#' @format A data frame with 189 rows and 4 variables: -#' \describe{ -#' \item{temperature}{temperature/celsius} -#' \item{pressure}{pressure/kilopascal} -#' \item{enthalpy}{enthalpy of vapourisation/kilojoule per kilogram} -#' \item{surfacetension}{surface tension/millinewton per metre} -#' } -#' @source Handbook of Chemistry and Physics, 94th ed., 6-10-90, Eric W. Lemmon. -#' @author Taha Ahmed -NULL diff --git a/R/unit-converters-electrochemical.R b/R/unit-converters-electrochemical.R deleted file mode 100644 index 4df2b50..0000000 --- a/R/unit-converters-electrochemical.R +++ /dev/null @@ -1,871 +0,0 @@ -#' AVS -> SHE -#' -#' Converts from absolute vacuum scale (AVS) to SHE scale -#' -#' @param avs Potential in AVS scale -#' -#' @return potential in SHE scale (numeric) -#' @export -AVS2SHE <- function(avs) { - .Deprecated("as.SHE") - she <- -(4.5 + avs) - return(she) -} - - -#' SHE -> AVS -#' -#' Converts from SHE scale to absolute vacuum (AVS) scale -#' -#' @param she Potential in SHE scale -#' -#' @return potential in AVS scale (numeric) -#' @export -SHE2AVS <- function(she) { - .Deprecated("as.SHE") - avs <- -(4.5 + she) - return(avs) -} - - - -#' Get standardised name of reference electrode -#' -#' Given a reference electrode label, this function returns its canonical name -#' (as defined by this package). -#' This function tries to match against as many variations as possible for each -#' reference electrode. -#' The entire point of this function is to decrease the mental load on the user -#' by not requiring them to remember a particular label or name for each reference -#' electrode, instead almost any sufficiently distinct label or string will still -#' be correctly identified. -#' -#' @param refname string or a vector of strings -#' -#' @return vector with corresponding "canonical" name or empty string (if none found) -#' @export -RefCanonicalName <- function(refname) { - # scale names - electrode.system <- list() - electrode.system[["SHE"]] <- - c("SHE", - "Standard hydrogen", - "Standard hydrogen electrode") - electrode.system[["AgCl/Ag"]] <- - c("AgCl/Ag", - "Ag/AgCl", - "AgCl", - "Silver-Silver chloride", - "Silver chloride", - "SSC") # Sometimes used abbr. for Saturated Silver Chloride - electrode.system[["Hg2Cl2/Hg"]] <- - c("Hg2Cl2/Hg", - "Hg/Hg2Cl2", - "Hg2Cl2", - "Calomel-Mercury", - "Mercury-Calomel", - "Calomel", - "SCE") - electrode.system[["AVS"]] <- - c("AVS", - "Vacuum", - "Vacuum scale", - "Absolute", - "Absolute scale", - "Absolute vacuum scale") - electrode.system[["Li"]] <- - c("Li", - "Li/Li+", - "Li+/Li", - "Lithium") - electrode.system[["Na"]] <- - c("Na", - "Na+/Na", - "Na/Na+", - "Sodium") - electrode.system[["Mg"]] <- - c("Mg", - "Mg2+/Mg", - "Mg/Mg2+", - "Magnesium") - - # if no argument or empty string supplied as arg, return the entire list as df - # to give the user a nice overview of all available options - if (missing(refname) || refname == "") { - max.row.length <- 0 - for (i in 1:length(electrode.system)) { - # find the longest row and save its length - this.row.length <- length(electrode.system[[i]]) - if (this.row.length > max.row.length) max.row.length <- this.row.length - } - # initialise an empty df with dimensions that fit electrode.system - overview.names <- - data.frame( - structure(dimnames = - list( - # rownames - seq(1, length(electrode.system)), - # colnames - c("canonical", paste0("option", seq(1, max.row.length - 1)))), - matrix("", - nrow = length(electrode.system), - ncol = max.row.length, - byrow = TRUE)), - stringsAsFactors = FALSE) - # now populate the df - for (i in 1:length(electrode.system)) { - this.row.length <- length(electrode.system[[i]]) - overview.names[i,1:this.row.length] <- electrode.system[[i]] - } - message(paste0("You did not specify any reference electrode name.\n", - "Here are the options supported by this function (case-insensitive):")) - print(knitr::kable(overview.names)) - } - - # defining refname in this manner makes sure to get all possible combinations - # but there might be a number of duplicates, but those we can - # get rid of in the next step - electrode <- - data.frame(refname = - # here we create lower-case version of electrode.system, - # a version with symbols (-/) subbed with spaces, - # and a lower-case with symbols subbed with spaces - c(unname(unlist(electrode.system)), - tolower(unname(unlist(electrode.system))), - gsub("[-/]", " ", unname(unlist(electrode.system))), - gsub("[-/]", " ", tolower(unname(unlist(electrode.system))))), - refcanon = - rep(sub("[0-9]$", "", names(unlist(electrode.system))), - 4), # this number needs to equal number of elements in c() above! - stringsAsFactors = FALSE) - # detect and remove duplicates - electrode <- - electrode[!duplicated(electrode$refname),] - # reset row numbering in dataframe just for good measure - row.names(electrode) <- 1:dim(electrode)[1] - - # pre-allocate the return vector - refcanon <- rep("", length(refname)) - # now all we have to do is check each user-submitted refname against - # electrode$refname and return the value on the same row but next column - for (i in 1:length(refname)) { - refcanon[i] <- - electrode$refcanon[which(electrode$refname == refname[i])] - } - - return(refcanon) -} - - - - -#' Potentials as SHE -#' -#' This function just outputs a tidy dataframe with potential vs SHE for -#' different scales, electrolytes, concentrations, and temperatures. -#' Using data from literature. -#' -#' @return tidy dataframe with the following columns -#' \tabular{ll}{ -#' \code{electrode} \tab reference electrode \cr -#' \code{electrolyte} \tab electrolyte \cr -#' \code{conc.num} \tab concentration of electrolyte, mol/L \cr -#' \code{conc.string} \tab concentration of electrolyte, as string, may also note temperature at which conc \cr -#' \code{temp} \tab temperature / degrees Celsius \cr -#' \code{SHE} \tab potential vs SHE / volt \cr -#' \code{sid} \tab set id, just for housekeeping inside this function \cr -#' \code{reference} \tab BibTeX reference \cr -#' \code{dEdT} \tab temperature coefficient / volt/kelvin \cr -#' } -#' @export -potentials.as.SHE <- function() { - # scale name should be one of canonical (see RefCanonicalName) - - # follow the convention of "each row one observation" (at different temperatures) - # all potentials vs SHE - - potentials <- - as.data.frame( - matrix(data = - # electrode # electrolyte # conc/M # conc label # temp # pot vs SHE # set id # ref - c("AgCl/Ag", "NaCl(aq)", "5.9", "saturated", "25", "0.2630", "9", "CRC 97th ed., 97-05-22", - "AgCl/Ag", "KCl(aq)", "3.5", "3.5M at 25C", "10", "0.215", "1", "Sawyer1995", - "AgCl/Ag", "KCl(aq)", "3.5", "3.5M at 25C", "15", "0.212", "1", "Sawyer1995", - "AgCl/Ag", "KCl(aq)", "3.5", "3.5M at 25C", "20", "0.208", "1", "Sawyer1995", - "AgCl/Ag", "KCl(aq)", "3.5", "3.5M at 25C", "25", "0.205", "1", "Sawyer1995", - "AgCl/Ag", "KCl(aq)", "3.5", "3.5M at 25C", "30", "0.201", "1", "Sawyer1995", - "AgCl/Ag", "KCl(aq)", "3.5", "3.5M at 25C", "35", "0.197", "1", "Sawyer1995", - "AgCl/Ag", "KCl(aq)", "3.5", "3.5M at 25C", "40", "0.193", "1", "Sawyer1995", - "AgCl/Ag", "KCl(aq)", "4.2", "saturated", "10", "0.214", "2", "Sawyer1995", - "AgCl/Ag", "KCl(aq)", "4.2", "saturated", "15", "0.209", "2", "Sawyer1995", - "AgCl/Ag", "KCl(aq)", "4.2", "saturated", "20", "0.204", "2", "Sawyer1995", - "AgCl/Ag", "KCl(aq)", "4.2", "saturated", "25", "0.199", "2", "Sawyer1995", - "AgCl/Ag", "KCl(aq)", "4.2", "saturated", "30", "0.194", "2", "Sawyer1995", - "AgCl/Ag", "KCl(aq)", "4.2", "saturated", "35", "0.189", "2", "Sawyer1995", - "AgCl/Ag", "KCl(aq)", "4.2", "saturated", "40", "0.184", "2", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "0.1", "0.1M at 25C", "10", "0.336", "3", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "0.1", "0.1M at 25C", "15", "0.336", "3", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "0.1", "0.1M at 25C", "20", "0.336", "3", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "0.1", "0.1M at 25C", "25", "0.336", "3", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "0.1", "0.1M at 25C", "30", "0.335", "3", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "0.1", "0.1M at 25C", "35", "0.334", "3", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "0.1", "0.1M at 25C", "40", "0.334", "3", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "1.0", "1.0M at 25C", "10", "0.287", "4", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "1.0", "1.0M at 25C", "20", "0.284", "4", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "1.0", "1.0M at 25C", "25", "0.283", "4", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "1.0", "1.0M at 25C", "30", "0.282", "4", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "1.0", "1.0M at 25C", "40", "0.278", "4", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "3.5", "3.5M at 25C", "10", "0.256", "5", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "3.5", "3.5M at 25C", "15", "0.254", "5", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "3.5", "3.5M at 25C", "20", "0.252", "5", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "3.5", "3.5M at 25C", "25", "0.250", "5", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "3.5", "3.5M at 25C", "30", "0.248", "5", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "3.5", "3.5M at 25C", "35", "0.246", "5", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "3.5", "3.5M at 25C", "40", "0.244", "5", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "4.2", "saturated", "10", "0.254", "6", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "4.2", "saturated", "15", "0.251", "6", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "4.2", "saturated", "20", "0.248", "6", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "4.2", "saturated", "25", "0.244", "6", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "4.2", "saturated", "30", "0.241", "6", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "4.2", "saturated", "35", "0.238", "6", "Sawyer1995", - "Hg2Cl2/Hg", "KCl(aq)", "4.2", "saturated", "40", "0.234", "6", "Sawyer1995", - "AVS", "", "", "", "25", "-4.44", "7", "Trasatti1986", - "SHE", "", "", "", "-273.15", "0.00", "8", "Inzelt2013", - "SHE", "", "", "", "0", "0.00", "8", "Inzelt2013", - "SHE", "", "", "", "25", "0.00", "8", "Inzelt2013", - # arbitrary max T=580C (temp at which sodalime glass loses rigidity) - "SHE", "", "", "", "580", "0.00", "8", "Inzelt2013", - "Li", "", "1.0", "1.0M at 25C", "25", "-3.0401", "10", "CRC 97th ed., 97-05-22", - "Na", "", "1.0", "1.0M at 25C", "25", "-2.71", "11", "CRC 97th ed., 97-05-22", - "Mg", "", "1.0", "1.0M at 25C", "25", "-2.372", "12", "CRC 97th ed., 97-05-22"), - ncol = 8, - byrow = TRUE), stringsAsFactors = FALSE) - - colnames(potentials) <- - c("electrode", - "electrolyte", - "conc.num", - "conc.string", - "temp", - "SHE", - "sid", - "reference") - - # convert these columns to type numeric - potentials[, c("conc.num", "temp", "SHE")] <- - as.numeric(as.character(unlist(potentials[, c("conc.num", "temp", "SHE")]))) - - # make room for a dE/dT column - potentials$dEdT <- as.numeric(NA) - - # calculate temperature coefficient (dE/dT) for each scale, concentration, and electrolyte (ie. set id) - for (s in 1:length(unique(potentials$sid))) { - # sid column eas added to data just to make this calculation here easier - subspot <- potentials[which(potentials$sid == unique(potentials$sid)[s]), ] - # a linear fit will give us temperature coefficient as slope - lm.subspot <- stats::lm(SHE ~ temp, data = subspot) - potentials[which(potentials$sid == unique(potentials$sid)[s]), "dEdT"] <- - lm.subspot$coefficients[2] - } - - return(potentials) -} - - - - -#' Convert from electrochemical or physical scale to SHE -#' -#' Convert an arbitrary number of potentials against any known electrochemical -#' scale (or the electronic vacuum scale) to potential vs SHE. -#' -#' @param potential potential in volt -#' @param scale name of the original scale -#' @param electrolyte optional, specify electrolyte solution, e.g., "KCl(aq)". Must match value in \code{as.SHE.data$electrolyte}. -#' @param concentration of electrolyte in mol/L, or as the string "saturated" -#' @param temperature of system in degrees Celsius -#' @param as.SHE.data dataframe with dataset -#' -#' @return potential in SHE scale -#' @export -as.SHE <- function(potential, - scale, - electrolyte = "", - concentration = "saturated", - temperature = 25, - as.SHE.data = potentials.as.SHE()) { - - # if the supplied temperature does not exist in the data, this function will attempt to interpolate - # note that concentration has to match, no interpolation is attempted for conc - - # potential and scale vectors supplied by user could have arbitrary length - # just make sure potential and scale args have the same length (or length(scale) == 1) - if (length(potential) == 0 | length(scale) == 0) { - stop("Potential or scale arguments cannot be empty!") - } else if (length(potential) != length(scale)) { - # stop, unless length(scale) == 1 where we will assume it should be recycled - if (length(scale) == 1) { - message("Arg has unit length. We'll recycle it to match length of .") - scale <- rep(scale, length(potential)) - } else { - stop("Length of and must be equal OR may be unit length.") - } - } - - arglength <- length(potential) - # make the args concentration, temperature and electrolyte this same length, - # unless the user supplied them (only necessary for length > 1) - if (arglength > 1) { - # handle two cases: - # 1. user did not touch concentration, temperature and electrolyte args. - # Assume they forgot and reset their length and print a message - # 2. user did change concentration or temperature or electrolyte, but still failed to - # ensure length equal to arglength. In this case, abort. - # note: we can get the default value set in the function call using formals() - if (identical(concentration, formals(as.SHE)$concentration) & - identical(temperature, formals(as.SHE)$temperature) & - identical(electrolyte, formals(as.SHE)$electrolyte)) { - # case 1 - # message("NOTE: default concentration and temperature values used for all potentials and scales.") - message(paste0("The default concentration (", formals(as.SHE)$concentration, ") and temperature (", formals(as.SHE)$temperature, "C) will be assumed for all your potential/scale values.")) - concentration <- rep(concentration, arglength) - temperature <- rep(temperature, arglength) - electrolyte <- rep(electrolyte, arglength) - } else { - # case 2 - stop("Concentration, temperature and electrolyte arguments must have the same number of elements as potential and scale!") - } - } - - ## we can now safely assume that length() == arglength - # place args into a single dataframe - # this way, we can correlate columns to each other by row - dfargs <- - data.frame(potential = potential, - scale = common::RefCanonicalName(scale), - electrolyte = electrolyte, - concentration = concentration, - temperature = temperature, - stringsAsFactors = FALSE) - # add column to keep track of vacuum scale - # dfargs$vacuum <- as.logical(FALSE) - # add column to hold calc potential vs SHE - dfargs$SHE <- as.numeric(NA) - - ## From here on, ONLY access the arguments via this dataframe - ## That is, use dfargs$electrolyte, NOT electrolyte - - # SHE scale special considerations - # 1. concentration is constant for SHE - if (any(dfargs$scale == common::RefCanonicalName("SHE"))) { - dfargs$concentration[which(dfargs$scale == common::RefCanonicalName("SHE"))] <- "" - dfargs$electrolyte[which(dfargs$scale == common::RefCanonicalName("SHE"))] <- "" - } - - # AVS scale special considerations - # 1. concentration is meaningless for AVS - if (any(dfargs$scale == common::RefCanonicalName("AVS"))) { - # concentration is meaningless for AVS (no electrolyte) so for those rows, we'll reset it - dfargs$concentration[which(dfargs$scale == common::RefCanonicalName("AVS"))] <- "" - dfargs$electrolyte[which(dfargs$scale == common::RefCanonicalName("AVS"))] <- "" - # dfargs$vacuum[which(dfargs$scale == common::RefCanonicalName("AVS"))] <- TRUE - } - - # now just work our way through dfargs, line-by-line to determine potential as SHE - # all necessary conditions should be recorded right here in dfargs - for (p in 1:dim(dfargs)[1]) { - ## WE ARE NOW WORKING ROW-BY-ROW THROUGH THE SUPPLIED ARGUMENTS IN dfargs - # Step-wise matching: - # + first, we subset against electrode scale. If dataset only has one row, done. Else, - # + we subset against either conc.string or conc.num. Stop if zero rows in dataset (error), otherwise proceed. - # Our "dataset" is the literature data supplied via the argument as.SHE.data - this.data.scale <- subset(as.SHE.data, electrode == dfargs$scale[p]) - # subset.scale <- subset(as.SHE.data, electrode == dfargs$scale[p]) - if (dim(this.data.scale)[1] > 1) { - # continue matching, now against conc.string or conc.num - if (is.character(dfargs$concentration[p])) { - this.data.concentration <- - # subset.concentration <- - subset(this.data.scale, conc.string == dfargs$concentration[p]) - } else { - this.data.concentration <- - # subset.concentration <- - subset(this.data.scale, conc.num == dfargs$concentration[p]) - } - # stop if the resulting dataframe after matching contains no rows - if (dim(this.data.concentration)[1] == 0) { - stop(paste0("Failed to find any matching entries in dataset for ", - paste(dfargs[p, ], collapse = " ", sep = ""))) - } - # Note: it's ok at this point if the resulting dataset contains more than one row as - # more matching will be done below - # If we haven't had reason to stop(), we should be good - # just housekeeping: rename the variable so we don't have to edit code below - this.SHE.data <- this.data.concentration - # subset.SHE.data <- subset.concentration - } else { - # just housekeeping again - this.SHE.data <- this.data.scale - # subset.SHE.data <- subset.scale - } - - - ## Electrolyte - # == We would like to transparently handle the following scenario: - # || if the user did not specify electrolyte solution (which we can check by using formals()) - # || but the dataset (after subsetting against scale and concentration above) still contains - # || more than one electrolyte - # >> Approach: we'll specify a "fallback" electrolyte, KCl (usually that's what the user wants) - # >> and inform/warn about it - - # KCl is a good assumption, as we always have KCl - # for the cases where an electrode system has more than one electrolyte - fallback.electrolyte <- "KCl(aq)" - if (length(unique(this.SHE.data$electrolyte)) > 1) { - if (formals(as.SHE)$electrolyte == "") { - warning(paste0("More than one electrolyte ", - "available for E(", dfargs$scale[p], ") in dataset. ", - "I'll assume you want ", fallback.electrolyte, ".")) - this.SHE.data <- - subset(this.SHE.data, electrolyte == fallback.electrolyte) - } else { - # else the user did change the electrolyte arg, use the user's value - this.SHE.data <- - subset(this.SHE.data, electrolyte == dfargs$electrolyte[p]) - # but stop if the resulting dataframe contains no rows - if (dim(this.SHE.data)[1] == 0) stop("Your choice of electrolyte does not match any data!") - } - } else { - # dataset contains only one unique electrolyte - # again, check if electrolyte in arg matches the one in dataset - # if it does, great, if it does not, print a message and use it anyway - if (unique(this.SHE.data$electrolyte) == dfargs$electrolyte[p]) { - this.SHE.data <- - subset(this.SHE.data, electrolyte == dfargs$electrolyte[p]) - } else { - # whatever electrolyte the user supplied does not match what's left in the datasubset - # but at this point the user is probably better served by returning the electrolyte we have - # along with an informative message (that's the only reason for the if-else below) - electrolytes.in.subset <- - unique(subset(as.SHE.data, electrode == dfargs$scale[p])$electrolyte) - if (dfargs$electrolyte[p] == "") { - message( - paste0('Electrolyte "" (empty string) not in dataset for E(', - dfargs$scale[p], '). ', - 'These electrolytes are: ', - paste(electrolytes.in.subset, collapse = ', or '), '.', - "I'll assume you want ", fallback.electrolyte, ".") - ) - } else { - message(paste0("Electrolyte ", dfargs$electrolyte[p], " not in dataset for E(", - dfargs$scale[p], "). ", - "These electrolytes are: ", - paste(electrolytes.in.subset, collapse = ", or "), ".", - "I'll assume you want ", fallback.electrolyte, ".") - ) - } - } - } - - # temperature - # either happens to match a temperature in the dataset, or we interpolate - # (under the assumption that potential varies linearly with temperature) - if (!any(this.SHE.data$temp == dfargs$temperature[p])) { - # sought temperature was not available in dataset, check that it falls inside - # note: important to use less/more-than-or-equal in case data only contains one value - if ((dfargs$temperature[p] <= max(this.SHE.data$temp)) && (dfargs$temperature[p] >= min(this.SHE.data$temp))) { - # within dataset range, do linear interpolation - lm.subset <- stats::lm(SHE ~ temp, data = this.SHE.data) - # interpolated temperature, calculated based on linear regression - # (more accurate than simple linear interpolation with approx()) - pot.interp <- - lm.subset$coefficients[2] * dfargs$temperature[p] + lm.subset$coefficients[1] - ### CALC POTENTIAL vs SHE - dfargs$SHE[p] <- - ifelse(dfargs$scale[p] == "AVS", - pot.interp - dfargs$potential[p], - pot.interp + dfargs$potential[p]) - } - } else { - # requested temperature does exist in dataset - ### CALC POTENTIAL vs SHE - dfargs$SHE[p] <- - ifelse(dfargs$scale[p] == "AVS", - subset(this.SHE.data, temp == dfargs$temperature[p])$SHE - dfargs$potential[p], - subset(this.SHE.data, temp == dfargs$temperature[p])$SHE + dfargs$potential[p]) - } - } - - return(dfargs$SHE) -} - - - - - -#' Convert from SHE scale to another electrochemical or physical scale -#' -#' Convert an arbitrary number of potentials vs SHE to another electrochemical -#' scale (or the vacuum scale). -#' The available target scales are those listed by \code{\link{potentials.as.SHE}}. -#' -#' @param potential potential in volt -#' @param scale name of the target scale -#' @param electrolyte optional, specify electrolyte solution, e.g., "KCl(aq)". Must match one of the values in \code{\link{potentials.as.SHE}$electrolyte} -#' @param concentration of electrolyte in mol/L, or as the string "saturated" -#' @param temperature of system in degrees Celsius -#' @param as.SHE.data by default this parameter reads the full dataset \code{\link{potentials.as.SHE}} -#' -#' @return potential in the specified target scale -#' @export -from.SHE <- function(potential, - scale, - electrolyte = "", - concentration = "saturated", - temperature = 25, - as.SHE.data = potentials.as.SHE()) { - - # if the supplied temperature does not exist in the data, this function will attempt to interpolate - # note that concentration has to match, no interpolation is attempted for conc - - # potential and scale vectors supplied by user could have arbitrary length - # just make sure potential and scale args have the same length (or length(scale) == 1) - if (length(potential) == 0 | length(scale) == 0) { - stop("Potential or scale arguments cannot be empty!") - } else if (length(potential) != length(scale)) { - # stop, unless length(scale) == 1 where we will assume it should be recycled - if (length(scale) == 1) { - message("Arg has unit length. We'll recycle it to match length of .") - scale <- rep(scale, length(potential)) - } else { - stop("Length of and must be equal OR may be unit length.") - } - } - - arglength <- length(potential) - # make the args concentration, temperature and electrolyte this same length, - # unless the user supplied them (only necessary for length > 1) - if (arglength > 1) { - # handle two cases: - # 1. user did not touch concentration, temperature and electrolyte args. - # Assume they forgot and reset their length and print a message - # 2. user did change concentration or temperature or electrolyte, but still failed to - # ensure length equal to arglength. In this case, abort. - # note: we can get the default value set in the function call using formals() - if (identical(concentration, formals(from.SHE)$concentration) & - identical(temperature, formals(from.SHE)$temperature) & - identical(electrolyte, formals(from.SHE)$electrolyte)) { - # case 1 - message(paste0("The default concentration (", formals(from.SHE)$concentration, ") and temperature (", formals(from.SHE)$temperature, "C) will be assumed for all your potential/scale values.")) - concentration <- rep(concentration, arglength) - temperature <- rep(temperature, arglength) - electrolyte <- rep(electrolyte, arglength) - } else { - # case 2 - stop("Concentration, temperature and electrolyte arguments must have the same number of elements as potential and scale!") - } - } - - ## we can now safely assume that length() == arglength - # place args into a single dataframe - # this way, we can correlate columns to each other by row - dfargs <- - data.frame(potential = potential, # vs SHE - scale = common::RefCanonicalName(scale), # target scale - electrolyte = electrolyte, - concentration = concentration, - temperature = temperature, - stringsAsFactors = FALSE) - - ## From here on, ONLY access the arguments via this dataframe - ## That is, use dfargs$electrolyte, NOT electrolyte (and so on) - - # SHE scale special considerations - # 1. concentration is constant for SHE - if (any(dfargs$scale == common::RefCanonicalName("SHE"))) { - dfargs$concentration[which(dfargs$scale == common::RefCanonicalName("SHE"))] <- "" - dfargs$electrolyte[which(dfargs$scale == common::RefCanonicalName("SHE"))] <- "" - } - - # AVS scale special considerations - # 1. concentration is meaningless for AVS - if (any(dfargs$scale == common::RefCanonicalName("AVS"))) { - # concentration is meaningless for AVS (no electrolyte) so for those rows, we'll reset it - dfargs$concentration[which(dfargs$scale == common::RefCanonicalName("AVS"))] <- "" - dfargs$electrolyte[which(dfargs$scale == common::RefCanonicalName("AVS"))] <- "" - } - - # now just work our way through dfargs, line-by-line to determine potential as SHE - # all necessary conditions should be recorded right here in dfargs - for (p in 1:dim(dfargs)[1]) { - ## WE ARE NOW WORKING ROW-BY-ROW THROUGH THE SUPPLIED ARGUMENTS IN dfargs - # Step-wise matching: - # + first, we subset against electrode scale. If dataset only has one row, done. Else, - # + we subset against either conc.string or conc.num. Stop if zero rows in dataset (error), otherwise proceed. - # Our "dataset" is the literature data supplied via the argument as.SHE.data - this.data.scale <- subset(as.SHE.data, electrode == dfargs$scale[p]) - # subset.scale <- subset(as.SHE.data, electrode == dfargs$scale[p]) - if (dim(this.data.scale)[1] > 1) { - # continue matching, now against conc.string or conc.num - if (is.character(dfargs$concentration[p])) { - this.data.concentration <- - subset(this.data.scale, conc.string == dfargs$concentration[p]) - } else { - this.data.concentration <- - subset(this.data.scale, conc.num == dfargs$concentration[p]) - } - # stop if the resulting dataframe after matching contains no rows - if (dim(this.data.concentration)[1] == 0) { - stop(paste0("Failed to find any matching entries in dataset for ", - paste(dfargs[p, ], collapse = " ", sep = ""))) - } - # Note: it's ok at this point if the resulting dataset contains more than one row as - # more matching will be done below - # If we haven't had reason to stop(), we should be good - # just housekeeping: rename the variable so we don't have to edit code below - this.SHE.data <- this.data.concentration - } else { - # just housekeeping again - this.SHE.data <- this.data.scale - } - - - ## Electrolyte - # == We would like to transparently handle the following scenario: - # || if the user did not specify electrolyte solution (which we can check by using formals()) - # || but the dataset (after subsetting against scale and concentration above) still contains - # || more than one electrolyte - # >> Approach: we'll specify a "fallback" electrolyte, KCl (usually that's what the user wants) - # >> and inform/warn about it - - # KCl is a good assumption, as we always have KCl for the cases where - # an electrode system has more than one electrolyte - fallback.electrolyte <- "KCl(aq)" - if (length(unique(this.SHE.data$electrolyte)) > 1) { - if (formals(as.SHE)$electrolyte == "") { - warning(paste0("More than one electrolyte ", - "available for E(", dfargs$scale[p], ") in dataset. ", - "I'll assume you want ", fallback.electrolyte, ".")) - this.SHE.data <- - subset(this.SHE.data, electrolyte == fallback.electrolyte) - } else { - # else the user did change the electrolyte arg, use the user's value - this.SHE.data <- - subset(this.SHE.data, electrolyte == dfargs$electrolyte[p]) - # but stop if the resulting dataframe contains no rows - if (dim(this.SHE.data)[1] == 0) stop("Your choice of electrolyte does not match any data!") - } - } else { - # dataset contains only one unique electrolyte - # again, check if electrolyte in arg matches the one in dataset - # if it does, great, if it does not, print a message and use it anyway - if (unique(this.SHE.data$electrolyte) == dfargs$electrolyte[p]) { - this.SHE.data <- - subset(this.SHE.data, electrolyte == dfargs$electrolyte[p]) - } else { - # whatever electrolyte the user supplied does not match what's left in the datasubset - # but at this point the user is probably better served by returning the electrolyte we have - # along with an informative message (that's the only reason for the if-else below) - electrolytes.in.subset <- - unique(subset(as.SHE.data, electrode == dfargs$scale[p])$electrolyte) - if (dfargs$electrolyte[p] == "") { - message( - paste0('Electrolyte "" (empty string) not in dataset for E(', - dfargs$scale[p], '). ', - 'These electrolytes are: ', - paste(electrolytes.in.subset, collapse = ', or '), '.', - "I'll assume you want ", fallback.electrolyte, ".") - ) - } else { - message(paste0("Electrolyte ", dfargs$electrolyte[p], " not in dataset for E(", - dfargs$scale[p], "). ", - "These electrolytes are: ", - paste(electrolytes.in.subset, collapse = ", or "), ".", - "I'll assume you want ", fallback.electrolyte, ".") - ) - } - } - } - - # temperature - # either happens to match a temperature in the dataset, or we interpolate - # (under the assumption that potential varies linearly with temperature) - if (!any(this.SHE.data$temp == dfargs$temperature[p])) { - # sought temperature was not available in dataset, check that it falls inside - # note: important to use less/more-than-or-equal in case data only contains one value - if ((dfargs$temperature[p] <= max(this.SHE.data$temp)) && (dfargs$temperature[p] >= min(this.SHE.data$temp))) { - # within dataset range, do linear interpolation - lm.subset <- stats::lm(SHE ~ temp, data = this.SHE.data) - # interpolated temperature, calculated based on linear regression - # (more accurate than simple linear interpolation with approx()) - pot.interp <- - lm.subset$coefficients[2] * dfargs$temperature[p] + lm.subset$coefficients[1] - ### CALC POTENTIAL vs requested scale - dfargs$potentialvsscale[p] <- - ifelse(dfargs$scale[p] == "AVS", - pot.interp - dfargs$potential[p], - dfargs$potential[p] - pot.interp) - } - } else { - # requested temperature does exist in dataset - ### CALC POTENTIAL vs requested scale - dfargs$potentialvsscale[p] <- - ifelse(dfargs$scale[p] == "AVS", - subset(this.SHE.data, temp == dfargs$temperature[p])$SHE - dfargs$potential[p], - dfargs$potential[p] - subset(this.SHE.data, temp == dfargs$temperature[p])$SHE) - } - } - - return(dfargs$potentialvsscale) -} - - - - - -#' ConvertRefPotEC -#' -#' This function does the heavy lifting. -#' Converts from an electrochemical reference scale into another. -#' SHE: standard hydrogen electrode -#' Ag/AgCl: silver silver-chloride electrode (3M KCl) -#' SCE: saturated calomel electrode -#' -#' @param argpotential potential (numeric) -#' @param argrefscale input reference scale (character string) -#' @param valuerefscale output reference scale (character string) -#' -#' @return potential in output reference scale (numeric) -ConvertRefPotEC <- function(argpotential, argrefscale, valuerefscale) { - .Deprecated("as.SHE") - ##### Add more reference electrodes here >> - refpotatSHEzero <- c( 0, -0.21, -0.24, 3) - refrownames <- c( "SHE", "Ag/AgCl", "SCE", "Li/Li+") - refcolnames <- c("SHE0", "AgCl0", "SCE0", "Li0") - ##### Add more reference electrodes here << - # - SHE0 <- - data.frame(matrix(refpotatSHEzero, - ncol = length(refpotatSHEzero), - byrow = T)) - refpotmtx <- matrix(NA, length(SHE0), length(SHE0)) - refpotmtx[,1] <- matrix(as.matrix(SHE0), ncol = 1, byrow = T) - for (c in 2:length(SHE0)) { - # loop over columns (except the first) - for (r in 1:length(SHE0)) { - # loop over rows - refpotmtx[r, c] <- refpotmtx[r, 1] - refpotmtx[c, 1] - } - } - refpotdf <- as.data.frame(refpotmtx) - names(refpotdf) <- refcolnames - row.names(refpotdf) <- refrownames - ## So far we have made a matrix of all the possible combinations, - ## given the vector refpotatSHEzero. The matrix is not strictly necessary, - ## but it may prove useful later. It does. - # - # Match argrefscale to the refrownames - argmatch <- match(argrefscale, refrownames, nomatch = 0) - # Match valuerefscale to the refrownames - valuematch <- match(valuerefscale, refrownames, nomatch = 0) - # We simply assume that the match was well-behaved - valuepotential <- argpotential + refpotdf[valuematch, argmatch] - # Check that arg and value electrodes are within bounds for a match - if (argmatch == 0 || valuematch == 0) { - # No match - # Perform suitable action - message("Arg out of bounds in call to ConvertRefPot") - valuepotential <- NA - } - return(valuepotential) -} - - -#' Convert from one electrochemical scale to another -#' -#' @param argpotential potential (numeric) -#' @param argrefscale input reference scale (char string) -#' @param valuerefscale output reference scale (char string) -#' -#' @return potential in output reference scale (numeric) -#' @export -ConvertRefPot <- function(argpotential, argrefscale, valuerefscale) { - .Deprecated("as.SHE") - # You should check that argpotential is valid numeric - - # IDEA: make a matrix out of these (scale names and flags) - - - - - - - # Valid scales - scale.names <- list() - scale.names[["SHE"]] <- c("SHE", "NHE", "she", "nhe") - scale.names[["AgCl"]] <- c("Ag/AgCl", "AgCl", "ag/agcl", "agcl") - scale.names[["SCE"]] <- c("SCE", "sce") - scale.names[["Li"]] <- c("Li/Li+", "Li", "Li+", "li", "li+", "li/li+") - scale.names[["AVS"]] <- c("AVS", "avs") - - # Set flags - bool.flags <- - as.data.frame(matrix(0, - nrow = length(scale.names), - ncol = 2)) - names(bool.flags) <- c("argref", "valueref") - row.names(bool.flags) <- names(scale.names) - - # argrefscale - # Check that argrefscale is valid character mode - # ... - - # steps through all scale names, "row-by-row", - # looking for any cell matching "argrefscale" string - # if found, save the position of that refelectrode (in scale.names) to - # that row and "argref" column of bool.flags - for (j in 1:length(row.names(bool.flags))) { - if (any(scale.names[[row.names(bool.flags)[j]]] == argrefscale)) { - bool.flags[row.names(bool.flags)[j], "argref"] <- j - } - } - - - # valuerefscale - # Check that valuerefscale is valid character mode - # ... - - for (k in 1:length(row.names(bool.flags))) { - if (any(scale.names[[row.names(bool.flags)[k]]] == valuerefscale)) { - bool.flags[row.names(bool.flags)[k], "valueref"] <- k - } - } - - # Depending on which flags are set, call the corresponding function - - decision.vector <- colSums(bool.flags) - - # Check if both scales are the same (no conversion needed). If so, abort gracefully. - # ... - - if (decision.vector["argref"] == 5 || decision.vector["valueref"] == 5) { - # AVS is requested, deal with it it - if (decision.vector["argref"] == 5) { - # Conversion _from_ AVS - rnpotential <- ConvertRefPotEC(AVS2SHE(argpotential), - "SHE", - scale.names[[decision.vector["valueref"]]][1]) - } - if (decision.vector["valueref"] == 5) { - # Conversion _to_ AVS - rnpotential <- SHE2AVS(ConvertRefPotEC(argpotential, - scale.names[[decision.vector["argref"]]][1], - "SHE")) - } - } else { - rnpotential <- ConvertRefPotEC(argpotential, - scale.names[[decision.vector["argref"]]][1], - scale.names[[decision.vector["valueref"]]][1]) - } - return(rnpotential) -} diff --git a/README.md b/README.md index 59cdf8f..c4dca65 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ -## A collection of general functions and data +# A collection of general functions and data -Includes common numerical functions, unit converters, -some LaTeX-specific functions, as well as reference data. +Includes common numerical functions and some LaTeX-specific functions. + + + +## NOTE: the electrochemical reference electrode functions + +have **moved** to the [`refelectrodes` package](https://github.com/chepec/refelectrodes)! diff --git a/data/vapourwater.csv b/data/vapourwater.csv deleted file mode 100644 index 65c5364..0000000 --- a/data/vapourwater.csv +++ /dev/null @@ -1,190 +0,0 @@ -"T/degreeCelsius","P/kPa","dvapH/kJ kg-1","surface tension/mN m-1" -0.01,0.61165,2500.9,75.65 -2,0.70599,2496.2,75.37 -4,0.81355,2491.4,75.08 -6,0.93536,2486.7,74.8 -8,1.073,2481.9,74.51 -10,1.2282,2477.2,74.22 -12,1.4028,2472.5,73.93 -14,1.599,2467.7,73.63 -16,1.8188,2463,73.34 -18,2.0647,2458.3,73.04 -20,2.3393,2453.5,72.74 -22,2.6453,2448.8,72.43 -24,2.9858,2444,72.13 -25,3.1699,2441.7,71.97 -26,3.3639,2439.3,71.82 -28,3.7831,2434.6,71.51 -30,4.247,2429.8,71.19 -32,4.7596,2425.1,70.88 -34,5.3251,2420.3,70.56 -36,5.9479,2415.5,70.24 -38,6.6328,2410.8,69.92 -40,7.3849,2406,69.6 -42,8.2096,2401.2,69.27 -44,9.1124,2396.4,68.94 -46,10.099,2391.6,68.61 -48,11.177,2386.8,68.28 -50,12.352,2381.9,67.94 -52,13.631,2377.1,67.61 -54,15.022,2372.3,67.27 -56,16.533,2367.4,66.93 -58,18.171,2362.5,66.58 -60,19.946,2357.7,66.24 -62,21.867,2352.8,65.89 -64,23.943,2347.8,65.54 -66,26.183,2342.9,65.19 -68,28.599,2338,64.84 -70,31.201,2333,64.48 -72,34,2328.1,64.12 -74,37.009,2323.1,63.76 -76,40.239,2318.1,63.4 -78,43.703,2313,63.04 -80,47.414,2308,62.67 -82,51.387,2302.9,62.31 -84,55.635,2297.9,61.94 -86,60.173,2292.8,61.56 -88,65.017,2287.6,61.19 -90,70.182,2282.5,60.82 -92,75.684,2277.3,60.44 -94,81.541,2272.1,60.06 -96,87.771,2266.9,59.68 -98,94.39,2261.7,59.3 -100,101.42,2256.4,58.91 -102,108.87,2251.1,58.53 -104,116.78,2245.8,58.14 -106,125.15,2240.4,57.75 -108,134.01,2235.1,57.36 -110,143.38,2229.6,56.96 -112,153.28,2224.2,56.57 -114,163.74,2218.7,56.17 -116,174.77,2213.2,55.77 -118,186.41,2207.7,55.37 -120,198.67,2202.1,54.97 -122,211.59,2196.5,54.56 -124,225.18,2190.9,54.16 -126,239.47,2185.2,53.75 -128,254.5,2179.5,53.34 -130,270.28,2173.7,52.93 -132,286.85,2167.9,52.52 -134,304.23,2162.1,52.11 -136,322.45,2156.2,51.69 -138,341.54,2150.3,51.27 -140,361.54,2144.3,50.86 -142,382.47,2138.3,50.44 -144,404.37,2132.2,50.01 -146,427.26,2126.1,49.59 -148,451.18,2119.9,49.17 -150,476.16,2113.7,48.74 -152,502.25,2107.5,48.31 -154,529.46,2101.2,47.89 -156,557.84,2094.8,47.46 -158,587.42,2088.4,47.02 -160,618.23,2082,46.59 -162,650.33,2075.5,46.16 -164,683.73,2068.9,45.72 -166,718.48,2062.3,45.28 -168,754.62,2055.6,44.85 -170,792.19,2048.8,44.41 -172,831.22,2042,43.97 -174,871.76,2035.1,43.52 -176,913.84,2028.2,43.08 -178,957.51,2021.2,42.64 -180,1002.8,2014.2,42.19 -182,1049.8,2007,41.74 -184,1098.5,1999.8,41.3 -186,1148.9,1992.6,40.85 -188,1201.1,1985.3,40.4 -190,1255.2,1977.9,39.95 -192,1311.2,1970.4,39.49 -194,1369.1,1962.8,39.04 -196,1429,1955.2,38.59 -198,1490.9,1947.5,38.13 -200,1554.9,1939.7,37.67 -202,1621,1931.9,37.22 -204,1689.3,1923.9,36.76 -206,1759.8,1915.9,36.3 -208,1832.6,1907.8,35.84 -210,1907.7,1899.6,35.38 -212,1985.1,1891.4,34.92 -214,2065,1883,34.46 -216,2147.3,1874.6,33.99 -218,2232.2,1866,33.53 -220,2319.6,1857.4,33.07 -222,2409.6,1848.6,32.6 -224,2502.3,1839.8,32.14 -226,2597.8,1830.9,31.67 -228,2696,1821.8,31.2 -230,2797.1,1812.7,30.74 -232,2901,1803.5,30.27 -234,3008,1794.1,29.8 -236,3117.9,1784.7,29.33 -238,3230.8,1775.1,28.86 -240,3346.9,1765.4,28.39 -242,3466.2,1755.6,27.92 -244,3588.7,1745.7,27.45 -246,3714.5,1735.6,26.98 -248,3843.6,1725.5,26.51 -250,3976.2,1715.2,26.04 -252,4112.2,1704.7,25.57 -254,4251.8,1694.2,25.1 -256,4394.9,1683.5,24.63 -258,4541.7,1672.6,24.16 -260,4692.3,1661.6,23.69 -262,4846.6,1650.5,23.22 -264,5004.7,1639.2,22.75 -266,5166.8,1627.8,22.28 -268,5332.9,1616.2,21.81 -270,5503,1604.4,21.34 -272,5677.2,1592.5,20.87 -274,5855.6,1580.4,20.4 -276,6038.3,1568.1,19.93 -278,6225.2,1555.6,19.46 -280,6416.6,1543,18.99 -282,6612.4,1530.1,18.53 -284,6812.8,1517.1,18.06 -286,7017.7,1503.8,17.59 -288,7227.4,1490.4,17.13 -290,7441.8,1476.7,16.66 -292,7661,1462.7,16.2 -294,7885.2,1448.6,15.74 -296,8114.3,1434.2,15.28 -298,8348.5,1419.5,14.82 -300,8587.9,1404.6,14.36 -302,8832.5,1389.4,13.9 -304,9082.4,1374,13.45 -306,9337.8,1358.2,12.99 -308,9598.6,1342.1,12.54 -310,9865.1,1325.7,12.09 -312,10137,1309,11.64 -314,10415,1291.9,11.19 -316,10699,1274.5,10.75 -318,10989,1256.6,10.3 -320,11284,1238.4,9.86 -322,11586,1219.7,9.43 -324,11895,1200.6,8.99 -326,12209,1180.9,8.56 -328,12530,1160.8,8.13 -330,12858,1140.2,7.7 -332,13193,1118.9,7.28 -334,13534,1097.1,6.86 -336,13882,1074.6,6.44 -338,14238,1051.3,6.03 -340,14601,1027.3,5.63 -342,14971,1002.5,5.22 -344,15349,976.7,4.83 -346,15734,949.9,4.43 -348,16128,922,4.05 -350,16529,892.7,3.67 -352,16939,862.1,3.29 -354,17358,829.8,2.93 -356,17785,795.5,2.57 -358,18221,759,2.22 -360,18666,719.8,1.88 -362,19121,677.3,1.55 -364,19585,630.5,1.23 -366,20060,578.2,0.93 -368,20546,517.8,0.65 -370,21044,443.8,0.39 -372,21554,340.3,0.16 -373.95,22064,0,0 diff --git a/data/vapourwater.rda b/data/vapourwater.rda deleted file mode 100644 index d810127..0000000 Binary files a/data/vapourwater.rda and /dev/null differ