as.SHE() now supports arguments with arbitrary length.

Ended up completely rewriting as.SHE() and some of the companion functions.
master
Taha Ahmed 6 years ago
parent dc0fcb0629
commit 82f635f3a3
  1. 2
      DESCRIPTION
  2. 232
      R/unit-converters-electrochemical.R
  3. 8
      man/RefCanonicalName.Rd
  4. 5
      man/as.SHE.Rd

@ -1,7 +1,7 @@
Package: common
Type: Package
Title: chepec common
Version: 0.0.0.9001
Version: 0.0.0.9002
Description: Commonly used functions and scripts.
Authors@R: person("Taha", "Ahmed", email = "taha@chepec.se", role = c("aut", "cre"))
License: GPL-3

@ -35,10 +35,14 @@ SHE2AVS <- function(she) {
#' (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
#' @param refname string or a vector of strings
#'
#' @return the canonical name or empty string
#' @return vector with corresponding "canonical" name or empty string (if none found)
#' @export
RefCanonicalName <- function(refname) {
# scale names
@ -73,33 +77,35 @@ RefCanonicalName <- function(refname) {
"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)),
# 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,
# and version with symbols (-/) subbed with spaces
c(unname(unlist(electrode.system)),
tolower(unname(unlist(electrode.system))),
gsub("[-/]", " ", unname(unlist(electrode.system)))),
refcanon =
rep(sub("[0-9]$", "", names(unlist(electrode.system))), 3),
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
}
# 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])]
}
# 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)])
}
return(refcanon)
}
@ -212,7 +218,10 @@ potentials.as.SHE <- function() {
#' Convert from electrochemical or electronic scale to SHE
#'
#' @param potential in the original scale, V or eV
#' 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 concentration of electrolyte in mol/L, or as the string "saturated"
#' @param temperature of system in degrees Celsius
@ -229,109 +238,104 @@ as.SHE <- function(potential,
# 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)
# make this work for arbitrary-length vectors of potential and scale
# make sure potential and scale args have the same length
if (length(potential) == 0 | length(scale) == 0) {
stop("Arguments potential or scale cannot be empty!")
} else if (length(potential) != length(scale)) {
stop("Arguments potential and scale must have equal number of elements")
}
# there is the simple case of
if (RefCanonicalName(scale) == "SHE") {
warning("This function can only convert from scales other than SHE!")
return(NA)
arglength <- length(potential)
# make the concentration and temperature args to this same length,
# unless the user supplied them (only necessary for > 1)
if (arglength > 1) {
# handle two cases:
# 1. user did not touch concentration or temperature args.
# Assume they forgot and reset their length and print a message
# 2. user did change concentration or temperature, 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)) {
# case 1
# message("NOTE: default concentration and temperature values used for all potentials and scales.")
message(paste0("Default concentration (", formals(as.SHE)$concentration, ") and default temperature (", formals(as.SHE)$temperature, "C) used for all supplied potential and scale values."))
concentration <- rep(concentration, arglength)
temperature <- rep(temperature, arglength)
} else {
# case 2
stop("Arguments concentration and temperature must have same number of elements as potential and scale!")
}
}
# 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
}
## we can now safely assume that length(<args>) == arglength
# place args into a single dataframe
# this way, we can correlate columns to each other by row
df <-
data.frame(potential = potential,
scale = RefCanonicalName(scale),
concentration = concentration,
temperature = temperature,
stringsAsFactors = FALSE)
# add column to keep track of vacuum scale
df$vacuum <- as.logical(FALSE)
# add column to hold calc potential vs SHE
df$SHE <- as.numeric(NA)
# AVS scale special considerations
# 1. concentration is meaningless
# 2. direction is opposite of electrochemical scales, requiring change of sign
if (any(df$scale == RefCanonicalName("AVS"))) {
# concentration is meaningless for AVS (no electrolyte)
# so for those rows, we'll reset it
df$concentration[which(df$scale == RefCanonicalName("AVS"))] <- ""
df$vacuum[which(df$scale == RefCanonicalName("AVS"))] <- TRUE
}
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)
}
# now just work our way through df, line-by-line to determine potential as SHE
# all necessary conditions should be recorded right here in df
for (p in 1:dim(df)[1]) {
if (is.character(df$concentration[p])) {
subset.SHE.data <-
subset(subset(as.SHE.data, conc.string == df$concentration[p]),
electrode == df$scale[p])
} else {
# requested temperature does exist in dataset
### CALC RETURN POTENTIAL
return(negifavs(subset(subspot, temp == temperature)$SHE, potential))
subset.SHE.data <-
subset(subset(as.SHE.data, conc.num == df$concentration[p]),
electrode == df$scale[p])
}
# 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))) {
# temperature
# either happens to match a temperature in the dataset, or we interpolate
# (under the assumption that potential varies linearly with temperature)
if (!any(subset.SHE.data$temp == df$temperature[p])) {
# sought temperature was not available in dataset, check that it falls inside
if ((df$temperature[p] < max(subset.SHE.data$temp)) &&
(df$temperature[p] > min(subset.SHE.data$temp))) {
# within dataset range, do linear interpolation
lm.subspot <- stats::lm(SHE ~ temp, data = subspot)
lm.subset <- stats::lm(SHE ~ temp, data = subset.SHE.data)
# 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)
pot.interp <-
lm.subset$coefficients[2] * df$temperature[p] + lm.subset$coefficients[1]
### CALC POTENTIAL vs SHE
df$SHE[p] <-
ifelse(df$vacuum[p],
pot.interp - df$potential[p],
pot.interp + df$potential[p])
}
} else {
# requested temperature does exist in dataset
### CALC RETURN POTENTIAL
return(negifavs(subset(subspot, temp == temperature)$SHE, potential))
### CALC POTENTIAL vs SHE
df$SHE[p] <-
ifelse(df$vacuum[p],
subset(subset.SHE.data, temp == df$temperature[p])$SHE - df$potential[p],
subset(subset.SHE.data, temp == df$temperature[p])$SHE + df$potential[p])
}
}
return(df$SHE)
}

@ -7,15 +7,19 @@
RefCanonicalName(refname)
}
\arguments{
\item{refname}{string}
\item{refname}{string or a vector of strings}
}
\value{
the canonical name or empty string
vector with corresponding "canonical" name or empty string (if none found)
}
\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.
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.
}

@ -8,7 +8,7 @@ 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{potential}{potential in volt}
\item{scale}{name of the original scale}
@ -22,6 +22,7 @@ as.SHE(potential, scale, concentration = "saturated", temperature = 25,
potential in SHE scale
}
\description{
Convert from electrochemical or electronic scale to SHE
Convert an arbitrary number of potentials against any known electrochemical
scale (or the electronic vacuum scale) to potential vs SHE.
}

Loading…
Cancel
Save