Passed
Push — master ( 4570af...abe023 )
by Daniel
03:02
created

amd.periodicset.PeriodicSet.copy()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nop 1
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 object type yielded by the readers :class:`.io.CifReader` and
6
:class:`.io.CSDReader`. The :class:`PeriodicSet` can be passed as the first argument
7
to :func:`.calculate.AMD` or :func:`.calculate.PDD` to calculate its invariants.
8
"""
9
10
from typing import Optional
11
import numpy as np
12
13
14
class PeriodicSet:
15
    """A periodic set is the mathematical representation of a crystal by putting a
16
    single point in the center of every atom. A periodic set is defined by a basis
17
    (unit cell) and collection of points (motif) which repeats according to the basis.
18
19
    :class:`PeriodicSet` s are returned by the readers in the :mod:`.io` module.
20
    Instances of this object can be passed to :func:`.calculate.AMD` or
21
    :func:`.calculate.PDD` to calculate the invariant.
22
    """
23
24
    def __init__(
25
            self,
26
            motif: np.ndarray,
27
            cell: np.ndarray,
28
            name: Optional[str] = None,
29
            **kwargs):
30
31
        self.motif = motif
32
        self.cell = cell
33
        self.name = name
34
        self.tags = kwargs
35
36
    def __str__(self):
37
38
        m, dims = self.motif.shape
39
        return f"PeriodicSet({self.name}: {m} motif points in {dims} dimensions)"
40
41
    def __repr__(self):
42
        return f"PeriodicSet(name: {self.name}, cell: {self.cell}, motif: {self.motif}, tags: {self.tags})"
43
44
    # used for debugging, checks if the motif/cell agree point for point
45
    # (disregarding order), NOT up to isometry.
46
    def __eq__(self, other):
0 ignored issues
show
best-practice introduced by
Too many return statements (7/6)
Loading history...
47
48
        if self.cell.shape != other.cell.shape or self.motif.shape != other.motif.shape:
49
            return False
50
51
        if not np.allclose(self.cell, other.cell):
52
            return False
53
54
        # this is the part that'd be confused by overlapping sites
55
        # just checks every motif point in either have a neighbour in the other
56
        diffs = np.amax(np.abs(other.motif[:, None] - self.motif), axis=-1)
57
        if not np.all((np.amin(diffs, axis=0) <= 1e-6) & (np.amin(diffs, axis=-1) <= 1e-6)):
58
            return False
59
60
        shared_tags = set(self.tags.keys()).intersection(set(other.tags.keys()))
61
        for tag in shared_tags:
62
            if np.isscalar(self.tags[tag]):
63
                if self.tags[tag] != other.tags[tag]:
64
                    return False
65
            elif isinstance(self.tags[tag], np.ndarray):
66
                if not np.allclose(self.tags[tag], other.tags[tag]):
67
                    return False
68
            elif isinstance(self.tags[tag], list):
69
                for i, i_ in zip(self.tags[tag], other.tags[tag]):
70
                    if i != i_:
71
                        return False
72
73
        return True
74
75
    def __getattr__(self, attr):
76
77
        if 'tags' not in self.__dict__:
78
            self.tags = {}
79
        if attr in self.tags:
80
            return self.tags[attr]
81
82
        raise AttributeError(f"{self.__class__.__name__} object has no attribute or tag {attr}")
83
84
    def __ne__(self, other):
85
        return not self.__eq__(other)
86