Skip to content

Commit 00e6dab

Browse files
author
Zhuoyang Ye
committed
[Func] Implement three measurement for density matrix.
1 parent 1f472ec commit 00e6dab

File tree

2 files changed

+216
-8
lines changed

2 files changed

+216
-8
lines changed

torchquantum/measurement/density_measurements.py

+214-8
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import torchquantum.operator as op
1515
from copy import deepcopy
1616
import matplotlib.pyplot as plt
17+
from measurements import gen_bitstrings
18+
from measurements import find_observable_groups
1719

1820
__all__ = [
1921
"expval_joint_sampling_grouping",
@@ -25,16 +27,94 @@
2527

2628

2729
def measure(noisedev: tq.NoiseDevice, n_shots=1024, draw_id=None):
28-
return
30+
"""Measure the target density matrix and obtain classical bitstream distribution
31+
Args:
32+
noisedev: input tq.NoiseDevice
33+
n_shots: number of simulated shots
34+
Returns:
35+
distribution of bitstrings
36+
"""
37+
bitstring_candidates = gen_bitstrings(noisedev.n_wires)
38+
39+
state_mag = noisedev.get_probs_1d().abs().detach().cpu().numpy()
40+
distri_all = []
41+
42+
for state_mag_one in state_mag:
43+
state_prob_one = state_mag_one
44+
measured = random.choices(
45+
population=bitstring_candidates,
46+
weights=state_prob_one,
47+
k=n_shots,
48+
)
49+
counter = Counter(measured)
50+
counter.update({key: 0 for key in bitstring_candidates})
51+
distri = dict(counter)
52+
distri = OrderedDict(sorted(distri.items()))
53+
distri_all.append(distri)
2954

55+
if draw_id is not None:
56+
plt.bar(distri_all[draw_id].keys(), distri_all[draw_id].values())
57+
plt.xticks(rotation="vertical")
58+
plt.xlabel("bitstring [qubit0, qubit1, ..., qubitN]")
59+
plt.title("distribution of measured bitstrings")
60+
plt.show()
61+
return distri_all
3062

3163

3264
def expval_joint_sampling_grouping(
33-
qdev: tq.NoiseDevice,
65+
noisedev: tq.NoiseDevice,
3466
observables: List[str],
3567
n_shots_per_group=1024,
3668
):
37-
return
69+
assert len(observables) == len(set(observables)), "each observable should be unique"
70+
# key is the group, values is the list of sub-observables
71+
obs = []
72+
for observable in observables:
73+
obs.append(observable.upper())
74+
# firstly find the groups
75+
groups = find_observable_groups(obs)
76+
77+
# rotation to the desired basis
78+
n_wires = noisedev.n_wires
79+
paulix = op.op_name_dict["paulix"]
80+
pauliy = op.op_name_dict["pauliy"]
81+
pauliz = op.op_name_dict["pauliz"]
82+
iden = op.op_name_dict["i"]
83+
pauli_dict = {"X": paulix, "Y": pauliy, "Z": pauliz, "I": iden}
84+
85+
expval_all_obs = {}
86+
for obs_group, obs_elements in groups.items():
87+
# for each group need to clone a new qdev and its states
88+
noisedev_clone = tq.NoiseDevice(n_wires=noisedev.n_wires, bsz=noisedev.bsz, device=noisedev.device)
89+
noisedev_clone.clone_densities(noisedev.densities)
90+
91+
for wire in range(n_wires):
92+
for rotation in pauli_dict[obs_group[wire]]().diagonalizing_gates():
93+
rotation(noisedev_clone, wires=wire)
94+
95+
# measure
96+
distributions = measure(noisedev_clone, n_shots=n_shots_per_group)
97+
# interpret the distribution for different observable elements
98+
for obs_element in obs_elements:
99+
expval_all = []
100+
mask = np.ones(len(obs_element), dtype=bool)
101+
mask[np.array([*obs_element]) == "I"] = False
102+
103+
for distri in distributions:
104+
n_eigen_one = 0
105+
n_eigen_minus_one = 0
106+
for bitstring, n_count in distri.items():
107+
if np.dot(list(map(lambda x: eval(x), [*bitstring])), mask).sum() % 2 == 0:
108+
n_eigen_one += n_count
109+
else:
110+
n_eigen_minus_one += n_count
111+
112+
expval = n_eigen_one / n_shots_per_group + (-1) * n_eigen_minus_one / n_shots_per_group
113+
114+
expval_all.append(expval)
115+
expval_all_obs[obs_element] = torch.tensor(expval_all, dtype=F_DTYPE)
116+
117+
return expval_all_obs
38118

39119

40120
def expval_joint_sampling(
@@ -46,24 +126,150 @@ def expval_joint_sampling(
46126

47127

48128
def expval_joint_analytical(
49-
qdev: tq.NoiseDevice,
129+
noisedev: tq.NoiseDevice,
50130
observable: str,
131+
n_shots=1024
51132
):
52-
return
133+
"""
134+
Compute the expectation value of a joint observable from sampling
135+
the measurement bistring
136+
Args:
137+
qdev: the quantum device
138+
observable: the joint observable, on the qubit 0, 1, 2, 3, etc in this order
139+
Returns:
140+
the expectation value
141+
Examples:
142+
>>> import torchquantum as tq
143+
>>> import torchquantum.functional as tqf
144+
>>> x = tq.NoiseDevice(n_wires=2)
145+
>>> tqf.hadamard(x, wires=0)
146+
>>> tqf.x(x, wires=1)
147+
>>> tqf.cnot(x, wires=[0, 1])
148+
>>> print(expval_joint_sampling(x, 'II', n_shots=8192))
149+
tensor([[0.9997]])
150+
>>> print(expval_joint_sampling(x, 'XX', n_shots=8192))
151+
tensor([[0.9991]])
152+
>>> print(expval_joint_sampling(x, 'ZZ', n_shots=8192))
153+
tensor([[-0.9980]])
154+
"""
155+
# rotation to the desired basis
156+
n_wires = noisedev.n_wires
157+
paulix = op.op_name_dict["paulix"]
158+
pauliy = op.op_name_dict["pauliy"]
159+
pauliz = op.op_name_dict["pauliz"]
160+
iden = op.op_name_dict["i"]
161+
pauli_dict = {"X": paulix, "Y": pauliy, "Z": pauliz, "I": iden}
162+
163+
noisedev_clone = tq.NoiseDevice(n_wires=noisedev.n_wires, bsz=noisedev.bsz, device=noisedev.device)
164+
noisedev_clone.clone_densities(noisedev.densities)
165+
166+
observable = observable.upper()
167+
for wire in range(n_wires):
168+
for rotation in pauli_dict[observable[wire]]().diagonalizing_gates():
169+
rotation(noisedev_clone, wires=wire)
170+
171+
mask = np.ones(len(observable), dtype=bool)
172+
mask[np.array([*observable]) == "I"] = False
173+
174+
expval_all = []
175+
# measure
176+
distributions = measure(noisedev_clone, n_shots=n_shots)
177+
for distri in distributions:
178+
n_eigen_one = 0
179+
n_eigen_minus_one = 0
180+
for bitstring, n_count in distri.items():
181+
if np.dot(list(map(lambda x: eval(x), [*bitstring])), mask).sum() % 2 == 0:
182+
n_eigen_one += n_count
183+
else:
184+
n_eigen_minus_one += n_count
185+
186+
expval = n_eigen_one / n_shots + (-1) * n_eigen_minus_one / n_shots
187+
expval_all.append(expval)
188+
189+
return torch.tensor(expval_all, dtype=F_DTYPE)
53190

54191

55192
def expval(
56-
qdev: tq.NoiseDevice,
193+
noisedev: tq.NoiseDevice,
57194
wires: Union[int, List[int]],
58195
observables: Union[op.Observable, List[op.Observable]],
59196
):
60-
return
197+
all_dims = np.arange(noisedev.densities.dim())
198+
if isinstance(wires, int):
199+
wires = [wires]
200+
observables = [observables]
201+
202+
# rotation to the desired basis
203+
for wire, observable in zip(wires, observables):
204+
for rotation in observable.diagonalizing_gates():
205+
rotation(noisedev, wires=wire)
206+
207+
# compute magnitude
208+
state_mag = noisedev.get_probs_1d()
61209

210+
expectations = []
211+
for wire, observable in zip(wires, observables):
212+
# compute marginal magnitude
213+
reduction_dims = np.delete(all_dims, [0, wire + 1])
214+
if reduction_dims.size == 0:
215+
probs = state_mag
216+
else:
217+
probs = state_mag.sum(list(reduction_dims))
218+
res = probs.mv(observable.eigvals.real.to(probs.device))
219+
expectations.append(res)
62220

221+
return torch.stack(expectations, dim=-1)
63222

64223

224+
class MeasureAll(tq.QuantumModule):
225+
"""Obtain the expectation value of all the qubits."""
65226

227+
def __init__(self, obs, v_c_reg_mapping=None):
228+
super().__init__()
229+
self.obs = obs
230+
self.v_c_reg_mapping = v_c_reg_mapping
231+
232+
def forward(self, qdev: tq.NoiseDevice):
233+
x = expval(qdev, list(range(qdev.n_wires)), [self.obs()] * qdev.n_wires)
234+
235+
if self.v_c_reg_mapping is not None:
236+
c2v_mapping = self.v_c_reg_mapping["c2v"]
237+
"""
238+
the measurement is not normal order, need permutation
239+
"""
240+
perm = []
241+
for k in range(x.shape[-1]):
242+
if k in c2v_mapping.keys():
243+
perm.append(c2v_mapping[k])
244+
x = x[:, perm]
245+
246+
if self.noise_model_tq is not None and self.noise_model_tq.is_add_noise:
247+
return self.noise_model_tq.apply_readout_error(x)
248+
else:
249+
return x
250+
251+
def set_v_c_reg_mapping(self, mapping):
252+
self.v_c_reg_mapping = mapping
66253

67254

68255
if __name__ == '__main__':
69-
print("")
256+
print("Yes")
257+
qdev = tq.NoiseDevice(n_wires=2, bsz=5, device="cpu", record_op=True) # use device='cuda' for GPU
258+
qdev.h(wires=0)
259+
qdev.cnot(wires=[0, 1])
260+
tqf.h(qdev, wires=1)
261+
tqf.x(qdev, wires=1)
262+
op = tq.RX(has_params=True, trainable=True, init_params=0.5)
263+
op(qdev, wires=0)
264+
265+
# measure the state on z basis
266+
print(tq.measure(qdev, n_shots=1024))
267+
268+
269+
270+
'''
271+
# obtain the expval on a observable
272+
expval = expval_joint_sampling(qdev, 'II', 100000)
273+
expval_ana = expval_joint_analytical(qdev, 'II')
274+
print(expval, expval_ana)
275+
'''

torchquantum/measurement/measurements.py

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ def measure(qdev, n_shots=1024, draw_id=None):
4343
distribution of bitstrings
4444
"""
4545
bitstring_candidates = gen_bitstrings(qdev.n_wires)
46+
47+
#state_prob =
4648
state_mag = qdev.get_states_1d().abs().detach().cpu().numpy()
4749
distri_all = []
4850

0 commit comments

Comments
 (0)