rubychem 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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`