alumina 0.0.1

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,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+ .yardoc
21
+ doc
22
+
23
+ ## PROJECT::SPECIFIC
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.
@@ -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.
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ module Alumina
2
+
3
+ # Container module for classes working with HyperChem's HIN file format.
4
+
5
+ module HIN
6
+ end
7
+ end
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Alumina" do
4
+ it "fails" do
5
+ should.flunk "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bacon'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'alumina'
7
+
8
+ Bacon.summary_on_exit
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