File: symbols.py

package info (click to toggle)
python-ase 3.21.1-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 13,936 kB
  • sloc: python: 122,428; xml: 946; makefile: 111; javascript: 47
file content (179 lines) | stat: -rw-r--r-- 5,607 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
from typing import List, Sequence, Set, Dict, Union, Iterator
import warnings
import collections.abc

import numpy as np

from ase.data import atomic_numbers, chemical_symbols
from ase.formula import Formula


def string2symbols(s: str) -> List[str]:
    """Convert string to list of chemical symbols."""
    return list(Formula(s))


def symbols2numbers(symbols) -> List[int]:
    if isinstance(symbols, str):
        symbols = string2symbols(symbols)
    numbers = []
    for s in symbols:
        if isinstance(s, str):
            numbers.append(atomic_numbers[s])
        else:
            numbers.append(int(s))
    return numbers


class Symbols(collections.abc.Sequence):
    """A sequence of chemical symbols.

    ``atoms.symbols`` is a :class:`ase.symbols.Symbols` object.  This
    object works like an editable view of ``atoms.numbers``, except
    its elements are manipulated as strings.

    Examples:

    >>> from ase.build import molecule
    >>> atoms = molecule('CH3CH2OH')
    >>> atoms.symbols
    Symbols('C2OH6')
    >>> atoms.symbols[:3]
    Symbols('C2O')
    >>> atoms.symbols == 'H'
    array([False, False, False,  True,  True,  True,  True,  True,  True], dtype=bool)
    >>> atoms.symbols[-3:] = 'Pu'
    >>> atoms.symbols
    Symbols('C2OH3Pu3')
    >>> atoms.symbols[3:6] = 'Mo2U'
    >>> atoms.symbols
    Symbols('C2OMo2UPu3')
    >>> atoms.symbols.formula
    Formula('C2OMo2UPu3')

    The :class:`ase.formula.Formula` object is useful for extended
    formatting options and analysis.

    """
    def __init__(self, numbers) -> None:
        self.numbers = np.asarray(numbers, int)

    @classmethod
    def fromsymbols(cls, symbols) -> 'Symbols':
        numbers = symbols2numbers(symbols)
        return cls(np.array(numbers))

    @property
    def formula(self) -> Formula:
        """Formula object."""
        string = Formula.from_list(self).format('reduce')
        return Formula(string)

    def __getitem__(self, key) -> Union['Symbols', str]:
        num = self.numbers[key]
        if np.isscalar(num):
            return chemical_symbols[num]
        return Symbols(num)

    def __iter__(self) -> Iterator[str]:
        for num in self.numbers:
            yield chemical_symbols[num]

    def __setitem__(self, key, value) -> None:
        numbers = symbols2numbers(value)
        if len(numbers) == 1:
            self.numbers[key] = numbers[0]
        else:
            self.numbers[key] = numbers

    def __len__(self) -> int:
        return len(self.numbers)

    def __str__(self) -> str:
        return self.get_chemical_formula('reduce')

    def __repr__(self) -> str:
        return 'Symbols(\'{}\')'.format(self)

    def __eq__(self, obj) -> bool:
        if not hasattr(obj, '__len__'):
            return False

        try:
            symbols = Symbols.fromsymbols(obj)
        except Exception:
            # Typically this would happen if obj cannot be converged to
            # atomic numbers.
            return False
        return self.numbers == symbols.numbers

    def get_chemical_formula(
            self,
            mode: str = 'hill',
            empirical: bool = False,
    ) -> str:
        """Get chemical formula.

        See documentation of ase.atoms.Atoms.get_chemical_formula()."""
        # XXX Delegate the work to the Formula object!
        if mode in ('reduce', 'all') and empirical:
            warnings.warn("Empirical chemical formula not available "
                          "for mode '{}'".format(mode))

        if len(self) == 0:
            return ''

        numbers = self.numbers

        if mode == 'reduce':
            n = len(numbers)
            changes = np.concatenate(([0], np.arange(1, n)[numbers[1:] !=
                                                           numbers[:-1]]))
            symbols = [chemical_symbols[e] for e in numbers[changes]]
            counts = np.append(changes[1:], n) - changes

            tokens = []
            for s, c in zip(symbols, counts):
                tokens.append(s)
                if c > 1:
                    tokens.append(str(c))
            formula = ''.join(tokens)
        elif mode == 'all':
            formula = ''.join([chemical_symbols[n] for n in numbers])
        else:
            symbols = [chemical_symbols[Z] for Z in numbers]
            f = Formula('', _tree=[(symbols, 1)])
            if empirical:
                f, _ = f.reduce()
            if mode in {'hill', 'metal'}:
                formula = f.format(mode)
            else:
                raise ValueError(
                    "Use mode = 'all', 'reduce', 'hill' or 'metal'.")

        return formula

    def search(self, symbols) -> Sequence[int]:
        """Return the indices of elements with given symbol or symbols."""
        numbers = set(symbols2numbers(symbols))
        indices = [i for i, number in enumerate(self.numbers)
                   if number in numbers]
        return np.array(indices, int)

    def species(self) -> Set[str]:
        """Return unique symbols as a set."""
        return set(self)

    def indices(self) -> Dict[str, Sequence[int]]:
        """Return dictionary mapping each unique symbol to indices.

        >>> from ase.build import molecule
        >>> atoms = molecule('CH3CH2OH')
        >>> atoms.symbols.indices()
        {'C': array([0, 1]), 'O': array([2]), 'H': array([3, 4, 5, 6, 7, 8])}

        """
        dct: Dict[str, List[int]] = {}
        for i, symbol in enumerate(self):
            dct.setdefault(symbol, []).append(i)
        return {key: np.array(value, int) for key, value in dct.items()}