Skip to content

Commit 56f9175

Browse files
committed
Merge branch 'sparse_parallel' into eom-dsrg
2 parents 4d2c5d1 + 2feb45e commit 56f9175

File tree

6 files changed

+148
-69
lines changed

6 files changed

+148
-69
lines changed

forte/api/sparse_exp_api.cc

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ namespace forte {
3939
void export_SparseExp(py::module& m) {
4040
py::class_<SparseExp>(m, "SparseExp", "A class to compute the exponential of a sparse operator")
4141
.def(py::init<int, double>(), "maxk"_a = 19, "screen_thresh"_a = 1.0e-12)
42+
.def("set_screen_thresh", &SparseExp::set_screen_thresh, "screen_thresh"_a)
43+
.def("set_maxk", &SparseExp::set_maxk, "maxk"_a)
4244
.def("apply_op",
4345
py::overload_cast<const SparseOperator&, const SparseState&, double>(
4446
&SparseExp::apply_op),

forte/api/sparse_fact_exp_api.cc

+3-2
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ void export_SparseFactExp(py::module& m) {
4141
m, "SparseFactExp",
4242
"A class to compute the product exponential of a sparse operator using factorization")
4343
.def(py::init<double>(), "screen_thresh"_a = 1.0e-12)
44-
.def("apply_op", &SparseFactExp::apply_op, "sop"_a, "state"_a, "inverse"_a = false)
44+
.def("set_screen_thresh", &SparseFactExp::set_screen_thresh, "screen_thresh"_a)
45+
.def("apply_op", &SparseFactExp::apply_op, "sop"_a, "state"_a, "inverse"_a = false, "reverse"_a = false)
4546
.def("apply_antiherm", &SparseFactExp::apply_antiherm, "sop"_a, "state"_a,
46-
"inverse"_a = false);
47+
"inverse"_a = false, "reverse"_a = false);
4748
}
4849

4950
} // namespace forte

forte/sparse_ci/sparse_exp.h

+4
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ class SparseExp {
9797
SparseState apply_antiherm(const SparseOperatorList& sop, const SparseState& state,
9898
double scaling_factor = 1.0);
9999

100+
void set_screen_thresh(double screen_thresh) { screen_thresh_ = screen_thresh; }
101+
102+
void set_maxk(int maxk) { maxk_ = maxk; }
103+
100104
private:
101105
int maxk_ = 19;
102106
double screen_thresh_ = 1e-12;

forte/sparse_ci/sparse_fact_exp.cc

+32-34
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ namespace forte {
3636
SparseFactExp::SparseFactExp(double screen_thresh) : screen_thresh_(screen_thresh) {}
3737

3838
SparseState SparseFactExp::apply_op(const SparseOperatorList& sop, const SparseState& state,
39-
bool inverse) {
39+
bool inverse, bool reverse) {
4040
// initialize a state object
4141
SparseState result(state);
4242

@@ -47,15 +47,10 @@ SparseState SparseFactExp::apply_op(const SparseOperatorList& sop, const SparseS
4747
Determinant sign_mask;
4848
Determinant idx;
4949
for (size_t m = 0, nterms = sop.size(); m < nterms; m++) {
50-
size_t n = inverse ? nterms - m - 1 : m;
50+
size_t n = (inverse ^ reverse) ? nterms - m - 1 : m;
5151
const auto& [sqop, coefficient] = sop(n);
52-
if (not sqop.is_nilpotent()) {
53-
std::string msg =
54-
"compute_on_the_fly_excitation is implemented only for nilpotent operators."
55-
"Operator " +
56-
sqop.str() + " is not nilpotent";
57-
throw std::runtime_error(msg);
58-
}
52+
bool is_idempotent = !sqop.is_nilpotent();
53+
5954
compute_sign_mask(sqop.cre(), sqop.ann(), sign_mask, idx);
6055
const auto t = (inverse ? -1.0 : 1.0) * coefficient;
6156
const auto screen_thresh_div_t = screen_thresh_ / std::abs(t);
@@ -66,9 +61,13 @@ SparseState SparseFactExp::apply_op(const SparseOperatorList& sop, const SparseS
6661
// test if we can apply this operator to this determinant
6762
if ((std::abs(c) > screen_thresh_div_t) and
6863
det.faster_can_apply_operator(sqop.cre(), sqop.ann())) {
69-
const auto sign =
70-
faster_apply_operator_to_det(det, new_det, sqop.cre(), sqop.ann(), sign_mask);
71-
new_terms.push_back(std::make_pair(new_det, c * t * sign));
64+
if (is_idempotent) {
65+
new_terms.emplace_back(det, c * (std::exp(t) - 1.0));
66+
} else {
67+
const auto sign = faster_apply_operator_to_det(det, new_det, sqop.cre(),
68+
sqop.ann(), sign_mask);
69+
new_terms.emplace_back(new_det, c * t * sign);
70+
}
7271
}
7372
}
7473
for (const auto& [det, c] : new_terms) {
@@ -82,7 +81,7 @@ SparseState SparseFactExp::apply_op(const SparseOperatorList& sop, const SparseS
8281
}
8382

8483
SparseState SparseFactExp::apply_antiherm(const SparseOperatorList& sop, const SparseState& state,
85-
bool inverse) {
84+
bool inverse, bool reverse) {
8685

8786
// initialize a state object
8887
SparseState result(state);
@@ -92,16 +91,11 @@ SparseState SparseFactExp::apply_antiherm(const SparseOperatorList& sop, const S
9291
Determinant sign_mask;
9392
Determinant idx;
9493
for (size_t m = 0, nterms = sop.size(); m < nterms; m++) {
95-
size_t n = inverse ? nterms - m - 1 : m;
94+
size_t n = (inverse ^ reverse) ? nterms - m - 1 : m;
9695

9796
const auto& [sqop, coefficient] = sop(n);
98-
if (not sqop.is_nilpotent()) {
99-
std::string msg =
100-
"compute_on_the_fly_antihermitian is implemented only for nilpotent operators."
101-
"Operator " +
102-
sqop.str() + " is not nilpotent";
103-
throw std::runtime_error(msg);
104-
}
97+
bool is_idempotent = !sqop.is_nilpotent();
98+
10599
compute_sign_mask(sqop.cre(), sqop.ann(), sign_mask, idx);
106100
const auto t = (inverse ? -1.0 : 1.0) * coefficient;
107101
const auto screen_thresh_div_t = screen_thresh_ / std::abs(t);
@@ -111,19 +105,23 @@ SparseState SparseFactExp::apply_antiherm(const SparseOperatorList& sop, const S
111105
// to have an amplitude less than screen_thresh
112106
// (here we use the approximation sin(x) ~ x, for x small)
113107
if (std::abs(c) > screen_thresh_div_t) {
114-
if (det.faster_can_apply_operator(sqop.cre(), sqop.ann())) {
115-
const auto theta = t * faster_apply_operator_to_det(det, new_det, sqop.cre(),
116-
sqop.ann(), sign_mask);
117-
new_terms.emplace_back(det, c * (std::cos(std::abs(theta)) - 1.0));
118-
new_terms.emplace_back(new_det, c * std::polar(1.0, std::arg(theta)) *
119-
std::sin(std::abs(theta)));
120-
} else if (det.faster_can_apply_operator(sqop.ann(), sqop.cre())) {
121-
const auto theta =
122-
-std::conj(t) * faster_apply_operator_to_det(det, new_det, sqop.ann(),
123-
sqop.cre(), sign_mask);
124-
new_terms.emplace_back(det, c * (std::cos(std::abs(theta)) - 1.0));
125-
new_terms.emplace_back(new_det, c * std::polar(1.0, std::arg(theta)) *
126-
std::sin(std::abs(theta)));
108+
if (is_idempotent and det.faster_can_apply_operator(sqop.cre(), sqop.ann())) {
109+
new_terms.emplace_back(det, c * (std::polar(1.0, 2.0 * std::imag(t)) - 1.0));
110+
} else {
111+
if (det.faster_can_apply_operator(sqop.cre(), sqop.ann())) {
112+
const auto theta = t * faster_apply_operator_to_det(
113+
det, new_det, sqop.cre(), sqop.ann(), sign_mask);
114+
new_terms.emplace_back(det, c * (std::cos(std::abs(theta)) - 1.0));
115+
new_terms.emplace_back(new_det, c * std::polar(1.0, std::arg(theta)) *
116+
std::sin(std::abs(theta)));
117+
} else if (det.faster_can_apply_operator(sqop.ann(), sqop.cre())) {
118+
const auto theta =
119+
-std::conj(t) * faster_apply_operator_to_det(det, new_det, sqop.ann(),
120+
sqop.cre(), sign_mask);
121+
new_terms.emplace_back(det, c * (std::cos(std::abs(theta)) - 1.0));
122+
new_terms.emplace_back(new_det, c * std::polar(1.0, std::arg(theta)) *
123+
std::sin(std::abs(theta)));
124+
}
127125
}
128126
}
129127
}

forte/sparse_ci/sparse_fact_exp.h

+9-2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ class SparseFactExp {
6464
///
6565
/// exp(-op1) exp(-op2) ... |state>
6666
///
67-
SparseState apply_op(const SparseOperatorList& sop, const SparseState& state, bool inverse);
67+
/// @param reverse If true, apply the operator in reverse order:
68+
/// ... exp(opN-1) exp(opN) |state>
69+
SparseState apply_op(const SparseOperatorList& sop, const SparseState& state,
70+
bool inverse, bool reverse = false);
6871

6972
/// @brief Compute the factorized exponential applied to a state using an exact algorithm
7073
///
@@ -82,8 +85,12 @@ class SparseFactExp {
8285
///
8386
/// exp(-op1 + op1^dagger) exp(-op2 + op2^dagger) ... |state>
8487
///
88+
/// @param reverse If true, apply the operator in reverse order:
89+
/// ... exp(opN-1) exp(opN) |state>
8590
SparseState apply_antiherm(const SparseOperatorList& sop, const SparseState& state,
86-
bool inverse);
91+
bool inverse, bool reverse = false);
92+
93+
void set_screen_thresh(double screen_thresh) { screen_thresh_ = screen_thresh; }
8794

8895
private:
8996
double screen_thresh_;

tests/pytest/sparse_ci/test_sparse_exp.py

+98-31
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
import numpy as np
44
import time
55
from forte import det
6+
import pytest
67

78

89
def test_sparse_exp_1():
9-
import pytest
10-
1110
### Test the linear operator ###
1211
op = forte.SparseOperator()
1312
ref = forte.SparseState({det("22"): 1.0})
@@ -26,6 +25,8 @@ def test_sparse_exp_1():
2625
assert wfn[det("0220")] == pytest.approx(0.15, abs=1e-9)
2726
assert wfn[det("2002")] == pytest.approx(-0.21, abs=1e-9)
2827

28+
29+
def test_sparse_exp_2():
2930
### Test the exponential operator with excitation operator ###
3031
op = forte.SparseOperator()
3132
ref = forte.SparseState({det("22"): 1.0})
@@ -53,6 +54,8 @@ def test_sparse_exp_1():
5354
assert wfn[det("+0-2")] == pytest.approx(-0.0077, abs=1e-9)
5455
assert wfn[det("-0+2")] == pytest.approx(-0.0077, abs=1e-9)
5556

57+
58+
def test_sparse_exp_3():
5659
### Test the exponential operator with antihermitian operator ###
5760
op = forte.SparseOperator()
5861
ref = forte.SparseState({det("22"): 1.0})
@@ -75,6 +78,8 @@ def test_sparse_exp_1():
7578
assert wfn2[det("+2-0")] == pytest.approx(0.0, abs=1e-9)
7679
assert wfn2[det("-2+0")] == pytest.approx(0.0, abs=1e-9)
7780

81+
82+
def test_sparse_exp_4():
7883
### Test the factorized exponential operator with an antihermitian operator ###
7984
op = forte.SparseOperatorList()
8085
op.add("[2a+ 0a-]", 0.1)
@@ -112,7 +117,6 @@ def test_sparse_exp_1():
112117
op.add("[1b+ 0b-]", 0.05)
113118
op.add("[2a+ 2b+ 1b- 1a-]", -0.07)
114119

115-
dtest = det("20")
116120
ref = forte.SparseState({det("20"): 0.5, det("02"): 0.8660254038})
117121
factexp = forte.SparseFactExp()
118122
wfn = factexp.apply_antiherm(op, ref)
@@ -123,56 +127,114 @@ def test_sparse_exp_1():
123127
assert wfn[det("020")] == pytest.approx(0.676180171388, abs=1e-9)
124128
assert wfn[det("-+0")] == pytest.approx(0.016058887563, abs=1e-9)
125129

126-
### Test the factorized exponential operator with an antihermitian operator with complex coefficients ###
130+
### Test idempotent operators with complex coefficients ###
127131
op = forte.SparseOperatorList()
128-
op.add("[1a+ 0a-]", 0.1 + 0.2j)
132+
op.add("[0a+ 0a-]", np.pi * 0.25j)
133+
exp = forte.SparseExp(maxk=100, screen_thresh=1e-15)
134+
factexp = forte.SparseFactExp()
135+
ref = forte.SparseState({forte.det("20"): 1.0})
136+
s1 = exp.apply_op(op, ref)
137+
s2 = factexp.apply_op(op, ref)
138+
assert s1[det("20")] == pytest.approx(s2[det("20")], abs=1e-9)
139+
assert s2[det("20")] == pytest.approx(np.sqrt(2) * (1.0 + 1.0j) / 2, abs=1e-9)
140+
s1 = exp.apply_antiherm(op, ref)
141+
s2 = factexp.apply_antiherm(op, ref)
142+
assert s1[det("20")] == pytest.approx(s2[det("20")], abs=1e-9)
143+
assert s2[det("20")] == pytest.approx(1.0j, abs=1e-9)
144+
op = forte.SparseOperatorList()
145+
op.add("[1a+ 1a-]", np.pi * 0.25j)
146+
s1 = exp.apply_antiherm(op, ref)
147+
s2 = factexp.apply_antiherm(op, ref)
148+
assert s1[det("20")] == pytest.approx(s2[det("20")], abs=1e-9)
149+
assert s2[det("20")] == pytest.approx(1.0, abs=1e-9)
129150

130-
op_inv = forte.SparseOperatorList()
131-
op_inv.add("[0a+ 1a-]", 0.1 - 0.2j)
132151

133-
exp = forte.SparseFactExp()
134-
ref = forte.SparseState({forte.det("20"): 0.5, forte.det("02"): 0.8660254038})
152+
def test_sparse_exp_5():
153+
### Test the reverse argument of factorized exponential operator ###
135154

136-
s1 = exp.apply_antiherm(op, ref)
137-
s2 = exp.apply_antiherm(op_inv, s1)
138-
assert s2[det("20")] == pytest.approx(0.5, abs=1e-9)
139-
assert s2[det("02")] == pytest.approx(0.8660254038, abs=1e-9)
155+
# this is the manually reversed op from test_sparse_exp_4
156+
op = forte.SparseOperatorList()
157+
op.add("[2a+ 2b+ 0b- 0a-]", 0.15)
158+
op.add("[2b+ 0b-]", 0.2)
159+
op.add("[2a+ 0a-]", 0.1)
160+
ref = forte.SparseState({det("22"): 1.0})
140161

141-
s1 = exp.apply_antiherm(op, ref, inverse=True)
142-
s2 = exp.apply_antiherm(op_inv, ref, inverse=False)
143-
assert s1 == s2
162+
factexp = forte.SparseFactExp()
163+
wfn = factexp.apply_antiherm(op, ref, reverse=True)
164+
165+
assert wfn[det("+2-0")] == pytest.approx(-0.197676811654, abs=1e-9)
166+
assert wfn[det("-2+0")] == pytest.approx(-0.097843395007, abs=1e-9)
167+
assert wfn[det("0220")] == pytest.approx(+0.165338757995, abs=1e-9)
168+
assert wfn[det("2200")] == pytest.approx(+0.961256283877, abs=1e-9)
169+
170+
wfn2 = factexp.apply_antiherm(op, wfn, inverse=True, reverse=True)
171+
172+
assert wfn2[det("2200")] == pytest.approx(1.0, abs=1e-9)
144173

145-
### Test the exponential operator with an antihermitian operator with complex coefficients ###
174+
op = forte.SparseOperatorList()
175+
op.add("[2a+ 2b+ 1b- 1a-]", -0.07)
176+
op.add("[1b+ 0b-]", 0.05)
177+
op.add("[1a+ 1b+ 0b- 0a-]", -0.3)
178+
op.add("[1a+ 0a-]", 0.1)
179+
180+
ref = forte.SparseState({det("20"): 0.5, det("02"): 0.8660254038})
181+
factexp = forte.SparseFactExp()
182+
wfn = factexp.apply_antiherm(op, ref, reverse=True)
183+
184+
assert wfn[det("200")] == pytest.approx(0.733340213919, abs=1e-9)
185+
assert wfn[det("+-0")] == pytest.approx(-0.049868863373, abs=1e-9)
186+
assert wfn[det("002")] == pytest.approx(-0.047410073759, abs=1e-9)
187+
assert wfn[det("020")] == pytest.approx(0.676180171388, abs=1e-9)
188+
assert wfn[det("-+0")] == pytest.approx(0.016058887563, abs=1e-9)
189+
190+
op = forte.SparseOperatorList()
191+
ref = forte.SparseState({det("22"): 1.0})
192+
op.add("[2a+ 0a-]", 0.1)
193+
op.add("[2b+ 0b-]", 0.1)
194+
op.add("[2a+ 2b+ 0b- 0a-]", 0.15)
195+
op.add("[3a+ 3b+ 1b- 1a-]", -0.077)
196+
197+
exp = forte.SparseFactExp()
198+
wfn = exp.apply_op(op, ref, reverse=True)
199+
assert wfn[det("2200")] == pytest.approx(1.0, abs=1e-9)
200+
assert wfn[det("0220")] == pytest.approx(0.16, abs=1e-9)
201+
assert wfn[det("+2-0")] == pytest.approx(-0.1, abs=1e-9)
202+
assert wfn[det("-2+0")] == pytest.approx(-0.1, abs=1e-9)
203+
assert wfn[det("2002")] == pytest.approx(-0.077, abs=1e-9)
204+
assert wfn[det("+0-2")] == pytest.approx(-0.0077, abs=1e-9)
205+
assert wfn[det("-0+2")] == pytest.approx(-0.0077, abs=1e-9)
206+
207+
208+
def test_sparse_exp_6():
209+
### Test the factorized exponential operator with an antihermitian operator with complex coefficients ###
146210
op = forte.SparseOperatorList()
147211
op.add("[1a+ 0a-]", 0.1 + 0.2j)
148-
op_explicit = forte.SparseOperatorList()
149-
op_explicit.add("[1a+ 0a-]", 0.1 + 0.2j)
150-
op_explicit.add("[0a+ 1a-]", -0.1 + 0.2j)
151212

152213
op_inv = forte.SparseOperatorList()
153214
op_inv.add("[0a+ 1a-]", 0.1 - 0.2j)
154-
op_inv_explicit = forte.SparseOperatorList()
155-
op_inv_explicit.add("[0a+ 1a-]", 0.1 - 0.2j)
156-
op_inv_explicit.add("[1a+ 0a-]", -0.1 - 0.2j)
157215

158-
exp = forte.SparseExp()
216+
exp = forte.SparseExp(maxk=100, screen_thresh=1e-15)
217+
factexp = forte.SparseFactExp()
159218
ref = forte.SparseState({forte.det("20"): 0.5, forte.det("02"): 0.8660254038})
160219

161220
s1 = exp.apply_antiherm(op, ref)
162-
s1_explicit = exp.apply_op(op_explicit, ref)
163-
assert s1 == s1_explicit
221+
s2 = factexp.apply_antiherm(op, ref)
222+
assert s1[det("20")] == pytest.approx(s2[det("20")], abs=1e-9)
223+
assert s1[det("02")] == pytest.approx(s2[det("02")], abs=1e-9)
224+
assert s1[det("+-")] == pytest.approx(s2[det("+-")], abs=1e-9)
225+
assert s1[det("-+")] == pytest.approx(s2[det("-+")], abs=1e-9)
226+
227+
s1 = exp.apply_antiherm(op, ref)
164228
s2 = exp.apply_antiherm(op_inv, s1)
165229
assert s2[det("20")] == pytest.approx(0.5, abs=1e-9)
166230
assert s2[det("02")] == pytest.approx(0.8660254038, abs=1e-9)
167-
s2_explicit = exp.apply_op(op_inv_explicit, s1)
168-
assert s2 == s2_explicit
169231

170-
s1 = exp.apply_antiherm(op, ref)
171-
s2 = exp.apply_antiherm(op_inv, ref, scaling_factor=-1.0)
232+
s1 = factexp.apply_antiherm(op, ref, inverse=True)
233+
s2 = factexp.apply_antiherm(op_inv, ref, inverse=False)
172234
assert s1 == s2
173235

174236

175-
def test_sparse_exp_2():
237+
def test_sparse_exp_7():
176238
# Compare the performance of the two methods to apply an operator to a state
177239
# when the operator all commute with each other
178240
norb = 10
@@ -239,3 +301,8 @@ def test_sparse_exp_2():
239301
if __name__ == "__main__":
240302
test_sparse_exp_1()
241303
test_sparse_exp_2()
304+
test_sparse_exp_3()
305+
test_sparse_exp_4()
306+
test_sparse_exp_5()
307+
test_sparse_exp_6()
308+
test_sparse_exp_7()

0 commit comments

Comments
 (0)