symfc 1.0.0__py3-none-any.whl

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.
symfc/__init__.py ADDED
@@ -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
symfc/api_symfc.py ADDED
@@ -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