qaoalib 0.2.1__tar.gz → 0.3.1__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.1/MANIFEST.in +1 -0
- {qaoalib-0.2.1 → qaoalib-0.3.1}/PKG-INFO +24 -16
- {qaoalib-0.2.1 → qaoalib-0.3.1}/README.md +17 -6
- qaoalib-0.3.1/pyproject.toml +26 -0
- qaoalib-0.3.1/qaoalib/__init__.py +4 -0
- qaoalib-0.3.1/qaoalib/ansatz.py +222 -0
- qaoalib-0.3.1/qaoalib/legacy/qaoa/__init__.py +3 -0
- qaoalib-0.3.1/qaoalib/legacy/qaoa/landscape/__init__.py +3 -0
- qaoalib-0.3.1/qaoalib/legacy/qaoa/landscape/base.py +90 -0
- qaoalib-0.3.1/qaoalib/legacy/qaoa/landscape/direct_numpy.py +79 -0
- qaoalib-0.3.1/qaoalib/legacy/qaoa/landscape/hadamard_test.py +39 -0
- qaoalib-0.3.1/qaoalib/legacy/qaoa/landscape/hybrid_fast.py +44 -0
- qaoalib-0.3.1/qaoalib/legacy/qaoa/layerwise.py +184 -0
- qaoalib-0.3.1/qaoalib/legacy/qaoa/qis.py +59 -0
- qaoalib-0.3.1/qaoalib/legacy/qaoa/qmc.py +51 -0
- qaoalib-0.3.1/qaoalib/legacy/qaoa/strategy.py +70 -0
- qaoalib-0.3.1/qaoalib/legacy/qaoa/utils.py +135 -0
- qaoalib-0.3.1/qaoalib/solver/lcc_vqe.py +118 -0
- {qaoalib-0.2.1 → qaoalib-0.3.1}/qaoalib/solver/vqe.py +13 -31
- {qaoalib-0.2.1 → qaoalib-0.3.1}/qaoalib.egg-info/PKG-INFO +24 -16
- qaoalib-0.3.1/qaoalib.egg-info/SOURCES.txt +31 -0
- {qaoalib-0.2.1 → qaoalib-0.3.1}/qaoalib.egg-info/requires.txt +3 -2
- qaoalib-0.3.1/requirements.txt +11 -0
- qaoalib-0.2.1/qaoalib/__init__.py +0 -2
- qaoalib-0.2.1/qaoalib/version.py +0 -1
- qaoalib-0.2.1/qaoalib.egg-info/SOURCES.txt +0 -18
- qaoalib-0.2.1/setup.py +0 -36
- qaoalib-0.2.1/test/test.py +0 -5
- {qaoalib-0.2.1 → qaoalib-0.3.1}/LICENSE +0 -0
- {qaoalib-0.2.1 → qaoalib-0.3.1}/qaoalib/json.py +0 -0
- {qaoalib-0.2.1 → qaoalib-0.3.1}/qaoalib/math.py +0 -0
- {qaoalib-0.2.1 → qaoalib-0.3.1}/qaoalib/models/__init__.py +0 -0
- {qaoalib-0.2.1 → qaoalib-0.3.1}/qaoalib/models/result.py +0 -0
- {qaoalib-0.2.1 → qaoalib-0.3.1}/qaoalib/solver/__init__.py +0 -0
- {qaoalib-0.2.1 → qaoalib-0.3.1}/qaoalib/utils.py +0 -0
- {qaoalib-0.2.1 → qaoalib-0.3.1}/qaoalib.egg-info/dependency_links.txt +0 -0
- {qaoalib-0.2.1 → qaoalib-0.3.1}/qaoalib.egg-info/top_level.txt +0 -0
- {qaoalib-0.2.1 → qaoalib-0.3.1}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include requirements.txt
|
|
@@ -1,35 +1,28 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: qaoalib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: A package for QAOA Maxcut calculations
|
|
5
|
-
|
|
6
|
-
Author: Xinwei Lee
|
|
7
|
-
Author-email: xenoicwyce@gmail.com
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
5
|
+
Project-URL: Homepage, https://github.com/xenoicwyce/qaoalib
|
|
11
6
|
Requires-Python: >=3.9
|
|
12
7
|
Description-Content-Type: text/markdown
|
|
13
8
|
License-File: LICENSE
|
|
9
|
+
Requires-Dist: numpy
|
|
10
|
+
Requires-Dist: scipy
|
|
14
11
|
Requires-Dist: qiskit
|
|
15
12
|
Requires-Dist: qiskit_optimization
|
|
13
|
+
Requires-Dist: qiskit_algorithms
|
|
16
14
|
Requires-Dist: qiskit_aer
|
|
17
|
-
Requires-Dist: numpy==1.26.0
|
|
18
|
-
Requires-Dist: scipy
|
|
19
15
|
Requires-Dist: pytest
|
|
20
16
|
Requires-Dist: pytest-cov
|
|
21
17
|
Requires-Dist: matplotlib
|
|
22
18
|
Requires-Dist: networkx
|
|
23
19
|
Requires-Dist: pydantic
|
|
20
|
+
Dynamic: license-file
|
|
24
21
|
|
|
25
22
|
# qaoalib
|
|
26
|
-
|
|
23
|
+
Implementations of VQA simulations for combinatorial optimization (mainly with graph and Max-cut).
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
- numpy
|
|
30
|
-
- networkx
|
|
31
|
-
- matplotlib
|
|
32
|
-
- qiskit
|
|
25
|
+
v0.2 works towards adapting the circuit simulations with new Qiskit primitives: `Sampler` and `Estimator` (starting from Qiskit 1.0).
|
|
33
26
|
|
|
34
27
|
# How to install
|
|
35
28
|
You can install from the PyPI:
|
|
@@ -38,6 +31,21 @@ pip install --upgrade qaoalib
|
|
|
38
31
|
```
|
|
39
32
|
|
|
40
33
|
# Usage
|
|
34
|
+
Solving Max-cut with VQE:
|
|
35
|
+
```
|
|
36
|
+
import networkx as nx
|
|
37
|
+
from qaoalib.solver import VQE
|
|
38
|
+
from qiskit_optimization.applications import Maxcut
|
|
39
|
+
|
|
40
|
+
G = nx.random_regular_graph(3, 6) # 3-regular graph wtih 6 nodes
|
|
41
|
+
qp = Maxcut(G).to_quadratic_program()
|
|
42
|
+
ansatz = # some preferred ansatz
|
|
43
|
+
solver = VQE(qp, ansatz)
|
|
44
|
+
result = solver.solve()
|
|
45
|
+
print(result)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
# Usage (legacy)
|
|
41
49
|
Calculate Max-cut expectation with `Qmc` or `QmcFastKron` (faster version):
|
|
42
50
|
```
|
|
43
51
|
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,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools", "wheel", "numpy", "networkx"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "qaoalib"
|
|
7
|
+
version = "0.3.1"
|
|
8
|
+
description = "A package for QAOA Maxcut calculations"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"numpy",
|
|
13
|
+
"scipy",
|
|
14
|
+
"qiskit",
|
|
15
|
+
"qiskit_optimization",
|
|
16
|
+
"qiskit_algorithms",
|
|
17
|
+
"qiskit_aer",
|
|
18
|
+
"pytest",
|
|
19
|
+
"pytest-cov",
|
|
20
|
+
"matplotlib",
|
|
21
|
+
"networkx",
|
|
22
|
+
"pydantic",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/xenoicwyce/qaoalib"
|
|
@@ -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,90 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
|
|
4
|
+
DEFAULT_RTOL = 1e-5
|
|
5
|
+
DEFAULT_ATOL = 1e-8
|
|
6
|
+
|
|
7
|
+
class QmcLandscapeBase:
|
|
8
|
+
"""
|
|
9
|
+
This is a base class.
|
|
10
|
+
It should be only used for inhertiance.
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self, G, prev_params=None):
|
|
13
|
+
if prev_params is not None:
|
|
14
|
+
if len(prev_params) % 2:
|
|
15
|
+
raise ValueError(f'Constructor failed. prev_params must have even length, got {len(prev_params)}.')
|
|
16
|
+
self.graph = G
|
|
17
|
+
self.num_qubits = len(G.nodes)
|
|
18
|
+
self.edge_list = list(G.edges)
|
|
19
|
+
self.prev_params = prev_params
|
|
20
|
+
self.grange = None
|
|
21
|
+
self.brange = None
|
|
22
|
+
self.exp_arr = None
|
|
23
|
+
self.depth = 1 if prev_params is None else len(prev_params)//2+1
|
|
24
|
+
self.npts = None
|
|
25
|
+
|
|
26
|
+
def _meshgrid(self):
|
|
27
|
+
gmin, gmax = min(self.grange), max(self.grange)
|
|
28
|
+
bmin, bmax = min(self.brange), max(self.brange)
|
|
29
|
+
gspace = np.linspace(gmin, gmax, self.npts)
|
|
30
|
+
bspace = np.linspace(bmin, bmax, self.npts)
|
|
31
|
+
return np.meshgrid(gspace, bspace)
|
|
32
|
+
|
|
33
|
+
def get_max(self, rtol=DEFAULT_RTOL, atol=DEFAULT_ATOL):
|
|
34
|
+
if self.exp_arr is None:
|
|
35
|
+
raise ValueError('Grid not found. Run create_grid() method first.')
|
|
36
|
+
|
|
37
|
+
gmesh, bmesh = self._meshgrid()
|
|
38
|
+
exp_max = np.max(self.exp_arr)
|
|
39
|
+
whr = np.where(np.isclose(self.exp_arr, exp_max, rtol=rtol, atol=atol))
|
|
40
|
+
indices = zip(whr[0], whr[1])
|
|
41
|
+
angle_list = [(gmesh[idx], bmesh[idx]) for idx in indices]
|
|
42
|
+
return (exp_max, angle_list)
|
|
43
|
+
|
|
44
|
+
def get_min(self, rtol=DEFAULT_RTOL, atol=DEFAULT_ATOL):
|
|
45
|
+
if self.exp_arr is None:
|
|
46
|
+
raise ValueError('Grid not found. Run create_grid() method first.')
|
|
47
|
+
|
|
48
|
+
gmesh, bmesh = self._meshgrid()
|
|
49
|
+
exp_min = np.min(self.exp_arr)
|
|
50
|
+
whr = np.where(np.isclose(self.exp_arr, exp_min, rtol=rtol, atol=atol))
|
|
51
|
+
indices = zip(whr[0], whr[1])
|
|
52
|
+
angle_list = [(gmesh[idx], bmesh[idx]) for idx in indices]
|
|
53
|
+
return (exp_min, angle_list)
|
|
54
|
+
|
|
55
|
+
def show_landscape(self, **plot_options):
|
|
56
|
+
defaults = {
|
|
57
|
+
'figsize': (16, 9),
|
|
58
|
+
}
|
|
59
|
+
defaults.update(plot_options)
|
|
60
|
+
figsize = defaults['figsize']
|
|
61
|
+
|
|
62
|
+
fig, ax = plt.subplots(subplot_kw={'projection': '3d'}, figsize=figsize)
|
|
63
|
+
|
|
64
|
+
if self.exp_arr is None:
|
|
65
|
+
raise ValueError('Grid not found. Run create_grid() method first.')
|
|
66
|
+
|
|
67
|
+
gmesh, bmesh = self._meshgrid()
|
|
68
|
+
surf = ax.plot_surface(gmesh, bmesh, self.exp_arr, cmap='coolwarm')
|
|
69
|
+
ax.set_xlabel('gamma')
|
|
70
|
+
ax.set_ylabel('beta')
|
|
71
|
+
ax.set_zlabel('expectation')
|
|
72
|
+
fig.colorbar(surf, shrink=.5)
|
|
73
|
+
|
|
74
|
+
plt.show()
|
|
75
|
+
|
|
76
|
+
def show_heatmap(self, legacy=False):
|
|
77
|
+
if self.exp_arr is None:
|
|
78
|
+
raise ValueError('Grid not found. Run create_grid() method first.')
|
|
79
|
+
|
|
80
|
+
if legacy:
|
|
81
|
+
plt.xlabel('norm(gamma_p)')
|
|
82
|
+
plt.ylabel('norm(beta_p)')
|
|
83
|
+
plt.imshow(self.exp_arr, cmap='coolwarm', origin='lower', extent=[0, 1, 0, 1])
|
|
84
|
+
else:
|
|
85
|
+
fig, ax = plt.subplots()
|
|
86
|
+
gmesh, bmesh = self._meshgrid()
|
|
87
|
+
ax.pcolormesh(gmesh, bmesh, self.exp_arr, cmap='coolwarm')
|
|
88
|
+
ax.set_xlabel('gamma')
|
|
89
|
+
ax.set_ylabel('beta')
|
|
90
|
+
plt.show()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from .base import QmcLandscapeBase
|
|
4
|
+
from ..utils import I, Z, plus
|
|
5
|
+
from ..utils import rx, make_params_vec
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DirectNumpy(QmcLandscapeBase):
|
|
9
|
+
def __init__(self, G, prev_params=None):
|
|
10
|
+
super().__init__(G, prev_params)
|
|
11
|
+
self.plusxn = self._mplus()
|
|
12
|
+
self.hamiltonian = self._hmt()
|
|
13
|
+
|
|
14
|
+
def _mplus(self):
|
|
15
|
+
ans = plus
|
|
16
|
+
for q in range(1, self.num_qubits):
|
|
17
|
+
ans = np.kron(ans, plus)
|
|
18
|
+
return ans
|
|
19
|
+
|
|
20
|
+
def _hmt(self):
|
|
21
|
+
N = 2 ** self.num_qubits
|
|
22
|
+
ans = np.zeros((N, N))
|
|
23
|
+
for u, v in self.edge_list:
|
|
24
|
+
ans += np.eye(N) - self.tensor(Z, [u, v])
|
|
25
|
+
return ans/2
|
|
26
|
+
|
|
27
|
+
def tensor(self, u3, qubits):
|
|
28
|
+
if 0 in qubits:
|
|
29
|
+
ans = u3
|
|
30
|
+
else:
|
|
31
|
+
ans = I
|
|
32
|
+
|
|
33
|
+
for idx in range(1, self.num_qubits):
|
|
34
|
+
if idx in qubits:
|
|
35
|
+
ans = np.kron(ans, u3)
|
|
36
|
+
else:
|
|
37
|
+
ans = np.kron(ans, I)
|
|
38
|
+
return ans
|
|
39
|
+
|
|
40
|
+
def ucost(self, q1, q2, gamma):
|
|
41
|
+
return np.diag(np.exp(1j*gamma/2*np.diag(np.eye(2**self.num_qubits)-self.tensor(Z, [q1, q2]))))
|
|
42
|
+
|
|
43
|
+
def umixer_all(self, beta):
|
|
44
|
+
return self.tensor(rx(2*beta), list(range(self.num_qubits)))
|
|
45
|
+
|
|
46
|
+
def ansatz(self, gamma_vec, beta_vec):
|
|
47
|
+
ans = self.plusxn
|
|
48
|
+
for gamma, beta in zip(gamma_vec, beta_vec):
|
|
49
|
+
for u, v in self.edge_list:
|
|
50
|
+
ans = self.ucost(u, v, gamma) @ ans
|
|
51
|
+
ans = self.umixer_all(beta) @ ans
|
|
52
|
+
return ans
|
|
53
|
+
|
|
54
|
+
def expectation(self, params):
|
|
55
|
+
if self.plusxn is None or self.hamiltonian is None:
|
|
56
|
+
self.plusxn = self._mplus()
|
|
57
|
+
self.hamiltonian = self._hmt()
|
|
58
|
+
depth = len(params)//2
|
|
59
|
+
gamma_vec = params[:depth]
|
|
60
|
+
beta_vec = params[depth:]
|
|
61
|
+
ansatz = self.ansatz(gamma_vec, beta_vec)
|
|
62
|
+
ans = ansatz.conj().T @ self.hamiltonian @ ansatz
|
|
63
|
+
ans = ans[0][0]
|
|
64
|
+
if np.isclose(ans.real, np.abs(ans)):
|
|
65
|
+
return ans.real
|
|
66
|
+
else:
|
|
67
|
+
return ans
|
|
68
|
+
|
|
69
|
+
def create_grid(self, npts=100, gmin=0, gmax=2*np.pi, bmin=0, bmax=np.pi):
|
|
70
|
+
self.grange = (gmin, gmax)
|
|
71
|
+
self.brange = (bmin, bmax)
|
|
72
|
+
self.npts = npts
|
|
73
|
+
gmesh, bmesh = self._meshgrid()
|
|
74
|
+
gg = gmesh.reshape((-1,))
|
|
75
|
+
bb = bmesh.reshape((-1,))
|
|
76
|
+
|
|
77
|
+
exp_arr = np.array(list(map(self.expectation, make_params_vec(gg, bb, self.prev_params))))\
|
|
78
|
+
.reshape((npts, npts))
|
|
79
|
+
self.exp_arr = exp_arr
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from .base import QmcLandscapeBase
|
|
4
|
+
from ..utils import interp, ht_expectation
|
|
5
|
+
from ..qis import hadamard_test_circuits, run_many_circuits
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HadamardTest(QmcLandscapeBase):
|
|
9
|
+
def __init__(self, G, prev_params=None):
|
|
10
|
+
super().__init__(G, prev_params)
|
|
11
|
+
|
|
12
|
+
def get_circuits(self, params):
|
|
13
|
+
return hadamard_test_circuits(self.graph, params)
|
|
14
|
+
|
|
15
|
+
def expectation_grid(self, gspace, bspace, npts, prev_params=None):
|
|
16
|
+
qc_list = []
|
|
17
|
+
for beta in bspace:
|
|
18
|
+
for gamma in gspace:
|
|
19
|
+
if prev_params is None:
|
|
20
|
+
qc_list += self.get_circuits([gamma, beta])
|
|
21
|
+
else:
|
|
22
|
+
qc_list += self.get_circuits(interp(prev_params, [gamma, beta]))
|
|
23
|
+
|
|
24
|
+
sv_list = run_many_circuits(qc_list)
|
|
25
|
+
exp_arr = np.array(list(map(ht_expectation, sv_list)))\
|
|
26
|
+
.reshape((npts*npts, len(self.edge_list)))
|
|
27
|
+
exp_arr = np.sum(exp_arr, axis=1).reshape((npts, npts))
|
|
28
|
+
return exp_arr
|
|
29
|
+
|
|
30
|
+
def create_grid(self, npts=100, gmin=0, gmax=2*np.pi, bmin=0, bmax=np.pi):
|
|
31
|
+
self.grange = (gmin, gmax)
|
|
32
|
+
self.brange = (bmin, bmax)
|
|
33
|
+
self.npts = npts
|
|
34
|
+
gmesh, bmesh = self._meshgrid()
|
|
35
|
+
|
|
36
|
+
gspace = np.linspace(gmin, gmax, npts)
|
|
37
|
+
bspace = np.linspace(bmin, bmax, npts)
|
|
38
|
+
exp_arr = self.expectation_grid(gspace, bspace, npts, self.prev_params)
|
|
39
|
+
self.exp_arr = exp_arr
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from .base import QmcLandscapeBase
|
|
4
|
+
from ..utils import I, Z, interp
|
|
5
|
+
from ..qis import qaoa_circuit, run_many_circuits
|
|
6
|
+
from ...math import fast_kron
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class HybridFast(QmcLandscapeBase):
|
|
10
|
+
def __init__(self, G, prev_params=None):
|
|
11
|
+
super().__init__(G, prev_params)
|
|
12
|
+
|
|
13
|
+
def get_circuit(self, params):
|
|
14
|
+
return qaoa_circuit(self.graph, params)
|
|
15
|
+
|
|
16
|
+
def _fast_kron_exp(self, sv):
|
|
17
|
+
sum_ = 0
|
|
18
|
+
for edge in self.edge_list:
|
|
19
|
+
kron_list = [Z if i in edge else I for i in range(self.num_qubits)]
|
|
20
|
+
kron_list.reverse()
|
|
21
|
+
sum_ += (sv.conj().T @ fast_kron(kron_list, sv)).item().real
|
|
22
|
+
# return a single expectation value
|
|
23
|
+
return (len(self.edge_list) - sum_)/2
|
|
24
|
+
|
|
25
|
+
def expectation_grid(self, gspace, bspace, npts, prev_params=None):
|
|
26
|
+
if prev_params is None:
|
|
27
|
+
qc_list = [self.get_circuit([gamma, beta]) for beta in bspace for gamma in gspace]
|
|
28
|
+
else:
|
|
29
|
+
qc_list = [self.get_circuit(interp(prev_params, [gamma, beta]))
|
|
30
|
+
for beta in bspace for gamma in gspace]
|
|
31
|
+
ansatz_list = run_many_circuits(qc_list)
|
|
32
|
+
exp_arr = np.array(list(map(self._fast_kron_exp, ansatz_list))).reshape((npts, npts))
|
|
33
|
+
return exp_arr
|
|
34
|
+
|
|
35
|
+
def create_grid(self, npts=100, gmin=0, gmax=2*np.pi, bmin=0, bmax=np.pi):
|
|
36
|
+
self.grange = (gmin, gmax)
|
|
37
|
+
self.brange = (bmin, bmax)
|
|
38
|
+
self.npts = npts
|
|
39
|
+
gmesh, bmesh = self._meshgrid()
|
|
40
|
+
|
|
41
|
+
gspace = np.linspace(gmin, gmax, npts)
|
|
42
|
+
bspace = np.linspace(bmin, bmax, npts)
|
|
43
|
+
exp_arr = self.expectation_grid(gspace, bspace, npts, self.prev_params)
|
|
44
|
+
self.exp_arr = exp_arr
|