aims 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+