/
chemical_analysis.rb
166 lines (149 loc) · 5.32 KB
/
chemical_analysis.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
require_relative "globals.rb"
require_relative "deep_dup.rb"
def flatten_chemical_formula(formulaIn)
formula = formulaIn.deep_dup
#puts "flatten_chemical_formula(): formula:#{formula}"
out = []
formula.each do |e|
if e[:type] == :chemical
out.push e.deep_dup
elsif e[:type] == :parent
modified_children = e[:children].each do |child|
child[:subscript] = child[:subscript] * e[:subscript]
end
flatten_chemical_formula(modified_children).each do |e0|
out.push e0.deep_dup
end
end
end
#puts "flatten_chemical_formula(): out:#{out}"
return out
end
def molar_mass(formula)
flat_formula = flatten_chemical_formula(formula)
mass = 0
out = []
flat_formula.each do |e|
mass += $PeriodicTable[e[:name]].round(1) * e[:subscript]
out << "#{e[:subscript]}(#{$PeriodicTable[e[:name]].round(1)})"
end
return {mass: mass, string: out.join(" + ")}
end
def get_chemical_sum(chem)
out = {}
flatten_chemical_formula(chem).each do |element|
elementName = element[:name].to_sym
if out[elementName].nil?
out[elementName] = 0
end
out[elementName] += element[:subscript]
end
return out
end
def get_reaction_sums(reaction)
out = {reactants: {}, products: {}}
reaction[:reactants].each do |reactant|
flatten_chemical_formula(reactant[:chemical]).each do |element|
elementName = element[:name].to_sym
if out[:reactants][elementName].nil?
out[:reactants][elementName] = 0
end
out[:reactants][elementName] += element[:subscript] * reactant[:coefficient]
end
end
reaction[:products].each do |product|
flatten_chemical_formula(product[:chemical]).each do |element|
elementName = element[:name].to_sym
if out[:products][elementName].nil?
out[:products][elementName] = 0
end
out[:products][elementName] += element[:subscript] * product[:coefficient]
end
end
print "get_reaction_sums(): output:" if $DEBUG
pp out if $DEBUG
return out
end
def are_sums_balanced(sums)
if sums[:reactants].sort == sums[:products].sort
return true
else
return false
end
end
def base_10_to_base_n(num, base, arrayLength)
outNum = []
while num > 0
divMod = num.divmod(base)
outNum << divMod[1]
num = divMod[0]
end
while outNum.length < arrayLength
outNum << 0
end
return outNum
end
def balance_reaction(reactionIn)
reaction = reactionIn.deep_dup
sums = get_reaction_sums(reaction)
if sums[:reactants].keys.sort != sums[:products].keys.sort
raise "Invalid chemical formula -- cannot be balanced"
end
# go through and fill up the coefficients 1 by 1, as if it was a base $MAX_BALANCE_SEARCH_SPACE number
digits = reaction[:reactants].length + reaction[:products].length
totalSearchSpace = $MAX_BALANCE_SEARCH_SPACE ** (digits)
currentSearch = 0
loop do
currentSearch += 1
coefficients = base_10_to_base_n(currentSearch, $MAX_BALANCE_SEARCH_SPACE, digits)
if coefficients.include? 0
next
end
print "balance_reaction(): coefficients:" if $DEBUG
pp coefficients if $DEBUG
reaction[:reactants].each_with_index do |_, i|
reaction[:reactants][i][:coefficient] = coefficients[i]
end
reaction[:products].each_with_index do |_, i|
reaction[:products][i][:coefficient] = coefficients[i + reaction[:reactants].length]
end
# success
break if are_sums_balanced(get_reaction_sums(reaction))
# exhaustion
if currentSearch > totalSearchSpace
raise "Can't balance equation -- search space exhausted. Increase globals.rb/$MAX_BALANCE_SEARCH_SPACE, possibly?"
end
end
print "balance_reaction(): reaction:" if $DEBUG
pp reaction if $DEBUG
return reaction
end
def stoichiometric_chart(given, ratio1, unit1, formula1, ratioArrayCenter, ratio2, unit2, formula2)
#binding.pry
topLines = []
bottomLines = []
topLines << "#{given} #{unit1} #{formula1}"
bottomLines << " " * topLines.last.length
bottomLines << "#{ratio1} #{unit1}"
topLines << "1 mol" + (" " * (bottomLines.last.length - "1 mol".length))
if !ratioArrayCenter.nil?
topLines << "#{ratioArrayCenter[0]} mol #{formula2}"
bottomLines << "#{ratioArrayCenter[1]} mol #{formula1}"
if topLines.last.length < bottomLines.last.length
topLines.last << " " * (bottomLines.last.length - topLines.last.length)
else
bottomLines.last << " " * (topLines.last.length - bottomLines.last.length)
end
topLines << "#{ratio2} #{unit2}"
bottomLines << "1 mol" + (" " * (bottomLines.last.length - "1 mol".length))
end
topLine = topLines.join(" | ")
bottomLine = bottomLines.join(" | ")
middleLine = "-" * topLine.length
if !ratioArrayCenter.nil?
middleLine << " = #{(given * ratioArrayCenter[0] * ratio2)/(ratio1 * ratioArrayCenter[1])} #{unit2} #{formula2}"
else
middleLine << " = #{given/ratio1} mol #{formula1}"
end
return "#{topLine}\n#{middleLine}\n#{bottomLine}"
end