aims 0.2.0

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,56 @@
1
+
2
+ module Aims
3
+
4
+ # Utility class for parsing an Aims geometry file
5
+ # Example Usage:
6
+ # uc = Aims::GeometryParser.parse("geometry.in")
7
+ class GeometryParser
8
+
9
+ # Parse a String representation of a geometry.in file
10
+ # - +str+ The String to parse
11
+ # - Return the Aims::Geometry object that was parsed
12
+ def GeometryParser.parse_string(str)
13
+ GeometryParser.parse_io(str)
14
+ end
15
+
16
+ # Parse an IO object representation of a geometry.in file
17
+ # - +io+ The IO object to parse
18
+ # - Return the Aims::Geometry object that was parsed
19
+ def GeometryParser.parse_io(io)
20
+ atoms = Array.new
21
+ vectors = nil
22
+ io.each_line{|line|
23
+ case line
24
+ when /\w*#.*/
25
+ # Comment line, Do nothing
26
+ when /atom/
27
+ a, x, y, z, species = line.split(' ')
28
+ atom = Atom.new(x.to_f,y.to_f,z.to_f,species)
29
+ atoms << atom
30
+ when /lattice_vector/
31
+ a, x, y, z = line.split(' ')
32
+ vectors = Array.new if vectors.nil?
33
+ vectors << Vector[x.to_f,y.to_f,z.to_f]
34
+ when /constrain_relaxation/
35
+ a, c = line.split(' ')
36
+ atoms.last.constrain << c
37
+ end
38
+ }
39
+ Geometry.new(atoms, vectors)
40
+ end
41
+
42
+ # Parse a geometry.in file
43
+ # - +filename+ the file to parse
44
+ # - return the Aims::Geometry object
45
+ def GeometryParser.parse(filename)
46
+ f = File.open(filename, 'r')
47
+ cell = GeometryParser.parse_io(f)
48
+ f.close
49
+ return cell
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+
56
+
@@ -0,0 +1,484 @@
1
+ module Aims
2
+
3
+ # A geometry relaxation step in the calculation
4
+ class GeometryStep
5
+
6
+ # The relaxation step number
7
+ attr_accessor :step_num
8
+
9
+ # An Array of Aims::SCIteration
10
+ attr_reader :sc_iterations
11
+
12
+ # The Aims::Geometry
13
+ attr_accessor :geometry
14
+
15
+ # The total energy for this geometry as a Float
16
+ attr_accessor :total_energy
17
+
18
+ # The total corrected energy for this geometry as a Float
19
+ attr_accessor :total_corrected_energy
20
+
21
+ # The chemical potential for this geometry
22
+ attr_accessor :chemical_potential
23
+
24
+ # The forces for this geometry
25
+ attr_accessor :forces
26
+
27
+ # Initialize a new geometry step
28
+ def initialize
29
+ @sc_iterations = Array.new
30
+ @forces = Array.new
31
+ @step_num = -1
32
+ @total_energy = Float::NAN
33
+ @total_corrected_energy = Float::NAN
34
+ @chemical_potential = Float::NAN
35
+ end
36
+
37
+ # Return the last self-consistency iteration for this relaxation step
38
+ def sc_iteration
39
+ self.sc_iterations.last
40
+ end
41
+
42
+ # Return the total energy per atom for this relaxation step
43
+ def total_energy_per_atom
44
+ self.total_energy/geometry.size rescue "N/A"
45
+ end
46
+
47
+ # Return the total corrected energy per atom for this relaxation step
48
+ def total_corrected_energy_per_atom
49
+ self.total_corrected_energy/geometry.size rescue "N/A"
50
+ end
51
+
52
+ # A hash with keys :description, :cpu_time, and :wall_time
53
+ # with detailed time accounting for this geometry step.
54
+ def timings
55
+ timings = Timings.new
56
+ self.sc_iterations.each{|sc_iter|
57
+ timings.add!(sc_iter.timings)
58
+ }
59
+ timings
60
+ end
61
+
62
+ # The total CPU time deterimed by summing the time for
63
+ # each self-consistency iteration
64
+ def total_cpu_time
65
+ self.sc_iterations.inject(0){|tot, sc_iter|
66
+ tot += sc_iter.total_cpu_time
67
+ }
68
+ end
69
+
70
+
71
+ # The total wall clock time deterimed by summing the time for
72
+ # each self-consistency iteration
73
+ def total_wall_time
74
+ self.sc_iterations.inject(0){|tot, sc_iter|
75
+ tot += sc_iter.total_wall_time
76
+ }
77
+ end
78
+ end
79
+
80
+ # A class for encapsulating computational timing information
81
+ class Timings
82
+
83
+ # Initialize a new timing
84
+ def initialize
85
+ @timing_hash = {}
86
+ end
87
+
88
+ # Enumerate over an array of hashes that looks like:
89
+ # [{:description => "Something", :cpu_time => 10.0, :wall_time => 10.1}, ...]
90
+ def each
91
+ @timing_hash.each_pair{|desc,timings|
92
+ h = {:description => desc, :cpu_time => timings[:cpu_time], :wall_time => timings[:wall_time]}
93
+ yield h
94
+ }
95
+ end
96
+
97
+ # Add another timings object to this one
98
+ def add!(timings)
99
+ timings.descriptions.each{|d|
100
+ add_cpu_time(d, timings.cpu_time(d))
101
+ add_wall_time(d, timings.wall_time(d))
102
+ }
103
+ end
104
+
105
+ # Get all the descriptions for this timing object
106
+ def descriptions
107
+ @timing_hash.keys
108
+ end
109
+
110
+ # Add cpu timing data for the given description
111
+ def add_cpu_time(desc, time)
112
+ @timing_hash[desc] = {:cpu_time => 0, :wall_time => 0} unless @timing_hash[desc]
113
+ @timing_hash[desc][:cpu_time] += time
114
+ end
115
+
116
+ # Add wall timing data for the given description
117
+ def add_wall_time(desc, time)
118
+ @timing_hash[desc] = {:cpu_time => 0, :wall_time => 0} unless @timing_hash[desc]
119
+ @timing_hash[desc][:wall_time] += time
120
+ end
121
+
122
+ # Get wall timing data for the given description
123
+ def wall_time(desc)
124
+ if @timing_hash[desc]
125
+ @timing_hash[desc][:wall_time] || 0
126
+ else
127
+ 0
128
+ end
129
+ end
130
+
131
+ # Get the total wall time
132
+ def total_wall_time
133
+ @timing_hash.inject(0) {|total, a|
134
+ total += a[1][:wall_time]
135
+ }
136
+ end
137
+
138
+ # Get cpu timing data for the given description
139
+ def cpu_time(desc)
140
+ if @timing_hash[desc]
141
+ @timing_hash[desc][:cpu_time] || 0
142
+ else
143
+ 0
144
+ end
145
+ end
146
+
147
+ # Get the total cpu time
148
+ def total_cpu_time
149
+ @timing_hash.inject(0) {|total, a|
150
+ total += a[1][:cpu_time]
151
+ }
152
+ end
153
+ end
154
+
155
+ # A single self-consistency iteration
156
+ class SCIteration
157
+
158
+ # The change in total energy
159
+ attr_accessor :d_etot
160
+
161
+ # The change in the sum of eigenvalues
162
+ attr_accessor :d_eev
163
+
164
+ # The change in charge density
165
+ attr_accessor :d_rho
166
+
167
+ # The Timings data for this iteration
168
+ attr_accessor :timings
169
+
170
+ # Initialize a new self-consistency iteration
171
+ def initialize
172
+ self.timings = Timings.new
173
+ self.d_eev = 0
174
+ self.d_rho = 0
175
+ self.d_etot = 0
176
+ end
177
+
178
+ # Return the total CPU time for this iteration
179
+ def total_cpu_time
180
+ self.timings.cpu_time("Time for this iteration")
181
+ # begin
182
+ # total= self.timings.find{|t| t[:description] =~ /Time for this iteration/}
183
+ # total[:cpu_time]
184
+ # rescue
185
+ # self.timings.inject(0){|total, time|
186
+ # total = total + (time[:cpu_time] || 0)
187
+ # }
188
+ # end
189
+ end
190
+
191
+ # Return the total wall clock time for this iteration
192
+ def total_wall_time
193
+ self.timings.wall_time("Time for this iteration")
194
+ # begin
195
+ # time = self.timings.find{|t| t[:description] =~ /Time for this iteration/}
196
+ # time[:wall_time] or 0
197
+ # rescue
198
+ # self.timings.inject(0){|total, time|
199
+ # total = total + (time[:wall_clock_time] || 0)
200
+ # }
201
+ # end
202
+ end
203
+ end
204
+
205
+ # An object encapsulating the data that is output from AIMS.
206
+ # This object is generated from Aims::OutputParser.parse(filename)
207
+ class AimsOutput
208
+ # Each Aims::GeometryStep geometry relaxation step
209
+ attr_accessor :geometry_steps
210
+
211
+ # The k-point grid for periodic calculations
212
+ attr_accessor :k_grid
213
+
214
+ # The name of the calculation output file
215
+ attr_accessor :original_file
216
+
217
+ # Boolean, true if the geometry is converged
218
+ attr_accessor :geometry_converged
219
+
220
+ # The detailed time accounting data as a Aims::Timings
221
+ # will be nil if the calculation did not complete
222
+ attr_accessor :timings
223
+
224
+ # ?
225
+ attr_accessor :computational_steps
226
+
227
+ # The number of atoms in the computation
228
+ attr_accessor :n_atoms
229
+
230
+ def initialize
231
+ self.geometry_steps = Array.new
232
+ self.geometry_converged = false
233
+ self.timings = {}
234
+ end
235
+
236
+ # Returns the best available value of the total energy
237
+ def total_energy
238
+ etot = self.geometry_steps.collect{|gs| gs.total_energy }.compact.last
239
+ if etot.nil?
240
+ Float::NAN
241
+ else
242
+ etot
243
+ end
244
+ end
245
+
246
+ def final_geometry
247
+ self.geometry_steps.last.geometry
248
+ end
249
+
250
+ def final_step
251
+ self.geometry_steps.last
252
+ end
253
+
254
+ def geometry_step
255
+ self.geometry_steps.last
256
+ end
257
+
258
+ def n_relaxation_steps
259
+ self.geometry_steps.size - 1
260
+ end
261
+
262
+ def n_sc_iterations
263
+ self.geometry_steps.inject(0){|total, step|
264
+ total = total + step.sc_iterations.size
265
+ }
266
+ end
267
+
268
+ def total_cpu_time
269
+ begin
270
+ self.timings.find{|t| t[:description] =~ /Total time$/}[:cpu_time]
271
+ rescue
272
+ self.geometry_steps.inject(0) {|total, step|
273
+ total = total + step.total_cpu_time
274
+ }
275
+ end
276
+ end
277
+
278
+ def sc_iteration
279
+ self.geometry_step.sc_iteration
280
+ end
281
+ end
282
+
283
+ # Parse an AIMS output file and generate an AimsOutput object
284
+ # Invoke with
285
+ # output = Aims::OutputParser.parse(filename)
286
+ class OutputParser
287
+
288
+ def OutputParser.parse_input_geometry(io, n_atoms)
289
+ atoms = []
290
+ n_atoms.times do
291
+ fields = io.readline.split(' ')
292
+ a = Atom.new
293
+ a.x, a.y, a.z = fields[4].to_f, fields[5].to_f, fields[6].to_f
294
+ a.species = fields[3]
295
+ atoms << a
296
+ end
297
+ Geometry.new(atoms, nil, :dont_make_bonds)
298
+ end
299
+
300
+ def OutputParser.parse_atom_frac(line)
301
+ nil
302
+ end
303
+
304
+ def OutputParser.parse_atom(line)
305
+ fields = line.split(' ')
306
+ a = Atom.new
307
+ a.x, a.y, a.z = fields[1].to_f, fields[2].to_f, fields[3].to_f
308
+ a.species = fields[4]
309
+ a
310
+ end
311
+
312
+ def OutputParser.parse_lattice_vector(line)
313
+ fields = line.split(' ')
314
+ [fields[1].to_f, fields[2].to_f, fields[3].to_f]
315
+ end
316
+
317
+ def OutputParser.parse_updated_geometry(io, n_atoms)
318
+ io.readline
319
+
320
+ vectors = []
321
+ atoms = []
322
+ continue = TRUE
323
+
324
+ begin
325
+ line = io.readline
326
+ case line
327
+ when /lattice_vector/
328
+ vectors << OutputParser.parse_lattice_vector(line)
329
+ when /atom\b/
330
+ atoms << OutputParser.parse_atom(line)
331
+ when /atom_frac/
332
+ OutputParser.parse_atom_frac(line)
333
+ when /^\s*$/
334
+ # do nothing
335
+ else
336
+ continue = FALSE
337
+ end
338
+ end while continue
339
+
340
+ vectors = nil if vectors.empty?
341
+
342
+ Geometry.new(atoms, vectors, :dont_make_bonds)
343
+ end
344
+
345
+ def OutputParser.parse_sc_timings(io)
346
+ line = io.readline
347
+ timings = Timings.new
348
+ until line =~ /---/
349
+ desc, times = line.split(":")
350
+ fields = times.split(" ")
351
+ description = desc.sub("|", "").strip
352
+ cpu = fields[0].to_f
353
+ wall = fields[2].to_f
354
+ timings.add_cpu_time(description, cpu)
355
+ timings.add_wall_time(description, wall)
356
+ line = io.readline
357
+ end
358
+ timings
359
+ end
360
+
361
+ def OutputParser.parse_detailed_time_accounting(io)
362
+ line = io.readline
363
+ timings = Timings.new
364
+ desc, times = line.split(":")
365
+ until times.nil?
366
+ fields = times.split(" ")
367
+ description = desc.sub("|", "").strip
368
+ cpu = fields[0].to_f
369
+ wall = fields[2].to_f
370
+ timings.add_cpu_time(description, cpu)
371
+ timings.add_wall_time(description, wall)
372
+ line = io.readline
373
+ desc, times = line.split(":")
374
+ end
375
+ timings
376
+ end
377
+
378
+ def OutputParser.parse_computational_steps(io)
379
+ line = io.readline
380
+ steps = []
381
+ desc, value = line.split(":")
382
+ until value.nil?
383
+ steps << {:description => desc.sub("|", " ").strip, :value => value.to_f}
384
+ line = io.readline
385
+ desc, value = line.split(":")
386
+ end
387
+ steps
388
+ end
389
+
390
+ def OutputParser.parse(filename)
391
+
392
+ n_atoms = 0
393
+ vectors = []
394
+ retval = AimsOutput.new
395
+ retval.original_file = filename
396
+
397
+ File.open(filename, 'r') do |f|
398
+ f.each_line{|line|
399
+ case line
400
+ when /Found k-point grid:/
401
+ retval.k_grid = line.split(":")[1].strip
402
+
403
+ when /Computational steps:/
404
+ retval.computational_steps = OutputParser.parse_computational_steps(f)
405
+
406
+ when /Detailed time accounting/
407
+ retval.timings = OutputParser.parse_detailed_time_accounting(f)
408
+
409
+ when /Begin self-consistency loop/
410
+ retval.geometry_step.sc_iterations << SCIteration.new
411
+
412
+ when /Begin self-consistency iteration/
413
+ retval.geometry_step.sc_iterations << SCIteration.new
414
+
415
+ when /End self-consistency iteration/, /End scf initialization - timings/
416
+ retval.sc_iteration.timings = OutputParser.parse_sc_timings(f)
417
+
418
+ when /Change of charge density/
419
+ retval.sc_iteration.d_rho = line.split(' ')[6].to_f
420
+
421
+ when /Change of sum of eigenvalues/
422
+ retval.sc_iteration.d_eev = line.split(' ')[7].to_f
423
+
424
+ when /Change of total energy/
425
+ retval.sc_iteration.d_etot = line.split(' ')[6].to_f
426
+
427
+ when /\|\ Total energy corrected/
428
+ retval.geometry_step.total_corrected_energy = line.split(' ')[5].to_f
429
+
430
+ when /\|\ Total energy uncorrected/
431
+ retval.geometry_step.total_energy = line.split(' ')[5].to_f
432
+
433
+ when /\|\ Number\ of\ atoms/
434
+ n_atoms = line.split(' ')[5].to_i
435
+ retval.n_atoms = n_atoms
436
+
437
+ when /\|\ Chemical potential/
438
+ retval.geometry_step.chemical_potential = line.split(' ')[8].to_f
439
+
440
+ when /Input\ geometry\:/
441
+ line = f.readline
442
+ if line=~/\|\ Unit\ cell\:/
443
+ 3.times {
444
+ line = f.readline
445
+ fields = line.split(' ')
446
+ vectors << Vector[fields[1].to_f, fields[2].to_f, fields[3].to_f]
447
+ }
448
+ end
449
+ 2.times {f.readline}
450
+ retval.geometry_steps << GeometryStep.new
451
+ retval.geometry_step.step_num = 0
452
+ retval.geometry_step.geometry = OutputParser.parse_input_geometry(f, n_atoms)
453
+ retval.geometry_step.geometry.lattice_vectors = vectors
454
+
455
+ when /\ Updated\ atomic\ structure\:/
456
+ last_step_num = retval.geometry_step.step_num
457
+ retval.geometry_steps << GeometryStep.new
458
+ retval.geometry_step.step_num = last_step_num + 1
459
+ retval.geometry_step.geometry = OutputParser.parse_updated_geometry(f, n_atoms)
460
+ # retval.geometry_step.geometry.lattice_vectors = vectors
461
+
462
+ when /\ Final\ atomic\ structure\:/
463
+ retval.geometry_step.geometry = OutputParser.parse_updated_geometry(f, n_atoms)
464
+ # retval.geometry_step.geometry.lattice_vectors = vectors
465
+ when /\ Total\ atomic\ forces/
466
+ line = f.readline
467
+ until line =~ /---/
468
+ fields = line.split(' ')
469
+ retval.geometry_step.forces << Vector[fields[2].to_f, fields[3].to_f, fields[4].to_f]
470
+ line = f.readline
471
+ end
472
+ when /Present geometry is converged./
473
+ retval.geometry_converged = true
474
+ end
475
+ }
476
+ end
477
+
478
+ return retval
479
+
480
+ end
481
+ end
482
+
483
+ end
484
+