qaoalib 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qaoalib-0.3.0/MANIFEST.in +1 -0
- {qaoalib-0.2.0 → qaoalib-0.3.0}/PKG-INFO +31 -9
- {qaoalib-0.2.0 → qaoalib-0.3.0}/README.md +17 -6
- qaoalib-0.3.0/qaoalib/ansatz.py +222 -0
- qaoalib-0.3.0/qaoalib/solver/__init__.py +1 -0
- qaoalib-0.3.0/qaoalib/solver/lcc_vqe.py +118 -0
- {qaoalib-0.2.0 → qaoalib-0.3.0}/qaoalib/solver/vqe.py +13 -31
- qaoalib-0.3.0/qaoalib/version.py +1 -0
- {qaoalib-0.2.0 → qaoalib-0.3.0}/qaoalib.egg-info/PKG-INFO +31 -9
- qaoalib-0.3.0/qaoalib.egg-info/SOURCES.txt +21 -0
- {qaoalib-0.2.0 → qaoalib-0.3.0}/qaoalib.egg-info/requires.txt +2 -1
- qaoalib-0.3.0/requirements.txt +11 -0
- {qaoalib-0.2.0 → qaoalib-0.3.0}/setup.py +6 -13
- qaoalib-0.2.0/qaoalib/qaoa/__init__.py +0 -3
- qaoalib-0.2.0/qaoalib/qaoa/landscape/__init__.py +0 -3
- qaoalib-0.2.0/qaoalib/qaoa/landscape/base.py +0 -90
- qaoalib-0.2.0/qaoalib/qaoa/landscape/direct_numpy.py +0 -79
- qaoalib-0.2.0/qaoalib/qaoa/landscape/hadamard_test.py +0 -39
- qaoalib-0.2.0/qaoalib/qaoa/landscape/hybrid_fast.py +0 -44
- qaoalib-0.2.0/qaoalib/qaoa/layerwise.py +0 -184
- qaoalib-0.2.0/qaoalib/qaoa/qis.py +0 -59
- qaoalib-0.2.0/qaoalib/qaoa/qmc.py +0 -51
- qaoalib-0.2.0/qaoalib/qaoa/strategy.py +0 -70
- qaoalib-0.2.0/qaoalib/qaoa/utils.py +0 -135
- qaoalib-0.2.0/qaoalib/solver/__init__.py +0 -1
- qaoalib-0.2.0/qaoalib/version.py +0 -1
- qaoalib-0.2.0/qaoalib.egg-info/SOURCES.txt +0 -29
- qaoalib-0.2.0/test/test.py +0 -3
- {qaoalib-0.2.0 → qaoalib-0.3.0}/LICENSE +0 -0
- {qaoalib-0.2.0 → qaoalib-0.3.0}/qaoalib/__init__.py +0 -0
- {qaoalib-0.2.0 → qaoalib-0.3.0}/qaoalib/json.py +0 -0
- {qaoalib-0.2.0 → qaoalib-0.3.0}/qaoalib/math.py +0 -0
- {qaoalib-0.2.0 → qaoalib-0.3.0}/qaoalib/models/__init__.py +0 -0
- {qaoalib-0.2.0 → qaoalib-0.3.0}/qaoalib/models/result.py +0 -0
- {qaoalib-0.2.0 → qaoalib-0.3.0}/qaoalib/utils.py +0 -0
- {qaoalib-0.2.0 → qaoalib-0.3.0}/qaoalib.egg-info/dependency_links.txt +0 -0
- {qaoalib-0.2.0 → qaoalib-0.3.0}/qaoalib.egg-info/top_level.txt +0 -0
- {qaoalib-0.2.0 → qaoalib-0.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include requirements.txt
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: qaoalib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A package for QAOA Maxcut calculations
|
|
5
5
|
Home-page: https://github.com/xenoicwyce/qaoalib
|
|
6
6
|
Author: Xinwei Lee
|
|
@@ -13,23 +13,30 @@ Description-Content-Type: text/markdown
|
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: qiskit
|
|
15
15
|
Requires-Dist: qiskit_optimization
|
|
16
|
+
Requires-Dist: qiskit_algorithms
|
|
16
17
|
Requires-Dist: qiskit_aer
|
|
17
|
-
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: numpy
|
|
18
19
|
Requires-Dist: scipy
|
|
19
20
|
Requires-Dist: pytest
|
|
20
21
|
Requires-Dist: pytest-cov
|
|
21
22
|
Requires-Dist: matplotlib
|
|
22
23
|
Requires-Dist: networkx
|
|
23
24
|
Requires-Dist: pydantic
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: author-email
|
|
27
|
+
Dynamic: classifier
|
|
28
|
+
Dynamic: description
|
|
29
|
+
Dynamic: description-content-type
|
|
30
|
+
Dynamic: home-page
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
Dynamic: requires-dist
|
|
33
|
+
Dynamic: requires-python
|
|
34
|
+
Dynamic: summary
|
|
24
35
|
|
|
25
36
|
# qaoalib
|
|
26
|
-
|
|
37
|
+
Implementations of VQA simulations for combinatorial optimization (mainly with graph and Max-cut).
|
|
27
38
|
|
|
28
|
-
|
|
29
|
-
- numpy
|
|
30
|
-
- networkx
|
|
31
|
-
- matplotlib
|
|
32
|
-
- qiskit
|
|
39
|
+
v0.2 works towards adapting the circuit simulations with new Qiskit primitives: `Sampler` and `Estimator` (starting from Qiskit 1.0).
|
|
33
40
|
|
|
34
41
|
# How to install
|
|
35
42
|
You can install from the PyPI:
|
|
@@ -38,6 +45,21 @@ pip install --upgrade qaoalib
|
|
|
38
45
|
```
|
|
39
46
|
|
|
40
47
|
# Usage
|
|
48
|
+
Solving Max-cut with VQE:
|
|
49
|
+
```
|
|
50
|
+
import networkx as nx
|
|
51
|
+
from qaoalib.solver import VQE
|
|
52
|
+
from qiskit_optimization.applications import Maxcut
|
|
53
|
+
|
|
54
|
+
G = nx.random_regular_graph(3, 6) # 3-regular graph wtih 6 nodes
|
|
55
|
+
qp = Maxcut(G).to_quadratic_program()
|
|
56
|
+
ansatz = # some preferred ansatz
|
|
57
|
+
solver = VQE(qp, ansatz)
|
|
58
|
+
result = solver.solve()
|
|
59
|
+
print(result)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
# Usage (legacy)
|
|
41
63
|
Calculate Max-cut expectation with `Qmc` or `QmcFastKron` (faster version):
|
|
42
64
|
```
|
|
43
65
|
import networkx as nx
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
# qaoalib
|
|
2
|
-
|
|
2
|
+
Implementations of VQA simulations for combinatorial optimization (mainly with graph and Max-cut).
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
- numpy
|
|
6
|
-
- networkx
|
|
7
|
-
- matplotlib
|
|
8
|
-
- qiskit
|
|
4
|
+
v0.2 works towards adapting the circuit simulations with new Qiskit primitives: `Sampler` and `Estimator` (starting from Qiskit 1.0).
|
|
9
5
|
|
|
10
6
|
# How to install
|
|
11
7
|
You can install from the PyPI:
|
|
@@ -14,6 +10,21 @@ pip install --upgrade qaoalib
|
|
|
14
10
|
```
|
|
15
11
|
|
|
16
12
|
# Usage
|
|
13
|
+
Solving Max-cut with VQE:
|
|
14
|
+
```
|
|
15
|
+
import networkx as nx
|
|
16
|
+
from qaoalib.solver import VQE
|
|
17
|
+
from qiskit_optimization.applications import Maxcut
|
|
18
|
+
|
|
19
|
+
G = nx.random_regular_graph(3, 6) # 3-regular graph wtih 6 nodes
|
|
20
|
+
qp = Maxcut(G).to_quadratic_program()
|
|
21
|
+
ansatz = # some preferred ansatz
|
|
22
|
+
solver = VQE(qp, ansatz)
|
|
23
|
+
result = solver.solve()
|
|
24
|
+
print(result)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
# Usage (legacy)
|
|
17
28
|
Calculate Max-cut expectation with `Qmc` or `QmcFastKron` (faster version):
|
|
18
29
|
```
|
|
19
30
|
import networkx as nx
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import networkx as nx
|
|
3
|
+
|
|
4
|
+
from qiskit import QuantumCircuit
|
|
5
|
+
from qiskit.quantum_info import SparsePauliOp
|
|
6
|
+
from qiskit.circuit import ParameterVector
|
|
7
|
+
from qiskit.circuit.library import PauliEvolutionGate
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def generate_sum_x_pauli_str(length):
|
|
11
|
+
ret = []
|
|
12
|
+
for i in range(length):
|
|
13
|
+
paulis = ['I'] * length
|
|
14
|
+
paulis[i] = 'X'
|
|
15
|
+
ret.append(''.join(paulis))
|
|
16
|
+
|
|
17
|
+
return ret
|
|
18
|
+
|
|
19
|
+
def qaoa(problem_ham: SparsePauliOp, reps: int = 1) -> QuantumCircuit:
|
|
20
|
+
r"""
|
|
21
|
+
Input:
|
|
22
|
+
- problem_ham: Problem Hamiltonian to construct the QAOA circuit.
|
|
23
|
+
Standard procedure would be:
|
|
24
|
+
```
|
|
25
|
+
hamiltonian, offset = qubo.to_ising()
|
|
26
|
+
qc = qaoa_circuit_from_qubo(hamiltonian)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
- qc: A QuantumCircuit object representing the QAOA circuit e^{-i\beta H_M} e^{-i\gamma H_C}.
|
|
31
|
+
"""
|
|
32
|
+
num_qubits = problem_ham.num_qubits
|
|
33
|
+
|
|
34
|
+
gamma = ParameterVector(name=r'$\gamma$', length=reps)
|
|
35
|
+
beta = ParameterVector(name=r'$\beta$', length=reps)
|
|
36
|
+
|
|
37
|
+
mixer_ham = SparsePauliOp(generate_sum_x_pauli_str(num_qubits))
|
|
38
|
+
|
|
39
|
+
qc = QuantumCircuit(num_qubits)
|
|
40
|
+
qc.h(range(num_qubits))
|
|
41
|
+
|
|
42
|
+
for p in range(reps):
|
|
43
|
+
exp_gamma = PauliEvolutionGate(problem_ham, time=gamma[p])
|
|
44
|
+
exp_beta = PauliEvolutionGate(mixer_ham, time=beta[p])
|
|
45
|
+
qc.append(exp_gamma, qargs=range(num_qubits))
|
|
46
|
+
qc.append(exp_beta, qargs=range(num_qubits))
|
|
47
|
+
|
|
48
|
+
return qc
|
|
49
|
+
|
|
50
|
+
def multi_angle_qaoa(problem_ham: SparsePauliOp, reps: int = 1) -> QuantumCircuit:
|
|
51
|
+
r"""
|
|
52
|
+
Input:
|
|
53
|
+
- problem_ham: Problem Hamiltonian to construct the QAOA circuit.
|
|
54
|
+
Standard procedure would be:
|
|
55
|
+
```
|
|
56
|
+
hamiltonian, offset = qubo.to_ising()
|
|
57
|
+
qc = qaoa_circuit_from_qubo(hamiltonian)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
- qc: A QuantumCircuit object representing the QAOA circuit e^{-i\beta H_M} e^{-i\gamma H_C}.
|
|
62
|
+
"""
|
|
63
|
+
num_qubits = problem_ham.num_qubits
|
|
64
|
+
|
|
65
|
+
gamma = ParameterVector(name=r'$\gamma$', length=len(problem_ham) * reps)
|
|
66
|
+
beta = ParameterVector(name=r'$\beta$', length=num_qubits * reps)
|
|
67
|
+
|
|
68
|
+
qc = QuantumCircuit(num_qubits)
|
|
69
|
+
qc.h(range(num_qubits))
|
|
70
|
+
|
|
71
|
+
for p in range(reps):
|
|
72
|
+
for idx, term in enumerate(problem_ham):
|
|
73
|
+
exp_gamma = PauliEvolutionGate(term, time=gamma[p * num_qubits + idx])
|
|
74
|
+
qc.append(exp_gamma, qargs=range(num_qubits))
|
|
75
|
+
for i in range(num_qubits):
|
|
76
|
+
qc.rx(beta[p * num_qubits + i], i)
|
|
77
|
+
|
|
78
|
+
return qc
|
|
79
|
+
|
|
80
|
+
def two_local(num_qubits: int, reps: int = 1, entanglement: str = 'linear') -> QuantumCircuit:
|
|
81
|
+
# only linear or circular entanglement
|
|
82
|
+
if entanglement not in ['linear', 'circular']:
|
|
83
|
+
raise ValueError(f'Entanglment must be linear or circular, got {entanglement} instead.')
|
|
84
|
+
|
|
85
|
+
qc = QuantumCircuit(num_qubits)
|
|
86
|
+
params = ParameterVector('θ', num_qubits * (reps + 1))
|
|
87
|
+
|
|
88
|
+
for i in range(num_qubits):
|
|
89
|
+
qc.ry(params[i], i)
|
|
90
|
+
|
|
91
|
+
for r in range(1, reps + 1):
|
|
92
|
+
for i in range(num_qubits - 1):
|
|
93
|
+
qc.cz(i, i+1)
|
|
94
|
+
if entanglement == 'circular':
|
|
95
|
+
qc.cz(num_qubits - 1, 0)
|
|
96
|
+
for i in range(num_qubits):
|
|
97
|
+
qc.ry(params[r * num_qubits + i], i)
|
|
98
|
+
|
|
99
|
+
return qc
|
|
100
|
+
|
|
101
|
+
def _det_warm_start_angle(ci: float, epsilon: float):
|
|
102
|
+
if ci <= epsilon:
|
|
103
|
+
return 2 * np.arcsin(np.sqrt(epsilon))
|
|
104
|
+
elif ci >= 1 - epsilon:
|
|
105
|
+
return 2 * np.arcsin(np.sqrt(1 - epsilon))
|
|
106
|
+
else:
|
|
107
|
+
return 2 * np.arcsin(np.sqrt(ci))
|
|
108
|
+
|
|
109
|
+
def warm_start_qaoa(
|
|
110
|
+
problem_ham: SparsePauliOp,
|
|
111
|
+
relaxed_qp_solution: list[float],
|
|
112
|
+
depth: int = 1,
|
|
113
|
+
regularization: float = 0.01,
|
|
114
|
+
flip_solution_indices: bool = False,
|
|
115
|
+
) -> QuantumCircuit:
|
|
116
|
+
r"""
|
|
117
|
+
Input:
|
|
118
|
+
- problem_ham: Problem Hamiltonian to construct the QAOA circuit.
|
|
119
|
+
- relaxed_qp_solution: The solution for the relaxed quadratic program.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
- qc: A QuantumCircuit object representing the QAOA circuit e^{-i\beta H_M} e^{-i\gamma H_C}.
|
|
123
|
+
"""
|
|
124
|
+
num_qubits = problem_ham.num_qubits
|
|
125
|
+
if num_qubits != len(relaxed_qp_solution):
|
|
126
|
+
raise ValueError(f'The number of qubits and the length of the solution must be equal.')
|
|
127
|
+
|
|
128
|
+
# try setting this to True if doesn't work, as Qiskit works with reversed indices.
|
|
129
|
+
if flip_solution_indices:
|
|
130
|
+
relaxed_qp_solution = relaxed_qp_solution[::-1]
|
|
131
|
+
|
|
132
|
+
gamma = ParameterVector(name=r'$\gamma$', length=depth)
|
|
133
|
+
beta = ParameterVector(name=r'$\beta$', length=depth)
|
|
134
|
+
|
|
135
|
+
# calculate the thetas for warm start
|
|
136
|
+
reg_warm_start_angle = lambda ci: _det_warm_start_angle(ci, regularization)
|
|
137
|
+
theta = list(map(reg_warm_start_angle, relaxed_qp_solution))
|
|
138
|
+
|
|
139
|
+
qc = QuantumCircuit(num_qubits)
|
|
140
|
+
|
|
141
|
+
# initial layer
|
|
142
|
+
for i in range(num_qubits):
|
|
143
|
+
qc.ry(theta[i], i)
|
|
144
|
+
|
|
145
|
+
for p in range(depth):
|
|
146
|
+
# phase separation
|
|
147
|
+
exp_gamma = PauliEvolutionGate(problem_ham, time=gamma[p])
|
|
148
|
+
qc.append(exp_gamma, qargs=range(num_qubits))
|
|
149
|
+
|
|
150
|
+
# mixer
|
|
151
|
+
for i in range(num_qubits):
|
|
152
|
+
qc.ry(-theta[i], i)
|
|
153
|
+
qc.rz(beta[p], i)
|
|
154
|
+
qc.ry(theta[i], i)
|
|
155
|
+
|
|
156
|
+
return qc
|
|
157
|
+
|
|
158
|
+
def rzy_gate(theta: float) -> QuantumCircuit:
|
|
159
|
+
"""
|
|
160
|
+
Add RZY gate to QuantumCircuit object, with Z acting on q1 and Y acting on q0 (following qiskit convention).
|
|
161
|
+
RZY(theta) = exp(-i * theta * kron(Y, Z)).
|
|
162
|
+
"""
|
|
163
|
+
qc = QuantumCircuit(2, name='Rzy')
|
|
164
|
+
qc.rx(np.pi/2, 1)
|
|
165
|
+
qc.cx(0, 1)
|
|
166
|
+
qc.rz(theta, 1)
|
|
167
|
+
qc.cx(0, 1)
|
|
168
|
+
qc.rx(-np.pi/2, 1)
|
|
169
|
+
return qc
|
|
170
|
+
|
|
171
|
+
def drop_weights(G: nx.Graph) -> nx.Graph:
|
|
172
|
+
G_unweighted = G.copy()
|
|
173
|
+
for _, _, data in G_unweighted.edges(data=True):
|
|
174
|
+
data['weights'] = 1.0
|
|
175
|
+
|
|
176
|
+
return G_unweighted
|
|
177
|
+
|
|
178
|
+
def ihva_from_graph(G: nx.Graph, reps: int = 1, weighted_gates: bool = False) -> QuantumCircuit:
|
|
179
|
+
num_nodes = len(G.nodes)
|
|
180
|
+
num_edges = len(G.edges)
|
|
181
|
+
edge_buffer = []
|
|
182
|
+
G_unweighted = drop_weights(G)
|
|
183
|
+
while G_unweighted.edges:
|
|
184
|
+
tree = nx.maximum_spanning_tree(G_unweighted)
|
|
185
|
+
edge_buffer += list(tree.edges)
|
|
186
|
+
G_unweighted.remove_edges_from(tree.edges)
|
|
187
|
+
|
|
188
|
+
qc = QuantumCircuit(num_nodes)
|
|
189
|
+
qc.h(range(num_nodes))
|
|
190
|
+
params = ParameterVector('θ', num_edges * reps)
|
|
191
|
+
for r in range(reps):
|
|
192
|
+
for idx, edge in enumerate(edge_buffer):
|
|
193
|
+
if r % 2 == 0:
|
|
194
|
+
i, j = edge
|
|
195
|
+
else:
|
|
196
|
+
j, i = edge
|
|
197
|
+
gate_weight = G[i][j]['weight'] if weighted_gates else 1.0
|
|
198
|
+
qc.append(rzy_gate(gate_weight * params [r * num_edges + idx] ), qargs=[i, j])
|
|
199
|
+
|
|
200
|
+
return qc
|
|
201
|
+
|
|
202
|
+
def linear_ansatz(num_qubits: int, reps: int = 1) -> QuantumCircuit:
|
|
203
|
+
qc = QuantumCircuit(num_qubits)
|
|
204
|
+
params = ParameterVector('θ', num_qubits * reps)
|
|
205
|
+
|
|
206
|
+
# initial state
|
|
207
|
+
qc.h(range(num_qubits - 1)) # leave the last qubit with state |0>
|
|
208
|
+
|
|
209
|
+
for r in range(reps):
|
|
210
|
+
for i in range(num_qubits):
|
|
211
|
+
qc.ry(params[r * num_qubits + i], i)
|
|
212
|
+
|
|
213
|
+
return qc
|
|
214
|
+
|
|
215
|
+
def pure_rx_ansatz(num_qubits: int) -> QuantumCircuit:
|
|
216
|
+
params = ParameterVector('θ', num_qubits)
|
|
217
|
+
qc = QuantumCircuit(num_qubits)
|
|
218
|
+
|
|
219
|
+
for i in range(num_qubits):
|
|
220
|
+
qc.rx(params[i], i)
|
|
221
|
+
|
|
222
|
+
return qc
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .vqe import VQE
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from qiskit import QuantumCircuit
|
|
4
|
+
from qiskit.circuit import ParameterVector
|
|
5
|
+
from qiskit.quantum_info import SparsePauliOp, Pauli
|
|
6
|
+
from qiskit_optimization import QuadraticProgram
|
|
7
|
+
|
|
8
|
+
from .vqe import VQE
|
|
9
|
+
|
|
10
|
+
class LCCVQE(VQE):
|
|
11
|
+
"""
|
|
12
|
+
LCC for VQE ansatz with circular entanglement.
|
|
13
|
+
* Only works for one-local (Z) or two-local (ZZ) operations.
|
|
14
|
+
* Currently only consider the TwoLocal ansatz with RY rotation, CZ entanglement, and reps=1.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
quadratic_program: QuadraticProgram,
|
|
20
|
+
shots: int = None,
|
|
21
|
+
sampler=None,
|
|
22
|
+
estimator=None,
|
|
23
|
+
optimizer=None,
|
|
24
|
+
) -> None:
|
|
25
|
+
super().__init__(
|
|
26
|
+
quadratic_program,
|
|
27
|
+
reps=1,
|
|
28
|
+
shots=shots,
|
|
29
|
+
sampler=sampler,
|
|
30
|
+
estimator=estimator,
|
|
31
|
+
optimizer=optimizer,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def get_pauli_indices(pauli: Pauli) -> list[int]:
|
|
36
|
+
return np.argwhere(pauli.z).reshape(-1).tolist()
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def generate_local_ansatz(num_qubits: int) -> QuantumCircuit:
|
|
40
|
+
qc = QuantumCircuit(num_qubits)
|
|
41
|
+
theta = ParameterVector('θ', 2 * num_qubits)
|
|
42
|
+
|
|
43
|
+
# First layer RY
|
|
44
|
+
for k in range(num_qubits):
|
|
45
|
+
qc.ry(theta[k], k)
|
|
46
|
+
|
|
47
|
+
# Entangling CNOT
|
|
48
|
+
if num_qubits == 6:
|
|
49
|
+
for j, k in [(0, 1), (1, 2), (3, 4), (4, 5)]:
|
|
50
|
+
qc.cz(j, k)
|
|
51
|
+
else:
|
|
52
|
+
for k in range(num_qubits - 1):
|
|
53
|
+
qc.cz(k, k + 1)
|
|
54
|
+
|
|
55
|
+
# Second layer RY
|
|
56
|
+
for k in range(num_qubits):
|
|
57
|
+
qc.ry(theta[num_qubits + k], k)
|
|
58
|
+
|
|
59
|
+
return qc
|
|
60
|
+
|
|
61
|
+
def _distance(self, i, j):
|
|
62
|
+
return min(abs(i - j), abs(i + self.num_qubits - j), abs(j - i + self.num_qubits))
|
|
63
|
+
|
|
64
|
+
def generate_pubs(
|
|
65
|
+
self,
|
|
66
|
+
params: list[float] | np.ndarray,
|
|
67
|
+
) -> list[tuple[QuantumCircuit, SparsePauliOp, np.ndarray]]:
|
|
68
|
+
pubs = []
|
|
69
|
+
|
|
70
|
+
for observable in self.hamiltonian:
|
|
71
|
+
pauli_indices = self.get_pauli_indices(observable.paulis[0])
|
|
72
|
+
if len(pauli_indices) == 1:
|
|
73
|
+
# one-local, 3-qubit
|
|
74
|
+
qc = self.generate_local_ansatz(3)
|
|
75
|
+
local_ob = SparsePauliOp('IZI', observable.coeffs[0])
|
|
76
|
+
i = pauli_indices[0]
|
|
77
|
+
first_layer = np.array([(i - 1), i, (i + 1)]) % self.num_qubits
|
|
78
|
+
|
|
79
|
+
elif len(pauli_indices) == 2:
|
|
80
|
+
# two-local
|
|
81
|
+
i, j = pauli_indices
|
|
82
|
+
if self._distance(i, j) == 1:
|
|
83
|
+
# 4-qubit
|
|
84
|
+
|
|
85
|
+
qc = self.generate_local_ansatz(4)
|
|
86
|
+
local_ob = SparsePauliOp('IZZI', observable.coeffs[0])
|
|
87
|
+
|
|
88
|
+
if i == 0 and j == self.num_qubits - 1:
|
|
89
|
+
first_layer = np.array([j - 1, j, i, i + 1]) % self.num_qubits
|
|
90
|
+
else:
|
|
91
|
+
first_layer = np.array([i - 1, i, j, j + 1]) % self.num_qubits
|
|
92
|
+
|
|
93
|
+
elif self._distance(i, j) == 2:
|
|
94
|
+
# 5-qubit
|
|
95
|
+
qc = self.generate_local_ansatz(5)
|
|
96
|
+
local_ob = SparsePauliOp('IZIZI', observable.coeffs[0])
|
|
97
|
+
|
|
98
|
+
if i + 2 == j:
|
|
99
|
+
first_layer = np.array([i - 1, i, i + 1, j, j + 1]) % self.num_qubits
|
|
100
|
+
else:
|
|
101
|
+
first_layer = np.array([j - 1, j, i - 1, i, i + 1]) % self.num_qubits
|
|
102
|
+
|
|
103
|
+
elif self._distance(i, j) > 2:
|
|
104
|
+
# 6-qubit
|
|
105
|
+
qc = self.generate_local_ansatz(6)
|
|
106
|
+
local_ob = SparsePauliOp('IZIIZI', observable.coeffs[0])
|
|
107
|
+
first_layer = np.array([i - 1, i, i + 1, j - 1, j, j + 1]) % self.num_qubits
|
|
108
|
+
else:
|
|
109
|
+
raise ValueError(f'Pauli indices must be of 1 or 2 length. Got {len(pauli_indices)} instead.')
|
|
110
|
+
|
|
111
|
+
second_layer = first_layer + self.num_qubits
|
|
112
|
+
param_indices = np.hstack([first_layer, second_layer]).tolist()
|
|
113
|
+
|
|
114
|
+
# construct pub
|
|
115
|
+
params = np.asarray(params)
|
|
116
|
+
pubs.append((qc, local_ob, params[param_indices]))
|
|
117
|
+
|
|
118
|
+
return pubs
|
|
@@ -21,12 +21,11 @@ class VQE:
|
|
|
21
21
|
def __init__(
|
|
22
22
|
self,
|
|
23
23
|
quadratic_program: QuadraticProgram,
|
|
24
|
-
ansatz: QuantumCircuit
|
|
25
|
-
reps: int = 1,
|
|
24
|
+
ansatz: QuantumCircuit,
|
|
26
25
|
shots: int = None,
|
|
27
26
|
sampler=None,
|
|
28
27
|
estimator=None,
|
|
29
|
-
optimizer=None,
|
|
28
|
+
optimizer=None,
|
|
30
29
|
) -> None:
|
|
31
30
|
self.qp = quadratic_program
|
|
32
31
|
self.converter = QuadraticProgramToQubo()
|
|
@@ -37,7 +36,7 @@ class VQE:
|
|
|
37
36
|
self.offset: float = offset
|
|
38
37
|
|
|
39
38
|
self.num_qubits = hamiltonian.num_qubits
|
|
40
|
-
self.ansatz =
|
|
39
|
+
self.ansatz = ansatz
|
|
41
40
|
|
|
42
41
|
backend_sv = AerSimulator(method='statevector')
|
|
43
42
|
backend_mps = AerSimulator(method='matrix_product_state')
|
|
@@ -48,33 +47,16 @@ class VQE:
|
|
|
48
47
|
|
|
49
48
|
self.optimal_params = None
|
|
50
49
|
self.optimal_solution = None
|
|
51
|
-
|
|
52
|
-
@staticmethod
|
|
53
|
-
def generate_full_ansatz(num_qubits: int, reps: int) -> QuantumCircuit:
|
|
54
|
-
qc = QuantumCircuit(num_qubits)
|
|
55
|
-
theta = ParameterVector('θ', (reps + 1) * num_qubits)
|
|
56
|
-
|
|
57
|
-
for k in range(num_qubits):
|
|
58
|
-
qc.ry(theta[k], k)
|
|
59
|
-
|
|
60
|
-
for r in range(1, reps + 1):
|
|
61
|
-
for k in range(num_qubits):
|
|
62
|
-
qc.cz(k, (k + 1) % num_qubits)
|
|
63
|
-
|
|
64
|
-
for k in range(num_qubits):
|
|
65
|
-
qc.ry(theta[r * num_qubits + k], k)
|
|
66
|
-
|
|
67
|
-
return qc
|
|
68
|
-
|
|
50
|
+
|
|
69
51
|
def generate_random_params(self, scale=TWO_PI):
|
|
70
52
|
return np.random.rand(self.ansatz.num_parameters) * scale
|
|
71
|
-
|
|
53
|
+
|
|
72
54
|
def generate_pubs(
|
|
73
|
-
self,
|
|
55
|
+
self,
|
|
74
56
|
params: list[float] | np.ndarray,
|
|
75
57
|
) -> list[tuple[QuantumCircuit, SparsePauliOp, np.ndarray]]:
|
|
76
58
|
return [(self.ansatz, self.hamiltonian, params)]
|
|
77
|
-
|
|
59
|
+
|
|
78
60
|
def compute_energy(self, params: list[float] | np.ndarray) -> float:
|
|
79
61
|
"""
|
|
80
62
|
Computes the sum of expectation of the ZZ terms.
|
|
@@ -83,14 +65,14 @@ class VQE:
|
|
|
83
65
|
results = self.estimator.run(pubs).result()
|
|
84
66
|
evs = [result.data.evs for result in results]
|
|
85
67
|
return sum(evs)
|
|
86
|
-
|
|
68
|
+
|
|
87
69
|
def _sample_optimal_circuit(self) -> dict[str, int]:
|
|
88
70
|
"""
|
|
89
|
-
Run the full circuit with sampler to get the solution.
|
|
71
|
+
Run the full circuit with sampler to get the solution.
|
|
90
72
|
"""
|
|
91
73
|
if self.optimal_params is None:
|
|
92
74
|
raise ValueError('Problem not yet solved. Run LCCVQE.solve() to solve the problem.')
|
|
93
|
-
|
|
75
|
+
|
|
94
76
|
ansatz = self.ansatz.copy()
|
|
95
77
|
ansatz.measure_all()
|
|
96
78
|
result = self.sampler.run([(ansatz, self.optimal_params)], shots=self.shots).result()[0]
|
|
@@ -103,13 +85,13 @@ class VQE:
|
|
|
103
85
|
for bit_string, count in counts.items():
|
|
104
86
|
if count == highest_count:
|
|
105
87
|
return list(map(int, bit_string[::-1])) # flip the bit-string due to qiskit ordering
|
|
106
|
-
|
|
88
|
+
|
|
107
89
|
def get_qp_solution(self) -> list[float]:
|
|
108
90
|
qubo_solution = self._sample_most_likely()
|
|
109
91
|
return self.converter.interpret(qubo_solution)
|
|
110
92
|
|
|
111
93
|
def solve(
|
|
112
|
-
self,
|
|
94
|
+
self,
|
|
113
95
|
initial_point: list[float] | np.ndarray = None,
|
|
114
96
|
run_sampler: bool = False,
|
|
115
97
|
) -> OptimizerResult:
|
|
@@ -120,7 +102,7 @@ class VQE:
|
|
|
120
102
|
initial_point = self.generate_random_params()
|
|
121
103
|
else:
|
|
122
104
|
assert np.asarray(initial_point).shape[0] == self.ansatz.num_parameters, 'Parameter length does not match.'
|
|
123
|
-
|
|
105
|
+
|
|
124
106
|
def obj_func(params):
|
|
125
107
|
return self.compute_energy(params)
|
|
126
108
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: qaoalib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A package for QAOA Maxcut calculations
|
|
5
5
|
Home-page: https://github.com/xenoicwyce/qaoalib
|
|
6
6
|
Author: Xinwei Lee
|
|
@@ -13,23 +13,30 @@ Description-Content-Type: text/markdown
|
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: qiskit
|
|
15
15
|
Requires-Dist: qiskit_optimization
|
|
16
|
+
Requires-Dist: qiskit_algorithms
|
|
16
17
|
Requires-Dist: qiskit_aer
|
|
17
|
-
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: numpy
|
|
18
19
|
Requires-Dist: scipy
|
|
19
20
|
Requires-Dist: pytest
|
|
20
21
|
Requires-Dist: pytest-cov
|
|
21
22
|
Requires-Dist: matplotlib
|
|
22
23
|
Requires-Dist: networkx
|
|
23
24
|
Requires-Dist: pydantic
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: author-email
|
|
27
|
+
Dynamic: classifier
|
|
28
|
+
Dynamic: description
|
|
29
|
+
Dynamic: description-content-type
|
|
30
|
+
Dynamic: home-page
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
Dynamic: requires-dist
|
|
33
|
+
Dynamic: requires-python
|
|
34
|
+
Dynamic: summary
|
|
24
35
|
|
|
25
36
|
# qaoalib
|
|
26
|
-
|
|
37
|
+
Implementations of VQA simulations for combinatorial optimization (mainly with graph and Max-cut).
|
|
27
38
|
|
|
28
|
-
|
|
29
|
-
- numpy
|
|
30
|
-
- networkx
|
|
31
|
-
- matplotlib
|
|
32
|
-
- qiskit
|
|
39
|
+
v0.2 works towards adapting the circuit simulations with new Qiskit primitives: `Sampler` and `Estimator` (starting from Qiskit 1.0).
|
|
33
40
|
|
|
34
41
|
# How to install
|
|
35
42
|
You can install from the PyPI:
|
|
@@ -38,6 +45,21 @@ pip install --upgrade qaoalib
|
|
|
38
45
|
```
|
|
39
46
|
|
|
40
47
|
# Usage
|
|
48
|
+
Solving Max-cut with VQE:
|
|
49
|
+
```
|
|
50
|
+
import networkx as nx
|
|
51
|
+
from qaoalib.solver import VQE
|
|
52
|
+
from qiskit_optimization.applications import Maxcut
|
|
53
|
+
|
|
54
|
+
G = nx.random_regular_graph(3, 6) # 3-regular graph wtih 6 nodes
|
|
55
|
+
qp = Maxcut(G).to_quadratic_program()
|
|
56
|
+
ansatz = # some preferred ansatz
|
|
57
|
+
solver = VQE(qp, ansatz)
|
|
58
|
+
result = solver.solve()
|
|
59
|
+
print(result)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
# Usage (legacy)
|
|
41
63
|
Calculate Max-cut expectation with `Qmc` or `QmcFastKron` (faster version):
|
|
42
64
|
```
|
|
43
65
|
import networkx as nx
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
requirements.txt
|
|
5
|
+
setup.py
|
|
6
|
+
qaoalib/__init__.py
|
|
7
|
+
qaoalib/ansatz.py
|
|
8
|
+
qaoalib/json.py
|
|
9
|
+
qaoalib/math.py
|
|
10
|
+
qaoalib/utils.py
|
|
11
|
+
qaoalib/version.py
|
|
12
|
+
qaoalib.egg-info/PKG-INFO
|
|
13
|
+
qaoalib.egg-info/SOURCES.txt
|
|
14
|
+
qaoalib.egg-info/dependency_links.txt
|
|
15
|
+
qaoalib.egg-info/requires.txt
|
|
16
|
+
qaoalib.egg-info/top_level.txt
|
|
17
|
+
qaoalib/models/__init__.py
|
|
18
|
+
qaoalib/models/result.py
|
|
19
|
+
qaoalib/solver/__init__.py
|
|
20
|
+
qaoalib/solver/lcc_vqe.py
|
|
21
|
+
qaoalib/solver/vqe.py
|