symfc 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. symfc-1.0.0/LICENSE +28 -0
  2. symfc-1.0.0/PKG-INFO +65 -0
  3. symfc-1.0.0/README.md +22 -0
  4. symfc-1.0.0/pyproject.toml +38 -0
  5. symfc-1.0.0/setup.cfg +4 -0
  6. symfc-1.0.0/src/symfc/__init__.py +4 -0
  7. symfc-1.0.0/src/symfc/api_symfc.py +180 -0
  8. symfc-1.0.0/src/symfc/basis_sets/__init__.py +5 -0
  9. symfc-1.0.0/src/symfc/basis_sets/basis_sets_O1.py +140 -0
  10. symfc-1.0.0/src/symfc/basis_sets/basis_sets_O2.py +171 -0
  11. symfc-1.0.0/src/symfc/basis_sets/basis_sets_O3.py +218 -0
  12. symfc-1.0.0/src/symfc/basis_sets/basis_sets_base.py +65 -0
  13. symfc-1.0.0/src/symfc/solvers/__init__.py +5 -0
  14. symfc-1.0.0/src/symfc/solvers/solver_O2.py +254 -0
  15. symfc-1.0.0/src/symfc/solvers/solver_O2O3.py +583 -0
  16. symfc-1.0.0/src/symfc/solvers/solver_O3.py +236 -0
  17. symfc-1.0.0/src/symfc/solvers/solver_base.py +54 -0
  18. symfc-1.0.0/src/symfc/spg_reps/__init__.py +6 -0
  19. symfc-1.0.0/src/symfc/spg_reps/spg_reps_O1.py +74 -0
  20. symfc-1.0.0/src/symfc/spg_reps/spg_reps_O2.py +73 -0
  21. symfc-1.0.0/src/symfc/spg_reps/spg_reps_O3.py +72 -0
  22. symfc-1.0.0/src/symfc/spg_reps/spg_reps_base.py +102 -0
  23. symfc-1.0.0/src/symfc/utils/__init__.py +1 -0
  24. symfc-1.0.0/src/symfc/utils/eig_tools.py +241 -0
  25. symfc-1.0.0/src/symfc/utils/matrix_tools_O1.py +50 -0
  26. symfc-1.0.0/src/symfc/utils/matrix_tools_O2.py +61 -0
  27. symfc-1.0.0/src/symfc/utils/matrix_tools_O3.py +342 -0
  28. symfc-1.0.0/src/symfc/utils/solver_funcs.py +41 -0
  29. symfc-1.0.0/src/symfc/utils/utils.py +164 -0
  30. symfc-1.0.0/src/symfc/utils/utils_O1.py +173 -0
  31. symfc-1.0.0/src/symfc/utils/utils_O2.py +281 -0
  32. symfc-1.0.0/src/symfc/utils/utils_O3.py +154 -0
  33. symfc-1.0.0/src/symfc/version.py +3 -0
  34. symfc-1.0.0/src/symfc.egg-info/PKG-INFO +65 -0
  35. symfc-1.0.0/src/symfc.egg-info/SOURCES.txt +37 -0
  36. symfc-1.0.0/src/symfc.egg-info/dependency_links.txt +1 -0
  37. symfc-1.0.0/src/symfc.egg-info/requires.txt +3 -0
  38. symfc-1.0.0/src/symfc.egg-info/top_level.txt +1 -0
  39. symfc-1.0.0/tests/test_api.py +86 -0
symfc-1.0.0/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2023, symfc project
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
symfc-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.1
2
+ Name: symfc
3
+ Version: 1.0.0
4
+ Summary: This is the symfc module.
5
+ Author-email: Atsuto Seko <seko@cms.mtl.kyoto-u.ac.jp>
6
+ Maintainer-email: Atsushi Togo <atztogo@gmail.com>
7
+ License: BSD 3-Clause License
8
+
9
+ Copyright (c) 2023, symfc project
10
+
11
+ Redistribution and use in source and binary forms, with or without
12
+ modification, are permitted provided that the following conditions are met:
13
+
14
+ 1. Redistributions of source code must retain the above copyright notice, this
15
+ list of conditions and the following disclaimer.
16
+
17
+ 2. Redistributions in binary form must reproduce the above copyright notice,
18
+ this list of conditions and the following disclaimer in the documentation
19
+ and/or other materials provided with the distribution.
20
+
21
+ 3. Neither the name of the copyright holder nor the names of its
22
+ contributors may be used to endorse or promote products derived from
23
+ this software without specific prior written permission.
24
+
25
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
29
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
33
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+
36
+ Project-URL: Homepage, https://github.com/symfc/symfc
37
+ Requires-Python: >=3.8
38
+ Description-Content-Type: text/markdown
39
+ License-File: LICENSE
40
+ Requires-Dist: numpy
41
+ Requires-Dist: scipy
42
+ Requires-Dist: spglib
43
+
44
+ # symfc
45
+
46
+ ## What does symfc do?
47
+
48
+ Atomic vibrations in crystals are often conveniently described using the phonon
49
+ model. In this model, the crystal potential is expanded into a Taylor series
50
+ with respect to atomic displacements from their equilibrium positions, and the
51
+ expansion coefficients are referred to as force constants.
52
+
53
+ Predicting phonon properties through computer simulations is becoming
54
+ increasingly popular, with the supercell approach being one of the techniques
55
+ employed for phonon calculations. In this method, force constants are derived
56
+ from datasets of atomic forces and displacements obtained from supercell
57
+ snapshots, which feature various configurations of atomic displacements.
58
+
59
+ While force constants possess specific symmetries, those computed from
60
+ displacement-force datasets often do not adhere to these symmetries due to
61
+ factors such as numerical noise or approximations used. Symfc is a software
62
+ designed to compute force constants from displacement-force datasets in the
63
+ supercell approach, ensuring they meet the required symmetry constraints.
64
+
65
+ For more detail, please refer https://arxiv.org/abs/2403.03588.
symfc-1.0.0/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # symfc
2
+
3
+ ## What does symfc do?
4
+
5
+ Atomic vibrations in crystals are often conveniently described using the phonon
6
+ model. In this model, the crystal potential is expanded into a Taylor series
7
+ with respect to atomic displacements from their equilibrium positions, and the
8
+ expansion coefficients are referred to as force constants.
9
+
10
+ Predicting phonon properties through computer simulations is becoming
11
+ increasingly popular, with the supercell approach being one of the techniques
12
+ employed for phonon calculations. In this method, force constants are derived
13
+ from datasets of atomic forces and displacements obtained from supercell
14
+ snapshots, which feature various configurations of atomic displacements.
15
+
16
+ While force constants possess specific symmetries, those computed from
17
+ displacement-force datasets often do not adhere to these symmetries due to
18
+ factors such as numerical noise or approximations used. Symfc is a software
19
+ designed to compute force constants from displacement-force datasets in the
20
+ supercell approach, ensuring they meet the required symmetry constraints.
21
+
22
+ For more detail, please refer https://arxiv.org/abs/2403.03588.
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "symfc"
7
+ dynamic = ["version"]
8
+ readme = {file = "README.md", content-type = "text/markdown"}
9
+ description = "This is the symfc module."
10
+ authors = [
11
+ { name = "Atsuto Seko", email = "seko@cms.mtl.kyoto-u.ac.jp" }
12
+ ]
13
+ maintainers = [
14
+ {name = "Atsushi Togo", email = "atztogo@gmail.com"}
15
+ ]
16
+ requires-python = ">=3.8"
17
+ dependencies = [
18
+ "numpy",
19
+ "scipy",
20
+ "spglib",
21
+ ]
22
+ license = {file = "LICENSE"}
23
+
24
+ [project.urls]
25
+ "Homepage" = "https://github.com/symfc/symfc"
26
+
27
+ [tool.setuptools.dynamic]
28
+ version = { attr = "symfc.version.__version__" }
29
+
30
+ [tool.flake8]
31
+ max-line-length = 88
32
+ extend-ignore = "E203,W503"
33
+
34
+ [tool.black]
35
+ line-length = 88
36
+
37
+ [tool.isort]
38
+ profile = "black"
symfc-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ """Forcd constants calculation code: Symfc."""
2
+
3
+ from .api_symfc import Symfc # noqa F401
4
+ from .version import __version__ # noqa F401
@@ -0,0 +1,180 @@
1
+ """Symfc API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from typing import Optional, Union
7
+
8
+ import numpy as np
9
+
10
+ from symfc.basis_sets import FCBasisSetBase, FCBasisSetO2, FCBasisSetO3
11
+ from symfc.solvers import FCSolverO2, FCSolverO2O3
12
+ from symfc.utils.utils import SymfcAtoms
13
+
14
+
15
+ class Symfc:
16
+ """Symfc API."""
17
+
18
+ def __init__(
19
+ self,
20
+ supercell: SymfcAtoms,
21
+ displacements: Optional[np.ndarray] = None,
22
+ forces: Optional[np.ndarray] = None,
23
+ use_mkl: bool = False,
24
+ log_level: int = 0,
25
+ ):
26
+ """Init method."""
27
+ self._supercell: SymfcAtoms = supercell
28
+ self._displacements: Optional[np.ndarray] = displacements
29
+ self._forces: Optional[np.ndarray] = forces
30
+ self._use_mkl = use_mkl
31
+ self._log_level = log_level
32
+
33
+ self._basis_set: dict[FCBasisSetBase] = {}
34
+ self._force_constants: dict[np.ndarray] = {}
35
+
36
+ @property
37
+ def basis_set(self) -> dict[FCBasisSetBase]:
38
+ """Return basis set instance.
39
+
40
+ Returns
41
+ -------
42
+ dict[FCBasisSet]
43
+ The key is the order of basis set in int.
44
+
45
+ """
46
+ return self._basis_set
47
+
48
+ @property
49
+ def force_constants(self) -> dict[np.ndarray]:
50
+ """Return force constants.
51
+
52
+ Returns
53
+ -------
54
+ dict[np.ndarray]
55
+ The key is the order of force_constants in int.
56
+
57
+ """
58
+ return self._force_constants
59
+
60
+ @property
61
+ def displacements(self) -> np.ndarray:
62
+ """Setter and getter of supercell displacements.
63
+
64
+ ndarray
65
+ shape=(n_snapshot, natom, 3), dtype='double', order='C'
66
+
67
+ """
68
+ return self._displacements
69
+
70
+ @displacements.setter
71
+ def displacements(self, displacements: Union[np.ndarray, list, tuple]):
72
+ self._displacements = np.array(displacements, dtype="double", order="C")
73
+
74
+ @property
75
+ def forces(self) -> np.ndarray:
76
+ """Setter and getter of supercell forces.
77
+
78
+ ndarray
79
+ shape=(n_snapshot, natom, 3), dtype='double', order='C'
80
+
81
+ """
82
+ return self._forces
83
+
84
+ @forces.setter
85
+ def forces(self, forces: Union[np.ndarray, list, tuple]):
86
+ self._forces = np.array(forces, dtype="double", order="C")
87
+
88
+ def run(self, orders: Sequence[int], is_compact_fc=True) -> Symfc:
89
+ """Run basis set and force constants calculation."""
90
+ if (
91
+ orders is not None
92
+ and self._displacements is not None
93
+ and self._forces is not None
94
+ ):
95
+ for order in orders:
96
+ self._compute_basis_set(order)
97
+ self.solve(orders, is_compact_fc=is_compact_fc)
98
+ return self
99
+
100
+ def solve(self, orders: Sequence[int], is_compact_fc=True) -> Symfc:
101
+ """Calculate force constants.
102
+
103
+ orders : Sequence[int]
104
+ Sequence of fc orders.
105
+
106
+ """
107
+ self._check_dataset()
108
+ for order in orders:
109
+ if order not in (2, 3):
110
+ raise NotImplementedError("Only order-2 and order-3 are implemented.")
111
+ if order == 2:
112
+ basis_set: FCBasisSetO2 = self._basis_set[2]
113
+ solver_o2 = FCSolverO2(
114
+ basis_set,
115
+ use_mkl=self._use_mkl,
116
+ log_level=self._log_level,
117
+ ).solve(self._displacements, self._forces)
118
+ if is_compact_fc:
119
+ self._force_constants[2] = solver_o2.compact_fc
120
+ else:
121
+ self._force_constants[2] = solver_o2.full_fc
122
+ if order == 3:
123
+ basis_set_o2: FCBasisSetO2 = self._basis_set[2]
124
+ basis_set_o3: FCBasisSetO3 = self._basis_set[3]
125
+ solver_o2o3 = FCSolverO2O3(
126
+ [basis_set_o2, basis_set_o3],
127
+ use_mkl=self._use_mkl,
128
+ log_level=self._log_level,
129
+ ).solve(self._displacements, self._forces)
130
+ if is_compact_fc:
131
+ fc2, fc3 = solver_o2o3.compact_fc
132
+ else:
133
+ fc2, fc3 = solver_o2o3.full_fc
134
+ self._force_constants[2] = fc2
135
+ self._force_constants[3] = fc3
136
+ return self
137
+
138
+ def _compute_basis_set(self, order: int):
139
+ """Set order of force constants."""
140
+ if order not in (2, 3):
141
+ raise NotImplementedError("Only fc2 basis set is implemented.")
142
+
143
+ if order == 2:
144
+ basis_set_o2 = FCBasisSetO2(
145
+ self._supercell, use_mkl=self._use_mkl, log_level=self._log_level
146
+ ).run()
147
+ self._basis_set[2] = basis_set_o2
148
+ if order == 3:
149
+ basis_set_o3 = FCBasisSetO3(
150
+ self._supercell, use_mkl=self._use_mkl, log_level=self._log_level
151
+ ).run()
152
+ self._basis_set[3] = basis_set_o3
153
+
154
+ def _check_dataset(self):
155
+ if self._displacements is None:
156
+ raise RuntimeError("Dispalcements not found.")
157
+ if self._forces is None:
158
+ raise RuntimeError("Forces not found.")
159
+ if self._displacements.shape != self._forces.shape:
160
+ raise RuntimeError("Shape mismatch between dispalcements and forces.")
161
+ if self._displacements.shape != self._forces.shape:
162
+ raise RuntimeError("Shape mismatch between dispalcements and forces.")
163
+ if self._displacements.ndim != 3 or self._displacements.shape[1:] != (
164
+ len(self._supercell),
165
+ 3,
166
+ ):
167
+ raise RuntimeError(
168
+ "Inconsistent array shape of displacements "
169
+ f"{self._displacements.shape} with respect to supercell "
170
+ f"{len(self._supercell)}."
171
+ )
172
+ if self._forces.ndim != 3 or self._forces.shape[1:] != (
173
+ len(self._supercell),
174
+ 3,
175
+ ):
176
+ raise RuntimeError(
177
+ "Inconsistent array shape of forces "
178
+ f"{self._forces.shape} with respect to supercell "
179
+ f"{len(self._supercell)}."
180
+ )
@@ -0,0 +1,5 @@
1
+ """Symmetry adapted basis sets of force constants."""
2
+
3
+ from .basis_sets_base import FCBasisSetBase # noqa 401
4
+ from .basis_sets_O2 import FCBasisSetO2 # noqa 401
5
+ from .basis_sets_O3 import FCBasisSetO3 # noqa 401
@@ -0,0 +1,140 @@
1
+ """Symmetry adapted basis sets of 1st order force constants."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional
6
+
7
+ import numpy as np
8
+ from scipy.sparse import csr_array
9
+
10
+ from symfc.spg_reps import SpgRepsO1
11
+ from symfc.utils.eig_tools import eigsh_projector
12
+ from symfc.utils.matrix_tools_O1 import compressed_projector_sum_rules
13
+ from symfc.utils.utils import SymfcAtoms
14
+ from symfc.utils.utils_O1 import (
15
+ get_compr_coset_reps_sum,
16
+ get_lat_trans_compr_matrix,
17
+ get_lat_trans_decompr_indices,
18
+ )
19
+
20
+ from .basis_sets_base import FCBasisSetBase
21
+
22
+
23
+ class FCBasisSetO1Base(FCBasisSetBase):
24
+ """Base class of FCBasisSetO1."""
25
+
26
+ def __init__(
27
+ self,
28
+ supercell: SymfcAtoms,
29
+ use_mkl: bool = False,
30
+ log_level: int = 0,
31
+ ):
32
+ """Init method.
33
+
34
+ Parameters
35
+ ----------
36
+ supercell : SymfcAtoms
37
+ Supercell.
38
+ use_mkl : bool
39
+ Use MKL or not. Default is False.
40
+ log_level : int, optional
41
+ Log level. Default is 0.
42
+
43
+ """
44
+ super().__init__(supercell, use_mkl=use_mkl, log_level=log_level)
45
+ self._spg_reps = SpgRepsO1(supercell)
46
+
47
+ def _get_c_trans(self) -> csr_array:
48
+ trans_perms = self._spg_reps.translation_permutations
49
+ n_lp, N = trans_perms.shape
50
+ decompr_idx = get_lat_trans_decompr_indices(trans_perms)
51
+ c_trans = get_lat_trans_compr_matrix(decompr_idx, N, n_lp)
52
+ return c_trans
53
+
54
+
55
+ class FCBasisSetO1(FCBasisSetO1Base):
56
+ """Dense symmetry adapted basis set for 1st order force constants.
57
+
58
+ Attributes
59
+ ----------
60
+ basis_set : ndarray
61
+ Compressed force constants basis set. The first dimension n_x (< n_a) is
62
+ given as a result of compression, which is depends on the system.
63
+ shape=(n_x, n_bases), dtype='double'
64
+ full_basis_set : ndarray
65
+ Full (decompressed) force constants basis set. shape=(N * 3,
66
+ n_bases), dtype='double'
67
+ translation_permutations : ndarray
68
+ Atom indices after lattice translations. shape=(lattice_translations,
69
+ supercell_atoms), dtype=int.
70
+
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ supercell: SymfcAtoms,
76
+ use_mkl: bool = False,
77
+ log_level: int = 0,
78
+ ):
79
+ """Init method.
80
+
81
+ Parameters
82
+ ----------
83
+ supercell : SymfcAtoms
84
+ Supercell.
85
+ log_level : int, optional
86
+ Log level. Default is 0.
87
+
88
+ """
89
+ super().__init__(supercell, use_mkl=use_mkl, log_level=log_level)
90
+ self._spg_reps = SpgRepsO1(supercell)
91
+ self._n_a_compression_matrix: Optional[csr_array] = None
92
+
93
+ @property
94
+ def basis_set(self) -> Optional[np.ndarray]:
95
+ """Return compressed basis set.
96
+
97
+ n_c = len(compressed_indices).
98
+
99
+ shape=(n_c, n_bases), dtype='double'.
100
+
101
+ """
102
+ return self._basis_set
103
+
104
+ @property
105
+ def full_basis_set(self) -> Optional[np.ndarray]:
106
+ """Return full (decompressed) basis set.
107
+
108
+ shape=(N*3, n_bases), dtype='double'.
109
+
110
+ Data in first dimension is ordered by (N,3).
111
+
112
+ """
113
+ return self._full_basis_set
114
+
115
+ @property
116
+ def compact_compression_matrix(self) -> Optional[np.ndarray]:
117
+ """Return compression matrix for compact basis set."""
118
+ pass
119
+
120
+ @property
121
+ def compression_matrix(self) -> Optional[np.ndarray]:
122
+ """Return compression matrix."""
123
+ pass
124
+
125
+ def run(self) -> FCBasisSetO1:
126
+ """Compute compressed force constants basis set."""
127
+ c_trans = self._get_c_trans()
128
+ coset_reps_sum = get_compr_coset_reps_sum(self._spg_reps)
129
+ proj_rt = coset_reps_sum
130
+
131
+ if len(proj_rt.data) == 0:
132
+ raise ValueError("No basis vectors exist.")
133
+
134
+ c_rt = eigsh_projector(proj_rt)
135
+ compress_mat = c_trans @ c_rt
136
+ proj = compressed_projector_sum_rules(compress_mat, self._natom)
137
+ self._basis_set = eigsh_projector(proj)
138
+ self._full_basis_set = compress_mat @ self._basis_set
139
+
140
+ return self
@@ -0,0 +1,171 @@
1
+ """Symmetry adapted basis sets of 2nd order force constants."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional
6
+
7
+ import numpy as np
8
+ from scipy.sparse import csr_array
9
+
10
+ from symfc.spg_reps import SpgRepsO2
11
+ from symfc.utils.eig_tools import (
12
+ dot_product_sparse,
13
+ eigsh_projector,
14
+ eigsh_projector_sumrule,
15
+ )
16
+ from symfc.utils.matrix_tools_O2 import compressed_projector_sum_rules
17
+ from symfc.utils.utils import SymfcAtoms
18
+ from symfc.utils.utils_O2 import (
19
+ get_compr_coset_reps_sum,
20
+ get_lat_trans_compr_matrix,
21
+ get_lat_trans_decompr_indices,
22
+ get_perm_compr_matrix,
23
+ )
24
+
25
+ from .basis_sets_base import FCBasisSetBase
26
+
27
+
28
+ class FCBasisSetO2(FCBasisSetBase):
29
+ """Dense symmetry adapted basis set for 2nd order force constants.
30
+
31
+ Attributes
32
+ ----------
33
+ basis_set : ndarray
34
+ Compressed force constants basis set. The first dimension n_x (< n_a) is
35
+ given as a result of compression, which is depends on the system.
36
+ shape=(n_x * N * 9, n_bases), dtype='double'
37
+ full_basis_set : ndarray
38
+ Full (decompressed) force constants basis set. shape=(N * N * 9,
39
+ n_bases), dtype='double'
40
+ translation_permutations : ndarray
41
+ Atom indices after lattice translations. shape=(lattice_translations,
42
+ supercell_atoms), dtype=int.
43
+
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ supercell: SymfcAtoms,
49
+ use_mkl: bool = False,
50
+ log_level: int = 0,
51
+ ):
52
+ """Init method.
53
+
54
+ Parameters
55
+ ----------
56
+ supercell : SymfcAtoms
57
+ Supercell.
58
+ use_mkl : bool
59
+ Use MKL or not. Default is False.
60
+ log_level : int, optional
61
+ Log level. Default is 0.
62
+
63
+ """
64
+ super().__init__(supercell, use_mkl=use_mkl, log_level=log_level)
65
+ self._spg_reps = SpgRepsO2(supercell)
66
+ self._n_a_compression_matrix: Optional[csr_array] = None
67
+
68
+ @property
69
+ def basis_set(self) -> Optional[np.ndarray]:
70
+ """Return compressed basis set.
71
+
72
+ n_c = len(compressed_indices).
73
+
74
+ shape=(n_c*N*3*3, n_bases), dtype='double'.
75
+
76
+ Data in first dimension is ordered by (n_c,N,3,3).
77
+
78
+ """
79
+ return self._basis_set
80
+
81
+ @property
82
+ def compression_matrix(self) -> Optional[csr_array]:
83
+ """Return compression matrix.
84
+
85
+ This expands fc basis_sets to (N*N*3*3, n_bases).
86
+
87
+ """
88
+ c_trans = self._get_c_trans()
89
+ return dot_product_sparse(
90
+ c_trans, self._n_a_compression_matrix, use_mkl=self._use_mkl
91
+ )
92
+
93
+ @property
94
+ def compact_compression_matrix(self) -> Optional[csr_array]:
95
+ """Return compact compression matrix.
96
+
97
+ This expands basis_sets to (n_a*N*3*3, n_bases).
98
+
99
+ """
100
+ n_lp = self.translation_permutations.shape[0]
101
+ return self._n_a_compression_matrix / np.sqrt(n_lp)
102
+
103
+ @property
104
+ def decompression_indices(self) -> np.ndarray:
105
+ """Return decompression indices in (N,N,3,3) order."""
106
+ trans_perms = self.translation_permutations
107
+ return get_lat_trans_decompr_indices(trans_perms)
108
+
109
+ def run(self) -> FCBasisSetO2:
110
+ """Compute compressed force constants basis set.
111
+
112
+ compression using C(pt)
113
+ = eigvecs of C(trans).T @ C(perm) @ C(perm).T @ C(trans)
114
+ proj_rpt = c_pt.transpose() @ coset_reps_sum @ c_pt
115
+
116
+ [C_pt @ C_trans.T] @ P_sum @ [C_trans @ C_pt] @ C_rpt
117
+ = C_rpt @ [C_rpt.T @ C_pt @ C_trans.T] @ P_sum @ [C_trans @ C_pt @ C_rpt]
118
+ = C_rpt @ compression_mat.T @ (I - P_sum^(c)) @ compression_mat
119
+ = C_rpt @ compression_mat.T @ (I - Csum(c) @ Csum(c).T) @ compression_mat
120
+ = C_rpt @ proj
121
+
122
+ compress_mat = c_trans @ c_pt @ c_rpt
123
+
124
+ """
125
+ N = self._natom
126
+ c_trans = self._get_c_trans()
127
+ n_a_compress_mat = self._get_n_a_compress_mat(c_trans)
128
+ compress_mat = dot_product_sparse(
129
+ c_trans, n_a_compress_mat, use_mkl=self._use_mkl
130
+ )
131
+ proj = compressed_projector_sum_rules(compress_mat, N, use_mkl=self._use_mkl)
132
+ eigvecs = eigsh_projector_sumrule(proj)
133
+
134
+ if self._log_level:
135
+ print(f"Final size of basis set: {eigvecs.shape}")
136
+
137
+ self._basis_set = eigvecs
138
+ self._n_a_compression_matrix = n_a_compress_mat
139
+ return self
140
+
141
+ def _get_n_a_compress_mat(self, c_trans: csr_array) -> csr_array:
142
+ """Return compression matrix without c_trans mutiplied.
143
+
144
+ This compression matrix is preserved as a class instance variable.
145
+ The full compression matrix is obtained by
146
+
147
+ c_trans @ n_a_compression_matrix.
148
+
149
+ The compact compression matrix is obtained by
150
+
151
+ n_a_compression_matrix / sqrt(n_lp).
152
+
153
+ """
154
+ N = self._natom
155
+ c_perm = get_perm_compr_matrix(N)
156
+ c_pt = dot_product_sparse(c_perm.T, c_trans, use_mkl=self._use_mkl)
157
+ proj_pt = dot_product_sparse(c_pt.T, c_pt, use_mkl=self._use_mkl)
158
+ coset_reps_sum = get_compr_coset_reps_sum(self._spg_reps)
159
+ c_pt = eigsh_projector(proj_pt)
160
+ proj_rpt = dot_product_sparse(coset_reps_sum, c_pt, use_mkl=self._use_mkl)
161
+ proj_rpt = dot_product_sparse(c_pt.T, proj_rpt)
162
+ c_rpt = eigsh_projector(proj_rpt)
163
+ n_a_compress_mat = dot_product_sparse(c_pt, c_rpt, use_mkl=self._use_mkl)
164
+ return n_a_compress_mat
165
+
166
+ def _get_c_trans(self) -> csr_array:
167
+ trans_perms = self._spg_reps.translation_permutations
168
+ n_lp, N = trans_perms.shape
169
+ decompr_idx = get_lat_trans_decompr_indices(trans_perms)
170
+ c_trans = get_lat_trans_compr_matrix(decompr_idx, N, n_lp)
171
+ return c_trans