Browse Source

Completely reworked electrochemical scale converter

by adding a new family of functions.
Corrected package version number to dev. A few other smaller (older) changes.
master
Taha Ahmed 5 years ago
parent
commit
5d7230ebaf
  1. 2
      DESCRIPTION
  2. 3
      NAMESPACE
  3. 22
      R/samples.R
  4. 340
      R/unit-converters-electrochemical.R
  5. 30
      R/unit-converters.R
  6. 6
      man/ConvertRefPotEC.Rd
  7. 19
      man/ExtractSampleIdString.Rd
  8. 21
      man/RefCanonicalName.Rd
  9. 27
      man/as.SHE.Rd
  10. 28
      man/potentials.as.SHE.Rd

2
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

3
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)

22
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

340
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

30
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)

6
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
}

19
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.
}

21
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.
}

27
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
}

28
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.
}
Loading…
Cancel
Save