Browse Source

Moved reference electrode converter functions and data to their own package.

Also removed vapour water functions that are already part of the water package.
master
Taha Ahmed 2 years ago
parent
commit
f96a3dc92e
  1. 137
      R/chemistry-tools.R
  2. 18
      R/data.R
  3. 871
      R/unit-converters-electrochemical.R
  4. 11
      README.md
  5. 190
      data/vapourwater.csv
  6. BIN
      data/vapourwater.rda

137
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)
}

18
R/data.R

@ -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

871
R/unit-converters-electrochemical.R

@ -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 <scale> has unit length. We'll recycle it to match length of <potential>.")
scale <- rep(scale, length(potential))
} else {
stop("Length of <potential> and <scale> must be equal OR <scale> 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(<args>) == 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 <scale> has unit length. We'll recycle it to match length of <potential>.")
scale <- rep(scale, length(potential))
} else {
stop("Length of <potential> and <scale> must be equal OR <scale> 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(<args>) == 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)
}

11
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)!

190
data/vapourwater.csv

@ -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
1 T/degreeCelsius P/kPa dvapH/kJ kg-1 surface tension/mN m-1
2 0.01 0.61165 2500.9 75.65
3 2 0.70599 2496.2 75.37
4 4 0.81355 2491.4 75.08
5 6 0.93536 2486.7 74.8
6 8 1.073 2481.9 74.51
7 10 1.2282 2477.2 74.22
8 12 1.4028 2472.5 73.93
9 14 1.599 2467.7 73.63
10 16 1.8188 2463 73.34
11 18 2.0647 2458.3 73.04
12 20 2.3393 2453.5 72.74
13 22 2.6453 2448.8 72.43
14 24 2.9858 2444 72.13
15 25 3.1699 2441.7 71.97
16 26 3.3639 2439.3 71.82
17 28 3.7831 2434.6 71.51
18 30 4.247 2429.8 71.19
19 32 4.7596 2425.1 70.88
20 34 5.3251 2420.3 70.56
21 36 5.9479 2415.5 70.24
22 38 6.6328 2410.8 69.92
23 40 7.3849 2406 69.6
24 42 8.2096 2401.2 69.27
25 44 9.1124 2396.4 68.94
26 46 10.099 2391.6 68.61
27 48 11.177 2386.8 68.28
28 50 12.352 2381.9 67.94
29 52 13.631 2377.1 67.61
30 54 15.022 2372.3 67.27
31 56 16.533 2367.4 66.93
32 58 18.171 2362.5 66.58
33 60 19.946 2357.7 66.24
34 62 21.867 2352.8 65.89
35 64 23.943 2347.8 65.54
36 66 26.183 2342.9 65.19
37 68 28.599 2338 64.84
38 70 31.201 2333 64.48
39 72 34 2328.1 64.12
40 74 37.009 2323.1 63.76
41 76 40.239 2318.1 63.4
42 78 43.703 2313 63.04
43 80 47.414 2308 62.67
44 82 51.387 2302.9 62.31
45 84 55.635 2297.9 61.94
46 86 60.173 2292.8 61.56
47 88 65.017 2287.6 61.19
48 90 70.182 2282.5 60.82
49 92 75.684 2277.3 60.44
50 94 81.541 2272.1 60.06
51 96 87.771 2266.9 59.68
52 98 94.39 2261.7 59.3
53 100 101.42 2256.4 58.91
54 102 108.87 2251.1 58.53
55 104 116.78 2245.8 58.14
56 106 125.15 2240.4 57.75
57 108 134.01 2235.1 57.36
58 110 143.38 2229.6 56.96
59 112 153.28 2224.2 56.57
60 114 163.74 2218.7 56.17
61 116 174.77 2213.2 55.77
62 118 186.41 2207.7 55.37
63 120 198.67 2202.1 54.97
64 122 211.59 2196.5 54.56
65 124 225.18 2190.9 54.16
66 126 239.47 2185.2 53.75
67 128 254.5 2179.5 53.34
68 130 270.28 2173.7 52.93
69 132 286.85 2167.9 52.52
70 134 304.23 2162.1 52.11
71 136 322.45 2156.2 51.69
72 138 341.54 2150.3 51.27
73 140 361.54 2144.3 50.86
74 142 382.47 2138.3 50.44
75 144 404.37 2132.2 50.01
76 146 427.26 2126.1 49.59
77 148 451.18 2119.9 49.17
78 150 476.16 2113.7 48.74
79 152 502.25 2107.5 48.31
80 154 529.46 2101.2 47.89
81 156 557.84 2094.8 47.46
82 158 587.42 2088.4 47.02
83 160 618.23 2082 46.59
84 162 650.33 2075.5 46.16
85 164 683.73 2068.9 45.72
86 166 718.48 2062.3 45.28
87 168 754.62 2055.6 4

BIN
data/vapourwater.rda

Binary file not shown.
Loading…
Cancel
Save