aims 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,46 @@
1
+ module Aims
2
+
3
+ # A representation of a bond between two atoms
4
+ # Bonds are not directional, so two bonds are equal if they bond the same atoms regradless of order
5
+ class Bond
6
+
7
+ attr_accessor :atoms
8
+
9
+ # Initialize a bond between two atoms
10
+ def initialize(atom1, atom2)
11
+ self.atoms = [atom1, atom2]
12
+ end
13
+
14
+ # Two bonds are equal iff the set of atoms
15
+ # in both bonds is the same.
16
+ def ==(bond)
17
+ # Take the difference between the two sets
18
+ diff1 = (self.atoms - bond.atoms)
19
+ diff2 = (bond.atoms - self.atoms)
20
+ # the lists are the same if both sets are empty
21
+ diff1.empty? & diff2.empty?
22
+ end
23
+
24
+ # Two bonds are eql? iff the set of atoms
25
+ # in both bonds is the same.
26
+ def eql?(bond)
27
+ self == bond
28
+ end
29
+
30
+ # Implementation of hash for equality testing
31
+ def hash
32
+ self.atoms.hash
33
+ end
34
+
35
+ # The bond length is the distance between the two atoms
36
+ def length
37
+ self.atoms[0].distance_to(self.atoms[1])
38
+ end
39
+
40
+ # Access the atoms in the bond
41
+ def [](i)
42
+ atoms[i]
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+
2
+ class Cube
3
+
4
+ def parse(io)
5
+ # First two lines are comments
6
+ io.readline
7
+ io.readline
8
+ # Third line is number of atoms and origin
9
+ natoms, origin_x, origin_y, origin_z = io.readline.split.collect{|s| s.to_f}
10
+
11
+ # 4th, 5th, and 6th lines define the voxels
12
+ n1, n1x, n1y, n1z = io.readline.split.collect{|str| str.to_f}
13
+ n2, n2x, n2y, n2z = io.readline.split.collect{|str| str.to_f}
14
+ n3, n3x, n3y, n3z = io.readline.split.collect{|str| str.to_f}
15
+
16
+ # Next the positions of the atoms
17
+ natoms.to_i.times do
18
+ # Read in the atoms
19
+ atomic_number, not_sure, x, y, z = io.readline.split.collect{|str| str.to_f}\
20
+ end
21
+
22
+ vals = Array.new(n1*n2*n3)
23
+ nread = 0
24
+ offset = 0
25
+ io.each_line{|line|
26
+ arr = line.split
27
+ nread = arr.size
28
+ vals[offset...offset+nread] = line.split
29
+ offset = offset+nread
30
+ }
31
+
32
+ # Finally the field
33
+ n1.to_i.times do |i|
34
+ n2.to_i.times do |j|
35
+ n3.to_i.times do |k|
36
+ v = vals.shift.to_f
37
+ x = i*n1x + j*n2x + k*n3x
38
+ y = i*n1y + j*n2y + k*n3y
39
+ z = i*n1z + j*n2z + k*n3z
40
+ puts [x, y, z, v].join(" ")
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,545 @@
1
+ module Aims
2
+
3
+ # Geometry is one of the core classes in the Aims module.
4
+ # It is a container class for Atoms and can optionally include
5
+ # lattice vectors for periodic systems.
6
+ #
7
+ # There are a number of utility methods for manipulating the geometry
8
+ # such as Geometry#rotate, Geometry#copy, Geometry#repeat, and Geometry#displace
9
+ #
10
+ # It is possible to define a collection of clip_planes using add_plane, add_plane_miller, or
11
+ # add_plane_cartesian to selectively hide atoms from the geometry.
12
+ # The planes can be defined in the coordinate system of the geometry or
13
+ # using miller indices if the miller index of the x and y axes have been specified
14
+ # with set_miller_indices.
15
+ #
16
+ # Once the miller indices have been defined, Geometry#align_x can be used to rotate the
17
+ # Geometry about the z-axis and align the x-axis with a given miller index.
18
+ class Geometry
19
+
20
+ include Vectorize
21
+ include Enumerable
22
+
23
+ # A three element array of lattice vectors for periodic systems
24
+ attr_accessor :lattice_vectors
25
+
26
+ # An array of Aims::Bond objects calculated when the Geometry is defined.
27
+ attr_accessor :bonds
28
+
29
+ # clip_planes is an array of planes
30
+ # The planes are defined with respect to the vectors millerX and millerZ
31
+ # which define the miller indices corresponding to the x and z direction
32
+ # Only atoms behind all the planes are visible
33
+ # This can be used to define a solid
34
+ attr_accessor :clip_planes
35
+ protected :clip_planes, :clip_planes=
36
+
37
+ # Rotation matrix for converting vectors in cartesian coordinates
38
+ # (the native coordinate system of the geometry) into vectors in
39
+ # the miller coordinate space. See set_miller_indices
40
+ attr_accessor :cart_to_miller
41
+ protected :cart_to_miller, :cart_to_miller=
42
+
43
+ # Rotation matrix for converting vectors from miller coordiantes
44
+ # to cartesian coordiantes. See set_miller_indices
45
+ attr_accessor :miller_to_cart
46
+ protected :miller_to_cart, :miller_to_cart=
47
+
48
+ # Initialize a Geometry with a list of atoms, and an optional list of lattice vectors
49
+ def initialize(atoms, vectors = nil, dont_make_bonds = false)
50
+
51
+ # Do some basic validation
52
+ unless atoms.is_a? Array
53
+ raise "Atoms must be an array!"
54
+ end
55
+
56
+ if atoms.empty?
57
+ raise "Atoms array is empty!"
58
+ end
59
+
60
+ atoms.each{|a|
61
+ unless a.is_a? Atom
62
+ raise "Atoms array contains invalid object type #{a.class}!"
63
+ end
64
+ }
65
+ # Ok. I'm satisfied
66
+ self.atoms = atoms
67
+
68
+ # Now check the lattice vectors
69
+ if vectors
70
+ self.lattice_vectors = vectors.collect{|v|
71
+ if v.is_a? Vector and v.size == 3
72
+ v
73
+ elsif v.is_a? Array and v.size == 3
74
+ Vector.elements(v)
75
+ else
76
+ raise "Invalid lattice vector"
77
+ end
78
+ }
79
+ unless self.lattice_vectors.size == 3
80
+ raise "There must be 3 lattice vectors, not #{self.lattice_vectors.size}."
81
+ end
82
+ end
83
+
84
+ @clip_planes = []
85
+ make_bonds unless dont_make_bonds
86
+ end
87
+
88
+ # The geometry is empty if there are no atoms.
89
+ def empty?
90
+ @atoms.empty?
91
+ end
92
+
93
+ # Define the atoms in basis of this Unit Cell
94
+ def atoms=(listOfAtoms)
95
+ @atoms = listOfAtoms
96
+ recache_visible_atoms
97
+ end
98
+
99
+ # Return the atoms in this unit cell. By default returns
100
+ # only the visible atoms, but this method will return all of the atoms
101
+ # if called with (visibleOnly = false)
102
+ def atoms(visibleOnly = true)
103
+ if visibleOnly and 0 < @clip_planes.size
104
+ @visibleAtoms
105
+ else
106
+ @atoms
107
+ end
108
+ end
109
+
110
+ # Generate and cache bonds for this geometry.
111
+ # A bond will be generated for every pair of atoms closer than +bond_length+
112
+ def make_bonds(bond_length = 4.0)
113
+ # initialize an empty array
114
+ self.bonds = Array.new
115
+
116
+ # Make bonds between all atoms
117
+ stack = atoms.dup
118
+
119
+ atom1 = stack.pop
120
+ while (not stack.empty?)
121
+ stack.each{|atom2|
122
+ b = Bond.new(atom1, atom2)
123
+ self.bonds << b if b.length < bond_length
124
+ }
125
+ atom1 = stack.pop
126
+ end
127
+ end
128
+
129
+ # Add a clip Plane to the unit cell
130
+ # recache the visible atoms if called with +recache = true+ (the Default)
131
+ def add_plane(aPlane, recache = true)
132
+ self.clip_planes << aPlane
133
+ recache_visible_atoms if recache
134
+ end
135
+
136
+ # Add a clipping plane defined by the outward normal (h,k,l)
137
+ # and a point on the plane (x,y,z)
138
+ # Only points behind the plane will be kept. Points in front
139
+ # of the plane will be invisible. They are not really gone, but
140
+ # can be returned by moving or removing the plane
141
+ def add_plane_miller(h,k,l,x,y,z)
142
+ normal = self.cartesian_from_miller(h, k, l)
143
+ self.add_plane_cartesian(normal[0], normal[1], normal[2], x, y, z)
144
+ end
145
+
146
+ # Add a clipping plane defined by the outward normal (nx,ny,nz)
147
+ # and a point on the plane (x,y,z)
148
+ # Only points behind the plane will be kept. Points in front
149
+ # of the plane will be invisible. They are not really gone, but
150
+ # can be returned by moving or removing the plane
151
+ def add_plane_cartesian(nx, ny, nz, x, y, z)
152
+ add_plane(Plane.new(nx, ny, nz, x, y, z), true)
153
+ end
154
+
155
+ # Clear the array of clip planes
156
+ def clear_planes
157
+ self.clip_planes.clear
158
+ recache_visible_atoms
159
+ end
160
+
161
+ # Remove a specific clip plane
162
+ def remove_plane(aPlane)
163
+ self.clip_planes.reject!{|p| p == aPlane}
164
+ recache_visible_atoms
165
+ end
166
+
167
+ # Define the [h, k, l] vectors for each cartesian direction x, y, and z
168
+ # There must be at least two vectors provided, and they must be orthogonal
169
+ # The z vector, if provided, must also be orthogonal. If it is not
170
+ # provided it will be calculated as the cross product of x and y
171
+ #
172
+ # These vectors will populate a matrix that for calculation of the
173
+ # miller indices for any arbitrary vector in cartesian space, and
174
+ # the calculation of the cartesian space vector for any arbitrary miller index
175
+ # See miller_from_cartesion and cartesian_from_miller for more details.
176
+ #
177
+ # The default miller indices are parallel to the cartesian indices. That is
178
+ # the default x-axes points in (100), y points along (010) and z points along (001)
179
+ #
180
+ def set_miller_indices(x, y, z=nil)
181
+ raise "Vectors must be orthogonal" unless 0 == dot(x,y)
182
+ if z
183
+ raise "Vectors must be orthogonal" unless 0 == dot(x,z)
184
+ else
185
+ z = cross(x, y)
186
+ end
187
+ self.cart_to_miller = Matrix[[x[0], y[0], z[0]],
188
+ [x[1], y[1], z[1]],
189
+ [x[2], y[2], z[2]]]
190
+ self.miller_to_cart = cart_to_miller.inverse
191
+ return nil
192
+ end
193
+
194
+ # Rotate the atoms about the z-axis so that the
195
+ # vector given by the miller index [h,k,l] is aligned
196
+ # with the cartesian unit vector [1,0,0]
197
+ # The vector [h,k,l] must be orthogonal to the defined
198
+ # miller index of the z-direction
199
+ def align_x(h,k,l)
200
+ millerZ = self.cart_to_miller.column(2)
201
+ unless 0 == dot([h,k,l], millerZ)
202
+ raise "Specified vector [#{[h,k,l].join(',')}] is not orthogonal to z-axis [#{millerZ.to_a.join(',')}]"
203
+ end
204
+
205
+ # Define the current x axis and the new x-axis
206
+ millerX = self.cart_to_miller.column(0)
207
+ newX = Vector[h,k,l]
208
+
209
+ # Find the angle between the current x direction and the new x-direction
210
+ angle = acos(dot(newX*(1/newX.r), millerX*(1/millerX.r)))*180/Math::PI
211
+
212
+ #Make the rotation in azimuth
213
+ self.rotate(0, angle)
214
+
215
+ end
216
+
217
+ # Given a vector (x,y,z) in cartesian coordinates,
218
+ # return the miller-index corresponding to that vector's direction
219
+ def miller_from_cartesian(x, y, z)
220
+ if self.cart_to_miller
221
+ self.cart_to_miller*Vector[x, y, z]
222
+ else
223
+ nil
224
+ end
225
+ end
226
+
227
+ # Given a miller index (h,k,l),
228
+ # return a vector in cartesian coordinates pointing in that direction
229
+ def cartesian_from_miller(h, k, l)
230
+
231
+ if self.miller_to_cart
232
+ self.miller_to_cart*Vector[h, k, l]
233
+ else
234
+ nil
235
+ end
236
+ end
237
+
238
+ # Recompute the atoms that are behind all the clip planes
239
+ # Atoms that are in front of any clip-plane are considered invisible.
240
+ def recache_visible_atoms
241
+
242
+ plane_count = (@clip_planes ? @clip_planes.length : 0)
243
+ return if plane_count == 0
244
+
245
+ if @visibleAtoms
246
+ @visibleAtoms.clear
247
+ else
248
+ @visibleAtoms = []
249
+ end
250
+ @atoms.each{|a|
251
+ i = plane_count
252
+ @clip_planes.each{|p|
253
+ i = i-1 if 0 >= p.distance_to_point(a.x, a.y, a.z)
254
+ }
255
+ @visibleAtoms << a if i == 0
256
+ }
257
+
258
+ make_bonds
259
+ end
260
+
261
+ # Rotate the geometry in 3 dimensions. The rotation is
262
+ # first about the x-axis then about the z-axis.
263
+ # * +az+ The azimuthal rotation in degrees about the z-axis
264
+ # * +alt+ The rotation in altitude in degrees about the x-axis
265
+ #
266
+ # Returns a new Aims#Geometry object.
267
+ def rotate(alt, az)
268
+ altrad = Math::PI/180*alt
269
+ azrad = Math::PI/180*az
270
+
271
+ sinalt = Math::sin(altrad)
272
+ cosalt = Math::cos(altrad)
273
+ sinaz = Math::sin(azrad)
274
+ cosaz = Math::cos(azrad)
275
+
276
+ mat1 = Matrix[[cosaz, -sinaz, 0], [sinaz, cosaz, 0], [0, 0, 1]]
277
+ mat2 = Matrix[[1.0, 0.0, 0.0], [0.0, cosalt, -sinalt], [0.0, sinalt, cosalt]]
278
+ mat = mat1*mat2
279
+ newatoms = atoms.collect{|a|
280
+ a.rotate(mat)
281
+ }
282
+ newvectors = lattice_vectors.collect{|v|
283
+ mat*v
284
+ }
285
+ uc = Geometry.new(newatoms, newvectors)
286
+ uc.cart_to_miller = mat*self.cart_to_miller
287
+ uc.miller_to_cart = uc.cart_to_miller.inverse
288
+
289
+ return uc
290
+ end
291
+
292
+ # Return a new Aims#Geometry that is a deep copy of this geometry.
293
+ def copy
294
+ newAtoms = []
295
+ newVecs = []
296
+ @atoms.each{|a|
297
+ newAtoms << a.copy
298
+ }
299
+ @lattice_vectors.each{|v|
300
+ newVecs << v*1
301
+ }
302
+ uc = Geometry.new(newAtoms, newVecs)
303
+ uc.clip_planes = self.clip_planes
304
+ uc.miller_to_cart = self.miller_to_cart
305
+ uc.cart_to_miller = self.cart_to_miller
306
+ uc.recache_visible_atoms
307
+ return uc
308
+ end
309
+
310
+ # Return the number of atoms in this unit cell.
311
+ def size
312
+ atoms.size
313
+ end
314
+
315
+ # Return a two element array contaiing two artificial Aims::Atom objects whose coordinates
316
+ # represent the lower-left and upper-right corners of the Geometry's bounding box.
317
+ def bounding_box(visible_only = true)
318
+ maxX = atoms(visible_only).first.x
319
+ maxY = atoms(visible_only).first.y
320
+ maxZ = atoms(visible_only).first.z
321
+ minX = maxX
322
+ minY = maxY
323
+ minZ = maxZ
324
+
325
+ atoms(visible_only).each{|a|
326
+ if a.x > maxX
327
+ maxX = a.x
328
+ elsif a.x < minX
329
+ minX = a.x
330
+ end
331
+
332
+ if a.y > maxY
333
+ maxY = a.y
334
+ elsif a.y < minY
335
+ minY = a.y
336
+ end
337
+
338
+ if a.z > maxZ
339
+ maxZ = a.z
340
+ elsif a.z < minZ
341
+ minZ = a.z
342
+ end
343
+ }
344
+
345
+ [Atom.new(maxX, maxY, maxZ), Atom.new(minX, minY, minZ)]
346
+ end
347
+
348
+ # Return an Atom whose coordinates are the center of the unit-cell.
349
+ def center(visible_only = true)
350
+ bounds = bounding_box(visible_only)
351
+ x = (bounds[0].x + bounds[1].x)/2.0
352
+ y = (bounds[0].y + bounds[1].y)/2.0
353
+ z = (bounds[0].z + bounds[1].z)/2.0
354
+ return Atom.new(x,y,z)
355
+ end
356
+
357
+ # Yield to each atom in the unit cell
358
+ def each
359
+ self.atoms.each{|a|
360
+ yield a
361
+ }
362
+ end
363
+
364
+ # Remove the specified atom from the unit cell
365
+ def remove_atom(atom)
366
+ atoms.reject!{|a|
367
+ a.id == atom.id
368
+ }
369
+ # Force a rehash of nearest-neighbor tree
370
+ @tree = nil
371
+ end
372
+
373
+ # Return the Atom at the given index
374
+ def [](index)
375
+ atoms[index]
376
+ end
377
+
378
+ # Return a new unit cell with all the atoms displaced by the amount x,y,z
379
+ def displace(x,y,z)
380
+ Geometry.new(atoms.collect{|a|
381
+ a.displace(x,y,z)
382
+ }, self.lattice_vectors)
383
+ #TODO copy miller indices
384
+ end
385
+
386
+ # Repeat a unit cell nx,ny,nz times in the directions
387
+ # of the lattice vectors.
388
+ # Negative values of nx,ny or nz results in displacement in the
389
+ # negative direction of the lattice vectors
390
+ def repeat(nx=1, ny=1, nz=1)
391
+
392
+ raise "Not a periodic system." if self.lattice_vectors.nil?
393
+
394
+ u = self.copy
395
+ v1 = self.lattice_vectors[0]
396
+ v2 = self.lattice_vectors[1]
397
+ v3 = self.lattice_vectors[2]
398
+
399
+ nx_sign = (0 < nx) ? 1 : -1
400
+ ny_sign = (0 < ny) ? 1 : -1
401
+ nz_sign = (0 < nz) ? 1 : -1
402
+
403
+ new_atoms = []
404
+ nx.abs.times do |i|
405
+ ny.abs.times do |j|
406
+ nz.abs.times do |k|
407
+ new_atoms << self.displace(nx_sign*i*v1[0] + ny_sign*j*v2[0] + nz_sign*k*v3[0],
408
+ nx_sign*i*v1[1] + ny_sign*j*v2[1] + nz_sign*k*v3[1],
409
+ nx_sign*i*v1[2] + ny_sign*j*v2[2] + nz_sign*k*v3[2]).atoms
410
+ end
411
+ end
412
+ end
413
+
414
+ u.atoms = new_atoms.flatten
415
+ u.lattice_vectors = [Vector[nx.abs*v1[0], nx.abs*v1[1], nx.abs*v1[2]],
416
+ Vector[ny.abs*v2[0], ny.abs*v2[1], ny.abs*v2[2]],
417
+ Vector[nz.abs*v3[0], nz.abs*v3[1], nz.abs*v3[2]]]
418
+ u.make_bonds
419
+ return u
420
+ end
421
+
422
+ # Concatenate the atoms from another unit cell to this unit cell.
423
+ # Currently does no validation on lattice vectors on miller vectors
424
+ def <<(aGeometry)
425
+ self.atoms.concat(aGeometry.atoms)
426
+ self.make_bonds
427
+ return self
428
+ end
429
+
430
+ # Print all the atoms joined by a newline
431
+ def to_s
432
+ self.atoms.collect{|a| a.to_s}.join("\n")
433
+ end
434
+
435
+ # Return a string formatted in the Aims geometry.in format.
436
+ def format_geometry_in
437
+ output = ""
438
+ if self.lattice_vectors
439
+ output << self.lattice_vectors.collect{|v| "lattice_vector #{v[0]} #{v[1]} #{v[2]}"}.join("\n")
440
+ output << "\n"
441
+ end
442
+ output << self.atoms.collect{|a| a.format_geometry_in}.join("\n")
443
+
444
+ output
445
+ end
446
+
447
+ # return a string in xyz format
448
+ def format_xyz
449
+ output = self.atoms.size.to_s + "\n"
450
+ output << "Aims Geometry \n"
451
+ self.atoms.each{ |a|
452
+ output << [a.species, a.x.to_s, a.y.to_s, a.z.to_s].join("\t") + "\n"
453
+ }
454
+ output
455
+ end
456
+
457
+ # Find the difference between this cell and another cell
458
+ # Return a cell with Pseudo-Atoms whose positions are really the differences
459
+ def delta(aCell)
460
+ raise "Cells do not have the same number of atoms" unless self.atoms.size == aCell.atoms.size
461
+
462
+ pseudo_atoms = []
463
+ self.atoms.size.times {|i|
464
+ a1 = self.atoms[i]
465
+ a2 = aCell.atoms[i]
466
+ raise "Species do not match" unless a1.species == a2.species
467
+ a = Atom.new
468
+ a.species = a1.species
469
+ a.x = a1.x - a2.x
470
+ a.y = a1.y - a2.y
471
+ a.z = a1.z - a2.z
472
+ pseudo_atoms << a
473
+ }
474
+ Geometry.new(pseudo_atoms)
475
+ end
476
+
477
+
478
+ # Move all atoms inside the primitive volume defined by the
479
+ # six planes of the lattice vectors
480
+ def correct
481
+
482
+ # Hash for storing bounding planes and the out-of-plane vector
483
+ # by which each atom will be displaced to move it into the primitive volume
484
+ # key = bounding plane
485
+ # value = out-of-plane lattice vector used to displace atoms
486
+ planes_vecs = {}
487
+
488
+ # Define the primitive volume as six planes by
489
+ # finding the normal to each pair of lattice vectors
490
+ # and making a plane with this normal that includes
491
+ # 1. The point at the head of the third lattice vector
492
+ # and pointing in the positive direction
493
+ #
494
+ # 2. The point at the tail of the third lattice vector
495
+ # and pointing in the negative direction
496
+ #
497
+ (0..2).each do |i|
498
+ out_vector = lattice_vectors[i] # The out of plane vector
499
+ plane_vectors = lattice_vectors.reject{|v| v == out_vector} # The in plane vectors
500
+ norm = cross(plane_vectors[0], plane_vectors[1])
501
+ # if the norm has a component in the direction of the out of plane vector
502
+ # then use the head of the out-of-plane vector as the intersection point
503
+ # otherwise use the tail (the origin)
504
+ if 0 < dot(norm, out_vector)
505
+ # First plane is in direction of norm and intersects head
506
+ # Displace vector is +1 if plane intersects tail and -1 if plane intersects head.
507
+ planes_vecs[Plane.new(norm[0], norm[1], norm[2], out_vector[0], out_vector[1], out_vector[2])] = out_vector*(-1)
508
+ # Second plane is opposite direction of norm and intersects tail
509
+ planes_vecs[Plane.new(-norm[0], -norm[1], -norm[2], 0, 0, 0)] = out_vector*1
510
+ else
511
+ # First plane is in opposite direction of norm and intersects head
512
+ planes_vecs[Plane.new(-norm[0], -norm[1], -norm[2], out_vector[0], out_vector[1], out_vector[2])] = out_vector*(-1)
513
+ # Second plane is in direction of norm and intersects tail
514
+ planes_vecs[Plane.new(norm[0], norm[1], norm[2], 0, 0, 0)] = out_vector*1
515
+ end
516
+ end
517
+
518
+ # Make a coyp of the unit cell
519
+ new_unit_cell = self.copy
520
+
521
+ # Move each atom behind all the planes
522
+ new_unit_cell.atoms(false).each do |atom|
523
+ planes_vecs.each_pair do |p, v|
524
+ if p.distance_to_point(0,0,0) == 0
525
+ # If the plane intersects the origin then
526
+ # move atoms not on the plane (inequality)
527
+ while p.distance_to_point(atom.x, atom.y, atom.z) > 0
528
+ atom.displace!(v[0], v[1], v[2])
529
+ end
530
+ else
531
+ # Move atoms that lie on the plane if the plane doesn't intersect the origin
532
+ while p.distance_to_point(atom.x, atom.y, atom.z) >= 0
533
+ atom.displace!(v[0], v[1], v[2])
534
+ end
535
+ end
536
+ end
537
+ end
538
+ new_unit_cell.atoms.uniq!
539
+ new_unit_cell.make_bonds
540
+ return new_unit_cell
541
+
542
+ end
543
+
544
+ end
545
+ end