Skip to content

Commit fb35f20

Browse files
author
Zhuoyang Ye
committed
[Examples] Add many noise examples.
1 parent a1c4012 commit fb35f20

10 files changed

+1818
-4
lines changed
+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
"""
2+
MIT License
3+
4+
Copyright (c) 2020-present TorchQuantum Authors
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
"""
24+
25+
import torch
26+
import torchquantum as tq
27+
28+
import random
29+
import numpy as np
30+
31+
from torchquantum.functional import mat_dict
32+
33+
from torchquantum.measurement import expval_joint_analytical_density
34+
35+
seed = 0
36+
random.seed(seed)
37+
np.random.seed(seed)
38+
torch.manual_seed(seed)
39+
40+
41+
class MAXCUT(tq.QuantumModule):
42+
"""computes the optimal cut for a given graph.
43+
outputs: the most probable bitstring decides the set {0 or 1} each
44+
node belongs to.
45+
"""
46+
47+
def __init__(self, n_wires, input_graph, n_layers):
48+
super().__init__()
49+
50+
self.n_wires = n_wires
51+
52+
self.input_graph = input_graph # list of edges
53+
self.n_layers = n_layers
54+
55+
self.betas = torch.nn.Parameter(0.01 * torch.rand(self.n_layers))
56+
self.gammas = torch.nn.Parameter(0.01 * torch.rand(self.n_layers))
57+
58+
def mixer(self, qdev, beta):
59+
"""
60+
Apply the single rotation and entangling layer of the QAOA ansatz.
61+
mixer = exp(-i * beta * sigma_x)
62+
"""
63+
for wire in range(self.n_wires):
64+
qdev.rx(
65+
wires=wire,
66+
params=beta.unsqueeze(0),
67+
) # type: ignore
68+
69+
def entangler(self, qdev, gamma):
70+
"""
71+
Apply the single rotation and entangling layer of the QAOA ansatz.
72+
entangler = exp(-i * gamma * (1 - sigma_z * sigma_z)/2)
73+
"""
74+
for edge in self.input_graph:
75+
qdev.cx(
76+
[edge[0], edge[1]],
77+
) # type: ignore
78+
qdev.rz(
79+
wires=edge[1],
80+
params=gamma.unsqueeze(0),
81+
) # type: ignore
82+
qdev.cx(
83+
[edge[0], edge[1]],
84+
) # type: ignore
85+
86+
def edge_to_PauliString(self, edge):
87+
# construct pauli string
88+
pauli_string = ""
89+
for wire in range(self.n_wires):
90+
if wire in edge:
91+
pauli_string += "Z"
92+
else:
93+
pauli_string += "I"
94+
return pauli_string
95+
96+
def circuit(self, qdev):
97+
"""
98+
execute the quantum circuit
99+
"""
100+
# print(self.betas, self.gammas)
101+
for wire in range(self.n_wires):
102+
qdev.h(
103+
wires=wire,
104+
) # type: ignore
105+
106+
for i in range(self.n_layers):
107+
self.mixer(qdev, self.betas[i])
108+
self.entangler(qdev, self.gammas[i])
109+
110+
def forward(self, measure_all=False):
111+
"""
112+
Apply the QAOA ansatz and only measure the edge qubit on z-basis.
113+
Args:
114+
if edge is None
115+
"""
116+
qdev = tq.NoiseDevice(
117+
n_wires=self.n_wires, device=self.betas.device, record_op=False,
118+
noise_model=tq.NoiseModel(kraus_dict={"Bitflip": 0.12, "Phaseflip": 0.12})
119+
)
120+
121+
self.circuit(qdev)
122+
123+
# turn on the record_op above to print the circuit
124+
# print(op_history2qiskit(self.n_wires, qdev.op_history))
125+
126+
# print(tq.measure(qdev, n_shots=1024))
127+
# compute the expectation value
128+
# print(qdev.get_states_1d())
129+
if measure_all is False:
130+
expVal = 0
131+
for edge in self.input_graph:
132+
pauli_string = self.edge_to_PauliString(edge)
133+
expv = expval_joint_analytical_density(qdev, observable=pauli_string)
134+
expVal += 0.5 * expv
135+
# print(pauli_string, expv)
136+
# print(expVal)
137+
return expVal
138+
else:
139+
return tq.measure_density(qdev, n_shots=1024, draw_id=0)
140+
141+
142+
def backprop_optimize(model, n_steps=100, lr=0.1):
143+
"""
144+
Optimize the QAOA ansatz over the parameters gamma and beta
145+
Args:
146+
betas (np.array): A list of beta parameters.
147+
gammas (np.array): A list of gamma parameters.
148+
n_steps (int): The number of steps to optimize, defaults to 10.
149+
lr (float): The learning rate, defaults to 0.1.
150+
"""
151+
# measure all edges in the input_graph
152+
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
153+
print(
154+
"The initial parameters are betas = {} and gammas = {}".format(
155+
*model.parameters()
156+
)
157+
)
158+
# optimize the parameters and return the optimal values
159+
for step in range(n_steps):
160+
optimizer.zero_grad()
161+
loss = model()
162+
loss.backward()
163+
optimizer.step()
164+
if step % 2 == 0:
165+
print("Step: {}, Cost Objective: {}".format(step, loss.item()))
166+
167+
print(
168+
"The optimal parameters are betas = {} and gammas = {}".format(
169+
*model.parameters()
170+
)
171+
)
172+
return model(measure_all=True)
173+
174+
175+
def main():
176+
# create a input_graph
177+
input_graph = [(0, 1), (0, 3), (1, 2), (2, 3)]
178+
n_wires = 4
179+
n_layers = 3
180+
model = MAXCUT(n_wires=n_wires, input_graph=input_graph, n_layers=n_layers)
181+
# model.to("cuda")
182+
# model.to(torch.device("cuda"))
183+
# circ = tq2qiskit(tq.QuantumDevice(n_wires=4), model)
184+
# print(circ)
185+
# print("The circuit is", circ.draw(output="mpl"))
186+
# circ.draw(output="mpl")
187+
# use backprop
188+
backprop_optimize(model, n_steps=300, lr=0.01)
189+
# use parameter shift rule
190+
# param_shift_optimize(model, n_steps=500, step_size=100000)
191+
192+
193+
"""
194+
Notes:
195+
1. input_graph = [(0, 1), (3, 0), (1, 2), (2, 3)], mixer 1st & entangler 2nd, n_layers >= 2, answer is correct.
196+
197+
"""
198+
199+
if __name__ == "__main__":
200+
# import pdb
201+
# pdb.set_trace()
202+
203+
main()

0 commit comments

Comments
 (0)