Skip to content

Commit 9f9a071

Browse files
authored
Merge pull request #76 from mit-han-lab/feat-qstate
Feat qstate add new writing of dev.op
2 parents 0e8ad5f + 7972ffd commit 9f9a071

File tree

3 files changed

+416
-0
lines changed

3 files changed

+416
-0
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import torch
2+
import torchquantum as tq
3+
import torchquantum.functional as tqf
4+
5+
import random
6+
import numpy as np
7+
8+
from torchquantum.functional import mat_dict
9+
10+
from torchquantum.plugins import tq2qiskit, qiskit2tq
11+
from torchquantum.measurement import expval_joint_analytical
12+
13+
seed = 0
14+
random.seed(seed)
15+
np.random.seed(seed)
16+
torch.manual_seed(seed)
17+
18+
class MAXCUT(tq.QuantumModule):
19+
"""computes the optimal cut for a given graph.
20+
outputs: the most probable bitstring decides the set {0 or 1} each
21+
node belongs to.
22+
"""
23+
24+
def __init__(self, n_wires, input_graph, n_layers):
25+
super().__init__()
26+
27+
self.n_wires = n_wires
28+
29+
self.input_graph = input_graph # list of edges
30+
self.n_layers = n_layers
31+
32+
self.betas = torch.nn.Parameter(0.01 * torch.rand(self.n_layers))
33+
self.gammas = torch.nn.Parameter(0.01 * torch.rand(self.n_layers))
34+
35+
def mixer(self, qdev, beta):
36+
"""
37+
Apply the single rotation and entangling layer of the QAOA ansatz.
38+
mixer = exp(-i * beta * sigma_x)
39+
"""
40+
for wire in range(self.n_wires):
41+
qdev.rx(
42+
wires=wire,
43+
params=2 * beta.unsqueeze(0),
44+
) # type: ignore
45+
46+
def entangler(self, qdev, gamma):
47+
"""
48+
Apply the single rotation and entangling layer of the QAOA ansatz.
49+
entangler = exp(-i * gamma * (1 - sigma_z * sigma_z)/2)
50+
"""
51+
for edge in self.input_graph:
52+
qdev.cx(
53+
[edge[0], edge[1]],
54+
) # type: ignore
55+
qdev.rz(
56+
wires=edge[1],
57+
params=gamma.unsqueeze(0),
58+
) # type: ignore
59+
qdev.cx(
60+
[edge[0], edge[1]],
61+
) # type: ignore
62+
63+
def edge_to_PauliString(self, edge):
64+
# construct pauli string
65+
pauli_string = ""
66+
for wire in range(self.n_wires):
67+
if wire in edge:
68+
pauli_string += "Z"
69+
else:
70+
pauli_string += "I"
71+
return pauli_string
72+
73+
def circuit(self, qdev):
74+
"""
75+
execute the quantum circuit
76+
"""
77+
for wire in range(self.n_wires):
78+
qdev.h(
79+
wires=wire,
80+
) # type: ignore
81+
82+
for i in range(self.n_layers):
83+
self.mixer(qdev, self.betas[i])
84+
self.entangler(qdev, self.gammas[i])
85+
86+
def forward(self, measure_all=False):
87+
"""
88+
Apply the QAOA ansatz and only measure the edge qubit on z-basis.
89+
Args:
90+
if edge is None
91+
"""
92+
qdev = tq.QuantumDevice(n_wires=self.n_wires, device=self.betas.device)
93+
94+
self.circuit(qdev)
95+
print(tq.measure(qdev, n_shots=1024))
96+
# compute the expectation value
97+
if measure_all is False:
98+
expVal = 0
99+
for edge in self.input_graph:
100+
pauli_string = self.edge_to_PauliString(edge)
101+
expVal -= 0.5 * (
102+
1 - expval_joint_analytical(qdev, observable=pauli_string)
103+
)
104+
return expVal
105+
else:
106+
return tq.measure(qdev, n_shots=1024, draw_id=0)
107+
108+
def backprop_optimize(model, n_steps=100, lr=0.1):
109+
"""
110+
Optimize the QAOA ansatz over the parameters gamma and beta
111+
Args:
112+
betas (np.array): A list of beta parameters.
113+
gammas (np.array): A list of gamma parameters.
114+
n_steps (int): The number of steps to optimize, defaults to 10.
115+
lr (float): The learning rate, defaults to 0.1.
116+
"""
117+
# measure all edges in the input_graph
118+
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
119+
print(
120+
"The initial parameters are betas = {} and gammas = {}".format(
121+
*model.parameters()
122+
)
123+
)
124+
# optimize the parameters and return the optimal values
125+
for step in range(n_steps):
126+
optimizer.zero_grad()
127+
loss = model()
128+
loss.backward()
129+
optimizer.step()
130+
if step % 2 == 0:
131+
print("Step: {}, Cost Objective: {}".format(step, loss.item()))
132+
133+
print(
134+
"The optimal parameters are betas = {} and gammas = {}".format(
135+
*model.parameters()
136+
)
137+
)
138+
return model(measure_all=True)
139+
140+
def main():
141+
# create a input_graph
142+
input_graph = [(0, 1), (0, 3), (1, 2), (2, 3)]
143+
n_wires = 4
144+
n_layers = 3
145+
model = MAXCUT(n_wires=n_wires, input_graph=input_graph, n_layers=n_layers)
146+
model.to("cuda")
147+
# model.to(torch.device("cuda"))
148+
circ = tq2qiskit(tq.QuantumDevice(n_wires=4), model)
149+
print(circ)
150+
# print("The circuit is", circ.draw(output="mpl"))
151+
# circ.draw(output="mpl")
152+
# use backprop
153+
backprop_optimize(model, n_steps=300, lr=0.01)
154+
# use parameter shift rule
155+
# param_shift_optimize(model, n_steps=10, step_size=0.1)
156+
157+
"""
158+
Notes:
159+
1. input_graph = [(0, 1), (3, 0), (1, 2), (2, 3)], mixer 1st & entangler 2nd, n_layers >= 2, answer is correct.
160+
161+
"""
162+
163+
if __name__ == "__main__":
164+
import pdb
165+
pdb.set_trace()
166+
167+
main()

0 commit comments

Comments
 (0)