Skip to content

Commit 49aad21

Browse files
authored
Merge pull request #261 from imagoulas/2qubit_gate_sparse_matrix
Added sparse matrix representation of 2qubit gates and updated tests
2 parents 0a515a9 + 1fcfb53 commit 49aad21

File tree

2 files changed

+225
-14
lines changed

2 files changed

+225
-14
lines changed

src/qforte/gate.cc

+56-14
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,67 @@ size_t Gate::control() const { return control_; }
2727
const complex_4_4_mat& Gate::matrix() const { return gate_; }
2828

2929
const SparseMatrix Gate::sparse_matrix(size_t nqubit) const {
30-
size_t nbasis = std::pow(2, nqubit);
31-
if (target_ != control_) {
32-
throw std::runtime_error("Gate must be a Pauli to convert to matrix!");
33-
} else if (target_ >= nqubit) {
30+
if (target_ >= nqubit) {
3431
throw std::runtime_error("Target index is too large for specified nqbits!");
3532
}
33+
if (control_ >= nqubit) {
34+
throw std::runtime_error("Control index is too large for specified nqbits!");
35+
}
3636

37+
size_t nbasis = std::pow(2, nqubit);
3738
SparseMatrix Spmat = SparseMatrix();
3839

39-
for (size_t i = 0; i < 2; i++) {
40-
for (size_t j = 0; j < 2; j++) {
41-
auto op_i_j = gate_[i][j];
42-
if (std::abs(op_i_j) > 1.0e-16) {
43-
for (size_t I = 0; I < nbasis; I++) {
44-
QubitBasis basis_I = QubitBasis(I);
45-
if (basis_I.get_bit(target_) == j) {
46-
QubitBasis basis_J = basis_I;
47-
basis_J.set_bit(target_, i);
48-
Spmat.set_element(basis_J.index(), basis_I.index(), op_i_j);
40+
if (target_ == control_) {
41+
// single-qubit case
42+
// Iterate over the gate matrix elements
43+
for (size_t i = 0; i < 2; i++) {
44+
for (size_t j = 0; j < 2; j++) {
45+
auto op_i_j = gate_[i][j];
46+
// Consider only non-zero elements (within a tolerance)
47+
if (std::abs(op_i_j) > 1.0e-14) {
48+
// Iterate over all basis states
49+
for (size_t I = 0; I < nbasis; I++) {
50+
QubitBasis basis_I = QubitBasis(I);
51+
// Check if the target qubit is in the correct state
52+
if (basis_I.get_bit(target_) == j) {
53+
QubitBasis basis_J = basis_I;
54+
// Set the target qubit to the new state
55+
basis_J.set_bit(target_, i);
56+
// Set the corresponding element in the sparse matrix
57+
Spmat.set_element(basis_J.index(), basis_I.index(), op_i_j);
58+
}
59+
}
60+
}
61+
}
62+
}
63+
} else {
64+
// two-qubit gate
65+
for (size_t i = 0; i < 4; i++) {
66+
for (size_t j = 0; j < 4; j++) {
67+
auto op_i_j = gate_[i][j];
68+
if (std::abs(op_i_j) > 1.0e-14) {
69+
for (size_t I = 0; I < nbasis; I++) {
70+
QubitBasis basis_I = QubitBasis(I);
71+
size_t target_bit = basis_I.get_bit(target_);
72+
size_t control_bit = basis_I.get_bit(control_);
73+
74+
// Combine the target and control bits into a 2-bit state
75+
size_t combined_state = (control_bit << 1) | target_bit;
76+
77+
// Check if the combined state matches the gate matrix column index
78+
if (combined_state == j) {
79+
QubitBasis basis_J = basis_I;
80+
81+
// Extract the new states for control and target bits
82+
size_t new_control_bit = (i >> 1) & 1;
83+
size_t new_target_bit = i & 1;
84+
85+
// Set the target and control qubits to the new states
86+
basis_J.set_bit(target_, new_target_bit);
87+
basis_J.set_bit(control_, new_control_bit);
88+
89+
Spmat.set_element(basis_J.index(), basis_I.index(), op_i_j);
90+
}
4991
}
5092
}
5193
}

tests/test_sparse_op.py

+169
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from qforte import build_circuit, Computer, QubitOperator
22
import numpy as np
3+
import qforte as qf
4+
import random
5+
from copy import deepcopy
36

47

58
class TestSparseOp:
@@ -102,3 +105,169 @@ def test_sparse_operator(self):
102105
print("Operator used for sparse matrix operator test: \n", qubit_op)
103106
print("||∆||: ", diff_val)
104107
assert diff_val < 1.0e-15
108+
109+
def test_sparse_gates(self):
110+
"""
111+
This test ensures that the sparse matrix representations of various
112+
gates agrees with those obtained using the kronecker product with
113+
the identity matrix
114+
"""
115+
116+
def sparse_matrix_to_numpy_array(sp_mat: dict, dim: int) -> np.array:
117+
mat = np.zeros((dim, dim), dtype=complex)
118+
for row in sp_mat:
119+
for column in sp_mat[row]:
120+
mat[row, column] = sp_mat[row][column]
121+
return mat
122+
123+
one_qubit_gate_pool = [
124+
"X",
125+
"Y",
126+
"Z",
127+
"Rx",
128+
"Ry",
129+
"Rz",
130+
"H",
131+
"S",
132+
"T",
133+
"R",
134+
"V",
135+
"adj(V)",
136+
]
137+
138+
two_qubit_gate_pool = [
139+
"CNOT",
140+
"aCNOT",
141+
"cY",
142+
"cZ",
143+
"cV",
144+
"SWAP",
145+
"cRz",
146+
"cR",
147+
"A",
148+
"adj(cV)",
149+
]
150+
151+
parametrized_gate_periods = {
152+
"Rx": 4 * np.pi,
153+
"Ry": 4 * np.pi,
154+
"Rz": 4 * np.pi,
155+
"R": 2 * np.pi,
156+
"cR": 2 * np.pi,
157+
"cRz": 4 * np.pi,
158+
"A": 2 * np.pi,
159+
}
160+
161+
dim = 2
162+
identity = np.eye(2)
163+
for gatetype in one_qubit_gate_pool:
164+
if gatetype in parametrized_gate_periods:
165+
period = parametrized_gate_periods[gatetype]
166+
parameter = random.uniform(-period / 2, period / 2)
167+
gate_0 = qf.gate(gatetype, 0, parameter)
168+
gate_1 = qf.gate(gatetype, 1, parameter)
169+
else:
170+
if gatetype == "adj(V)":
171+
gate_0 = qf.gate("V", 0)
172+
gate_0 = gate_0.adjoint()
173+
gate_1 = qf.gate("V", 1)
174+
gate_1 = gate_1.adjoint()
175+
else:
176+
gate_0 = qf.gate(gatetype, 0)
177+
gate_1 = qf.gate(gatetype, 1)
178+
sparse_mat_0 = gate_0.sparse_matrix(2)
179+
sparse_mat_1 = gate_1.sparse_matrix(2)
180+
mat = sparse_matrix_to_numpy_array(gate_0.sparse_matrix(1).to_map(), dim)
181+
mat_0 = sparse_matrix_to_numpy_array(sparse_mat_0.to_map(), dim * 2)
182+
mat_1 = sparse_matrix_to_numpy_array(sparse_mat_1.to_map(), dim * 2)
183+
mat_0_kron = np.kron(identity, mat)
184+
mat_1_kron = np.kron(mat, identity)
185+
assert np.all(mat_0 == mat_0_kron)
186+
assert np.all(mat_1 == mat_1_kron)
187+
188+
dim = 4
189+
for gatetype in two_qubit_gate_pool:
190+
if gatetype in parametrized_gate_periods:
191+
period = parametrized_gate_periods[gatetype]
192+
parameter = random.uniform(-period / 2, period / 2)
193+
gate_01 = qf.gate(gatetype, 0, 1, parameter)
194+
gate_10 = qf.gate(gatetype, 1, 0, parameter)
195+
gate_12 = qf.gate(gatetype, 1, 2, parameter)
196+
gate_21 = qf.gate(gatetype, 2, 1, parameter)
197+
else:
198+
if gatetype == "adj(cV)":
199+
gate_01 = qf.gate("cV", 0, 1)
200+
gate_01 = gate_01.adjoint()
201+
gate_10 = qf.gate("cV", 1, 0)
202+
gate_10 = gate_10.adjoint()
203+
gate_12 = qf.gate("cV", 1, 2)
204+
gate_12 = gate_12.adjoint()
205+
gate_21 = qf.gate("cV", 2, 1)
206+
gate_21 = gate_21.adjoint()
207+
else:
208+
gate_01 = qf.gate(gatetype, 0, 1)
209+
gate_10 = qf.gate(gatetype, 1, 0)
210+
gate_12 = qf.gate(gatetype, 1, 2)
211+
gate_21 = qf.gate(gatetype, 2, 1)
212+
sparse_mat_01 = gate_01.sparse_matrix(3)
213+
sparse_mat_10 = gate_10.sparse_matrix(3)
214+
sparse_mat_12 = gate_12.sparse_matrix(3)
215+
sparse_mat_21 = gate_21.sparse_matrix(3)
216+
mat1 = sparse_matrix_to_numpy_array(gate_01.sparse_matrix(2).to_map(), dim)
217+
mat2 = sparse_matrix_to_numpy_array(gate_10.sparse_matrix(2).to_map(), dim)
218+
mat_01 = sparse_matrix_to_numpy_array(sparse_mat_01.to_map(), dim * 2)
219+
mat_10 = sparse_matrix_to_numpy_array(sparse_mat_10.to_map(), dim * 2)
220+
mat_12 = sparse_matrix_to_numpy_array(sparse_mat_12.to_map(), dim * 2)
221+
mat_21 = sparse_matrix_to_numpy_array(sparse_mat_21.to_map(), dim * 2)
222+
mat_01_kron = np.kron(identity, mat1)
223+
mat_10_kron = np.kron(identity, mat2)
224+
mat_12_kron = np.kron(mat1, identity)
225+
mat_21_kron = np.kron(mat2, identity)
226+
assert np.all(mat_01 == mat_01_kron)
227+
assert np.all(mat_10 == mat_10_kron)
228+
assert np.all(mat_12 == mat_12_kron)
229+
assert np.all(mat_21 == mat_21_kron)
230+
231+
def test_circuit_sparse_matrix(self):
232+
Rhh = 1.5
233+
234+
mol = qf.system_factory(
235+
system_type="molecule",
236+
build_type="psi4",
237+
basis="sto-6g",
238+
mol_geometry=[
239+
("H", (0, 0, -3 * Rhh / 2)),
240+
("H", (0, 0, -Rhh / 2)),
241+
("H", (0, 0, Rhh / 2)),
242+
("H", (0, 0, 3 * Rhh / 2)),
243+
],
244+
symmetry="d2h",
245+
multiplicity=1,
246+
charge=0,
247+
num_frozen_docc=0,
248+
num_frozen_uocc=0,
249+
run_mp2=0,
250+
run_ccsd=0,
251+
run_cisd=0,
252+
run_fci=0,
253+
)
254+
255+
for compact in [False, True]:
256+
uccsd = qf.UCCNVQE(
257+
mol, compact_excitations=compact, qubit_excitations=False
258+
)
259+
uccsd.run(
260+
pool_type="SD", opt_maxiter=200, optimizer="bfgs", opt_thresh=1.0e-5
261+
)
262+
263+
qc1 = qf.Computer(mol.hamiltonian.num_qubits())
264+
qc1.apply_circuit(uccsd.build_Uvqc())
265+
coeff_vec1 = deepcopy(qc1.get_coeff_vec())
266+
267+
Uvqc = uccsd.build_Uvqc()
268+
sparse_Uvqc = Uvqc.sparse_matrix(mol.hamiltonian.num_qubits())
269+
qc2 = qf.Computer(mol.hamiltonian.num_qubits())
270+
qc2.apply_sparse_matrix(sparse_Uvqc)
271+
coeff_vec2 = qc2.get_coeff_vec()
272+
273+
assert np.linalg.norm(np.array(coeff_vec2) - np.array(coeff_vec1)) < 1.0e-14

0 commit comments

Comments
 (0)