Skip to content

Commit ad08d94

Browse files
authored
Implement new algorithm and fix test cases (#438)
1 parent 99b96ae commit ad08d94

File tree

4 files changed

+102
-15
lines changed

4 files changed

+102
-15
lines changed

forte/modules/general_cc.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def make_cluster_operator(max_exc, naelpi, mo_space_info, psi4_wfn):
135135
den = e_bvir + e_avir - e_aocc - e_bocc
136136
denominators.append(den)
137137

138-
print(f"Number of amplitudes: {sop.size()}")
138+
print(f"Number of amplitudes: {len(sop)}")
139139
return (sop, denominators)
140140

141141

@@ -203,7 +203,7 @@ def solve_cc_equations(
203203
residual, e, e_proj = residual_equations(cc_type, t, op, selected_op, ref, ham, exp, compute_threshold, linked)
204204

205205
residual_norm = 0.0
206-
for l in range(selected_op.size()):
206+
for l in range(len(selected_op)):
207207
t[l] -= residual[op_pool[l]] / denominators[op_pool[l]]
208208
residual_norm += abs(residual[op_pool[l]]) ** 2
209209

@@ -442,15 +442,15 @@ def _run(self, data: ForteData) -> ForteData:
442442

443443
# the list of operators selected from the full list
444444
if self.select_type is None:
445-
op_pool = list(range(op.size()))
446-
t = [0.0] * op.size()
447-
print(f"\n The excitation operator pool contains {op.size()} elements")
445+
op_pool = list(range(len(op)))
446+
t = [0.0] * len(op)
447+
print(f"\n The excitation operator pool contains {len(op)} elements")
448448
else:
449449
raise RuntimeError("Selected CC methods are not implemented yet")
450450
print(f"\n Selecting operators using the {selec_type} scheme")
451451
t = []
452452
op_pool = []
453-
print(f"\n The selected operator pool contains {selected_op.size()} elements")
453+
print(f"\n The selected operator pool contains {len(selected_op)} elements")
454454

455455
old_e = 0.0
456456
start = time.time()

forte/sparse_ci/sparse_state_functions.cc

+90-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* that implements a variety of quantum chemistry methods for strongly
66
* correlated electrons.
77
*
8-
* Copyright (c) 2012-2024 by its authors (see COPYING, COPYING.LESSER, AUTHORS).
8+
* Copyright (c) 2012-2025 by its authors (see COPYING, COPYING.LESSER, AUTHORS).
99
*
1010
* The copyrights for code used from other parties are included in
1111
* the corresponding files.
@@ -39,20 +39,26 @@
3939

4040
namespace forte {
4141

42+
// This is a naive implementation of the operator application that is used for testing
4243
SparseState apply_operator_impl_naive(bool is_antihermitian, const SparseOperator& sop,
4344
const SparseState& state, double screen_thresh);
4445

46+
// This is the grouped implementation of the operator application. Fast, but scaling is not optimal.
4547
SparseState apply_operator_impl_grouped(bool is_antihermitian, const SparseOperator& sop,
4648
const SparseState& state, double screen_thresh);
4749

50+
// The default implementation is the grouped implementation with grouping into alfa strings
51+
SparseState apply_operator_impl_grouped_string(bool is_antihermitian, const SparseOperator& sop,
52+
const SparseState& state, double screen_thresh);
53+
4854
SparseState apply_operator_lin(const SparseOperator& sop, const SparseState& state,
4955
double screen_thresh) {
50-
return apply_operator_impl_grouped(false, sop, state, screen_thresh);
56+
return apply_operator_impl_grouped_string(false, sop, state, screen_thresh);
5157
}
5258

5359
SparseState apply_operator_antiherm(const SparseOperator& sop, const SparseState& state,
5460
double screen_thresh) {
55-
return apply_operator_impl_grouped(true, sop, state, screen_thresh);
61+
return apply_operator_impl_grouped_string(true, sop, state, screen_thresh);
5662
}
5763

5864
// This is a naive implementation of the operator application that is used for testing
@@ -172,6 +178,87 @@ SparseState apply_operator_impl_grouped(bool is_antihermitian, const SparseOpera
172178
return new_terms;
173179
}
174180

181+
// This is a kernel that applies the operator to the state using a grouped approach
182+
// It has a lower cost complexity
183+
// It assumes that the operator is grouped by the annihilation operators and that these are prepared
184+
// in another function calling this kernel
185+
template <bool positive>
186+
void apply_operator_kernel_string(const auto& sop_groups, const auto& state_groups,
187+
const auto& screen_thresh, auto& new_terms) {
188+
Determinant new_det;
189+
Determinant sign_mask;
190+
Determinant idx;
191+
for (const auto& [sqop_ann_a, sqop_group] : sop_groups) {
192+
for (const auto& [det_a, state_group] : state_groups) {
193+
// can we annihilate the alfa string?
194+
if (det_a.fast_a_and_b_equal_b(sqop_ann_a)) {
195+
// loop over the creation operators in this group
196+
for (const auto& [sqop_ann, sqop_cre, t] : sqop_group) {
197+
for (const auto& [det, c] : state_group) {
198+
if (det.faster_can_apply_operator(sqop_cre, sqop_ann)) {
199+
if (std::abs(c * t) > screen_thresh) {
200+
compute_sign_mask(sqop_cre, sqop_ann, sign_mask, idx);
201+
const auto value = faster_apply_operator_to_det(
202+
det, new_det, sqop_cre, sqop_ann, sign_mask);
203+
if constexpr (positive) {
204+
new_terms[new_det] += value * t * c;
205+
} else {
206+
new_terms[new_det] -= value * t * c;
207+
}
208+
}
209+
}
210+
}
211+
}
212+
}
213+
}
214+
}
215+
}
216+
217+
// This is the grouped implementation of the operator application. It mostly prepares the
218+
// operator and state and then calls the kernel to apply the operator
219+
SparseState apply_operator_impl_grouped_string(bool is_antihermitian, const SparseOperator& sop,
220+
const SparseState& state, double screen_thresh) {
221+
if (screen_thresh < 0) {
222+
throw std::invalid_argument(
223+
"apply_operator_impl_grouped:screen_thresh must be non-negative");
224+
}
225+
SparseState new_terms;
226+
227+
// Group the determinants by common alfa strings
228+
std::unordered_map<String, std::vector<std::pair<Determinant, sparse_scalar_t>>, String::Hash>
229+
state_groups;
230+
for (const auto& [det, c] : state) {
231+
state_groups[det.get_alfa_bits()].emplace_back(det, c);
232+
}
233+
234+
// Group the operators by common alfa annihilation strings
235+
std::unordered_map<String, std::vector<std::tuple<Determinant, Determinant, sparse_scalar_t>>,
236+
String::Hash>
237+
sop_groups;
238+
for (const auto& [sqop, t] : sop.elements()) {
239+
sop_groups[sqop.ann().get_alfa_bits()].emplace_back(sqop.ann(), sqop.cre(), t);
240+
}
241+
242+
// Call the kernel to apply the operator (adding the result)
243+
apply_operator_kernel_string<true>(sop_groups, state_groups, screen_thresh, new_terms);
244+
245+
if (not is_antihermitian) {
246+
return new_terms;
247+
}
248+
249+
// Group the operators by common alfa creation strings
250+
// Here we swap the annihilation and creation operators for the antihermitian case
251+
sop_groups.clear();
252+
for (const auto& [sqop, t] : sop.elements()) {
253+
sop_groups[sqop.cre().get_alfa_bits()].emplace_back(sqop.cre(), sqop.ann(), t);
254+
}
255+
256+
// Call the kernel to apply the operator (subtracting the result)
257+
apply_operator_kernel_string<false>(sop_groups, state_groups, screen_thresh, new_terms);
258+
259+
return new_terms;
260+
}
261+
175262
std::vector<sparse_scalar_t> get_projection(const SparseOperatorList& sop, const SparseState& ref,
176263
const SparseState& state) {
177264
local_timer t;

tests/performance/sparse/sparse_operator_timing.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def sparse_operator_correctness():
4343
opH = forte.sparse_operator_hamiltonian(as_ints)
4444

4545
print("\n\n Number of determinants: ", len(dets))
46-
print(" Number of integrals: ", opH.size())
46+
print(" Number of integrals: ", len(opH))
4747

4848
# Apply the Hamiltonian to a state that spans the entire Hilbert space
4949
c = 1 / np.sqrt(len(dets))
@@ -86,7 +86,7 @@ def sparse_operator_timing_1():
8686
opH = forte.sparse_operator_hamiltonian(as_ints)
8787

8888
print("\n\n Number of determinants: ", len(dets))
89-
print(" Number of integrals: ", opH.size())
89+
print(" Number of integrals: ", len(opH))
9090

9191
# Apply the Hamiltonian to a state that spans the entire Hilbert space
9292
c = 1 / np.sqrt(len(dets))
@@ -133,7 +133,7 @@ def sparse_operator_timing_2():
133133

134134
ndets = 7000
135135
print("\n\n Number of determinants: ", ndets)
136-
print(" Number of integrals: ", opH.size())
136+
print(" Number of integrals: ", len(opH))
137137

138138
# Apply the Hamiltonian to a state that spans 7000 determinants
139139
c = 1 / np.sqrt(ndets)
@@ -188,7 +188,7 @@ def sparse_operator_timing_3():
188188

189189
ndets = 20000
190190
print("\n\n Number of determinants: ", ndets)
191-
print(" Number of integrals: ", opH.size())
191+
print(" Number of integrals: ", len(opH))
192192

193193
# Apply the Hamiltonian to a state that spans 7000 determinants
194194
c = 1 / np.sqrt(ndets)

tests/pytest/sparse_ci/test_sparse_operator.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ def test_sparse_operator_list_reverse():
475475
sopl.add("[1a+ 1a-]", 1.0)
476476
sopl.add("[0a+ 0a-]", 2.0)
477477
reversed_sopl = sopl.reverse()
478-
assert sopl.size() == 2
478+
assert len(sopl) == 2
479479
assert reversed_sopl[0] == 2.0
480480
assert reversed_sopl[1] == 1.0
481481
assert reversed_sopl(0)[0].str() == "[0a+ 0a-]"
@@ -487,7 +487,7 @@ def test_sparse_operator_list_remove():
487487
sopl.add("[1a+ 1a-]", 1.0)
488488
sopl.add("[0a+ 0a-]", 1.0)
489489
sopl.remove("[1a+ 1a-]")
490-
assert sopl.size() == 1
490+
assert len(sopl) == 1
491491

492492

493493
if __name__ == "__main__":

0 commit comments

Comments
 (0)