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.
- 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`
|