import numpy as np import scipy.sparse as sp import math import random import matplotlib.pyplot as plt from scipy.special import jn from scipy.sparse import identity, csr_matrix, kron, diags, eye from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit.circuit.library import MCXGate, MCPhaseGate, RXGate, CRXGate, QFTGate, StatePreparation, PauliEvolutionGate, RZGate from qiskit.quantum_info import SparsePauliOp, Statevector, Operator, Pauli from scipy.linalg import expm # from tools import * from qiskit.qasm3 import dumps # QASM 3 exporter from qiskit.qasm3 import loads from qiskit.circuit.library import QFT from qiskit.primitives import StatevectorEstimator from qiskit import transpile from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit, apply_circuit_to_state, compute_overlap from qiskit_aer import AerSimulator from qiskit_ionq import IonQProvider import os my_api_key = os.getenv("IONQ_API_KEY") from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as Estimator # provider = IonQProvider() api_token = "SgUkiDq1r2bVEadyiUfvtuxQ03Qci6UW" provider = IonQProvider(api_token) ionq_backend = provider.get_backend("simulator") simulator_settings = AerSimulator( method="matrix_product_state", matrix_product_state_max_bond_dimension=100, ) def Wj(j, theta, lam, name='Wj', xgate=False): if not xgate: name = f' $W_{j}$ ' qc=QuantumCircuit(j, name=name) if j > 1: qc.cx(j-1, range(j-1)) if lam != 0: qc.p(lam, j-1) qc.h(j-1) if xgate: qc.x(range(j-1)) # the multicontrolled rz gate # it will be decomposed in qiskit if j > 1: qc.mcrz(theta, range(j-1), j-1) else: qc.rz(theta, j-1) if xgate: qc.x(range(j-1)) qc.h(j-1) if lam != 0: qc.p(-lam, j-1) if j > 1: qc.cx(j-1, range(j-1)) return qc def Wj_block(j, n, ctrl_state, theta, lam, name='Wj_block', xgate=False): if not xgate: name = f' $W_{j}_block$ ' qc=QuantumCircuit(n + j, name=name) if j > 1: qc.cx(n + j-1, range(n, n+j-1)) if lam != 0: qc.p(lam, n + j -1) qc.h(n + j -1) if xgate and j>1: if isinstance(xgate, (list, tuple)): # selective application for idx, flag in enumerate(xgate): if flag: # only apply where flag == 1 qc.x(n + idx) elif xgate is True: # apply to all qc.x(range(n, n+j-1)) # the multicontrolled rz gate # it will be decomposed in qiskit if j > 1: mcrz = RZGate(theta).control(len(ctrl_state) + j-1, ctrl_state = "1"*(j-1)+ctrl_state) qc.append(mcrz, range(0, n + j)) else: mcrz = RZGate(theta).control(len(ctrl_state), ctrl_state = ctrl_state) qc.append(mcrz, range(0, n+j)) if xgate and j>1: if isinstance(xgate, (list, tuple)): # selective application for idx, flag in enumerate(xgate): if flag: # only apply where flag == 1 qc.x(n + idx) elif xgate is True: # apply to all qc.x(range(n, n+j-1)) qc.h(n+ j-1) if lam != 0: qc.p(-lam, n + j-1) if j > 1: qc.cx(n + j-1, range(n, n +j-1)) return qc.to_gate(label=name) def V1(nx, dt, name = "V1"): n = int(np.ceil(np.log2(nx))) derivatives = QuantumRegister(2*n) blocks = QuantumRegister(2) qc = QuantumCircuit(derivatives, blocks) W1 = Wj_block(2, n, "0"*n, -dt , 0, xgate=True) qc.append(W1, list(derivatives[0:n])+list(blocks[:])) # qc.barrier() W2 = Wj_block(3, n-1, "1"*(n-1), dt , 0, xgate=[0,1]) qc.append(W2, list(derivatives[1:n])+[derivatives[0]]+list(blocks[:])) # qc.barrier() W3 = Wj_block(1, n+1, "0"*(n+1), dt , 0, xgate=False) qc.append(W3, list(derivatives[n:2*n])+list(blocks[:])) # qc.barrier() W4 = Wj_block(2, n, "0"+"1"*(n-1), -dt , 0, xgate=False) qc.append(W4, list(derivatives[n+1:2*n]) + [blocks[0]] + [derivatives[n]] + [blocks[1]]) return qc def V2(nx, dt, name = "V2"): n = int(np.ceil(np.log2(nx))) derivatives = QuantumRegister(2*n) blocks = QuantumRegister(2) qc = QuantumCircuit(derivatives, blocks) W1 = Wj_block(2, 0, "", -2*dt , -np.pi/2, xgate=True) qc.append(W1, list(blocks[:])) # qc.barrier() for j in range(1, n+1): W2 = Wj_block(2+j, 0, "", 2*dt , -np.pi/2, xgate=[1]*(j-1)+[0,1]) qc.append(W2, list(derivatives[0:j])+list(blocks[:])) # qc.barrier() W3 = Wj_block(2, n, "0"*n, -dt , -np.pi/2, xgate=True) qc.append(W3, list(derivatives[0:n])+list(blocks[:])) # qc.barrier() W4 = Wj_block(2, n, "1"*n, 2*dt , -np.pi/2, xgate=True) qc.append(W4, list(derivatives[0:n])+list(blocks[:])) # qc.barrier() W5 = Wj_block(3, n-1, "1"*(n-1), dt , -np.pi/2, xgate=[0,1]) qc.append(W5, list(derivatives[1:n])+[derivatives[0]]+list(blocks[:])) # qc.barrier() W6 = Wj_block(1, 1, "0", 2*dt , -np.pi/2, xgate=False) qc.append(W6, list(blocks[:])) # qc.barrier() for j in range(1, n+1): W7 = Wj_block(1+j, 1, "0", -2*dt , -np.pi/2, xgate=[1]*(j-1)) qc.append(W7, [blocks[0]]+list(derivatives[n:n+j])+[blocks[1]]) # qc.barrier() W8 = Wj_block(1, n+1, "0"*(n+1), dt , -np.pi/2, xgate=False) qc.append(W8, list(derivatives[n:2*n])+list(blocks[:])) # qc.barrier() W9 = Wj_block(1, n+1, "0"+"1"*(n), -2*dt , -np.pi/2, xgate=False) qc.append(W9, list(derivatives[n:2*n])+list(blocks[:])) # qc.barrier() W10 = Wj_block(2, n, "0"+"1"*(n-1), -dt , -np.pi/2, xgate=False) qc.append(W10, list(derivatives[n+1:2*n]) + [blocks[0]] + [derivatives[n]] + [blocks[1]]) # qc.barrier() return qc def schro(nx, na, R, dt, initial_state, steps): nq = int(np.ceil(np.log2(nx))) # warped phase transformation dp = 2 * R * np.pi / 2**na p = np.arange(- R * np.pi, R * np.pi, step=dp) fp = np.exp(-np.abs(p)) norm1 = np.linalg.norm(fp[2**(na-1):]) # norm of p>=0 # construct quantum circuit system = QuantumRegister(2*nq+2, name='system') ancilla = QuantumRegister(na, name='ancilla') qc = QuantumCircuit(system, ancilla) # initialization prep = StatePreparation(initial_state) anc_prep = StatePreparation(fp / np.linalg.norm(fp)) qc.append(prep, system) # qc.append(anc_prep, ancilla) qc.initialize(fp / np.linalg.norm(fp), ancilla) # QFT qc.append(QFTGate(na), ancilla) qc.x(ancilla[-1]) A1 = V1(nx, dt, name = "V1").to_gate() A2 = V2(nx, dt, name = "V2") # Hamiltonian simulation for Nt steps for i in range(steps): # circuit for one step for j in range(na): # repeat controlled H1 for 2**j times qc.append(A1.control().repeat(2**j), [ancilla[j]] + system[:]) # qc.append(A1.inverse().control(ctrl_state = "0").repeat(2**(na-1)), [ancilla[na-1]] + system[:]) qc.append(A1.inverse().repeat(2**(na-1)), system[:]) qc.append(A2, system[:]) # rearrange eta qc.x(ancilla[-1]) qc.append(QFTGate(na).inverse(), ancilla) return qc def circ_for_magnitude(field, x, y, nx, na, R, dt, initial_state, steps): qc = schro(nx, na, R, dt, initial_state, steps) naimark = QuantumRegister(1, name='Naimark') qc.add_register(naimark) if field == 'Ez': index = nx * y + x elif field == 'Hx': index = 2*nx*nx + nx * y + x else: index = 3*nx*nx + nx * y + x index_bin = format(index, f'0{qc.num_qubits-2}b') ctrl_state = '1' + index_bin ctrl_qubits = qc.qubits[:-1] qc.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state) return qc def circuits_for_sign(field, x, y, nx, na, dt, R, initial_state, steps, xref, yref, field_ref = 'Ez'): qc = schro(nx, na, R, dt, initial_state, steps) naimark = QuantumRegister(1, name='Naimark') qc.add_register(naimark) if field == 'Ez': index = nx * y + x elif field == 'Hx': index = 2*nx*nx + nx * y + x else: index = 3*nx*nx + nx * y + x if field_ref == 'Ez': index_ref = nx * yref + xref elif field_ref == 'Hx': index_ref = 2*nx*nx + nx * yref + xref else: index_ref = 3*nx*nx + nx * yref + xref index_bin = [(index >> i) & 1 for i in range(qc.num_qubits-2)] index_ref_bin = [(index_ref >> i) & 1 for i in range(qc.num_qubits-2)] index_bin.append(1) index_ref_bin.append(1) #Convert reference bitstring to 00000 for i, bit in enumerate(index_ref_bin): if bit == 1: qc.x(i) d_bits = [b ^ r for b, r in zip(index_ref_bin, index_bin)] control = d_bits.index(1) #Convert the other bitstring to 0001000 for target, bit in enumerate(d_bits): if bit == 1 and target != control: qc.cx(control, target) qc.h(control) ctrl_state_sum = '0'*(qc.num_qubits-1) ctrl_state_diff = '0'*(qc.num_qubits-1-control-1)+'1'+'0'*(control) qcdiff = qc.copy() ctrl_qubits = qc.qubits[:-1] qc.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state_sum) qcdiff.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state_diff) return qc, qcdiff def get_absolute_field_value(qc, nq, na, offset, norm): pauli_label = 'Z'+'I'*(2*nq+2+na) observable = SparsePauliOp(Pauli(pauli_label)) ######################################################################################## estimator = StatevectorEstimator() # === Run Estimator (no parameters needed) === pub = (qc, observable) job = estimator.run([pub]) result = job.result()[0] z_exp = result.data.evs.item() ######################################################################################### # === Compute projector expectation === pi_expect = (1 - z_exp) / 2 Absolute_value = norm*np.sqrt(pi_expect)-offset return Absolute_value def get_relative_sign(qc, qcdiff, nq, na): pauli_label = 'Z'+'I'*(2*nq+2+na) observable = SparsePauliOp(Pauli(pauli_label)) ######################################################################################## estimator = StatevectorEstimator() # === Run Estimator === pub = (qc, observable) job = estimator.run([pub]) result = job.result()[0] z_exp = result.data.evs.item() pub_diff = (qcdiff, observable) job_diff = estimator.run([pub_diff]) result_diff = job_diff.result()[0] z_exp_diff = result_diff.data.evs.item() ######################################################################################### # === Compute projector expectation === pi_expect_sum = (1 - z_exp) / 2 pi_expect_diff = (1 - z_exp_diff) / 2 relative_sign = 'same' if pi_expect_sum >= pi_expect_diff else 'different' return relative_sign def Eref_value(nx, nq, R, dt, na, steps, xref, yref, field_ref = 'Ez'): if steps < 31: offset = 1 else : offset = 0.15 deltastate = np.zeros(4*nx*nx) # deltastate[nx*nx//2+nx//2:nx*nx//2+nx//2+1] = 1 deltastate[nx*yref+xref] = 1 deltastate[0:nx*nx] = deltastate[0:nx*nx] + offset norm1 = np.linalg.norm(deltastate) initial_state = deltastate/norm1 dp = 2 * R * np.pi / 2**na p = np.arange(- R * np.pi, R * np.pi, step=dp) fp = np.exp(-np.abs(p)) norm2 = np.linalg.norm(fp) norm = norm1 * norm2 qc = circ_for_magnitude(field_ref, xref, yref, nx, na, R, dt, initial_state, steps) Ezref = get_absolute_field_value(qc, nq, na, offset, norm) return Ezref def transpile_circ(circ, basis_gates=None): """ Transpile the circuit to the specified basis gates. """ if basis_gates is None: basis_gates = ['z', 'y', 'x', 'sdg', 's', 'h', 'rz', 'ry', 'rx', 'ecr', 'cz', 'cx'] transpiled_circ = transpile(circ, basis_gates=basis_gates) return transpiled_circ def compute_fidelity(circ1, circ2): circ_1 = tensornetwork_from_circuit(transpile_circ(circ1), simulator_settings) circ_2 = tensornetwork_from_circuit(transpile_circ(circ2), simulator_settings) fidelity = abs(compute_overlap(circ_1, circ_2))**2 return fidelity # def create_impulse_state(grid_dims, impulse_pos): # """ # Creates an initial state vector with a single delta impulse at a specified grid position. # The 2D grid is flattened into a 1D vector in row-major order, and this # vector is then padded to match the full simulation state space size (4x). # Args: # grid_dims (tuple): A tuple (width, height) defining the simulation grid dimensions. # For your original code, this would be (nx, nx). # impulse_pos (tuple): A tuple (x, y) for the position of the impulse. # Coordinates are 0-indexed. # Returns: # numpy.ndarray: The full, padded initial state vector with a single 1. # Raises: # ValueError: If the impulse position is outside the grid dimensions. # """ # grid_width, grid_height = grid_dims # impulse_x, impulse_y = impulse_pos # # --- Input Validation --- # # Ensure the requested impulse position is actually on the grid. # if not (0 <= impulse_x < grid_width and 0 <= impulse_y < grid_height): # raise ValueError(f"Impulse position ({impulse_x}, {impulse_y}) is outside the " # f"grid dimensions ({grid_width}x{grid_height}).") # # --- 1. Calculate the 1D Array Index --- # # Convert the (x, y) coordinate to a single index in a flattened 1D array. # # The formula for row-major order is: index = y_coord * width + x_coord # flat_index = impulse_y * grid_width + impulse_x # # --- 2. Create the Full, Padded State Vector --- # grid_size = grid_width * grid_height # total_size = 4 * grid_size # The simulation space is 4x the grid size. # initial_state = np.zeros(total_size) # # --- 3. Set the Delta Impulse --- # initial_state[flat_index] = 1 # return initial_state