14
14
import torchquantum .operator as op
15
15
from copy import deepcopy
16
16
import matplotlib .pyplot as plt
17
+ from measurements import gen_bitstrings
18
+ from measurements import find_observable_groups
17
19
18
20
__all__ = [
19
21
"expval_joint_sampling_grouping" ,
25
27
26
28
27
29
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 )
29
54
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
30
62
31
63
32
64
def expval_joint_sampling_grouping (
33
- qdev : tq .NoiseDevice ,
65
+ noisedev : tq .NoiseDevice ,
34
66
observables : List [str ],
35
67
n_shots_per_group = 1024 ,
36
68
):
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
38
118
39
119
40
120
def expval_joint_sampling (
@@ -46,24 +126,150 @@ def expval_joint_sampling(
46
126
47
127
48
128
def expval_joint_analytical (
49
- qdev : tq .NoiseDevice ,
129
+ noisedev : tq .NoiseDevice ,
50
130
observable : str ,
131
+ n_shots = 1024
51
132
):
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 )
53
190
54
191
55
192
def expval (
56
- qdev : tq .NoiseDevice ,
193
+ noisedev : tq .NoiseDevice ,
57
194
wires : Union [int , List [int ]],
58
195
observables : Union [op .Observable , List [op .Observable ]],
59
196
):
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 ()
61
209
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 )
62
220
221
+ return torch .stack (expectations , dim = - 1 )
63
222
64
223
224
+ class MeasureAll (tq .QuantumModule ):
225
+ """Obtain the expectation value of all the qubits."""
65
226
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
66
253
67
254
68
255
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
+ '''
0 commit comments