From c72f0603d6ce767c151bcc85d902ccca2f3102a3 Mon Sep 17 00:00:00 2001 From: d1ssk Date: Mon, 29 May 2023 22:23:11 +0900 Subject: [PATCH 1/2] add and modify methods. edit examples and tests --- examples/aer_sim.py | 55 +++-------- examples/ibm_device.py | 66 ++++--------- graphix_ibmq/runner.py | 180 +++++++++++++++++++++++++---------- tests/test_ibmq_interface.py | 9 +- 4 files changed, 166 insertions(+), 144 deletions(-) diff --git a/examples/aer_sim.py b/examples/aer_sim.py index 2e95c2e..021be7b 100644 --- a/examples/aer_sim.py +++ b/examples/aer_sim.py @@ -14,11 +14,10 @@ import random from graphix import Circuit from graphix_ibmq.runner import IBMQBackend -from qiskit import transpile from qiskit.tools.visualization import plot_histogram -from qiskit_aer import AerSimulator from qiskit_aer.noise import NoiseModel, depolarizing_error + def cp(circuit, theta, control, target): """Controlled phase gate, decomposed""" circuit.rz(control, theta / 2) @@ -34,28 +33,6 @@ def swap(circuit, a, b): circuit.cnot(b, a) circuit.cnot(a, b) -def format_result(pattern, result): - """Format the result so that only the result corresponding to the output qubit is taken out. - - Returns - ------- - masked_results : dict - Dictionary of formatted results. - """ - masked_results = {} - N_node = pattern.Nnode + len(pattern.results) - - # Iterate over original measurement results - for key, value in result.get_counts().items(): - masked_key = "" - for idx in pattern.output_nodes: - masked_key += key[N_node - idx - 1] - if masked_key in masked_results: - masked_results[masked_key] += value - else: - masked_results[masked_key] = value - - return masked_results #%% # Now let us define a circuit to apply QFT to three-qubit state. @@ -68,16 +45,16 @@ def format_result(pattern, result): # prepare random state for each input qubit for i in range(3): theta = random.uniform(0, np.pi) - phi = random.uniform(0, 2*np.pi) + phi = random.uniform(0, 2 * np.pi) circuit.ry(i, theta) circuit.rz(i, phi) - psi[i] = [np.cos(theta/2), np.sin(theta/2)*np.exp(1j*phi)] + psi[i] = [np.cos(theta / 2), np.sin(theta / 2) * np.exp(1j * phi)] # 8 dimension input statevector -input_state = [0]*8 -for i in range(8): +input_state = [0] * 8 +for i in range(8): i_str = f"{i:03b}" - input_state[i] = psi[0][int(i_str[0])]*psi[1][int(i_str[1])]*psi[2][int(i_str[2])] + input_state[i] = psi[0][int(i_str[0])] * psi[1][int(i_str[1])] * psi[2][int(i_str[2])] # QFT circuit.h(0) @@ -112,11 +89,8 @@ def format_result(pattern, result): #%% # We can now simulate the circuit with Aer. -simulator = AerSimulator() -circ_sim = transpile(backend.circ, simulator) - # run and get counts -result = format_result(pattern, simulator.run(circ_sim, shots=1024).result()) +result = backend.simulate() #%% # We can also simulate the circuit with noise model @@ -125,7 +99,7 @@ def format_result(pattern, result): noise_model = NoiseModel() # add depolarizing error to all single qubit u1, u2, u3 gates error = depolarizing_error(0.01, 1) -noise_model.add_all_qubit_quantum_error(error, ['u1', 'u2', 'u3']) +noise_model.add_all_qubit_quantum_error(error, ["u1", "u2", "u3"]) # print noise model info print(noise_model) @@ -133,28 +107,25 @@ def format_result(pattern, result): #%% # Now we can run the simulation with noise model -sim_noise = AerSimulator(noise_model=noise_model) -# transpile circuit for noisy basis gates -circ_noise = transpile(backend.circ, sim_noise) # run and get counts -result_noise = format_result(pattern, sim_noise.run(circ_noise).result()) +result_noise = backend.simulate(noise_model=noise_model) #%% # Now let us compare the results with theoretical output # calculate the theoretical output state -state = [0]*8 -omega = np.exp(1j*np.pi/4) +state = [0] * 8 +omega = np.exp(1j * np.pi / 4) for i in range(8): for j in range(8): - state[i] += input_state[j]*omega**(i*j)/2**1.5 + state[i] += input_state[j] * omega ** (i * j) / 2**1.5 # calculate the theoretical counts count_theory = {} for i in range(2**3): - count_theory[f"{i:03b}"] = 1024*np.abs(state[i])**2 + count_theory[f"{i:03b}"] = 1024 * np.abs(state[i]) ** 2 # plot and compare the results plot_histogram( diff --git a/examples/ibm_device.py b/examples/ibm_device.py index eb6b1f3..b41fca1 100644 --- a/examples/ibm_device.py +++ b/examples/ibm_device.py @@ -14,12 +14,11 @@ import random from graphix import Circuit from graphix_ibmq.runner import IBMQBackend -from qiskit import IBMQ, transpile -from qiskit_ibm_provider import IBMProvider +from qiskit import IBMQ from qiskit.tools.visualization import plot_histogram -from qiskit_aer import AerSimulator from qiskit.providers.fake_provider import FakeLagos + def cp(circuit, theta, control, target): """Controlled phase gate, decomposed""" circuit.rz(control, theta / 2) @@ -35,28 +34,6 @@ def swap(circuit, a, b): circuit.cnot(b, a) circuit.cnot(a, b) -def format_result(pattern, result): - """Format the result so that only the result corresponding to the output qubit is taken out. - - Returns - ------- - masked_results : dict - Dictionary of formatted results. - """ - masked_results = {} - N_node = pattern.Nnode + len(pattern.results) - - # Iterate over original measurement results - for key, value in result.get_counts().items(): - masked_key = "" - for idx in pattern.output_nodes: - masked_key += key[N_node - idx - 1] - if masked_key in masked_results: - masked_results[masked_key] += value - else: - masked_results[masked_key] = value - - return masked_results #%% # Now let us define a circuit to apply QFT to three-qubit state. @@ -69,16 +46,16 @@ def format_result(pattern, result): # prepare random state for each input qubit for i in range(3): theta = random.uniform(0, np.pi) - phi = random.uniform(0, 2*np.pi) + phi = random.uniform(0, 2 * np.pi) circuit.ry(i, theta) circuit.rz(i, phi) - psi[i] = [np.cos(theta/2), np.sin(theta/2)*np.exp(1j*phi)] + psi[i] = [np.cos(theta / 2), np.sin(theta / 2) * np.exp(1j * phi)] # 8 dimension input statevector -input_state = [0]*8 -for i in range(8): +input_state = [0] * 8 +for i in range(8): i_str = f"{i:03b}" - input_state[i] = psi[0][int(i_str[0])]*psi[1][int(i_str[1])]*psi[2][int(i_str[2])] + input_state[i] = psi[0][int(i_str[0])] * psi[1][int(i_str[1])] * psi[2][int(i_str[2])] # QFT circuit.h(0) @@ -113,60 +90,51 @@ def format_result(pattern, result): #%% # Get the API token and load the IBMQ acount. -IBMQ.save_account('MY_API_TOKEN', overwrite=True) +IBMQ.save_account("MY_API_TOKEN", overwrite=True) IBMQ.load_account() #%% # Get provider and the backend. -instance_name = 'ibm-q/open/main' +instance_name = "ibm-q/open/main" backend_name = "ibm_lagos" -provider = IBMProvider(instance=instance_name) -backend = provider.get_backend(backend_name) -print(f"Using backend {backend.name}") +backend.get_backend(instance=instance_name, resource=backend_name) #%% # We can now execute the circuit on the device backend. -circ_device = transpile(backend.circ, backend) -job = backend.run(circ_device, shots=1024, dynamic=True) -print(f"Your job's id: {job.job_id()}") -result = format_result(pattern, job.result()) +result = backend.run() #%% # Retrieve the job if needed -# job = provider.backend.retrieve_job("Job ID") -# result = format_result(pattern, job.result()) +# result = backend.retrieve_result("Job ID") #%% # We can simulate the circuit with noise model based on the device we used # get the noise model of the device backend backend_noisemodel = FakeLagos() -sim_noise = AerSimulator.from_backend(backend_noisemodel) -# transpile the circuit for the noisy basis gates -circ_noise = transpile(backend.circ, sim_noise) # execute noisy simulation and get counts -result_noise = format_result(pattern, sim_noise.run(circ_noise).result()) +result_noise = backend.simulate(noise_model=backend_noisemodel) #%% # Now let us compare the results with theoretical output # calculate the theoretical output state -state = [0]*8 -omega = np.exp(1j*np.pi/4) +state = [0] * 8 +omega = np.exp(1j * np.pi / 4) for i in range(8): for j in range(8): - state[i] += input_state[j]*omega**(i*j)/2**1.5 + state[i] += input_state[j] * omega ** (i * j) / 2**1.5 # calculate the theoretical counts count_theory = {} for i in range(2**3): - count_theory[f"{i:03b}"] = 1024*np.abs(state[i])**2 + count_theory[f"{i:03b}"] = 1024 * np.abs(state[i]) ** 2 # plot and compare the results plot_histogram( diff --git a/graphix_ibmq/runner.py b/graphix_ibmq/runner.py index 5e8aa5a..76bfd56 100644 --- a/graphix_ibmq/runner.py +++ b/graphix_ibmq/runner.py @@ -1,18 +1,33 @@ import numpy as np -from qiskit_ibm_provider import IBMProvider -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister -from qiskit import transpile -from qiskit.providers.ibmq import least_busy -from graphix.clifford import CLIFFORD_CONJ +from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile +from qiskit_ibm_provider import IBMProvider, least_busy +from qiskit_aer import AerSimulator +from qiskit_aer.noise import NoiseModel +from graphix.clifford import CLIFFORD_CONJ, CLIFFORD_TO_QASM3 class IBMQBackend: - """runs MBQC pattern with IBM quantum device.""" + """Interface for MBQC pattern execution on IBM quantum device. + + Attributes + ---------- + pattern: :class:`graphix.pattern.Pattern` object + MBQC pattern to be executed. + circ: :class:`qiskit.circuit.quantumcircuit.QuantumCircuit` object + qiskit circuit corresponding to the pattern. + job: :class:`qiskit_ibm_provider.job.ibm_circuit_job.IBMCircuitJob` object + job object of the execution. + instance : str + instance name of IBMQ provider. + resource : str + resource name of IBMQ provider. + """ def __init__(self, pattern): """ - Parameteres - ----------- + + Parameters + ---------- pattern: :class:`graphix.pattern.Pattern` object MBQC pattern to be executed. """ @@ -20,12 +35,17 @@ def __init__(self, pattern): def get_backend(self, instance="ibm-q/open/main", resource=None): """get the backend object - Parameteres - ----------- + + Parameters + ---------- instance : str instance name of IBMQ provider. resource : str resource name of IBMQ provider. + backend : :class:`qiskit_ibm_provider.ibm_backend.IBMBackend` object + IBMQ device backend + circ : :class:`qiskit.circuit.quantumcircuit.QuantumCircuit` object + qiskit circuit corresponding to the pattern. """ self.instance = instance self.provider = IBMProvider(instance=self.instance) @@ -45,11 +65,12 @@ def get_backend(self, instance="ibm-q/open/main", resource=None): def to_qiskit(self, save_statevector=False): """convert the MBQC pattern to the qiskit cuicuit and add to attributes. - Parameteres - ----------- + + Parameters + ---------- pattern : :class:`graphix.pattern.Pattern` object MBQC pattern to be converted to qiskit circuit. - save_statevector (False) : bool, optional + save_statevector : bool, optional whether to save the statevector before the measurements of output qubits. """ n = self.pattern.max_space() @@ -101,7 +122,7 @@ def to_qiskit(self, save_statevector=False): elif len(cmd) == 7: cid = cmd[6] - for op in CLIFFORD_TO_QISKIT[CLIFFORD_CONJ[cid]]: + for op in CLIFFORD_TO_QASM3[CLIFFORD_CONJ[cid]]: exec(f"circ.{op}({circ_ind})") if plane == "XY": @@ -138,7 +159,7 @@ def to_qiskit(self, save_statevector=False): if cmd[0] == "C": circ_ind = qubit_dict[cmd[1]] cid = cmd[2] - for op in CLIFFORD_TO_QISKIT[cid]: + for op in CLIFFORD_TO_QASM3[cid]: exec(f"circ.{op}({circ_ind})") if save_statevector: @@ -159,39 +180,102 @@ def to_qiskit(self, save_statevector=False): self.circ = circ - def transpile(self, optimization_level=1): + def transpile(self, backend=None, optimization_level=1): """transpile the circuit for the designated resource. - Parameteres - ----------- - optimization_level (1) : int, optional + + Parameters + ---------- + optimization_level : int, optional the optimization level of the transpilation. """ - self.circ = transpile(self.circ, backend=self.backend, optimization_level=optimization_level) - - -CLIFFORD_TO_QISKIT = [ - ["id"], - ["x"], - ["y"], - ["z"], - ["s"], - ["sdg"], - ["h"], - ["sdg", "h", "sdg"], - ["h", "x"], - ["sdg", "y"], - ["sdg", "x"], - ["h", "y"], - ["h", "z"], - ["sdg", "h", "sdg", "y"], - ["sdg", "h", "s"], - ["sdg", "h", "sdg", "x"], - ["sdg", "h"], - ["sdg", "h", "y"], - ["sdg", "h", "z"], - ["sdg", "h", "x"], - ["h", "s"], - ["h", "sdg"], - ["h", "x", "sdg"], - ["h", "x", "s"], -] + if backend is None: + backend = self.backend + self.circ = transpile(self.circ, backend=backend, optimization_level=optimization_level) + + def simulate(self, shots=1024, noise_model=None, format_result=True): + """simulate the circuit with Aer. + + Parameters + ---------- + shots : int, optional + the number of shots. + noise_model : :class:`qiskit_aer.backends.aer_simulator.AerSimulator` object, optional + noise model to be used in the simulation. + format_result : bool, optional + whether to format the result so that only the result corresponding to the output qubit is taken out. + Returns + ---------- + result : + the measurement result, + in the representation depending on the backend used. + """ + if noise_model is not None: + if type(noise_model) is NoiseModel: + simulator = AerSimulator(noise_model=noise_model) + else: + simulator = AerSimulator.from_backend(noise_model) + else: + simulator = AerSimulator() + circ_sim = transpile(self.circ, simulator) + result = simulator.run(circ_sim, shots=shots).result() + if format_result: + result = self.format_result(result) + + return result + + def run(self, shots=1024, format_result=True, optimization_level=1): + """Perform the execution. + + Returns + ------- + result : + the measurement result, + in the representation depending on the backend used. + """ + self.transpile(optimization_level=optimization_level) + self.job = self.backend.run(self.circ, shots=shots, dynamic=True) + print(f"Your job's id: {self.job.job_id()}") + result = self.job.result() + if format_result: + result = self.format_result(result) + + return result + + def format_result(self, result): + """Format the result so that only the result corresponding to the output qubit is taken out. + + Returns + ------- + masked_results : dict + Dictionary of formatted results. + """ + masked_results = {} + N_node = self.pattern.Nnode + len(self.pattern.results) + + # Iterate over original measurement results + for key, value in result.get_counts().items(): + masked_key = "" + for idx in self.pattern.output_nodes: + masked_key += key[N_node - idx - 1] + if masked_key in masked_results: + masked_results[masked_key] += value + else: + masked_results[masked_key] = value + + return masked_results + + def retrieve_result(self, job_id, format_result=True): + """Retrieve the execution result. + + Returns + ------- + result : + the measurement result, + in the representation depending on the backend used. + """ + self.job = self.provider.retrieve_job(job_id) + result = self.job.result() + if format_result: + result = self.format_result(result) + + return result diff --git a/tests/test_ibmq_interface.py b/tests/test_ibmq_interface.py index 849ffd7..2c0007d 100644 --- a/tests/test_ibmq_interface.py +++ b/tests/test_ibmq_interface.py @@ -1,6 +1,6 @@ import unittest import numpy as np -from qiskit import Aer, transpile +from qiskit_aer import AerSimulator from graphix_ibmq.runner import IBMQBackend import tests.random_circuit as rc @@ -27,11 +27,10 @@ def test_to_qiskit(self): state = pattern.simulate_pattern() ibmq_backend = IBMQBackend(pattern) - simulator = Aer.get_backend("aer_simulator") ibmq_backend.to_qiskit(save_statevector=True) - qiskit_circuit = transpile(ibmq_backend.circ, simulator) - sim_result = simulator.run(qiskit_circuit).result() - state_qiskit = sim_result.get_statevector(qiskit_circuit) + ibmq_backend.transpile(backend=AerSimulator()) + sim_result = ibmq_backend.simulate(format_result=False) + state_qiskit = sim_result.get_statevector(ibmq_backend.circ) state_qiskit_mod = modify_statevector(state_qiskit, ibmq_backend.circ_output) np.testing.assert_almost_equal(np.abs(np.dot(state_qiskit_mod.conjugate(), state.flatten())), 1) From e32ff2a47aab664f2bdb759ba6e0baab3c6dfc41 Mon Sep 17 00:00:00 2001 From: d1ssk Date: Mon, 29 May 2023 23:37:45 +0900 Subject: [PATCH 2/2] add comments --- graphix_ibmq/runner.py | 40 ++++++++++++++++++++++++------------ tests/test_ibmq_interface.py | 2 -- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/graphix_ibmq/runner.py b/graphix_ibmq/runner.py index 76bfd56..1efba1c 100644 --- a/graphix_ibmq/runner.py +++ b/graphix_ibmq/runner.py @@ -21,6 +21,8 @@ class IBMQBackend: instance name of IBMQ provider. resource : str resource name of IBMQ provider. + backend : :class:`qiskit_ibm_provider.ibm_backend.IBMBackend` object + IBMQ device backend """ def __init__(self, pattern): @@ -42,10 +44,6 @@ def get_backend(self, instance="ibm-q/open/main", resource=None): instance name of IBMQ provider. resource : str resource name of IBMQ provider. - backend : :class:`qiskit_ibm_provider.ibm_backend.IBMBackend` object - IBMQ device backend - circ : :class:`qiskit.circuit.quantumcircuit.QuantumCircuit` object - qiskit circuit corresponding to the pattern. """ self.instance = instance self.provider = IBMProvider(instance=self.instance) @@ -185,6 +183,8 @@ def transpile(self, backend=None, optimization_level=1): Parameters ---------- + backend : :class:`qiskit_ibm_provider.ibm_backend.IBMBackend` object, optional + backend to be used for transpilation. optimization_level : int, optional the optimization level of the transpilation. """ @@ -203,11 +203,11 @@ def simulate(self, shots=1024, noise_model=None, format_result=True): noise model to be used in the simulation. format_result : bool, optional whether to format the result so that only the result corresponding to the output qubit is taken out. + Returns ---------- - result : - the measurement result, - in the representation depending on the backend used. + result : dict + the measurement result. """ if noise_model is not None: if type(noise_model) is NoiseModel: @@ -226,11 +226,19 @@ def simulate(self, shots=1024, noise_model=None, format_result=True): def run(self, shots=1024, format_result=True, optimization_level=1): """Perform the execution. + Parameters + ---------- + shots : int, optional + the number of shots. + format_result : bool, optional + whether to format the result so that only the result corresponding to the output qubit is taken out. + optimization_level : int, optional + the optimization level of the transpilation. + Returns ------- - result : - the measurement result, - in the representation depending on the backend used. + result : dict + the measurement result. """ self.transpile(optimization_level=optimization_level) self.job = self.backend.run(self.circ, shots=shots, dynamic=True) @@ -267,11 +275,17 @@ def format_result(self, result): def retrieve_result(self, job_id, format_result=True): """Retrieve the execution result. + Parameters + ---------- + job_id : str + the id of the job. + format_result : bool, optional + whether to format the result so that only the result corresponding to the output qubit is taken out. + Returns ------- - result : - the measurement result, - in the representation depending on the backend used. + result : dict + the measurement result. """ self.job = self.provider.retrieve_job(job_id) result = self.job.result() diff --git a/tests/test_ibmq_interface.py b/tests/test_ibmq_interface.py index 2c0007d..6478376 100644 --- a/tests/test_ibmq_interface.py +++ b/tests/test_ibmq_interface.py @@ -1,6 +1,5 @@ import unittest import numpy as np -from qiskit_aer import AerSimulator from graphix_ibmq.runner import IBMQBackend import tests.random_circuit as rc @@ -28,7 +27,6 @@ def test_to_qiskit(self): ibmq_backend = IBMQBackend(pattern) ibmq_backend.to_qiskit(save_statevector=True) - ibmq_backend.transpile(backend=AerSimulator()) sim_result = ibmq_backend.simulate(format_result=False) state_qiskit = sim_result.get_statevector(ibmq_backend.circ) state_qiskit_mod = modify_statevector(state_qiskit, ibmq_backend.circ_output)