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/lib/aims/plane.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
module Aims
|
2
|
+
|
3
|
+
# A Class representing a plane.
|
4
|
+
# Internally stored in Hessian Normal Form.
|
5
|
+
# 0 = Ax + By + Cz - D
|
6
|
+
#
|
7
|
+
# (A,B,C) is the plane normal.
|
8
|
+
# D is the distance from the origin.
|
9
|
+
class Plane
|
10
|
+
attr_reader :a, :b, :c, :d
|
11
|
+
|
12
|
+
# Initialize this plane with the normal (a,b,c) and a
|
13
|
+
# point (x,y,z) on the plane
|
14
|
+
def initialize(a, b, c, x=0, y=0, z=0)
|
15
|
+
@a = a
|
16
|
+
@b = b
|
17
|
+
@c = c
|
18
|
+
if (@a == 0 and @b == 0 and @c == 0)
|
19
|
+
raise "Invalid definition of plane."
|
20
|
+
end
|
21
|
+
@d = a*x + b*y + c*z
|
22
|
+
end
|
23
|
+
|
24
|
+
# return some arbitrary point on the plane
|
25
|
+
def any_point_on_plane
|
26
|
+
|
27
|
+
unless (@c == 0)
|
28
|
+
return Vector[0, 0, @d/@c]
|
29
|
+
end
|
30
|
+
|
31
|
+
unless (@b == 0)
|
32
|
+
return Vector[0, @d/@b, 0]
|
33
|
+
end
|
34
|
+
|
35
|
+
unless (@a == 0)
|
36
|
+
return Vector[@d/@a, 0, 0]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Actually if we get to this point, the plane undetermined and all of R3 satisfies the definition
|
40
|
+
return Vector[0,0,0]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return the distance to point (x,y,z)
|
44
|
+
#
|
45
|
+
# distance = D - Ax - By - Cz
|
46
|
+
def distance_to_point(x, y, z)
|
47
|
+
a*x + b*y + c*z - d
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return the unit normal Vector[a, b, c]
|
51
|
+
def unit_normal
|
52
|
+
v = Vector[@a, @b, @c]
|
53
|
+
v*(1/v.r)
|
54
|
+
end
|
55
|
+
|
56
|
+
# The equation for the interstion of a ray and a plane
|
57
|
+
# NOT YET IMPLEMENTED
|
58
|
+
def intersection_with_ray(a, b)
|
59
|
+
raise "Sorry. Plane#intersection_with_ray is not yet implemented"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Displace this plane a distance in the direction of its normal
|
63
|
+
def displace_along_normal(distance)
|
64
|
+
@d += distance
|
65
|
+
end
|
66
|
+
|
67
|
+
# Two planes are equal if there ABCD parameters are equal
|
68
|
+
def ==(aPlane)
|
69
|
+
@a == aPlane.a and @b == aPlane.b and @c == aPlane.c and @d == aPlane.d
|
70
|
+
end
|
71
|
+
alias_method :eql?, :==
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Aims
|
2
|
+
module Vectorize
|
3
|
+
|
4
|
+
# Dot product of two n-element arrays
|
5
|
+
def dot(a, b)
|
6
|
+
unless a.size == b.size
|
7
|
+
raise "Vectors must be the same length"
|
8
|
+
end
|
9
|
+
|
10
|
+
# Make element-by-element array of pairs
|
11
|
+
(a.to_a).zip(b.to_a).inject(0) {|tot, pair| tot = tot + pair[0]*pair[1]}
|
12
|
+
end
|
13
|
+
|
14
|
+
# Cross product of two arrays of length 3
|
15
|
+
def cross(b,c)
|
16
|
+
unless b.size == 3 and c.size == 3
|
17
|
+
raise "Vectors must be of length 3"
|
18
|
+
end
|
19
|
+
Vector[b[1]*c[2] - b[2]*c[1], b[2]*c[0] - b[0]*c[2], b[0]*c[1] - b[1]*c[0]]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/aims/volume.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
module Aims
|
2
|
+
|
3
|
+
# A volume is defined by a minimum of four planes that intersect in a minimum
|
4
|
+
# of four points. The normals of the planes must all point outward. This is tested
|
5
|
+
#
|
6
|
+
class Volume
|
7
|
+
|
8
|
+
include Vectorize
|
9
|
+
|
10
|
+
# Quick recursive method for calculating combinations
|
11
|
+
def Volume.choose(list, num, head = [])
|
12
|
+
|
13
|
+
_head = head.dup
|
14
|
+
_list = list.dup
|
15
|
+
_num = num
|
16
|
+
|
17
|
+
if _num == 0
|
18
|
+
return [_head]
|
19
|
+
end
|
20
|
+
|
21
|
+
new_heads = []
|
22
|
+
while _list.size > _num-1
|
23
|
+
h = _head + [_list.shift]
|
24
|
+
new_heads += Volume.choose(_list, num-1, h)
|
25
|
+
end
|
26
|
+
return new_heads
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Return an array of tuples that define the
|
32
|
+
# vertices of intersection of these planes
|
33
|
+
# Vertices are removed that lie in front of any plane
|
34
|
+
def Volume.intersection_points(planes)
|
35
|
+
combos = Volume.choose(planes, 3)
|
36
|
+
points = []
|
37
|
+
combos.each{|c|
|
38
|
+
n1 = c[0].unit_normal
|
39
|
+
n2 = c[1].unit_normal
|
40
|
+
n3 = c[2].unit_normal
|
41
|
+
d = Matrix[n1, n2,n3].transpose.det
|
42
|
+
|
43
|
+
# The determinant is zero if any two planes are parallel
|
44
|
+
unless (d == 0)
|
45
|
+
p1 = c[0].any_point_on_plane
|
46
|
+
p2 = c[1].any_point_on_plane
|
47
|
+
p3 = c[2].any_point_on_plane
|
48
|
+
|
49
|
+
# This defines the point of intersection of three planes.
|
50
|
+
points << (cross(n2,n3)*dot(p1, n1) + cross(n3,n1)*dot(p2,n2) + cross(n1,n2)*dot(p3, n3))*(1/d)
|
51
|
+
end
|
52
|
+
}
|
53
|
+
|
54
|
+
# Only keep the points that are behind all planes
|
55
|
+
keepers = []
|
56
|
+
points.each{|pt|
|
57
|
+
keep = true
|
58
|
+
planes.each {|pl|
|
59
|
+
keep = (pl.distance_to_point(pt[0], pt[1], pt[2]) <= 0)
|
60
|
+
break unless keep
|
61
|
+
}
|
62
|
+
keepers << pt if keep
|
63
|
+
}
|
64
|
+
return keepers
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(planes)
|
68
|
+
|
69
|
+
points = Volume.intersection_points(planes)
|
70
|
+
if (4 > points.size)
|
71
|
+
raise "Planes do not intersect in a closed volume."
|
72
|
+
end
|
73
|
+
@points = points
|
74
|
+
@planes = planes
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return the bounding box for this volume
|
78
|
+
def bounding_box
|
79
|
+
unless @bbox
|
80
|
+
p = @points[0]
|
81
|
+
minX = p[0]
|
82
|
+
maxX = p[0]
|
83
|
+
minY = p[1]
|
84
|
+
maxY = p[1]
|
85
|
+
minZ = p[2]
|
86
|
+
maxZ = p[2]
|
87
|
+
@points.each{|p|
|
88
|
+
minX = p[0] if p[0] < minX
|
89
|
+
maxX = p[0] if p[0] > maxX
|
90
|
+
minY = p[1] if p[1] < minY
|
91
|
+
maxY = p[1] if p[1] > maxY
|
92
|
+
minZ = p[2] if p[2] < minZ
|
93
|
+
maxZ = p[2] if p[2] > maxZ
|
94
|
+
}
|
95
|
+
@max = Vector[maxX, maxY,maxZ]
|
96
|
+
@min = Vector[minX, minY, minZ]
|
97
|
+
@bbox = Volume.new([Plane.new(-1,0,0, minX, minY, minZ),
|
98
|
+
Plane.new(0,-1,0, minX, minY, minZ),
|
99
|
+
Plane.new(0,0,-1, minX, minY, minZ),
|
100
|
+
Plane.new(1,0,0, maxX, maxY, maxZ),
|
101
|
+
Plane.new(0,1,0, maxX, maxY, maxZ),
|
102
|
+
Plane.new(0,0,1, maxX, maxY, maxZ)])
|
103
|
+
end
|
104
|
+
@bbox
|
105
|
+
end
|
106
|
+
|
107
|
+
# Return the point on the bounding box that represents the maximum value
|
108
|
+
# of any cartesian coordinate (the upper right corner)
|
109
|
+
def max_point
|
110
|
+
# generate the bounding box if not already done
|
111
|
+
bounding_box
|
112
|
+
# return the max
|
113
|
+
@max
|
114
|
+
end
|
115
|
+
|
116
|
+
# Return the point on the bounding box that represents the minimum value
|
117
|
+
# of any cartesian coordinate (the lower left corner).
|
118
|
+
def min_point
|
119
|
+
# generate the bounding box if not already done
|
120
|
+
bounding_box
|
121
|
+
# return the min
|
122
|
+
@min
|
123
|
+
end
|
124
|
+
|
125
|
+
# A volume contains a point if it lies behind all the planes
|
126
|
+
def contains_point(x,y,z)
|
127
|
+
behind = true
|
128
|
+
@planes.each{|p|
|
129
|
+
behind = (0 >= p.distance_to_point(x,y,z))
|
130
|
+
break if not behind
|
131
|
+
}
|
132
|
+
return behind
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
|
2
|
+
require 'mathn'
|
3
|
+
|
4
|
+
module Aims
|
5
|
+
class Wurtzite
|
6
|
+
|
7
|
+
include Math
|
8
|
+
|
9
|
+
attr_accessor :lattice_const, :cation, :anion
|
10
|
+
|
11
|
+
# Initialize the wurtzite Geometry
|
12
|
+
# cation and anion are the atomic
|
13
|
+
# species occupying the two different sub-lattices.
|
14
|
+
# lattice_const specifies the lattice constant
|
15
|
+
def initialize(cation, anion, lattice_const)
|
16
|
+
self.lattice_const = lattice_const
|
17
|
+
self.cation = cation
|
18
|
+
self.anion = anion
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_bulk
|
22
|
+
# The lattice constant
|
23
|
+
a = (ARGV[0] || 4.0).to_f
|
24
|
+
c = 1.63299*a # sqrt(8/3)a
|
25
|
+
#a = 6.19231 # Strained GaSb, Unstrained GaSb is 6.22
|
26
|
+
#a = 5.75 # Unstrained GaAs
|
27
|
+
|
28
|
+
# The atoms on a HCP
|
29
|
+
as1 = Atom.new(0,0,0,'As')
|
30
|
+
as2 = as1.displace(0.5*a, 0.33333*a, 0.5*c)
|
31
|
+
|
32
|
+
ga1 = Atom.new(0.0, 0.0, c*0.386, 'Ga')
|
33
|
+
ga2 = ga1.displace(0.5*a,0.33333*a, 0.5*c)
|
34
|
+
|
35
|
+
# The lattice Vectors
|
36
|
+
v1 = Vector[1.0, 0.0, 0.0]*a
|
37
|
+
v2 = Vector[0.5, 0.5*sqrt(3), 0.0]*a
|
38
|
+
v3 = Vector[0.0, 0.0, 1.0]*c
|
39
|
+
|
40
|
+
# The unit cell
|
41
|
+
wz = Geometry.new([as1,ga1,as2,ga2], [v1, v2, v3])
|
42
|
+
|
43
|
+
# wz.set_miller_indices(millerX, millerY, millerZ)
|
44
|
+
return wz
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,311 @@
|
|
1
|
+
require 'mathn'
|
2
|
+
|
3
|
+
module Aims
|
4
|
+
|
5
|
+
# Factory class for generating slabs for various crystal surfaces
|
6
|
+
# of specified thickness and with the specified vacuum.
|
7
|
+
# Example:
|
8
|
+
# zb = ZincBlende.new("Ga", "As", 5.65)
|
9
|
+
# zb.get_bulk -> # returns the a unit cell with two atoms
|
10
|
+
# zb.get_111B_surface(7, 20, 3) # returns a slab of 111B 7 monolayers thick, with 20 angstrom of vacuum, and the bottom 3 layers constrained
|
11
|
+
class ZincBlende
|
12
|
+
|
13
|
+
include Vectorize
|
14
|
+
include Math
|
15
|
+
|
16
|
+
attr_accessor :lattice_const, :cation, :anion
|
17
|
+
|
18
|
+
# Initialize the zinc-blende Geometry
|
19
|
+
# cation and anion are the atomic
|
20
|
+
# species occupying the two different sub-lattices.
|
21
|
+
# lattice_const specifies the lattice constant
|
22
|
+
def initialize(cation, anion, lattice_const)
|
23
|
+
self.lattice_const = lattice_const
|
24
|
+
self.cation = cation
|
25
|
+
self.anion = anion
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return the traditional unit cell of bulk zinc blende
|
29
|
+
def get_bulk
|
30
|
+
b = 0.25*self.lattice_const
|
31
|
+
a1 = Atom.new(0, 0, 0, self.cation)
|
32
|
+
a2 = Atom.new(b, b, b, self.anion)
|
33
|
+
|
34
|
+
v1 = Vector[0.5, 0.5, 0.0]*self.lattice_const
|
35
|
+
v2 = Vector[0.5, 0.0, 0.5]*self.lattice_const
|
36
|
+
v3 = Vector[0.0, 0.5, 0.5]*self.lattice_const
|
37
|
+
zb = Geometry.new([a1, a2], [v1, v2, v3])
|
38
|
+
|
39
|
+
millerx = [1, 0, 0]
|
40
|
+
millery = [0, 1, 0]
|
41
|
+
millerz = [0, 0, 1]
|
42
|
+
|
43
|
+
zb.set_miller_indices(millerx, millery, millerz)
|
44
|
+
|
45
|
+
return zb
|
46
|
+
end
|
47
|
+
|
48
|
+
# Fill the given volume with atoms
|
49
|
+
def fill_volume(volume)
|
50
|
+
|
51
|
+
# First fill a cube that bounds the volume
|
52
|
+
max = volume.max_point
|
53
|
+
min = volume.min_point
|
54
|
+
|
55
|
+
dx = max[0] - min[0]
|
56
|
+
dy = max[1] - min[1]
|
57
|
+
dz = max[2] - min[2]
|
58
|
+
|
59
|
+
bulk = get_bulk
|
60
|
+
|
61
|
+
# This inverse matrix gives the number of repetitions
|
62
|
+
m = Matrix[[dx,0,0], [0,dy,0], [0,0,dz]]
|
63
|
+
v = Matrix[bulk.lattice_vectors[0].to_a,
|
64
|
+
bulk.lattice_vectors[1].to_a,
|
65
|
+
bulk.lattice_vectors[2].to_a]
|
66
|
+
rep_mat = m*(v.inverse)
|
67
|
+
|
68
|
+
# The only way I can figure out how to do this for an
|
69
|
+
# arbitrary set of lattice vectors is to fill the volume
|
70
|
+
# out along each edge of the super-cube and then eliminate duplicates
|
71
|
+
atoms = []
|
72
|
+
3.times do |i|
|
73
|
+
# this vector is the number of repetitions in the unit cell
|
74
|
+
# to fill the volume out along the i-th edge of the super-cube
|
75
|
+
n_repeat = rep_mat.row(i)
|
76
|
+
|
77
|
+
# Give the proper sign to the repeat
|
78
|
+
nx = (n_repeat[0] < 0) ? n_repeat[0].floor-1 : n_repeat[0].ceil+1
|
79
|
+
ny = (n_repeat[1] < 0) ? n_repeat[1].floor-1 : n_repeat[1].ceil+1
|
80
|
+
nz = (n_repeat[2] < 0) ? n_repeat[2].floor-1 : n_repeat[2].ceil+1
|
81
|
+
|
82
|
+
atoms += bulk.repeat(nx, ny, nz).atoms.find_all{|a| volume.contains_point(a.x, a.y, a.z)}
|
83
|
+
end
|
84
|
+
Geometry.new(atoms.uniq)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Return a unit cell for a slab of 001
|
88
|
+
# Specify the number of atomic monolayers,
|
89
|
+
# the vacuum thickness in angstrom,
|
90
|
+
# and the number of layers to constrain at the base of the slab
|
91
|
+
def get_001_surface(monolayers, vacuum, constrain_layers = 0)
|
92
|
+
anion = Atom.new(0,0,0,self.cation)
|
93
|
+
cation = Atom.new(0.25*self.lattice_const, 0.25*self.lattice_const, 0.25*self.lattice_const, self.anion)
|
94
|
+
v1 = Vector[0.5, 0.5, 0]*self.lattice_const
|
95
|
+
v2 = Vector[-0.5,0.5,0]*self.lattice_const
|
96
|
+
v3 = Vector[0.5, 0, 0.5]*self.lattice_const
|
97
|
+
|
98
|
+
zb = Geometry.new([anion, cation], [v1,v2,v3])
|
99
|
+
millerX = [1,0,0]
|
100
|
+
millerY = [0,1,0]
|
101
|
+
millerZ = [0,0,1]
|
102
|
+
zb.set_miller_indices(millerX, millerY, millerZ)
|
103
|
+
|
104
|
+
# Repeat the unit cell. The unit cell is a bi-layer so divide by 2
|
105
|
+
zb = zb.repeat(1,1,(monolayers/2).ceil)
|
106
|
+
|
107
|
+
if 0 < vacuum
|
108
|
+
# Add vacuum
|
109
|
+
monolayerSep = v3[2]/2
|
110
|
+
zb.lattice_vectors[2] = Vector[0, 0, monolayers*monolayerSep.abs + vacuum.to_f]
|
111
|
+
# Move everything into a nice tidy unit cell.
|
112
|
+
zb = zb.correct
|
113
|
+
end
|
114
|
+
|
115
|
+
minZ = zb.atoms.min{|a,b| a.z <=> b.z}.z
|
116
|
+
|
117
|
+
# Reject the top layer of atoms if an odd number of monolayers was requested.
|
118
|
+
# This is necessary because the primitive cell is a bilayer
|
119
|
+
zb.atoms.reject! {|a|
|
120
|
+
a.z >= (minZ + monolayerSep.abs*monolayers)
|
121
|
+
}
|
122
|
+
|
123
|
+
# Constrain the bottom layers
|
124
|
+
zb.atoms.each{|a|
|
125
|
+
if (a.z < minZ + monolayerSep.abs*constrain_layers)
|
126
|
+
a.constrain = ".true."
|
127
|
+
end
|
128
|
+
}
|
129
|
+
|
130
|
+
# Return the completed unit cell
|
131
|
+
return zb
|
132
|
+
end
|
133
|
+
alias_method :get_100_surface, :get_001_surface
|
134
|
+
alias_method :get_010_surface, :get_001_surface
|
135
|
+
|
136
|
+
# Return a unit cell for a slab of 111A (anion terminated)
|
137
|
+
# specify the number of atomic monolayers,
|
138
|
+
# the vacuum thickness in angstrom,
|
139
|
+
# and the number of layers to constrain at the base of the slab
|
140
|
+
def get_111A_surface(monolayers, vacuum, constrain_layers = 0)
|
141
|
+
# get the 111B surface, then reflect it about z=0
|
142
|
+
get_111_surface("A", monolayers, vacuum, constrain_layers)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Return a unit cell for a slab of 111A (cation terminated)
|
146
|
+
# specify the number of atomic monolayers,
|
147
|
+
# the vacuum thickness in angstrom,
|
148
|
+
# and the number of layers to constrain at the base of the slab
|
149
|
+
def get_111B_surface(monolayers, vacuum, constrain_layers = 0)
|
150
|
+
get_111_surface("B", monolayers, vacuum, constrain_layers)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Return a unit cell for a slab of 111
|
154
|
+
# dir is either "A" or "B" for the cation or anion terminated slab
|
155
|
+
# specify the number of atomic monolayers
|
156
|
+
# and the vacuum thickness in angstrom
|
157
|
+
def get_111_surface(dir, monolayers, vacuum, constrain_layers = 0)
|
158
|
+
|
159
|
+
if dir == "A"
|
160
|
+
top_atom = self.anion
|
161
|
+
bot_atom = self.cation
|
162
|
+
elsif dir == "B"
|
163
|
+
top_atom = self.cation
|
164
|
+
bot_atom = self.anion
|
165
|
+
else
|
166
|
+
raise "Direction must be either A or B"
|
167
|
+
end
|
168
|
+
|
169
|
+
# The atoms on a FCC
|
170
|
+
as1 = Atom.new(0.0, 0.0, 0.0, top_atom)
|
171
|
+
ga1 = Atom.new(0.0, 0.0, -sqrt(3)/4*self.lattice_const, bot_atom)
|
172
|
+
|
173
|
+
# The lattice Vectors
|
174
|
+
v1 = Vector[0.5*sqrt(2), 0.0, 0.0]*self.lattice_const
|
175
|
+
v2 = Vector[sqrt(2)*0.25, sqrt(6)*0.25, 0.0]*self.lattice_const
|
176
|
+
v3 = Vector[sqrt(2)*0.25, sqrt(2.0/3.0)*0.25, -1*sqrt(4.0/3.0)*0.5]*self.lattice_const
|
177
|
+
|
178
|
+
# The unit cell
|
179
|
+
zb = Geometry.new([as1, ga1], [v1, v2, v3])
|
180
|
+
|
181
|
+
# The Miller Indices
|
182
|
+
millerX = [-1, 1, 0] # Orientation of the crystal pointing in the cartesian +x axis
|
183
|
+
millerY = [1, 1, -2] # Orientation of the crystal pointing in the cartesian +y axis
|
184
|
+
millerZ = [-1, -1, -1] # Orientation of the crystal pointing in the cartesian +z axis
|
185
|
+
|
186
|
+
zb.set_miller_indices(millerX, millerY, millerZ)
|
187
|
+
|
188
|
+
# Repeat the unit cell and add vacuum
|
189
|
+
if 0 < vacuum
|
190
|
+
# We actually repeat the unit cell monolayers+1 times because
|
191
|
+
# I will strip off the top and bottom atoms to make the proper surface
|
192
|
+
zb = zb.repeat(1,1,monolayers+1)
|
193
|
+
|
194
|
+
bilayerSep = v3[2]
|
195
|
+
zb.lattice_vectors[2] = Vector[0, 0, monolayers*(bilayerSep.abs) + vacuum]
|
196
|
+
|
197
|
+
# Strip off the top and bottom atom
|
198
|
+
minZ = zb.atoms.min{|a,b| a.z <=> b.z}.z
|
199
|
+
maxZ = zb.atoms.max{|a,b| a.z <=> b.z}.z
|
200
|
+
|
201
|
+
zb.atoms.reject!{|a| a.z == maxZ}
|
202
|
+
zb.atoms.reject!{|a| a.z == minZ}
|
203
|
+
|
204
|
+
# Constrain the bottom layers if requested
|
205
|
+
if 0 < constrain_layers
|
206
|
+
# get the min again because we removed the atoms at minZ above
|
207
|
+
minZ = zb.atoms.min{|a,b| a.z <=> b.z}.z
|
208
|
+
constrain_below = minZ + bilayerSep.abs*constrain_layers
|
209
|
+
zb.atoms.each{|a|
|
210
|
+
if (a.z < constrain_below)
|
211
|
+
a.constrain = ".true."
|
212
|
+
end
|
213
|
+
}
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
zb
|
218
|
+
end
|
219
|
+
|
220
|
+
# return a unit cell for a slab of 112
|
221
|
+
# specify the number of atomic monolayers and the vacuum thickness in angstrom
|
222
|
+
def get_112_surface(monolayers, vacuum=0, constrain_layers = 0)
|
223
|
+
atom1 = Atom.new(0,0,0,self.cation)
|
224
|
+
atom2 = Atom.new(self.lattice_const*sqrt(3)/2, 0, 0, self.anion)
|
225
|
+
|
226
|
+
v1 = Vector[sqrt(3), 0, 0]*self.lattice_const
|
227
|
+
v2 = Vector[0, sqrt(2)/2, 0]*self.lattice_const
|
228
|
+
v3 = Vector[1/sqrt(3), 1/(sqrt(3)*2), -1/(sqrt(3)*2)]*self.lattice_const
|
229
|
+
|
230
|
+
millerX = Vector[1, 1, -2];
|
231
|
+
millerY = Vector[-1, 1, 0];
|
232
|
+
millerZ = Vector[-1, -1, -1]
|
233
|
+
|
234
|
+
# The unit cell
|
235
|
+
zb = Geometry.new([atom1, atom2], [v1, v2, v3])
|
236
|
+
zb.set_miller_indices(millerX, millerY, millerZ)
|
237
|
+
|
238
|
+
# Repeat the unit cell
|
239
|
+
zb = zb.repeat(1,1,monolayers)
|
240
|
+
|
241
|
+
|
242
|
+
if 0 < vacuum
|
243
|
+
# Add vacuum
|
244
|
+
monolayerSep = v3[2]
|
245
|
+
zb.lattice_vectors[2] = Vector[0, 0, (monolayers*monolayerSep).abs + vacuum.to_f]
|
246
|
+
# Move everything into a nice tidy unit cell.
|
247
|
+
zb = zb.correct
|
248
|
+
end
|
249
|
+
|
250
|
+
# # Constrain the bottom 2 layers
|
251
|
+
# zb.atoms.each{|a|
|
252
|
+
# if (a.z < monolayerSep*2)
|
253
|
+
# a.constrain = ".true."
|
254
|
+
# end
|
255
|
+
# }
|
256
|
+
|
257
|
+
|
258
|
+
# Return the completed unit cell
|
259
|
+
return zb
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
# Return a unit cell for a slab of 110
|
264
|
+
# specify the number of atomic monolayers
|
265
|
+
# and the vacuum thickness in angstrom
|
266
|
+
def get_110_surface(monolayers, vacuum=0, constrain_layers = 0)
|
267
|
+
|
268
|
+
# The atoms on a FCC
|
269
|
+
atom1 = Atom.new(0,0,0,self.cation)
|
270
|
+
atom2 = Atom.new(self.lattice_const*1/(2*sqrt(2)), self.lattice_const*0.25, 0.0, self.anion)
|
271
|
+
|
272
|
+
# The lattice Vectors
|
273
|
+
v1 = Vector[1/sqrt(2), 0.0, 0.0]*self.lattice_const
|
274
|
+
v2 = Vector[0.0, 1.0, 0.0]*self.lattice_const
|
275
|
+
v3 = Vector[1/(2*sqrt(2)), -0.5, 1/(2*sqrt(2))]*self.lattice_const
|
276
|
+
|
277
|
+
# The miller indices for each primitive cartesian direction
|
278
|
+
millerX = Vector[1, -1, 0]
|
279
|
+
millerY = Vector[0, 0, 1]
|
280
|
+
millerZ = Vector[1, 1, 0]
|
281
|
+
|
282
|
+
# The unit cell
|
283
|
+
zb = Geometry.new([atom1, atom2], [v1, v2, v3])
|
284
|
+
zb.set_miller_indices(millerX, millerY, millerZ)
|
285
|
+
|
286
|
+
# Repeat the unit cell
|
287
|
+
zb = zb.repeat(1,1,monolayers)
|
288
|
+
|
289
|
+
|
290
|
+
if 0 < vacuum
|
291
|
+
# Add vacuum
|
292
|
+
monolayerSep = v3[2]
|
293
|
+
zb.lattice_vectors[2] = Vector[0, 0, monolayers*monolayerSep.abs + vacuum.to_f]
|
294
|
+
# Move everything into a nice tidy unit cell.
|
295
|
+
zb = zb.correct
|
296
|
+
end
|
297
|
+
|
298
|
+
# # Constrain the bottom layers
|
299
|
+
zb.atoms.each{|a|
|
300
|
+
if (a.z < monolayerSep*constrain_layers)
|
301
|
+
a.constrain = ".true."
|
302
|
+
end
|
303
|
+
}
|
304
|
+
|
305
|
+
|
306
|
+
# Return the completed unit cell
|
307
|
+
return zb
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
end
|