@@ -123,236 +123,6 @@ def order(self):
123
123
"""
124
124
pass
125
125
126
-
127
- def simulate (self , coeff , qv ):
128
-
129
- from qrisp import h , cx , rz , conjugate , control , QuantumBool , mcx , x , p , QuantumEnvironment , gphase
130
-
131
- sorted_term , flip_sign = self .sort ()
132
-
133
- coeff *= flip_sign
134
-
135
- ladder_list = sorted_term .ladder_list
136
-
137
- active_indices = [index for index , is_creator in ladder_list ]
138
-
139
- # Some hamiltonians contain terms of the for a(1)*c(1), ie.
140
- # two ladder operators, which operate on the same qubit.
141
- # We filter them out and discuss their precise treatment below
142
-
143
- active_indices = []
144
- active_index_is_creator = []
145
-
146
- double_indices = []
147
- double_index_is_creator = []
148
- i = 1
149
-
150
- for i in range (len (ladder_list )):
151
-
152
- ladder_op = ladder_list [i ]
153
- ladder_index = ladder_op [0 ]
154
- is_creator = ladder_op [1 ]
155
-
156
- if i > 0 and ladder_index == ladder_list [i - 1 ][0 ]:
157
- double_indices .append (active_indices .pop (- 1 ))
158
- double_index_is_creator .append (ladder_list [i - 1 ][1 ])
159
- continue
160
-
161
- active_indices .append (ladder_index )
162
- active_index_is_creator .append (is_creator )
163
-
164
- if len (active_indices ) == 0 and len (double_indices ) == 0 :
165
- gphase (coeff )
166
- return
167
- elif len (active_indices ) == 0 :
168
- for i in range (len (double_indices )):
169
- if double_index_is_creator [i ]:
170
- x (qv [double_indices [i ]])
171
- p (coeff , qv [double_indices [i ]])
172
- if double_index_is_creator [i ]:
173
- x (qv [double_indices [i ]])
174
- return
175
-
176
-
177
- # In the Jordan-Wigner transform annihilation/creation operators are
178
- # represented as operators of the form
179
-
180
- # ZZZZA111
181
-
182
- # or
183
-
184
- # ZZC11111
185
-
186
- # Where Z is the Z Operator, A/C are creation/annihilation operators and
187
- # 1 is the identity.
188
-
189
- # We now are given a list of these types of operators.
190
- # The first step is now to identify on which qubits there are actually
191
- # Z operators acting and where they cancel out.
192
-
193
- # We do this by creating an array that operates under boolean arithmetic
194
- Z_qubits = np .zeros (qv .size )
195
-
196
-
197
- for i in active_indices :
198
- # This array contains the Z Operators, which need to be executed.
199
- Z_incr = np .zeros (qv .size )
200
- Z_incr [:i ] = 1
201
-
202
- # Update the overall tracker of Z gates
203
- Z_qubits = (Z_qubits + Z_incr )% 2
204
-
205
- # We can assume the ladder_list is sorted, so every Z operator
206
- # that acts on an A or C is acting from the left.
207
- # We have and Z*C = -C and Z*A = A
208
- # because of C = |1><0| and A = |0><1|
209
-
210
- # Therefore we flip the sign of the coefficient for every creator
211
- # that has an overall Z acting on it.
212
-
213
- for i in range (len (ladder_list )):
214
- if Z_qubits [ladder_list [i ][0 ]] == 1 :
215
- if ladder_list [i ][1 ]:
216
- coeff *= - 1
217
- Z_qubits [ladder_list [i ][0 ]] = 0
218
-
219
-
220
-
221
- # We now start implementing performing the quantum operations
222
-
223
- # There are three challenges-
224
-
225
- # 1. Implement the "double_indices" i.e. creation/annihilation
226
- # operators where two act on the same qubit.
227
- # 2. Implement the other creation annihilation operators.
228
- # 3. Implement the Z operators.
229
-
230
- # For step 2 we recreate the circuit in https://arxiv.org/abs/2310.12256
231
-
232
- # The circuit on page 4 looks like this
233
-
234
- # ┌───┐ »
235
- # qv.0: ┤ X ├─────────────────■────────────────────────────────■───────»
236
- # └─┬─┘┌───┐ │ │ »
237
- # qv.1: ──┼──┤ X ├────────────■────────────────────────────────■───────»
238
- # │ └─┬─┘┌───┐ │ │ »
239
- # qv.2: ──┼────┼──┤ X ├───────o────■──────────────────────■────o───────»
240
- # │ │ └─┬─┘┌───┐ │ ┌─┴─┐┌────────────────┐┌─┴─┐ │ ┌───┐»
241
- # qv.3: ──■────■────■──┤ H ├──┼──┤ X ├┤ Rz(-0.5*theta) ├┤ X ├──┼──┤ H ├»
242
- # └───┘┌─┴─┐└───┘└───────┬────────┘└───┘┌─┴─┐└───┘»
243
- # hs_ancilla.0: ────────────────────┤ X ├─────────────■──────────────┤ X ├─────»
244
- # └───┘ └───┘ »
245
- # « ┌───┐
246
- # « qv.0: ──────────┤ X ├
247
- # « ┌───┐└─┬─┘
248
- # « qv.1: ─────┤ X ├──┼──
249
- # « ┌───┐└─┬─┘ │
250
- # « qv.2: ┤ X ├──┼────┼──
251
- # « └─┬─┘ │ │
252
- # « qv.3: ──■────■────■──
253
- # «
254
- # «hs_ancilla.0: ───────────────
255
-
256
- # In it's essence this circuit is a conjugation with an inverse GHZ state
257
- # preparation and a multi controlled RZ gate.
258
-
259
- def inv_ghz_state (qb_list ):
260
- if operator_ctrl_state [- 1 ] == "1" :
261
- x (qb_list [- 1 ])
262
- for qb in qb_list [:- 1 ]:
263
- cx (qb_list [- 1 ], qb )
264
- if operator_ctrl_state [- 1 ] == "1" :
265
- x (qb_list [- 1 ])
266
- h (qb_list [- 1 ])
267
-
268
- # Determine ctrl state and the qubits the creation/annihilation
269
- # operators act on
270
- operator_ctrl_state = ""
271
- operator_qubits = []
272
- for i in range (len (active_indices )):
273
- operator_ctrl_state += str (int (active_index_is_creator [i ]))
274
- operator_qubits .append (qv [active_indices [i ]])
275
-
276
- # The qubit that receives the RZ gate will be called anchor qubit.
277
- anchor_index = active_indices [- 1 ]
278
-
279
-
280
- with conjugate (inv_ghz_state )(operator_qubits ):
281
-
282
- # To realize the behavior of the "double_indices" i.e. the qubits
283
- # that receive two creation annihilation operators, not that such a
284
- # term produces a hamiltonian of the form
285
-
286
- # H = c(0)*a(0)*a(1)*a(2) + h.c.
287
- # = |1><1| (H_red + h.c.)
288
-
289
- # where H_red = a(1)*a(2)
290
-
291
- # Simulating this H is therefore the controlled version of H_red:
292
-
293
- # exp(itH) = |0><0| ID + |1><1| exp(itH_red)
294
-
295
- # We can therefore add the "double_indices" to this conjugation.
296
-
297
- double_index_ctrl_state = ""
298
- double_index_qubits = []
299
- for i in range (len (double_index_is_creator )):
300
- if double_index_is_creator [i ]:
301
- double_index_ctrl_state += "1"
302
- else :
303
- double_index_ctrl_state += "0"
304
-
305
- double_index_qubits .append (qv [double_indices [i ]])
306
-
307
- if len (active_indices ) == 2 :
308
- hs_ancilla = qv [active_indices [0 ]]
309
- if operator_ctrl_state [0 ] == "0" :
310
- env = conjugate (x )(hs_ancilla )
311
- else :
312
- env = QuantumEnvironment ()
313
- else :
314
- # We furthermore allocate an ancillae to perform an efficient
315
- # multi controlled rz.
316
- hs_ancilla = QuantumBool ()
317
-
318
- env = conjugate (mcx )(operator_qubits [:- 1 ] + double_index_qubits ,
319
- hs_ancilla ,
320
- ctrl_state = operator_ctrl_state [:- 1 ] + double_index_ctrl_state ,
321
- method = "gray_pt" )
322
-
323
- with env :
324
-
325
- # Before we execute the RZ, we need to deal with the Z terms (next to the anihilation/
326
- # creation) operators.
327
-
328
- # The qubits that only receive a Z operator will now be called "passive qubits"
329
-
330
- # By inspection we see that the Z operators are equivalent to the
331
- # term (-1)**(q_1 (+) q_2)
332
- # It therefore suffices to compute the parity value of all the passive
333
- # qubits and flip the sign of the coefficient based on the parity.
334
-
335
- # To this end we perform several CX gate on-to the anchor qubit.
336
- # Since the anchor qubit will receive an RZ gate, each bitflip will
337
- # induce a sign flip of the phase.
338
-
339
- # We achieve this by executing a conjugation with the following function
340
- def flip_anchor_qubit (qv , anchor_index , Z_qubits ):
341
- for i in range (qv .size ):
342
- # Z_qubits will contain a 1 if a flip should be executed.
343
- if Z_qubits [i ]:
344
- cx (qv [i ], qv [anchor_index ])
345
-
346
- # Perform the conjugation
347
- with conjugate (flip_anchor_qubit )(qv , anchor_index , Z_qubits ):
348
-
349
- # Perform the controlled RZ
350
- with control (hs_ancilla ):
351
- rz (coeff , qv [anchor_index ])
352
-
353
- if len (active_indices ) != 2 :
354
- # Delete ancilla
355
- hs_ancilla .delete ()
356
126
357
127
def sort (self ):
358
128
# Sort ladder operators (ladder operator semantics are order in-dependent)
0 commit comments