Skip to content

Commit 6aafbfa

Browse files
hsim13372jarrodmcc
authored andcommitted
Added sparse functionality to uccsd_operator (#65)
* Accounted for sparsity of inputs for uccsd_operator and expanded general functionality to include both sparse nd arrays and sparse list formats.
1 parent 04848b2 commit 6aafbfa

File tree

2 files changed

+103
-23
lines changed

2 files changed

+103
-23
lines changed

src/fermilib/utils/_unitary_cc.py

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,23 @@ def uccsd_operator(single_amplitudes, double_amplitudes, anti_hermitian=True):
3232
"""Create a fermionic operator that is the generator of uccsd.
3333
3434
This a the most straight-forward method to generate UCCSD operators,
35-
however it is slightly inefficient. In particular, it parameterizes
36-
all possible exictations, so it represents a generalized unitary coupled
35+
however it is slightly inefficient. In particular, it parameterizes
36+
all possible excitations, so it represents a generalized unitary coupled
3737
cluster ansatz, but also does not explicitly enforce the uniqueness
38-
in paramterization, so it is redundant. For example there will be a linear
38+
in parametrization, so it is redundant. For example there will be a linear
3939
dependency in the ansatz of single_amplitudes[i,j] and
4040
single_amplitudes[j,i].
4141
4242
Args:
43-
single_amplitudes(ndarray): [NxN] array storing single excitation
44-
amplitudes corresponding to t[i,j] * (a_i^\dagger a_j + H.C.)
45-
double_amplitudes(ndarray): [NxNxNxN] array storing double excitation
43+
single_amplitudes(list or ndarray): list of lists with each sublist
44+
storing a list of indices followed by single excitation amplitudes
45+
i.e. [[[i,j],t_ij], ...] OR [NxN] array storing single excitation
4646
amplitudes corresponding to
47+
t[i,j] * (a_i^\dagger a_j + H.C.)
48+
double_amplitudes(list or ndarray): list of lists with each sublist
49+
storing a list of indices followed by double excitation amplitudes
50+
i.e. [[[i,j,k,l],t_ijkl], ...] OR [NxNxNxN] array storing double
51+
excitation amplitudes corresponding to
4752
t[i,j,k,l] * (a_i^\dagger a_j a_k^\dagger a_l + H.C.)
4853
anti_hermitian(Bool): Flag to generate only normal CCSD operator
4954
rather than unitary variant, primarily for testing
@@ -52,34 +57,63 @@ def uccsd_operator(single_amplitudes, double_amplitudes, anti_hermitian=True):
5257
uccsd_generator(FermionOperator): Anti-hermitian fermion operator that
5358
is the generator for the uccsd wavefunction.
5459
"""
55-
n_orbitals = single_amplitudes.shape[0]
56-
assert(n_orbitals == double_amplitudes.shape[0])
60+
5761
uccsd_generator = FermionOperator()
5862

63+
# Re-format inputs (ndarrays to lists) if necessary
64+
if (isinstance(single_amplitudes, numpy.ndarray) or
65+
isinstance(double_amplitudes, numpy.ndarray)):
66+
single_amplitudes, double_amplitudes = convert_amplitude_format(
67+
single_amplitudes,
68+
double_amplitudes)
69+
5970
# Add single excitations
60-
for i, j in itertools.product(range(n_orbitals), repeat=2):
61-
if single_amplitudes[i, j] == 0.:
62-
continue
63-
uccsd_generator += FermionOperator(((i, 1), (j, 0)),
64-
single_amplitudes[i, j])
71+
for (i, j), t_ij in single_amplitudes:
72+
i, j = int(i), int(j)
73+
uccsd_generator += FermionOperator(((i, 1), (j, 0)), t_ij)
6574
if anti_hermitian:
66-
uccsd_generator += FermionOperator(((j, 1), (i, 0)),
67-
-single_amplitudes[i, j])
75+
uccsd_generator += FermionOperator(((j, 1), (i, 0)), -t_ij)
6876

69-
# Add double excitations
70-
for i, j, k, l in itertools.product(range(n_orbitals), repeat=4):
71-
if double_amplitudes[i, j, k, l] == 0.:
72-
continue
77+
# Add double excitations
78+
for (i, j, k, l), t_ijkl in double_amplitudes:
79+
i, j, k, l = int(i), int(j), int(k), int(l)
7380
uccsd_generator += FermionOperator(
74-
((i, 1), (j, 0), (k, 1), (l, 0)),
75-
double_amplitudes[i, j, k, l])
81+
((i, 1), (j, 0), (k, 1), (l, 0)), t_ijkl)
7682
if anti_hermitian:
7783
uccsd_generator += FermionOperator(
78-
((l, 1), (k, 0), (j, 1), (i, 0)),
79-
-double_amplitudes[i, j, k, l])
84+
((l, 1), (k, 0), (j, 1), (i, 0)), -t_ijkl)
8085
return uccsd_generator
8186

8287

88+
def convert_amplitude_format(single_amplitudes, double_amplitudes):
89+
"""Re-format single_amplitudes and double_amplitudes from ndarrays to lists.
90+
91+
Args:
92+
single_amplitudes(ndarray): [NxN] array storing single excitation
93+
amplitudes corresponding to t[i,j] * (a_i^\dagger a_j + H.C.)
94+
double_amplitudes(ndarray): [NxNxNxN] array storing double excitation
95+
amplitudes corresponding to
96+
t[i,j,k,l] * (a_i^\dagger a_j a_k^\dagger a_l + H.C.)
97+
98+
Returns:
99+
single_amplitudes_list(list): list of lists with each sublist storing
100+
a list of indices followed by single excitation amplitudes
101+
i.e. [[[i,j],t_ij], ...]
102+
double_amplitudes_list(list): list of lists with each sublist storing
103+
a list of indices followed by double excitation amplitudes
104+
i.e. [[[i,j,k,l],t_ijkl], ...]
105+
"""
106+
single_amplitudes_list, double_amplitudes_list = [], []
107+
108+
for i, j in zip(*single_amplitudes.nonzero()):
109+
single_amplitudes_list.append([[i, j], single_amplitudes[i, j]])
110+
111+
for i, j, k, l in zip(*double_amplitudes.nonzero()):
112+
double_amplitudes_list.append([[i, j, k, l],
113+
double_amplitudes[i, j, k, l]])
114+
return single_amplitudes_list, double_amplitudes_list
115+
116+
83117
def uccsd_singlet_paramsize(n_qubits, n_electrons):
84118
"""Determine number of independent amplitudes for singlet UCCSD
85119

src/fermilib/utils/_unitary_cc_test.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,52 @@ def test_simulation_with_graph(self):
168168
All(Measure) | wavefunction
169169
self.assertAlmostEqual(energy, -1.13727017463)
170170

171+
def test_sparse_uccsd_operator_numpy_inputs(self):
172+
"""Test numpy ndarray inputs to uccsd_operator that are sparse"""
173+
test_orbitals = 30
174+
sparse_single_amplitudes = numpy.zeros((test_orbitals, test_orbitals))
175+
sparse_double_amplitudes = numpy.zeros((test_orbitals, test_orbitals,
176+
test_orbitals, test_orbitals))
177+
178+
sparse_single_amplitudes[3, 5] = 0.12345
179+
sparse_single_amplitudes[12, 4] = 0.44313
180+
181+
sparse_double_amplitudes[0, 12, 6, 2] = 0.3434
182+
sparse_double_amplitudes[1, 4, 6, 13] = -0.23423
183+
184+
generator = uccsd_operator(sparse_single_amplitudes,
185+
sparse_double_amplitudes)
186+
187+
test_generator = (0.12345 * FermionOperator("3^ 5") +
188+
(-0.12345) * FermionOperator("5^ 3") +
189+
0.44313 * FermionOperator("12^ 4") +
190+
(-0.44313) * FermionOperator("4^ 12") +
191+
0.3434 * FermionOperator("0^ 12 6^ 2") +
192+
(-0.3434) * FermionOperator("2^ 6 12^ 0") +
193+
(-0.23423) * FermionOperator("1^ 4 6^ 13") +
194+
0.23423 * FermionOperator("13^ 6 4^ 1"))
195+
self.assertTrue(test_generator.isclose(generator))
196+
197+
def test_sparse_uccsd_operator_list_inputs(self):
198+
"""Test list inputs to uccsd_operator that are sparse"""
199+
sparse_single_amplitudes = [[[3, 5], 0.12345],
200+
[[12, 4], 0.44313]]
201+
sparse_double_amplitudes = [[[0, 12, 6, 2], 0.3434],
202+
[[1, 4, 6, 13], -0.23423]]
203+
204+
generator = uccsd_operator(sparse_single_amplitudes,
205+
sparse_double_amplitudes)
206+
207+
test_generator = (0.12345 * FermionOperator("3^ 5") +
208+
(-0.12345) * FermionOperator("5^ 3") +
209+
0.44313 * FermionOperator("12^ 4") +
210+
(-0.44313) * FermionOperator("4^ 12") +
211+
0.3434 * FermionOperator("0^ 12 6^ 2") +
212+
(-0.3434) * FermionOperator("2^ 6 12^ 0") +
213+
(-0.23423) * FermionOperator("1^ 4 6^ 13") +
214+
0.23423 * FermionOperator("13^ 6 4^ 1"))
215+
self.assertTrue(test_generator.isclose(generator))
216+
171217
# Run test.
172218
if __name__ == '__main__':
173219
unittest.main()

0 commit comments

Comments
 (0)