Passed
Push — master ( 3151ff...9779af )
by Daniel
08:02
created

amd.periodicset.lattice_cubic()   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nop 2
1
"""Implements the :class:`PeriodicSet` class representing a periodic set,
2
defined by a motif and unit cell. This models a crystal with a point at the
3
center of each atom.
4
5
This is the type yielded by the readers :class:`amd.CifReader <.io.CifReader>` 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
6
and :class:`amd.CSDReader <.io.CSDReader>`. A :class:`PeriodicSet` can be passed 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
7
as the first argument to :func:`amd.AMD() <.calculate.AMD>` or 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
8
:func:`amd.PDD() <.calculate.PDD>` to calculate its invariants.
9
"""
10
11
from typing import Optional
12
import numpy as np
13
14
from .utils import (
15
    cellpar_to_cell,
16
    cellpar_to_cell_2D,
17
    cell_to_cellpar,
18
    cell_to_cellpar_2D,
19
    random_cell,
20
)
21
22
23
class PeriodicSet:
24
    """A periodic set is the mathematical representation of a crystal by putting a
25
    single point in the center of every atom. It is defined by a basis (unit cell) 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
26
    and collection of points (motif) which repeats according to the basis.
27
28
    :class:`PeriodicSet` s are returned by the readers in the :mod:`.io` module.
29
    They can be passed to :func:`amd.AMD() <.calculate.AMD>` or 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
30
    :func:`amd.PDD() <.calculate.PDD>` to calculate the invariants.
31
32
    Parameters
33
    ----------
34
    motif : :class:`numpy.ndarray`
35
        Cartesian (orthogonal) coordinates of the motif, shape (no points, dims).
36
    cell : :class:`numpy.ndarray`
37
        Cartesian (orthogonal) square array representing the unit cell, shape (dims, dims).
38
        Use :func:`amd.cellpar_to_cell <.utils.cellpar_to_cell>` to convert 6 cell 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
39
        parameters to an orthogonal square matrix.
40
    name : str, optional
41
        Name of the periodic set.
42
    asymmetric_unit : :class:`numpy.ndarray`, optional
43
        Indices for the asymmetric unit, pointing to the motif.
44
    wyckoff_multiplicities : :class:`numpy.ndarray`, optional
45
        Wyckoff multiplicities of each atom in the asymmetric unit
46
        (number of unique sites generated under all symmetries).
47
    types : :class:`numpy.ndarray`, optional
48
        Array of atomic numbers of motif points.
49
    """
50
51
    def __init__(
0 ignored issues
show
best-practice introduced by
Too many arguments (7/5)
Loading history...
52
            self,
53
            motif: np.ndarray,
54
            cell: np.ndarray,
55
            name: Optional[str] = None,
56
            asymmetric_unit: Optional[np.ndarray] = None,
57
            wyckoff_multiplicities: Optional[np.ndarray] = None,
58
            types : Optional[np.ndarray] = None
0 ignored issues
show
Coding Style introduced by
No space allowed before :
Loading history...
59
    ):
60
61
        self.motif = motif
62
        self.cell = cell
63
        self.name = name
64
        self.asymmetric_unit = asymmetric_unit
65
        self.wyckoff_multiplicities = wyckoff_multiplicities
66
        self.types = types
67
        self.ndim = self.cell.shape[0]
68
69
    def __str__(self):
70
        return repr(self)
71
72
    def __repr__(self):
73
74
        if self.asymmetric_unit is None:
75
            n_asym_sites = len(self.motif)
76
        else:
77
            n_asym_sites = len(self.asymmetric_unit)
78
79
        s = f'PeriodicSet(name={self.name}, ' \
80
            f'motif {self.motif.shape}, ' \
81
            f'{n_asym_sites} asym sites'
82
83
        if self.cell.shape[0] == 2:
84
            cellpar = np.round(cell_to_cellpar_2D(self.cell), 2)
85
            cell_str = f'a={cellpar[0]}, b={cellpar[1]}, α={cellpar[2]}'
86
            s += ', ' + cell_str
87
        elif self.cell.shape[0] == 3:
88
            cellpar = np.round(cell_to_cellpar(self.cell), 2)
89
            cell_str = f'a={cellpar[0]}, b={cellpar[1]}, c={cellpar[2]}, ' \
90
                       f'α={cellpar[3]}, β={cellpar[4]}, γ={cellpar[5]}'
91
            s += ', ' + cell_str
92
93
        s += ')'
94
95
        return s
96
 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
97
    # used for debugging, checks if the motif/cell agree point for point
98
    # (disregarding order), NOT up to isometry.
99
    def __eq__(self, other):
100
101
        if self.cell.shape != other.cell.shape or self.motif.shape != other.motif.shape:
102
            return False
103
104
        if not np.allclose(self.cell, other.cell):
105
            return False
106
107
        # needs fixing, currently only for tests/debugging.
108
        # doesn't even check if motifs are alike because pbcs may make them look different
109
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
110
        # m1 = np.mod(self.motif @ np.linalg.inv(self.cell), 1)
111
        # m2 = np.mod(other.motif @ np.linalg.inv(other.cell), 1)
112
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
113
        # diffs = np.amax(np.abs(m2[:, None] - m1), axis=-1)
114
        # if not np.all((np.amin(diffs, axis=0) <= 1e-8) | (np.amin(diffs, axis=-1) <= 1e-8)):
115
        #     return False
116
117
        # diffs = np.amax(np.abs(other.motif[:, None] - self.motif), axis=-1)
118
        # if not np.all((np.amin(diffs, axis=0) <= 1e-6) | (np.amin(diffs, axis=-1) <= 1e-6)):
119
        #     return False
120
121
        return True
122
123
    def __ne__(self, other):
124
        return not self.__eq__(other)
125
126
127
128
    @staticmethod
129
    def cubic(scale=1, dims=3):
130
        """Return a :class:`PeriodicSet` representing a cubic lattice."""
131
        cell = np.identity(dims) * scale
132
        return PeriodicSet(np.zeros((1, dims)), cell)
133
134
    @staticmethod
135
    def hexagonal(scale=1, dims=3):
136
        """Dimensions 2 and 3 only. Return a :class:`PeriodicSet` representing a
137
        hexagonal lattice."""
138
        if dims == 3:
139
            cell = cellpar_to_cell(scale, scale, scale, 90, 90, 120)
140
        elif dims == 2:
141
            cell = cellpar_to_cell_2D(scale, scale, 60) 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
142
        else:
143
            msg = f'hexagonal lattice only implemented for dimensions 2 and 3 (passed {dims})'
144
            raise NotImplementedError(msg)
145
146
        return PeriodicSet(np.zeros((1, dims)), cell)
147
148
    @staticmethod
149
    def _random(n_points, length_bounds=(1, 2), angle_bounds=(60, 120), dims=3):
150
        """Dimensions 2 and 3 only. Return a :class:`PeriodicSet` with a chosen
151
        number of randomly placed points, in random cell with edges between 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
152
        length_bounds and angles between angle_bounds."""
153
        cell = random_cell(length_bounds=length_bounds, angle_bounds=angle_bounds, dims=dims)
154
        frac_motif = np.random.uniform(size=(n_points, dims))
155
        motif = frac_motif @ cell
156
        return PeriodicSet(motif, cell)
157