symvb 2.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.
@@ -0,0 +1,49 @@
1
+ import itertools
2
+
3
+ import sys
4
+
5
+ sys.setrecursionlimit(30000)
6
+
7
+
8
+ class OrbitalPermutations:
9
+ # will return permutations of vector [0,1,..,n-1] together with parity signs
10
+ def __init__(self, n):
11
+ self.n = n
12
+ self.permutations = []
13
+ self.permutation_signs = []
14
+
15
+ self.get_permutation_indices(n)
16
+
17
+ def __iter__(self):
18
+ return zip(self.permutations, self.permutation_signs)
19
+
20
+ def N_flips(self, v):
21
+ # Computes the number of permutations from the consecutive order
22
+ if len(v) == 0:
23
+ return 0
24
+ v2 = list(v).copy()
25
+
26
+ # find 1 and swap elements
27
+ i1 = v2.index(0)
28
+ v2[i1] = v2[0]
29
+ v2[0] = 1
30
+
31
+ v2 = [x - 1 for x in v2]
32
+ add_perm = 0 if i1 == 0 else 1
33
+
34
+ return self.N_flips(v2[1:]) + add_perm
35
+
36
+ def parity_sign(self, v):
37
+ # Returns the parity (+1/-1) of a given permutation vector
38
+ n = self.N_flips(v)
39
+ if (n % 2) == 0:
40
+ return 1
41
+ else:
42
+ return -1
43
+
44
+ def get_permutation_indices(self, n):
45
+ # gets the permutation indices and permutation signs
46
+ if n == 0:
47
+ n = 1
48
+ self.permutations = list(itertools.permutations(range(n)))
49
+ self.permutation_signs = [self.parity_sign(v) for v in self.permutations]
symvb/slaterdet.py ADDED
@@ -0,0 +1,142 @@
1
+ from symvb.functions import sorti
2
+ from symvb.orbital_permutations import OrbitalPermutations
3
+ import symvb
4
+ import logging
5
+
6
+ logging.basicConfig(format=('%(levelname)-8s: %(message)s'))
7
+
8
+
9
+ class SlaterDet:
10
+ """
11
+ Parses the Slater determinant string |s| and computes the <det1|O|det2> elements
12
+ """
13
+ def __init__(self, s=""):
14
+ self.det_string = s
15
+ self.alpha_indices = []
16
+ self.alpha_string = ''
17
+ self.beta_indices = []
18
+ self.beta_string = ''
19
+ self.spins = ''
20
+ self.Nel = 0
21
+ if len(self.det_string) > 0:
22
+ self.parse_det()
23
+
24
+ def __repr__(self):
25
+ s = '|%s|' % self.det_string
26
+ return s
27
+
28
+ # Description of the magic functions: https://docs.python.org/3/reference/datamodel.html
29
+ def __add__(self, other):
30
+ return symvb.FixedPsi(self) + other
31
+
32
+ def __sub__(self, other):
33
+ if other.__class__.__name__ == 'SlaterDet' and self.det_string == other.det_string:
34
+ return SlaterDet()
35
+ return symvb.FixedPsi(self) + (-1) * other
36
+
37
+ def __rsub__(self, other):
38
+ return symvb.FixedPsi(other) + (-1) * symvb.FixedPsi(self)
39
+
40
+ def __neg__(self):
41
+ return (-1) * symvb.FixedPsi(self)
42
+
43
+ def __mul__(self, other):
44
+ if isinstance(other, int) or isinstance(other, float):
45
+ return symvb.FixedPsi(self) * other
46
+ if other.__class__.__name__ == 'SlaterDet':
47
+ # make sure we do not place two electrons on the same orbital
48
+ if len(set(self.alpha_string).intersection(set(other.alpha_string))) > 0:
49
+ return SlaterDet()
50
+ if len(set(self.beta_string).intersection(set(other.beta_string))) > 0:
51
+ return SlaterDet()
52
+ return SlaterDet(self.det_string + other.det_string)
53
+ if other.__class__.__name__ == 'FixedPsi':
54
+ return symvb.FixedPsi(self) * other
55
+ return NotImplemented
56
+
57
+ def __rmul__(self, other):
58
+ if isinstance(other, int) or isinstance(other, float):
59
+ return symvb.FixedPsi(self) * other
60
+
61
+ def parse_det(self):
62
+ s = self.det_string
63
+ if len(s) == 0:
64
+ return
65
+
66
+ i = 0
67
+ for c in s:
68
+ if c.islower():
69
+ assert c not in self.alpha_string # two electrons cannot occupy the same spinorbital
70
+ self.alpha_indices.append(i)
71
+ self.spins += '+'
72
+ self.alpha_string += c
73
+ else:
74
+ assert c not in self.beta_string # two electrons cannot occupy the same spinorbital
75
+ self.beta_indices.append(i)
76
+ self.spins += '-'
77
+ self.beta_string += c
78
+ i = i + 1
79
+ self.Nel = i
80
+
81
+ def get_orbital_permutations(self):
82
+ # gets all spin-restricted permutations of orbital products
83
+ A = OrbitalPermutations(len(self.alpha_indices))
84
+ B = OrbitalPermutations(len(self.beta_indices))
85
+
86
+ dets = []
87
+ signs = []
88
+ for a_orbs, a_sign in A:
89
+ for b_orbs, b_sign in B:
90
+ i_a = 0
91
+ i_b = 0
92
+ s = ''
93
+ for i in range(len(self.det_string)):
94
+ if self.spins[i] == '+':
95
+ c = self.alpha_string[a_orbs[i_a]]
96
+ i_a += 1
97
+ else:
98
+ c = self.beta_string[b_orbs[i_b]]
99
+ i_b += 1
100
+ s = s + c
101
+ dets.append(s)
102
+ signs.append(a_sign * b_sign)
103
+
104
+ return [dets, signs]
105
+
106
+ def is_compatible(self, R):
107
+ if self.Nel != R.Nel:
108
+ # logging.warning('Different number of electrons: %i vs %i' % (self.Nel, R.Nel))
109
+ return False
110
+ if self.spins != R.spins:
111
+ # logging.warning('The determinant spins are incompatible: %s vs %s' % (self.spins, R.spins))
112
+ return False
113
+ return True
114
+
115
+ def get_sorted(self):
116
+ # Sorts orbital labels within alpha and beta blocks into alphabetic order
117
+ # (spin pattern unchanged). Returns FixedPsi with the sign from the
118
+ # required orbital permutation folded into the coefficient.
119
+ sa, ia = sorti(self.alpha_string)
120
+ sb, ib = sorti(self.beta_string)
121
+
122
+ s = self.det_string
123
+ for i in range(len(self.alpha_indices)):
124
+ j = self.alpha_indices[i]
125
+ s = s[:j] + sa[i] + s[j + 1:]
126
+
127
+ for i in range(len(self.beta_indices)):
128
+ j = self.beta_indices[i]
129
+ s = s[:j] + sb[i] + s[j + 1:]
130
+
131
+ # Construct a fresh SlaterDet from the sorted string so internal
132
+ # fields (alpha_indices / alpha_string / etc.) reflect the new ordering.
133
+ d = SlaterDet(s)
134
+
135
+ coef = 1 if (ia + ib) % 2 == 0 else -1
136
+ fp = symvb.FixedPsi()
137
+ fp.add_det(d, coef=coef)
138
+ return fp
139
+
140
+
141
+ if __name__ == '__main__':
142
+ print(SlaterDet('AbCd').spins)
symvb/spin.py ADDED
@@ -0,0 +1,233 @@
1
+ """
2
+ Total-spin (S^2) machinery: construct the S^2 matrix over a Slater-det
3
+ basis with fixed Sz and project onto a chosen total-spin eigenspace.
4
+
5
+ For the Sz = 0 subspace,
6
+
7
+ S^2 = S_+ S_- (the S_z^2 - S_z piece vanishes)
8
+ = sum_{i, j} c^+_{i alpha} c_{i beta} c^+_{j beta} c_{j alpha}.
9
+
10
+ The action of S^2 on a Slater det |D> is computed by applying the four
11
+ second-quantized operators in sequence, tracking fermion signs through
12
+ a canonical spin-orbital ordering (alpha_1, beta_1, alpha_2, beta_2, ...).
13
+ The resulting matrix element <D'|S^2|D> is summed over all orbital
14
+ pairs (i, j) to build the full S^2 matrix.
15
+
16
+ Given an S^2 matrix over any Slater-det basis, `project_onto_S(H, S2, S)`
17
+ returns the Hamiltonian block projected onto the S(S+1) eigenspace.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import numpy as np
22
+
23
+
24
+ def _canon_det(alpha_set, beta_set):
25
+ """
26
+ Canonical det string: sort alpha and beta sets, interleave in pairs
27
+ matching symvb.functions.generate_det_strings. Returns None if
28
+ n_alpha != n_beta (our caller will work with balanced dets only).
29
+ """
30
+ a = sorted(alpha_set)
31
+ b = sorted(beta_set)
32
+ if len(a) != len(b):
33
+ return None
34
+ return ''.join(a[k] + b[k].upper() for k in range(len(a)))
35
+
36
+
37
+ def _spin_orbital_list(alpha_set, beta_set):
38
+ """Canonical ordering: alpha_1, beta_1, alpha_2, beta_2, ... sorted by orbital."""
39
+ orbs = sorted(alpha_set | beta_set)
40
+ return [(orb, s) for orb in orbs for s in (0, 1) if
41
+ (s == 0 and orb in alpha_set) or (s == 1 and orb in beta_set)]
42
+
43
+
44
+ def _apply_c(alpha, beta, orb, spin, create):
45
+ """
46
+ Apply c_{orb, spin} or c^+_{orb, spin} to the det specified by the
47
+ alpha/beta orbital sets. Returns (new_alpha, new_beta, sign) or
48
+ (None, None, 0) if annihilated. sign = (-1)^{# occupied spin-orbitals
49
+ before (orb, spin) in the canonical ordering}.
50
+
51
+ Canonical ordering: for each orbital in alphabetical order, alpha first
52
+ then beta.
53
+ """
54
+ present = (orb in alpha) if spin == 0 else (orb in beta)
55
+ if create == present: # trying to create an occupied slot, or annihilate empty
56
+ return None, None, 0
57
+
58
+ # Jordan-Wigner sign: count occupied spin-orbitals strictly before (orb, spin)
59
+ count = 0
60
+ for x in sorted(alpha | beta):
61
+ if x < orb:
62
+ count += int(x in alpha) + int(x in beta)
63
+ elif x == orb:
64
+ if spin == 1 and x in alpha:
65
+ count += 1
66
+ sign = 1 if count % 2 == 0 else -1
67
+
68
+ new_alpha = alpha.copy()
69
+ new_beta = beta.copy()
70
+ if spin == 0:
71
+ if create:
72
+ new_alpha.add(orb)
73
+ else:
74
+ new_alpha.discard(orb)
75
+ else:
76
+ if create:
77
+ new_beta.add(orb)
78
+ else:
79
+ new_beta.discard(orb)
80
+ return new_alpha, new_beta, sign
81
+
82
+
83
+ def _apply_s_pl_s_mi(orbs, alpha, beta):
84
+ """
85
+ Apply S_+ S_- = sum_{i, j} c^+_{i a} c_{i b} c^+_{j b} c_{j a} to |D>.
86
+ Returns a dict { canonical_det_string : signed_coefficient, ... }.
87
+ `orbs` is the list of orbital labels to iterate over.
88
+ """
89
+ out = {}
90
+ for j in orbs:
91
+ # c_{j alpha}
92
+ a1, b1, s1 = _apply_c(alpha, beta, j, 0, create=False)
93
+ if a1 is None:
94
+ continue
95
+ for i in orbs:
96
+ # c^+_{j beta}
97
+ a2, b2, s2 = _apply_c(a1, b1, j, 1, create=True)
98
+ if a2 is None:
99
+ continue
100
+ # c_{i beta}
101
+ a3, b3, s3 = _apply_c(a2, b2, i, 1, create=False)
102
+ if a3 is None:
103
+ continue
104
+ # c^+_{i alpha}
105
+ a4, b4, s4 = _apply_c(a3, b3, i, 0, create=True)
106
+ if a4 is None:
107
+ continue
108
+ sign = s1 * s2 * s3 * s4
109
+ key = _canon_det(a4, b4)
110
+ if key is None:
111
+ continue
112
+ out[key] = out.get(key, 0) + sign
113
+ return out
114
+
115
+
116
+ def _symvb_to_canonical_sign(det_string, orbital_index):
117
+ """
118
+ Sign relating the symvb Slater det (creation order = det_string order)
119
+ to the canonical Jordan-Wigner ordering (alphabetical orbital, alpha
120
+ before beta for each orbital).
121
+
122
+ For det_string 'x0 X1 x2 X3 ...' the creation order is
123
+ (x0, alpha), (X1, beta), (x2, alpha), (X3, beta), ...
124
+ To put in canonical ascending order we count inversions in the list
125
+ of spin-orbital indices (2 * orb_index + spin).
126
+ """
127
+ indices = []
128
+ for pos, c in enumerate(det_string):
129
+ orb = c.lower()
130
+ spin = 0 if c.islower() else 1
131
+ indices.append(2 * orbital_index[orb] + spin)
132
+ inv = 0
133
+ for a in range(len(indices)):
134
+ for b in range(a + 1, len(indices)):
135
+ if indices[a] > indices[b]:
136
+ inv += 1
137
+ return 1 if inv % 2 == 0 else -1
138
+
139
+
140
+ def s_squared_matrix(det_strings, orbs=None):
141
+ """
142
+ Build the S^2 matrix in a Slater-det basis at Sz = 0.
143
+
144
+ Thin wrapper around `symvb.operators.s_squared(orbs).matrix(...)`;
145
+ kept for backward compatibility. Returns a numpy float ndarray with
146
+ eigenvalues S(S+1).
147
+ """
148
+ from symvb import operators as _op
149
+ if orbs is None:
150
+ orbs = sorted({ch.lower() for ds in det_strings for ch in ds})
151
+ return np.array(_op.s_squared(orbs).matrix(det_strings), dtype=float)
152
+
153
+
154
+ def _apply_eta_pl_eta_mi(orbs, site_sign, alpha, beta):
155
+ """
156
+ Apply eta_+ eta_- = sum_{i,j} s_i s_j c^+_{i alpha} c^+_{i beta}
157
+ c_{j beta} c_{j alpha}
158
+ to |D> (alpha, beta occupation sets), where s_i = site_sign[i] = +/-1.
159
+ Returns a dict { canonical_det_string : signed_coefficient, ... }.
160
+ """
161
+ out = {}
162
+ for j in orbs:
163
+ # c_{j alpha}
164
+ a1, b1, s1 = _apply_c(alpha, beta, j, 0, create=False)
165
+ if a1 is None:
166
+ continue
167
+ # c_{j beta}
168
+ a2, b2, s2 = _apply_c(a1, b1, j, 1, create=False)
169
+ if a2 is None:
170
+ continue
171
+ for i in orbs:
172
+ # c^+_{i beta}
173
+ a3, b3, s3 = _apply_c(a2, b2, i, 1, create=True)
174
+ if a3 is None:
175
+ continue
176
+ # c^+_{i alpha}
177
+ a4, b4, s4 = _apply_c(a3, b3, i, 0, create=True)
178
+ if a4 is None:
179
+ continue
180
+ sign = s1 * s2 * s3 * s4 * site_sign[i] * site_sign[j]
181
+ key = _canon_det(a4, b4)
182
+ if key is None:
183
+ continue
184
+ out[key] = out.get(key, 0) + sign
185
+ return out
186
+
187
+
188
+ def eta_squared_matrix(det_strings, site_signs, orbs=None):
189
+ """
190
+ Build the eta-pseudospin squared matrix eta^2 in a Slater-det basis
191
+ at eta_z = (N - L) / 2 = 0 (half-filling on a bipartite lattice).
192
+
193
+ The eta operators are
194
+ eta_+ = sum_i s_i c^+_{i alpha} c^+_{i beta}
195
+ eta_- = (eta_+)^dagger
196
+ eta_z = (N_total - L) / 2
197
+ where s_i = +/-1 is the sublattice sign. On a bipartite nearest-
198
+ neighbor hopping lattice at half-filling, [H, eta_+-] = 0; on adding
199
+ Hubbard U this reduces to [H, eta_z] = 0 only.
200
+
201
+ Parameters
202
+ ----------
203
+ det_strings : list of symvb Slater-det strings, all at N = L.
204
+ site_signs : dict {orbital_label: +1 or -1} encoding the bipartition.
205
+ orbs : optional list of orbital labels (inferred from site_signs if
206
+ omitted).
207
+
208
+ Returns
209
+ -------
210
+ eta2 : (N, N) ndarray. Eigenvalues are eta(eta+1) for eta = 0, 1, 2, ...
211
+
212
+ Thin wrapper around `symvb.operators.eta_squared(...).matrix(...)`.
213
+ """
214
+ from symvb import operators as _op
215
+ return np.array(_op.eta_squared(site_signs).matrix(det_strings), dtype=float)
216
+
217
+
218
+ def project_onto_S(H, S2, target_S, tol=1e-8):
219
+ """
220
+ Project H onto the eigenspace of S^2 with eigenvalue target_S * (target_S + 1).
221
+
222
+ Returns (H_block, U) where U has orthonormal columns spanning the
223
+ target-S subspace and H_block = U^T H U.
224
+ """
225
+ target = target_S * (target_S + 1)
226
+ evals, evecs = np.linalg.eigh((S2 + S2.T) / 2)
227
+ mask = np.abs(evals - target) < tol
228
+ if not mask.any():
229
+ raise ValueError(f'no S^2 eigenvalues near S(S+1) = {target} '
230
+ f'(spectrum: {sorted(set(round(e, 6) for e in evals))})')
231
+ U = evecs[:, mask]
232
+ H_block = U.T @ H @ U
233
+ return H_block, U