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