diff --git a/DESCRIPTION b/DESCRIPTION index 3a9da9b..0e5bdb2 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: common Type: Package Title: chepec common -Version: 0.1.0 +Version: 0.0.0.9000 Description: Commonly used functions and scripts. Authors@R: person("Taha", "Ahmed", email = "taha@chepec.se", role = c("aut", "cre")) License: GPL-3 diff --git a/NAMESPACE b/NAMESPACE index 9288058..498237b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,6 +3,7 @@ export(AVS2SHE) export(Celsius2Kelvin) export(ConvertRefPot) +export(ExtractSampleIdString) export(GenericXtableSetAttributes) export(Kelvin2Celsius) export(LoadRData2Variable) @@ -12,12 +13,14 @@ export(SHE2AVS) export(SubfigureGenerator) export(SubstrateHistory) export(TabularXtableHeader) +export(as.SHE) export(as.degrees) export(as.radians) export(int2padstr) export(is.wholenumber) export(molarity2mass) export(numbers2words) +export(potentials.as.SHE) export(roundup) export(siunitx.uncertainty) export(thth2d) diff --git a/R/samples.R b/R/samples.R index 54bab57..992d3b6 100644 --- a/R/samples.R +++ b/R/samples.R @@ -27,6 +27,28 @@ ProvideSampleId <- function (pathexpfile, implementation = "filename") { } + +#' Extract sampleid from a string +#' +#' Extract sampleid from a string by utilising the fact that samepleid's +#' adhere to a defined format. +#' +#' @param string character string that contains a sampleid +#' +#' @return sampleid (character string) +#' @export +ExtractSampleIdString <- function(string) { + # regmatches() extracts matched substrings based on the results of regexpr and friends + # another way to do this would be stringr::str_extract() + sampleid <- + regmatches(string, regexpr("\\d{1,2}[A-Za-z]{1}\\d{2,4}", string)) + # return empty string rather than character(0) + if (length(sampleid) == 0) sampleid <- "" + return(sampleid) +} + + + #' Display the history of a substrate (sampleid) #' #' @param sampleid string diff --git a/R/unit-converters-electrochemical.R b/R/unit-converters-electrochemical.R index 18ac1eb..bb2076c 100644 --- a/R/unit-converters-electrochemical.R +++ b/R/unit-converters-electrochemical.R @@ -26,13 +26,326 @@ SHE2AVS <- function(she) { } + +#' 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. +#' +#' @param refname string +#' +#' @return the canonical name or empty string +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") # saturated silver-silver chloride is sometimes abbreviated SSC + electrode.system[["Hg2Cl2/Hg"]] <- + c("Hg2Cl2/Hg", + "Hg/Hg2Cl2", + "Hg2Cl2", + "calomel-mercury", + "mercury-calomel", + "SCE") + electrode.system[["AVS"]] <- + c("AVS", + "vacuum", + "vacuum scale", + "absolute", + "absolute scale", + "absolute vacuum scale") + electrode.system[["Li"]] <- + c("Li", + "Li/Li+", + "Lithium") + + # to match the lowercase version, use tolower() + # perhaps also replace hyphens and slashes with space? + + matches <- + data.frame(electrode = names(electrode.system), + m = rep(0, length(electrode.system)), + stringsAsFactors = FALSE) + + # loop over electrode systems + for (i in 1:length(electrode.system)) { + # check for a match in any cell of this row, + # also trying all lower-case and substituting symbols with spaces + if (any(electrode.system[[i]] == refname) || + any(tolower(electrode.system[[i]]) == refname) || + any(gsub("[-/]", " ", electrode.system[[i]]) == refname)) { + matches$m[i] <- 1 + } + } + + # if everything went as expected we should have just one match + if (sum(matches$m) != 1) { + # something wrong (should probably add warn/error here) + # for now, just return empty string + return("") + } else { + return(matches$electrode[which(matches$m == 1)]) + } +} + + + + +#' 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", "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"), + 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 and concentration (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 electronic scale to SHE +#' +#' @param potential in the original scale, V or eV +#' @param scale name of the original scale +#' @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, + 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 + + if (RefCanonicalName(scale) == "") { + warning("as.SHE(): Sorry, you have supplied an unrecognised electrode scale.") + return(NA) + } + + # there is the simple case of + if (RefCanonicalName(scale) == "SHE") { + warning("This function can only convert from scales other than SHE!") + return(NA) + } + + # AVS needs special consideration + if (RefCanonicalName(scale) == "AVS") { + # reset arg concentration + concentration <- "" + # second, since AVS scale goes in the opposite direction to the electrochemical scales + # we will define our own function + negifavs <- function(a, b) { + a - b + } + } else { + # we will define the same function differently for + # the case we're not dealing with AVS + negifavs <- function(a, b) { + a + b + } + } + + if (is.character(concentration)) { + # supplied concentration is character string + subspot <- + subset(subset(as.SHE.data, + electrode == RefCanonicalName(scale)), + conc.string == concentration) + # if either "scale" or "concentration" are not found in the data, subspot will contain zero rows + if (dim(subspot)[1] == 0) { + warning("as.SHE(): Supplied scale or concentration does not exist in data. Returning NA.") + return(NA) + } + # so far, we have + # scale: checked! + # concentration: checked! + # only temperature remains to be handled + # temperature value could happen to match a value in the data, or lie somewhere in between + # note: we will not allow extrapolation + if (!any(subspot$temp == temperature)) { + # if sought temperature is not available in dataset, check that it falls inside + if ((temperature < max(subspot$temp)) && (temperature > min(subspot$temp))) { + # within dataset range, do linear interpolation + lm.subspot <- stats::lm(SHE ~ temp, data = subspot) + # interpolated temperature, calculated based on linear regression + # (more accurate than simple linear interpolation with approx()) + potinterp <- + lm.subspot$coefficients[2] * temperature + lm.subspot$coefficients[1] + ### CALC RETURN POTENTIAL + return(negifavs(potinterp, potential)) + } else { + # outside dataset range, warning and return NA (we don't extrapolate) + warning("as.SHE(): the temperature you requested falls outside data range.") + return(NA) + } + } else { + # requested temperature does exist in dataset + ### CALC RETURN POTENTIAL + return(negifavs(subset(subspot, temp == temperature)$SHE, potential)) + } + # outer-most if-else + } else { + # supplied concentration is numeric + # note: all code inside this else is the same as inside the if, + # just for the case of numeric concentration + subspot <- + subset(subset(as.SHE.data, + electrode == RefCanonicalName(scale)), + conc.num == concentration) + # if either "scale" or "concentration" are not found in the data, subspot will contain zero rows + if (dim(subspot)[1] == 0) { + warning("as.SHE(): Supplied scale or concentration does not exist in data. Returning NA.") + return(NA) + } + if (!any(subspot$temp == temperature)) { + # if sought temperature is not available in dataset, check that it falls inside + if ((temperature < max(subspot$temp)) && (temperature > min(subspot$temp))) { + # within dataset range, do linear interpolation + lm.subspot <- stats::lm(SHE ~ temp, data = subspot) + # interpolated temperature, calculated based on linear regression + # (more accurate than simple linear interpolation with approx()) + potinterp <- + lm.subspot$coefficients[2] * temperature + lm.subspot$coefficients[1] + ### CALC RETURN POTENTIAL + return(negifavs(potinterp, potential)) + } else { + # outside dataset range, warning and return NA (we don't extrapolate) + warning("as.SHE(): the temperature you requested falls outside data range.") + return(NA) + } + } else { + # requested temperature does exist in dataset + ### CALC RETURN POTENTIAL + return(negifavs(subset(subspot, temp == temperature)$SHE, potential)) + } + + } +} + + + + + + + + + #' ConvertRefPotEC #' #' This function does the heavy lifting. #' Converts from an electrochemical reference scale into another. -#' SHE: standard hydrogen electrode scale -#' Ag/AgCl: silver silver-chloride electrode scale (3M KCl) -#' SCE: standard calomel scale +#' 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) @@ -46,9 +359,12 @@ ConvertRefPotEC <- function(argpotential, argrefscale, valuerefscale) { refcolnames <- c("SHE0", "AgCl0", "SCE0", "Li0") ##### Add more reference electrodes here << # - SHE0 <- data.frame(matrix(refpotatSHEzero, ncol=length(refpotatSHEzero), byrow=T)) + 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) + 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)) { @@ -93,6 +409,11 @@ ConvertRefPot <- function(argpotential, argrefscale, valuerefscale) { # IDEA: make a matrix out of these (scale names and flags) + + + + + # Valid scales scale.names <- list() scale.names[["SHE"]] <- c("SHE", "NHE", "she", "nhe") @@ -102,7 +423,10 @@ ConvertRefPot <- function(argpotential, argrefscale, valuerefscale) { scale.names[["AVS"]] <- c("AVS", "avs") # Set flags - bool.flags <- as.data.frame(matrix(0, nrow = length(scale.names), ncol = 2)) + 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) @@ -110,6 +434,10 @@ ConvertRefPot <- function(argpotential, argrefscale, valuerefscale) { # 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 diff --git a/R/unit-converters.R b/R/unit-converters.R index e833d9b..901db16 100644 --- a/R/unit-converters.R +++ b/R/unit-converters.R @@ -1,3 +1,33 @@ +# nm2eV <- function(nm) { +# ## Depends on: +# ## chepec/chetex/common/R/sunlight/solarconstants.R +# # Converts wavelength in nm to energy in eV +# # +# sun.constants <- solar.constants() +# +# eV <- +# sun.constants["h.eV", "value"] * +# 1E9 * sun.constants["c", "value"] / nm +# +# return(eV) +# } + + +# eV2nm <- function(eV) { +# ## Depends on: +# ## chepec/chetex/common/R/sunlight/solarconstants.R +# # Converts energy in eV to wavelength in nm +# # +# sun.constants <- solar.constants() +# +# nm <- +# sun.constants["h.eV", "value"] * +# 1E9 * sun.constants["c", "value"] / eV +# +# return(nm) +# } + + #' Convert wavelength to wavenumber #' #' Converts wavelength (nm) to wavenumber (cm-1) diff --git a/man/ConvertRefPotEC.Rd b/man/ConvertRefPotEC.Rd index 10da9b7..525bfcb 100644 --- a/man/ConvertRefPotEC.Rd +++ b/man/ConvertRefPotEC.Rd @@ -19,8 +19,8 @@ potential in output reference scale (numeric) \description{ This function does the heavy lifting. Converts from an electrochemical reference scale into another. -SHE: standard hydrogen electrode scale -Ag/AgCl: silver silver-chloride electrode scale (3M KCl) -SCE: standard calomel scale +SHE: standard hydrogen electrode +Ag/AgCl: silver silver-chloride electrode (3M KCl) +SCE: saturated calomel electrode } diff --git a/man/ExtractSampleIdString.Rd b/man/ExtractSampleIdString.Rd new file mode 100644 index 0000000..f9edc3a --- /dev/null +++ b/man/ExtractSampleIdString.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/samples.R +\name{ExtractSampleIdString} +\alias{ExtractSampleIdString} +\title{Extract sampleid from a string} +\usage{ +ExtractSampleIdString(string) +} +\arguments{ +\item{string}{character string that contains a sampleid} +} +\value{ +sampleid (character string) +} +\description{ +Extract sampleid from a string by utilising the fact that samepleid's +adhere to a defined format. +} + diff --git a/man/RefCanonicalName.Rd b/man/RefCanonicalName.Rd new file mode 100644 index 0000000..6209bf0 --- /dev/null +++ b/man/RefCanonicalName.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/unit-converters-electrochemical.R +\name{RefCanonicalName} +\alias{RefCanonicalName} +\title{Get standardised name of reference electrode} +\usage{ +RefCanonicalName(refname) +} +\arguments{ +\item{refname}{string} +} +\value{ +the canonical name or empty string +} +\description{ +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. +} + diff --git a/man/as.SHE.Rd b/man/as.SHE.Rd new file mode 100644 index 0000000..8bd416a --- /dev/null +++ b/man/as.SHE.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/unit-converters-electrochemical.R +\name{as.SHE} +\alias{as.SHE} +\title{Convert from electrochemical or electronic scale to SHE} +\usage{ +as.SHE(potential, scale, concentration = "saturated", temperature = 25, + as.SHE.data = potentials.as.SHE()) +} +\arguments{ +\item{potential}{in the original scale, V or eV} + +\item{scale}{name of the original scale} + +\item{concentration}{of electrolyte in mol/L, or as the string "saturated"} + +\item{temperature}{of system in degrees Celsius} + +\item{as.SHE.data}{dataframe with dataset} +} +\value{ +potential in SHE scale +} +\description{ +Convert from electrochemical or electronic scale to SHE +} + diff --git a/man/potentials.as.SHE.Rd b/man/potentials.as.SHE.Rd new file mode 100644 index 0000000..b0e8eed --- /dev/null +++ b/man/potentials.as.SHE.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/unit-converters-electrochemical.R +\name{potentials.as.SHE} +\alias{potentials.as.SHE} +\title{Potentials as SHE} +\usage{ +potentials.as.SHE() +} +\value{ +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 + } +} +\description{ +This function just outputs a tidy dataframe with potential vs SHE for +different scales, electrolytes, concentrations, and temperatures. +Using data from literature. +} +