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.
- data/README.rdoc +100 -0
- data/bin/aims_output.rb +175 -0
- data/bin/aims_summary.rb +28 -0
- data/lib/aims.rb +35 -0
- data/lib/aims/atom.rb +197 -0
- data/lib/aims/bond.rb +46 -0
- data/lib/aims/cube.rb +46 -0
- data/lib/aims/geometry.rb +545 -0
- data/lib/aims/geometry_parser.rb +56 -0
- data/lib/aims/output.rb +484 -0
- data/lib/aims/plane.rb +74 -0
- data/lib/aims/vectorize.rb +22 -0
- data/lib/aims/volume.rb +137 -0
- data/lib/aims/wurtzite.rb +48 -0
- data/lib/aims/zinc_blende.rb +311 -0
- data/spec/atom_spec.rb +40 -0
- data/spec/bond_spec.rb +0 -0
- data/spec/output_spec.rb +42 -0
- data/spec/zinc_blende_spec.rb +63 -0
- metadata +82 -0
@@ -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
|
+
|
data/lib/aims/output.rb
ADDED
@@ -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
|
+
|