alumina 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +23 -0
- data/LICENSE +20 -0
- data/README.textile +17 -0
- data/Rakefile +63 -0
- data/VERSION +1 -0
- data/data/elements.yml +355 -0
- data/lib/alumina.rb +32 -0
- data/lib/alumina/atom.rb +93 -0
- data/lib/alumina/element.rb +30 -0
- data/lib/alumina/errors.rb +15 -0
- data/lib/alumina/hin.rb +7 -0
- data/lib/alumina/hin/parser.rb +139 -0
- data/lib/alumina/hin/writer.rb +34 -0
- data/lib/alumina/molecule.rb +89 -0
- data/lib/extensions.rb +59 -0
- data/spec/alumina_spec.rb +7 -0
- data/spec/spec_helper.rb +8 -0
- metadata +113 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Tim Morgan
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= Alumina
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2010 Tim Morgan. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "alumina"
|
8
|
+
gem.summary = %Q{Ruby parser for various chemistry-related file formats}
|
9
|
+
gem.description = %Q{This gem parses HyperChem's .HIN files, PDB files, and in the future others, converting them to Ruby objects for easy manipulation}
|
10
|
+
gem.email = "git@timothymorgan.info"
|
11
|
+
gem.homepage = "http://github.com/RISCfuture/alumina"
|
12
|
+
gem.authors = ["Tim Morgan"]
|
13
|
+
gem.add_development_dependency "bacon", ">= 0"
|
14
|
+
gem.add_development_dependency "yard", ">= 0"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
Rake::TestTask.new(:spec) do |spec|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
25
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
26
|
+
spec.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rcov/rcovtask'
|
31
|
+
Rcov::RcovTask.new do |spec|
|
32
|
+
spec.libs << 'spec'
|
33
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
34
|
+
spec.verbose = true
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
task :rcov do
|
38
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
task :spec => :check_dependencies
|
43
|
+
|
44
|
+
task :default => :spec
|
45
|
+
|
46
|
+
begin
|
47
|
+
require 'yard'
|
48
|
+
YARD::Rake::YardocTask.new do |doc|
|
49
|
+
doc.options << "-m" << "textile"
|
50
|
+
doc.options << "--protected"
|
51
|
+
doc.options << "-r" << "README.textile"
|
52
|
+
doc.options << "-o" << "doc"
|
53
|
+
doc.options << "--title" << "Alumina Documentation".inspect
|
54
|
+
|
55
|
+
doc.files << "lib/**/*"
|
56
|
+
end
|
57
|
+
rescue LoadError
|
58
|
+
task :yard do
|
59
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
task :doc => :yard
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/data/elements.yml
ADDED
@@ -0,0 +1,355 @@
|
|
1
|
+
---
|
2
|
+
1:
|
3
|
+
:symbol: H
|
4
|
+
:name: Hydrogen
|
5
|
+
2:
|
6
|
+
:symbol: He
|
7
|
+
:name: Helium
|
8
|
+
3:
|
9
|
+
:symbol: Li
|
10
|
+
:name: Lithium
|
11
|
+
4:
|
12
|
+
:symbol: Be
|
13
|
+
:name: Beryllium
|
14
|
+
5:
|
15
|
+
:symbol: B
|
16
|
+
:name: Boron
|
17
|
+
6:
|
18
|
+
:symbol: C
|
19
|
+
:name: Carbon
|
20
|
+
7:
|
21
|
+
:symbol: N
|
22
|
+
:name: Nitrogen
|
23
|
+
8:
|
24
|
+
:symbol: O
|
25
|
+
:name: Oxygen
|
26
|
+
9:
|
27
|
+
:symbol: F
|
28
|
+
:name: Fluorine
|
29
|
+
10:
|
30
|
+
:symbol: Ne
|
31
|
+
:name: Neon
|
32
|
+
11:
|
33
|
+
:symbol: Na
|
34
|
+
:name: Sodium
|
35
|
+
12:
|
36
|
+
:symbol: Mg
|
37
|
+
:name: Magnesium
|
38
|
+
13:
|
39
|
+
:symbol: Al
|
40
|
+
:name: Aluminium
|
41
|
+
14:
|
42
|
+
:symbol: Si
|
43
|
+
:name: Silicon
|
44
|
+
15:
|
45
|
+
:symbol: P
|
46
|
+
:name: Phosphorus
|
47
|
+
16:
|
48
|
+
:symbol: S
|
49
|
+
:name: Sulfur
|
50
|
+
17:
|
51
|
+
:symbol: Cl
|
52
|
+
:name: Chlorine
|
53
|
+
18:
|
54
|
+
:symbol: Ar
|
55
|
+
:name: Argon
|
56
|
+
19:
|
57
|
+
:symbol: K
|
58
|
+
:name: Potassium
|
59
|
+
20:
|
60
|
+
:symbol: Ca
|
61
|
+
:name: Calcium
|
62
|
+
21:
|
63
|
+
:symbol: Sc
|
64
|
+
:name: Scandium
|
65
|
+
22:
|
66
|
+
:symbol: Ti
|
67
|
+
:name: Titanium
|
68
|
+
23:
|
69
|
+
:symbol: V
|
70
|
+
:name: Vanadium
|
71
|
+
24:
|
72
|
+
:symbol: Cr
|
73
|
+
:name: Chromium
|
74
|
+
25:
|
75
|
+
:symbol: Mn
|
76
|
+
:name: Manganese
|
77
|
+
26:
|
78
|
+
:symbol: Fe
|
79
|
+
:name: Iron
|
80
|
+
27:
|
81
|
+
:symbol: Co
|
82
|
+
:name: Cobalt
|
83
|
+
28:
|
84
|
+
:symbol: Ni
|
85
|
+
:name: Nickel
|
86
|
+
29:
|
87
|
+
:symbol: Cu
|
88
|
+
:name: Copper
|
89
|
+
30:
|
90
|
+
:symbol: Zn
|
91
|
+
:name: Zinc
|
92
|
+
31:
|
93
|
+
:symbol: Ga
|
94
|
+
:name: Gallium
|
95
|
+
32:
|
96
|
+
:symbol: Ge
|
97
|
+
:name: Germanium
|
98
|
+
33:
|
99
|
+
:symbol: As
|
100
|
+
:name: Arsenic
|
101
|
+
34:
|
102
|
+
:symbol: Se
|
103
|
+
:name: Selenium
|
104
|
+
35:
|
105
|
+
:symbol: Br
|
106
|
+
:name: Bromine
|
107
|
+
36:
|
108
|
+
:symbol: Kr
|
109
|
+
:name: Krypton
|
110
|
+
37:
|
111
|
+
:symbol: Rb
|
112
|
+
:name: Rubidium
|
113
|
+
38:
|
114
|
+
:symbol: Sr
|
115
|
+
:name: Strontium
|
116
|
+
39:
|
117
|
+
:symbol: Y
|
118
|
+
:name: Yttrium
|
119
|
+
40:
|
120
|
+
:symbol: Zr
|
121
|
+
:name: Zirconium
|
122
|
+
41:
|
123
|
+
:symbol: Nb
|
124
|
+
:name: Niobium
|
125
|
+
42:
|
126
|
+
:symbol: Mo
|
127
|
+
:name: Molybdenum
|
128
|
+
43:
|
129
|
+
:symbol: Tc
|
130
|
+
:name: Technetium
|
131
|
+
44:
|
132
|
+
:symbol: Ru
|
133
|
+
:name: Ruthenium
|
134
|
+
45:
|
135
|
+
:symbol: Rh
|
136
|
+
:name: Rhodium
|
137
|
+
46:
|
138
|
+
:symbol: Pd
|
139
|
+
:name: Palladium
|
140
|
+
47:
|
141
|
+
:symbol: Ag
|
142
|
+
:name: Silver
|
143
|
+
48:
|
144
|
+
:symbol: Cd
|
145
|
+
:name: Cadmium
|
146
|
+
49:
|
147
|
+
:symbol: In
|
148
|
+
:name: Indium
|
149
|
+
50:
|
150
|
+
:symbol: Sn
|
151
|
+
:name: Tin
|
152
|
+
51:
|
153
|
+
:symbol: Sb
|
154
|
+
:name: Antimony
|
155
|
+
52:
|
156
|
+
:symbol: Te
|
157
|
+
:name: Tellurium
|
158
|
+
53:
|
159
|
+
:symbol: I
|
160
|
+
:name: Iodine
|
161
|
+
54:
|
162
|
+
:symbol: Xe
|
163
|
+
:name: Xenon
|
164
|
+
55:
|
165
|
+
:symbol: Cs
|
166
|
+
:name: Caesium
|
167
|
+
56:
|
168
|
+
:symbol: Ba
|
169
|
+
:name: Barium
|
170
|
+
57:
|
171
|
+
:symbol: La
|
172
|
+
:name: Lanthanum
|
173
|
+
58:
|
174
|
+
:symbol: Ce
|
175
|
+
:name: Cerium
|
176
|
+
59:
|
177
|
+
:symbol: Pr
|
178
|
+
:name: Praseodymium
|
179
|
+
60:
|
180
|
+
:symbol: Nd
|
181
|
+
:name: Neodymium
|
182
|
+
61:
|
183
|
+
:symbol: Pm
|
184
|
+
:name: Promethium
|
185
|
+
62:
|
186
|
+
:symbol: Sm
|
187
|
+
:name: Samarium
|
188
|
+
63:
|
189
|
+
:symbol: Eu
|
190
|
+
:name: Europium
|
191
|
+
64:
|
192
|
+
:symbol: Gd
|
193
|
+
:name: Gadolinium
|
194
|
+
65:
|
195
|
+
:symbol: Tb
|
196
|
+
:name: Terbium
|
197
|
+
66:
|
198
|
+
:symbol: Dy
|
199
|
+
:name: Dysprosium
|
200
|
+
67:
|
201
|
+
:symbol: Ho
|
202
|
+
:name: Holmium
|
203
|
+
68:
|
204
|
+
:symbol: Er
|
205
|
+
:name: Erbium
|
206
|
+
69:
|
207
|
+
:symbol: Tm
|
208
|
+
:name: Thulium
|
209
|
+
70:
|
210
|
+
:symbol: Yb
|
211
|
+
:name: Ytterbium
|
212
|
+
71:
|
213
|
+
:symbol: Lu
|
214
|
+
:name: Lutetium
|
215
|
+
72:
|
216
|
+
:symbol: Hf
|
217
|
+
:name: Hafnium
|
218
|
+
73:
|
219
|
+
:symbol: Ta
|
220
|
+
:name: Tantalum
|
221
|
+
74:
|
222
|
+
:symbol: W
|
223
|
+
:name: Tungsten
|
224
|
+
75:
|
225
|
+
:symbol: Re
|
226
|
+
:name: Rhenium
|
227
|
+
76:
|
228
|
+
:symbol: Os
|
229
|
+
:name: Osmium
|
230
|
+
77:
|
231
|
+
:symbol: Ir
|
232
|
+
:name: Iridium
|
233
|
+
78:
|
234
|
+
:symbol: Pt
|
235
|
+
:name: Platinum
|
236
|
+
79:
|
237
|
+
:symbol: Au
|
238
|
+
:name: Gold
|
239
|
+
80:
|
240
|
+
:symbol: Hg
|
241
|
+
:name: Mercury
|
242
|
+
81:
|
243
|
+
:symbol: Tl
|
244
|
+
:name: Thallium
|
245
|
+
82:
|
246
|
+
:symbol: Pb
|
247
|
+
:name: Lead
|
248
|
+
83:
|
249
|
+
:symbol: Bi
|
250
|
+
:name: Bismuth
|
251
|
+
84:
|
252
|
+
:symbol: Po
|
253
|
+
:name: Polonium
|
254
|
+
85:
|
255
|
+
:symbol: At
|
256
|
+
:name: Astatine
|
257
|
+
86:
|
258
|
+
:symbol: Rn
|
259
|
+
:name: Radon
|
260
|
+
87:
|
261
|
+
:symbol: Fr
|
262
|
+
:name: Francium
|
263
|
+
88:
|
264
|
+
:symbol: Ra
|
265
|
+
:name: Radium
|
266
|
+
89:
|
267
|
+
:symbol: Ac
|
268
|
+
:name: Actinium
|
269
|
+
90:
|
270
|
+
:symbol: Th
|
271
|
+
:name: Thorium
|
272
|
+
91:
|
273
|
+
:symbol: Pa
|
274
|
+
:name: Protactinium
|
275
|
+
92:
|
276
|
+
:symbol: U
|
277
|
+
:name: Uranium
|
278
|
+
93:
|
279
|
+
:symbol: Np
|
280
|
+
:name: Neptunium
|
281
|
+
94:
|
282
|
+
:symbol: Pu
|
283
|
+
:name: Plutonium
|
284
|
+
95:
|
285
|
+
:symbol: Am
|
286
|
+
:name: Americium
|
287
|
+
96:
|
288
|
+
:symbol: Cm
|
289
|
+
:name: Curium
|
290
|
+
97:
|
291
|
+
:symbol: Bk
|
292
|
+
:name: Berkelium
|
293
|
+
98:
|
294
|
+
:symbol: Cf
|
295
|
+
:name: Californium
|
296
|
+
99:
|
297
|
+
:symbol: Es
|
298
|
+
:name: Einsteinium
|
299
|
+
100:
|
300
|
+
:symbol: Fm
|
301
|
+
:name: Fermium
|
302
|
+
101:
|
303
|
+
:symbol: Md
|
304
|
+
:name: Mendelevium
|
305
|
+
102:
|
306
|
+
:symbol: "No"
|
307
|
+
:name: Nobelium
|
308
|
+
103:
|
309
|
+
:symbol: Lr
|
310
|
+
:name: Lawrencium
|
311
|
+
104:
|
312
|
+
:symbol: Rf
|
313
|
+
:name: Rutherfordium
|
314
|
+
105:
|
315
|
+
:symbol: Db
|
316
|
+
:name: Dubnium
|
317
|
+
106:
|
318
|
+
:symbol: Sg
|
319
|
+
:name: Seaborgium
|
320
|
+
107:
|
321
|
+
:symbol: Bh
|
322
|
+
:name: Bohrium
|
323
|
+
108:
|
324
|
+
:symbol: Hs
|
325
|
+
:name: Hassium
|
326
|
+
109:
|
327
|
+
:symbol: Mt
|
328
|
+
:name: Meitnerium
|
329
|
+
110:
|
330
|
+
:symbol: Ds
|
331
|
+
:name: Darmstadtium
|
332
|
+
111:
|
333
|
+
:symbol: Rg
|
334
|
+
:name: Roentgenium
|
335
|
+
112:
|
336
|
+
:symbol: Cn
|
337
|
+
:name: Copernicium
|
338
|
+
113:
|
339
|
+
:symbol: Uut
|
340
|
+
:name: Ununtrium
|
341
|
+
114:
|
342
|
+
:symbol: Uuq
|
343
|
+
:name: Ununquadium
|
344
|
+
115:
|
345
|
+
:symbol: Uup
|
346
|
+
:name: Ununpentium
|
347
|
+
116:
|
348
|
+
:symbol: Uuh
|
349
|
+
:name: Ununhexium
|
350
|
+
117:
|
351
|
+
:symbol: Uuh
|
352
|
+
:name: Ununseptium
|
353
|
+
118:
|
354
|
+
:symbol: Uuo
|
355
|
+
:name: Ununoctium
|
data/lib/alumina.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'extensions'
|
3
|
+
require 'alumina/errors'
|
4
|
+
require 'alumina/hin/writer'
|
5
|
+
require 'alumina/element'
|
6
|
+
require 'alumina/atom'
|
7
|
+
require 'alumina/molecule'
|
8
|
+
require 'alumina/hin'
|
9
|
+
require 'alumina/hin/parser'
|
10
|
+
|
11
|
+
# Container module for classes of the Alumina gem.
|
12
|
+
|
13
|
+
module Alumina
|
14
|
+
# @private
|
15
|
+
ELEMENTS_PATH = File.expand_path("#{File.dirname(__FILE__)}/../data/elements.yml")
|
16
|
+
# An array of all {Element Elements}, loaded from a YAML data file.
|
17
|
+
ELEMENTS = YAML.load(File.read(ELEMENTS_PATH)).map { |atomic_number, data| Element.new(atomic_number, data[:name], data[:symbol]) }
|
18
|
+
# All elements hashed by their symbol (e.g., "Ag" for silver).
|
19
|
+
ELEMENTS_BY_SYMBOL = ELEMENTS.inject({}) { |hsh, cur| hsh[cur.symbol] = cur ; hsh }
|
20
|
+
# All elements hashed by their atomic number.
|
21
|
+
ELEMENTS_BY_NUMBER = ELEMENTS.inject({}) { |hsh, cur| hsh[cur.atomic_number] = cur ; hsh }
|
22
|
+
|
23
|
+
# Creates a {HIN::Parser} and calls {HIN::Parser#parse} on it.
|
24
|
+
#
|
25
|
+
# @param [IO, String] input The HIN data to parse.
|
26
|
+
# @return [Array<Alumina::Molecule>] The molecules described by the data.
|
27
|
+
# @raise [ParseError] If the HIN data is improperly formatted.
|
28
|
+
|
29
|
+
def self.HIN(input)
|
30
|
+
Alumina::HIN::Parser.new.parse(input)
|
31
|
+
end
|
32
|
+
end
|
data/lib/alumina/atom.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
module Alumina
|
2
|
+
|
3
|
+
# An atom as part of a {Molecule}.
|
4
|
+
|
5
|
+
class Atom
|
6
|
+
include Alumina::HIN::Writer::Atom
|
7
|
+
|
8
|
+
BOND_TYPES = [ :single, :double, :triple, :aromatic ]
|
9
|
+
|
10
|
+
# @return [Fixnum] The unique numerical identifier assigned to the atom.
|
11
|
+
attr_accessor :id
|
12
|
+
# @return [String, nil] The optional label given to the atom.
|
13
|
+
attr_accessor :label
|
14
|
+
# @return [Element] The atomic type.
|
15
|
+
attr_accessor :element
|
16
|
+
# @return [Float] The atom's _x_-coordinate in the molecule.
|
17
|
+
attr_accessor :x
|
18
|
+
# @return [Float] The atom's _y_-coordinate in the molecule.
|
19
|
+
attr_accessor :y
|
20
|
+
# @return [Float] The atom's _z_-coordinate in the molecule.
|
21
|
+
attr_accessor :z
|
22
|
+
# @return [Hash<Atom, Symbol>] The atoms this atom is bonded to, along with
|
23
|
+
# the bond type.
|
24
|
+
attr_accessor :bonds
|
25
|
+
# @return [Fixnum] The partial charge.
|
26
|
+
attr_accessor :partial_charge
|
27
|
+
|
28
|
+
# @private
|
29
|
+
attr_accessor :ignored1, :ignored2
|
30
|
+
|
31
|
+
# Initializes a new atom. After initializing it you can define its bonds
|
32
|
+
# strucrure using the {.bind} method. Atoms are added to
|
33
|
+
# {Molecule Molecules} with the @<<@ method.
|
34
|
+
#
|
35
|
+
# @param [Fixnum] id A unique identifier for this atom. Uniqueness is not
|
36
|
+
# checked here.
|
37
|
+
# @param [Element] element The atom's type.
|
38
|
+
# @param [Float] x The atom's _x_-coordinate in the molecule.
|
39
|
+
# @param [Float] y The atom's _y_-coordinate in the molecule.
|
40
|
+
# @param [Float] z The atom's _z_-coordinate in the molecule.
|
41
|
+
|
42
|
+
def initialize(id, element, x, y, z)
|
43
|
+
@id = id
|
44
|
+
@element = element
|
45
|
+
@x = x
|
46
|
+
@y = y
|
47
|
+
@z = z
|
48
|
+
@bonds = Hash.new
|
49
|
+
end
|
50
|
+
|
51
|
+
# Binds two atoms together.
|
52
|
+
#
|
53
|
+
# @param [Atom] atom1 An atom to bind.
|
54
|
+
# @param [Atom] atom2 An atom to bind.
|
55
|
+
# @param [Symbol] The bond type. (See {BOND_TYPES}.)
|
56
|
+
# @raise [ArgumentError] If an invalid bond type is given.
|
57
|
+
|
58
|
+
def self.bind(atom1, atom2, type)
|
59
|
+
raise ArgumentError, "Invalid bond type #{type.inspect}" unless BOND_TYPES.include?(type)
|
60
|
+
|
61
|
+
atom1.bonds[atom2] = type
|
62
|
+
atom2.bonds[atom1] = type
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Fixnum] The number of atoms bound to this atom.
|
66
|
+
|
67
|
+
def bond_count
|
68
|
+
bonds.size
|
69
|
+
end
|
70
|
+
|
71
|
+
# Creates a duplicate of this atom with no bonds.
|
72
|
+
#
|
73
|
+
# @return [Atom] A duplicate of this atom.
|
74
|
+
|
75
|
+
def dup
|
76
|
+
atom = Atom.new(id, element, x, y, z)
|
77
|
+
atom.label = label
|
78
|
+
atom.partial_charge = partial_charge
|
79
|
+
atom.ignored1 = ignored1
|
80
|
+
atom.ignored2 = ignored2
|
81
|
+
return atom
|
82
|
+
end
|
83
|
+
|
84
|
+
# @private
|
85
|
+
def inspect
|
86
|
+
if label then
|
87
|
+
"#<Atom ##{id} #{label} (#{element.symbol})>"
|
88
|
+
else
|
89
|
+
"#<Atom ##{id} #{element.symbol}>"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Alumina
|
2
|
+
|
3
|
+
# Each of the elements has a singleton instance of this class. You do not
|
4
|
+
# create instances of this class; they are loaded from a data file.
|
5
|
+
|
6
|
+
class Element
|
7
|
+
# @return [Fixnum] The element's atomic number (hydrogen is 1).
|
8
|
+
attr_reader :atomic_number
|
9
|
+
# @return [String] The element's name.
|
10
|
+
attr_reader :name
|
11
|
+
# @return [String] The element's symbol (hydrogen is @H@).
|
12
|
+
attr_reader :symbol
|
13
|
+
|
14
|
+
# @private
|
15
|
+
def initialize(atomic_number, name, symbol)
|
16
|
+
@atomic_number = atomic_number
|
17
|
+
@name = name
|
18
|
+
@symbol = symbol
|
19
|
+
end
|
20
|
+
|
21
|
+
# Provides a natural ordering based on atomic number.
|
22
|
+
#
|
23
|
+
# @param [Element] other The other element to compare against.
|
24
|
+
# @return [-1, 0, 1] The sort equivalency.
|
25
|
+
|
26
|
+
def <=>(other)
|
27
|
+
atomic_number <=> other.atomic_number
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Alumina
|
2
|
+
|
3
|
+
# Raised when invalid HIN data is encountered.
|
4
|
+
|
5
|
+
class ParseError < StandardError
|
6
|
+
# @return [Fixnum] The line in the HIN data at which the error occurred.
|
7
|
+
attr_accessor :line
|
8
|
+
|
9
|
+
# @private
|
10
|
+
def initialize(line, reason)
|
11
|
+
@line = line
|
12
|
+
super(reason)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/alumina/hin.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
module Alumina
|
2
|
+
module HIN
|
3
|
+
|
4
|
+
# Class that parses HIN files to produce {Molecule} instances. This class is
|
5
|
+
# not thread-safe! You must instantiate a new parser for each thread of your
|
6
|
+
# program.
|
7
|
+
|
8
|
+
class Parser
|
9
|
+
# Mapping of HIN file bond types to their symbol values for the {Atom}
|
10
|
+
# class.
|
11
|
+
BOND_TYPES = {
|
12
|
+
's' => :single,
|
13
|
+
'd' => :double,
|
14
|
+
't' => :triple,
|
15
|
+
'a' => :aromatic
|
16
|
+
}
|
17
|
+
|
18
|
+
# If @true@, does not raise {ParseError} when
|
19
|
+
#
|
20
|
+
# * unknown commands are encountered,
|
21
|
+
# * the bond count is incorrect, or
|
22
|
+
# * an @endmol@'s ID does not correspond to its @mol@'s ID.
|
23
|
+
attr_accessor :lenient
|
24
|
+
|
25
|
+
# Parses HIN data and returns an array of {Molecule Molecules}.
|
26
|
+
#
|
27
|
+
# @param [IO, String] input The HIN data to parse.
|
28
|
+
# @return [Array<Alumina::Molecule>] The molecules described by the data.
|
29
|
+
# @raise [ParseError] If the HIN data is improperly formatted.
|
30
|
+
|
31
|
+
def parse(input)
|
32
|
+
@molecules = Array.new
|
33
|
+
@current_molecule = nil
|
34
|
+
@line_counter = 0
|
35
|
+
@bonds = Hash.autonew
|
36
|
+
|
37
|
+
input.each_line do |line|
|
38
|
+
@line_counter += 1
|
39
|
+
next if line =~ /^\s*$/
|
40
|
+
next if line[0] == ';' # comment
|
41
|
+
parts = line.split(/\s+/)
|
42
|
+
|
43
|
+
command = parts.shift
|
44
|
+
method = :"parse_#{command}"
|
45
|
+
if respond_to?(method) then
|
46
|
+
send(method, *parts)
|
47
|
+
else
|
48
|
+
if lenient then
|
49
|
+
next
|
50
|
+
else
|
51
|
+
raise ParseError.new(@line_counter, "Unknown command #{command}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
@bonds.each do |molecule, firsts|
|
57
|
+
firsts.each do |atom1, lasts|
|
58
|
+
lasts.each do |atom2, bond_type|
|
59
|
+
Atom.bind molecule[atom1], molecule[atom2], BOND_TYPES[bond_type]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
return @molecules
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def parse_forcefield(*unknowns)
|
70
|
+
#TODO
|
71
|
+
end
|
72
|
+
|
73
|
+
def parse_sys(*unknowns)
|
74
|
+
#TODO
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse_view(*unknowns)
|
78
|
+
#TODO
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_box(*unknowns)
|
82
|
+
#TODO
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse_seed(*unknowns)
|
86
|
+
#TODO
|
87
|
+
end
|
88
|
+
|
89
|
+
# @private
|
90
|
+
def parse_mol(id, label=nil)
|
91
|
+
if @current_molecule then
|
92
|
+
raise ParseError.new(@line_counter, "Can't have a mol section within another mol section")
|
93
|
+
else
|
94
|
+
label = nil if label.blank?
|
95
|
+
@current_molecule = Molecule.new(id.to_i, label)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# @private
|
100
|
+
def parse_endmol(id)
|
101
|
+
raise ParseError.new(@line_counter, "Given endmol with id #{@id}, but open molecule has ID #{@current_molecule.id}") if id.to_i != @current_molecule.id and not lenient
|
102
|
+
|
103
|
+
@molecules << @current_molecule
|
104
|
+
@current_molecule = nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# @private
|
108
|
+
def parse_atom(id, label, symbol, ignored1, ignored2, partial_charge, x, y, z, bond_count, *bonds)
|
109
|
+
if @current_molecule then
|
110
|
+
label = nil if label.blank?
|
111
|
+
element = ELEMENTS_BY_SYMBOL[symbol]
|
112
|
+
raise ParseError.new("Unknown element #{symbol}") unless element
|
113
|
+
|
114
|
+
atom = Atom.new(id.to_i, element, x.to_f, y.to_f, z.to_f)
|
115
|
+
atom.label = label
|
116
|
+
atom.ignored1 = ignored1
|
117
|
+
atom.ignored2 = ignored2
|
118
|
+
atom.partial_charge = partial_charge.to_i
|
119
|
+
|
120
|
+
bond_count = bond_count.to_i
|
121
|
+
raise ParseError.new(@line_counter, "Expected #{bond_count} bonds but found #{bonds.size/2}") if bond_count != bonds.size/2 and not lenient
|
122
|
+
|
123
|
+
bonds.in_groups_of(2).each do |(atom_id, bond_type)|
|
124
|
+
first_id = [ atom.id, atom_id.to_i ].min
|
125
|
+
last_id = [ atom.id, atom_id.to_i ].max
|
126
|
+
if @bonds[@current_molecule][first_id][last_id] != {} and @bonds[@current_molecule][first_id][last_id] != bond_type then
|
127
|
+
raise ParseError.new(@line_counter, "Assymetric bond between #{first_id} and #{last_id} of type #{bond_type}")
|
128
|
+
end
|
129
|
+
raise ParseError.new(@line_counter, "Unknown bond type #{bond_type}") unless BOND_TYPES.include?(bond_type)
|
130
|
+
@bonds[@current_molecule][first_id][last_id] = bond_type
|
131
|
+
end
|
132
|
+
@current_molecule << atom
|
133
|
+
else
|
134
|
+
raise ParseError.new(@line_counter, "Can't have an atom command outside of a mol section")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Alumina
|
2
|
+
module HIN
|
3
|
+
|
4
|
+
# Modules that add methods for writing data in HIN format.
|
5
|
+
|
6
|
+
module Writer
|
7
|
+
|
8
|
+
# Adds methods for writing data in HIN format to {Molecule} objects.
|
9
|
+
|
10
|
+
module Molecule
|
11
|
+
|
12
|
+
# Outputs this molecule in HIN format.
|
13
|
+
|
14
|
+
def to_hin
|
15
|
+
lines = atoms.map(&:to_hin)
|
16
|
+
lines.unshift "mol #{id}#{" #{label}" if label}"
|
17
|
+
lines.push "endmol #{id}"
|
18
|
+
lines.join("\n")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Adds methods for writing data in HIN format to {Atom} objects.
|
23
|
+
|
24
|
+
module Atom
|
25
|
+
|
26
|
+
# Outputs this atom in HIN format.
|
27
|
+
|
28
|
+
def to_hin
|
29
|
+
"atom #{id} #{label || '-'} #{element.symbol} #{ignored1} #{ignored2} #{partial_charge} #{x} #{y} #{z} #{bonds.size} " + bonds.map { |atom, type| "#{atom.id} #{HIN::Parser::BOND_TYPES.key(type)}" }.join(' ')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Alumina
|
2
|
+
|
3
|
+
# A molecule as represented by HIN data, consisting of multiple {Atom Atoms}.
|
4
|
+
|
5
|
+
class Molecule
|
6
|
+
include Alumina::HIN::Writer::Molecule
|
7
|
+
|
8
|
+
# @return [Fixnum] The unique numerical identifier for this molecule.
|
9
|
+
attr_accessor :id
|
10
|
+
# @return [String, nil] The optional label given to this molecule.
|
11
|
+
attr_accessor :label
|
12
|
+
|
13
|
+
# Creates a new instance.
|
14
|
+
#
|
15
|
+
# @param [Fixnum] id The molecule's unique ID. Uniqueness is not checked in
|
16
|
+
# this method.
|
17
|
+
# @param [String] label A label to give the molecule.
|
18
|
+
|
19
|
+
def initialize(id, label=nil)
|
20
|
+
@id = id
|
21
|
+
@label = label
|
22
|
+
@atoms = Hash.new
|
23
|
+
end
|
24
|
+
|
25
|
+
# Adds an atom to this molecule. If there is already an atom in this
|
26
|
+
# molecule sharing this atom's {Atom#id ID}, it will be replaced by this
|
27
|
+
# atom.
|
28
|
+
#
|
29
|
+
# @param [Atom] atom The atom to add.
|
30
|
+
|
31
|
+
def <<(atom)
|
32
|
+
@atoms[atom.id] = atom
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Array<Atom>] An array of atoms in this molecule.
|
36
|
+
|
37
|
+
def atoms
|
38
|
+
@atoms.values
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns an atom for a given unique identifier.
|
42
|
+
#
|
43
|
+
# @param [Fixnum] ident The identifier to search.
|
44
|
+
# @return [Atom, nil] The atom with that identifier, or @nil@ if no such
|
45
|
+
# atom was found.
|
46
|
+
|
47
|
+
def atom(ident)
|
48
|
+
@atoms[ident]
|
49
|
+
end
|
50
|
+
|
51
|
+
alias :[] :atom
|
52
|
+
|
53
|
+
# @return [String] Returns the plain-text molecular formula for this atom;
|
54
|
+
# for example, @C7H5N3O6@ for TNT.
|
55
|
+
|
56
|
+
def molecular_formula
|
57
|
+
counts = atoms.map(&:element).inject(Hash.new(0)) { |hsh, cur| hsh[cur] += 1 ; hsh }
|
58
|
+
counts.keys.sort.reverse.map { |element| "#{element.symbol}#{num_for counts[element]}" }.join
|
59
|
+
end
|
60
|
+
|
61
|
+
# @private
|
62
|
+
def inspect
|
63
|
+
if label then
|
64
|
+
"#<Molecule ##{id} (#{label}): #{molecular_formula}>"
|
65
|
+
else
|
66
|
+
"#<Molecule ##{id}: #{molecular_formula}>"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def num_for(num)
|
73
|
+
num.to_s.chars.map do |char|
|
74
|
+
case char
|
75
|
+
when '0' then ?\u2080
|
76
|
+
when '1' then ?\u2081
|
77
|
+
when '2' then ?\u2082
|
78
|
+
when '3' then ?\u2083
|
79
|
+
when '4' then ?\u2084
|
80
|
+
when '5' then ?\u2085
|
81
|
+
when '6' then ?\u2086
|
82
|
+
when '7' then ?\u2087
|
83
|
+
when '8' then ?\u2088
|
84
|
+
when '9' then ?\u2089
|
85
|
+
end
|
86
|
+
end.join
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/extensions.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# @private
|
2
|
+
class String
|
3
|
+
|
4
|
+
# @private
|
5
|
+
def blank?
|
6
|
+
self =~ /^\s*$/
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# @private
|
11
|
+
class NilClass
|
12
|
+
|
13
|
+
# @private
|
14
|
+
def blank?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# @private
|
20
|
+
class Array
|
21
|
+
# @private -- Credit to DHH and the Rails team
|
22
|
+
def in_groups_of(number, fill_with = nil)
|
23
|
+
if fill_with == false
|
24
|
+
collection = self
|
25
|
+
else
|
26
|
+
# size % number gives how many extra we have;
|
27
|
+
# subtracting from number gives how many to add;
|
28
|
+
# modulo number ensures we don't add group of just fill.
|
29
|
+
padding = (number - size % number) % number
|
30
|
+
collection = dup.concat([fill_with] * padding)
|
31
|
+
end
|
32
|
+
|
33
|
+
if block_given?
|
34
|
+
collection.each_slice(number) { |slice| yield(slice) }
|
35
|
+
else
|
36
|
+
returning [] do |groups|
|
37
|
+
collection.each_slice(number) { |group| groups << group }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @private
|
44
|
+
class Object
|
45
|
+
# @private -- Credit to DHH and the Rails team
|
46
|
+
def returning(value)
|
47
|
+
yield(value)
|
48
|
+
value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# @private
|
53
|
+
class Hash
|
54
|
+
# @private -- credit to Trans, Jan Molic, Facets team
|
55
|
+
def self.autonew(*args)
|
56
|
+
leet = lambda { |hsh, key| hsh[key] = new( &leet ) }
|
57
|
+
new(*args,&leet)
|
58
|
+
end
|
59
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: alumina
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Tim Morgan
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-06-02 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: bacon
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: yard
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
description: This gem parses HyperChem's .HIN files, PDB files, and in the future others, converting them to Ruby objects for easy manipulation
|
50
|
+
email: git@timothymorgan.info
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files:
|
56
|
+
- LICENSE
|
57
|
+
- README.textile
|
58
|
+
files:
|
59
|
+
- .document
|
60
|
+
- .gitignore
|
61
|
+
- LICENSE
|
62
|
+
- README.textile
|
63
|
+
- Rakefile
|
64
|
+
- VERSION
|
65
|
+
- data/elements.yml
|
66
|
+
- lib/alumina.rb
|
67
|
+
- lib/alumina/atom.rb
|
68
|
+
- lib/alumina/element.rb
|
69
|
+
- lib/alumina/errors.rb
|
70
|
+
- lib/alumina/hin.rb
|
71
|
+
- lib/alumina/hin/parser.rb
|
72
|
+
- lib/alumina/hin/writer.rb
|
73
|
+
- lib/alumina/molecule.rb
|
74
|
+
- lib/extensions.rb
|
75
|
+
- spec/alumina_spec.rb
|
76
|
+
- spec/spec_helper.rb
|
77
|
+
has_rdoc: true
|
78
|
+
homepage: http://github.com/RISCfuture/alumina
|
79
|
+
licenses: []
|
80
|
+
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options:
|
83
|
+
- --charset=UTF-8
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
hash: 3
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
version: "0"
|
104
|
+
requirements: []
|
105
|
+
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 1.3.7
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: Ruby parser for various chemistry-related file formats
|
111
|
+
test_files:
|
112
|
+
- spec/alumina_spec.rb
|
113
|
+
- spec/spec_helper.rb
|