rubychem 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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"
@@ -1,3 +1,3 @@
1
1
  load 'rubychem/molecule.rb'
2
2
  load 'rubychem/valence.rb'
3
- load 'rubychem/equation.rb'
3
+ load 'rubychem/equation.rb'
@@ -1,24 +1,26 @@
1
1
  module RubyChem
2
2
  class Equation
3
- attr_accessor :left, :right
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
- # Parse string... so that x + y + z = a + b
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
- process_sides(left_and_right)
15
+ process_equation_string(left_and_right)
14
16
  end
15
17
 
16
- def process_sides(left_and_right)
17
- process(left_and_right[0], "left")
18
- process(left_and_right[1], "right")
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 process(equation, side)
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
@@ -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 }
@@ -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
- 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
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
- def calculate_electronic_config
16
- @shell_location = 0
17
- 1.upto(100) do |x|
18
- until @v <= 0
19
- shell_electrons = determine_valence_electrons_in_shell(@shell_location)
20
- @before_subtract = @v
21
- @v -= shell_electrons
22
- @calc_valence[Fillorder[@shell_location]] = shell_electrons
23
- move_on(@v)
24
- end
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
 
@@ -1,3 +1,3 @@
1
1
  module Rubychem
2
- VERSION = "1.0.4"
2
+ VERSION = "1.0.5"
3
3
  end
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
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 00:00:00.000000000 Z
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.markdown
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
@@ -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`