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
data/README.rdoc
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
|
2
|
+
= What is the Aims Gem?
|
3
|
+
|
4
|
+
The Aims gem is a ruby interface to the FHI-Aims ab-initio molecular simulation package published by the Fritz-Haber Institute [https://aimsclub.fhi-berlin.mpg.de/]. It provides a set of tools for parsing and generating the input and output files for AIMS.
|
5
|
+
|
6
|
+
The aims gem is written by Joshua Shapiro and release under the MIT license.
|
7
|
+
|
8
|
+
Copyright (c) Joshua Shapiro 2012
|
9
|
+
|
10
|
+
= Why should I use the Aims Gem?
|
11
|
+
|
12
|
+
If you like Ruby, and you like AIMS, then this gem provides you with a pure Ruby object-oriented interface to AIMS. You can use this library to:
|
13
|
+
|
14
|
+
* Simplify the generation of complex geometries
|
15
|
+
* Bulk and common surface geometries of ZincBlende and Wurtzite are predefined.
|
16
|
+
* Automate the generation of geometry and control files
|
17
|
+
* Parse the output of Aims into ruby objects for analysis
|
18
|
+
* Summarize the output of AIMS using simple scripts that come with the gem
|
19
|
+
* Whatever else you can imagine..
|
20
|
+
|
21
|
+
= Installation
|
22
|
+
|
23
|
+
To use this gem, you will need to have an installation of ruby. If you own a mac or a linux machine, then you already have ruby. If you have a windows machine, then you need to install ruby. The recommended way to install Ruby on windows (as of June 2012) is via http://rubyinstaller.org .
|
24
|
+
|
25
|
+
Once you have ruby, then just invoke the following from a terminal window.
|
26
|
+
|
27
|
+
gem install aims
|
28
|
+
|
29
|
+
= Usage
|
30
|
+
== Using the Aims GEM to generate geometry.in files
|
31
|
+
|
32
|
+
The following code can be used interactively in an +irb+ ruby interpreter, or can be
|
33
|
+
invoked in a ruby script.
|
34
|
+
|
35
|
+
=== Example 1: Generate a primitive unit cell of Silicon
|
36
|
+
|
37
|
+
require 'aims'
|
38
|
+
include Aims
|
39
|
+
|
40
|
+
# Define the lattice constant
|
41
|
+
lattice_const = 5.43
|
42
|
+
|
43
|
+
# Define the basis
|
44
|
+
a1 = Atom.new(0,0,0, "Si")
|
45
|
+
a2 = Atom.new(lattice_const/4, lattice_const/4, lattice_const/4, "Si")
|
46
|
+
|
47
|
+
# Define the primitive vectors
|
48
|
+
v1 = [lattice_const/2, lattice_const/2, 0]
|
49
|
+
v2 = [lattice_const/2, 0, lattice_const/2]
|
50
|
+
v3 = [0, lattice_const/2, lattice_const/2]
|
51
|
+
|
52
|
+
# Define the unit cell
|
53
|
+
uc = Geometry.new([a1, a2], [v1, v2, v3])
|
54
|
+
|
55
|
+
# Output the unit cell
|
56
|
+
puts uc.format_geometry_in
|
57
|
+
|
58
|
+
=== Example 2: Shortcut for generating a primitive unit cell of Zinc-Blende
|
59
|
+
|
60
|
+
require 'aims'
|
61
|
+
include Aims
|
62
|
+
|
63
|
+
zb = ZincBlende.new("Ga", "As", 5.65)
|
64
|
+
|
65
|
+
# Get the bulk geometry
|
66
|
+
puts zb.get_bulk.format_geometry_in
|
67
|
+
|
68
|
+
And here is how you get a (100) surface with 7 layers and 20 angstrom of vacuum
|
69
|
+
layers = 7
|
70
|
+
vacuum = 20
|
71
|
+
puts zb.get_001_surface(layers, vacuum)
|
72
|
+
|
73
|
+
And here is how you can constrain the bottom three layers
|
74
|
+
constrain = 3
|
75
|
+
puts zb.get_001_surface(layers, vacuum, constrain)
|
76
|
+
|
77
|
+
== Scripts that come with the Aims GEM
|
78
|
+
|
79
|
+
There are currently two scripts that come with the GEM
|
80
|
+
|
81
|
+
=== aims_output.rb
|
82
|
+
Quickly output the total energy, and timing information from the
|
83
|
+
calculation to make sure everything went smoothly.
|
84
|
+
Don't use this as a replacement for actually looking at the output of Aims.
|
85
|
+
|
86
|
+
usage: aims_output.rb [options] file1 [file2 ...]
|
87
|
+
-s, --step [N] Output information for relaxation step.
|
88
|
+
Specify an integer, 'first', 'last', or 'all'
|
89
|
+
Default is 'all'
|
90
|
+
--debug Debug output
|
91
|
+
--geometry-delta Display change from input geometry to final geometry
|
92
|
+
-c, --self-consistency Output self-consistency information
|
93
|
+
-f Output max force component for each geometry relaxation step
|
94
|
+
-t Output timings
|
95
|
+
|
96
|
+
=== aims_summary.rb
|
97
|
+
Display a one-line summary for a list of calculations in tabular form.
|
98
|
+
Useful for copying and pasting into a spreadsheet.
|
99
|
+
|
100
|
+
usage: aims_summary.rb file1 [file2] ...
|
data/bin/aims_output.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'aims'
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
options = {:step => :all}
|
9
|
+
|
10
|
+
optParser = OptionParser.new do |opts|
|
11
|
+
opts.banner = "usage: #{File.basename $0} [options] file1 [file2 ...]"
|
12
|
+
opts.on('-s', '--step [N]', 'Output information for relaxation step.',
|
13
|
+
"Specify an integer, 'first', 'last', or 'all'",
|
14
|
+
"Default is 'all'") do |s|
|
15
|
+
case s
|
16
|
+
when /([1-9]+)/
|
17
|
+
options[:step] = $1.to_i
|
18
|
+
when "first"
|
19
|
+
options[:step] = :first
|
20
|
+
when "last"
|
21
|
+
options[:step] = :last
|
22
|
+
else
|
23
|
+
options[:step] = :all
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on('--debug', 'Debug output') do
|
28
|
+
options[:debug] = true
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on('--geometry-delta', 'Display change from input geometry to final geometry') do
|
32
|
+
options[:geometry_delta] = true
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on('-c', '--self-consistency', 'Output self-consistency information') do
|
36
|
+
options[:self_consistency] = true
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on('-f', 'Output max force component for each geometry relaxation step') do
|
40
|
+
options[:forces] = true
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on('-t', 'Output timings') do
|
44
|
+
options[:timings] = true
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
begin
|
50
|
+
optParser.parse!(ARGV)
|
51
|
+
|
52
|
+
files = ARGV
|
53
|
+
if files.empty?
|
54
|
+
puts optParser.help
|
55
|
+
exit
|
56
|
+
end
|
57
|
+
outputs = files.collect{|f|
|
58
|
+
Aims::OutputParser.parse(f)
|
59
|
+
}
|
60
|
+
|
61
|
+
total_sc_iterations = 0
|
62
|
+
total_relaxations = 0
|
63
|
+
|
64
|
+
outputs.each{|output|
|
65
|
+
|
66
|
+
puts "**************************************************************************"
|
67
|
+
puts "**"
|
68
|
+
puts "** #{output.original_file}"
|
69
|
+
puts "**"
|
70
|
+
puts "**************************************************************************"
|
71
|
+
|
72
|
+
steps = case options[:step]
|
73
|
+
when Integer
|
74
|
+
stepno = options[:step]
|
75
|
+
if stepno < 0
|
76
|
+
[output.geometry_steps.last]
|
77
|
+
elsif stepno < output.geometry_steps.size
|
78
|
+
[output.geometry_steps[stepno]]
|
79
|
+
else
|
80
|
+
[output.geometry_steps.last]
|
81
|
+
end
|
82
|
+
when :first
|
83
|
+
[output.geometry_steps.first]
|
84
|
+
when :last
|
85
|
+
[output.geometry_steps.last]
|
86
|
+
else
|
87
|
+
output.geometry_steps
|
88
|
+
end
|
89
|
+
|
90
|
+
steps.each_with_index{|step, i|
|
91
|
+
|
92
|
+
total_relaxations += 1
|
93
|
+
total_sc_iterations += step.sc_iterations.size
|
94
|
+
|
95
|
+
sciter_format = "%-20s %20i"
|
96
|
+
timings_format = "%-35s %20.5f"
|
97
|
+
energy_format = "%-35s %20.5f"
|
98
|
+
force_format = "%-35s %20.5e"
|
99
|
+
|
100
|
+
puts "= Relaxation Step #{step.step_num} ="
|
101
|
+
|
102
|
+
indent = " "
|
103
|
+
puts indent + sciter_format % ["SC Iterations", step.sc_iterations.size]
|
104
|
+
puts indent + energy_format % ["Total Energy", step.total_energy]
|
105
|
+
puts indent + timings_format % ["Total CPU time", step.total_cpu_time]
|
106
|
+
puts indent + timings_format % ["Total Wall time", step.total_wall_time]
|
107
|
+
if options[:forces] and not step.forces.empty?
|
108
|
+
puts indent + force_format % ["Max Force", step.forces.max{|a,b| a.r <=> b.r}.r]
|
109
|
+
end
|
110
|
+
if options[:timings]
|
111
|
+
puts " Cumulative SC Timings:"
|
112
|
+
step.timings.each{|t| puts " " +timings_format % [t[:description], t[:cpu_time]]}
|
113
|
+
end
|
114
|
+
|
115
|
+
if options[:self_consistency]
|
116
|
+
|
117
|
+
indent = " "
|
118
|
+
|
119
|
+
# Iterate over each sc iteration
|
120
|
+
step.sc_iterations.each_with_index{|sc_iter, iter|
|
121
|
+
# SC Iteration Header
|
122
|
+
puts " == SC Iteration #{iter} =="
|
123
|
+
|
124
|
+
# Output convergence criterion
|
125
|
+
puts indent + energy_format % ["Change in total energy", sc_iter.d_etot]
|
126
|
+
puts indent + energy_format % ["Change in sum of eigenvalues", sc_iter.d_eev]
|
127
|
+
puts indent + energy_format % ["Change in charge density", sc_iter.d_rho]
|
128
|
+
|
129
|
+
# Output timings if requested
|
130
|
+
if options[:timings]
|
131
|
+
if sc_iter.timings
|
132
|
+
sc_iter.timings.each{|t|
|
133
|
+
puts indent + timings_format % [t[:description], t[:cpu_time]]
|
134
|
+
}
|
135
|
+
else
|
136
|
+
puts "No timing data available."
|
137
|
+
end
|
138
|
+
end
|
139
|
+
puts ""
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
puts "\n\n"
|
144
|
+
|
145
|
+
|
146
|
+
}
|
147
|
+
|
148
|
+
unless output.geometry_converged
|
149
|
+
puts "Warning Geometry not converged!"
|
150
|
+
end
|
151
|
+
|
152
|
+
if options[:geometry_delta]
|
153
|
+
puts "= Change in atomic positions for calculation"
|
154
|
+
puts output.geometry_steps.last.geometry.delta(output.geometry_steps.first.geometry)
|
155
|
+
end
|
156
|
+
}
|
157
|
+
|
158
|
+
|
159
|
+
|
160
|
+
# puts "Total relaxation steps: #{total_relaxations}"
|
161
|
+
# puts "Total sc iterations: #{total_sc_iterations}"
|
162
|
+
|
163
|
+
rescue
|
164
|
+
puts ""
|
165
|
+
puts "Sorry. There was an error parsing the remainder of the file."
|
166
|
+
if options[:debug]
|
167
|
+
puts $!.message
|
168
|
+
puts $!.backtrace
|
169
|
+
else
|
170
|
+
puts "Rerun with --debug for more info"
|
171
|
+
end
|
172
|
+
puts ""
|
173
|
+
exit
|
174
|
+
end
|
175
|
+
|
data/bin/aims_summary.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'aims'
|
4
|
+
files = ARGV
|
5
|
+
if files.empty?
|
6
|
+
puts "usage: #{File.basename $0} file1 [file2] ..."
|
7
|
+
exit
|
8
|
+
end
|
9
|
+
|
10
|
+
STDOUT.sync = true
|
11
|
+
puts "%-10s \t %-20s \t %-15s \t %9s \t %7s \t %10s \t %12s \t %8s \t %10s" % %w(RUN FILE TOTAL_ENERGY NUM_ATOMS K-GRID CONVERGED RELAX_STEPS SC_ITERS TOTAL_TIME)
|
12
|
+
format = "%-10s \t %-20s \t %+15e \t %9i \t %7s \t %10s \t %12i \t %8i \t %10.2f"
|
13
|
+
files.each{|f|
|
14
|
+
run = f.split(".").last
|
15
|
+
begin
|
16
|
+
o = Aims::OutputParser.parse(f)
|
17
|
+
puts format % [run, f[0...20],
|
18
|
+
(o.total_energy.nan? ? Float::NAN : o.total_energy),
|
19
|
+
(o.n_atoms or -1),
|
20
|
+
(o.k_grid ? o.k_grid.squeeze : "-"),
|
21
|
+
o.geometry_converged,
|
22
|
+
(o.n_relaxation_steps or -1),
|
23
|
+
(o.n_sc_iterations or -1),
|
24
|
+
o.total_cpu_time]
|
25
|
+
rescue
|
26
|
+
puts [run, f, "***ERROR***", $!.message].join("\t")
|
27
|
+
end
|
28
|
+
}
|
data/lib/aims.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# The Aims RubyGem is distributed under the MIT license
|
2
|
+
#
|
3
|
+
# Copyright (c) 2012 Joshua Shapiro
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
6
|
+
# software and associated documentation files (the "Software"), to deal in the Software
|
7
|
+
# without restriction, including without limitation the rights to use, copy, modify,
|
8
|
+
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to the following
|
10
|
+
# conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in all copies
|
13
|
+
# or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
16
|
+
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
17
|
+
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
18
|
+
# FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
19
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
20
|
+
# DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
require 'aims/vectorize.rb'
|
23
|
+
require 'aims/atom.rb'
|
24
|
+
require 'aims/bond.rb'
|
25
|
+
require 'aims/geometry_parser.rb'
|
26
|
+
require 'aims/output.rb'
|
27
|
+
require 'aims/plane.rb'
|
28
|
+
require 'aims/geometry.rb'
|
29
|
+
require 'aims/volume.rb'
|
30
|
+
require 'aims/wurtzite.rb'
|
31
|
+
require 'aims/zinc_blende.rb'
|
32
|
+
|
33
|
+
# :include:README.rdoc
|
34
|
+
module Aims
|
35
|
+
end
|
data/lib/aims/atom.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
module Aims
|
2
|
+
|
3
|
+
class Atom
|
4
|
+
# The last id assigned to an atom
|
5
|
+
@@lastid = 0
|
6
|
+
|
7
|
+
# The x coordinate of the atom in angstrom
|
8
|
+
attr_accessor :x
|
9
|
+
# The y coordinate of the atom in angstrom
|
10
|
+
attr_accessor :y
|
11
|
+
# The z coordinate of the atom in angstrom
|
12
|
+
attr_accessor :z
|
13
|
+
# The +id+ of this atom. Every atom has a unique id
|
14
|
+
attr_accessor :id
|
15
|
+
# The species of this atom
|
16
|
+
attr_accessor :species
|
17
|
+
# The relaxation constraints of this atom
|
18
|
+
attr_accessor :constrain
|
19
|
+
# Two atoms are equal if their coordinates are the same to this precision
|
20
|
+
attr_accessor :precision
|
21
|
+
|
22
|
+
include Enumerable
|
23
|
+
|
24
|
+
# Create an atom of the specified species at the given coordinates
|
25
|
+
# * +x+ The x coordinate of the atom in angstrom
|
26
|
+
# * +y+ The y coordinate of the atom in angstrom
|
27
|
+
# * +z+ The z coordinate of the atom in angstrom
|
28
|
+
# * +s+ The atomic species ex. "C", "Si", "S", etc. (can be nil)
|
29
|
+
# * +c+ The relaxation constraints. valid values are TRUE, FALSE, ".true.", ".false.", "x", "y", "z" or %w(x y z)
|
30
|
+
def initialize(x=nil, y=nil, z=nil, s=nil, c=Array.new)
|
31
|
+
self.x = x
|
32
|
+
self.y = y
|
33
|
+
self.z = z
|
34
|
+
self.species = s
|
35
|
+
self.precision = 0.0001
|
36
|
+
self.id = (@@lastid +=1)
|
37
|
+
self.constrain = c
|
38
|
+
end
|
39
|
+
|
40
|
+
# A boolean value,
|
41
|
+
# True if the atom has relaxation constraints
|
42
|
+
def constrained?
|
43
|
+
if self.constrain
|
44
|
+
if self.constrain == true
|
45
|
+
true
|
46
|
+
elsif self.constrain.is_a? String
|
47
|
+
true
|
48
|
+
elsif self.constrain.is_a? Array and not self.constrain.empty?
|
49
|
+
true
|
50
|
+
else
|
51
|
+
false
|
52
|
+
end
|
53
|
+
else
|
54
|
+
false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Two atoms are equal if their coordinates are equal and they are the same species
|
59
|
+
def ==(atom)
|
60
|
+
((self.x-atom.x).abs < self.precision) &
|
61
|
+
((self.y-atom.y).abs < self.precision) &
|
62
|
+
((self.z-atom.z).abs < self.precision) &
|
63
|
+
(self.species == atom.species)
|
64
|
+
end
|
65
|
+
alias_method :eql?, :==
|
66
|
+
|
67
|
+
# Implementation for Hash equality testing
|
68
|
+
def hash
|
69
|
+
(self.x*self.y + self.z).abs.ceil
|
70
|
+
end
|
71
|
+
|
72
|
+
# Enumerate over each coordinate (x,y,z)
|
73
|
+
def each
|
74
|
+
[self.x, self.y, self.z].each{|i| yield i}
|
75
|
+
end
|
76
|
+
|
77
|
+
# Index into the Atom's coordinates (x,y,z)
|
78
|
+
def [](i)
|
79
|
+
case i
|
80
|
+
when 0
|
81
|
+
self.x
|
82
|
+
when 1
|
83
|
+
self.y
|
84
|
+
when 2
|
85
|
+
self.z
|
86
|
+
else
|
87
|
+
raise "Index Out of Bounds"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Return the distance to another atom
|
92
|
+
def distance_to(atom)
|
93
|
+
Math.sqrt((self.x - atom.x)**2 + (self.y - atom.y)**2 + (self.z - atom.z)**2)
|
94
|
+
end
|
95
|
+
|
96
|
+
# A deep copy of the atom
|
97
|
+
def copy
|
98
|
+
Atom.new(self.x, self.y, self.z, self.species, self.constrain)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Return a new atom with the same species and relaxation constraints
|
102
|
+
# but with coordinates displaced by +x+, +y+, +z+
|
103
|
+
def displace(x,y,z)
|
104
|
+
Atom.new(self.x+x, self.y+y, self.z+z, self.species, self.constrain)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Displace this atom in place
|
108
|
+
def displace!(x,y,z)
|
109
|
+
self.x += x
|
110
|
+
self.y += y
|
111
|
+
self.z += z
|
112
|
+
end
|
113
|
+
|
114
|
+
# Return an atom rotated about the z-axis using the origin as the center-point.
|
115
|
+
# * +angle+ Is the amount to rotate in degrees (or it can respond to :sin and :cos)
|
116
|
+
def rotate_Z(angle)
|
117
|
+
sinA = if angle.respond_to? :sin
|
118
|
+
angle.sine
|
119
|
+
else
|
120
|
+
Math.sin(angle*Math::PI/180)
|
121
|
+
end
|
122
|
+
cosA = if angle.respond_to? :cos
|
123
|
+
angle.cos
|
124
|
+
else
|
125
|
+
Math.cos(angle*Math::PI/180)
|
126
|
+
end
|
127
|
+
|
128
|
+
mat = Matrix[[cosA, -1*sinA, 0],[sinA, cosA, 0], [0,0,1]]
|
129
|
+
rotate(mat)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Return an atom rotated about the x-axis using the origin as the center-point.
|
133
|
+
# * +angle+ Is the amount to rotate in degrees (or it can respond to :sin and :cos)
|
134
|
+
def rotate_X(angle)
|
135
|
+
sinA = if angle.respond_to? :sin
|
136
|
+
angle.sine
|
137
|
+
else
|
138
|
+
Math.sin(angle*Math::PI/180)
|
139
|
+
end
|
140
|
+
cosA = if angle.respond_to? :cos
|
141
|
+
angle.cos
|
142
|
+
else
|
143
|
+
Math.cos(angle*Math::PI/180)
|
144
|
+
end
|
145
|
+
mat = Matrix[[1, 0, 0], [0, cosA, -1*sinA],[0, sinA, cosA]]
|
146
|
+
rotate(mat)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Return an atom rotated about the y-axis using the origin as the center-point.
|
150
|
+
# * +angle+ Is the amount to rotate in degrees (or it can respond to :sin and :cos)
|
151
|
+
def rotate_Y(angle)
|
152
|
+
sinA = if angle.respond_to? :sin
|
153
|
+
angle.sine
|
154
|
+
else
|
155
|
+
Math.sin(angle*Math::PI/180)
|
156
|
+
end
|
157
|
+
cosA = if angle.respond_to? :cos
|
158
|
+
angle.cos
|
159
|
+
else
|
160
|
+
Math.cos(angle*Math::PI/180)
|
161
|
+
end
|
162
|
+
mat = Matrix[[cosA, 0, -1*sinA],[0, 1, 0], [sinA, 0, cosA]]
|
163
|
+
rotate(mat)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Return a new rotated atom about the origin using the given 3x3 Math::Matrix.
|
167
|
+
def rotate(mat)
|
168
|
+
v = Vector[self.x, self.y, self.z]
|
169
|
+
newv = mat*v
|
170
|
+
Atom.new(newv[0], newv[1], newv[2], self.species, self.constrain)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Print a string representation of this atom
|
174
|
+
def to_s
|
175
|
+
"%s %16.6f %16.6f %16.6f" % [self.species, self.x, self.y, self.z]
|
176
|
+
end
|
177
|
+
|
178
|
+
# Print a string representation of this atom formatted in the
|
179
|
+
# geometry.in format used by Aims
|
180
|
+
def format_geometry_in
|
181
|
+
line = "atom %16.6f %16.6f %16.6f %s" % [self.x, self.y, self.z, self.species]
|
182
|
+
if self.constrain
|
183
|
+
if self.constrain == true
|
184
|
+
line << "\nconstrain_relaxation .true."
|
185
|
+
elsif self.constrain.is_a? String
|
186
|
+
line << "\nconstrain_relaxation #{self.constrain}"
|
187
|
+
elsif self.constrain.is_a? Array and not self.constrain.empty?
|
188
|
+
self.constrain.each{|c|
|
189
|
+
line << "\nconstrain_relaxation #{c}"
|
190
|
+
}
|
191
|
+
line << "\n"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
line
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|