Skip to content

Instantly share code, notes, and snippets.

@quantum9Innovation
Last active January 27, 2022 02:57
Show Gist options
  • Save quantum9Innovation/fa9597603bf731f62ccc9fd228a38333 to your computer and use it in GitHub Desktop.
Save quantum9Innovation/fa9597603bf731f62ccc9fd228a38333 to your computer and use it in GitHub Desktop.
Calculate empirical formulas of chemical compounds given their stoichiometric mass composition data (assuming a ~1% error rate)
// Calculate empirical formulas of chemical compounds given their stochiometric
// composition data
// USAGE:
// `node index.js {percentage}%{symbol} ...`
// Imports
const MolarMass = require('molarmass.js')
const molar = new MolarMass()
const lcm = require('lcm')
// Argument parsing
const args = process.argv.slice(2)
switch ( args.length ) {
case 0: throw new Error('No arguments given')
case 1: console.log(args[0].split('%')[1] + '1'); process.exit(0)
}
// Embedded utils
let simplify = (continued) => {
// Simplify a continued fraction
// into a fraction with numerator `p`
// and denominator `q`:
// return [p, q]
let p = continued[continued.length - 1]
let q = 1
for ( let i = continued.length - 2; i >= 0; i-- ) {
// Cache `p` and `q`
let p_cache = p
let q_cache = q
// Update `p` and `q`
// Reciprocal
p = q_cache
q = p_cache
// Add
p += continued[i] * q
}
return [p, q]
}
let rational = (decimal, error=0.01) => {
// Express `decimal` as a continued fraction,
// then simplify the result and
// return the numerator (p) and denominator (q)
// in an array: [p, q]
// while guaranteeing that |p/(q*d) - 1| <= e
// where e is the error and d is `decimal`
// Express as continued fraction
let expansion = []
let latest = decimal
let p = Math.floor(decimal); let q = 1;
while ( Math.abs(p / (q * decimal) - 1) > error ) {
// Append to expansion
expansion.push(
Math.floor(latest)
)
// Update latest
latest = 1 / (latest - Math.floor(latest))
// Update p and q
fraction = simplify(expansion)
p = fraction[0]; q = fraction[1]
}
return [p, q]
}
// Main
let map = {}
// Parse arguments as raw percentages (masses) into `map`
for ( let arg of args ) {
let [percentage, symbol] = arg.split('%')
map[symbol] = parseFloat(percentage)
}
// Convert masses to moles
for ( let [symbol, mass] of Object.entries(map) ) {
map[symbol] = mass / molar.getMolarMass(symbol)
}
// Convert moles to ratios (in terms of first element)
const first = map[Object.keys(map)[0]]
for ( let [symbol, moles] of Object.entries(map) ) {
map[symbol] = moles / first
}
// Convert ratios to rational approximations ([p, q])
for ( let [symbol, ratio] of Object.entries(map) ) {
map[symbol] = rational(ratio)
}
// Get rid of all the denominators (`q`)
let denominators = Object.values(map).map(fraction => fraction[1])
let k = 1
for ( let denominator of denominators ) k = lcm(k, denominator)
for ( let [symbol, [p, q]] of Object.entries(map) ) {
map[symbol] = Math.floor(k * p / q)
}
// Print
let str = ''
for ( let [symbol, subscript] of Object.entries(map) ) {
str += symbol + subscript
}
console.log(str)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment