Chemical calculator: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎{{header|zkl}}: added code)
m (→‎{{header|zkl}}: piddle on my shoe)
Line 573: Line 573:
=={{header|zkl}}==
=={{header|zkl}}==
<lang zkl>fcn molarMass(str,mass=0.0){
<lang zkl>fcn molarMass(str,mass=0.0){
while(span:=str.span("(",")",False)){ // get inner most () group
while(span:=str.span("(",")",False)){ // get inner most () group
group:=str[span.xplode()]; // (CH3)
group:=str[span.xplode()]; // (CH3)
str =str.del(span.xplode()); // nuke (CH3)
str =str.del(span.xplode()); // nuke (CH3)
w:=molarMass(group[1,-1],mass); // remove "(" & ")"
w :=molarMass(group[1,-1],mass); // remove "(" & ")"
i,s2 := span[0], str[i,*];
i,s2 := span[0], str[i,*];
if(m.search(s2)) // well crap, (CH3)2
if(m.search(s2)) // well crap, (CH3)2
{ z:=m.matched[1]; str = str.del(i,z.len()); mass=w*z.toInt() }
{ z:=m.matched[1]; str=str.del(i,z.len()); mass=w*z.toInt() }
else mass=w;
else mass=w;
}
}
ms:=List(mass); // HO --> (1.008,15.999).sum()
ms:=List(mass); // HO --> (1.008,15.999).sum()
while(str){
while(str){
nm:=atoms.filter1('wrap(a){ (a+"*").glob(str,0) });
nm:=atoms.filter1('wrap(a){ a.glob(str,0) }); // case matters
if(not nm) // H2, didn't match 2, look for 2 now
if(not nm) // H2, didn't match 2, look for 2 now
{ m.search(str); nm=m.matched[1]; ms[-1]*=nm.toInt(); }
{ m.search(str); nm=m.matched[1]; ms[-1]*=nm.toInt(); }
else ms.append(atomicMass[nm]);
else ms.append(atomicMass[nm=nm[0,-1]]); // "H*"-->"H"
str=str.del(0,nm.len()) // nuke H or 2
str=str.del(0,nm.len()); // nuke H or 2
}
}
ms.reduce('+);
ms.reduce('+);
Line 614: Line 614:
"Y" , 88.905840, "Yb",173.054000, "Zn", 65.380000, "Zr", 91.224000,
"Y" , 88.905840, "Yb",173.054000, "Zn", 65.380000, "Zr", 91.224000,
), m=RegExp("([1-9]+)"),
), m=RegExp("([1-9]+)"),
atoms=fcn{ // sort by name length
atoms=fcn{ // sort by name length, tack on "*" so "H*" globs H2
nms:=atomicMass.keys;
nms:=atomicMass.keys;
[(nms.apply("len") : (0).max(_)) .. 1, -1].pump(List, // 2..1
[(nms.apply("len") : (0).max(_)) .. 1, -1].pump(List, // 2..1
'wrap(n){ nms.filter('wrap(nm){ nm.len()==n }) }).flatten()
'wrap(n){ nms.filter('wrap(nm){ nm.len()==n }) }).flatten()
}();</lang>
.apply('+("*"));
}(); // --> (Lu*, Es*, Er*, Eu*, ... V*, W*, Y*)</lang>
<lang zkl>foreach cstr in (T("H","H2","H2O","Na2SO4","C6H12","COOH(C(CH3)2)3CH3"))
<lang zkl>foreach cstr in (T("H","H2","H2O","Na2SO4","C6H12","COOH(C(CH3)2)3CH3"))
{ println(cstr," --> ",molarMass(cstr)) }</lang>
{ println(cstr," --> ",molarMass(cstr)) }</lang>

Revision as of 00:26, 19 March 2019

Chemical calculator is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

This application calculates the molar mass given the molecule's chemical formula.

Introduction
  • A molecule consists of atoms. E.g. water, H2O has two hydrogen atoms and one oxygen atom
  • The mass of water, H2O is 1.008 * 2 + 15.999 = 18.015
  • An atom name consists of one upper-case letter followed by zero or one lower-case letter.
    • H (Hydrogen)
    • He (Helium)
  • In the future one more lower-case letter might be used.
    • Uue (Ununennium)
    • Ubn (Unbinilium)
  • The number of atoms is stated behind the atom or atom group
  • An atom group is specified using parenthesis. E.g. Butyric acid, (CH3)2CHCOOH, has two CH3 groups
  • A group may contain other groups, e.g. COOH(C(CH3)2)3CH3
  • The atom masses are available in the Python section below
Background
  • The mass is dimensionless. It is relative to 1/12 of Carbon-12
  • Carbon-12 has exactly 12 protons, 12 electrons and 12 neutrons
  • One mole of H2O has the mass 18.015 grams
  • One mole is 6.02214076E23
Examples

<lang python>assert 1.008 == molar_mass('H') assert 2.016 == molar_mass('H2') assert 18.015 == molar_mass('H2O') assert 142.03553856000002 == molar_mass('Na2SO4') assert 84.162 == molar_mass('C6H12') assert 186.29499999999996 == molar_mass('COOH(C(CH3)2)3CH3')</lang>

Link

https://en.wikipedia.org/wiki/Molecular_mass

Go

Translation of: Python

<lang go>package main

import (

   "fmt"
   "strconv"
   "strings"

)

type any = interface{}

type ht = map[string]int

type Stack []any

func (s *Stack) push(a any) {

   *s = append(*s, a)

}

func (s *Stack) pop() any {

   le := len(*s)
   if le == 0 {
       panic("Attempt to pop from an empty stack")
   }
   le--
   a := (*s)[le]
   *s = (*s)[:le]
   return a

}

var atomicMass = map[string]float64{

   "H":  1.008,
   "He": 4.002602,
   "Li": 6.94,
   "Be": 9.0121831,
   "B":  10.81,
   "C":  12.011,
   "N":  14.007,
   "O":  15.999,
   "F":  18.998403163,
   "Ne": 20.1797,
   "Na": 22.98976928,
   "Mg": 24.305,
   "Al": 26.9815385,
   "Si": 28.085,
   "P":  30.973761998,
   "S":  32.06,
   "Cl": 35.45,
   "Ar": 39.948,
   "K":  39.0983,
   "Ca": 40.078,
   "Sc": 44.955908,
   "Ti": 47.867,
   "V":  50.9415,
   "Cr": 51.9961,
   "Mn": 54.938044,
   "Fe": 55.845,
   "Co": 58.933194,
   "Ni": 58.6934,
   "Cu": 63.546,
   "Zn": 65.38,
   "Ga": 69.723,
   "Ge": 72.630,
   "As": 74.921595,
   "Se": 78.971,
   "Br": 79.904,
   "Kr": 83.798,
   "Rb": 85.4678,
   "Sr": 87.62,
   "Y":  88.90584,
   "Zr": 91.224,
   "Nb": 92.90637,
   "Mo": 95.95,
   "Ru": 101.07,
   "Rh": 102.90550,
   "Pd": 106.42,
   "Ag": 107.8682,
   "Cd": 112.414,
   "In": 114.818,
   "Sn": 118.710,
   "Sb": 121.760,
   "Te": 127.60,
   "I":  126.90447,
   "Xe": 131.293,
   "Cs": 132.90545196,
   "Ba": 137.327,
   "La": 138.90547,
   "Ce": 140.116,
   "Pr": 140.90766,
   "Nd": 144.242,
   "Pm": 145,
   "Sm": 150.36,
   "Eu": 151.964,
   "Gd": 157.25,
   "Tb": 158.92535,
   "Dy": 162.500,
   "Ho": 164.93033,
   "Er": 167.259,
   "Tm": 168.93422,
   "Yb": 173.054,
   "Lu": 174.9668,
   "Hf": 178.49,
   "Ta": 180.94788,
   "W":  183.84,
   "Re": 186.207,
   "Os": 190.23,
   "Ir": 192.217,
   "Pt": 195.084,
   "Au": 196.966569,
   "Hg": 200.592,
   "Tl": 204.38,
   "Pb": 207.2,
   "Bi": 208.98040,
   "Po": 209,
   "At": 210,
   "Rn": 222,
   "Fr": 223,
   "Ra": 226,
   "Ac": 227,
   "Th": 232.0377,
   "Pa": 231.03588,
   "U":  238.02891,
   "Np": 237,
   "Pu": 244,
   "Am": 243,
   "Cm": 247,
   "Bk": 247,
   "Cf": 251,
   "Es": 252,
   "Fm": 257,

}

// "H2O" => H 2 O func parse(s string) (result []string, pattern string) {

   for i := 0; i < len(s); {
       switch {
       case s[i] >= 'A' && s[i] <= 'Z':
           if i+1 < len(s) && s[i+1] >= 'a' && s[i+1] <= 'z' {
               result = append(result, fmt.Sprintf("%c%c", s[i], s[i+1]))
               pattern += "A"
               i += 2
           } else {
               result = append(result, string(s[i]))
               pattern += "A"
               i++
           }
       case s[i] >= '0' && s[i] <= '9':
           antal := int(s[i]) - 48
           i++
           for i < len(s) && s[i] >= '0' && s[i] <= '9' {
               antal = antal*10 + int(s[i]) - 48
               i++
           }
           result = append(result, strconv.Itoa(antal))
           pattern += "1"
       default:
           result = append(result, string(s[i]))
           pattern += string(s[i])
           i++
       }
   }
   return

}

// H 2 O => H * 2 + O func pass1(m1 []string, m2 string) (result []string) {

   const symbols = "A1()"
   matrix := [4]string{
       "+*+=", // A = Atom
       "+x+=", // 1 = Count
       "=x=x", // (
       "+*+=", // )
   }
   add := func(a string) []string { return []string{a, "+"} }
   mul := func(a string) []string { return []string{a, "*"} }
   err := func(a string) []string { return []string{} }
   nop := func(a string) []string { return []string{a} }
   operation := map[byte]func(string) []string{
       '+': add, '*': mul, 'x': err, '=': nop,
   }
   for i := 0; i < len(m1)-1; i++ {
       ch0 := m2[i]
       ch1 := m2[i+1]
       i0 := strings.IndexByte(symbols, ch0)
       i1 := strings.IndexByte(symbols, ch1)
       op := matrix[i0][i1]
       result = append(result, operation[op](m1[i])...)
   }
   result = append(result, m1[len(m1)-1])
   return

}

// H * 2 + O => H 2 * O + func pass2(tokens []string) (result []string) {

   ops := "+*"
   stack := new(Stack)
   for _, token := range tokens {
       switch token {
       case "(":
           stack.push(token)
       case ")":
           for len(*stack) > 0 {
               op := stack.pop().(string)
               if op == "(" {
                   break
               }
               result = append(result, op)
           }
       default:
           if strings.Index(ops, token) >= 0 {
               for len(*stack) > 0 {
                   op := (*stack)[len(*stack)-1].(string)
                   if strings.Index(ops, op) == -1 {
                       break
                   }
                   if strings.Index(ops, token) >= strings.Index(ops, op) {
                       break
                   }
                   stack.pop()
                   result = append(result, op)
               }
               stack.push(token)
           } else {
               result = append(result, token)
           }
       }
   }
   for len(*stack) > 0 {
       result = append(result, stack.pop().(string))
   }
   return

}

// H 2 * O + => { H:2, O:1 } func pass3(rpn []string) ht {

   stack := new(Stack)
   for _, item := range rpn {
       switch {
       case item == "+":
           h1 := stack.pop().(ht)
           h2 := (*stack)[len(*stack)-1].(ht)
           for key := range h1 {
               if h2[key] > 0 {
                   h2[key] += h1[key]
               } else {
                   h2[key] = h1[key]
               }
           }
       case item == "*":
           antal, _ := strconv.Atoi(stack.pop().(string))
           hash := (*stack)[len(*stack)-1].(ht)
           for key := range hash {
               hash[key] *= antal
           }
       case atomicMass[item] > 0.0:
           res := make(ht)
           res[item] = 1
           stack.push(res)
       default:
           stack.push(item)
       }
   }
   return stack.pop().(ht)

}

// { H:2, O:1 } => 18.015 func pass4(atoms ht) float64 {

   sum := 0.0
   for key := range atoms {
       sum += atomicMass[key] * float64(atoms[key])
   }
   return sum

}

func molarMass(molecule string) float64 {

   atomList, pattern := parse(molecule)
   infix := pass1(atomList, pattern)
   rpn := pass2(infix)
   atoms := pass3(rpn)
   return pass4(atoms)

}

func main() {

   molecules := []string{"H", "H2", "H2O", "Na2SO4", "C6H12", "COOH(C(CH3)2)3CH3"}
   for _, molecule := range molecules {
       mass := molarMass(molecule)
       fmt.Printf("%17s -> %g\n", molecule, mass)
   }

}</lang>

Output:
                H -> 1.008
               H2 -> 2.016
              H2O -> 18.015
           Na2SO4 -> 142.03553856000002
            C6H12 -> 84.162
COOH(C(CH3)2)3CH3 -> 186.29499999999996

Python

Atom masses

<lang python>ATOMIC_MASS = { 'H':1.008, 'He':4.002602, 'Li':6.94, 'Be':9.0121831, 'B':10.81, 'C':12.011, 'N':14.007, 'O':15.999, 'F':18.998403163, 'Ne':20.1797, 'Na':22.98976928, 'Mg':24.305, 'Al':26.9815385, 'Si':28.085, 'P':30.973761998, 'S':32.06, 'Cl':35.45, 'Ar':39.948, 'K':39.0983, 'Ca':40.078, 'Sc':44.955908, 'Ti':47.867, 'V':50.9415, 'Cr':51.9961, 'Mn':54.938044, 'Fe':55.845, 'Co':58.933194, 'Ni':58.6934, 'Cu':63.546, 'Zn':65.38, 'Ga':69.723, 'Ge':72.630, 'As':74.921595, 'Se':78.971, 'Br':79.904, 'Kr':83.798, 'Rb':85.4678, 'Sr':87.62, 'Y':88.90584, 'Zr':91.224, 'Nb':92.90637, 'Mo':95.95, 'Ru':101.07, 'Rh':102.90550, 'Pd':106.42, 'Ag':107.8682, 'Cd':112.414, 'In':114.818, 'Sn':118.710, 'Sb':121.760, 'Te':127.60, 'I':126.90447, 'Xe':131.293, 'Cs':132.90545196, 'Ba':137.327, 'La':138.90547, 'Ce':140.116, 'Pr':140.90766, 'Nd':144.242, 'Pm':145, 'Sm':150.36, 'Eu':151.964, 'Gd':157.25, 'Tb':158.92535, 'Dy':162.500, 'Ho':164.93033, 'Er':167.259, 'Tm':168.93422, 'Yb':173.054, 'Lu':174.9668, 'Hf':178.49, 'Ta':180.94788, 'W':183.84, 'Re':186.207, 'Os':190.23, 'Ir':192.217, 'Pt':195.084, 'Au':196.966569, 'Hg':200.592, 'Tl':204.38, 'Pb':207.2, 'Bi':208.98040, 'Po':209, 'At':210, 'Rn':222, 'Fr':223, 'Ra':226, 'Ac':227, 'Th':232.0377, 'Pa':231.03588, 'U':238.02891, 'Np':237, 'Pu':244, 'Am':243, 'Cm':247, 'Bk':247, 'Cf':251, 'Es':252, 'Fm':257 }</lang>

Solution

<lang python>from atomic_mass import ATOMIC_MASS

def parse(s): # 'H2O' => H 2 O result = [] pattern = i = 0 while i<len(s): if s[i].isupper(): if i+1<len(s) and s[i+1].islower(): result.append(s[i]+s[i+1]) pattern += 'A' i+=2 else: result.append(s[i]) pattern += 'A' i+=1 elif s[i].isdigit(): antal = int(s[i]) i+=1 while i<len(s) and s[i].isdigit(): antal = antal*10 + int(s[i]) i+=1 result.append(str(antal)) pattern += '1' else: result.append(s[i]) pattern += s[i] i+=1 return result,pattern

def pass1(m1,m2): # H 2 O => H * 2 + O SYMBOLS = 'A1()' matrix = [ '+*+=', # A = Atom '+x+=', # 1 = Count '=x=x', # ( '+*+='] # )

result = []

add = lambda a, b: [a, '+'] mul = lambda a, b: [a, '*'] error = lambda a, b: [] nop = lambda a, b: [a] operation = {'+': add, '*': mul, 'x': error, '=': nop}

for i in range(len(m1)-1): ch0 = m2[i] ch1 = m2[i+1] i0 = SYMBOLS.index(ch0) i1 = SYMBOLS.index(ch1) op = matrix[i0][i1] result += operation[op](m1[i],m1[i+1]) result.append(m1[-1]) return result

def pass2(tokens): # H * 2 + O => H 2 * O + ops = "+*" stack = [] result = []

for token in tokens: if token == '(': stack.append(token) elif token == ')': while len(stack) > 0: op = stack.pop() if op == '(': break result.append(op) else: if token in ops: while len(stack) > 0: op = stack[-1] if not (op in ops): break if ops.find(token) >= ops.find(op): break stack.pop() result.append(op) stack.append(token) else: result.append(token)

while len(stack) > 0: result.append(stack.pop()) return result

def pass3(rpn): # H 2 * O + => { H:2, O:1 } stack = [] for item in rpn: if item == '+': h1 = stack.pop() h2 = stack[-1] for key in h1: if key in h2: h2[key] += h1[key] else: h2[key] = h1[key] elif item == '*': antal = int(stack.pop()) hash = stack[-1] for key in hash: hash[key] *= antal elif item in ATOMIC_MASS: res = {} res[item] = 1 stack.append(res) else: stack.append(item) return stack.pop()

def pass4(atoms): # { H:2, O:1 } => 18.015 return sum([ATOMIC_MASS[key] * atoms[key] for key in atoms])

def molar_mass(molecule): atom_list,pattern = parse(molecule) infix = pass1(atom_list,pattern) rpn = pass2(infix) atoms = pass3(rpn) return pass4(atoms)

#assert 'Na 10 C O O H ( C ( C H 3 ) 2 ) 3 C H 3' == ' '.join(atom_list) #assert 'A1AAAA(A(AA1)1)1AA1' == pattern #assert 'Na * 10 + C + O + O + H + ( C + ( C + H * 3 ) * 2 ) * 3 + C + H * 3' == (' '.join(infix)) #assert 'Na 10 * C O O H C C H 3 * + 2 * + 3 * C H 3 * + + + + + + +' == ' '.join(rpn) #assert {'Na':10, 'C': 11, 'O': 2, 'H': 22} == atoms

assert 1.008 == molar_mass('H') assert 2.016 == molar_mass('H2') assert 18.015 == molar_mass('H2O') assert 142.03553856000002 == molar_mass('Na2SO4') assert 84.162 == molar_mass('C6H12') assert 186.29499999999996 == molar_mass('COOH(C(CH3)2)3CH3')</lang>

zkl

<lang zkl>fcn molarMass(str,mass=0.0){

   while(span:=str.span("(",")",False)){  // get inner most () group
     group:=str[span.xplode()];	  // (CH3)
     str   =str.del(span.xplode());      // nuke (CH3)
     w    :=molarMass(group[1,-1],mass); // remove "(" & ")"
     i,s2 := span[0], str[i,*];
     if(m.search(s2))			  // well crap, (CH3)2
        { z:=m.matched[1]; str=str.del(i,z.len()); mass=w*z.toInt() }
     else mass=w;
  }
  ms:=List(mass);	// HO --> (1.008,15.999).sum()
  while(str){ 
     nm:=atoms.filter1('wrap(a){ a.glob(str,0) });  // case matters
     if(not nm) 	// H2, didn't match 2, look for 2 now

{ m.search(str); nm=m.matched[1]; ms[-1]*=nm.toInt(); }

     else ms.append(atomicMass[nm=nm[0,-1]]);  // "H*"-->"H"
     str=str.del(0,nm.len());		        // nuke H or 2
  }
  ms.reduce('+);

}</lang> <lang zkl>var [const] atomicMass = Dictionary(

 "Ac",227.000000, "Ag",107.868200, "Al", 26.981538, "Am",243.000000, "Ar", 39.948000, 
 "As", 74.921595, "At",210.000000, "Au",196.966569, "B" , 10.810000, "Ba",137.327000, 
 "Be",  9.012183, "Bi",208.980400, "Bk",247.000000, "Br", 79.904000, "C" , 12.011000, 
 "Ca", 40.078000, "Cd",112.414000, "Ce",140.116000, "Cf",251.000000, "Cl", 35.450000, 
 "Cm",247.000000, "Co", 58.933194, "Cr", 51.996100, "Cs",132.905452, "Cu", 63.546000, 
 "Dy",162.500000, "Er",167.259000, "Es",252.000000, "Eu",151.964000, "F" , 18.998403, 
 "Fe", 55.845000, "Fm",257.000000, "Fr",223.000000, "Ga", 69.723000, "Gd",157.250000, 
 "Ge", 72.630000, "H" ,  1.008000, "He",  4.002602, "Hf",178.490000, "Hg",200.592000, 
 "Ho",164.930330, "I" ,126.904470, "In",114.818000, "Ir",192.217000, "K" , 39.098300, 
 "Kr", 83.798000, "La",138.905470, "Li",  6.940000, "Lu",174.966800, "Mg", 24.305000, 
 "Mn", 54.938044, "Mo", 95.950000, "N" , 14.007000, "Na", 22.989769, "Nb", 92.906370, 
 "Nd",144.242000, "Ne", 20.179700, "Ni", 58.693400, "Np",237.000000, "O" , 15.999000, 
 "Os",190.230000, "P" , 30.973762, "Pa",231.035880, "Pb",207.200000, "Pd",106.420000, 
 "Pm",145.000000, "Po",209.000000, "Pr",140.907660, "Pt",195.084000, "Pu",244.000000, 
 "Ra",226.000000, "Rb", 85.467800, "Re",186.207000, "Rh",102.905500, "Rn",222.000000, 
 "Ru",101.070000, "S" , 32.060000, "Sb",121.760000, "Sc", 44.955908, "Se", 78.971000, 
 "Si", 28.085000, "Sm",150.360000, "Sn",118.710000, "Sr", 87.620000, "Ta",180.947880, 
 "Tb",158.925350, "Te",127.600000, "Th",232.037700, "Ti", 47.867000, "Tl",204.380000, 
 "Tm",168.934220, "U" ,238.028910, "V" , 50.941500, "W" ,183.840000, "Xe",131.293000, 
 "Y" , 88.905840, "Yb",173.054000, "Zn", 65.380000, "Zr", 91.224000, 

), m=RegExp("([1-9]+)"),

  atoms=fcn{  // sort by name length, tack on "*" so "H*" globs H2
     nms:=atomicMass.keys;
     [(nms.apply("len") : (0).max(_)) .. 1, -1].pump(List, // 2..1
        'wrap(n){ nms.filter('wrap(nm){ nm.len()==n }) }).flatten()
     .apply('+("*"));
  }();		// --> (Lu*, Es*, Er*, Eu*, ...  V*, W*, Y*)</lang>

<lang zkl>foreach cstr in (T("H","H2","H2O","Na2SO4","C6H12","COOH(C(CH3)2)3CH3"))

  { println(cstr," --> ",molarMass(cstr)) }</lang>
Output:
H --> 1.008
H2 --> 2.016
H2O --> 18.015
Na2SO4 --> 142.036
C6H12 --> 84.162
COOH(C(CH3)2)3CH3 --> 186.295