harishaseebat92 commited on
Commit
f106663
·
1 Parent(s): 84cdd4c

added utils folder

Browse files
utils/EBU_Quantum ADDED
@@ -0,0 +1 @@
 
 
1
+ Subproject commit 8b4d693379b544ea9d3b4163189ac46fe52887bb
utils/adapt-aqc ADDED
@@ -0,0 +1 @@
 
 
1
+ Subproject commit da9bf5895b1b694b167f7eaecae358b670ea29d9
utils/base_functions.py ADDED
@@ -0,0 +1,443 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import scipy.sparse as sp
3
+ import math
4
+ import random
5
+ import matplotlib.pyplot as plt
6
+ from scipy.special import jn
7
+ from scipy.sparse import identity, csr_matrix, kron, diags, eye
8
+ from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
9
+ from qiskit.circuit.library import MCXGate, MCPhaseGate, RXGate, CRXGate, QFTGate, StatePreparation, PauliEvolutionGate, RZGate
10
+ from qiskit.quantum_info import SparsePauliOp, Statevector, Operator, Pauli
11
+ from scipy.linalg import expm
12
+ # from tools import *
13
+ from qiskit.qasm3 import dumps # QASM 3 exporter
14
+ from qiskit.qasm3 import loads
15
+ from qiskit.circuit.library import QFT
16
+ from qiskit.primitives import StatevectorEstimator
17
+ from qiskit import transpile
18
+ from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit, apply_circuit_to_state, compute_overlap
19
+ from qiskit_aer import AerSimulator
20
+
21
+
22
+ simulator_settings = AerSimulator(
23
+ method="matrix_product_state",
24
+ matrix_product_state_max_bond_dimension=100,
25
+ )
26
+
27
+ def Wj(j, theta, lam, name='Wj', xgate=False):
28
+ if not xgate:
29
+ name = f' $W_{j}$ '
30
+ qc=QuantumCircuit(j, name=name)
31
+
32
+ if j > 1:
33
+ qc.cx(j-1, range(j-1))
34
+ if lam != 0:
35
+ qc.p(lam, j-1)
36
+ qc.h(j-1)
37
+ if xgate:
38
+ qc.x(range(j-1))
39
+
40
+ # the multicontrolled rz gate
41
+ # it will be decomposed in qiskit
42
+ if j > 1:
43
+ qc.mcrz(theta, range(j-1), j-1)
44
+ else:
45
+ qc.rz(theta, j-1)
46
+
47
+ if xgate:
48
+ qc.x(range(j-1))
49
+ qc.h(j-1)
50
+ if lam != 0:
51
+ qc.p(-lam, j-1)
52
+ if j > 1:
53
+ qc.cx(j-1, range(j-1))
54
+
55
+ return qc
56
+
57
+ def Wj_block(j, n, ctrl_state, theta, lam, name='Wj_block', xgate=False):
58
+ if not xgate:
59
+ name = f' $W_{j}_block$ '
60
+ qc=QuantumCircuit(n + j, name=name)
61
+
62
+ if j > 1:
63
+ qc.cx(n + j-1, range(n, n+j-1))
64
+ if lam != 0:
65
+ qc.p(lam, n + j -1)
66
+ qc.h(n + j -1)
67
+
68
+ if xgate and j>1:
69
+ if isinstance(xgate, (list, tuple)): # selective application
70
+ for idx, flag in enumerate(xgate):
71
+ if flag: # only apply where flag == 1
72
+ qc.x(n + idx)
73
+ elif xgate is True: # apply to all
74
+ qc.x(range(n, n+j-1))
75
+
76
+ # the multicontrolled rz gate
77
+ # it will be decomposed in qiskit
78
+ if j > 1:
79
+ mcrz = RZGate(theta).control(len(ctrl_state) + j-1, ctrl_state = "1"*(j-1)+ctrl_state)
80
+ qc.append(mcrz, range(0, n + j))
81
+ else:
82
+ mcrz = RZGate(theta).control(len(ctrl_state), ctrl_state = ctrl_state)
83
+ qc.append(mcrz, range(0, n+j))
84
+
85
+ if xgate and j>1:
86
+ if isinstance(xgate, (list, tuple)): # selective application
87
+ for idx, flag in enumerate(xgate):
88
+ if flag: # only apply where flag == 1
89
+ qc.x(n + idx)
90
+ elif xgate is True: # apply to all
91
+ qc.x(range(n, n+j-1))
92
+
93
+ qc.h(n+ j-1)
94
+ if lam != 0:
95
+ qc.p(-lam, n + j-1)
96
+ if j > 1:
97
+ qc.cx(n + j-1, range(n, n +j-1))
98
+
99
+ return qc.to_gate(label=name)
100
+
101
+ def V1(nx, dt, name = "V1"):
102
+ n = int(np.ceil(np.log2(nx)))
103
+
104
+ derivatives = QuantumRegister(2*n)
105
+ blocks = QuantumRegister(2)
106
+
107
+ qc = QuantumCircuit(derivatives, blocks)
108
+
109
+ W1 = Wj_block(2, n, "0"*n, -dt , 0, xgate=True)
110
+ qc.append(W1, list(derivatives[0:n])+list(blocks[:]))
111
+
112
+ # qc.barrier()
113
+
114
+ W2 = Wj_block(3, n-1, "1"*(n-1), dt , 0, xgate=[0,1])
115
+ qc.append(W2, list(derivatives[1:n])+[derivatives[0]]+list(blocks[:]))
116
+
117
+ # qc.barrier()
118
+
119
+ W3 = Wj_block(1, n+1, "0"*(n+1), dt , 0, xgate=False)
120
+ qc.append(W3, list(derivatives[n:2*n])+list(blocks[:]))
121
+
122
+ # qc.barrier()
123
+
124
+ W4 = Wj_block(2, n, "0"+"1"*(n-1), -dt , 0, xgate=False)
125
+ qc.append(W4, list(derivatives[n+1:2*n]) + [blocks[0]] + [derivatives[n]] + [blocks[1]])
126
+
127
+ return qc
128
+
129
+ def V2(nx, dt, name = "V2"):
130
+ n = int(np.ceil(np.log2(nx)))
131
+
132
+ derivatives = QuantumRegister(2*n)
133
+ blocks = QuantumRegister(2)
134
+
135
+ qc = QuantumCircuit(derivatives, blocks)
136
+
137
+ W1 = Wj_block(2, 0, "", -2*dt , -np.pi/2, xgate=True)
138
+ qc.append(W1, list(blocks[:]))
139
+
140
+ # qc.barrier()
141
+
142
+ for j in range(1, n+1):
143
+ W2 = Wj_block(2+j, 0, "", 2*dt , -np.pi/2, xgate=[1]*(j-1)+[0,1])
144
+ qc.append(W2, list(derivatives[0:j])+list(blocks[:]))
145
+
146
+ # qc.barrier()
147
+
148
+ W3 = Wj_block(2, n, "0"*n, -dt , -np.pi/2, xgate=True)
149
+ qc.append(W3, list(derivatives[0:n])+list(blocks[:]))
150
+
151
+ # qc.barrier()
152
+
153
+ W4 = Wj_block(2, n, "1"*n, 2*dt , -np.pi/2, xgate=True)
154
+ qc.append(W4, list(derivatives[0:n])+list(blocks[:]))
155
+
156
+ # qc.barrier()
157
+
158
+ W5 = Wj_block(3, n-1, "1"*(n-1), dt , -np.pi/2, xgate=[0,1])
159
+ qc.append(W5, list(derivatives[1:n])+[derivatives[0]]+list(blocks[:]))
160
+
161
+ # qc.barrier()
162
+
163
+ W6 = Wj_block(1, 1, "0", 2*dt , -np.pi/2, xgate=False)
164
+ qc.append(W6, list(blocks[:]))
165
+
166
+ # qc.barrier()
167
+
168
+ for j in range(1, n+1):
169
+ W7 = Wj_block(1+j, 1, "0", -2*dt , -np.pi/2, xgate=[1]*(j-1))
170
+ qc.append(W7, [blocks[0]]+list(derivatives[n:n+j])+[blocks[1]])
171
+
172
+ # qc.barrier()
173
+
174
+ W8 = Wj_block(1, n+1, "0"*(n+1), dt , -np.pi/2, xgate=False)
175
+ qc.append(W8, list(derivatives[n:2*n])+list(blocks[:]))
176
+
177
+ # qc.barrier()
178
+
179
+ W9 = Wj_block(1, n+1, "0"+"1"*(n), -2*dt , -np.pi/2, xgate=False)
180
+ qc.append(W9, list(derivatives[n:2*n])+list(blocks[:]))
181
+
182
+ # qc.barrier()
183
+
184
+ W10 = Wj_block(2, n, "0"+"1"*(n-1), -dt , -np.pi/2, xgate=False)
185
+ qc.append(W10, list(derivatives[n+1:2*n]) + [blocks[0]] + [derivatives[n]] + [blocks[1]])
186
+
187
+ # qc.barrier()
188
+
189
+ return qc
190
+
191
+ def schro(nx, na, R, dt, initial_state, steps):
192
+
193
+ nq = int(np.ceil(np.log2(nx)))
194
+
195
+ # warped phase transformation
196
+ dp = 2 * R * np.pi / 2**na
197
+ p = np.arange(- R * np.pi, R * np.pi, step=dp)
198
+ fp = np.exp(-np.abs(p))
199
+ norm1 = np.linalg.norm(fp[2**(na-1):]) # norm of p>=0
200
+
201
+ # construct quantum circuit
202
+ system = QuantumRegister(2*nq+2, name='system')
203
+ ancilla = QuantumRegister(na, name='ancilla')
204
+ qc = QuantumCircuit(system, ancilla)
205
+
206
+ # initialization
207
+ prep = StatePreparation(initial_state)
208
+ anc_prep = StatePreparation(fp / np.linalg.norm(fp))
209
+
210
+ qc.append(prep, system)
211
+ # qc.append(anc_prep, ancilla)
212
+ qc.initialize(fp / np.linalg.norm(fp), ancilla)
213
+
214
+
215
+ # QFT
216
+ qc.append(QFTGate(na), ancilla)
217
+ qc.x(ancilla[-1])
218
+
219
+ A1 = V1(nx, dt, name = "V1").to_gate()
220
+ A2 = V2(nx, dt, name = "V2")
221
+
222
+
223
+ # Hamiltonian simulation for Nt steps
224
+ for i in range(steps):
225
+ # circuit for one step
226
+ for j in range(na):
227
+ # repeat controlled H1 for 2**j times
228
+ qc.append(A1.control().repeat(2**j), [ancilla[j]] + system[:])
229
+
230
+ # qc.append(A1.inverse().control(ctrl_state = "0").repeat(2**(na-1)), [ancilla[na-1]] + system[:])
231
+ qc.append(A1.inverse().repeat(2**(na-1)), system[:])
232
+ qc.append(A2, system[:])
233
+
234
+ # rearrange eta
235
+ qc.x(ancilla[-1])
236
+ qc.append(QFTGate(na).inverse(), ancilla)
237
+
238
+ return qc
239
+
240
+
241
+
242
+ def circ_for_magnitude(field, x, y, nx, na, R, dt, initial_state, steps):
243
+
244
+ qc = schro(nx, na, R, dt, initial_state, steps)
245
+ naimark = QuantumRegister(1, name='Naimark')
246
+ qc.add_register(naimark)
247
+
248
+ if field == 'Ez':
249
+ index = nx * y + x
250
+ elif field == 'Hx':
251
+ index = 2*nx*nx + nx * y + x
252
+ else:
253
+ index = 3*nx*nx + nx * y + x
254
+
255
+ index_bin = format(index, f'0{qc.num_qubits-2}b')
256
+ ctrl_state = '1' + index_bin
257
+ ctrl_qubits = qc.qubits[:-1]
258
+ qc.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state)
259
+
260
+ return qc
261
+
262
+ def circuits_for_sign(field, x, y, nx, na, dt, R, initial_state, steps, xref, yref, field_ref = 'Ez'):
263
+ qc = schro(nx, na, R, dt, initial_state, steps)
264
+
265
+ naimark = QuantumRegister(1, name='Naimark')
266
+ qc.add_register(naimark)
267
+
268
+ if field == 'Ez':
269
+ index = nx * y + x
270
+ elif field == 'Hx':
271
+ index = 2*nx*nx + nx * y + x
272
+ else:
273
+ index = 3*nx*nx + nx * y + x
274
+
275
+ if field_ref == 'Ez':
276
+ index_ref = nx * yref + xref
277
+ elif field_ref == 'Hx':
278
+ index_ref = 2*nx*nx + nx * yref + xref
279
+ else:
280
+ index_ref = 3*nx*nx + nx * yref + xref
281
+
282
+ index_bin = [(index >> i) & 1 for i in range(qc.num_qubits-2)]
283
+ index_ref_bin = [(index_ref >> i) & 1 for i in range(qc.num_qubits-2)]
284
+ index_bin.append(1)
285
+ index_ref_bin.append(1)
286
+
287
+ #Convert reference bitstring to 00000
288
+ for i, bit in enumerate(index_ref_bin):
289
+ if bit == 1:
290
+ qc.x(i)
291
+
292
+ d_bits = [b ^ r for b, r in zip(index_ref_bin, index_bin)]
293
+ control = d_bits.index(1)
294
+
295
+ #Convert the other bitstring to 0001000
296
+ for target, bit in enumerate(d_bits):
297
+ if bit == 1 and target != control:
298
+ qc.cx(control, target)
299
+ qc.h(control)
300
+
301
+ ctrl_state_sum = '0'*(qc.num_qubits-1)
302
+ ctrl_state_diff = '0'*(qc.num_qubits-1-control-1)+'1'+'0'*(control)
303
+
304
+ qcdiff = qc.copy()
305
+
306
+ ctrl_qubits = qc.qubits[:-1]
307
+
308
+ qc.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state_sum)
309
+ qcdiff.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state_diff)
310
+
311
+ return qc, qcdiff
312
+
313
+ def get_absolute_field_value(qc, nq, na, offset, norm):
314
+
315
+ pauli_label = 'Z'+'I'*(2*nq+2+na)
316
+ observable = SparsePauliOp(Pauli(pauli_label))
317
+ ########################################################################################
318
+ estimator = StatevectorEstimator()
319
+
320
+ # === Run Estimator (no parameters needed) ===
321
+ pub = (qc, observable)
322
+ job = estimator.run([pub])
323
+ result = job.result()[0]
324
+ z_exp = result.data.evs.item()
325
+ #########################################################################################
326
+ # === Compute projector expectation ===
327
+ pi_expect = (1 - z_exp) / 2
328
+
329
+ Absolute_value = norm*np.sqrt(pi_expect)-offset
330
+
331
+ return Absolute_value
332
+
333
+ def get_relative_sign(qc, qcdiff, nq, na):
334
+
335
+ pauli_label = 'Z'+'I'*(2*nq+2+na)
336
+ observable = SparsePauliOp(Pauli(pauli_label))
337
+ ########################################################################################
338
+ estimator = StatevectorEstimator()
339
+
340
+ # === Run Estimator ===
341
+ pub = (qc, observable)
342
+ job = estimator.run([pub])
343
+ result = job.result()[0]
344
+ z_exp = result.data.evs.item()
345
+
346
+ pub_diff = (qcdiff, observable)
347
+ job_diff = estimator.run([pub_diff])
348
+ result_diff = job_diff.result()[0]
349
+ z_exp_diff = result_diff.data.evs.item()
350
+ #########################################################################################
351
+ # === Compute projector expectation ===
352
+ pi_expect_sum = (1 - z_exp) / 2
353
+ pi_expect_diff = (1 - z_exp_diff) / 2
354
+
355
+ relative_sign = 'same' if pi_expect_sum >= pi_expect_diff else 'different'
356
+
357
+ return relative_sign
358
+
359
+ def Eref_value(nx, nq, R, dt, na, steps, xref, yref, field_ref = 'Ez'):
360
+ if steps < 31:
361
+ offset = 1
362
+ else :
363
+ offset = 0.15
364
+ deltastate = np.zeros(4*nx*nx)
365
+ # deltastate[nx*nx//2+nx//2:nx*nx//2+nx//2+1] = 1
366
+ deltastate[nx*yref+xref] = 1
367
+ deltastate[0:nx*nx] = deltastate[0:nx*nx] + offset
368
+ norm1 = np.linalg.norm(deltastate)
369
+ initial_state = deltastate/norm1
370
+
371
+ dp = 2 * R * np.pi / 2**na
372
+ p = np.arange(- R * np.pi, R * np.pi, step=dp)
373
+ fp = np.exp(-np.abs(p))
374
+ norm2 = np.linalg.norm(fp)
375
+ norm = norm1 * norm2
376
+
377
+ qc = circ_for_magnitude(field_ref, xref, yref, nx, na, R, dt, initial_state, steps)
378
+
379
+ Ezref = get_absolute_field_value(qc, nq, na, offset, norm)
380
+
381
+ return Ezref
382
+
383
+
384
+ def transpile_circ(circ, basis_gates=None):
385
+ """
386
+ Transpile the circuit to the specified basis gates.
387
+ """
388
+ if basis_gates is None:
389
+ basis_gates = ['z', 'y', 'x', 'sdg', 's', 'h', 'rz', 'ry', 'rx', 'ecr', 'cz', 'cx']
390
+
391
+ transpiled_circ = transpile(circ, basis_gates=basis_gates)
392
+ return transpiled_circ
393
+
394
+ def compute_fidelity(circ1, circ2):
395
+
396
+ circ_1 = tensornetwork_from_circuit(transpile_circ(circ1), simulator_settings)
397
+ circ_2 = tensornetwork_from_circuit(transpile_circ(circ2), simulator_settings)
398
+ fidelity = abs(compute_overlap(circ_1, circ_2))**2
399
+
400
+ return fidelity
401
+
402
+ # def create_impulse_state(grid_dims, impulse_pos):
403
+ # """
404
+ # Creates an initial state vector with a single delta impulse at a specified grid position.
405
+
406
+ # The 2D grid is flattened into a 1D vector in row-major order, and this
407
+ # vector is then padded to match the full simulation state space size (4x).
408
+
409
+ # Args:
410
+ # grid_dims (tuple): A tuple (width, height) defining the simulation grid dimensions.
411
+ # For your original code, this would be (nx, nx).
412
+ # impulse_pos (tuple): A tuple (x, y) for the position of the impulse.
413
+ # Coordinates are 0-indexed.
414
+
415
+ # Returns:
416
+ # numpy.ndarray: The full, padded initial state vector with a single 1.
417
+
418
+ # Raises:
419
+ # ValueError: If the impulse position is outside the grid dimensions.
420
+ # """
421
+ # grid_width, grid_height = grid_dims
422
+ # impulse_x, impulse_y = impulse_pos
423
+
424
+ # # --- Input Validation ---
425
+ # # Ensure the requested impulse position is actually on the grid.
426
+ # if not (0 <= impulse_x < grid_width and 0 <= impulse_y < grid_height):
427
+ # raise ValueError(f"Impulse position ({impulse_x}, {impulse_y}) is outside the "
428
+ # f"grid dimensions ({grid_width}x{grid_height}).")
429
+
430
+ # # --- 1. Calculate the 1D Array Index ---
431
+ # # Convert the (x, y) coordinate to a single index in a flattened 1D array.
432
+ # # The formula for row-major order is: index = y_coord * width + x_coord
433
+ # flat_index = impulse_y * grid_width + impulse_x
434
+
435
+ # # --- 2. Create the Full, Padded State Vector ---
436
+ # grid_size = grid_width * grid_height
437
+ # total_size = 4 * grid_size # The simulation space is 4x the grid size.
438
+ # initial_state = np.zeros(total_size)
439
+
440
+ # # --- 3. Set the Delta Impulse ---
441
+ # initial_state[flat_index] = 1
442
+
443
+ # return initial_state
utils/base_ionq.py ADDED
@@ -0,0 +1,458 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import scipy.sparse as sp
3
+ import math
4
+ import random
5
+ import matplotlib.pyplot as plt
6
+ from scipy.special import jn
7
+ from scipy.sparse import identity, csr_matrix, kron, diags, eye
8
+ from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
9
+ from qiskit.circuit.library import MCXGate, MCPhaseGate, RXGate, CRXGate, QFTGate, StatePreparation, PauliEvolutionGate, RZGate
10
+ from qiskit.quantum_info import SparsePauliOp, Statevector, Operator, Pauli
11
+ from scipy.linalg import expm
12
+ # from tools import *
13
+ from qiskit.qasm3 import dumps # QASM 3 exporter
14
+ from qiskit.qasm3 import loads
15
+ from qiskit.circuit.library import QFT
16
+ from qiskit.primitives import StatevectorEstimator
17
+ from qiskit import transpile
18
+ from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit, apply_circuit_to_state, compute_overlap
19
+ from qiskit_aer import AerSimulator
20
+ from qiskit_ionq import IonQProvider
21
+ import os
22
+ my_api_key = os.getenv("IONQ_API_KEY")
23
+ from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as Estimator
24
+
25
+
26
+
27
+
28
+ # provider = IonQProvider()
29
+
30
+ api_token = "SgUkiDq1r2bVEadyiUfvtuxQ03Qci6UW"
31
+ provider = IonQProvider(api_token)
32
+ ionq_backend = provider.get_backend("simulator")
33
+
34
+
35
+
36
+
37
+ simulator_settings = AerSimulator(
38
+ method="matrix_product_state",
39
+ matrix_product_state_max_bond_dimension=100,
40
+ )
41
+
42
+ def Wj(j, theta, lam, name='Wj', xgate=False):
43
+ if not xgate:
44
+ name = f' $W_{j}$ '
45
+ qc=QuantumCircuit(j, name=name)
46
+
47
+ if j > 1:
48
+ qc.cx(j-1, range(j-1))
49
+ if lam != 0:
50
+ qc.p(lam, j-1)
51
+ qc.h(j-1)
52
+ if xgate:
53
+ qc.x(range(j-1))
54
+
55
+ # the multicontrolled rz gate
56
+ # it will be decomposed in qiskit
57
+ if j > 1:
58
+ qc.mcrz(theta, range(j-1), j-1)
59
+ else:
60
+ qc.rz(theta, j-1)
61
+
62
+ if xgate:
63
+ qc.x(range(j-1))
64
+ qc.h(j-1)
65
+ if lam != 0:
66
+ qc.p(-lam, j-1)
67
+ if j > 1:
68
+ qc.cx(j-1, range(j-1))
69
+
70
+ return qc
71
+
72
+ def Wj_block(j, n, ctrl_state, theta, lam, name='Wj_block', xgate=False):
73
+ if not xgate:
74
+ name = f' $W_{j}_block$ '
75
+ qc=QuantumCircuit(n + j, name=name)
76
+
77
+ if j > 1:
78
+ qc.cx(n + j-1, range(n, n+j-1))
79
+ if lam != 0:
80
+ qc.p(lam, n + j -1)
81
+ qc.h(n + j -1)
82
+
83
+ if xgate and j>1:
84
+ if isinstance(xgate, (list, tuple)): # selective application
85
+ for idx, flag in enumerate(xgate):
86
+ if flag: # only apply where flag == 1
87
+ qc.x(n + idx)
88
+ elif xgate is True: # apply to all
89
+ qc.x(range(n, n+j-1))
90
+
91
+ # the multicontrolled rz gate
92
+ # it will be decomposed in qiskit
93
+ if j > 1:
94
+ mcrz = RZGate(theta).control(len(ctrl_state) + j-1, ctrl_state = "1"*(j-1)+ctrl_state)
95
+ qc.append(mcrz, range(0, n + j))
96
+ else:
97
+ mcrz = RZGate(theta).control(len(ctrl_state), ctrl_state = ctrl_state)
98
+ qc.append(mcrz, range(0, n+j))
99
+
100
+ if xgate and j>1:
101
+ if isinstance(xgate, (list, tuple)): # selective application
102
+ for idx, flag in enumerate(xgate):
103
+ if flag: # only apply where flag == 1
104
+ qc.x(n + idx)
105
+ elif xgate is True: # apply to all
106
+ qc.x(range(n, n+j-1))
107
+
108
+ qc.h(n+ j-1)
109
+ if lam != 0:
110
+ qc.p(-lam, n + j-1)
111
+ if j > 1:
112
+ qc.cx(n + j-1, range(n, n +j-1))
113
+
114
+ return qc.to_gate(label=name)
115
+
116
+ def V1(nx, dt, name = "V1"):
117
+ n = int(np.ceil(np.log2(nx)))
118
+
119
+ derivatives = QuantumRegister(2*n)
120
+ blocks = QuantumRegister(2)
121
+
122
+ qc = QuantumCircuit(derivatives, blocks)
123
+
124
+ W1 = Wj_block(2, n, "0"*n, -dt , 0, xgate=True)
125
+ qc.append(W1, list(derivatives[0:n])+list(blocks[:]))
126
+
127
+ # qc.barrier()
128
+
129
+ W2 = Wj_block(3, n-1, "1"*(n-1), dt , 0, xgate=[0,1])
130
+ qc.append(W2, list(derivatives[1:n])+[derivatives[0]]+list(blocks[:]))
131
+
132
+ # qc.barrier()
133
+
134
+ W3 = Wj_block(1, n+1, "0"*(n+1), dt , 0, xgate=False)
135
+ qc.append(W3, list(derivatives[n:2*n])+list(blocks[:]))
136
+
137
+ # qc.barrier()
138
+
139
+ W4 = Wj_block(2, n, "0"+"1"*(n-1), -dt , 0, xgate=False)
140
+ qc.append(W4, list(derivatives[n+1:2*n]) + [blocks[0]] + [derivatives[n]] + [blocks[1]])
141
+
142
+ return qc
143
+
144
+ def V2(nx, dt, name = "V2"):
145
+ n = int(np.ceil(np.log2(nx)))
146
+
147
+ derivatives = QuantumRegister(2*n)
148
+ blocks = QuantumRegister(2)
149
+
150
+ qc = QuantumCircuit(derivatives, blocks)
151
+
152
+ W1 = Wj_block(2, 0, "", -2*dt , -np.pi/2, xgate=True)
153
+ qc.append(W1, list(blocks[:]))
154
+
155
+ # qc.barrier()
156
+
157
+ for j in range(1, n+1):
158
+ W2 = Wj_block(2+j, 0, "", 2*dt , -np.pi/2, xgate=[1]*(j-1)+[0,1])
159
+ qc.append(W2, list(derivatives[0:j])+list(blocks[:]))
160
+
161
+ # qc.barrier()
162
+
163
+ W3 = Wj_block(2, n, "0"*n, -dt , -np.pi/2, xgate=True)
164
+ qc.append(W3, list(derivatives[0:n])+list(blocks[:]))
165
+
166
+ # qc.barrier()
167
+
168
+ W4 = Wj_block(2, n, "1"*n, 2*dt , -np.pi/2, xgate=True)
169
+ qc.append(W4, list(derivatives[0:n])+list(blocks[:]))
170
+
171
+ # qc.barrier()
172
+
173
+ W5 = Wj_block(3, n-1, "1"*(n-1), dt , -np.pi/2, xgate=[0,1])
174
+ qc.append(W5, list(derivatives[1:n])+[derivatives[0]]+list(blocks[:]))
175
+
176
+ # qc.barrier()
177
+
178
+ W6 = Wj_block(1, 1, "0", 2*dt , -np.pi/2, xgate=False)
179
+ qc.append(W6, list(blocks[:]))
180
+
181
+ # qc.barrier()
182
+
183
+ for j in range(1, n+1):
184
+ W7 = Wj_block(1+j, 1, "0", -2*dt , -np.pi/2, xgate=[1]*(j-1))
185
+ qc.append(W7, [blocks[0]]+list(derivatives[n:n+j])+[blocks[1]])
186
+
187
+ # qc.barrier()
188
+
189
+ W8 = Wj_block(1, n+1, "0"*(n+1), dt , -np.pi/2, xgate=False)
190
+ qc.append(W8, list(derivatives[n:2*n])+list(blocks[:]))
191
+
192
+ # qc.barrier()
193
+
194
+ W9 = Wj_block(1, n+1, "0"+"1"*(n), -2*dt , -np.pi/2, xgate=False)
195
+ qc.append(W9, list(derivatives[n:2*n])+list(blocks[:]))
196
+
197
+ # qc.barrier()
198
+
199
+ W10 = Wj_block(2, n, "0"+"1"*(n-1), -dt , -np.pi/2, xgate=False)
200
+ qc.append(W10, list(derivatives[n+1:2*n]) + [blocks[0]] + [derivatives[n]] + [blocks[1]])
201
+
202
+ # qc.barrier()
203
+
204
+ return qc
205
+
206
+ def schro(nx, na, R, dt, initial_state, steps):
207
+
208
+ nq = int(np.ceil(np.log2(nx)))
209
+
210
+ # warped phase transformation
211
+ dp = 2 * R * np.pi / 2**na
212
+ p = np.arange(- R * np.pi, R * np.pi, step=dp)
213
+ fp = np.exp(-np.abs(p))
214
+ norm1 = np.linalg.norm(fp[2**(na-1):]) # norm of p>=0
215
+
216
+ # construct quantum circuit
217
+ system = QuantumRegister(2*nq+2, name='system')
218
+ ancilla = QuantumRegister(na, name='ancilla')
219
+ qc = QuantumCircuit(system, ancilla)
220
+
221
+ # initialization
222
+ prep = StatePreparation(initial_state)
223
+ anc_prep = StatePreparation(fp / np.linalg.norm(fp))
224
+
225
+ qc.append(prep, system)
226
+ # qc.append(anc_prep, ancilla)
227
+ qc.initialize(fp / np.linalg.norm(fp), ancilla)
228
+
229
+
230
+ # QFT
231
+ qc.append(QFTGate(na), ancilla)
232
+ qc.x(ancilla[-1])
233
+
234
+ A1 = V1(nx, dt, name = "V1").to_gate()
235
+ A2 = V2(nx, dt, name = "V2")
236
+
237
+
238
+ # Hamiltonian simulation for Nt steps
239
+ for i in range(steps):
240
+ # circuit for one step
241
+ for j in range(na):
242
+ # repeat controlled H1 for 2**j times
243
+ qc.append(A1.control().repeat(2**j), [ancilla[j]] + system[:])
244
+
245
+ # qc.append(A1.inverse().control(ctrl_state = "0").repeat(2**(na-1)), [ancilla[na-1]] + system[:])
246
+ qc.append(A1.inverse().repeat(2**(na-1)), system[:])
247
+ qc.append(A2, system[:])
248
+
249
+ # rearrange eta
250
+ qc.x(ancilla[-1])
251
+ qc.append(QFTGate(na).inverse(), ancilla)
252
+
253
+ return qc
254
+
255
+
256
+
257
+ def circ_for_magnitude(field, x, y, nx, na, R, dt, initial_state, steps):
258
+
259
+ qc = schro(nx, na, R, dt, initial_state, steps)
260
+ naimark = QuantumRegister(1, name='Naimark')
261
+ qc.add_register(naimark)
262
+
263
+ if field == 'Ez':
264
+ index = nx * y + x
265
+ elif field == 'Hx':
266
+ index = 2*nx*nx + nx * y + x
267
+ else:
268
+ index = 3*nx*nx + nx * y + x
269
+
270
+ index_bin = format(index, f'0{qc.num_qubits-2}b')
271
+ ctrl_state = '1' + index_bin
272
+ ctrl_qubits = qc.qubits[:-1]
273
+ qc.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state)
274
+
275
+ return qc
276
+
277
+ def circuits_for_sign(field, x, y, nx, na, dt, R, initial_state, steps, xref, yref, field_ref = 'Ez'):
278
+ qc = schro(nx, na, R, dt, initial_state, steps)
279
+
280
+ naimark = QuantumRegister(1, name='Naimark')
281
+ qc.add_register(naimark)
282
+
283
+ if field == 'Ez':
284
+ index = nx * y + x
285
+ elif field == 'Hx':
286
+ index = 2*nx*nx + nx * y + x
287
+ else:
288
+ index = 3*nx*nx + nx * y + x
289
+
290
+ if field_ref == 'Ez':
291
+ index_ref = nx * yref + xref
292
+ elif field_ref == 'Hx':
293
+ index_ref = 2*nx*nx + nx * yref + xref
294
+ else:
295
+ index_ref = 3*nx*nx + nx * yref + xref
296
+
297
+ index_bin = [(index >> i) & 1 for i in range(qc.num_qubits-2)]
298
+ index_ref_bin = [(index_ref >> i) & 1 for i in range(qc.num_qubits-2)]
299
+ index_bin.append(1)
300
+ index_ref_bin.append(1)
301
+
302
+ #Convert reference bitstring to 00000
303
+ for i, bit in enumerate(index_ref_bin):
304
+ if bit == 1:
305
+ qc.x(i)
306
+
307
+ d_bits = [b ^ r for b, r in zip(index_ref_bin, index_bin)]
308
+ control = d_bits.index(1)
309
+
310
+ #Convert the other bitstring to 0001000
311
+ for target, bit in enumerate(d_bits):
312
+ if bit == 1 and target != control:
313
+ qc.cx(control, target)
314
+ qc.h(control)
315
+
316
+ ctrl_state_sum = '0'*(qc.num_qubits-1)
317
+ ctrl_state_diff = '0'*(qc.num_qubits-1-control-1)+'1'+'0'*(control)
318
+
319
+ qcdiff = qc.copy()
320
+
321
+ ctrl_qubits = qc.qubits[:-1]
322
+
323
+ qc.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state_sum)
324
+ qcdiff.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state_diff)
325
+
326
+ return qc, qcdiff
327
+
328
+ def get_absolute_field_value(qc, nq, na, offset, norm):
329
+
330
+ pauli_label = 'Z'+'I'*(2*nq+2+na)
331
+ observable = SparsePauliOp(Pauli(pauli_label))
332
+ ########################################################################################
333
+ estimator = StatevectorEstimator()
334
+
335
+ # === Run Estimator (no parameters needed) ===
336
+ pub = (qc, observable)
337
+ job = estimator.run([pub])
338
+ result = job.result()[0]
339
+ z_exp = result.data.evs.item()
340
+ #########################################################################################
341
+ # === Compute projector expectation ===
342
+ pi_expect = (1 - z_exp) / 2
343
+
344
+ Absolute_value = norm*np.sqrt(pi_expect)-offset
345
+
346
+ return Absolute_value
347
+
348
+ def get_relative_sign(qc, qcdiff, nq, na):
349
+
350
+ pauli_label = 'Z'+'I'*(2*nq+2+na)
351
+ observable = SparsePauliOp(Pauli(pauli_label))
352
+ ########################################################################################
353
+ estimator = StatevectorEstimator()
354
+
355
+ # === Run Estimator ===
356
+ pub = (qc, observable)
357
+ job = estimator.run([pub])
358
+ result = job.result()[0]
359
+ z_exp = result.data.evs.item()
360
+
361
+ pub_diff = (qcdiff, observable)
362
+ job_diff = estimator.run([pub_diff])
363
+ result_diff = job_diff.result()[0]
364
+ z_exp_diff = result_diff.data.evs.item()
365
+ #########################################################################################
366
+ # === Compute projector expectation ===
367
+ pi_expect_sum = (1 - z_exp) / 2
368
+ pi_expect_diff = (1 - z_exp_diff) / 2
369
+
370
+ relative_sign = 'same' if pi_expect_sum >= pi_expect_diff else 'different'
371
+
372
+ return relative_sign
373
+
374
+ def Eref_value(nx, nq, R, dt, na, steps, xref, yref, field_ref = 'Ez'):
375
+ if steps < 31:
376
+ offset = 1
377
+ else :
378
+ offset = 0.15
379
+ deltastate = np.zeros(4*nx*nx)
380
+ # deltastate[nx*nx//2+nx//2:nx*nx//2+nx//2+1] = 1
381
+ deltastate[nx*yref+xref] = 1
382
+ deltastate[0:nx*nx] = deltastate[0:nx*nx] + offset
383
+ norm1 = np.linalg.norm(deltastate)
384
+ initial_state = deltastate/norm1
385
+
386
+ dp = 2 * R * np.pi / 2**na
387
+ p = np.arange(- R * np.pi, R * np.pi, step=dp)
388
+ fp = np.exp(-np.abs(p))
389
+ norm2 = np.linalg.norm(fp)
390
+ norm = norm1 * norm2
391
+
392
+ qc = circ_for_magnitude(field_ref, xref, yref, nx, na, R, dt, initial_state, steps)
393
+
394
+ Ezref = get_absolute_field_value(qc, nq, na, offset, norm)
395
+
396
+ return Ezref
397
+
398
+
399
+ def transpile_circ(circ, basis_gates=None):
400
+ """
401
+ Transpile the circuit to the specified basis gates.
402
+ """
403
+ if basis_gates is None:
404
+ basis_gates = ['z', 'y', 'x', 'sdg', 's', 'h', 'rz', 'ry', 'rx', 'ecr', 'cz', 'cx']
405
+
406
+ transpiled_circ = transpile(circ, basis_gates=basis_gates)
407
+ return transpiled_circ
408
+
409
+ def compute_fidelity(circ1, circ2):
410
+
411
+ circ_1 = tensornetwork_from_circuit(transpile_circ(circ1), simulator_settings)
412
+ circ_2 = tensornetwork_from_circuit(transpile_circ(circ2), simulator_settings)
413
+ fidelity = abs(compute_overlap(circ_1, circ_2))**2
414
+
415
+ return fidelity
416
+
417
+ # def create_impulse_state(grid_dims, impulse_pos):
418
+ # """
419
+ # Creates an initial state vector with a single delta impulse at a specified grid position.
420
+
421
+ # The 2D grid is flattened into a 1D vector in row-major order, and this
422
+ # vector is then padded to match the full simulation state space size (4x).
423
+
424
+ # Args:
425
+ # grid_dims (tuple): A tuple (width, height) defining the simulation grid dimensions.
426
+ # For your original code, this would be (nx, nx).
427
+ # impulse_pos (tuple): A tuple (x, y) for the position of the impulse.
428
+ # Coordinates are 0-indexed.
429
+
430
+ # Returns:
431
+ # numpy.ndarray: The full, padded initial state vector with a single 1.
432
+
433
+ # Raises:
434
+ # ValueError: If the impulse position is outside the grid dimensions.
435
+ # """
436
+ # grid_width, grid_height = grid_dims
437
+ # impulse_x, impulse_y = impulse_pos
438
+
439
+ # # --- Input Validation ---
440
+ # # Ensure the requested impulse position is actually on the grid.
441
+ # if not (0 <= impulse_x < grid_width and 0 <= impulse_y < grid_height):
442
+ # raise ValueError(f"Impulse position ({impulse_x}, {impulse_y}) is outside the "
443
+ # f"grid dimensions ({grid_width}x{grid_height}).")
444
+
445
+ # # --- 1. Calculate the 1D Array Index ---
446
+ # # Convert the (x, y) coordinate to a single index in a flattened 1D array.
447
+ # # The formula for row-major order is: index = y_coord * width + x_coord
448
+ # flat_index = impulse_y * grid_width + impulse_x
449
+
450
+ # # --- 2. Create the Full, Padded State Vector ---
451
+ # grid_size = grid_width * grid_height
452
+ # total_size = 4 * grid_size # The simulation space is 4x the grid size.
453
+ # initial_state = np.zeros(total_size)
454
+
455
+ # # --- 3. Set the Delta Impulse ---
456
+ # initial_state[flat_index] = 1
457
+
458
+ # return initial_state
utils/delta_impulse_generator.py ADDED
@@ -0,0 +1,493 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import math
3
+ from qiskit.circuit import QuantumCircuit, QuantumRegister
4
+ from qiskit.circuit.library import StatePreparation, QFTGate, RZGate
5
+ from qiskit.quantum_info import Statevector
6
+ import pyvista as pv
7
+
8
+ def create_impulse_state(grid_dims, impulse_pos):
9
+ """
10
+ Creates an initial state vector with a single delta impulse at a specified grid position.
11
+
12
+ The 2D grid is flattened into a 1D vector in row-major order, and this
13
+ vector is then padded to match the full simulation state space size (4x).
14
+
15
+ Args:
16
+ grid_dims (tuple): A tuple (width, height) defining the simulation grid dimensions.
17
+ For your original code, this would be (nx, nx).
18
+ impulse_pos (tuple): A tuple (x, y) for the position of the impulse.
19
+ Coordinates are 0-indexed.
20
+
21
+ Returns:
22
+ numpy.ndarray: The full, padded initial state vector with a single 1.
23
+
24
+ Raises:
25
+ ValueError: If the impulse position is outside the grid dimensions.
26
+ """
27
+ grid_width, grid_height = grid_dims
28
+ impulse_x, impulse_y = impulse_pos
29
+
30
+ # --- Input Validation ---
31
+ # Ensure the requested impulse position is actually on the grid.
32
+ if not (0 <= impulse_x < grid_width and 0 <= impulse_y < grid_height):
33
+ raise ValueError(f"Impulse position ({impulse_x}, {impulse_y}) is outside the "
34
+ f"grid dimensions ({grid_width}x{grid_height}).")
35
+
36
+ # --- 1. Calculate the 1D Array Index ---
37
+ # Convert the (x, y) coordinate to a single index in a flattened 1D array.
38
+ # The formula for row-major order is: index = y_coord * width + x_coord
39
+ flat_index = impulse_y * grid_width + impulse_x
40
+
41
+ # --- 2. Create the Full, Padded State Vector ---
42
+ grid_size = grid_width * grid_height
43
+ total_size = 4 * grid_size # The simulation space is 4x the grid size.
44
+ initial_state = np.zeros(total_size)
45
+
46
+ # --- 3. Set the Delta Impulse ---
47
+ initial_state[flat_index] = 1
48
+
49
+ return initial_state
50
+
51
+ def create_gaussian_state(grid_dims, mu, sigma):
52
+ """
53
+ Creates an initial state vector with a 2D Gaussian distribution.
54
+
55
+ The state is normalized and padded to match the full simulation state space size (4x).
56
+
57
+ Args:
58
+ grid_dims (tuple): A tuple (width, height) defining the grid dimensions.
59
+ mu (tuple): A tuple (mu_x, mu_y) for the center (mean) of the Gaussian.
60
+ sigma (tuple): A tuple (sigma_x, sigma_y) for the standard deviation (spread).
61
+
62
+ Returns:
63
+ numpy.ndarray: The full, padded initial state vector for the Gaussian state.
64
+
65
+ Raises:
66
+ ValueError: If sigma values are not positive.
67
+ """
68
+ grid_width, grid_height = grid_dims
69
+ mu_x, mu_y = mu
70
+ sigma_x, sigma_y = sigma
71
+
72
+ if sigma_x <= 0 or sigma_y <= 0:
73
+ raise ValueError("Sigma values (spread) must be positive.")
74
+
75
+ # --- 1. Create a Coordinate Grid ---
76
+ x = np.arange(0, grid_width)
77
+ y = np.arange(0, grid_height)
78
+ X, Y = np.meshgrid(x, y)
79
+
80
+ # --- 2. Calculate the 2D Gaussian Function ---
81
+ gaussian_2d = np.exp(-((X - mu_x)**2 / (2 * sigma_x**2)) -
82
+ ((Y - mu_y)**2 / (2 * sigma_y**2)))
83
+
84
+ # --- 3. Normalize the State Vector ---
85
+ # For a valid quantum state, the L2 norm (sum of squares of amplitudes) must be 1.
86
+ norm = np.linalg.norm(gaussian_2d)
87
+ if norm > 0:
88
+ gaussian_2d = gaussian_2d / norm
89
+
90
+ # --- 4. Flatten and Pad the Vector ---
91
+ gaussian_flat = gaussian_2d.flatten()
92
+ grid_size = grid_width * grid_height
93
+ total_size = 4 * grid_size
94
+ initial_state = np.pad(gaussian_flat, (0, total_size - grid_size), mode='constant')
95
+
96
+ return initial_state
97
+
98
+
99
+
100
+
101
+
102
+ # --- New: Continuous-position helpers for excitation before meshing ---
103
+ def _normalize_to_unit(vec: np.ndarray) -> np.ndarray:
104
+ n = np.linalg.norm(vec)
105
+ return vec / n if n > 0 else vec
106
+
107
+
108
+
109
+
110
+ def create_impulse_state_from_pos(grid_dims, pos01):
111
+ """
112
+ Create a delta-like initial state from continuous position pos01=(x,y) in [0,1].
113
+
114
+ Why grid_dims?
115
+ - Simulation runs on a discrete nx×ny lattice; the continuous position must be
116
+ discretized onto that grid to produce the state vector fed into the solver.
117
+ - grid_dims provides (nx, ny) so we can map (x,y)∈[0,1]→grid coordinates via
118
+ gx = x*(nx-1), gy = y*(ny-1), then distribute amplitude bilinearly to the 4
119
+ neighboring nodes. This is required only for the simulation state, not the preview.
120
+
121
+ The preview uses create_impulse_preview_state(), which renders a smooth bump on a
122
+ fixed unit-square grid independent of nx for visualization.
123
+ """
124
+ grid_width, grid_height = grid_dims
125
+ px, py = pos01
126
+ px = float(max(0.0, min(1.0, px)))
127
+ py = float(max(0.0, min(1.0, py)))
128
+
129
+ gx = px * (grid_width - 1)
130
+ gy = py * (grid_height - 1)
131
+ i0, j0 = int(np.floor(gx)), int(np.floor(gy))
132
+ i1, j1 = min(i0 + 1, grid_width - 1), min(j0 + 1, grid_height - 1)
133
+ dx, dy = gx - i0, gy - j0
134
+
135
+ w00 = (1 - dx) * (1 - dy)
136
+ w10 = dx * (1 - dy)
137
+ w01 = (1 - dx) * dy
138
+ w11 = dx * dy
139
+
140
+ grid_size = grid_width * grid_height
141
+ total_size = 4 * grid_size
142
+ field = np.zeros(grid_size)
143
+ field[j0 * grid_width + i0] += w00
144
+ field[j0 * grid_width + i1] += w10
145
+ field[j1 * grid_width + i0] += w01
146
+ field[j1 * grid_width + i1] += w11
147
+ field = _normalize_to_unit(field)
148
+
149
+ initial_state = np.zeros(total_size)
150
+ initial_state[:grid_size] = field
151
+ return initial_state
152
+
153
+
154
+ def create_gaussian_state_from_pos(grid_dims, mu01, sigma01):
155
+ """
156
+ Create a Gaussian initial state with center mu01=(x,y) and spreads sigma01=(sx,sy)
157
+ in [0,1] of the domain, then discretize to the solver grid given by grid_dims.
158
+
159
+ Why grid_dims?
160
+ - The quantum solver expects a vector aligned to the chosen nx×ny simulation grid.
161
+ We convert normalized μ and σ (fractions of the domain) into grid units using
162
+ (nx-1) and (ny-1). This step is necessary for the simulation, not for the preview.
163
+
164
+ For preview-only rendering, use create_impulse_preview_state() to keep the visuals
165
+ continuous and independent of nx.
166
+ """
167
+ grid_width, grid_height = grid_dims
168
+ mu_x01, mu_y01 = mu01
169
+ sig_x01, sig_y01 = sigma01
170
+
171
+ mu_x01 = float(max(0.0, min(1.0, mu_x01)))
172
+ mu_y01 = float(max(0.0, min(1.0, mu_y01)))
173
+ sig_x01 = float(sig_x01)
174
+ sig_y01 = float(sig_y01)
175
+ if sig_x01 <= 0 or sig_y01 <= 0:
176
+ raise ValueError("Sigma values (spread) must be positive.")
177
+
178
+ mu_x = mu_x01 * (grid_width - 1)
179
+ mu_y = mu_y01 * (grid_height - 1)
180
+ sigma_x = sig_x01 * (grid_width - 1)
181
+ sigma_y = sig_y01 * (grid_height - 1)
182
+
183
+ x = np.arange(0, grid_width)
184
+ y = np.arange(0, grid_height)
185
+ X, Y = np.meshgrid(x, y)
186
+ gaussian_2d = np.exp(-((X - mu_x) ** 2) / (2 * sigma_x ** 2) - ((Y - mu_y) ** 2) / (2 * sigma_y ** 2))
187
+
188
+ field = _normalize_to_unit(gaussian_2d.ravel())
189
+ grid_size = grid_width * grid_height
190
+ total_size = 4 * grid_size
191
+ initial_state = np.zeros(total_size)
192
+ initial_state[:grid_size] = field
193
+ return initial_state
194
+
195
+ # --- Simulation Code (from previous context) ---
196
+ def Wj_block(j, n, ctrl_state, theta, lam, name='Wj_block', xgate=False):
197
+ qc = QuantumCircuit(n + j, name=name)
198
+ if j > 1: qc.cx(n + j - 1, range(n, n + j - 1))
199
+ if lam != 0: qc.p(lam, n + j - 1)
200
+ qc.h(n + j - 1)
201
+ if xgate and j > 1:
202
+ if isinstance(xgate, (list, tuple)):
203
+ for idx, flag in enumerate(xgate):
204
+ if flag: qc.x(n + idx)
205
+ elif xgate is True: qc.x(range(n, n + j - 1))
206
+ if j > 1:
207
+ mcrz = RZGate(theta).control(len(ctrl_state) + j - 1, ctrl_state="1" * (j - 1) + ctrl_state)
208
+ qc.append(mcrz, range(0, n + j))
209
+ else:
210
+ mcrz = RZGate(theta).control(len(ctrl_state), ctrl_state=ctrl_state)
211
+ qc.append(mcrz, range(0, n + j))
212
+ if xgate and j > 1:
213
+ if isinstance(xgate, (list, tuple)):
214
+ for idx, flag in enumerate(xgate):
215
+ if flag: qc.x(n + idx)
216
+ elif xgate is True: qc.x(range(n, n + j - 1))
217
+ qc.h(n + j - 1)
218
+ if lam != 0: qc.p(-lam, n + j - 1)
219
+ if j > 1: qc.cx(n + j - 1, range(n, n + j - 1))
220
+ return qc.to_gate(label=name)
221
+
222
+ def V1(nx, dt):
223
+ n = int(np.ceil(np.log2(nx)))
224
+ derivatives, blocks = QuantumRegister(2 * n), QuantumRegister(2)
225
+ qc = QuantumCircuit(derivatives, blocks)
226
+ qc.append(Wj_block(2, n, "0" * n, -dt, 0, xgate=True), list(derivatives[0:n]) + list(blocks[:]))
227
+ qc.append(Wj_block(3, n - 1, "1" * (n - 1), dt, 0, xgate=[0, 1]), list(derivatives[1:n]) + [derivatives[0]] + list(blocks[:]))
228
+ qc.append(Wj_block(1, n + 1, "0" * (n + 1), dt, 0, xgate=True), list(derivatives[n:2 * n]) + list(blocks[:]))
229
+ qc.append(Wj_block(2, n, "0" + "1" * (n - 1), -dt, 0, xgate=False), list(derivatives[n + 1:2 * n]) + [blocks[0]] + [derivatives[n]] + [blocks[1]])
230
+ return qc
231
+
232
+ def V2(nx, dt):
233
+ n = int(np.ceil(np.log2(nx)))
234
+ derivatives, blocks = QuantumRegister(2 * n), QuantumRegister(2)
235
+ qc = QuantumCircuit(derivatives, blocks)
236
+ qc.append(Wj_block(2, 0, "", -2 * dt, -np.pi / 2, xgate=True), blocks[:])
237
+ for j in range(1, n + 1): qc.append(Wj_block(2 + j, 0, "", 2 * dt, -np.pi / 2, xgate=[1] * (j - 1) + [0, 1]), list(derivatives[0:j]) + list(blocks[:]))
238
+ qc.append(Wj_block(2, n, "0" * n, -dt, -np.pi / 2, xgate=True), list(derivatives[0:n]) + list(blocks[:]))
239
+ qc.append(Wj_block(2, n, "1" * n, 2 * dt, -np.pi / 2, xgate=True), list(derivatives[0:n]) + list(blocks[:]))
240
+ qc.append(Wj_block(3, n - 1, "1" * (n - 1), dt, -np.pi / 2, xgate=[0, 1]), list(derivatives[1:n]) + [derivatives[0]] + list(blocks[:]))
241
+ qc.append(Wj_block(1, 1, "0", 2 * dt, -np.pi / 2, xgate=False), blocks[:])
242
+ for j in range(1, n + 1): qc.append(Wj_block(1 + j, 1, "0", -2 * dt, -np.pi / 2, xgate=[1] * (j - 1)), [blocks[0]] + list(derivatives[n:n + j]) + [blocks[1]])
243
+ qc.append(Wj_block(1, n + 1, "0" * (n + 1), dt, -np.pi / 2, xgate=False), list(derivatives[n:2 * n]) + list(blocks[:]))
244
+ qc.append(Wj_block(1, n + 1, "0" + "1" * n, -2 * dt, -np.pi / 2, xgate=False), list(derivatives[n:2 * n]) + list(blocks[:]))
245
+ qc.append(Wj_block(2, n, "0" + "1" * (n - 1), -dt, -np.pi / 2, xgate=False), list(derivatives[n + 1:2 * n]) + [blocks[0]] + [derivatives[n]] + [blocks[1]])
246
+ return qc
247
+
248
+ def run_sim(nx, na, R, initial_state, T, snapshot_dt=None, stop_check=None, progress_callback=None, print_callback=None):
249
+ """
250
+ Runs the quantum simulation for electromagnetic scattering with fixed dt=0.1.
251
+ Captures frames only at user-defined snapshot times: [0, Δt, 2Δt, ..., ≤ T_eff],
252
+ always including t=0 and the final solver-aligned T (T_eff = floor(T/dt)*dt).
253
+
254
+ Returns:
255
+ frames (np.ndarray), snapshot_times (np.ndarray)
256
+ """
257
+ def _log(msg):
258
+ if print_callback:
259
+ print_callback(msg)
260
+ else:
261
+ print(msg)
262
+
263
+ dt = 0.1
264
+ # Validate total time and compute solver-aligned end time
265
+ try:
266
+ T_val = float(T)
267
+ except Exception:
268
+ return np.array([]), np.array([])
269
+ if T_val <= 0:
270
+ return np.array([]), np.array([])
271
+
272
+ steps = int(np.floor(T_val / dt))
273
+ if steps <= 0:
274
+ return np.array([]), np.array([])
275
+ T_eff = steps * dt
276
+
277
+ # Determine snapshot Δt on solver grid
278
+ tol = 1e-12
279
+ if snapshot_dt is None:
280
+ snapshot_dt_val = dt
281
+ else:
282
+ try:
283
+ snapshot_dt_val = float(snapshot_dt)
284
+ except Exception:
285
+ snapshot_dt_val = dt
286
+ if snapshot_dt_val < dt - tol:
287
+ snapshot_dt_val = dt
288
+ k = max(1, int(round(snapshot_dt_val / dt)))
289
+ snapshot_dt_eff = k * dt
290
+
291
+ # Build requested snapshot times on solver grid
292
+ target_times = [0.0]
293
+ t = 0.0
294
+ while t + snapshot_dt_eff <= T_eff + tol:
295
+ t = round(t + snapshot_dt_eff, 12)
296
+ if t <= T_eff + tol:
297
+ target_times.append(min(t, T_eff))
298
+ if abs(target_times[-1] - T_eff) > tol:
299
+ target_times.append(T_eff)
300
+
301
+ # Setup circuit
302
+ nq = int(np.ceil(np.log2(nx)))
303
+ dp = 2 * R * np.pi / 2 ** na
304
+ p = np.arange(-R * np.pi, R * np.pi, step=dp)
305
+ fp = np.exp(-np.abs(p))
306
+ system, ancilla = QuantumRegister(2 * nq + 2), QuantumRegister(na)
307
+ qc = QuantumCircuit(system, ancilla)
308
+ qc.append(StatePreparation(initial_state), system)
309
+ qc.append(StatePreparation(fp / np.linalg.norm(fp)), ancilla)
310
+ expA1 = V1(nx, dt).to_gate()
311
+ expA2 = V2(nx, dt)
312
+
313
+ frames = []
314
+ # Capture initial frame at t=0
315
+ sv0 = np.real(Statevector(qc)).reshape(2 ** na, 2 ** (2 * nq + 2))
316
+ frames.append(sv0[2 ** (na - 1)])
317
+ next_idx = 1 # next target_times index to capture
318
+
319
+ _log(f"Starting simulation: T={T_eff:.2f}s, steps={steps}, snapshot_dt={snapshot_dt_eff:.2f}s")
320
+
321
+ for i in range(steps):
322
+ if stop_check and stop_check():
323
+ _log(f"Simulation interrupted at step {i}/{steps}")
324
+ break
325
+ # One solver step
326
+ qc.append(QFTGate(na), ancilla)
327
+ qc.x(ancilla[-1])
328
+ for j in range(na - 1):
329
+ qc.append(expA1.control().repeat(2 ** j), [ancilla[j]] + system[:])
330
+ qc.append(expA1.inverse().control(ctrl_state="0").repeat(2 ** (na - 1)), [ancilla[na - 1]] + system[:])
331
+ qc.append(expA2, system[:])
332
+ qc.x(ancilla[-1])
333
+ qc.append(QFTGate(na).inverse(), ancilla)
334
+
335
+ current_time = (i + 1) * dt
336
+ if next_idx < len(target_times) and abs(current_time - target_times[next_idx]) <= tol:
337
+ u = np.real(Statevector(qc)).reshape(2 ** na, 2 ** (2 * nq + 2))
338
+ frames.append(u[2 ** (na - 1)])
339
+ next_idx += 1
340
+
341
+ if progress_callback:
342
+ try:
343
+ progress = ((i + 1) / steps) * 100
344
+ progress_callback(progress)
345
+ except Exception:
346
+ pass
347
+
348
+ if progress_callback:
349
+ try:
350
+ progress_callback(100.0)
351
+ except Exception:
352
+ pass
353
+
354
+ _log("Simulation completed.")
355
+
356
+ # Ensure snapshot_times align with number of captured frames (covers early stop)
357
+ frames_arr = np.asarray(frames)
358
+ times_arr = np.asarray(target_times[: len(frames_arr)])
359
+ return frames_arr, times_arr
360
+
361
+ def create_impulse_preview_state(preview_n: int, pos01, sigma01: float = 0.02):
362
+ """
363
+ Smooth delta-like preview on a unit square using a narrow Gaussian (sigma in [0,1]).
364
+ Preview-only helper, independent of simulation grid size (nx). Use this for the
365
+ Excitation preview; use the *_from_pos() variants for the actual simulation.
366
+ """
367
+ try:
368
+ sx = float(sigma01) if sigma01 and sigma01 > 0 else 0.02
369
+ except Exception:
370
+ sx = 0.02
371
+ return create_gaussian_state_from_pos((int(preview_n), int(preview_n)), (float(pos01[0]), float(pos01[1])), (sx, sx))
372
+
373
+
374
+
375
+
376
+
377
+
378
+ ##### Statevector Estimator Simulation Code Below #####
379
+
380
+ from .base_functions import *
381
+
382
+ def create_time_frames(total_time, snapshot_interval):
383
+ dt = 0.1
384
+ tol = 1e-9
385
+ try:
386
+ T_val = float(total_time)
387
+ except (ValueError, TypeError):
388
+ return []
389
+ if T_val <= 0:
390
+ return []
391
+ steps = int(np.floor(T_val / dt))
392
+ if steps <= 0:
393
+ return [0.0]
394
+ T_eff = steps * dt
395
+ try:
396
+ snapshot_dt_val = float(snapshot_interval)
397
+ except (ValueError, TypeError):
398
+ snapshot_dt_val = dt
399
+ if snapshot_dt_val < dt:
400
+ snapshot_dt_val = dt
401
+ k = max(1, int(round(snapshot_dt_val / dt)))
402
+ snapshot_dt_eff = k * dt
403
+ times = np.arange(0, T_eff + tol, snapshot_dt_eff)
404
+ if abs(times[-1] - T_eff) > tol:
405
+ times = np.append(times, T_eff)
406
+ times = np.round(times, 12)
407
+ unique_times = []
408
+ for t in times:
409
+ if not unique_times or abs(t - unique_times[-1]) > tol:
410
+ unique_times.append(float(t))
411
+ return unique_times
412
+
413
+
414
+
415
+ def run_sve(field, x, y, T, snapshot_time, nx, initial_state, impulse_pos, progress_callback=None, print_callback=None):
416
+ """Statevector Estimator for time-series field values.
417
+
418
+ Supports both single-point and multi-point modes.
419
+
420
+ - Single-point (backward compatible): x, y are integers; returns list[float].
421
+ - Multi-point: x is a list/tuple of (ix, iy) integer pairs and y is None; returns dict[(ix,iy) -> list[float]].
422
+ """
423
+ def _log(msg):
424
+ if print_callback:
425
+ print_callback(msg)
426
+ else:
427
+ print(msg)
428
+
429
+ na = 1
430
+ dt = 0.1
431
+ R = 4
432
+ nq = int(np.ceil(np.log2(nx)))
433
+
434
+ # Normalize monitor points input
435
+ if isinstance(x, (list, tuple)) and y is None:
436
+ points = [tuple(map(int, pt)) for pt in x]
437
+ multi = True
438
+ else:
439
+ points = [(int(x), int(y))]
440
+ multi = False
441
+
442
+ xref, yref = impulse_pos
443
+
444
+ offset = 0
445
+ grid_dims = (nx, nx)
446
+ initial_state = create_impulse_state(grid_dims, impulse_pos)
447
+
448
+ dp = 2 * R * np.pi / 2**na
449
+ p = np.arange(- R * np.pi, R * np.pi, step=dp)
450
+ fp = np.exp(-np.abs(p))
451
+ norm = np.linalg.norm(fp)
452
+
453
+ time_frames = create_time_frames(T, snapshot_time)
454
+ total_frames = len(time_frames)
455
+
456
+ _log(f"Starting QPU simulation: T={T}s, frames={total_frames}, points={len(points)}")
457
+
458
+ # Prepare outputs
459
+ if multi:
460
+ series_by_point = { (px, py): [] for (px, py) in points }
461
+ else:
462
+ series_single = []
463
+
464
+ for idx, time in enumerate(time_frames):
465
+ steps = int(math.ceil(time / dt))
466
+ # Reference Ez field at impulse location for sign
467
+ Eref = Eref_value(nx, nq, R, dt, na, steps, xref, yref, field_ref='Ez')
468
+
469
+ for (px, py) in points:
470
+ circ_magnitude = circ_for_magnitude(field, px, py, nx, na, R, dt, initial_state, steps)
471
+ magnitude = get_absolute_field_value(circ_magnitude, nq, na, offset, norm)
472
+
473
+ if field == 'Ez' and px == xref and py == yref:
474
+ Field_value = -magnitude if Eref < 0 else magnitude
475
+ else:
476
+ circsum, circdiff = circuits_for_sign(field, px, py, nx, na, dt, R, initial_state, steps, xref, yref, field_ref='Ez')
477
+ sign = get_relative_sign(circsum, circdiff, nq, na)
478
+ if (sign == 'same' and Eref > 0) or (sign == 'different' and Eref < 0):
479
+ Field_value = magnitude
480
+ else:
481
+ Field_value = -magnitude
482
+
483
+ if multi:
484
+ series_by_point[(px, py)].append(Field_value)
485
+ else:
486
+ series_single.append(Field_value)
487
+
488
+ if progress_callback:
489
+ progress_callback((idx + 1) / total_frames * 100)
490
+
491
+ _log("Statevector Estimator simulation completed.")
492
+
493
+ return series_by_point if multi else series_single
wq ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Merge branch 'main' of https://huggingface.co/spaces/ansysresearch/quantum
2
+ # Please enter a commit message to explain why this merge is necessary,
3
+ # especially if it merges an updated upstream into a topic branch.
4
+ #
5
+ # Lines starting with '#' will be ignored, and an empty message aborts
6
+ # the commit.