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.
@@ -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