rubychem 1.0.4 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +38 -0
- data/lib/rubychem.rb +1 -1
- data/lib/rubychem/equation.rb +238 -8
- data/lib/rubychem/molecule.rb +1 -0
- data/lib/rubychem/valence.rb +19 -20
- data/lib/rubychem/version.rb +1 -1
- metadata +3 -3
- data/README.markdown +0 -18
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
Rubychem
|
2
|
+
===
|
3
|
+
Rubychem (<a href="http://github.com/fogonthedowns/rubychem" target="_blank">http://github.com/fogonthedowns/rubychem</a>) is a new Chemistry library for Ruby, supporting the calculation of moles, balancing chemical equations and valence orbital calculations. We wrote Rubychem with three goals:
|
4
|
+
|
5
|
+
* Lightweight: Rubychem should be a light simple library, providing a chemistry API and returning simple Ruby.
|
6
|
+
* Fast: Rubychem should, out of the box, be quick.
|
7
|
+
* Accurate: Rubychem should have complete test coverage, so you can rely on it.
|
8
|
+
|
9
|
+
|
10
|
+
1.0.4 beta
|
11
|
+
---
|
12
|
+
Version 1.0.4 is currently in beta, filled with features you'll love. To download the beta:
|
13
|
+
|
14
|
+
gem install rubychem
|
15
|
+
|
16
|
+
|
17
|
+
Chemistry Calculations
|
18
|
+
---
|
19
|
+
Using rubychem is quite straightforward:
|
20
|
+
|
21
|
+
valence = RubyChem::Valence.new(10).calc_valence
|
22
|
+
{"1s"=>2, "2s"=>2, "2p"=>6}
|
23
|
+
chemical_species = RubyChem::Chemical.new("H2O").chem_species
|
24
|
+
moles = RubyChem::Chemical.new("H2O",2).moles
|
25
|
+
|
26
|
+
|
27
|
+
You can use rubychem to parse chemical equations
|
28
|
+
|
29
|
+
equation = RubyChem::Equation.new("NaCl = Na + Cl")
|
30
|
+
equation.left
|
31
|
+
[#<RubyChem::Chemical:0x007f842313dae0 @chem_species=[["Na", "1"]], @mm=22.99, @moles=0.04349717268377556>, #<RubyChem::Chemical:0x007f842313d2e8 @chem_species=[["Cl", "1"]], @mm=35.45, @moles=0.028208744710860365>]
|
32
|
+
|
33
|
+
|
34
|
+
You can enter an unbalanced chemical equation and have rubychem take care of the dirty work
|
35
|
+
|
36
|
+
chemical = RubyChem::Equation.new("C12H26+O2=CO2+H2O")
|
37
|
+
chemical.balance
|
38
|
+
"2C12H26 + 37O2 = 24C1O2 + 26H2O1"
|
data/lib/rubychem.rb
CHANGED
data/lib/rubychem/equation.rb
CHANGED
@@ -1,24 +1,26 @@
|
|
1
1
|
module RubyChem
|
2
2
|
class Equation
|
3
|
-
|
3
|
+
require 'rational'
|
4
|
+
attr_accessor :left, :right, :right_system_of_equations, :left_system_of_equations, :left_total, :right_total, :array, :search_order, :balanced
|
4
5
|
|
5
6
|
# Checks if two formulas are balanced.
|
6
|
-
#
|
7
|
+
# Takes user input.. such as x + y + z = a + b
|
7
8
|
# yields @left[Chemical1, Chemical2, Chemical3], @right[Chemical1,Chemical2]
|
8
9
|
|
9
10
|
def initialize(equation)
|
10
11
|
@left = Array.new
|
11
12
|
@right = Array.new
|
13
|
+
|
12
14
|
left_and_right = equation.split(/\=/)
|
13
|
-
|
15
|
+
process_equation_string(left_and_right)
|
14
16
|
end
|
15
17
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
18
|
+
def process_equation_string(left_and_right)
|
19
|
+
instantiate_chemical_object_from_string(left_and_right[0], "left")
|
20
|
+
instantiate_chemical_object_from_string(left_and_right[1], "right")
|
19
21
|
end
|
20
22
|
|
21
|
-
def
|
23
|
+
def instantiate_chemical_object_from_string(equation, side)
|
22
24
|
if side == "left"
|
23
25
|
equation.split(/\+/).each do |chemical|
|
24
26
|
@left << RubyChem::Chemical.new(chemical.strip)
|
@@ -29,7 +31,235 @@ module RubyChem
|
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|
32
|
-
|
34
|
+
|
35
|
+
|
36
|
+
# add up all atoms, on each side
|
37
|
+
# left = {Na:1,Cl:2}
|
38
|
+
# right = {Na:2,Cl:4}
|
39
|
+
def list_atoms
|
40
|
+
@left_total = Hash.new
|
41
|
+
@right_total = Hash.new
|
42
|
+
list_part(@left, @left_total)
|
43
|
+
list_part(@right, @right_total)
|
44
|
+
end
|
45
|
+
|
46
|
+
def list_part(part, total)
|
47
|
+
part.each do |chemical|
|
48
|
+
chemical.chem_species.each do |atom|
|
49
|
+
if total[atom[0]].nil?
|
50
|
+
total[atom[0]] = atom[1]
|
51
|
+
else
|
52
|
+
total[atom[0]] += atom[1]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def is_balanced?
|
59
|
+
@right_total.each_key do |key|
|
60
|
+
@balanced = @right_total[key] == @left_total[key]
|
61
|
+
end
|
62
|
+
@balanced
|
63
|
+
end
|
64
|
+
|
65
|
+
# linear algebra
|
66
|
+
# http://www.saintjoe.edu/~karend/m244/ChemicalEquations.pdf
|
67
|
+
# Set up a system of equations from unbalanced chemical
|
68
|
+
# rearrange the system of equations to form an augmented matrix
|
69
|
+
# transform a matrix to the reduced row echelon form
|
70
|
+
|
71
|
+
# System of Equations setup
|
72
|
+
# 1. Assign unknown coeficients
|
73
|
+
# C12H26 + O2 = CO2 + H2O
|
74
|
+
# aC12H26 + bO2 = cCO2 + dH2O
|
75
|
+
|
76
|
+
def set_up_system_of_equations
|
77
|
+
@right_system_of_equations = Hash.new
|
78
|
+
@left_system_of_equations = Hash.new
|
79
|
+
part_set_up_system_of_equations(@right,@right_system_of_equations)
|
80
|
+
part_set_up_system_of_equations(@left,@left_system_of_equations)
|
81
|
+
end
|
82
|
+
|
83
|
+
def part_set_up_system_of_equations(part,total)
|
84
|
+
part.each do |key|
|
85
|
+
total[key] = 0
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# 2. determnie instances on left and right of each atom, and assign those to coeficients
|
90
|
+
# C12H26 + O2 = CO2 + H2O
|
91
|
+
# O = {left:"2b",right:"2c + 1d"}
|
92
|
+
# O = "2b=2c+1d"
|
93
|
+
|
94
|
+
def assign_coeficients_to_system_of_equations
|
95
|
+
self.list_atoms
|
96
|
+
atom_list = self.left_total.merge(self.right_total)
|
97
|
+
# Get the Chemical list
|
98
|
+
atom_list.keys.each do |atom|
|
99
|
+
|
100
|
+
self.set_up_system_of_equations
|
101
|
+
assign_coeficients_to_part_system_of_equations(@right_system_of_equations,atom)
|
102
|
+
assign_coeficients_to_part_system_of_equations(@left_system_of_equations,atom)
|
103
|
+
subtract_right_side
|
104
|
+
write_matrix
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def assign_coeficients_to_part_system_of_equations(part,atom_to_search)
|
109
|
+
chemicals = part.keys
|
110
|
+
chemicals.each do |chemical|
|
111
|
+
@search_order << chemical
|
112
|
+
# look at the Chemical
|
113
|
+
chemical.chem_species.each do |atom|
|
114
|
+
# Does the chemical have the atom we are looking for?
|
115
|
+
if atom_to_search == atom[0]
|
116
|
+
part[chemical] = atom[1]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def subtract_right_side
|
123
|
+
self.right_system_of_equations.each do |k,v|
|
124
|
+
self.right_system_of_equations[k] = v *= -1
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# 3. Rearrange the system of equations and write it in a matrix
|
129
|
+
# a b c d
|
130
|
+
#O 0 2 -2 -1
|
131
|
+
# [[0,2,-2,-1]]
|
132
|
+
|
133
|
+
def write_matrix
|
134
|
+
array = Array.new
|
135
|
+
@left_system_of_equations.keys.each do |key|
|
136
|
+
array << @left_system_of_equations[key]
|
137
|
+
end
|
138
|
+
@right_system_of_equations.keys.each do |key|
|
139
|
+
array << @right_system_of_equations[key]
|
140
|
+
end
|
141
|
+
@array << array
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
def balance
|
146
|
+
@search_order = Array.new
|
147
|
+
@array = Array.new
|
148
|
+
@reduced_row_echelon_form = Array.new
|
149
|
+
self.assign_coeficients_to_system_of_equations
|
150
|
+
@reduced_row_echelon_form = self.reduced_row_echelon_form(@array)
|
151
|
+
self.apply_solved_equivalent_fractions_to_fraction
|
152
|
+
end
|
153
|
+
|
154
|
+
# from the reduced row echelon form we are left with a set
|
155
|
+
# of equivalent fractions to transform into a whole number
|
156
|
+
# we do this by using the gcdlcm method
|
157
|
+
|
158
|
+
def solve_equivalent_fractions
|
159
|
+
last = 0
|
160
|
+
array = Array.new
|
161
|
+
@reduced_row_echelon_form.each do |x|
|
162
|
+
array = last.gcdlcm(x.last.denominator)
|
163
|
+
last = x.last.denominator
|
164
|
+
end
|
165
|
+
array.max
|
166
|
+
end
|
167
|
+
|
168
|
+
# Now that we have the whole number from solve_equivalent_fractions
|
169
|
+
# we must apply that to each of the fractions to solve for the
|
170
|
+
# balanced equation
|
171
|
+
|
172
|
+
def apply_solved_equivalent_fractions_to_fraction
|
173
|
+
int = self.solve_equivalent_fractions
|
174
|
+
answer = []
|
175
|
+
@reduced_row_echelon_form.each do |row|
|
176
|
+
answer << row.last * int
|
177
|
+
end
|
178
|
+
answer << int
|
179
|
+
count = 0
|
180
|
+
@balanced = Hash.new
|
181
|
+
@left_system_of_equations.each do |x,v|
|
182
|
+
answer[count]
|
183
|
+
@left_system_of_equations[x] = answer[count].to_i.abs unless answer[count].nil?
|
184
|
+
count += 1
|
185
|
+
end
|
186
|
+
@right_system_of_equations.each do |x,v|
|
187
|
+
answer[count]
|
188
|
+
@right_system_of_equations[x] = answer[count].to_i.abs unless answer[count].nil?
|
189
|
+
count += 1
|
190
|
+
end
|
191
|
+
answer
|
192
|
+
@balanced = {left:@left_system_of_equations,right:@right_system_of_equations}
|
193
|
+
self.balanced_string
|
194
|
+
end
|
195
|
+
|
196
|
+
def balanced_string
|
197
|
+
array_of_strings = Array.new
|
198
|
+
@balanced[:left].each{|x,y|array_of_strings << (x.chem_species.unshift(y).join);x.chem_species.shift}
|
199
|
+
left = array_of_strings.join(" + ")
|
200
|
+
array_of_strings = []
|
201
|
+
@balanced[:right].each{|x,y|array_of_strings << (x.chem_species.unshift(y).join);x.chem_species.shift}
|
202
|
+
right = array_of_strings.join(" + ")
|
203
|
+
left + " = " + right
|
204
|
+
end
|
205
|
+
|
206
|
+
# returns an 2-D array where each element is a Rational
|
207
|
+
def reduced_row_echelon_form(ary)
|
208
|
+
lead = 0
|
209
|
+
rows = ary.size
|
210
|
+
cols = ary[0].size
|
211
|
+
rary = convert_to_rational(ary) # use rational arithmetic
|
212
|
+
catch :done do
|
213
|
+
rows.times do |r|
|
214
|
+
throw :done if cols <= lead
|
215
|
+
i = r
|
216
|
+
while rary[i][lead] == 0
|
217
|
+
i += 1
|
218
|
+
if rows == i
|
219
|
+
i = r
|
220
|
+
lead += 1
|
221
|
+
throw :done if cols == lead
|
222
|
+
end
|
223
|
+
end
|
224
|
+
# swap rows i and r
|
225
|
+
rary[i], rary[r] = rary[r], rary[i]
|
226
|
+
# normalize row r
|
227
|
+
v = rary[r][lead]
|
228
|
+
rary[r].collect! {|x| x /= v}
|
229
|
+
# reduce other rows
|
230
|
+
rows.times do |i|
|
231
|
+
next if i == r
|
232
|
+
v = rary[i][lead]
|
233
|
+
rary[i].each_index {|j| rary[i][j] -= v * rary[r][j]}
|
234
|
+
end
|
235
|
+
lead += 1
|
236
|
+
end
|
237
|
+
end
|
238
|
+
rary
|
239
|
+
end
|
240
|
+
|
241
|
+
def convert_to_rational(ary)
|
242
|
+
new = []
|
243
|
+
ary.each_index do |row|
|
244
|
+
new << ary[row].collect {|elem| Rational(elem)}
|
245
|
+
end
|
246
|
+
new
|
247
|
+
end
|
248
|
+
|
249
|
+
# type should be one of :to_s, :to_i, :to_f, :to_r
|
250
|
+
def convert_to(ary, type)
|
251
|
+
new = []
|
252
|
+
ary.each_index do |row|
|
253
|
+
new << ary[row].collect {|elem| elem.send(type)}
|
254
|
+
end
|
255
|
+
new
|
256
|
+
end
|
257
|
+
|
258
|
+
def print_matrix(m)
|
259
|
+
max = m[0].collect {-1}
|
260
|
+
m.each {|row| row.each_index {|i| max[i] = [max[i], row[i].to_s.length].max}}
|
261
|
+
m.each {|row| row.each_index {|i| print "%#{max[i]}s " % row[i].to_s}; puts ""}
|
262
|
+
end
|
33
263
|
|
34
264
|
end
|
35
265
|
end
|
data/lib/rubychem/molecule.rb
CHANGED
@@ -27,6 +27,7 @@ module RubyChem
|
|
27
27
|
def speciate(x,grams=1)
|
28
28
|
@chem_species = x.map { |chem| chem.scan(/[A-Z][^A-Z]*/) }.flatten
|
29
29
|
@chem_species.map! {|chem| chem.scan /[A-Z]+|\d+/i }
|
30
|
+
@chem_species.map!{|s1, s2| [s1, *(s2.to_i if s2), *(1 if s2.nil?),]}
|
30
31
|
atom_masses = @chem_species.map { |(elem, coeff)| MASSES[elem.to_sym] * (coeff || 1).to_f }
|
31
32
|
x = atom_masses.map { |int| int.to_f }
|
32
33
|
@mm = x.inject(0) { |s,v| s+= v }
|
data/lib/rubychem/valence.rb
CHANGED
@@ -3,28 +3,27 @@ class Valence
|
|
3
3
|
Fillorder = ["1s","2s","2p","3s","3p","4s","3d","4p","5s","4d","5p","6s","4f","5d","6p","7s","5f","6d","7p"]
|
4
4
|
attr_accessor :calc_valence
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
def initialize(k)
|
7
|
+
@mass = k
|
8
|
+
determine_valence(@mass)
|
9
|
+
@calc_valence = Hash.new
|
10
|
+
@calc_valence = calculate_electronic_config
|
11
|
+
identify_valence_electrons
|
12
|
+
end
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
@calc_valence
|
14
|
+
def calculate_electronic_config
|
15
|
+
@shell_location = 0
|
16
|
+
1.upto(100) do |x|
|
17
|
+
until @v <= 0
|
18
|
+
shell_electrons = determine_valence_electrons_in_shell(@shell_location)
|
19
|
+
@before_subtract = @v
|
20
|
+
@v -= shell_electrons
|
21
|
+
@calc_valence[Fillorder[@shell_location]] = shell_electrons
|
22
|
+
move_on(@v)
|
23
|
+
end
|
27
24
|
end
|
25
|
+
@calc_valence
|
26
|
+
end
|
28
27
|
|
29
28
|
private
|
30
29
|
|
data/lib/rubychem/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubychem
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-18 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: This is an open source library of chemistry, goodies. I'm dedicating
|
15
15
|
this code to my major professor Arthur Brecher and Graduate PI Dr. Rogers of Bowling
|
@@ -24,7 +24,7 @@ files:
|
|
24
24
|
- Gemfile
|
25
25
|
- Gemfile.lock
|
26
26
|
- LICENSE
|
27
|
-
- README.
|
27
|
+
- README.md
|
28
28
|
- Rakefile
|
29
29
|
- features/step_definitions/user_starts_program_steps.rb
|
30
30
|
- features/step_definitions/user_submits_compound_steps.rb
|
data/README.markdown
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
# RubyChem - scripting chemistry intelligence
|
2
|
-
|
3
|
-
************************************************************************
|
4
|
-
|
5
|
-
# Installation
|
6
|
-
|
7
|
-
gem install rubychem
|
8
|
-
|
9
|
-
|
10
|
-
# Use
|
11
|
-
x = RubyChem::Valence.new(1).calc_valence
|
12
|
-
|
13
|
-
x = RubyChem::Chemical.new("H2O")
|
14
|
-
=> #<RubyChem::Chemical:0x00000101059540 @chem_species=[["H", "2"], ["O", "1"]], @mm=18.01, @moles=.0.010205122971731808>
|
15
|
-
x = RubyChem::Chemical.new("H2O").chem_species
|
16
|
-
=> [["H", "2"], ["O", "1"]]
|
17
|
-
x = RubyChem::Chemical.new("H2O",1).moles
|
18
|
-
=> 0.010205122971731808`
|