Statistiques
| Révision :

root / ase / lattice / bravais.py @ 1

Historique | Voir | Annoter | Télécharger (18,3 ko)

1 1 tkerber
"""Bravais.py - class for generating Bravais lattices etc.
2 1 tkerber

3 1 tkerber
This is a base class for numerous classes setting up pieces of crystal.
4 1 tkerber
"""
5 1 tkerber
6 1 tkerber
import math
7 1 tkerber
import numpy as np
8 1 tkerber
from ase.atoms import Atoms
9 1 tkerber
import ase.data
10 1 tkerber
11 1 tkerber
class Bravais:
12 1 tkerber
    """Bravais lattice factory.
13 1 tkerber

14 1 tkerber
    This is a base class for the objects producing various lattices
15 1 tkerber
    (SC, FCC, ...).
16 1 tkerber
    """
17 1 tkerber
18 1 tkerber
    # The following methods are NOT defined here, but must be defined
19 1 tkerber
    # in classes inhering from Bravais:
20 1 tkerber
    #   get_lattice_constant
21 1 tkerber
    #   make_crystal_basis
22 1 tkerber
    # The following class attributes are NOT defined here, but must be defined
23 1 tkerber
    # in classes inhering from Bravais:
24 1 tkerber
    #   int_basis
25 1 tkerber
    #   inverse_basis
26 1 tkerber
27 1 tkerber
    other = {0:(1,2), 1:(2,0), 2:(0,1)}
28 1 tkerber
29 1 tkerber
    # For Bravais lattices with a basis, set the basis here.  Leave as
30 1 tkerber
    # None if no basis is present.
31 1 tkerber
    bravais_basis = None
32 1 tkerber
33 1 tkerber
    # If more than one type of element appear in the crystal, give the
34 1 tkerber
    # order here.  For example, if two elements appear in a 3:1 ratio,
35 1 tkerber
    # bravais_basis could contain four vectors, and element_basis
36 1 tkerber
    # could be (0,0,1,0) - the third atom in the basis is different
37 1 tkerber
    # from the other three.  Leave as None if all atoms are of the
38 1 tkerber
    # same type.
39 1 tkerber
    element_basis = None
40 1 tkerber
41 1 tkerber
    # How small numbers should be considered zero in the unit cell?
42 1 tkerber
    chop_tolerance = 1e-10
43 1 tkerber
44 1 tkerber
    def __call__(self, symbol,
45 1 tkerber
                 directions=(None,None,None), miller=(None,None,None),
46 1 tkerber
                 size=(1,1,1), latticeconstant=None,
47 1 tkerber
                 pbc=True, align=True, debug=0):
48 1 tkerber
        "Create a lattice."
49 1 tkerber
        self.size = size
50 1 tkerber
        self.pbc = pbc
51 1 tkerber
        self.debug = debug
52 1 tkerber
        self.process_element(symbol)
53 1 tkerber
        self.find_directions(directions, miller)
54 1 tkerber
        if self.debug:
55 1 tkerber
            self.print_directions_and_miller()
56 1 tkerber
        self.convert_to_natural_basis()
57 1 tkerber
        if self.debug >= 2:
58 1 tkerber
            self.print_directions_and_miller(" (natural basis)")
59 1 tkerber
        if latticeconstant is None:
60 1 tkerber
            if self.element_basis is None:
61 1 tkerber
                self.latticeconstant = self.get_lattice_constant()
62 1 tkerber
            else:
63 1 tkerber
                raise ValueError,\
64 1 tkerber
                      "A lattice constant must be specified for a compound"
65 1 tkerber
        else:
66 1 tkerber
            self.latticeconstant = latticeconstant
67 1 tkerber
        if self.debug:
68 1 tkerber
            print "Expected number of atoms in unit cell:", self.calc_num_atoms()
69 1 tkerber
        if self.debug >= 2:
70 1 tkerber
            print "Bravais lattice basis:", self.bravais_basis
71 1 tkerber
            if self.bravais_basis is not None:
72 1 tkerber
                print " ... in natural basis:", self.natural_bravais_basis
73 1 tkerber
        self.make_crystal_basis()
74 1 tkerber
        self.make_unit_cell()
75 1 tkerber
        if align:
76 1 tkerber
            self.align()
77 1 tkerber
        return self.make_list_of_atoms()
78 1 tkerber
79 1 tkerber
    def align(self):
80 1 tkerber
        "Align the first axis along x-axis and the second in the x-y plane."
81 1 tkerber
        degree = 180/np.pi
82 1 tkerber
        if self.debug >= 2:
83 1 tkerber
            print "Basis before alignment:"
84 1 tkerber
            print self.basis
85 1 tkerber
        if self.basis[0][0]**2 + self.basis[0][2]**2 < 0.01 * self.basis[0][1]**2:
86 1 tkerber
            # First basis vector along y axis - rotate 90 deg along z
87 1 tkerber
            t = np.array([[0, -1, 0],
88 1 tkerber
                          [1, 0, 0],
89 1 tkerber
                          [0, 0, 1]], np.float)
90 1 tkerber
            self.basis = np.dot(self.basis, t)
91 1 tkerber
            transf = t
92 1 tkerber
            if self.debug >= 2:
93 1 tkerber
                print "Rotating -90 degrees around z axis for numerical stability."
94 1 tkerber
                print self.basis
95 1 tkerber
        else:
96 1 tkerber
            transf = np.identity(3, np.float)
97 1 tkerber
        assert abs(np.linalg.det(transf) - 1) < 1e-6
98 1 tkerber
        # Rotate first basis vector into xy plane
99 1 tkerber
        theta = math.atan2(self.basis[0,2], self.basis[0,0])
100 1 tkerber
        t = np.array([[np.cos(theta), 0, -np.sin(theta)],
101 1 tkerber
                      [         0, 1, 0          ],
102 1 tkerber
                      [np.sin(theta), 0, np.cos(theta) ]])
103 1 tkerber
        self.basis = np.dot(self.basis, t)
104 1 tkerber
        transf = np.dot(transf, t)
105 1 tkerber
        if self.debug >= 2:
106 1 tkerber
            print "Rotating %f degrees around y axis." % (-theta*degree,)
107 1 tkerber
            print self.basis
108 1 tkerber
        assert abs(np.linalg.det(transf) - 1) < 1e-6
109 1 tkerber
        # Rotate first basis vector to point along x axis
110 1 tkerber
        theta = math.atan2(self.basis[0,1], self.basis[0,0])
111 1 tkerber
        t = np.array([[np.cos(theta), -np.sin(theta), 0],
112 1 tkerber
                      [np.sin(theta),  np.cos(theta), 0],
113 1 tkerber
                      [         0,           0, 1]])
114 1 tkerber
        self.basis = np.dot(self.basis, t)
115 1 tkerber
        transf = np.dot(transf, t)
116 1 tkerber
        if self.debug >= 2:
117 1 tkerber
            print "Rotating %f degrees around z axis." % (-theta*degree,)
118 1 tkerber
            print self.basis
119 1 tkerber
        assert abs(np.linalg.det(transf) - 1) < 1e-6
120 1 tkerber
        # Rotate second basis vector into xy plane
121 1 tkerber
        theta = math.atan2(self.basis[1,2], self.basis[1,1])
122 1 tkerber
        t = np.array([[1, 0, 0],
123 1 tkerber
                      [0, np.cos(theta), -np.sin(theta)],
124 1 tkerber
                      [0, np.sin(theta),  np.cos(theta)]])
125 1 tkerber
        self.basis = np.dot(self.basis, t)
126 1 tkerber
        transf = np.dot(transf, t)
127 1 tkerber
        if self.debug >= 2:
128 1 tkerber
            print "Rotating %f degrees around x axis." % (-theta*degree,)
129 1 tkerber
            print self.basis
130 1 tkerber
        assert abs(np.linalg.det(transf) - 1) < 1e-6
131 1 tkerber
        # Now we better rotate the atoms as well
132 1 tkerber
        self.atoms = np.dot(self.atoms, transf)
133 1 tkerber
        # ... and rotate miller_basis
134 1 tkerber
        self.miller_basis = np.dot(self.miller_basis, transf)
135 1 tkerber
136 1 tkerber
    def make_list_of_atoms(self):
137 1 tkerber
        "Repeat the unit cell."
138 1 tkerber
        nrep = self.size[0] * self.size[1] * self.size[2]
139 1 tkerber
        if nrep <= 0:
140 1 tkerber
            raise ValueError, "Cannot create a non-positive number of unit cells"
141 1 tkerber
        # Now the unit cells must be merged.
142 1 tkerber
        a2 = []
143 1 tkerber
        e2 = []
144 1 tkerber
        for i in xrange(self.size[0]):
145 1 tkerber
            offset = self.basis[0] * i
146 1 tkerber
            a2.append(self.atoms + offset[np.newaxis,:])
147 1 tkerber
            e2.append(self.elements)
148 1 tkerber
        atoms = np.concatenate(a2)
149 1 tkerber
        elements = np.concatenate(e2)
150 1 tkerber
        a2 = []
151 1 tkerber
        e2 = []
152 1 tkerber
        for j in xrange(self.size[1]):
153 1 tkerber
            offset = self.basis[1] * j
154 1 tkerber
            a2.append(atoms + offset[np.newaxis,:])
155 1 tkerber
            e2.append(elements)
156 1 tkerber
        atoms = np.concatenate(a2)
157 1 tkerber
        elements = np.concatenate(e2)
158 1 tkerber
        a2 = []
159 1 tkerber
        e2 = []
160 1 tkerber
        for k in xrange(self.size[2]):
161 1 tkerber
            offset = self.basis[2] * k
162 1 tkerber
            a2.append(atoms + offset[np.newaxis,:])
163 1 tkerber
            e2.append(elements)
164 1 tkerber
        atoms = np.concatenate(a2)
165 1 tkerber
        elements = np.concatenate(e2)
166 1 tkerber
        del a2, e2
167 1 tkerber
        assert len(atoms) == nrep * len(self.atoms)
168 1 tkerber
        basis = np.array([[self.size[0],0,0],
169 1 tkerber
                          [0,self.size[1],0],
170 1 tkerber
                          [0,0,self.size[2]]])
171 1 tkerber
        basis = np.dot(basis, self.basis)
172 1 tkerber
173 1 tkerber
        # Tiny elements should be replaced by zero.  The cutoff is
174 1 tkerber
        # determined by chop_tolerance which is a class attribute.
175 1 tkerber
        basis = np.where(np.abs(basis) < self.chop_tolerance,
176 1 tkerber
                         0.0, basis)
177 1 tkerber
178 1 tkerber
        # None should be replaced, and memory should be freed.
179 1 tkerber
        lattice = Lattice(positions=atoms, cell=basis, numbers=elements,
180 1 tkerber
                          pbc=self.pbc)
181 1 tkerber
        lattice.millerbasis = self.miller_basis
182 1 tkerber
        # Add info for lattice.surface.AddAdsorbate
183 1 tkerber
        lattice._addsorbate_info_size = np.array(self.size[:2])
184 1 tkerber
        return lattice
185 1 tkerber
186 1 tkerber
    def process_element(self, element):
187 1 tkerber
        "Extract atomic number from element"
188 1 tkerber
        # The types that can be elements: integers and strings
189 1 tkerber
        if self.element_basis is None:
190 1 tkerber
            if isinstance(element, type("string")):
191 1 tkerber
                self.atomicnumber = ase.data.atomic_numbers[element]
192 1 tkerber
            elif isinstance(element, int):
193 1 tkerber
                self.atomicnumber = element
194 1 tkerber
            else:
195 1 tkerber
                raise TypeError("The symbol argument must be a string or an atomic number.")
196 1 tkerber
        else:
197 1 tkerber
            atomicnumber = []
198 1 tkerber
            try:
199 1 tkerber
                if len(element) != max(self.element_basis) + 1:
200 1 tkerber
                    oops = True
201 1 tkerber
                else:
202 1 tkerber
                    oops = False
203 1 tkerber
            except TypeError:
204 1 tkerber
                oops = True
205 1 tkerber
            if oops:
206 1 tkerber
                raise TypeError(
207 1 tkerber
                    ("The symbol argument must be a sequence of length %d"
208 1 tkerber
                         +" (one for each kind of lattice position")
209 1 tkerber
                        % (max(self.element_basis)+1,))
210 1 tkerber
            for e in element:
211 1 tkerber
                if isinstance(e, type("string")):
212 1 tkerber
                    atomicnumber.append(ase.data.atomic_numbers[e])
213 1 tkerber
                elif isinstance(element, int):
214 1 tkerber
                    atomicnumber.append(e)
215 1 tkerber
                else:
216 1 tkerber
                    raise TypeError("The symbols argument must be a sequence of strings or atomic numbers.")
217 1 tkerber
            self.atomicnumber = [atomicnumber[i] for i in self.element_basis]
218 1 tkerber
            assert len(self.atomicnumber) == len(self.bravais_basis)
219 1 tkerber
220 1 tkerber
    def convert_to_natural_basis(self):
221 1 tkerber
        "Convert directions and miller indices to the natural basis."
222 1 tkerber
        self.directions = np.dot(self.directions, self.inverse_basis)
223 1 tkerber
        if self.bravais_basis is not None:
224 1 tkerber
            self.natural_bravais_basis = np.dot(self.bravais_basis,
225 1 tkerber
                                                        self.inverse_basis)
226 1 tkerber
        for i in (0,1,2):
227 1 tkerber
            self.directions[i] = reduceindex(self.directions[i])
228 1 tkerber
        for i in (0,1,2):
229 1 tkerber
            (j,k) = self.other[i]
230 1 tkerber
            self.miller[i] = reduceindex(self.handedness *
231 1 tkerber
                                         cross(self.directions[j],
232 1 tkerber
                                                self.directions[k]))
233 1 tkerber
234 1 tkerber
    def calc_num_atoms(self):
235 1 tkerber
        v = int(round(abs(np.linalg.det(self.directions))))
236 1 tkerber
        if self.bravais_basis is None:
237 1 tkerber
            return v
238 1 tkerber
        else:
239 1 tkerber
            return v * len(self.bravais_basis)
240 1 tkerber
241 1 tkerber
    def make_unit_cell(self):
242 1 tkerber
        "Make the unit cell."
243 1 tkerber
        # Make three loops, and find the positions in the integral
244 1 tkerber
        # lattice.  Each time a position is found, the atom is placed
245 1 tkerber
        # in the real unit cell by put_atom().
246 1 tkerber
        self.natoms = self.calc_num_atoms()
247 1 tkerber
        self.nput = 0
248 1 tkerber
        self.atoms = np.zeros((self.natoms,3), np.float)
249 1 tkerber
        self.elements = np.zeros(self.natoms, np.int)
250 1 tkerber
        self.farpoint = farpoint = sum(self.directions)
251 1 tkerber
        #printprogress = self.debug and (len(self.atoms) > 250)
252 1 tkerber
        percent = 0
253 1 tkerber
        # Find the radius of the sphere containing the whole system
254 1 tkerber
        sqrad = 0
255 1 tkerber
        for i in (0,1):
256 1 tkerber
            for j in (0,1):
257 1 tkerber
                for k in (0,1):
258 1 tkerber
                    vect = (i * self.directions[0] +
259 1 tkerber
                            j * self.directions[1] +
260 1 tkerber
                            k * self.directions[2])
261 1 tkerber
                    if np.dot(vect,vect) > sqrad:
262 1 tkerber
                        sqrad = np.dot(vect,vect)
263 1 tkerber
        del i,j,k
264 1 tkerber
        # Loop along first crystal axis (i)
265 1 tkerber
        for (istart, istep) in ((0,1), (-1,-1)):
266 1 tkerber
            i = istart
267 1 tkerber
            icont = True
268 1 tkerber
            while icont:
269 1 tkerber
                nj = 0
270 1 tkerber
                for (jstart, jstep) in ((0,1), (-1,-1)):
271 1 tkerber
                    j = jstart
272 1 tkerber
                    jcont = True
273 1 tkerber
                    while jcont:
274 1 tkerber
                        nk = 0
275 1 tkerber
                        for (kstart, kstep) in ((0,1), (-1,-1)):
276 1 tkerber
                            k = kstart
277 1 tkerber
                            #print "Starting line i=%d, j=%d, k=%d, step=(%d,%d,%d)" % (i,j,k,istep,jstep,kstep)
278 1 tkerber
                            kcont = True
279 1 tkerber
                            while kcont:
280 1 tkerber
                                # Now (i,j,k) loops over Z^3, except that
281 1 tkerber
                                # the loops can be cut off when we get outside
282 1 tkerber
                                # the unit cell.
283 1 tkerber
                                point = np.array((i,j,k))
284 1 tkerber
                                if self.inside(point):
285 1 tkerber
                                    self.put_atom(point)
286 1 tkerber
                                    nk += 1
287 1 tkerber
                                    nj += 1
288 1 tkerber
                                # Is k too high?
289 1 tkerber
                                if np.dot(point,point) > sqrad:
290 1 tkerber
                                    assert not self.inside(point)
291 1 tkerber
                                    kcont = False
292 1 tkerber
                                k += kstep
293 1 tkerber
                        # Is j too high?
294 1 tkerber
                        if i*i+j*j > sqrad:
295 1 tkerber
                            jcont = False
296 1 tkerber
                        j += jstep
297 1 tkerber
                # Is i too high?
298 1 tkerber
                if i*i > sqrad:
299 1 tkerber
                    icont = False
300 1 tkerber
                i += istep
301 1 tkerber
                #if printprogress:
302 1 tkerber
                #    perce = int(100*self.nput / len(self.atoms))
303 1 tkerber
                #    if perce > percent + 10:
304 1 tkerber
                #        print ("%d%%" % perce),
305 1 tkerber
                #        percent = perce
306 1 tkerber
        assert(self.nput == self.natoms)
307 1 tkerber
308 1 tkerber
    def inside(self, point):
309 1 tkerber
        "Is a point inside the unit cell?"
310 1 tkerber
        return (np.dot(self.miller[0], point) >= 0 and
311 1 tkerber
                np.dot(self.miller[0], point - self.farpoint) < 0 and
312 1 tkerber
                np.dot(self.miller[1], point) >= 0 and
313 1 tkerber
                np.dot(self.miller[1], point - self.farpoint) < 0 and
314 1 tkerber
                np.dot(self.miller[2], point) >= 0 and
315 1 tkerber
                np.dot(self.miller[2], point - self.farpoint) < 0)
316 1 tkerber
317 1 tkerber
    def put_atom(self, point):
318 1 tkerber
        "Place an atom given its integer coordinates."
319 1 tkerber
        if self.bravais_basis is None:
320 1 tkerber
            # No basis - just place a single atom
321 1 tkerber
            pos = np.dot(point, self.crystal_basis)
322 1 tkerber
            if self.debug >= 2:
323 1 tkerber
                print ("Placing an atom at (%d,%d,%d) ~ (%.3f, %.3f, %.3f)."
324 1 tkerber
                       % (tuple(point) + tuple(pos)))
325 1 tkerber
            self.atoms[self.nput] = pos
326 1 tkerber
            self.elements[self.nput] = self.atomicnumber
327 1 tkerber
            self.nput += 1
328 1 tkerber
        else:
329 1 tkerber
            for i, offset in enumerate(self.natural_bravais_basis):
330 1 tkerber
                pos = np.dot(point + offset, self.crystal_basis)
331 1 tkerber
                if self.debug >= 2:
332 1 tkerber
                    print ("Placing an atom at (%d+%f, %d+%f, %d+%f) ~ (%.3f, %.3f, %.3f)."
333 1 tkerber
                           % (point[0], offset[0], point[1], offset[1],
334 1 tkerber
                              point[2], offset[2], pos[0], pos[1], pos[2]))
335 1 tkerber
                self.atoms[self.nput] = pos
336 1 tkerber
                if self.element_basis is None:
337 1 tkerber
                    self.elements[self.nput] = self.atomicnumber
338 1 tkerber
                else:
339 1 tkerber
                    self.elements[self.nput] = self.atomicnumber[i]
340 1 tkerber
                self.nput += 1
341 1 tkerber
342 1 tkerber
    def find_directions(self, directions, miller):
343 1 tkerber
        "Find missing directions and miller indices from the specified ones."
344 1 tkerber
        directions = list(directions)
345 1 tkerber
        miller = list(miller)
346 1 tkerber
        # If no directions etc are specified, use a sensible default.
347 1 tkerber
        if directions == [None, None, None] and miller == [None, None, None]:
348 1 tkerber
            directions = [[1,0,0], [0,1,0], [0,0,1]]
349 1 tkerber
        # Now fill in missing directions and miller indices.  This is an
350 1 tkerber
        # iterative process.
351 1 tkerber
        change = 1
352 1 tkerber
        while change:
353 1 tkerber
            change = False
354 1 tkerber
            missing = 0
355 1 tkerber
            for i in (0,1,2):
356 1 tkerber
                (j,k) = self.other[i]
357 1 tkerber
                if directions[i] is None:
358 1 tkerber
                    missing += 1
359 1 tkerber
                    if miller[j] is not None and miller[k] is not None:
360 1 tkerber
                        directions[i] = reduceindex(cross(miller[j],
361 1 tkerber
                                                          miller[k]))
362 1 tkerber
                        change = True
363 1 tkerber
                        if self.debug >= 2:
364 1 tkerber
                            print "Calculating directions[%d] from miller indices" % i
365 1 tkerber
                if miller[i] is None:
366 1 tkerber
                    missing += 1
367 1 tkerber
                    if directions[j] is not None and directions[k] is not None:
368 1 tkerber
                        miller[i] = reduceindex(cross(directions[j],
369 1 tkerber
                                                      directions[k]))
370 1 tkerber
                        change = True
371 1 tkerber
                        if self.debug >= 2:
372 1 tkerber
                            print "Calculating miller[%d] from directions" % i
373 1 tkerber
        if missing:
374 1 tkerber
            raise ValueError, "Specification of directions and miller indices is incomplete."
375 1 tkerber
        # Make sure that everything is Numeric arrays
376 1 tkerber
        self.directions = np.array(directions)
377 1 tkerber
        self.miller = np.array(miller)
378 1 tkerber
        # Check for left-handed coordinate system
379 1 tkerber
        if np.linalg.det(self.directions) < 0:
380 1 tkerber
            print "WARNING: Creating a left-handed coordinate system!"
381 1 tkerber
            self.miller = -self.miller
382 1 tkerber
            self.handedness = -1
383 1 tkerber
        else:
384 1 tkerber
            self.handedness = 1
385 1 tkerber
        # Now check for consistency
386 1 tkerber
        for i in (0,1,2):
387 1 tkerber
            (j,k) = self.other[i]
388 1 tkerber
            m = reduceindex(self.handedness *
389 1 tkerber
                            cross(self.directions[j], self.directions[k]))
390 1 tkerber
            if sum(np.not_equal(m, self.miller[i])):
391 1 tkerber
                print "ERROR: Miller index %s is inconsisten with directions %d and %d" % (i,j,k)
392 1 tkerber
                print "Miller indices:"
393 1 tkerber
                print str(self.miller)
394 1 tkerber
                print "Directions:"
395 1 tkerber
                print str(self.directions)
396 1 tkerber
                raise ValueError, "Inconsistent specification of miller indices and directions."
397 1 tkerber
398 1 tkerber
    def print_directions_and_miller(self, txt=""):
399 1 tkerber
        "Print direction vectors and Miller indices."
400 1 tkerber
        print "Direction vectors of unit cell%s:" % (txt,)
401 1 tkerber
        for i in (0,1,2):
402 1 tkerber
            print "   ", self.directions[i]
403 1 tkerber
        print "Miller indices of surfaces%s:" % (txt,)
404 1 tkerber
        for i in (0,1,2):
405 1 tkerber
            print "   ", self.miller[i]
406 1 tkerber
407 1 tkerber
class MillerInfo:
408 1 tkerber
    """Mixin class to provide information about Miller indices."""
409 1 tkerber
    def miller_to_direction(self, miller):
410 1 tkerber
        """Returns the direction corresponding to a given Miller index."""
411 1 tkerber
        return np.dot(miller, self.millerbasis)
412 1 tkerber
413 1 tkerber
class Lattice(Atoms, MillerInfo):
414 1 tkerber
    """List of atoms initially containing a regular lattice of atoms.
415 1 tkerber

416 1 tkerber
    A part from the usual list of atoms methods this list of atoms type
417 1 tkerber
    also has a method, `miller_to_direction`, used to convert from Miller
418 1 tkerber
    indices to directions in the coordinate system of the lattice.
419 1 tkerber
    """
420 1 tkerber
    pass
421 1 tkerber
422 1 tkerber
# Helper functions
423 1 tkerber
def cross(a, b):
424 1 tkerber
    """The cross product of two vectors."""
425 1 tkerber
    return np.array((a[1]*b[2] - b[1]*a[2],
426 1 tkerber
                     a[2]*b[0] - b[2]*a[0],
427 1 tkerber
                     a[0]*b[1] - b[0]*a[1]))
428 1 tkerber
429 1 tkerber
def gcd(a,b):
430 1 tkerber
    """Greatest Common Divisor of a and b."""
431 1 tkerber
    while a != 0:
432 1 tkerber
        a,b = b%a,a
433 1 tkerber
    return b
434 1 tkerber
435 1 tkerber
def reduceindex(M):
436 1 tkerber
    "Reduce Miller index to the lowest equivalent integers."
437 1 tkerber
    oldM = M
438 1 tkerber
    g = gcd(M[0], M[1])
439 1 tkerber
    h = gcd(g, M[2])
440 1 tkerber
    while h != 1:
441 1 tkerber
        M = M/h
442 1 tkerber
        g = gcd(M[0], M[1])
443 1 tkerber
        h = gcd(g, M[2])
444 1 tkerber
    if np.dot(oldM, M) > 0:
445 1 tkerber
        return M
446 1 tkerber
    else:
447 1 tkerber
        return -M