diff --git a/hamiltonian-simulation/qiskit/ham_class.py b/hamiltonian-simulation/qiskit/ham_class.py deleted file mode 100644 index 79b2a8e3..00000000 --- a/hamiltonian-simulation/qiskit/ham_class.py +++ /dev/null @@ -1,656 +0,0 @@ -''' -Hamiltonian Simulation Benchmark Program - Qiskit Kernel -(C) Quantum Economic Development Consortium (QED-C) 2024. -''' - -''' -There are multiple Hamiltonians and three methods defined for this kernel. -Hamiltonians are applied via a base class HamiltonianKernel and derived classes for specific hamiltonians. -The Hamiltonian name is specified in the "hamiltonian" argument. -The "method" argument indicates the type of fidelity comparison that will be done. -In this case, method 3 is used to create a mirror circuit for scalability. -''' -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister -import numpy as np -import math - -pi = math.pi - -# Gates to be saved for printing purpose. -XX_ = None -YY_ = None -ZZ_ = None -XXYYZZ_ = None -XX_mirror_ = None -YY_mirror_ = None -ZZ_mirror_ = None -XXYYZZ_mirror_ = None -XXYYZZ_quasi_mirror_ = None - - -# For validating the implementation of XXYYZZ operation (saved for possible use in drawing) -_use_XX_YY_ZZ_gates = False - -## use initial state in the abstract class -class HamiltonianKernel(object): - def __init__(self, n_spins, K, t, hamiltonian, w, hx, hz, use_XX_YY_ZZ_gates, method, random_pauli_flag, init_state): - self.n_spins = n_spins - self.K = K - self.t = t - self.tau = t / K - self.hamiltonian = hamiltonian - self.w = w - self.h_x = hx - self.h_z = hz - self.use_XX_YY_ZZ_gates = use_XX_YY_ZZ_gates - self.random_pauli_flag = random_pauli_flag - self.method = method - self.init_state = init_state - - self.QCI_ = None # Initial Circuit - self.QC_ = None # Total Circuit - self.QCH_ = None # Hamiltonian - self.QC2D_ = None # Mirror Circuit - self.QCRS_ = None # Resultant Pauli - - self.qr = QuantumRegister(n_spins) - self.cr = ClassicalRegister(n_spins) - self.qc = QuantumCircuit(self.qr, self.cr, name = hamiltonian) - - def overall_circuit(self): - i_state = self.initial_state() #create initial state - self.qc.append(i_state, self.qr) #append the initial state to the quantum circuit - circuit = self.create_hamiltonian() #create the hamiltonian circuit - self.qc.append(circuit, self.qr) #append the hamiltoniain to the quantum circuit - - if self.method == 3: - #checks if random pauli flag is true to apply quasi inverse. - if self.random_pauli_flag: - quasi_inverse_circuit = self.create_quasi_inverse_hamiltonian() - self.qc.append(quasi_inverse_circuit, self.qr) - else: - #applies regular inverse. - inverse_circuit = self.create_inverse_hamiltonian() - self.qc.append(inverse_circuit, self.qr) - - # Measure all qubits - for i_qubit in range(self.n_spins): - self.qc.measure(self.qr[i_qubit], self.cr[i_qubit]) - - #Save smaller circuit example for display - if self.QC_ is None or self.n_spins <= 6: - if self.n_spins < 9: - self.QC_ = self.qc - - # Collapse the sub-circuits used in this benchmark (for Qiskit) - qc2 = self.qc.decompose().decompose() - return qc2 - - #apply initial state to the quantum circuit - def initial_state(self) -> QuantumCircuit: - #Initialize the quantum state. - qr = QuantumRegister(self.n_spins) - qc = QuantumCircuit(qr, name = "InitialState") - if self.init_state == "checkerboard" or self.init_state == "neele": - # Checkerboard state, or "Neele" state - for k in range(0, self.n_spins, 2): - qc.x([k]) - elif self.init_state == "ghz": - # GHZ state: 1/sqrt(2) (|00...> + |11...>) - qc.h(0) - for k in range(1, self.n_spins): - qc.cx(k-1, k) - self.QCI_ = qc - return qc - - def create_hamiltonian(self) -> QuantumCircuit: - pass - - def create_inverse_hamiltonian(self) -> QuantumCircuit: - pass - - def create_quasi_inverse_hamiltonian(self) -> QuantumCircuit: - pass - - ### List of random paulis to apply if method == 3. - def random_paulis_list(self): - """Create a list of random paulis to apply to mirror circuit.""" - pauli_tracker_list = [] - for i in range(self.n_spins): - gate = np.random.choice(["x","z"]) - if gate == "x": - pauli_tracker_list.append("x") - if gate == "z": - pauli_tracker_list.append("z") - return pauli_tracker_list - - #### Resultant Pauli after applying quasi inverse Hamiltonain. - def ResultantPauli(self)-> QuantumCircuit: - """Create a quantum oracle that is the result of applying quasi inverse Hamiltonain and random Pauli to Hamiltonian.""" - qr = QuantumRegister(self.n_spins) - qc = QuantumCircuit(qr, name = "ResultantPaulis") - for n in range(self.n_spins): - qc.x(n) # You can apply any Pauli, but you must also change the state you are comparing with. - - qc.barrier() - self.QCRS_ = qc - return qc - - #### Draw the circuits of this benchmark program - def kernel_draw(self): - # Print a sample circuit - print("Sample Circuit:") - print(self.QC_ if self.QC_ is not None else " ... too large!") - - print("Hamiltonian:") - print(self.QCH_ if self.QCH_ is not None else " ... too large!") - - # we don't restrict save of large sub-circuits, so skip printing if num_qubits too large - if self.QCI_ is not None and self.n_spins > 6: - print("... subcircuits too large to print") - - print(" Initial State:") - if self.QCI_ is not None: print(self.QCI_) - - print(f" Hamiltonian ({self.QCH_.name if self.QCH_ is not None else '?'}):") - if self.QCH_ is not None: print(self.QCH_) - - if self.QC2D_ is not None: - print("Quasi-Hamiltonian:") - print(self.QC2D_) - - if self.QCRS_ is not None: - print(" Resultant Paulis:") - print(self.QCRS_) - -######################## -####*****************### -######################## - -# Derived class for Heisenberg. -class HeisenbergHamiltonianKernel(HamiltonianKernel): - - #apply Heisenberg hamiltonian. - def create_hamiltonian(self) -> QuantumCircuit: - qr = QuantumRegister(self.n_spins) - qc = QuantumCircuit(qr, name="Heisenberg") - for k in range(self.K): - [qc.rx(2 * self.tau * self.w * self.h_x[i], qr[i]) for i in range(self.n_spins)] - [qc.rz(2 * self.tau * self.w * self.h_z[i], qr[i]) for i in range(self.n_spins)] - qc.barrier() - - if self.use_XX_YY_ZZ_gates: - for j in range(2): - for i in range(j % 2, self.n_spins - 1, 2): - qc.append(xx_gate(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - for j in range(2): - for i in range(j % 2, self.n_spins - 1, 2): - qc.append(yy_gate(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - for j in range(2): - for i in range(j % 2, self.n_spins - 1, 2): - qc.append(zz_gate(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - else: - for j in range(2): - for i in range(j % 2, self.n_spins - 1, 2): - qc.append(xxyyzz_opt_gate(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - qc.barrier() - - self.QCH_ = qc - return qc - - #apply inverse of the hamiltonian to simulate negative time evolution. - def create_inverse_hamiltonian(self) -> QuantumCircuit: - qr = QuantumRegister(self.n_spins) - qc = QuantumCircuit(qr, name="InverseHeisenberg") - for k in range(self.K): - if self.use_XX_YY_ZZ_gates: - for j in range(2): - for i in range(j % 2, self.n_spins - 1, 2): - qc.append(zz_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - for j in reversed(range(2)): - for i in reversed(range(j % 2, self.n_spins - 1, 2)): - qc.append(yy_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - for j in range(2): - for i in range(j % 2, self.n_spins - 1, 2): - qc.append(xx_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - else: - for j in reversed(range(2)): - for i in reversed(range(j % 2, self.n_spins - 1, 2)): - qc.append(xxyyzz_opt_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - qc.barrier() - [qc.rz(-2 * self.tau * self.w * self.h_z[i], qr[i]) for i in range(self.n_spins)] - [qc.rx(-2 * self.tau * self.w * self.h_x[i], qr[i]) for i in range(self.n_spins)] - qc.barrier() - QC2D_ = qc - return qc - - #create quasi inverse hamiltonian to simulate negative time evolution with randomized paulis applied. - def create_quasi_inverse_hamiltonian(self) -> QuantumCircuit: - qr = QuantumRegister(self.n_spins) - qc = QuantumCircuit(qr, name = "QuasiInverseHeisenberg") - - # Apply random paulis - pauli_list = self.random_paulis_list() - - for i, gate in enumerate(pauli_list): - if gate == "x": - qc.x(qr[i]) - else: - qc.z(qr[i]) - - qc.barrier() - - self.QCRS_ = res_pauli = self.ResultantPauli() # create a resultant pauli that we want to apply to initial state. - - for k in range(self.K): - # Basic implementation of exp(-i * t * (XX + YY + ZZ)): - if self.use_XX_YY_ZZ_gates: - # regular inverse of XX + YY + ZZ operators on each pair of quibts in linear chain - # XX operator on each pair of qubits in linear chain - for j in range(2): - for i in range(j%2, self.n_spins - 1, 2): - qc.append(zz_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - - # YY operator on each pair of qubits in linear chain - for j in range(2): - for i in range(j%2, self.n_spins - 1, 2): - qc.append(yy_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - - # ZZ operation on each pair of qubits in linear chain - for j in range(2): - for i in range(j%2, self.n_spins - 1, 2): - qc.append(xx_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - - else: - # optimized Inverse of XX + YY + ZZ operator on each pair of qubits in linear chain - for j in reversed(range(2)): - - #Keep a track of what pauli is applied at the first part of mirror circuit. - if j == 0 and k == 0: - if self.n_spins % 2 == 1: - if pauli_list[0] == "x": - qc.x(qr[0]) - if pauli_list[0] == "z": - qc.z(qr[0]) - ###applying a little twirl to prevent compiler from creating identity. - if self.n_spins % 2 == 0: - if pauli_list[0] == "x": - qc.x(qr[0]) - if pauli_list[0] == "z": - qc.z(qr[0]) - if pauli_list[self.n_spins-1] == "x": - qc.x(qr[self.n_spins-1]) - if pauli_list[self.n_spins-1] == "z": - qc.z(qr[self.n_spins-1]) - - for i in reversed(range(j % 2, self.n_spins - 1, 2)): - if k == 0 and j == 1: - gate_i = pauli_list[i] - gate_next = pauli_list[(i + 1) % self.n_spins] - qc.append(xxyyzz_opt_gate_quasi_mirror(self.tau, gate_i, gate_next).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - - else: - qc.append(xxyyzz_opt_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - - qc.barrier() - - # the Pauli spin vector product - [qc.rz(-2 * self.tau * self.w * self.h_z[i], qr[i]) for i in range(self.n_spins)] - [qc.rx(-2 * self.tau * self.w * self.h_x[i], qr[i]) for i in range(self.n_spins)] - qc.barrier() - - qc.append(self.QCRS_,qr) - self.QC2D_ = qc - return qc - - #apply extra circuit printing specific to Heisenberg. - def kernel_draw(self): - super().kernel_draw() - if self.use_XX_YY_ZZ_gates: - print("\nXX, YY, ZZ = ") - print(XX_) - print(YY_) - print(ZZ_) - if self.method == 3: - print("\nXX, YY, ZZ \u2020 = ") - print(XX_mirror_) - print(YY_mirror_) - print(ZZ_mirror_) - else: - print("\nXXYYZZ = ") - print(XXYYZZ_) - if self.method == 3: - print("\nXXYYZZ\u2020 = ") - print(XXYYZZ_mirror_) - if self.random_pauli_flag: - print("Qusai Inverse XXYYZZ:") - print(XXYYZZ_quasi_mirror_) - - -######################## -####*****************### -######################## - -#Derived Class for TFIM. -class TfimHamiltonianKernel(HamiltonianKernel): - - #apply tfim hamiltonian. - def create_hamiltonian(self) -> QuantumCircuit: - self.h = 0.2 - qr = QuantumRegister(self.n_spins) - qc = QuantumCircuit(qr, name="TFIM") - for k in range(self.K): - for i in range(self.n_spins): - qc.rx(2 * self.tau * self.h, qr[i]) - qc.barrier() - for j in range(2): - for i in range(j % 2, self.n_spins - 1, 2): - qc.append(zz_gate(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - qc.barrier() - self.QCH_ = qc - return qc - - #apply inverse of the hamiltonian to simulate negative time evolution. - def create_inverse_hamiltonian(self) -> QuantumCircuit: - qr = QuantumRegister(self.n_spins) - qc = QuantumCircuit(qr, name="InverseTFIM") - for k in range(self.K): - for j in range(2): - for i in range(j % 2, self.n_spins - 1, 2): - qc.append(zz_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - qc.barrier() - for i in range(self.n_spins): - qc.rx(-2 * self.tau * self.h, qr[i]) - qc.barrier() - self.QC2D_ = qc - return qc - - #create quasi inverse hamiltonian to simulate negative time evolution with randomized paulis applied. - def create_quasi_inverse_hamiltonian(self) -> QuantumCircuit: - qr = QuantumRegister(self.n_spins) - qc = QuantumCircuit(qr, name="QuasiInverseTFIM") - for k in range(self.K): - for j in range(2): - for i in range(j % 2, self.n_spins - 1, 2): - qc.append(zz_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) - qc.barrier() - for i in range(self.n_spins): - qc.rx(-2 * self.tau * self.h, qr[i]) - qc.barrier() - self.QC2D_ = qc - return qc - - #apply extra circuit printing specific to tfim. - def kernel_draw(self): - super().kernel_draw() - print("\nZZ = ") - print(ZZ_) - if self.method == 3: - print("\nZZ\u2020 = ") - print(ZZ_mirror_) - - -######################## -####*****************### -######################## - -############### XX, YY, ZZ Gate Implementations - -def xx_gate(tau: float) -> QuantumCircuit: - """ - Simple XX gate on q0 and q1 with angle 'tau'. - - Args: - tau (float): The rotation angle. - - Returns: - QuantumCircuit: The XX gate circuit. - """ - qr = QuantumRegister(2) - qc = QuantumCircuit(qr, name="XX") - qc.h(qr[0]) - qc.h(qr[1]) - qc.cx(qr[0], qr[1]) - qc.rz(pi * tau, qr[1]) - qc.cx(qr[0], qr[1]) - qc.h(qr[0]) - qc.h(qr[1]) - - global XX_ - XX_ = qc - - return qc - -def yy_gate(tau: float) -> QuantumCircuit: - """ - Simple YY gate on q0 and q1 with angle 'tau'. - - Args: - tau (float): The rotation angle. - - Returns: - QuantumCircuit: The YY gate circuit. - """ - qr = QuantumRegister(2) - qc = QuantumCircuit(qr, name="YY") - qc.s(qr[0]) - qc.s(qr[1]) - qc.h(qr[0]) - qc.h(qr[1]) - qc.cx(qr[0], qr[1]) - qc.rz(pi * tau, qr[1]) - qc.cx(qr[0], qr[1]) - qc.h(qr[0]) - qc.h(qr[1]) - qc.sdg(qr[0]) - qc.sdg(qr[1]) - - global YY_ - YY_ = qc - - return qc - -def zz_gate(tau: float) -> QuantumCircuit: - """ - Simple ZZ gate on q0 and q1 with angle 'tau'. - - Args: - tau (float): The rotation angle. - - Returns: - QuantumCircuit: The ZZ gate circuit. - """ - qr = QuantumRegister(2) - qc = QuantumCircuit(qr, name="ZZ") - qc.cx(qr[0], qr[1]) - qc.rz(pi * tau, qr[1]) - qc.cx(qr[0], qr[1]) - - global ZZ_ - ZZ_ = qc - - return qc - -def xxyyzz_opt_gate(tau: float) -> QuantumCircuit: - """ - Optimal combined XXYYZZ gate (with double coupling) on q0 and q1 with angle 'tau'. - - Args: - tau (float): The rotation angle. - - Returns: - QuantumCircuit: The optimal combined XXYYZZ gate circuit. - """ - alpha = tau - beta = tau - gamma = tau - qr = QuantumRegister(2) - qc = QuantumCircuit(qr, name="XXYYZZ") - qc.rz(pi / 2, qr[1]) - qc.cx(qr[1], qr[0]) - qc.rz(pi * gamma - pi / 2, qr[0]) - qc.ry(pi / 2 - pi * alpha, qr[1]) - qc.cx(qr[0], qr[1]) - qc.ry(pi * beta - pi / 2, qr[1]) - qc.cx(qr[1], qr[0]) - qc.rz(-pi / 2, qr[0]) - - global XXYYZZ_ - XXYYZZ_ = qc - - return qc - - -############### Mirrors of XX, YY, ZZ Gate Implementations -def xx_gate_mirror(tau: float) -> QuantumCircuit: - """ - Simple XX mirror gate on q0 and q1 with angle 'tau'. - - Args: - tau (float): The rotation angle. - - Returns: - QuantumCircuit: The XX_mirror_ gate circuit. - """ - qr = QuantumRegister(2, 'q') - qc = QuantumCircuit(qr, name="XX\u2020") - qc.h(qr[0]) - qc.h(qr[1]) - qc.cx(qr[0], qr[1]) - qc.rz(-pi * tau, qr[1]) - qc.cx(qr[0], qr[1]) - qc.h(qr[0]) - qc.h(qr[1]) - - global XX_mirror_ - XX_mirror_ = qc - - return qc - -def yy_gate_mirror(tau: float) -> QuantumCircuit: - """ - Simple YY mirror gate on q0 and q1 with angle 'tau'. - - Args: - tau (float): The rotation angle. - - Returns: - QuantumCircuit: The YY_mirror_ gate circuit. - """ - qr = QuantumRegister(2) - qc = QuantumCircuit(qr, name="YY\u2020") - qc.s(qr[0]) - qc.s(qr[1]) - qc.h(qr[0]) - qc.h(qr[1]) - qc.cx(qr[0], qr[1]) - qc.rz(-pi * tau, qr[1]) - qc.cx(qr[0], qr[1]) - qc.h(qr[0]) - qc.h(qr[1]) - qc.sdg(qr[0]) - qc.sdg(qr[1]) - - global YY_mirror_ - YY_mirror_ = qc - - return qc - -def zz_gate_mirror(tau: float) -> QuantumCircuit: - """ - Simple ZZ mirror gate on q0 and q1 with angle 'tau'. - - Args: - tau (float): The rotation angle. - - Returns: - QuantumCircuit: The ZZ_mirror_ gate circuit. - """ - qr = QuantumRegister(2) - qc = QuantumCircuit(qr, name="ZZ\u2020") - qc.cx(qr[0], qr[1]) - qc.rz(-pi * tau, qr[1]) - qc.cx(qr[0], qr[1]) - - global ZZ_mirror_ - ZZ_mirror_ = qc - - return qc - -def xxyyzz_opt_gate_mirror(tau: float) -> QuantumCircuit: - """ - Optimal combined XXYYZZ mirror gate (with double coupling) on q0 and q1 with angle 'tau'. - - Args: - tau (float): The rotation angle. - - Returns: - QuantumCircuit: The optimal combined XXYYZZ_mirror_ gate circuit. - """ - alpha = tau - beta = tau - gamma = tau - qr = QuantumRegister(2) - qc = QuantumCircuit(qr, name="XXYYZZ\u2020") - qc.rz(pi / 2, qr[0]) - qc.cx(qr[1], qr[0]) - qc.ry(-pi * beta + pi / 2, qr[1]) - qc.cx(qr[0], qr[1]) - qc.ry(-pi / 2 + pi * alpha, qr[1]) - qc.rz(-pi * gamma + pi / 2, qr[0]) - qc.cx(qr[1], qr[0]) - qc.rz(-pi / 2, qr[1]) - - global XXYYZZ_mirror_ - XXYYZZ_mirror_ = qc - - return qc - - -def xxyyzz_opt_gate_quasi_mirror(tau: float, pauli1: str, pauli2: str) -> QuantumCircuit: - """ - Optimal combined XXYYZZ quasi mirror gate (with double coupling) on q0 and q1 with angle 'tau'. - - Args: - tau (float): The rotation angle. - - Returns: - QuantumCircuit: The optimal combined XXYYZZ_mirror_ gate circuit. - """ - alpha = tau - beta = tau - gamma = tau - qr = QuantumRegister(2) - qc = QuantumCircuit(qr, name="XXYYZZ~Q") - - if pauli1 == "x": - qc.h(qr[0]) - qc.z(qr[0]) - qc.rx(pi / 2, qr[0]) #### X(Random Pauli) --- X --- Rz is equivalent to X ------ H - Z - H ----Rz - qc.h(qr[0]) #### which is equivalent to X ------ H - Z - Rx ----H - - if pauli1 == "z": - qc.h(qr[0]) - qc.x(qr[0]) - qc.rx(pi / 2, qr[0]) #### X(Random Pauli) --- Z --- Rz is equivalent to Z ------ H - X - H ----Rz - qc.h(qr[0]) #### #### which is equivalent to Z ------ H - X - Rx ----H - - if pauli2 == "x": - qc.x(qr[1]) - if pauli2 == "z": - qc.z(qr[1]) - - qc.cx(qr[1], qr[0]) - qc.ry(-pi * beta + pi / 2, qr[1]) - qc.cx(qr[0], qr[1]) - qc.ry(-pi / 2 + pi * alpha, qr[1]) - qc.rz(-pi * gamma + pi / 2, qr[0]) - qc.cx(qr[1], qr[0]) - qc.rz(-pi / 2, qr[1]) - - global XXYYZZ_quasi_mirror_ - XXYYZZ_quasi_mirror_ = qc - - return qc - - diff --git a/hamiltonian-simulation/qiskit/ham_sim.py b/hamiltonian-simulation/qiskit/ham_sim.py deleted file mode 100644 index facca813..00000000 --- a/hamiltonian-simulation/qiskit/ham_sim.py +++ /dev/null @@ -1,297 +0,0 @@ -''' -Hamiltonian Simulation Benchmark Program - Qiskit -(C) Quantum Economic Development Consortium (QED-C) 2024. -''' - -''' -This program benchmarks Hamiltonian simulation using Qiskit. -The central function is the `run()` method, which orchestrates the entire benchmarking process. - -HamiltonianSimulation forms the trotterized circuit used in the benchmark. - -HamiltonianSimulationExact runs a classical calculation that perfectly simulates hamiltonian evolution, although it does not scale well. -''' - -import json -import os -import sys -import time -from ham_class import HamiltonianKernel, HeisenbergHamiltonianKernel, TfimHamiltonianKernel -import numpy as np - -sys.path[1:1] = ["_common", "_common/qiskit"] -sys.path[1:1] = ["../../_common", "../../_common/qiskit"] -import execute as ex -import metrics as metrics - - -# Benchmark Name -benchmark_name = "Hamiltonian Simulation" - -np.random.seed(0) - -verbose = False - - -# Import precalculated data to compare against -filename = os.path.join(os.path.dirname(__file__), os.path.pardir, "_common", "precalculated_data.json") -with open(filename, 'r') as file: - data = file.read() -precalculated_data = json.loads(data) - -# Creates a key for distribution of initial state for method = 3. -def key_from_initial_state(num_qubits, num_shots, init_state, random_pauli_flag): - def generate_pattern(starting_bit): - pattern = ''.join([str((i + starting_bit) % 2) for i in range(num_qubits)]) - return pattern - - correct_dist = {} - - if init_state == "checkerboard": - if random_pauli_flag: - starting_bit = 0 if num_qubits % 2 != 0 else 1 - else: - starting_bit = 1 if num_qubits % 2 != 0 else 0 - correct_dist[generate_pattern(starting_bit)] = num_shots - elif init_state == "ghz": - correct_dist = { - '0' * num_qubits: num_shots/2, - '1' * num_qubits: num_shots/2 - } - - return correct_dist - - -############### Result Data Analysis - -#def analyze_and_print_result(qc: QuantumCircuit, result, num_qubits: int, -def analyze_and_print_result(qc, result, num_qubits: int, - type: str, num_shots: int, hamiltonian: str, method: int, random_pauli_flag: bool, init_state: str) -> tuple: - """ - Analyze and print the measured results. Compute the quality of the result based on operator expectation for each state. - - Args: - qc (QuantumCircuit): The quantum circuit. - result: The result from the execution. - num_qubits (int): Number of qubits. - type (str): Type of the simulation. - num_shots (int): Number of shots. - hamiltonian (str): Which hamiltonian to run. "heisenberg" by default but can also choose "TFIM". - method (int): Method for fidelity checking (1 for noiseless trotterized quantum, 2 for exact classical), 3 for mirror circuit. - - Returns: - tuple: Counts and fidelity. - """ - counts = result.get_counts(qc) - if verbose: - print(f"For type {type} measured: {counts}") - - hamiltonian = hamiltonian.strip().lower() - - # Precalculated correct distribution - if method == 1 and hamiltonian == "heisenberg": - correct_dist = precalculated_data[f"Heisenberg - Qubits{num_qubits}"] - elif method == 2 and hamiltonian == "heisenberg": - correct_dist = precalculated_data[f"Exact Heisenberg - Qubits{num_qubits}"] - elif method == 1 and hamiltonian == "tfim": - correct_dist = precalculated_data[f"TFIM - Qubits{num_qubits}"] - elif method == 2 and hamiltonian == "tfim": - correct_dist = precalculated_data[f"Exact TFIM - Qubits{num_qubits}"] - elif method == 3: - correct_dist = key_from_initial_state(num_qubits, num_shots, init_state, random_pauli_flag ) - else: - raise ValueError("Method is not 1 or 2 or 3, or hamiltonian is not tfim or heisenberg.") - - if verbose: - print(f"Correct dist: {correct_dist}") - - # Use polarization fidelity rescaling - fidelity = metrics.polarization_fidelity(counts, correct_dist) - return counts, fidelity - - -############### Benchmark Loop - -def run(min_qubits: int = 2, max_qubits: int = 8, max_circuits: int = 3, - skip_qubits: int = 1, num_shots: int = 100, - hamiltonian: str = "heisenberg", method: int = 1, - use_XX_YY_ZZ_gates: bool = False, random_pauli_flag: bool = True, init_state: str = "checkerboard", - backend_id: str = None, provider_backend = None, - hub: str = "ibm-q", group: str = "open", project: str = "main", exec_options = None, - context = None, api = None): - """ - Execute program with default parameters. - - Args: - min_qubits (int): Minimum number of qubits (smallest circuit is 2 qubits). - max_qubits (int): Maximum number of qubits. - max_circuits (int): Maximum number of circuits to execute per group. - skip_qubits (int): Increment of number of qubits. - num_shots (int): Number of shots for each circuit execution. - use_XX_YY_ZZ_gates (bool): Flag to use unoptimized XX, YY, ZZ gates. - backend_id (str): Backend identifier for execution. - provider_backend: Provider backend instance. - hub (str): IBM Quantum hub. - group (str): IBM Quantum group. - project (str): IBM Quantum project. - exec_options: Execution options. - - hamiltonian (str): Which hamiltonian to run. "heisenberg" by default but can also choose "TFIM". - method (int): Method for fidelity checking (1 for noiseless trotterized quantum, 2 for exact classical), 3 for mirror circuit. - context: Execution context. - - Returns: - None - """ - print(f"{benchmark_name} Benchmark Program - Qiskit") - - # Validate parameters (smallest circuit is 2 qubits) - max_qubits = max(2, max_qubits) - min_qubits = min(max(2, min_qubits), max_qubits) - if min_qubits % 2 == 1: min_qubits += 1 # min_qubits must be even - skip_qubits = max(1, skip_qubits) - - # Create context identifier - if context is None: context = f"{benchmark_name} Benchmark" - - # Set the flag to use an XX YY ZZ shim if given - if use_XX_YY_ZZ_gates: - print("... using unoptimized XX YY ZZ gates") - - # Initialize metrics module - metrics.init_metrics() - - # Define custom result handler - def execution_handler(qc, result, num_qubits, type, num_shots): - # Determine fidelity of result set - num_qubits = int(num_qubits) - counts, expectation_a = analyze_and_print_result(qc, result, num_qubits, type, num_shots, hamiltonian, method, random_pauli_flag, init_state) - metrics.store_metric(num_qubits, type, 'fidelity', expectation_a) - - # Initialize execution module using the execution result handler above and specified backend_id - ex.init_execution(execution_handler) - ex.set_execution_target(backend_id, provider_backend=provider_backend, - hub=hub, group=group, project=project, exec_options=exec_options, - context=context) - - # Execute Benchmark Program N times for multiple circuit sizes - # Accumulate metrics asynchronously as circuits complete - for num_qubits in range(min_qubits, max_qubits + 1, skip_qubits): - - # Reset random seed - np.random.seed(0) - - # Determine number of circuits to execute for this group - num_circuits = max(1, max_circuits) - - print(f"************\nExecuting [{num_circuits}] circuits with num_qubits = {num_qubits}") - - # Parameters of simulation - #### CANNOT BE MODIFIED W/O ALSO MODIFYING PRECALCULATED DATA ######### - w = precalculated_data['w'] # Strength of disorder - k = precalculated_data['k'] # Trotter error. - # A large Trotter order approximates the Hamiltonian evolution better. - # But a large Trotter order also means the circuit is deeper. - # For ideal or noise-less quantum circuits, k >> 1 gives perfect Hamiltonian simulation. - t = precalculated_data['t'] # Time of simulation - ####################################################################### - - # Loop over only 1 circuit - for circuit_id in range(num_circuits): - ts = time.time() - hx = precalculated_data['hx'][:num_qubits] # Precalculated random numbers between [-1, 1] - hz = precalculated_data['hz'][:num_qubits] - - # Create HeisenbergKernel or TFIM kernel - if hamiltonian == "heisenberg" : - qc_object = HeisenbergHamiltonianKernel(num_qubits, K=k, t=t, - hamiltonian=hamiltonian, - w=w, hx = hx, hz = hz, - use_XX_YY_ZZ_gates = use_XX_YY_ZZ_gates, - method = method, random_pauli_flag = random_pauli_flag, init_state = init_state) - - if hamiltonian == "tfim" : - qc_object = TfimHamiltonianKernel(num_qubits, K=k, t=t, - hamiltonian=hamiltonian, - w=w, hx = hx, hz = hz, - use_XX_YY_ZZ_gates = use_XX_YY_ZZ_gates, - method = method, random_pauli_flag = random_pauli_flag, init_state = init_state) - - qc = qc_object.overall_circuit() - - metrics.store_metric(num_qubits, circuit_id, 'create_time', time.time() - ts) - - # Submit circuit for execution on target (simulator, cloud simulator, or hardware) - ex.submit_circuit(qc, num_qubits, circuit_id, num_shots) - - # Wait for some active circuits to complete; report metrics when groups complete - ex.throttle_execution(metrics.finalize_group) - - # Wait for all active circuits to complete; report metrics when groups complete - ex.finalize_execution(metrics.finalize_group) - - ########## - - # draw a sample circuit - qc_object.kernel_draw() - - # Plot metrics for all circuit sizes - options = {"ham": hamiltonian, "method":method, "shots": num_shots, "reps": max_circuits} - if use_XX_YY_ZZ_gates: options.update({ "xyz": use_XX_YY_ZZ_gates }) - metrics.plot_metrics(f"Benchmark Results - {benchmark_name} - Qiskit", options=options) - - -####################### -# MAIN - -import argparse -def get_args(): - parser = argparse.ArgumentParser(description="Bernstei-Vazirani Benchmark") - #parser.add_argument("--api", "-a", default=None, help="Programming API", type=str) - #parser.add_argument("--target", "-t", default=None, help="Target Backend", type=str) - parser.add_argument("--backend_id", "-b", default=None, help="Backend Identifier", type=str) - parser.add_argument("--num_shots", "-s", default=100, help="Number of shots", type=int) - parser.add_argument("--num_qubits", "-n", default=0, help="Number of qubits (min = max = N)", type=int) - parser.add_argument("--min_qubits", "-min", default=3, help="Minimum number of qubits", type=int) - parser.add_argument("--max_qubits", "-max", default=8, help="Maximum number of qubits", type=int) - parser.add_argument("--skip_qubits", "-k", default=1, help="Number of qubits to skip", type=int) - parser.add_argument("--max_circuits", "-c", default=3, help="Maximum circuit repetitions", type=int) - parser.add_argument("--hamiltonian", "-ham", default="heisenberg", help="Name of Hamiltonian", type=str) - parser.add_argument("--method", "-m", default=1, help="Algorithm Method", type=int) - parser.add_argument("--use_XX_YY_ZZ_gates", action="store_true", help="Use explicit XX, YY, ZZ gates") - #parser.add_argument("--theta", default=0.0, help="Input Theta Value", type=float) - parser.add_argument("--nonoise", "-non", action="store_true", help="Use Noiseless Simulator") - parser.add_argument("--verbose", "-v", action="store_true", help="Verbose") - parser.add_argument("--random_pauli_flag", "-ranp", action="store_true", help="random pauli flag") - parser.add_argument("--init_state", "-init", default="checkerboard", help="initial state") - return parser.parse_args() - -# if main, execute method -if __name__ == '__main__': - args = get_args() - - # configure the QED-C Benchmark package for use with the given API - # (done here so we can set verbose for now) - #PhaseEstimation, kernel_draw = qedc_benchmarks_init(args.api) - - # special argument handling - ex.verbose = args.verbose - verbose = args.verbose - - if args.num_qubits > 0: args.min_qubits = args.max_qubits = args.num_qubits - - # execute benchmark program - run(min_qubits=args.min_qubits, max_qubits=args.max_qubits, - skip_qubits=args.skip_qubits, max_circuits=args.max_circuits, - num_shots=args.num_shots, - hamiltonian=args.hamiltonian, - method=args.method, - random_pauli_flag=args.random_pauli_flag, - use_XX_YY_ZZ_gates =args.use_XX_YY_ZZ_gates, - init_state = args.init_state, - #theta=args.theta, - backend_id=args.backend_id, - exec_options = {"noise_model" : None} if args.nonoise else {}, - #api=args.api - ) - diff --git a/hamiltonian-simulation/qiskit/hamiltonian_simulation_benchmark.py b/hamiltonian-simulation/qiskit/hamiltonian_simulation_benchmark.py index e916e1c5..c85f3f0b 100644 --- a/hamiltonian-simulation/qiskit/hamiltonian_simulation_benchmark.py +++ b/hamiltonian-simulation/qiskit/hamiltonian_simulation_benchmark.py @@ -16,16 +16,14 @@ import os import sys import time - import numpy as np sys.path[1:1] = ["_common", "_common/qiskit"] sys.path[1:1] = ["../../_common", "../../_common/qiskit"] + import execute as ex import metrics as metrics - -from hamiltonian_simulation_kernel import HamiltonianSimulation, kernel_draw - +from hamiltonian_simulation_kernel import HamiltonianKernel, HeisenbergHamiltonianKernel, TfimHamiltonianKernel # Benchmark Name benchmark_name = "Hamiltonian Simulation" @@ -130,11 +128,7 @@ def run(min_qubits: int = 2, max_qubits: int = 8, max_circuits: int = 3, max_circuits (int): Maximum number of circuits to execute per group. skip_qubits (int): Increment of number of qubits. num_shots (int): Number of shots for each circuit execution. - hamiltonian (str): Which hamiltonian to run. "heisenberg" by default but can also choose "TFIM". - method (int): Method for fidelity checking (1 for noiseless trotterized quantum, 2 for exact classical, 3 for mirror circuit.) use_XX_YY_ZZ_gates (bool): Flag to use unoptimized XX, YY, ZZ gates. - random_pauli_flag (bool): Flag to use a random set of paulis, in addition to a quasi-hamiltonian. - init_state (str): The desired initial state. Choices are "checkerboard" or "ghz". backend_id (str): Backend identifier for execution. provider_backend: Provider backend instance. hub (str): IBM Quantum hub. @@ -142,6 +136,8 @@ def run(min_qubits: int = 2, max_qubits: int = 8, max_circuits: int = 3, project (str): IBM Quantum project. exec_options: Execution options. + hamiltonian (str): Which hamiltonian to run. "heisenberg" by default but can also choose "TFIM". + method (int): Method for fidelity checking (1 for noiseless trotterized quantum, 2 for exact classical), 3 for mirror circuit. context: Execution context. Returns: @@ -206,19 +202,31 @@ def execution_handler(qc, result, num_qubits, type, num_shots): hx = precalculated_data['hx'][:num_qubits] # Precalculated random numbers between [-1, 1] hz = precalculated_data['hz'][:num_qubits] - qc = HamiltonianSimulation(num_qubits, K=k, t=t, - hamiltonian=hamiltonian, - w=w, hx = hx, hz = hz, - use_XX_YY_ZZ_gates = use_XX_YY_ZZ_gates, - method = method, random_pauli_flag = random_pauli_flag) + # Create HeisenbergKernel or TFIM kernel + if hamiltonian == "heisenberg" : + qc_object = HeisenbergHamiltonianKernel(num_qubits, K=k, t=t, + hamiltonian=hamiltonian, + w=w, hx = hx, hz = hz, + use_XX_YY_ZZ_gates = use_XX_YY_ZZ_gates, + method = method, random_pauli_flag = random_pauli_flag, init_state = init_state) + if hamiltonian == "tfim" : + qc_object = TfimHamiltonianKernel(num_qubits, K=k, t=t, + hamiltonian=hamiltonian, + w=w, hx = hx, hz = hz, + use_XX_YY_ZZ_gates = use_XX_YY_ZZ_gates, + method = method, random_pauli_flag = random_pauli_flag, init_state = init_state) + qc = qc_object.overall_circuit() metrics.store_metric(num_qubits, circuit_id, 'create_time', time.time() - ts) - qc.draw() # Submit circuit for execution on target (simulator, cloud simulator, or hardware) ex.submit_circuit(qc, num_qubits, circuit_id, num_shots) + + # draw a sample circuit + if circuit_id == 0: + qc_object.kernel_draw() # Wait for some active circuits to complete; report metrics when groups complete ex.throttle_execution(metrics.finalize_group) @@ -229,7 +237,7 @@ def execution_handler(qc, result, num_qubits, type, num_shots): ########## # draw a sample circuit - kernel_draw(hamiltonian, use_XX_YY_ZZ_gates, method, random_pauli_flag) + qc_object.kernel_draw() # Plot metrics for all circuit sizes options = {"ham": hamiltonian, "method":method, "shots": num_shots, "reps": max_circuits} @@ -260,6 +268,7 @@ def get_args(): parser.add_argument("--verbose", "-v", action="store_true", help="Verbose") parser.add_argument("--random_pauli_flag", "-ranp", action="store_true", help="random pauli flag") parser.add_argument("--init_state", "-init", default="checkerboard", help="initial state") + return parser.parse_args() # if main, execute method diff --git a/hamiltonian-simulation/qiskit/hamiltonian_simulation_kernel.py b/hamiltonian-simulation/qiskit/hamiltonian_simulation_kernel.py index 9383cf21..f1870430 100644 --- a/hamiltonian-simulation/qiskit/hamiltonian_simulation_kernel.py +++ b/hamiltonian-simulation/qiskit/hamiltonian_simulation_kernel.py @@ -5,408 +5,396 @@ ''' There are multiple Hamiltonians and three methods defined for this kernel. +Hamiltonians are applied via a base class HamiltonianKernel and derived classes for specific hamiltonians. The Hamiltonian name is specified in the "hamiltonian" argument. The "method" argument indicates the type of fidelity comparison that will be done. In this case, method 3 is used to create a mirror circuit for scalability. ''' - from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister import numpy as np import math -from typing import List pi = math.pi -# DEVNOTE: the global variables below will be converted to class instance variables later - -# Saved circuits and subcircuits for display -QC_ = None -QCI_ = None -QCR_ = None -QCRP_ = None -QCRS_ = None - -QC2_ = None +# Gates to be saved for printing purpose. XX_ = None YY_ = None ZZ_ = None XXYYZZ_ = None - -# Mirror Gates of the previous four gates -QC2D_ = None XX_mirror_ = None YY_mirror_ = None ZZ_mirror_ = None XXYYZZ_mirror_ = None -XXYYZZ_quasi_mirror = None +XXYYZZ_quasi_mirror_ = None + # For validating the implementation of XXYYZZ operation (saved for possible use in drawing) _use_XX_YY_ZZ_gates = False -############### Circuit Definition -def initial_state(n_spins: int, initial_state: str = "checker") -> QuantumCircuit: - """ - Initialize the quantum state. - - Args: - n_spins (int): Number of spins (qubits). - initial_state (str): The chosen initial state. By default applies the checkerboard state, but can also be set to "ghz", the GHZ state. - - Returns: - QuantumCircuit: The initialized quantum circuit. - """ - qc = QuantumCircuit(n_spins, name = "InitialState") - - if initial_state.strip().lower() == "checkerboard" or initial_state.strip().lower() == "neele": - # Checkerboard state, or "Neele" state - for k in range(0, n_spins, 2): - qc.x([k]) - elif initial_state.strip().lower() == "ghz": - # GHZ state: 1/sqrt(2) (|00...> + |11...>) - qc.h(0) - for k in range(1, n_spins): - qc.cx(k-1, k) - - return qc - -############## Heisenberg Circuit -def Heisenberg(n_spins: int, K: int, t: float, tau: float, w: float, h_x: List[float], h_z: List[float], - use_XX_YY_ZZ_gates: bool = False) -> QuantumCircuit: - qr = QuantumRegister(n_spins) - qc = QuantumCircuit(qr, name = "Heisenberg") - # Loop over each Trotter step, adding gates to the circuit defining the Hamiltonian - for k in range(K): - # Pauli spin vector product - [qc.rx(2 * tau * w * h_x[i], qr[i]) for i in range(n_spins)] - [qc.rz(2 * tau * w * h_z[i], qr[i]) for i in range(n_spins)] - qc.barrier() +## use initial state in the abstract class +class HamiltonianKernel(object): + def __init__(self, n_spins, K, t, hamiltonian, w, hx, hz, use_XX_YY_ZZ_gates, method, random_pauli_flag, init_state): + self.n_spins = n_spins + self.K = K + self.t = t + self.tau = t / K + self.hamiltonian = hamiltonian + self.w = w + self.h_x = hx + self.h_z = hz + self.use_XX_YY_ZZ_gates = use_XX_YY_ZZ_gates + self.random_pauli_flag = random_pauli_flag + self.method = method + self.init_state = init_state + + self.QCI_ = None # Initial Circuit + self.QC_ = None # Total Circuit + self.QCH_ = None # Hamiltonian + self.QC2D_ = None # Mirror Circuit + self.QCRS_ = None # Resultant Pauli + + self.qr = QuantumRegister(n_spins) + self.cr = ClassicalRegister(n_spins) + self.qc = QuantumCircuit(self.qr, self.cr, name = hamiltonian) + + def overall_circuit(self): + i_state = self.initial_state() #create initial state + self.qc.append(i_state, self.qr) #append the initial state to the quantum circuit + circuit = self.create_hamiltonian() #create the hamiltonian circuit + self.qc.append(circuit, self.qr) #append the hamiltoniain to the quantum circuit + + if self.method == 3: + #checks if random pauli flag is true to apply quasi inverse. + if self.random_pauli_flag: + quasi_inverse_circuit = self.create_quasi_inverse_hamiltonian() + self.qc.append(quasi_inverse_circuit, self.qr) + else: + #applies regular inverse. + inverse_circuit = self.create_inverse_hamiltonian() + self.qc.append(inverse_circuit, self.qr) - # Basic implementation of exp(i * t * (XX + YY + ZZ)) - if use_XX_YY_ZZ_gates: - # XX operator on each pair of qubits in linear chain - for j in range(2): - for i in range(j%2, n_spins - 1, 2): - qc.append(xx_gate(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - - # YY operator on each pair of qubits in linear chain - for j in range(2): - for i in range(j%2, n_spins - 1, 2): - qc.append(yy_gate(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - - # ZZ operation on each pair of qubits in linear chain - for j in range(2): - for i in range(j%2, n_spins - 1, 2): - qc.append(zz_gate(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - - else: - # Optimized XX + YY + ZZ operator on each pair of qubits in linear chain - for j in range(2): - for i in range(j % 2, n_spins - 1, 2): - qc.append(xxyyzz_opt_gate(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - qc.barrier() + # Measure all qubits + for i_qubit in range(self.n_spins): + self.qc.measure(self.qr[i_qubit], self.cr[i_qubit]) - return qc + #Save smaller circuit example for display + # if self.QC_ is None or self.n_spins <= 6: + # if self.n_spins < 9: + # self.QC_ = self.qc -########### TFIM hamiltonian circuit -def Tfim(n_spins: int, K: int, tau: float, use_XX_YY_ZZ_gates: bool)-> QuantumCircuit: - h = 1 # Strength of transverse field - qr = QuantumRegister(n_spins) - qc = QuantumCircuit(qr, name = "TFIM") - for k in range(K): - for i in range(n_spins): - qc.rx(2 * tau * h, qr[i]) - qc.barrier() - - for j in range(2): - for i in range(j % 2, n_spins - 1, 2): - qc.append(zz_gate(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - qc.barrier() - return qc - -############## Create a list of random paulis. -def random_paulis_list(n_spins): - """Create a list of random paulis to apply to mirror circuit.""" - pauli_tracker_list = [] - for i in range(n_spins): - gate = np.random.choice(["x","z"]) - if gate == "x": - pauli_tracker_list.append("x") - if gate == "z": - pauli_tracker_list.append("z") - return pauli_tracker_list - -############# Resultant Pauli after applying quasi inverse Hamiltonain and random Pauli to Hamiltonian. -def ResultantPauli(n_spins)-> QuantumCircuit: - """Create a quantum oracle that is the result of applying quasi inverse Hamiltonain and random Pauli to Hamiltonian.""" - qr = QuantumRegister(n_spins) - qc = QuantumCircuit(qr, name = "ResultantPaulis") - for n in range(n_spins): - qc.x(n) # You can apply any Pauli, but you must also change the state you are comparing with. + # Collapse the sub-circuits used in this benchmark (for Qiskit) + qc2 = self.qc.decompose().decompose() - qc.barrier() - - return qc -########## Quasi Hamiltonian for any Hamiltonian -########## ~H P H = R ==> ~H = R H' P' ; ~H is QuasiHamiltonian, P is Random Pauli, H is Hamiltonian, R is resultant circuit that appends on the initial state - -#########Quasi Inverse of Tfim hamiltonian -def QuasiInverseTfim(n_spins: int, K: int, tau: float, use_XX_YY_ZZ_gates: bool)-> QuantumCircuit: - h = 1 - qr = QuantumRegister(n_spins) - qc = QuantumCircuit(qr, name = "tfimInverse") - for k in range(K): - for j in range(2): - for i in range(j % 2, n_spins - 1, 2): - qc.append(zz_gate_mirror(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - qc.barrier() - for i in range(n_spins): - qc.rx(-2 * tau * h, qr[i]) + return qc2 + + #apply initial state to the quantum circuit + def initial_state(self) -> QuantumCircuit: + #Initialize the quantum state. + qr = QuantumRegister(self.n_spins) + qc = QuantumCircuit(qr, name = "InitialState") + if self.init_state == "checkerboard" or self.init_state == "neele": + # Checkerboard state, or "Neele" state + for k in range(0, self.n_spins, 2): + qc.x([k]) + elif self.init_state == "ghz": + # GHZ state: 1/sqrt(2) (|00...> + |11...>) + qc.h(0) + for k in range(1, self.n_spins): + qc.cx(k-1, k) + self.QCI_ = qc + return qc + + def create_hamiltonian(self) -> QuantumCircuit: + pass + + def create_inverse_hamiltonian(self) -> QuantumCircuit: + pass + + def create_quasi_inverse_hamiltonian(self) -> QuantumCircuit: + pass + + ### List of random paulis to apply if method == 3. + def random_paulis_list(self): + """Create a list of random paulis to apply to mirror circuit.""" + pauli_tracker_list = [] + for i in range(self.n_spins): + gate = np.random.choice(["x","z"]) + if gate == "x": + pauli_tracker_list.append("x") + if gate == "z": + pauli_tracker_list.append("z") + return pauli_tracker_list + + #### Resultant Pauli after applying quasi inverse Hamiltonain. + def ResultantPauli(self)-> QuantumCircuit: + """Create a quantum oracle that is the result of applying quasi inverse Hamiltonain and random Pauli to Hamiltonian.""" + qr = QuantumRegister(self.n_spins) + qc = QuantumCircuit(qr, name = "ResultantPaulis") + for n in range(self.n_spins): + qc.x(n) # You can apply any Pauli, but you must also change the state you are comparing with. + qc.barrier() - return qc - -########Quasi Inverse of Heisenberg hamiltonian -def QuasiInverseHeisenberg(n_spins: int, K: int, t: float, tau: float, w: float, h_x: List[float], h_z: List[float], - use_XX_YY_ZZ_gates: bool) -> QuantumCircuit: - qr = QuantumRegister(n_spins) - qc = QuantumCircuit(qr, name = "quasiheisenberg") - - # Apply random paulis - pauli_list = random_paulis_list(n_spins) - - for i, gate in enumerate(pauli_list): - if gate == "x": - qc.x(qr[i]) - else: - qc.z(qr[i]) - - qc.barrier() - - QCRS_ = res_pauli = ResultantPauli(n_spins) # create a resultant pauli that we want to apply to initial state. - - for k in range(K): - # Basic implementation of exp(-i * t * (XX + YY + ZZ)): - if use_XX_YY_ZZ_gates: - # regular inverse of XX + YY + ZZ operators on each pair of quibts in linear chain - # XX operator on each pair of qubits in linear chain - for j in range(2): - for i in range(j%2, n_spins - 1, 2): - qc.append(zz_gate_mirror(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - - # YY operator on each pair of qubits in linear chain - for j in range(2): - for i in range(j%2, n_spins - 1, 2): - qc.append(yy_gate_mirror(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - - # ZZ operation on each pair of qubits in linear chain - for j in range(2): - for i in range(j%2, n_spins - 1, 2): - qc.append(xx_gate_mirror(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - - else: - # optimized Inverse of XX + YY + ZZ operator on each pair of qubits in linear chain - for j in reversed(range(2)): + self.QCRS_ = qc + return qc + + #### Draw the circuits of this benchmark program + def kernel_draw(self): + if self.n_spins == 6: + # Print a sample circuit + print("Sample Circuit:") + print(self.QC_ if self.QC_ is not None else " ... too large!") + + # we don't restrict save of large sub-circuits, so skip printing if num_qubits too large + if self.QCI_ is not None and self.n_spins > 6: + print("... subcircuits too large to print") - #Keep a track of what pauli is applied at the first part of mirror circuit. - if j == 0 and k == 0: - if n_spins % 2 == 1: - if pauli_list[0] == "x": - qc.x(qr[0]) - if pauli_list[0] == "z": - qc.z(qr[0]) - ###applying a little twirl to prevent compiler from creating identity. - if n_spins % 2 == 0: - if pauli_list[0] == "x": - qc.x(qr[0]) - if pauli_list[0] == "z": - qc.z(qr[0]) - if pauli_list[n_spins-1] == "x": - qc.x(qr[n_spins-1]) - if pauli_list[n_spins-1] == "z": - qc.z(qr[n_spins-1]) - - for i in reversed(range(j % 2, n_spins - 1, 2)): - if k == 0 and j == 1: - gate_i = pauli_list[i] - gate_next = pauli_list[(i + 1) % n_spins] - qc.append(xxyyzz_opt_gate_quasi_mirror(tau, gate_i, gate_next).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - - else: - qc.append(xxyyzz_opt_gate_mirror(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - - qc.barrier() + print(" Initial State:") + if self.QCI_ is not None: print(self.QCI_) + + print(f" Hamiltonian ({self.QCH_.name if self.QCH_ is not None else '?'}):") + if self.QCH_ is not None: print(self.QCH_) + + if self.QC2D_ is not None: + print("Quasi-Hamiltonian:") + print(self.QC2D_) + + if self.QCRS_ is not None: + print(" Resultant Paulis:") + print(self.QCRS_) + +######################## +####*****************### +######################## + +# Derived class for Heisenberg. +class HeisenbergHamiltonianKernel(HamiltonianKernel): + + #apply Heisenberg hamiltonian. + def create_hamiltonian(self) -> QuantumCircuit: + qr = QuantumRegister(self.n_spins) + qc = QuantumCircuit(qr, name="Heisenberg") + for k in range(self.K): + [qc.rx(2 * self.tau * self.w * self.h_x[i], qr[i]) for i in range(self.n_spins)] + [qc.rz(2 * self.tau * self.w * self.h_z[i], qr[i]) for i in range(self.n_spins)] + qc.barrier() + + if self.use_XX_YY_ZZ_gates: + for j in range(2): + for i in range(j % 2, self.n_spins - 1, 2): + qc.append(xx_gate(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + for j in range(2): + for i in range(j % 2, self.n_spins - 1, 2): + qc.append(yy_gate(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + for j in range(2): + for i in range(j % 2, self.n_spins - 1, 2): + qc.append(zz_gate(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + else: + for j in range(2): + for i in range(j % 2, self.n_spins - 1, 2): + qc.append(xxyyzz_opt_gate(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + qc.barrier() + + self.QCH_ = qc + return qc + + #apply inverse of the hamiltonian to simulate negative time evolution. + def create_inverse_hamiltonian(self) -> QuantumCircuit: + qr = QuantumRegister(self.n_spins) + qc = QuantumCircuit(qr, name="InverseHeisenberg") + for k in range(self.K): + if self.use_XX_YY_ZZ_gates: + for j in range(2): + for i in range(j % 2, self.n_spins - 1, 2): + qc.append(zz_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + for j in reversed(range(2)): + for i in reversed(range(j % 2, self.n_spins - 1, 2)): + qc.append(yy_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + for j in range(2): + for i in range(j % 2, self.n_spins - 1, 2): + qc.append(xx_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + else: + for j in reversed(range(2)): + for i in reversed(range(j % 2, self.n_spins - 1, 2)): + qc.append(xxyyzz_opt_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + qc.barrier() + [qc.rz(-2 * self.tau * self.w * self.h_z[i], qr[i]) for i in range(self.n_spins)] + [qc.rx(-2 * self.tau * self.w * self.h_x[i], qr[i]) for i in range(self.n_spins)] + qc.barrier() + QC2D_ = qc + return qc + + #create quasi inverse hamiltonian to simulate negative time evolution with randomized paulis applied. + def create_quasi_inverse_hamiltonian(self) -> QuantumCircuit: + qr = QuantumRegister(self.n_spins) + qc = QuantumCircuit(qr, name = "QuasiInverseHeisenberg") + + # Apply random paulis + pauli_list = self.random_paulis_list() + + for i, gate in enumerate(pauli_list): + if gate == "x": + qc.x(qr[i]) + else: + qc.z(qr[i]) - # the Pauli spin vector product - [qc.rz(-2 * tau * w * h_z[i], qr[i]) for i in range(n_spins)] - [qc.rx(-2 * tau * w * h_x[i], qr[i]) for i in range(n_spins)] qc.barrier() - qc.append(QCRS_,qr) - - return qc + self.QCRS_ = res_pauli = self.ResultantPauli() # create a resultant pauli that we want to apply to initial state. + + for k in range(self.K): + # Basic implementation of exp(-i * t * (XX + YY + ZZ)): + if self.use_XX_YY_ZZ_gates: + # regular inverse of XX + YY + ZZ operators on each pair of quibts in linear chain + # XX operator on each pair of qubits in linear chain + for j in range(2): + for i in range(j%2, self.n_spins - 1, 2): + qc.append(zz_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + + # YY operator on each pair of qubits in linear chain + for j in range(2): + for i in range(j%2, self.n_spins - 1, 2): + qc.append(yy_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + + # ZZ operation on each pair of qubits in linear chain + for j in range(2): + for i in range(j%2, self.n_spins - 1, 2): + qc.append(xx_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) -#Inverse of Heisenberg model. mirror gates are applied. -def InverseHeisenberg(n_spins: int, K: int, t: float, tau: float, w: float, h_x: List[float], h_z: List[float], - use_XX_YY_ZZ_gates: bool = False) -> QuantumCircuit: - - qr = QuantumRegister(n_spins) - qc = QuantumCircuit(qr, name = "HeisenbergInverse") - # Add mirror gates for negative time simulation - for k in range(K): - # Basic implementation of exp(-i * t * (XX + YY + ZZ)): - if use_XX_YY_ZZ_gates: - # regular inverse of XX + YY + ZZ operators on each pair of quibts in linear chain - # XX operator on each pair of qubits in linear chain + else: + # optimized Inverse of XX + YY + ZZ operator on each pair of qubits in linear chain + for j in reversed(range(2)): + + #Keep a track of what pauli is applied at the first part of mirror circuit. + if j == 0 and k == 0: + if self.n_spins % 2 == 1: + if pauli_list[0] == "x": + qc.x(qr[0]) + if pauli_list[0] == "z": + qc.z(qr[0]) + ###applying a little twirl to prevent compiler from creating identity. + if self.n_spins % 2 == 0: + if pauli_list[0] == "x": + qc.x(qr[0]) + if pauli_list[0] == "z": + qc.z(qr[0]) + if pauli_list[self.n_spins-1] == "x": + qc.x(qr[self.n_spins-1]) + if pauli_list[self.n_spins-1] == "z": + qc.z(qr[self.n_spins-1]) + + for i in reversed(range(j % 2, self.n_spins - 1, 2)): + if k == 0 and j == 1: + gate_i = pauli_list[i] + gate_next = pauli_list[(i + 1) % self.n_spins] + qc.append(xxyyzz_opt_gate_quasi_mirror(self.tau, gate_i, gate_next).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + + else: + qc.append(xxyyzz_opt_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + + qc.barrier() + + # the Pauli spin vector product + [qc.rz(-2 * self.tau * self.w * self.h_z[i], qr[i]) for i in range(self.n_spins)] + [qc.rx(-2 * self.tau * self.w * self.h_x[i], qr[i]) for i in range(self.n_spins)] + qc.barrier() + + qc.append(self.QCRS_,qr) + self.QC2D_ = qc + return qc + + #apply extra circuit printing specific to Heisenberg. + def kernel_draw(self): + if self.n_spins == 6: + super().kernel_draw() + if self.use_XX_YY_ZZ_gates: + print("\nXX, YY, ZZ = ") + print(XX_) + print(YY_) + print(ZZ_) + if self.method == 3: + print("\nXX, YY, ZZ \u2020 = ") + print(XX_mirror_) + print(YY_mirror_) + print(ZZ_mirror_) + else: + print("\nXXYYZZ = ") + print(XXYYZZ_) + if self.method == 3: + print("\nXXYYZZ\u2020 = ") + print(XXYYZZ_mirror_) + if self.random_pauli_flag: + print("Qusai Inverse XXYYZZ:") + print(XXYYZZ_quasi_mirror_) + + +######################## +####*****************### +######################## + +#Derived Class for TFIM. +class TfimHamiltonianKernel(HamiltonianKernel): + + #apply tfim hamiltonian. + def create_hamiltonian(self) -> QuantumCircuit: + self.h = 0.2 + qr = QuantumRegister(self.n_spins) + qc = QuantumCircuit(qr, name="TFIM") + for k in range(self.K): + for i in range(self.n_spins): + qc.rx(2 * self.tau * self.h, qr[i]) + qc.barrier() for j in range(2): - for i in range(j%2, n_spins - 1, 2): - qc.append(zz_gate_mirror(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - - # YY operator on each pair of qubits in linear chain - for j in reversed(range(2)): - for i in reversed(range(j%2, n_spins - 1, 2)): - qc.append(yy_gate_mirror(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - - # ZZ operation on each pair of qubits in linear chain + for i in range(j % 2, self.n_spins - 1, 2): + qc.append(zz_gate(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + qc.barrier() + self.QCH_ = qc + return qc + + #apply inverse of the hamiltonian to simulate negative time evolution. + def create_inverse_hamiltonian(self) -> QuantumCircuit: + qr = QuantumRegister(self.n_spins) + qc = QuantumCircuit(qr, name="InverseTFIM") + for k in range(self.K): for j in range(2): - for i in range(j%2, n_spins - 1, 2): - qc.append(xx_gate_mirror(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - - else: - # optimized Inverse of XX + YY + ZZ operator on each pair of qubits in linear chain - for j in reversed(range(2)): - for i in reversed(range(j % 2, n_spins - 1, 2)): - qc.append(xxyyzz_opt_gate_mirror(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - qc.barrier() - - # the Pauli spin vector product - [qc.rz(-2 * tau * w * h_z[i], qr[i]) for i in range(n_spins)] - [qc.rx(-2 * tau * w * h_x[i], qr[i]) for i in range(n_spins)] - qc.barrier() - - return qc - -#########Inverse of tfim hamiltonian -def InverseTfim(n_spins: int, K: int, tau: float, use_XX_YY_ZZ_gates: bool)-> QuantumCircuit: - h = 1 - qr = QuantumRegister(n_spins) - qc = QuantumCircuit(qr, name = "tfimInverse") - for k in range(K): - for j in range(2): - for i in range(j % 2, n_spins - 1, 2): - qc.append(zz_gate_mirror(tau).to_instruction(), [qr[i], qr[(i + 1) % n_spins]]) - qc.barrier() - for i in range(n_spins): - qc.rx(-2 * tau * h, qr[i]) - qc.barrier() - return qc - - -def HamiltonianSimulation(n_spins: int, K: int, t: float, - hamiltonian: str, w: float, hx: List[float], hz: List[float], - use_XX_YY_ZZ_gates: bool = False, - method: int = 1, random_pauli_flag: bool = True) -> QuantumCircuit: - """ - Construct a Qiskit circuit for Hamiltonian simulation. - - Args: - n_spins (int): Number of spins (qubits). - K (int): The Trotterization order. - t (float): Duration of simulation. - hamiltonian (str): Which hamiltonian to run. "heisenberg" by default but can also choose "TFIM". - w (float): Strength of two-qubit interactions for heisenberg hamiltonian. - hx (list[float]): Strength of internal disorder parameter for heisenberg hamiltonian. - hz (list[float]): Strength of internal disorder parameter for heisenberg hamiltonian. - - Returns: - QuantumCircuit: The constructed Qiskit circuit. - """ - global QC_, QCI_, QCRP_, QCRS_, QC2_, QC2D_ - global _use_XX_YY_ZZ_gates - _use_XX_YY_ZZ_gates = use_XX_YY_ZZ_gates - - num_qubits = n_spins - secret_int = f"{K}-{t}" - - # Allocate qubits - qr = QuantumRegister(n_spins) - cr = ClassicalRegister(n_spins) - qc = QuantumCircuit(qr, cr, name=f"hamsim-{num_qubits}-{secret_int}") - tau = t / K - - h_x = hx[:n_spins] - h_z = hz[:n_spins] - - hamiltonian = hamiltonian.strip().lower() - - if hamiltonian == "heisenberg": + for i in range(j % 2, self.n_spins - 1, 2): + qc.append(zz_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + qc.barrier() + for i in range(self.n_spins): + qc.rx(-2 * self.tau * self.h, qr[i]) + qc.barrier() + self.QC2D_ = qc + return qc + + #create quasi inverse hamiltonian to simulate negative time evolution with randomized paulis applied. + def create_quasi_inverse_hamiltonian(self) -> QuantumCircuit: + qr = QuantumRegister(self.n_spins) + qc = QuantumCircuit(qr, name="QuasiInverseTFIM") + for k in range(self.K): + for j in range(2): + for i in range(j % 2, self.n_spins - 1, 2): + qc.append(zz_gate_mirror(self.tau).to_instruction(), [qr[i], qr[(i + 1) % self.n_spins]]) + qc.barrier() + for i in range(self.n_spins): + qc.rx(-2 * self.tau * self.h, qr[i]) + qc.barrier() + self.QC2D_ = qc + return qc - # append the initial state circuit to the quantum circuit - init_state = "checkerboard" - QCI_ = initial_state(n_spins, init_state) - qc.append(QCI_, qr) - qc.barrier() - - #append the Hamiltonian-specific circuit - QC2_ = heisenberg_circuit = Heisenberg(n_spins, K, t, tau, w, h_x, h_z, use_XX_YY_ZZ_gates) - qc.append(heisenberg_circuit, qr) - qc.barrier() - - if (method == 3): - if random_pauli_flag: - QC2D_ = quasi_heisenberg= QuasiInverseHeisenberg(n_spins, K, t, tau, w, h_x, h_z, use_XX_YY_ZZ_gates) - qc.append(quasi_heisenberg, qr) - qc.barrier() - - else: - #if random_pauli_flag is False, just use traditional mirror circuit, i.e. Apply Inverse of Hamiltonian to the Hamiltonian. - QC2D_ = inverse_heisenberg = InverseHeisenberg(n_spins, K, t, tau, w, h_x, h_z, use_XX_YY_ZZ_gates) - qc.append(inverse_heisenberg, qr) - qc.barrier() + #apply extra circuit printing specific to tfim. + def kernel_draw(self): + if self.n_spins == 6: + super().kernel_draw() + print("\nZZ = ") + print(ZZ_) + if self.method == 3: + print("\nZZ\u2020 = ") + print(ZZ_mirror_) - - elif hamiltonian == "tfim": - # append the initial state circuit to the quantum circuit - init_state = "ghz" - QCI_ = initial_state(n_spins, init_state) - qc.append(QCI_, qr) - qc.barrier() - - # append the Hamiltonian-specific circuit - QC2_ = tfim_circuit = Tfim(n_spins, K, tau, use_XX_YY_ZZ_gates) - qc.append(tfim_circuit, qr) - qc.barrier() - - if (method == 3): - if random_pauli_flag: - #if random_pauli_flag is True, use Quasi Inverse circuit. - QC2D_ = inverse_tfim = QuasiInverseTfim(n_spins, K, tau, use_XX_YY_ZZ_gates) - qc.append(inverse_tfim, qr) - qc.barrier() - - else: - #if random_pauli_flag is False, just use traditional mirror circuit, i.e. Apply Inverse of Hamiltonian to the Hamiltonian to give Inverse. - QC2D_ = inverse_tfim = InverseTfim(n_spins, K, tau, use_XX_YY_ZZ_gates) - qc.append(inverse_tfim, qr) - qc.barrier() - - else: - raise ValueError("Invalid Hamiltonian specification.") - - # Measure all qubits - for i_qubit in range(n_spins): - qc.measure(qr[i_qubit], cr[i_qubit]) - - #Save smaller circuit example for display - if QC_ is None or n_spins <= 6: - if n_spins < 9: - QC_ = qc - - # Collapse the sub-circuits used in this benchmark (for Qiskit) - qc2 = qc.decompose().decompose() - - return qc2 - +######################## +####*****************### +######################## ############### XX, YY, ZZ Gate Implementations @@ -667,60 +655,4 @@ def xxyyzz_opt_gate_quasi_mirror(tau: float, pauli1: str, pauli2: str) -> Quantu return qc -############### BV Circuit Drawer -# Draw the circuits of this benchmark program -def kernel_draw(hamiltonian: str = "heisenberg", use_XX_YY_ZZ_gates: bool = False, method: int = 1,random_pauli_flag: bool = True): - - # Print a sample circuit - print("Sample Circuit:") - print(QC_ if QC_ is not None else " ... too large!") - - # we don't restrict save of large sub-circuits, so skip printing if num_qubits too large - if QCI_ is not None and QCI_.num_qubits > 6: - print("... subcircuits too large to print") - return - - # print(" Initial State:") - # if QCI_ is not None: print(QCI_) - - print(f" Hamiltonian ({QC2_.name if QC2_ is not None else '?'}):") - if QC2_ is not None: print(QC2_) - - if QC2D_ is not None: - print("Quasi-Hamiltonian:") - print(QC2D_) - - if hamiltonian == "heisenberg": - if use_XX_YY_ZZ_gates: - print("\nXX, YY, ZZ = ") - print(XX_) - print(YY_) - print(ZZ_) - if method == 3: - print("\nXX, YY, ZZ \u2020 = ") - print(XX_mirror_) - print(YY_mirror_) - print(ZZ_mirror_) - else: - print("\nXXYYZZ = ") - print(XXYYZZ_) - if method == 3: - print("\nXXYYZZ\u2020 = ") - print(XXYYZZ_mirror_) - - if hamiltonian == "tfim": - print("\nZZ = ") - print(ZZ_) - if method == 3: - print("\nZZ\u2020 = ") - print(ZZ_mirror_) - - if QCRP_ is not None: - print(" Random Paulis:") - print(QCRP_) - - if QCRS_ is not None: - print(" Resultant Paulis:") - print(QCRS_) -