Statistiques
| Révision :

root / ase / lattice / spacegroup / spacegroup.py @ 1

Historique | Voir | Annoter | Télécharger (14,86 ko)

1 1 tkerber
# Copyright (C) 2010, Jesper Friis
2 1 tkerber
# (see accompanying license files for details).
3 1 tkerber
4 1 tkerber
"""Definition of the Spacegroup class.
5 1 tkerber

6 1 tkerber
This module only depends on NumPy and the space group database.
7 1 tkerber
"""
8 1 tkerber
9 1 tkerber
import os
10 1 tkerber
import warnings
11 1 tkerber
12 1 tkerber
import numpy as np
13 1 tkerber
14 1 tkerber
__all__ = ['Spacegroup']
15 1 tkerber
16 1 tkerber
17 1 tkerber
class SpacegroupError(Exception):
18 1 tkerber
    """Base exception for the spacegroup module."""
19 1 tkerber
    pass
20 1 tkerber
21 1 tkerber
class SpacegroupNotFoundError(SpacegroupError):
22 1 tkerber
    """Raised when given space group cannot be found in data base."""
23 1 tkerber
    pass
24 1 tkerber
25 1 tkerber
class SpacegroupValueError(SpacegroupError):
26 1 tkerber
    """Raised when arguments have invalid value."""
27 1 tkerber
    pass
28 1 tkerber
29 1 tkerber
30 1 tkerber
class Spacegroup(object):
31 1 tkerber
    """A space group class.
32 1 tkerber

33 1 tkerber
    The instances of Spacegroup describes the symmetry operations for
34 1 tkerber
    the given space group.
35 1 tkerber

36 1 tkerber
    Example:
37 1 tkerber

38 1 tkerber
    >>> from spacegroup import Spacegroup
39 1 tkerber
    >>>
40 1 tkerber
    >>> sg = Spacegroup(225)
41 1 tkerber
    >>> print 'Space group', sg.no, sg.symbol
42 1 tkerber
    Space group 225 F m -3 m
43 1 tkerber
    >>> sg.scaled_primitive_cell
44 1 tkerber
    array([[ 0. ,  0.5,  0.5],
45 1 tkerber
           [ 0.5,  0. ,  0.5],
46 1 tkerber
           [ 0.5,  0.5,  0. ]])
47 1 tkerber
    >>> sites, kinds = sg.equivalent_sites([[0,0,0]])
48 1 tkerber
    >>> sites
49 1 tkerber
    array([[ 0. ,  0. ,  0. ],
50 1 tkerber
           [ 0. ,  0.5,  0.5],
51 1 tkerber
           [ 0.5,  0. ,  0.5],
52 1 tkerber
           [ 0.5,  0.5,  0. ]])
53 1 tkerber
    """
54 1 tkerber
    no = property(
55 1 tkerber
        lambda self: self._no,
56 1 tkerber
        doc='Space group number in International Tables of Crystallography.')
57 1 tkerber
    symbol = property(
58 1 tkerber
        lambda self: self._symbol,
59 1 tkerber
        doc='Hermann-Mauguin (or international) symbol for the space group.')
60 1 tkerber
    setting = property(
61 1 tkerber
        lambda self: self._setting,
62 1 tkerber
        doc='Space group setting. Either one or two.')
63 1 tkerber
    lattice = property(
64 1 tkerber
        lambda self: self._symbol[0],
65 1 tkerber
        doc="""Lattice type:
66 1 tkerber
            P      primitive
67 1 tkerber
            I      body centering, h+k+l=2n
68 1 tkerber
            F      face centering, h,k,l all odd or even
69 1 tkerber
            A,B,C  single face centering, k+l=2n, h+l=2n, h+k=2n
70 1 tkerber
            R      rhombohedral centering, -h+k+l=3n (obverse); h-k+l=3n (reverse)
71 1 tkerber
            """)
72 1 tkerber
    centrosymmetric = property(
73 1 tkerber
        lambda self: self._centrosymmetric,
74 1 tkerber
        doc='Whether a center of symmetry exists.')
75 1 tkerber
    scaled_primitive_cell = property(
76 1 tkerber
        lambda self: self._scaled_primitive_cell,
77 1 tkerber
        doc='Primitive cell in scaled coordinates as a matrix with the '
78 1 tkerber
        'primitive vectors along the rows.')
79 1 tkerber
    reciprocal_cell = property(
80 1 tkerber
        lambda self: self._reciprocal_cell,
81 1 tkerber
        doc='Tree Miller indices that span all kinematically non-forbidden '
82 1 tkerber
        'reflections as a matrix with the Miller indices along the rows.')
83 1 tkerber
    nsubtrans = property(
84 1 tkerber
        lambda self: len(self._subtrans),
85 1 tkerber
        doc='Number of cell-subtranslation vectors.')
86 1 tkerber
    def _get_nsymop(self):
87 1 tkerber
        if self.centrosymmetric:
88 1 tkerber
            return 2 * len(self._rotations) * len(self._subtrans)
89 1 tkerber
        else:
90 1 tkerber
            return len(self._rotations) * len(self._subtrans)
91 1 tkerber
    nsymop = property(_get_nsymop, doc='Total number of symmetry operations.')
92 1 tkerber
    subtrans = property(
93 1 tkerber
        lambda self: self._subtrans,
94 1 tkerber
        doc='Translations vectors belonging to cell-sub-translations.')
95 1 tkerber
    rotations = property(
96 1 tkerber
        lambda self: self._rotations,
97 1 tkerber
        doc='Symmetry rotation matrices. The invertions are not included '
98 1 tkerber
        'for centrosymmetrical crystals.')
99 1 tkerber
    translations = property(
100 1 tkerber
        lambda self: self._translations,
101 1 tkerber
        doc='Symmetry translations. The invertions are not included '
102 1 tkerber
        'for centrosymmetrical crystals.')
103 1 tkerber
104 1 tkerber
    def __init__(self, spacegroup, setting=1, datafile=None):
105 1 tkerber
        """Returns a new Spacegroup instance.
106 1 tkerber

107 1 tkerber
        Parameters:
108 1 tkerber

109 1 tkerber
        spacegroup : int | string | Spacegroup instance
110 1 tkerber
            The space group number in International Tables of
111 1 tkerber
            Crystallography or its Hermann-Mauguin symbol. E.g.
112 1 tkerber
            spacegroup=225 and spacegroup='F m -3 m' are equivalent.
113 1 tkerber
        setting : 1 | 2
114 1 tkerber
            Some space groups have more than one setting. `setting`
115 1 tkerber
            determines Which of these should be used.
116 1 tkerber
        datafile : None | string
117 1 tkerber
            Path to database file. If `None`, the the default database
118 1 tkerber
            will be used.
119 1 tkerber
        """
120 1 tkerber
        if isinstance(spacegroup, Spacegroup):
121 1 tkerber
            for k, v in spacegroup.__dict__.iteritems():
122 1 tkerber
                setattr(self, k, v)
123 1 tkerber
            return
124 1 tkerber
        if not datafile:
125 1 tkerber
            datafile = get_datafile()
126 1 tkerber
        f = open(datafile, 'r')
127 1 tkerber
        try:
128 1 tkerber
            _read_datafile(self, spacegroup, setting, f)
129 1 tkerber
        finally:
130 1 tkerber
            f.close()
131 1 tkerber
132 1 tkerber
    def __repr__(self):
133 1 tkerber
        return 'Spacegroup(%d, setting=%d)'%(self.no, self.setting)
134 1 tkerber
135 1 tkerber
    def __str__(self):
136 1 tkerber
        """Return a string representation of the space group data in
137 1 tkerber
        the same format as found the database."""
138 1 tkerber
        retval = []
139 1 tkerber
        # no, symbol
140 1 tkerber
        retval.append('%-3d   %s\n' % (self.no, self.symbol))
141 1 tkerber
        # setting
142 1 tkerber
        retval.append('  setting %d\n' % (self.setting))
143 1 tkerber
        # centrosymmetric
144 1 tkerber
        retval.append('  centrosymmetric %d\n' % (self.centrosymmetric))
145 1 tkerber
        # primitive vectors
146 1 tkerber
        retval.append('  primitive vectors\n')
147 1 tkerber
        for i in range(3):
148 1 tkerber
            retval.append('   ')
149 1 tkerber
            for j in range(3):
150 1 tkerber
                retval.append(' %13.10f' % (self.scaled_primitive_cell[i, j]))
151 1 tkerber
            retval.append('\n')
152 1 tkerber
        # primitive reciprocal vectors
153 1 tkerber
        retval.append('  reciprocal vectors\n')
154 1 tkerber
        for i in range(3):
155 1 tkerber
            retval.append('   ')
156 1 tkerber
            for j in range(3):
157 1 tkerber
                retval.append(' %3d' % (self.reciprocal_cell[i, j]))
158 1 tkerber
            retval.append('\n')
159 1 tkerber
        # sublattice
160 1 tkerber
        retval.append('  %d subtranslations\n' % self.nsubtrans)
161 1 tkerber
        for i in range(self.nsubtrans):
162 1 tkerber
            retval.append('   ')
163 1 tkerber
            for j in range(3):
164 1 tkerber
                retval.append(' %13.10f' % (self.subtrans[i, j]))
165 1 tkerber
            retval.append('\n')
166 1 tkerber
        # symmetry operations
167 1 tkerber
        nrot = len(self.rotations)
168 1 tkerber
        retval.append('  %d symmetry operations (rot+trans)\n' % nrot)
169 1 tkerber
        for i in range(nrot):
170 1 tkerber
            retval.append(' ')
171 1 tkerber
            for j in range(3):
172 1 tkerber
                retval.append(' ')
173 1 tkerber
                for k in range(3):
174 1 tkerber
                    retval.append(' %2d' % (self.rotations[i, j, k]))
175 1 tkerber
                retval.append('  ')
176 1 tkerber
            for j in range(3):
177 1 tkerber
                retval.append(' %13.10f' % self.translations[i, j])
178 1 tkerber
            retval.append('\n')
179 1 tkerber
        retval.append('\n')
180 1 tkerber
        return ''.join(retval)
181 1 tkerber
182 1 tkerber
    def __eq__(self, other):
183 1 tkerber
        """Chech whether *self* and *other* refer to the same
184 1 tkerber
        spacegroup number and setting."""
185 1 tkerber
        if isinstance(other, int):
186 1 tkerber
            return self.no == other
187 1 tkerber
        elif isinstance(other, str):
188 1 tkerber
            return self.symbol == other
189 1 tkerber
        else:
190 1 tkerber
            return self.no == other.no and self.setting == other.setting
191 1 tkerber
192 1 tkerber
    def __index__(self):
193 1 tkerber
        return self.no
194 1 tkerber
195 1 tkerber
    def get_symop(self):
196 1 tkerber
        """Returns all symmetry operations (including inversions and
197 1 tkerber
        subtranslations) as a sequence of (rotation, translation)
198 1 tkerber
        tuples."""
199 1 tkerber
        symop = []
200 1 tkerber
        parities = [1]
201 1 tkerber
        if self.centrosymmetric:
202 1 tkerber
            parities.append(-1)
203 1 tkerber
        for parity in parities:
204 1 tkerber
            for subtrans in self.subtrans:
205 1 tkerber
                for rot,trans in zip(self.rotations, self.translations):
206 1 tkerber
                    newtrans = np.mod(trans + subtrans, 1)
207 1 tkerber
                    symop.append((parity*rot, newtrans))
208 1 tkerber
        return symop
209 1 tkerber
210 1 tkerber
    def get_rotations(self):
211 1 tkerber
        """Return all rotations, including inversions for
212 1 tkerber
        centrosymmetric crystals."""
213 1 tkerber
        if self.centrosymmetric:
214 1 tkerber
            return np.vstack((self.rotations, -self.rotations))
215 1 tkerber
        else:
216 1 tkerber
            return self.rotations
217 1 tkerber
218 1 tkerber
    def equivalent_reflections(self, hkl):
219 1 tkerber
        """Return all equivalent reflections to the list of Miller indices
220 1 tkerber
        in hkl.
221 1 tkerber

222 1 tkerber
        Example:
223 1 tkerber

224 1 tkerber
        >>> from spacegroup import Spacegroup
225 1 tkerber
        >>> sg = Spacegroup(225)  # fcc
226 1 tkerber
        >>> sg.equivalent_reflections([[0,0,2]])
227 1 tkerber
        array([[ 0,  0, -2],
228 1 tkerber
               [ 0, -2,  0],
229 1 tkerber
               [-2,  0,  0],
230 1 tkerber
               [ 2,  0,  0],
231 1 tkerber
               [ 0,  2,  0],
232 1 tkerber
               [ 0,  0,  2]])
233 1 tkerber
        """
234 1 tkerber
        hkl = np.array(hkl, dtype='int')
235 1 tkerber
        rot = self.get_rotations()
236 1 tkerber
        n,nrot = len(hkl), len(rot)
237 1 tkerber
        R = rot.transpose(0, 2, 1).reshape((3*nrot, 3)).T
238 1 tkerber
        refl = np.dot(hkl, R).reshape((n*nrot, 3))
239 1 tkerber
        ind = np.lexsort(refl.T)
240 1 tkerber
        refl = refl[ind]
241 1 tkerber
        diff = np.diff(refl, axis=0)
242 1 tkerber
        mask = np.any(diff, axis=1)
243 1 tkerber
        return np.vstack((refl[mask], refl[-1,:]))
244 1 tkerber
245 1 tkerber
    def equivalent_sites(self, scaled_positions, ondublicates='error',
246 1 tkerber
                         symprec=1e-3):
247 1 tkerber
        """Returns the scaled positions and all their equivalent sites.
248 1 tkerber

249 1 tkerber
        Parameters:
250 1 tkerber

251 1 tkerber
        scaled_positions: list | array
252 1 tkerber
            List of non-equivalent sites given in unit cell coordinates.
253 1 tkerber
        ondublicates : 'keep' | 'replace' | 'warn' | 'error'
254 1 tkerber
            Action if `scaled_positions` contain symmetry-equivalent
255 1 tkerber
            positions:
256 1 tkerber

257 1 tkerber
            'keep'
258 1 tkerber
               ignore additional symmetry-equivalent positions
259 1 tkerber
            'replace'
260 1 tkerber
                replace
261 1 tkerber
            'warn'
262 1 tkerber
                like 'keep', but issue an UserWarning
263 1 tkerber
            'error'
264 1 tkerber
                raises a SpacegroupValueError
265 1 tkerber

266 1 tkerber
        symprec: float
267 1 tkerber
            Minimum "distance" betweed two sites in scaled coordinates
268 1 tkerber
            before they are counted as the same site.
269 1 tkerber

270 1 tkerber
        Returns:
271 1 tkerber

272 1 tkerber
        sites: array
273 1 tkerber
            A NumPy array of equivalent sites.
274 1 tkerber
        kinds: list
275 1 tkerber
            A list of integer indices specifying which input site is
276 1 tkerber
            equivalent to the corresponding returned site.
277 1 tkerber
        """
278 1 tkerber
        kinds = []
279 1 tkerber
        sites = []
280 1 tkerber
        symprec2 = symprec**2
281 1 tkerber
        for kind, pos in enumerate(scaled_positions):
282 1 tkerber
            for rot, trans in self.get_symop():
283 1 tkerber
                site = np.mod(np.dot(rot, pos) + trans, 1.)
284 1 tkerber
                if not sites:
285 1 tkerber
                    sites.append(site)
286 1 tkerber
                    kinds.append(kind)
287 1 tkerber
                    continue
288 1 tkerber
                t = site - sites
289 1 tkerber
                mask = np.sum(t*t, 1) < symprec2
290 1 tkerber
                if np.any(mask):
291 1 tkerber
                    ind = np.argwhere(mask)[0][0]
292 1 tkerber
                    if kinds[ind] == kind:
293 1 tkerber
                        pass
294 1 tkerber
                    elif ondublicates == 'keep':
295 1 tkerber
                        pass
296 1 tkerber
                    elif ondublicates == 'replace':
297 1 tkerber
                        kinds[ind] = kind
298 1 tkerber
                    elif ondublicates == 'warn':
299 1 tkerber
                        warnings.warn('scaled_positions %d and %d '
300 1 tkerber
                                      'are equivalent'%(kinds[ind], kind))
301 1 tkerber
                    elif ondublicates == 'error':
302 1 tkerber
                        raise SpacegroupValueError(
303 1 tkerber
                            'scaled_positions %d and %d are equivalent'%(
304 1 tkerber
                                kinds[ind], kind))
305 1 tkerber
                    else:
306 1 tkerber
                        raise SpacegroupValueError(
307 1 tkerber
                            'Argument "ondublicates" must be one of: '
308 1 tkerber
                            '"keep", "replace", "warn" or "error".')
309 1 tkerber
                else:
310 1 tkerber
                    sites.append(site)
311 1 tkerber
                    kinds.append(kind)
312 1 tkerber
        return np.array(sites), kinds
313 1 tkerber
314 1 tkerber
315 1 tkerber
316 1 tkerber
317 1 tkerber
def get_datafile():
318 1 tkerber
    """Return default path to datafile."""
319 1 tkerber
    return os.path.join(os.path.dirname(__file__), 'spacegroup.dat')
320 1 tkerber
321 1 tkerber
322 1 tkerber
323 1 tkerber
324 1 tkerber
#-----------------------------------------------------------------
325 1 tkerber
# Functions for parsing the database. They are moved outside the
326 1 tkerber
# Spacegroup class in order to make it easier to later implement
327 1 tkerber
# caching to avoid reading the database each time a new Spacegroup
328 1 tkerber
# instance is created.
329 1 tkerber
#-----------------------------------------------------------------
330 1 tkerber
331 1 tkerber
def _skip_to_blank(f, spacegroup, setting):
332 1 tkerber
    """Read lines from f until a blank line is encountered."""
333 1 tkerber
    while True:
334 1 tkerber
        line = f.readline()
335 1 tkerber
        if not line:
336 1 tkerber
            raise SpacegroupNotFoundError(
337 1 tkerber
                'invalid spacegroup %s, setting %i not found in data base' %
338 1 tkerber
                ( spacegroup, setting ) )
339 1 tkerber
        if not line.strip():
340 1 tkerber
            break
341 1 tkerber
342 1 tkerber
343 1 tkerber
def _skip_to_nonblank(f, spacegroup, setting):
344 1 tkerber
    """Read lines from f until a nonblank line not starting with a
345 1 tkerber
    hash (#) is encountered and returns this and the next line."""
346 1 tkerber
    while True:
347 1 tkerber
        line1 = f.readline()
348 1 tkerber
        if not line1:
349 1 tkerber
            raise SpacegroupNotFoundError(
350 1 tkerber
                'invalid spacegroup %s, setting %i not found in data base' %
351 1 tkerber
                ( spacegroup, setting ) )
352 1 tkerber
        line1.strip()
353 1 tkerber
        if line1 and not line1.startswith('#'):
354 1 tkerber
            line2 = f.readline()
355 1 tkerber
            break
356 1 tkerber
    return line1, line2
357 1 tkerber
358 1 tkerber
359 1 tkerber
def _read_datafile_entry(spg, no, symbol, setting, f):
360 1 tkerber
    """Read space group data from f to spg."""
361 1 tkerber
    spg._no = no
362 1 tkerber
    spg._symbol = symbol.strip()
363 1 tkerber
    spg._setting = setting
364 1 tkerber
    spg._centrosymmetric = bool(int(f.readline().split()[1]))
365 1 tkerber
    # primitive vectors
366 1 tkerber
    f.readline()
367 1 tkerber
    spg._scaled_primitive_cell = np.array([map(float, f.readline().split())
368 1 tkerber
                                       for i in range(3)],
369 1 tkerber
                                      dtype=np.float)
370 1 tkerber
    # primitive reciprocal vectors
371 1 tkerber
    f.readline()
372 1 tkerber
    spg._reciprocal_cell = np.array([map(int, f.readline().split())
373 1 tkerber
                                        for i in range(3)],
374 1 tkerber
                                       dtype=np.int)
375 1 tkerber
    # subtranslations
376 1 tkerber
    spg._nsubtrans = int(f.readline().split()[0])
377 1 tkerber
    spg._subtrans = np.array([map(float, f.readline().split())
378 1 tkerber
                              for i in range(spg._nsubtrans)],
379 1 tkerber
                             dtype=np.float)
380 1 tkerber
    # symmetry operations
381 1 tkerber
    nsym = int(f.readline().split()[0])
382 1 tkerber
    symop = np.array([map(float, f.readline().split()) for i in range(nsym)],
383 1 tkerber
                     dtype=np.float)
384 1 tkerber
    spg._nsymop = nsym
385 1 tkerber
    spg._rotations = np.array(symop[:,:9].reshape((nsym,3,3)), dtype=np.int)
386 1 tkerber
    spg._translations = symop[:,9:]
387 1 tkerber
388 1 tkerber
389 1 tkerber
def _read_datafile(spg, spacegroup, setting, f):
390 1 tkerber
    if isinstance(spacegroup, int):
391 1 tkerber
        pass
392 1 tkerber
    elif isinstance(spacegroup, basestring):
393 1 tkerber
        spacegroup = ' '.join(spacegroup.strip().split())
394 1 tkerber
    else:
395 1 tkerber
        raise SpacegroupValueError('`spacegroup` must be of type int or str')
396 1 tkerber
    while True:
397 1 tkerber
        line1, line2 = _skip_to_nonblank(f, spacegroup, setting)
398 1 tkerber
        _no,_symbol = line1.strip().split(None, 1)
399 1 tkerber
        _setting = int(line2.strip().split()[1])
400 1 tkerber
        _no = int(_no)
401 1 tkerber
        if ((isinstance(spacegroup, int) and _no == spacegroup) or
402 1 tkerber
            (isinstance(spacegroup, basestring) and
403 1 tkerber
             _symbol == spacegroup)) and _setting == setting:
404 1 tkerber
            _read_datafile_entry(spg, _no, _symbol, _setting, f)
405 1 tkerber
            break
406 1 tkerber
        else:
407 1 tkerber
            _skip_to_blank(f, spacegroup, setting)
408 1 tkerber
409 1 tkerber
410 1 tkerber
411 1 tkerber
#-----------------------------------------------------------------
412 1 tkerber
# Self test
413 1 tkerber
if __name__ == '__main__':
414 1 tkerber
415 1 tkerber
    # Import spacegroup in order to ensure that __file__ is defined
416 1 tkerber
    # such that the data base can be found.
417 1 tkerber
    import spacegroup
418 1 tkerber
419 1 tkerber
    import doctest
420 1 tkerber
    print 'doctest: ', doctest.testmod()