chem-eb
Version:
Simple Chemical Equation Balancing
140 lines (132 loc) • 4.41 kB
JavaScript
let $ = require('meeko')
let chemSymbol =
'H,He,Li,Be,B,C,N,O,F,Ne,Na,Mg,Al,Si,P,S,Cl,Ar,K,Ca,Sc,Ti,V,Cr,Mn,Fe,Co,Ni,Cu,Zn,Ga,Ge,As,Se,Br,Kr,Rb,Sr,Y,Zr,Nb,Mo,Tc,Ru,Rh,Pd,Ag,Cd,In,Sn,Sb,Te,I,Xe,Cs,Ba,La,Ce,Pr,Nd,Pm,Sm,Eu,Gd,Tb,Dy,Ho,Er,Tm,Yb,Lu,Hf,Ta,W,Re,Os,Ir,Pt,Au,Hg,Tl,Pb,Bi,Po,At,Rn,Fr,Ra,Ac,Th,Pa,U,Np,Pu,Am,Cm,Bk,Cf,Es,Fm,Md,No,Lr,Rf,Db,Sg,Bh,Hs,Mt,Ds,Rg,Cn,Nh,Fl,Mc,Lv,Ts,Og'.split(
','
)
function expandElm (elm) {
const regex = /\(([^)]+)\)(\d*)/
return elm
.replace(regex, (match, group, count) => {
count = +count || 1 // default 1
return group.repeat(count)
})
.replace(/\(|\)/g, '') // remove '(' ,')'
}
function chemEq2Matrix (chemStr) {
let eqArr = chemStr.replace(/ +/g, '').split(/(\->|=|\-|→)/g)
// console.log(eqArr)
let leftEqArrOri = eqArr[0].split('+') // unexpand element
let rightEqArrOri = eqArr[2].split('+')
let leftEqArr = leftEqArrOri.map(x => expandElm(x))
let rightEqArr = rightEqArrOri.map(x => expandElm(x))
let chemElmObj = {} // element inside 有哪些元素
eqArr = [...leftEqArr, ...rightEqArr]
// console.log(leftEqArr, rightEqArr)
let len = chemSymbol.length
let leftLen = leftEqArr.length
let rightLen = rightEqArr.length
let matrix = [[1, ...Array(leftLen + rightLen).fill(0)]]
for (let i = 0; i < len; i++) {
let chemElm = chemSymbol[i]
let rowArr = []
let idx = 0
for (let n = 0; n < leftLen + rightLen; n++) {
let isElm =
eqArr[n].match(
new RegExp(chemElm + '[0-9]*(?=[A-Z]+|[0-9]+|$)', 'g')
) || [] // 同一个式子里面有多个 同样的元素
if (isElm.length > 0) {
let sum = $.math.sum(isElm.map(x => +x.replace(chemElm, '') || 1))
rowArr[idx] = (idx >= leftLen ? -1 : 1) * sum
//console.log(chemElm, sum, rowArr[idx])
chemElmObj[chemElm] = 1
} else {
rowArr[idx] = 0
}
idx++
}
if (rowArr.some(x => x != 0)) {
rowArr.push(0)
matrix.push(rowArr)
}
}
if (matrix[0].length <= matrix.length) {
//over-determined equation 解决超定方程
matrix.remove(0)
for (let i = 0; i < matrix.length; i++) {
matrix[i].remove(matrix[i].length - 1)
}
matrix = $.math.mat.mul($.math.mat.transpose(matrix), matrix)
for (let i = 0; i < matrix.length; i++) {
matrix[i].push(0)
}
matrix.unshift([1, ...Array(leftLen + rightLen).fill(0)]) //重新填入值
//解决超定方程
// 方程组大于未知数 移除有第一个主元的一行
for (let i = 1; i < matrix.length; i++) {
if (matrix[i][0]) {
matrix.remove(i, 1)
break
}
}
}
return {
matrix,
leftEqArr,
rightEqArr,
leftEqArrOri,
rightEqArrOri,
leftLen,
rightLen,
chemElmObj
}
}
function balanceEq (chemStr) {
try {
// render formula
let m = chemEq2Matrix(chemStr)
// console.log(m)
m.leftEqArrColor = []
m.rightEqArrColor = []
let pos = m.matrix[0].length - 1
for (let i = 0; i < 200; i++) {
let newMatrix = m.matrix.copy()
newMatrix[0][pos] = i + 1
let rst = $.math.gaussian(newMatrix)
if (
rst.every(x => {
return $.math.approximatelyEqual(x, x.round(0) | 0)
})
) {
let newEqArr = []
let ratio
for (let i = 0; i < m.leftLen + m.rightLen; i++) {
ratio = rst[i].round(0)
if (i < m.leftLen) {
m.leftEqArrColor[i] = $.c.y(ratio) + m.leftEqArrOri[i]
m.leftEqArr[i] = (ratio === 1 ? '' : ratio) + m.leftEqArrOri[i]
} else {
m.rightEqArrColor[i - m.leftLen] =
$.c.y(ratio) + m.rightEqArrOri[i - m.leftLen]
m.rightEqArr[i - m.leftLen] =
(ratio === 1 ? '' : ratio) + m.rightEqArrOri[i - m.leftLen]
}
}
let outChemColorStr =
m.leftEqArrColor.join(' + ') + ' = ' + m.rightEqArrColor.join(' + ')
let outChemStr =
m.leftEqArr.join(' + ') + ' = ' + m.rightEqArr.join(' + ')
return {
oriChem: chemStr,
outChem: outChemStr,
outChemCol: outChemColorStr
}
}
}
return { oriChem: chemStr, outChem: '', outChemCol: '' }
} catch (e) {
//TODO: 增加错误原因
return { oriChem: chemStr, outChem: '', outChemCol: '' }
}
}
module.exports = balanceEq