Skip to content

Commit 55fac07

Browse files
committed
Clarified mapping functions, added the unittest for the function to compute overlap of Slater determinants. Since now, the convention is to use the doubled overlap matrix - alpha and beta blocks (user_notation = 1) in sd2indx and to do the sorting, so the overlap doesn't depend on the particular ordering of spin-orbitals entered by user, but only on their identities
1 parent 559d1a2 commit 55fac07

2 files changed

Lines changed: 94 additions & 37 deletions

File tree

src/libra_py/workflows/nbra/mapping.py

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def sd2indx(inp, nbasis=0, do_sort=False, user_notation=0):
112112

113113

114114

115-
def ovlp_arb(_SD1, _SD2, S, active_space=None, do_sort=False, user_notation=0, verbose=False):
115+
def ovlp_arb(_SD1, _SD2, S, active_space=None, verbose=False):
116116
"""Compute the overlap of two generic SDs: <SD1|SD2>
117117
This functions translates the explicit use of two Python `for` loops
118118
into a more efficient way using numpy
@@ -129,15 +129,6 @@ def ovlp_arb(_SD1, _SD2, S, active_space=None, do_sort=False, user_notation=0, v
129129
active_space ( list of ints ): indices of the orbitals (starting from 1) to
130130
include into consideration. If None - all the orbitals will be used [ default: None ]
131131
132-
do_sort ( Boolean ): the flag to tell whether the indices should be sorted in
133-
a particular order (according to canonic ordering defined in the `citools.slatdet`):
134-
- True - for new scheme (needed for the SAC) [default]
135-
- use with False for Pyxaid mapping!
136-
137-
user_notation (int):
138-
- 0 : short-hand - in this case, the mapping goes into [0, N) range [default - because it has been used for a while]
139-
- 1 : extended - in this case, the mapping goes into [0, 2*N) range
140-
141132
verbose ( Bool ): whether to print some extra info [ default: False]
142133
143134
Returns:
@@ -169,33 +160,27 @@ def ovlp_arb(_SD1, _SD2, S, active_space=None, do_sort=False, user_notation=0, v
169160
# Apply the determinant formula
170161
det_size = len(SD1)
171162
s = np.zeros( (det_size, det_size), dtype=np.float64);
172-
""" Commented on 7/22/2025
173-
# ============================== The numpy version of the above double-for-loops
174-
# Find the tensor product of the SD1 and SD2
175-
SD_tensor_product = np.tensordot(SD1, SD2, axes=0)
176-
# Next, find the sign of these elementwise multiplications
177-
SD_tensor_product_sign = np.sign(SD_tensor_product)
178-
# Now, find where we have alpha-beta indices so that the
179-
negative_sign_indices = np.where(SD_tensor_product_sign < 0)
180-
s[negative_sign_indices] = 0
181-
"""
163+
182164
# Let's build the matrix related to sd1 and sd2 from the KS orbitals
183-
# For this, we reuire to turn each element into matrix indices
184-
sd1,_,_ = sd2indx(SD1, nbasis, do_sort, user_notation)
185-
sd2,_,_ = sd2indx(SD2, nbasis, do_sort, user_notation)
165+
# For this, we require to turn each element into matrix indices
166+
sd1, p1 = sd2indx(SD1, nbasis, do_sort=True, user_notation=1)
167+
sd2, p2 = sd2indx(SD2, nbasis, do_sort=True, user_notation=1)
186168

169+
if verbose==True:
170+
print(sd1, sd2)
171+
print(p1, p2)
187172

188173
"""
189-
ALEXEY: Instead of this trick, use user_notation = 1
174+
#ALEXEY on 12/08/2025: Instead of this trick, we use user_notation = 1 above
190175
191176
# What about beta indices?! We should add `nbasis/2` to them. This is added on 7/22/2025
192177
# With this simple approach, there is no need for tensor product or the `if else` clause brought
193178
# previously since the elements corresponding to the alpha-beta orbitals are already
194179
# zero in the two-spinor format of the KS matrices :)
180+
195181
beta_indices = np.where(np.array(SD1) < 0)
196182
sd1 = np.array(sd1)
197183
sd1[beta_indices] += int(nbasis/2)
198-
199184
beta_indices = np.where(np.array(SD2) < 0)
200185
sd2 = np.array(sd2)
201186
sd2[beta_indices] += int(nbasis/2)
@@ -207,13 +192,14 @@ def ovlp_arb(_SD1, _SD2, S, active_space=None, do_sort=False, user_notation=0, v
207192

208193
if verbose==True:
209194
print(s)
195+
210196
res = np.linalg.det(s)
211197

212198
return res
213199

214200

215201

216-
def ovlp_mat_arb(SD1, SD2, _S, active_space=None, do_sort=False, user_notation=0, verbose=False):
202+
def ovlp_mat_arb(SD1, SD2, _S, active_space=None, verbose=False):
217203
"""Compute a matrix of overlaps in the SD basis
218204
219205
Args:
@@ -233,15 +219,6 @@ def ovlp_mat_arb(SD1, SD2, _S, active_space=None, do_sort=False, user_notation=0
233219
active_space ( list of ints ): indices of the orbitals (starting from 1) to
234220
include into consideration. If None - all the orbitals will be used [ default: None ]
235221
236-
do_sort ( Boolean ): the flag to tell whether the indices should be sorted in
237-
a particular order (according to canonic ordering defined in the `citools.slatdet`):
238-
- True - for new scheme (needed for the SAC) [default]
239-
- use with False for Pyxaid mapping!
240-
241-
user_notation (int):
242-
- 0 : short-hand - in this case, the mapping goes into [0, N) range [default - because it has been used for a while]
243-
- 1 : extended - in this case, the mapping goes into [0, 2*N) range
244-
245222
verbose ( Bool ): whether to print some extra info [ default: False]
246223
247224
Returns:
@@ -261,7 +238,7 @@ def ovlp_mat_arb(SD1, SD2, _S, active_space=None, do_sort=False, user_notation=0
261238

262239
for n in range(0, N):
263240
for m in range(0, M):
264-
res[n, m] = ovlp_arb(SD1[n], SD2[m], S, active_space, do_sort, user_notation, verbose)
241+
res[n, m] = ovlp_arb(SD1[n], SD2[m], S, active_space, verbose)
265242

266243
return res
267244

unittests/test_mapping.py

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
elif sys.platform == "linux" or sys.platform == "linux2":
2727
from liblibra_core import *
2828

29-
from libra_py.workflows.nbra.mapping import sd2indx
29+
from libra_py.workflows.nbra.mapping import sd2indx, ovlp_arb
3030

31+
32+
#=============================== testing sd2indx ============================
3133
def generate():
3234
"""
3335
Run this function to see what is the current results
@@ -43,6 +45,7 @@ def generate():
4345
out, p = sd2indx(inp, nbasis=6, do_sort=do_sort, user_notation=user_notation)
4446
print(F"{inp} do_sort={do_sort}, user_notation={user_notation} -> {out}, {p}" )
4547

48+
#generate()
4649

4750
# Test cases
4851
test_data = [
@@ -84,3 +87,80 @@ def test_sd2indx(inp, do_sort, user_notation, expected_out, expected_p):
8487
assert out == expected_out, f"Output mismatch for inp={inp}, do_sort={do_sort}, user_notation={user_notation}"
8588
assert p == expected_p, f"Phase mismatch for inp={inp}, do_sort={do_sort}, user_notation={user_notation}"
8689

90+
91+
#================================== testing ovlp_arb =================================
92+
def generate2():
93+
S = np.eye(6)
94+
S[0,0] = 0.91
95+
S[1,1] = -0.98
96+
S[3,3] = 0.91
97+
S[4,4] = -0.98
98+
99+
basis = [ [1,-1, 2, -2],
100+
[-1,1, 2, -2],
101+
[1,-1, 2, -3],
102+
[1,-1,-2, 3]
103+
]
104+
for inp1 in basis:
105+
for inp2 in basis:
106+
# We should always sort - to get user-input-independent results
107+
# We always use doubled matrix notation - so that the translations work as intended
108+
out1, _ = sd2indx(inp1, nbasis=6, do_sort=do_sort, user_notation=user_notation)
109+
out2, _ = sd2indx(inp2, nbasis=6, do_sort=do_sort, user_notation=user_notation)
110+
x = ovlp_arb(inp1, inp2, S, None, False)
111+
print(F"<{inp1}|{inp2}> = <{out1}|{out2}> = {x}")
112+
113+
114+
#generate2()
115+
116+
117+
basis = [
118+
[1, -1, 2, -2],
119+
[-1, 1, 2, -2],
120+
[1, -1, 2, -3],
121+
[1, -1, -2, 3],
122+
]
123+
124+
125+
# Expected results
126+
expected = {
127+
((1,-1, 2,-2), (1,-1, 2,-2)): 0.79530724,
128+
((1,-1, 2,-2), (-1,1, 2,-2)): 0.79530724,
129+
((1,-1, 2,-2), (1,-1, 2,-3)): 0.0,
130+
((1,-1, 2,-2), (1,-1,-2, 3)): 0.0,
131+
132+
((-1,1, 2,-2), (1,-1, 2,-2)): 0.79530724,
133+
((-1,1, 2,-2), (-1,1, 2,-2)): 0.79530724,
134+
((-1,1, 2,-2), (1,-1, 2,-3)): 0.0,
135+
((-1,1, 2,-2), (1,-1,-2, 3)): 0.0,
136+
137+
((1,-1, 2,-3), (1,-1, 2,-2)): 0.0,
138+
((1,-1, 2,-3), (-1,1, 2,-2)): 0.0,
139+
((1,-1, 2,-3), (1,-1, 2,-3)): -0.8115380000000001,
140+
((1,-1, 2,-3), (1,-1,-2, 3)): 0.0,
141+
142+
((1,-1,-2, 3), (1,-1, 2,-2)): 0.0,
143+
((1,-1,-2, 3), (-1,1, 2,-2)): 0.0,
144+
((1,-1,-2, 3), (1,-1, 2,-3)): 0.0,
145+
((1,-1,-2, 3), (1,-1,-2, 3)): -0.8115380000000001,
146+
}
147+
148+
@pytest.mark.parametrize("inp1", basis)
149+
@pytest.mark.parametrize("inp2", basis)
150+
def test_overlap(inp1, inp2):
151+
152+
key = (tuple(inp1), tuple(inp2))
153+
x_expected = expected[key]
154+
155+
# Build the S matrix
156+
S = np.eye(6)
157+
S[0,0] = 0.91
158+
S[1,1] = -0.98
159+
S[3,3] = 0.91
160+
S[4,4] = -0.98
161+
162+
x = ovlp_arb(inp1, inp2, S, None, False)
163+
164+
# Numerical tolerance
165+
assert np.isclose(x, x_expected, atol=1e-12)
166+

0 commit comments

Comments
 (0)