From 3c706e692d8da5f1a1b688a082b6082602f3cbd6 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 10 Feb 2023 16:31:17 +0800 Subject: [PATCH 01/42] Qibo circuit convertor --- src/qibotn/QiboCircuitConvertor.py | 107 +++++++++++++++++++++++++++++ src/qibotn/__main__.py | 59 ++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 src/qibotn/QiboCircuitConvertor.py create mode 100644 src/qibotn/__main__.py diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py new file mode 100644 index 00000000..39fb010c --- /dev/null +++ b/src/qibotn/QiboCircuitConvertor.py @@ -0,0 +1,107 @@ + +import cupy as cp +import numpy as np + +EINSUM_SYMBOLS_BASE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +class QiboCircuitToEinsum: + def __init__(self, circuit, dtype='complex128'): + + self.backend = cp + self.dtype = getattr(self.backend, dtype) + + self.input_tensor_counter = np. zeros((circuit.nqubits,)) + self.gates = [] + for gate in circuit.queue: + targets = list(gate.target_qubits) + for target in targets: + self.input_tensor_counter[target] = self.input_tensor_counter[target] + 1 + controls = list(gate.control_qubits) + for control in controls: + self.input_tensor_counter[control] = self.input_tensor_counter[control] + 1 + gate_qubits = controls + targets + self.gates.append((cp.asarray(gate.matrix).reshape((2,) * 2 * len(gate_qubits)), gate_qubits)) + + self.qubit_name = [indx for indx, value in enumerate(self.input_tensor_counter) if value > 0] + + def state_vector(self): + + input_tensor_count = np.count_nonzero(self.input_tensor_counter) + + input_operands = self._get_bitstring_tensors('0'*input_tensor_count, self.dtype, backend=self.backend) + + mode_labels, qubits_frontier, next_frontier = self._init_mode_labels_from_qubits(self.qubit_name) + + gate_mode_labels, gate_operands = self._parse_gates_to_mode_labels_operands(self.gates, + qubits_frontier, + next_frontier) + + operands = input_operands + gate_operands + mode_labels += gate_mode_labels + + expression = self._convert_mode_labels_to_expression(mode_labels, qubits_frontier) + + return expression, operands + + def _get_symbol(self,i): + """ + Return a Unicode as label for index. + + .. note:: This function is adopted from `opt_einsum `_ + """ + if i < 52: + return EINSUM_SYMBOLS_BASE[i] + return chr(i + 140) + + def _init_mode_labels_from_qubits(self,qubits): + + frontier_dict ={} + n = len(qubits) + for x in range(n): + frontier_dict[qubits[x]]=x + return [[i] for i in range(n)], frontier_dict, n + + def _get_bitstring_tensors(self, bitstring, dtype=np.complex128, backend=cp): + + asarray = backend.asarray #_get_backend_asarray_func(backend) + state_0 = asarray([1, 0], dtype=dtype) + state_1 = asarray([0, 1], dtype=dtype) + + basis_map = {'0': state_0, + '1': state_1} + + operands = [basis_map[ibit] for ibit in bitstring] + return operands + + def _parse_gates_to_mode_labels_operands( + self, + gates, + qubits_frontier, + next_frontier + ): + + mode_labels = [] + operands = [] + + for tensor, gate_qubits in gates: + operands.append(tensor) + input_mode_labels = [] + output_mode_labels = [] + for q in gate_qubits: + input_mode_labels.append(qubits_frontier[q]) + output_mode_labels.append(next_frontier) + qubits_frontier[q] = next_frontier + next_frontier += 1 + mode_labels.append(output_mode_labels+input_mode_labels) + return mode_labels, operands + + def _convert_mode_labels_to_expression(self,input_mode_labels, output_mode_labels): + + out_list = [] + for key in output_mode_labels: + out_list.append(output_mode_labels[key]) + + input_symbols = [''.join(map(self._get_symbol, idx)) for idx in input_mode_labels] + expression = ','.join(input_symbols) + '->' + ''.join(map(self._get_symbol, out_list)) + + return expression diff --git a/src/qibotn/__main__.py b/src/qibotn/__main__.py new file mode 100644 index 00000000..61b61e8e --- /dev/null +++ b/src/qibotn/__main__.py @@ -0,0 +1,59 @@ +import argparse +from QiboCircuitConvertor import QiboCircuitToEinsum +from cuquantum import contract +import cupy as cp +from qibo.models import * +from timeit import default_timer as timer + +def parser(): + + parser = argparse.ArgumentParser() + + parser.add_argument("--nqubits", default=10, type=int, help="Number of quibits in the circuits.") + + parser.add_argument("--circuit", default="qft", type=str, + help="Type of circuit to use. See README for the list of " + "available circuits.") + + parser.add_argument("--precision", default='complex128', type=str, + help="Numerical precision of the simulation. " + "Choose between 'complex128' and 'complex64'.") + + return parser.parse_args() + +def main(args: argparse.Namespace): + + print("Testing for %d nqubits" % (args.nqubits)) + nqubits = args.nqubits + circuit_name = args.circuit + datatype = args.precision + #Create qibo quibit + + if circuit_name in ("qft", "QFT"): + circuit = QFT(nqubits) + else: + raise NotImplementedError(f"Cannot find circuit {circuit_name}.") + + myconvertor = QiboCircuitToEinsum(circuit, dtype=datatype) + + expression, operands = myconvertor.state_vector() + + start = timer() + result_qibo = circuit() + end = timer() + circuit_eval_time = end - start + print("Simulation time: Qibo =",circuit_eval_time, 's') + + start = timer() + sv_cutn = contract(expression, *operands) + end = timer() + circuit_eval_time = end - start + print("Simulation time: cuQuantum cuTensorNet =",circuit_eval_time, 's') + + #print(f"is sv in agreement?", cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True))) + assert cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True)) + +if __name__ == "__main__": + main(parser()) + + From 9e01ff9e257883525ab727e8f9bb8ccf20bdca9a Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 10 Feb 2023 16:34:31 +0800 Subject: [PATCH 02/42] Qibo circuit convertor --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 6a256b3f..9b699b67 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,5 @@ # Pyre type checker .pyre/ + +*.pyc \ No newline at end of file From 82aebb19d06571c6643bfe38b2dec9b6bc1c0345 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Mon, 13 Feb 2023 09:39:46 +0800 Subject: [PATCH 03/42] Ending with newline --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9b699b67..7b5d2682 100644 --- a/.gitignore +++ b/.gitignore @@ -128,4 +128,4 @@ # Pyre type checker .pyre/ -*.pyc \ No newline at end of file +*.pyc From 9890d1ffedc587aa0923cbb3d762551bef060fab Mon Sep 17 00:00:00 2001 From: tankya2 Date: Mon, 13 Feb 2023 14:14:53 +0800 Subject: [PATCH 04/42] Created run_bench to get rid of repeated test code --- src/qibotn/__main__.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/qibotn/__main__.py b/src/qibotn/__main__.py index 61b61e8e..e6dad2f0 100644 --- a/src/qibotn/__main__.py +++ b/src/qibotn/__main__.py @@ -21,6 +21,16 @@ def parser(): return parser.parse_args() +def run_bench(task, label): + + start = timer() + result = task() + end = timer() + circuit_eval_time = end - start + print(f"Simulation time: {label} = {circuit_eval_time}s") + + return result + def main(args: argparse.Namespace): print("Testing for %d nqubits" % (args.nqubits)) @@ -35,20 +45,10 @@ def main(args: argparse.Namespace): raise NotImplementedError(f"Cannot find circuit {circuit_name}.") myconvertor = QiboCircuitToEinsum(circuit, dtype=datatype) - expression, operands = myconvertor.state_vector() - start = timer() - result_qibo = circuit() - end = timer() - circuit_eval_time = end - start - print("Simulation time: Qibo =",circuit_eval_time, 's') - - start = timer() - sv_cutn = contract(expression, *operands) - end = timer() - circuit_eval_time = end - start - print("Simulation time: cuQuantum cuTensorNet =",circuit_eval_time, 's') + result_qibo = run_bench(circuit, "Qibo") + sv_cutn = run_bench(lambda:contract(expression, *operands), "cuQuantum cuTensorNet") #print(f"is sv in agreement?", cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True))) assert cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True)) From 98fa7650c1bc0b2ac2f2bce87ed9af63c600a401 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Mon, 13 Feb 2023 14:33:58 +0800 Subject: [PATCH 05/42] Format change using Black --- src/qibotn/QiboCircuitConvertor.py | 87 +++++++++++++++++------------- src/qibotn/__main__.py | 55 +++++++++++-------- 2 files changed, 83 insertions(+), 59 deletions(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index 39fb010c..45000df7 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -1,49 +1,66 @@ - import cupy as cp import numpy as np EINSUM_SYMBOLS_BASE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + class QiboCircuitToEinsum: - def __init__(self, circuit, dtype='complex128'): - + def __init__(self, circuit, dtype="complex128"): self.backend = cp - self.dtype = getattr(self.backend, dtype) + self.dtype = getattr(self.backend, dtype) - self.input_tensor_counter = np. zeros((circuit.nqubits,)) + self.input_tensor_counter = np.zeros((circuit.nqubits,)) self.gates = [] for gate in circuit.queue: targets = list(gate.target_qubits) for target in targets: - self.input_tensor_counter[target] = self.input_tensor_counter[target] + 1 + self.input_tensor_counter[target] = ( + self.input_tensor_counter[target] + 1 + ) controls = list(gate.control_qubits) for control in controls: - self.input_tensor_counter[control] = self.input_tensor_counter[control] + 1 + self.input_tensor_counter[control] = ( + self.input_tensor_counter[control] + 1 + ) gate_qubits = controls + targets - self.gates.append((cp.asarray(gate.matrix).reshape((2,) * 2 * len(gate_qubits)), gate_qubits)) + self.gates.append( + ( + cp.asarray(gate.matrix).reshape((2,) * 2 * len(gate_qubits)), + gate_qubits, + ) + ) - self.qubit_name = [indx for indx, value in enumerate(self.input_tensor_counter) if value > 0] + self.qubit_name = [ + indx for indx, value in enumerate(self.input_tensor_counter) if value > 0 + ] def state_vector(self): - input_tensor_count = np.count_nonzero(self.input_tensor_counter) - input_operands = self._get_bitstring_tensors('0'*input_tensor_count, self.dtype, backend=self.backend) - - mode_labels, qubits_frontier, next_frontier = self._init_mode_labels_from_qubits(self.qubit_name) + input_operands = self._get_bitstring_tensors( + "0" * input_tensor_count, self.dtype, backend=self.backend + ) + + ( + mode_labels, + qubits_frontier, + next_frontier, + ) = self._init_mode_labels_from_qubits(self.qubit_name) - gate_mode_labels, gate_operands = self._parse_gates_to_mode_labels_operands(self.gates, - qubits_frontier, - next_frontier) + gate_mode_labels, gate_operands = self._parse_gates_to_mode_labels_operands( + self.gates, qubits_frontier, next_frontier + ) operands = input_operands + gate_operands mode_labels += gate_mode_labels - expression = self._convert_mode_labels_to_expression(mode_labels, qubits_frontier) + expression = self._convert_mode_labels_to_expression( + mode_labels, qubits_frontier + ) return expression, operands - def _get_symbol(self,i): + def _get_symbol(self, i): """ Return a Unicode as label for index. @@ -53,33 +70,26 @@ def _get_symbol(self,i): return EINSUM_SYMBOLS_BASE[i] return chr(i + 140) - def _init_mode_labels_from_qubits(self,qubits): - - frontier_dict ={} + def _init_mode_labels_from_qubits(self, qubits): + frontier_dict = {} n = len(qubits) for x in range(n): - frontier_dict[qubits[x]]=x + frontier_dict[qubits[x]] = x return [[i] for i in range(n)], frontier_dict, n def _get_bitstring_tensors(self, bitstring, dtype=np.complex128, backend=cp): - - asarray = backend.asarray #_get_backend_asarray_func(backend) + asarray = backend.asarray # _get_backend_asarray_func(backend) state_0 = asarray([1, 0], dtype=dtype) state_1 = asarray([0, 1], dtype=dtype) - basis_map = {'0': state_0, - '1': state_1} - + basis_map = {"0": state_0, "1": state_1} + operands = [basis_map[ibit] for ibit in bitstring] return operands def _parse_gates_to_mode_labels_operands( - self, - gates, - qubits_frontier, - next_frontier + self, gates, qubits_frontier, next_frontier ): - mode_labels = [] operands = [] @@ -92,16 +102,19 @@ def _parse_gates_to_mode_labels_operands( output_mode_labels.append(next_frontier) qubits_frontier[q] = next_frontier next_frontier += 1 - mode_labels.append(output_mode_labels+input_mode_labels) + mode_labels.append(output_mode_labels + input_mode_labels) return mode_labels, operands - def _convert_mode_labels_to_expression(self,input_mode_labels, output_mode_labels): - + def _convert_mode_labels_to_expression(self, input_mode_labels, output_mode_labels): out_list = [] for key in output_mode_labels: out_list.append(output_mode_labels[key]) - input_symbols = [''.join(map(self._get_symbol, idx)) for idx in input_mode_labels] - expression = ','.join(input_symbols) + '->' + ''.join(map(self._get_symbol, out_list)) + input_symbols = [ + "".join(map(self._get_symbol, idx)) for idx in input_mode_labels + ] + expression = ( + ",".join(input_symbols) + "->" + "".join(map(self._get_symbol, out_list)) + ) return expression diff --git a/src/qibotn/__main__.py b/src/qibotn/__main__.py index e6dad2f0..edbb52be 100644 --- a/src/qibotn/__main__.py +++ b/src/qibotn/__main__.py @@ -5,39 +5,49 @@ from qibo.models import * from timeit import default_timer as timer -def parser(): +def parser(): parser = argparse.ArgumentParser() - parser.add_argument("--nqubits", default=10, type=int, help="Number of quibits in the circuits.") - - parser.add_argument("--circuit", default="qft", type=str, - help="Type of circuit to use. See README for the list of " - "available circuits.") - - parser.add_argument("--precision", default='complex128', type=str, - help="Numerical precision of the simulation. " - "Choose between 'complex128' and 'complex64'.") + parser.add_argument( + "--nqubits", default=10, type=int, help="Number of quibits in the circuits." + ) + + parser.add_argument( + "--circuit", + default="qft", + type=str, + help="Type of circuit to use. See README for the list of " + "available circuits.", + ) + + parser.add_argument( + "--precision", + default="complex128", + type=str, + help="Numerical precision of the simulation. " + "Choose between 'complex128' and 'complex64'.", + ) return parser.parse_args() -def run_bench(task, label): - start = timer() +def run_bench(task, label): + start = timer() result = task() - end = timer() + end = timer() circuit_eval_time = end - start print(f"Simulation time: {label} = {circuit_eval_time}s") - + return result -def main(args: argparse.Namespace): +def main(args: argparse.Namespace): print("Testing for %d nqubits" % (args.nqubits)) - nqubits = args.nqubits + nqubits = args.nqubits circuit_name = args.circuit - datatype = args.precision - #Create qibo quibit + datatype = args.precision + # Create qibo quibit if circuit_name in ("qft", "QFT"): circuit = QFT(nqubits) @@ -48,12 +58,13 @@ def main(args: argparse.Namespace): expression, operands = myconvertor.state_vector() result_qibo = run_bench(circuit, "Qibo") - sv_cutn = run_bench(lambda:contract(expression, *operands), "cuQuantum cuTensorNet") + sv_cutn = run_bench( + lambda: contract(expression, *operands), "cuQuantum cuTensorNet" + ) - #print(f"is sv in agreement?", cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True))) + # print(f"is sv in agreement?", cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True))) assert cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True)) + if __name__ == "__main__": main(parser()) - - From dc362bd992b62ecb52c05842e1516e7b788d6358 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Tue, 14 Feb 2023 14:54:51 +0800 Subject: [PATCH 06/42] Changed contract() input to interleaved format --- src/qibotn/QiboCircuitConvertor.py | 36 +++++------------------------- src/qibotn/__main__.py | 7 ++---- 2 files changed, 8 insertions(+), 35 deletions(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index 45000df7..f92e1cdb 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -1,8 +1,6 @@ import cupy as cp import numpy as np -EINSUM_SYMBOLS_BASE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - class QiboCircuitToEinsum: def __init__(self, circuit, dtype="complex128"): @@ -54,21 +52,13 @@ def state_vector(self): operands = input_operands + gate_operands mode_labels += gate_mode_labels - expression = self._convert_mode_labels_to_expression( - mode_labels, qubits_frontier - ) - - return expression, operands - - def _get_symbol(self, i): - """ - Return a Unicode as label for index. + out_list = [] + for key in qubits_frontier: + out_list.append(qubits_frontier[key]) - .. note:: This function is adopted from `opt_einsum `_ - """ - if i < 52: - return EINSUM_SYMBOLS_BASE[i] - return chr(i + 140) + operand_exp_interleave = [x for y in zip(operands, mode_labels) for x in y] + operand_exp_interleave.append(out_list) + return operand_exp_interleave def _init_mode_labels_from_qubits(self, qubits): frontier_dict = {} @@ -104,17 +94,3 @@ def _parse_gates_to_mode_labels_operands( next_frontier += 1 mode_labels.append(output_mode_labels + input_mode_labels) return mode_labels, operands - - def _convert_mode_labels_to_expression(self, input_mode_labels, output_mode_labels): - out_list = [] - for key in output_mode_labels: - out_list.append(output_mode_labels[key]) - - input_symbols = [ - "".join(map(self._get_symbol, idx)) for idx in input_mode_labels - ] - expression = ( - ",".join(input_symbols) + "->" + "".join(map(self._get_symbol, out_list)) - ) - - return expression diff --git a/src/qibotn/__main__.py b/src/qibotn/__main__.py index edbb52be..03db0834 100644 --- a/src/qibotn/__main__.py +++ b/src/qibotn/__main__.py @@ -47,7 +47,6 @@ def main(args: argparse.Namespace): nqubits = args.nqubits circuit_name = args.circuit datatype = args.precision - # Create qibo quibit if circuit_name in ("qft", "QFT"): circuit = QFT(nqubits) @@ -55,12 +54,10 @@ def main(args: argparse.Namespace): raise NotImplementedError(f"Cannot find circuit {circuit_name}.") myconvertor = QiboCircuitToEinsum(circuit, dtype=datatype) - expression, operands = myconvertor.state_vector() + operands_expression = myconvertor.state_vector() result_qibo = run_bench(circuit, "Qibo") - sv_cutn = run_bench( - lambda: contract(expression, *operands), "cuQuantum cuTensorNet" - ) + sv_cutn = run_bench(lambda: contract(*operands_expression), "cuQuantum cuTensorNet") # print(f"is sv in agreement?", cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True))) assert cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True)) From 6838faba33e96f03e03e9c460be5c785d1d630a0 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 10 Feb 2023 16:31:17 +0800 Subject: [PATCH 07/42] Qibo circuit convertor --- src/qibotn/QiboCircuitConvertor.py | 107 +++++++++++++++++++++++++++++ src/qibotn/__main__.py | 68 +++++++++++++++++- 2 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 src/qibotn/QiboCircuitConvertor.py diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py new file mode 100644 index 00000000..39fb010c --- /dev/null +++ b/src/qibotn/QiboCircuitConvertor.py @@ -0,0 +1,107 @@ + +import cupy as cp +import numpy as np + +EINSUM_SYMBOLS_BASE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +class QiboCircuitToEinsum: + def __init__(self, circuit, dtype='complex128'): + + self.backend = cp + self.dtype = getattr(self.backend, dtype) + + self.input_tensor_counter = np. zeros((circuit.nqubits,)) + self.gates = [] + for gate in circuit.queue: + targets = list(gate.target_qubits) + for target in targets: + self.input_tensor_counter[target] = self.input_tensor_counter[target] + 1 + controls = list(gate.control_qubits) + for control in controls: + self.input_tensor_counter[control] = self.input_tensor_counter[control] + 1 + gate_qubits = controls + targets + self.gates.append((cp.asarray(gate.matrix).reshape((2,) * 2 * len(gate_qubits)), gate_qubits)) + + self.qubit_name = [indx for indx, value in enumerate(self.input_tensor_counter) if value > 0] + + def state_vector(self): + + input_tensor_count = np.count_nonzero(self.input_tensor_counter) + + input_operands = self._get_bitstring_tensors('0'*input_tensor_count, self.dtype, backend=self.backend) + + mode_labels, qubits_frontier, next_frontier = self._init_mode_labels_from_qubits(self.qubit_name) + + gate_mode_labels, gate_operands = self._parse_gates_to_mode_labels_operands(self.gates, + qubits_frontier, + next_frontier) + + operands = input_operands + gate_operands + mode_labels += gate_mode_labels + + expression = self._convert_mode_labels_to_expression(mode_labels, qubits_frontier) + + return expression, operands + + def _get_symbol(self,i): + """ + Return a Unicode as label for index. + + .. note:: This function is adopted from `opt_einsum `_ + """ + if i < 52: + return EINSUM_SYMBOLS_BASE[i] + return chr(i + 140) + + def _init_mode_labels_from_qubits(self,qubits): + + frontier_dict ={} + n = len(qubits) + for x in range(n): + frontier_dict[qubits[x]]=x + return [[i] for i in range(n)], frontier_dict, n + + def _get_bitstring_tensors(self, bitstring, dtype=np.complex128, backend=cp): + + asarray = backend.asarray #_get_backend_asarray_func(backend) + state_0 = asarray([1, 0], dtype=dtype) + state_1 = asarray([0, 1], dtype=dtype) + + basis_map = {'0': state_0, + '1': state_1} + + operands = [basis_map[ibit] for ibit in bitstring] + return operands + + def _parse_gates_to_mode_labels_operands( + self, + gates, + qubits_frontier, + next_frontier + ): + + mode_labels = [] + operands = [] + + for tensor, gate_qubits in gates: + operands.append(tensor) + input_mode_labels = [] + output_mode_labels = [] + for q in gate_qubits: + input_mode_labels.append(qubits_frontier[q]) + output_mode_labels.append(next_frontier) + qubits_frontier[q] = next_frontier + next_frontier += 1 + mode_labels.append(output_mode_labels+input_mode_labels) + return mode_labels, operands + + def _convert_mode_labels_to_expression(self,input_mode_labels, output_mode_labels): + + out_list = [] + for key in output_mode_labels: + out_list.append(output_mode_labels[key]) + + input_symbols = [''.join(map(self._get_symbol, idx)) for idx in input_mode_labels] + expression = ','.join(input_symbols) + '->' + ''.join(map(self._get_symbol, out_list)) + + return expression diff --git a/src/qibotn/__main__.py b/src/qibotn/__main__.py index 8ed74396..b42f84df 100644 --- a/src/qibotn/__main__.py +++ b/src/qibotn/__main__.py @@ -1,5 +1,11 @@ import argparse -from qibotn import qasm_quimb +from timeit import default_timer as timer + +from qibotn import quimb as qiboquimb +from QiboCircuitConvertor import QiboCircuitToEinsum +from cuquantum import contract +import cupy as cp +from qibo.models import * def parser(): @@ -12,7 +18,65 @@ def parser(): def main(args: argparse.Namespace): print("Testing for %d nqubits" % (args.nqubits)) - qasm_quimb.eval_QI_qft(args.nqubits, args.qasm_circ, args.init_state) + qiboquimb.eval(args.nqubits, args.qasm_circ, args.init_state) + + +def parser_cuquantum(): + parser = argparse.ArgumentParser() + + parser.add_argument( + "--nqubits", default=10, type=int, help="Number of quibits in the circuits." + ) + + parser.add_argument( + "--circuit", + default="qft", + type=str, + help="Type of circuit to use. See README for the list of " + "available circuits.", + ) + + parser.add_argument( + "--precision", + default="complex128", + type=str, + help="Numerical precision of the simulation. " + "Choose between 'complex128' and 'complex64'.", + ) + + return parser.parse_args() + + +def main_cuquantum(args: argparse.Namespace): + print("Testing for %d nqubits" % (args.nqubits)) + nqubits = args.nqubits + circuit_name = args.circuit + datatype = args.precision + # Create qibo quibit + + if circuit_name in ("qft", "QFT"): + circuit = QFT(nqubits) + else: + raise NotImplementedError(f"Cannot find circuit {circuit_name}.") + + myconvertor = QiboCircuitToEinsum(circuit, dtype=datatype) + + expression, operands = myconvertor.state_vector() + + start = timer() + result_qibo = circuit() + end = timer() + circuit_eval_time = end - start + print("Simulation time: Qibo =", circuit_eval_time, "s") + + start = timer() + sv_cutn = contract(expression, *operands) + end = timer() + circuit_eval_time = end - start + print("Simulation time: cuQuantum cuTensorNet =", circuit_eval_time, "s") + + # print(f"is sv in agreement?", cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True))) + assert cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True)) if __name__ == "__main__": From bed3a50be523aaefd319c17e83534aa52481c59e Mon Sep 17 00:00:00 2001 From: tankya2 Date: Mon, 13 Feb 2023 14:14:53 +0800 Subject: [PATCH 08/42] Created run_bench to get rid of repeated test code --- src/qibotn/__main__.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/qibotn/__main__.py b/src/qibotn/__main__.py index b42f84df..c09b2fc7 100644 --- a/src/qibotn/__main__.py +++ b/src/qibotn/__main__.py @@ -5,7 +5,7 @@ from QiboCircuitConvertor import QiboCircuitToEinsum from cuquantum import contract import cupy as cp -from qibo.models import * +from qibo.models import QFT def parser(): @@ -47,6 +47,16 @@ def parser_cuquantum(): return parser.parse_args() +def run_bench(task, label): + start = timer() + result = task() + end = timer() + circuit_eval_time = end - start + print(f"Simulation time: {label} = {circuit_eval_time}s") + + return result + + def main_cuquantum(args: argparse.Namespace): print("Testing for %d nqubits" % (args.nqubits)) nqubits = args.nqubits @@ -60,20 +70,12 @@ def main_cuquantum(args: argparse.Namespace): raise NotImplementedError(f"Cannot find circuit {circuit_name}.") myconvertor = QiboCircuitToEinsum(circuit, dtype=datatype) - expression, operands = myconvertor.state_vector() - start = timer() - result_qibo = circuit() - end = timer() - circuit_eval_time = end - start - print("Simulation time: Qibo =", circuit_eval_time, "s") - - start = timer() - sv_cutn = contract(expression, *operands) - end = timer() - circuit_eval_time = end - start - print("Simulation time: cuQuantum cuTensorNet =", circuit_eval_time, "s") + result_qibo = run_bench(circuit, "Qibo") + sv_cutn = run_bench( + lambda: contract(expression, *operands), "cuQuantum cuTensorNet" + ) # print(f"is sv in agreement?", cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True))) assert cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True)) From 3b00c8133099d494d9d3d53a0e58fe86f99e05e3 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Mon, 13 Feb 2023 14:33:58 +0800 Subject: [PATCH 09/42] Format change using Black --- src/qibotn/QiboCircuitConvertor.py | 87 +++++++++++++++++------------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index 39fb010c..45000df7 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -1,49 +1,66 @@ - import cupy as cp import numpy as np EINSUM_SYMBOLS_BASE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + class QiboCircuitToEinsum: - def __init__(self, circuit, dtype='complex128'): - + def __init__(self, circuit, dtype="complex128"): self.backend = cp - self.dtype = getattr(self.backend, dtype) + self.dtype = getattr(self.backend, dtype) - self.input_tensor_counter = np. zeros((circuit.nqubits,)) + self.input_tensor_counter = np.zeros((circuit.nqubits,)) self.gates = [] for gate in circuit.queue: targets = list(gate.target_qubits) for target in targets: - self.input_tensor_counter[target] = self.input_tensor_counter[target] + 1 + self.input_tensor_counter[target] = ( + self.input_tensor_counter[target] + 1 + ) controls = list(gate.control_qubits) for control in controls: - self.input_tensor_counter[control] = self.input_tensor_counter[control] + 1 + self.input_tensor_counter[control] = ( + self.input_tensor_counter[control] + 1 + ) gate_qubits = controls + targets - self.gates.append((cp.asarray(gate.matrix).reshape((2,) * 2 * len(gate_qubits)), gate_qubits)) + self.gates.append( + ( + cp.asarray(gate.matrix).reshape((2,) * 2 * len(gate_qubits)), + gate_qubits, + ) + ) - self.qubit_name = [indx for indx, value in enumerate(self.input_tensor_counter) if value > 0] + self.qubit_name = [ + indx for indx, value in enumerate(self.input_tensor_counter) if value > 0 + ] def state_vector(self): - input_tensor_count = np.count_nonzero(self.input_tensor_counter) - input_operands = self._get_bitstring_tensors('0'*input_tensor_count, self.dtype, backend=self.backend) - - mode_labels, qubits_frontier, next_frontier = self._init_mode_labels_from_qubits(self.qubit_name) + input_operands = self._get_bitstring_tensors( + "0" * input_tensor_count, self.dtype, backend=self.backend + ) + + ( + mode_labels, + qubits_frontier, + next_frontier, + ) = self._init_mode_labels_from_qubits(self.qubit_name) - gate_mode_labels, gate_operands = self._parse_gates_to_mode_labels_operands(self.gates, - qubits_frontier, - next_frontier) + gate_mode_labels, gate_operands = self._parse_gates_to_mode_labels_operands( + self.gates, qubits_frontier, next_frontier + ) operands = input_operands + gate_operands mode_labels += gate_mode_labels - expression = self._convert_mode_labels_to_expression(mode_labels, qubits_frontier) + expression = self._convert_mode_labels_to_expression( + mode_labels, qubits_frontier + ) return expression, operands - def _get_symbol(self,i): + def _get_symbol(self, i): """ Return a Unicode as label for index. @@ -53,33 +70,26 @@ def _get_symbol(self,i): return EINSUM_SYMBOLS_BASE[i] return chr(i + 140) - def _init_mode_labels_from_qubits(self,qubits): - - frontier_dict ={} + def _init_mode_labels_from_qubits(self, qubits): + frontier_dict = {} n = len(qubits) for x in range(n): - frontier_dict[qubits[x]]=x + frontier_dict[qubits[x]] = x return [[i] for i in range(n)], frontier_dict, n def _get_bitstring_tensors(self, bitstring, dtype=np.complex128, backend=cp): - - asarray = backend.asarray #_get_backend_asarray_func(backend) + asarray = backend.asarray # _get_backend_asarray_func(backend) state_0 = asarray([1, 0], dtype=dtype) state_1 = asarray([0, 1], dtype=dtype) - basis_map = {'0': state_0, - '1': state_1} - + basis_map = {"0": state_0, "1": state_1} + operands = [basis_map[ibit] for ibit in bitstring] return operands def _parse_gates_to_mode_labels_operands( - self, - gates, - qubits_frontier, - next_frontier + self, gates, qubits_frontier, next_frontier ): - mode_labels = [] operands = [] @@ -92,16 +102,19 @@ def _parse_gates_to_mode_labels_operands( output_mode_labels.append(next_frontier) qubits_frontier[q] = next_frontier next_frontier += 1 - mode_labels.append(output_mode_labels+input_mode_labels) + mode_labels.append(output_mode_labels + input_mode_labels) return mode_labels, operands - def _convert_mode_labels_to_expression(self,input_mode_labels, output_mode_labels): - + def _convert_mode_labels_to_expression(self, input_mode_labels, output_mode_labels): out_list = [] for key in output_mode_labels: out_list.append(output_mode_labels[key]) - input_symbols = [''.join(map(self._get_symbol, idx)) for idx in input_mode_labels] - expression = ','.join(input_symbols) + '->' + ''.join(map(self._get_symbol, out_list)) + input_symbols = [ + "".join(map(self._get_symbol, idx)) for idx in input_mode_labels + ] + expression = ( + ",".join(input_symbols) + "->" + "".join(map(self._get_symbol, out_list)) + ) return expression From 93ec07c2379fb5ccb416ad3506cd5de187df07f9 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Tue, 14 Feb 2023 14:54:51 +0800 Subject: [PATCH 10/42] Changed contract() input to interleaved format --- src/qibotn/QiboCircuitConvertor.py | 36 +++++------------------------- src/qibotn/__main__.py | 7 ++---- 2 files changed, 8 insertions(+), 35 deletions(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index 45000df7..f92e1cdb 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -1,8 +1,6 @@ import cupy as cp import numpy as np -EINSUM_SYMBOLS_BASE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - class QiboCircuitToEinsum: def __init__(self, circuit, dtype="complex128"): @@ -54,21 +52,13 @@ def state_vector(self): operands = input_operands + gate_operands mode_labels += gate_mode_labels - expression = self._convert_mode_labels_to_expression( - mode_labels, qubits_frontier - ) - - return expression, operands - - def _get_symbol(self, i): - """ - Return a Unicode as label for index. + out_list = [] + for key in qubits_frontier: + out_list.append(qubits_frontier[key]) - .. note:: This function is adopted from `opt_einsum `_ - """ - if i < 52: - return EINSUM_SYMBOLS_BASE[i] - return chr(i + 140) + operand_exp_interleave = [x for y in zip(operands, mode_labels) for x in y] + operand_exp_interleave.append(out_list) + return operand_exp_interleave def _init_mode_labels_from_qubits(self, qubits): frontier_dict = {} @@ -104,17 +94,3 @@ def _parse_gates_to_mode_labels_operands( next_frontier += 1 mode_labels.append(output_mode_labels + input_mode_labels) return mode_labels, operands - - def _convert_mode_labels_to_expression(self, input_mode_labels, output_mode_labels): - out_list = [] - for key in output_mode_labels: - out_list.append(output_mode_labels[key]) - - input_symbols = [ - "".join(map(self._get_symbol, idx)) for idx in input_mode_labels - ] - expression = ( - ",".join(input_symbols) + "->" + "".join(map(self._get_symbol, out_list)) - ) - - return expression diff --git a/src/qibotn/__main__.py b/src/qibotn/__main__.py index c09b2fc7..31a7097e 100644 --- a/src/qibotn/__main__.py +++ b/src/qibotn/__main__.py @@ -62,7 +62,6 @@ def main_cuquantum(args: argparse.Namespace): nqubits = args.nqubits circuit_name = args.circuit datatype = args.precision - # Create qibo quibit if circuit_name in ("qft", "QFT"): circuit = QFT(nqubits) @@ -70,12 +69,10 @@ def main_cuquantum(args: argparse.Namespace): raise NotImplementedError(f"Cannot find circuit {circuit_name}.") myconvertor = QiboCircuitToEinsum(circuit, dtype=datatype) - expression, operands = myconvertor.state_vector() + operands_expression = myconvertor.state_vector() result_qibo = run_bench(circuit, "Qibo") - sv_cutn = run_bench( - lambda: contract(expression, *operands), "cuQuantum cuTensorNet" - ) + sv_cutn = run_bench(lambda: contract(*operands_expression), "cuQuantum cuTensorNet") # print(f"is sv in agreement?", cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True))) assert cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True)) From a2ee2eeb99da2eeb6e2353e627e5349f1e1f6709 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Wed, 22 Feb 2023 09:55:15 +0800 Subject: [PATCH 11/42] Update to make codes clearer --- src/qibotn/QiboCircuitConvertor.py | 33 +++++++++++++----------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index f92e1cdb..dcbf3e7c 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -8,32 +8,27 @@ def __init__(self, circuit, dtype="complex128"): self.dtype = getattr(self.backend, dtype) self.input_tensor_counter = np.zeros((circuit.nqubits,)) - self.gates = [] + self.gate_tensors = [] for gate in circuit.queue: - targets = list(gate.target_qubits) - for target in targets: - self.input_tensor_counter[target] = ( - self.input_tensor_counter[target] + 1 - ) - controls = list(gate.control_qubits) - for control in controls: - self.input_tensor_counter[control] = ( - self.input_tensor_counter[control] + 1 - ) - gate_qubits = controls + targets - self.gates.append( + for target in gate.target_qubits: + self.input_tensor_counter[target] += 1 + for control in gate.control_qubits: + self.input_tensor_counter[control] += 1 + gate_qubits = gate.control_qubits + gate.target_qubits + # self.gate_tensors is to extract into a list the gate matrix together with the qubit id that it is acting on + self.gate_tensors.append( ( cp.asarray(gate.matrix).reshape((2,) * 2 * len(gate_qubits)), gate_qubits, ) ) - - self.qubit_name = [ + # self.active_qubits is to identify qubits with at least 1 gate acting on it in the whole circuit. + self.active_qubits = [ indx for indx, value in enumerate(self.input_tensor_counter) if value > 0 ] def state_vector(self): - input_tensor_count = np.count_nonzero(self.input_tensor_counter) + input_tensor_count = len(self.active_qubits) input_operands = self._get_bitstring_tensors( "0" * input_tensor_count, self.dtype, backend=self.backend @@ -43,10 +38,10 @@ def state_vector(self): mode_labels, qubits_frontier, next_frontier, - ) = self._init_mode_labels_from_qubits(self.qubit_name) + ) = self._init_mode_labels_from_qubits(self.active_qubits) gate_mode_labels, gate_operands = self._parse_gates_to_mode_labels_operands( - self.gates, qubits_frontier, next_frontier + self.gate_tensors, qubits_frontier, next_frontier ) operands = input_operands + gate_operands @@ -68,7 +63,7 @@ def _init_mode_labels_from_qubits(self, qubits): return [[i] for i in range(n)], frontier_dict, n def _get_bitstring_tensors(self, bitstring, dtype=np.complex128, backend=cp): - asarray = backend.asarray # _get_backend_asarray_func(backend) + asarray = backend.asarray state_0 = asarray([1, 0], dtype=dtype) state_1 = asarray([0, 1], dtype=dtype) From 74880c3777d71e2d6a3a292ee16fe4d57f0ada54 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Wed, 22 Feb 2023 10:37:41 +0800 Subject: [PATCH 12/42] Change the call to main to cuQuantum --- src/qibotn/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibotn/__main__.py b/src/qibotn/__main__.py index 31a7097e..f81bfd30 100644 --- a/src/qibotn/__main__.py +++ b/src/qibotn/__main__.py @@ -79,4 +79,4 @@ def main_cuquantum(args: argparse.Namespace): if __name__ == "__main__": - main(parser()) + main_cuquantum(parser_cuquantum()) From b200aed9683c084ce28d012dd1d8b82b0dc2a0cd Mon Sep 17 00:00:00 2001 From: tankya2 Date: Thu, 23 Feb 2023 12:11:18 +0800 Subject: [PATCH 13/42] Removed duplicated *.pyc --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index d2c21c0f..d903da02 100644 --- a/.gitignore +++ b/.gitignore @@ -146,7 +146,6 @@ dmypy.json # Pyre type checker .pyre/ -*.pyc # pytype static type analyzer .pytype/ From c05fe12e46b7e368b19fb4dadb1dbeda1e09f9cd Mon Sep 17 00:00:00 2001 From: tankya2 Date: Tue, 21 Mar 2023 15:17:29 +0800 Subject: [PATCH 14/42] Updated with pytest script for testing --- setup.py | 5 ++++ src/qibotn/cutn.py | 10 +++++++ tests/test_cuquantum_cutensor_backend.py | 38 ++++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 src/qibotn/cutn.py create mode 100644 tests/test_cuquantum_cutensor_backend.py diff --git a/setup.py b/setup.py index aefac894..147e58bf 100644 --- a/setup.py +++ b/setup.py @@ -42,6 +42,11 @@ def version(): "qibo>=0.1.10", "qibojit>=0.0.7", "quimb[tensor]>=1.4.0", + "cupy>=11.6.0", + "cuquantum-python-cu11>=22.11.0.1", + "custatevec-cu11>=1.2.0", + "cutensor-cu11>=1.6.2", + "cutensornet-cu11>=2.0.0", ], extras_require={ "docs": [], diff --git a/src/qibotn/cutn.py b/src/qibotn/cutn.py new file mode 100644 index 00000000..fe0ae386 --- /dev/null +++ b/src/qibotn/cutn.py @@ -0,0 +1,10 @@ +# from qibotn import quimb as qiboquimb +from QiboCircuitConvertor import QiboCircuitToEinsum +from cuquantum import contract + + +def eval(qibo_circ): + myconvertor = QiboCircuitToEinsum(qibo_circ, dtype="complex128") + operands_expression = myconvertor.state_vector() + results = contract(*operands_expression) + return results.flatten() diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py new file mode 100644 index 00000000..9c989a33 --- /dev/null +++ b/tests/test_cuquantum_cutensor_backend.py @@ -0,0 +1,38 @@ +import copy +import os +from timeit import default_timer as timer + +import config +import numpy as np +import pytest +import qibo +from qibo.models import QFT + + +def qibo_qft(nqubits, swaps): + circ_qibo = QFT(nqubits, swaps) + state_vec = np.array(circ_qibo()) + return circ_qibo, state_vec + + +def time(func): + start = timer() + res = func() + end = timer() + time = end - start + return time, res + + +@pytest.mark.parametrize("nqubits", [1, 2, 5, 10]) +def test_eval(nqubits: int): + import qibotn.cutn + + # Test qibo + qibo.set_backend(backend=config.qibo.backend, platform=config.qibo.platform) + qibo_time, (qibo_circ, result_sv) = time(lambda: qibo_qft(nqubits, swaps=True)) + + # Test Cuquantum + cutn_time, result_tn = time(lambda: qibotn.cutn.eval(qibo_circ)) + + assert 1e-2 * qibo_time < cutn_time < 1e2 * qibo_time + assert np.allclose(result_sv, result_tn), "Resulting dense vectors do not match" From 3dbc4e623e1827e96104c0b500382a09e0ca8a0f Mon Sep 17 00:00:00 2001 From: tankya2 Date: Wed, 22 Mar 2023 09:01:19 +0800 Subject: [PATCH 15/42] Changed python version to 3.8 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 147e58bf..5d98ccde 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ def version(): "pylint>=2.16.0", ], }, - python_requires=">=3.7.0", + python_requires=">=3.8.0", long_description=(HERE / "README.md").read_text(encoding="utf-8"), long_description_content_type="text/markdown", ) From 727e11f34af03f748cbebde839f7ed2600d8d2fd Mon Sep 17 00:00:00 2001 From: tankya2 Date: Wed, 22 Mar 2023 09:12:13 +0800 Subject: [PATCH 16/42] Update cuquantum version --- setup.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 5d98ccde..2b1184ce 100644 --- a/setup.py +++ b/setup.py @@ -43,10 +43,7 @@ def version(): "qibojit>=0.0.7", "quimb[tensor]>=1.4.0", "cupy>=11.6.0", - "cuquantum-python-cu11>=22.11.0.1", - "custatevec-cu11>=1.2.0", - "cutensor-cu11>=1.6.2", - "cutensornet-cu11>=2.0.0", + "cuquantum-python-cu11", ], extras_require={ "docs": [], From 04c4190c98c7366b6ca72aaf55c636999b590425 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Wed, 22 Mar 2023 10:51:32 +0800 Subject: [PATCH 17/42] Removed python 3.7 from version matrix --- .github/workflows/rules.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rules.yml b/.github/workflows/rules.yml index 177ed867..c0f29179 100644 --- a/.github/workflows/rules.yml +++ b/.github/workflows/rules.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: [3.7, 3.8, 3.9, "3.10"] + python-version: [3.8, 3.9, "3.10"] uses: qiboteam/workflows/.github/workflows/rules.yml@main with: os: ${{ matrix.os }} From 57414dfb446150ead6e0ddadef20c965f9b51047 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Mon, 3 Apr 2023 10:18:53 +0800 Subject: [PATCH 18/42] added in decorator to mark GPU --- tests/test_cuquantum_cutensor_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index 9c989a33..07bb337f 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -22,7 +22,7 @@ def time(func): time = end - start return time, res - +@pytest.mark.gpu @pytest.mark.parametrize("nqubits", [1, 2, 5, 10]) def test_eval(nqubits: int): import qibotn.cutn From bed7d7178792c63e398507b7ff79a606383fa2d8 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Mon, 3 Apr 2023 10:21:35 +0800 Subject: [PATCH 19/42] Commented away GPU portion --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2b1184ce..17355df5 100644 --- a/setup.py +++ b/setup.py @@ -42,8 +42,8 @@ def version(): "qibo>=0.1.10", "qibojit>=0.0.7", "quimb[tensor]>=1.4.0", - "cupy>=11.6.0", - "cuquantum-python-cu11", + #"cupy>=11.6.0", + #"cuquantum-python-cu11", ], extras_require={ "docs": [], From 7751dbd2f9ec6bed9640e0c126ab48d9dd903ea8 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Mon, 3 Apr 2023 10:31:23 +0800 Subject: [PATCH 20/42] Added back GPU portion [skip ci] --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 17355df5..2b1184ce 100644 --- a/setup.py +++ b/setup.py @@ -42,8 +42,8 @@ def version(): "qibo>=0.1.10", "qibojit>=0.0.7", "quimb[tensor]>=1.4.0", - #"cupy>=11.6.0", - #"cuquantum-python-cu11", + "cupy>=11.6.0", + "cuquantum-python-cu11", ], extras_require={ "docs": [], From aee995802f9986ae3bfa8c0151ce0a98faa300f2 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Tue, 18 Apr 2023 11:34:00 +0800 Subject: [PATCH 21/42] Add datatype as an input in eval() [skip ci] --- src/qibotn/cutn.py | 4 ++-- tests/test_cuquantum_cutensor_backend.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/qibotn/cutn.py b/src/qibotn/cutn.py index fe0ae386..026604a3 100644 --- a/src/qibotn/cutn.py +++ b/src/qibotn/cutn.py @@ -3,8 +3,8 @@ from cuquantum import contract -def eval(qibo_circ): - myconvertor = QiboCircuitToEinsum(qibo_circ, dtype="complex128") +def eval(qibo_circ,datatype): + myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) operands_expression = myconvertor.state_vector() results = contract(*operands_expression) return results.flatten() diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index 07bb337f..30823efa 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -32,7 +32,8 @@ def test_eval(nqubits: int): qibo_time, (qibo_circ, result_sv) = time(lambda: qibo_qft(nqubits, swaps=True)) # Test Cuquantum - cutn_time, result_tn = time(lambda: qibotn.cutn.eval(qibo_circ)) + data_type = "complex128" + cutn_time, result_tn = time(lambda: qibotn.cutn.eval(qibo_circ,data_type)) assert 1e-2 * qibo_time < cutn_time < 1e2 * qibo_time assert np.allclose(result_sv, result_tn), "Resulting dense vectors do not match" From c592c6018a16c9e65da2af5615f1e9c2b750dce3 Mon Sep 17 00:00:00 2001 From: Liwei Yang Date: Wed, 19 Apr 2023 10:13:30 +0800 Subject: [PATCH 22/42] Make the import statement more concise --- src/qibotn/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibotn/__main__.py b/src/qibotn/__main__.py index f81bfd30..f7027558 100644 --- a/src/qibotn/__main__.py +++ b/src/qibotn/__main__.py @@ -1,7 +1,7 @@ import argparse from timeit import default_timer as timer -from qibotn import quimb as qiboquimb +import qibotn.quimb from QiboCircuitConvertor import QiboCircuitToEinsum from cuquantum import contract import cupy as cp @@ -18,7 +18,7 @@ def parser(): def main(args: argparse.Namespace): print("Testing for %d nqubits" % (args.nqubits)) - qiboquimb.eval(args.nqubits, args.qasm_circ, args.init_state) + qibotn.quimb.eval(args.nqubits, args.qasm_circ, args.init_state) def parser_cuquantum(): From a50cb76fdb5f7b203bf122482bd5e78481a2a1f1 Mon Sep 17 00:00:00 2001 From: Liwei Yang Date: Wed, 19 Apr 2023 10:32:17 +0800 Subject: [PATCH 23/42] Remove unnecessary aliases --- src/qibotn/__main__.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/qibotn/__main__.py b/src/qibotn/__main__.py index f7027558..f79a24e6 100644 --- a/src/qibotn/__main__.py +++ b/src/qibotn/__main__.py @@ -59,14 +59,12 @@ def run_bench(task, label): def main_cuquantum(args: argparse.Namespace): print("Testing for %d nqubits" % (args.nqubits)) - nqubits = args.nqubits - circuit_name = args.circuit datatype = args.precision - if circuit_name in ("qft", "QFT"): - circuit = QFT(nqubits) + if args.circuit.lower() == "qft": + circuit = QFT(args.nqubits) else: - raise NotImplementedError(f"Cannot find circuit {circuit_name}.") + raise NotImplementedError(f"Cannot find circuit {args.circuit}.") myconvertor = QiboCircuitToEinsum(circuit, dtype=datatype) operands_expression = myconvertor.state_vector() From d86eaf0134329b356c6da11545b9881fc2aae903 Mon Sep 17 00:00:00 2001 From: Liwei Yang Date: Wed, 19 Apr 2023 15:00:43 +0800 Subject: [PATCH 24/42] Fix the import error for QiboCircuitConvertor --- src/qibotn/cutn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibotn/cutn.py b/src/qibotn/cutn.py index 026604a3..985d7bbc 100644 --- a/src/qibotn/cutn.py +++ b/src/qibotn/cutn.py @@ -1,9 +1,9 @@ # from qibotn import quimb as qiboquimb -from QiboCircuitConvertor import QiboCircuitToEinsum +from qibotn.QiboCircuitConvertor import QiboCircuitToEinsum from cuquantum import contract -def eval(qibo_circ,datatype): +def eval(qibo_circ, datatype): myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) operands_expression = myconvertor.state_vector() results = contract(*operands_expression) From bea6b1c16659305fe80538017343a5c23f550828 Mon Sep 17 00:00:00 2001 From: Liwei Yang Date: Wed, 19 Apr 2023 15:09:01 +0800 Subject: [PATCH 25/42] Remove the test in __main__.py because the test has been moved into folder --- src/qibotn/__main__.py | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/qibotn/__main__.py b/src/qibotn/__main__.py index f79a24e6..15a775ac 100644 --- a/src/qibotn/__main__.py +++ b/src/qibotn/__main__.py @@ -57,24 +57,5 @@ def run_bench(task, label): return result -def main_cuquantum(args: argparse.Namespace): - print("Testing for %d nqubits" % (args.nqubits)) - datatype = args.precision - - if args.circuit.lower() == "qft": - circuit = QFT(args.nqubits) - else: - raise NotImplementedError(f"Cannot find circuit {args.circuit}.") - - myconvertor = QiboCircuitToEinsum(circuit, dtype=datatype) - operands_expression = myconvertor.state_vector() - - result_qibo = run_bench(circuit, "Qibo") - sv_cutn = run_bench(lambda: contract(*operands_expression), "cuQuantum cuTensorNet") - - # print(f"is sv in agreement?", cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True))) - assert cp.allclose(sv_cutn.flatten(), result_qibo.state(numpy=True)) - - if __name__ == "__main__": - main_cuquantum(parser_cuquantum()) + main(parser()) From 725c92e75dc73c3e0b6d315d79f3089c961c10ec Mon Sep 17 00:00:00 2001 From: Liwei Yang Date: Wed, 19 Apr 2023 15:12:08 +0800 Subject: [PATCH 26/42] Remove unused imports, format it using black --- tests/test_cuquantum_cutensor_backend.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index 30823efa..74cf2e12 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -1,5 +1,3 @@ -import copy -import os from timeit import default_timer as timer import config @@ -22,18 +20,22 @@ def time(func): time = end - start return time, res + @pytest.mark.gpu @pytest.mark.parametrize("nqubits", [1, 2, 5, 10]) def test_eval(nqubits: int): import qibotn.cutn # Test qibo - qibo.set_backend(backend=config.qibo.backend, platform=config.qibo.platform) - qibo_time, (qibo_circ, result_sv) = time(lambda: qibo_qft(nqubits, swaps=True)) + qibo.set_backend(backend=config.qibo.backend, + platform=config.qibo.platform) + qibo_time, (qibo_circ, result_sv) = time( + lambda: qibo_qft(nqubits, swaps=True)) # Test Cuquantum data_type = "complex128" - cutn_time, result_tn = time(lambda: qibotn.cutn.eval(qibo_circ,data_type)) + cutn_time, result_tn = time(lambda: qibotn.cutn.eval(qibo_circ, data_type)) assert 1e-2 * qibo_time < cutn_time < 1e2 * qibo_time - assert np.allclose(result_sv, result_tn), "Resulting dense vectors do not match" + assert np.allclose( + result_sv, result_tn), "Resulting dense vectors do not match" From 820744e57e0a7fc2b4f63cfe7e1d90df3736c3a4 Mon Sep 17 00:00:00 2001 From: Liwei Yang Date: Wed, 19 Apr 2023 15:19:26 +0800 Subject: [PATCH 27/42] Remove unused functions --- src/qibotn/__main__.py | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/src/qibotn/__main__.py b/src/qibotn/__main__.py index 15a775ac..052dfaaf 100644 --- a/src/qibotn/__main__.py +++ b/src/qibotn/__main__.py @@ -21,41 +21,5 @@ def main(args: argparse.Namespace): qibotn.quimb.eval(args.nqubits, args.qasm_circ, args.init_state) -def parser_cuquantum(): - parser = argparse.ArgumentParser() - - parser.add_argument( - "--nqubits", default=10, type=int, help="Number of quibits in the circuits." - ) - - parser.add_argument( - "--circuit", - default="qft", - type=str, - help="Type of circuit to use. See README for the list of " - "available circuits.", - ) - - parser.add_argument( - "--precision", - default="complex128", - type=str, - help="Numerical precision of the simulation. " - "Choose between 'complex128' and 'complex64'.", - ) - - return parser.parse_args() - - -def run_bench(task, label): - start = timer() - result = task() - end = timer() - circuit_eval_time = end - start - print(f"Simulation time: {label} = {circuit_eval_time}s") - - return result - - if __name__ == "__main__": main(parser()) From 5bfa1d9f5284ed8a8e089dd1ae235b30e0b3001a Mon Sep 17 00:00:00 2001 From: Liwei Yang Date: Wed, 19 Apr 2023 15:21:24 +0800 Subject: [PATCH 28/42] Remove unused imports --- src/qibotn/__main__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/qibotn/__main__.py b/src/qibotn/__main__.py index 052dfaaf..0476be5e 100644 --- a/src/qibotn/__main__.py +++ b/src/qibotn/__main__.py @@ -1,11 +1,6 @@ import argparse -from timeit import default_timer as timer import qibotn.quimb -from QiboCircuitConvertor import QiboCircuitToEinsum -from cuquantum import contract -import cupy as cp -from qibo.models import QFT def parser(): From 4d36afb9efde1478a0f274670ed918dd77578bf4 Mon Sep 17 00:00:00 2001 From: Liwei Yang Date: Wed, 19 Apr 2023 16:11:14 +0800 Subject: [PATCH 29/42] Expose the precision dtype to the caller so that users can specify the precision for testing --- tests/test_cuquantum_cutensor_backend.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index 74cf2e12..e438b240 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -23,7 +23,14 @@ def time(func): @pytest.mark.gpu @pytest.mark.parametrize("nqubits", [1, 2, 5, 10]) -def test_eval(nqubits: int): +def test_eval(nqubits: int, dtype="complex128"): + """Evaluate QASM with cuQuantum. + + Args: + nqubits (int): Total number of qubits in the system. + dtype (str): The data type for precision, 'complex64' for single, + 'complex128' for double. + """ import qibotn.cutn # Test qibo @@ -33,8 +40,7 @@ def test_eval(nqubits: int): lambda: qibo_qft(nqubits, swaps=True)) # Test Cuquantum - data_type = "complex128" - cutn_time, result_tn = time(lambda: qibotn.cutn.eval(qibo_circ, data_type)) + cutn_time, result_tn = time(lambda: qibotn.cutn.eval(qibo_circ, dtype)) assert 1e-2 * qibo_time < cutn_time < 1e2 * qibo_time assert np.allclose( From e517b4fe7c6a9d15ed03e5e8b55c6b17ba625b03 Mon Sep 17 00:00:00 2001 From: Liwei Yang Date: Wed, 19 Apr 2023 16:23:59 +0800 Subject: [PATCH 30/42] Avoid flatten() so as to keep the shape information of contraction results --- src/qibotn/cutn.py | 2 +- tests/test_cuquantum_cutensor_backend.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qibotn/cutn.py b/src/qibotn/cutn.py index 985d7bbc..36f78666 100644 --- a/src/qibotn/cutn.py +++ b/src/qibotn/cutn.py @@ -7,4 +7,4 @@ def eval(qibo_circ, datatype): myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) operands_expression = myconvertor.state_vector() results = contract(*operands_expression) - return results.flatten() + return results diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index e438b240..e7f28043 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -40,7 +40,8 @@ def test_eval(nqubits: int, dtype="complex128"): lambda: qibo_qft(nqubits, swaps=True)) # Test Cuquantum - cutn_time, result_tn = time(lambda: qibotn.cutn.eval(qibo_circ, dtype)) + cutn_time, result_tn = time( + lambda: qibotn.cutn.eval(qibo_circ, dtype).flatten()) assert 1e-2 * qibo_time < cutn_time < 1e2 * qibo_time assert np.allclose( From e849e6926bbc58e2d7f8d5243256ea9519a3b467 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Wed, 19 Apr 2023 16:28:05 +0800 Subject: [PATCH 31/42] Added comments and refactor codes --- src/qibotn/QiboCircuitConvertor.py | 39 ++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index dcbf3e7c..2ed3ce71 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -3,31 +3,50 @@ class QiboCircuitToEinsum: + """This class takes a quantum circuit defined in Qibo (i.e. a Circuit object) + and convert it to an equivalent Tensor Network (TN) representation that is formatted for + cuQuantum' contract method to compute the state vectors. + See document for detail: https://docs.nvidia.com/cuda/cuquantum/python/api/generated/cuquantum.contract.html + + When the class is constructed, it first process the circuit to an intermediate form by extracting the gate matrix + and grouping each gate with its corresponding qubit it is acting on to a list. + + It is then converted it to an equivalent TN expression following the Einstein + summation convention through the class function state_vector_operand(). + The output is to be used by cuQuantum's contract() for computation of the state vectors of the circuit. + """ + def __init__(self, circuit, dtype="complex128"): + def op_shape_from_qubits(nqubits): + """This function is to modify the shape of the tensor to the required format by cuQuantum + (qubit_states,) * input_output * qubits_involved + """ + return (2,) * 2 * nqubits + self.backend = cp self.dtype = getattr(self.backend, dtype) - self.input_tensor_counter = np.zeros((circuit.nqubits,)) self.gate_tensors = [] + gates_qubits = [] + for gate in circuit.queue: - for target in gate.target_qubits: - self.input_tensor_counter[target] += 1 - for control in gate.control_qubits: - self.input_tensor_counter[control] += 1 gate_qubits = gate.control_qubits + gate.target_qubits + gates_qubits.extend(gate_qubits) + # self.gate_tensors is to extract into a list the gate matrix together with the qubit id that it is acting on + # https://github.com/NVIDIA/cuQuantum/blob/6b6339358f859ea930907b79854b90b2db71ab92/python/cuquantum/cutensornet/_internal/circuit_parser_utils_cirq.py#L32 + required_shape = op_shape_from_qubits(len(gate_qubits)) self.gate_tensors.append( ( - cp.asarray(gate.matrix).reshape((2,) * 2 * len(gate_qubits)), + cp.asarray(gate.matrix).reshape(required_shape), gate_qubits, ) ) + # self.active_qubits is to identify qubits with at least 1 gate acting on it in the whole circuit. - self.active_qubits = [ - indx for indx, value in enumerate(self.input_tensor_counter) if value > 0 - ] + self.active_qubits = np.unique(gates_qubits) - def state_vector(self): + def state_vector_operands(self): input_tensor_count = len(self.active_qubits) input_operands = self._get_bitstring_tensors( From af199170717b2f694a8308c9d2192e28c82e94ed Mon Sep 17 00:00:00 2001 From: tankya2 Date: Wed, 19 Apr 2023 16:44:53 +0800 Subject: [PATCH 32/42] Updated function call --- src/qibotn/cutn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibotn/cutn.py b/src/qibotn/cutn.py index 36f78666..6deb078a 100644 --- a/src/qibotn/cutn.py +++ b/src/qibotn/cutn.py @@ -5,6 +5,6 @@ def eval(qibo_circ, datatype): myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) - operands_expression = myconvertor.state_vector() + operands_expression = myconvertor.state_vector_operands() results = contract(*operands_expression) return results From fae91374a0a2fa14f0a03208b315ca3bc422088c Mon Sep 17 00:00:00 2001 From: Liwei Yang Date: Wed, 19 Apr 2023 17:02:41 +0800 Subject: [PATCH 33/42] Minor update with black formatting --- src/qibotn/QiboCircuitConvertor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index 2ed3ce71..2d964237 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -70,7 +70,8 @@ def state_vector_operands(self): for key in qubits_frontier: out_list.append(qubits_frontier[key]) - operand_exp_interleave = [x for y in zip(operands, mode_labels) for x in y] + operand_exp_interleave = [x for y in zip( + operands, mode_labels) for x in y] operand_exp_interleave.append(out_list) return operand_exp_interleave From f36a7a75a9651072d77053aaa90a8077d436707e Mon Sep 17 00:00:00 2001 From: Liwei Yang Date: Wed, 19 Apr 2023 17:33:16 +0800 Subject: [PATCH 34/42] Minor typo fix --- src/qibotn/QiboCircuitConvertor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index 2d964237..347f99f8 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -12,7 +12,7 @@ class QiboCircuitToEinsum: and grouping each gate with its corresponding qubit it is acting on to a list. It is then converted it to an equivalent TN expression following the Einstein - summation convention through the class function state_vector_operand(). + summation convention through the class function state_vector_operands(). The output is to be used by cuQuantum's contract() for computation of the state vectors of the circuit. """ From 88710f54f3503c28ac97c0c13de19e72d161edbd Mon Sep 17 00:00:00 2001 From: Liwei Yang Date: Fri, 21 Apr 2023 10:42:20 +0800 Subject: [PATCH 35/42] Minor refactoring for conciseness --- src/qibotn/cutn.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/qibotn/cutn.py b/src/qibotn/cutn.py index 6deb078a..e6f3e8c3 100644 --- a/src/qibotn/cutn.py +++ b/src/qibotn/cutn.py @@ -5,6 +5,4 @@ def eval(qibo_circ, datatype): myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) - operands_expression = myconvertor.state_vector_operands() - results = contract(*operands_expression) - return results + return contract(*myconvertor.state_vector_operands()) From 00b6540ea621a61835e7d722a34d25ba3c89a2fa Mon Sep 17 00:00:00 2001 From: Liwei Yang Date: Fri, 21 Apr 2023 10:58:19 +0800 Subject: [PATCH 36/42] Minor inlining update --- src/qibotn/QiboCircuitConvertor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index 347f99f8..abd39057 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -21,7 +21,7 @@ def op_shape_from_qubits(nqubits): """This function is to modify the shape of the tensor to the required format by cuQuantum (qubit_states,) * input_output * qubits_involved """ - return (2,) * 2 * nqubits + return (2, 2) * nqubits self.backend = cp self.dtype = getattr(self.backend, dtype) From 9cc1dcdffb20ae074b0b56c4e12cc5c0b7290322 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 21 Apr 2023 11:11:06 +0800 Subject: [PATCH 37/42] Updated docstring to class --- src/qibotn/QiboCircuitConvertor.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index 347f99f8..694f545e 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -3,17 +3,17 @@ class QiboCircuitToEinsum: - """This class takes a quantum circuit defined in Qibo (i.e. a Circuit object) - and convert it to an equivalent Tensor Network (TN) representation that is formatted for - cuQuantum' contract method to compute the state vectors. - See document for detail: https://docs.nvidia.com/cuda/cuquantum/python/api/generated/cuquantum.contract.html + """Convert a circuit to Tensor Network(TN) representation. + The circuit is first processed to an intermediate form by grouping each gate + matrix with its corresponding qubit it is acting on to a list. It is then + converted it to an equivalent TN expression through the class function + state_vector_operands() following the Einstein summation convention in the + interleave format. - When the class is constructed, it first process the circuit to an intermediate form by extracting the gate matrix - and grouping each gate with its corresponding qubit it is acting on to a list. + See document for detail of the format: https://docs.nvidia.com/cuda/cuquantum/python/api/generated/cuquantum.contract.html - It is then converted it to an equivalent TN expression following the Einstein - summation convention through the class function state_vector_operands(). - The output is to be used by cuQuantum's contract() for computation of the state vectors of the circuit. + The output is to be used by cuQuantum's contract() for computation of the + state vectors of the circuit. """ def __init__(self, circuit, dtype="complex128"): @@ -70,8 +70,7 @@ def state_vector_operands(self): for key in qubits_frontier: out_list.append(qubits_frontier[key]) - operand_exp_interleave = [x for y in zip( - operands, mode_labels) for x in y] + operand_exp_interleave = [x for y in zip(operands, mode_labels) for x in y] operand_exp_interleave.append(out_list) return operand_exp_interleave From 3cb799ddf1a42cb949cd6f1b569f179aac753e41 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 21 Apr 2023 11:16:39 +0800 Subject: [PATCH 38/42] Lifted shape function to class scope --- src/qibotn/QiboCircuitConvertor.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index b77aefaf..5596c7dd 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -17,12 +17,6 @@ class QiboCircuitToEinsum: """ def __init__(self, circuit, dtype="complex128"): - def op_shape_from_qubits(nqubits): - """This function is to modify the shape of the tensor to the required format by cuQuantum - (qubit_states,) * input_output * qubits_involved - """ - return (2, 2) * nqubits - self.backend = cp self.dtype = getattr(self.backend, dtype) @@ -35,7 +29,7 @@ def op_shape_from_qubits(nqubits): # self.gate_tensors is to extract into a list the gate matrix together with the qubit id that it is acting on # https://github.com/NVIDIA/cuQuantum/blob/6b6339358f859ea930907b79854b90b2db71ab92/python/cuquantum/cutensornet/_internal/circuit_parser_utils_cirq.py#L32 - required_shape = op_shape_from_qubits(len(gate_qubits)) + required_shape = self.op_shape_from_qubits(len(gate_qubits)) self.gate_tensors.append( ( cp.asarray(gate.matrix).reshape(required_shape), @@ -108,3 +102,9 @@ def _parse_gates_to_mode_labels_operands( next_frontier += 1 mode_labels.append(output_mode_labels + input_mode_labels) return mode_labels, operands + + def op_shape_from_qubits(self, nqubits): + """This function is to modify the shape of the tensor to the required format by cuQuantum + (qubit_states,) * input_output * qubits_involved + """ + return (2, 2) * nqubits From 0eb186b43baa48c8202b1bd3cc4d8dcdef024ea4 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 21 Apr 2023 11:28:03 +0800 Subject: [PATCH 39/42] Modify docstring of op_shape_from_qubits --- src/qibotn/QiboCircuitConvertor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index 5596c7dd..18c79fd6 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -104,7 +104,7 @@ def _parse_gates_to_mode_labels_operands( return mode_labels, operands def op_shape_from_qubits(self, nqubits): - """This function is to modify the shape of the tensor to the required format by cuQuantum - (qubit_states,) * input_output * qubits_involved + """Modify tensor to cuQuantum shape + (qubit_states,input_output) * qubits_involved """ return (2, 2) * nqubits From fa11a0ffac23264fe1eaba3d20ccaee5d4c29c44 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 21 Apr 2023 16:20:49 +0800 Subject: [PATCH 40/42] Grouped codes to higher level functions --- src/qibotn/QiboCircuitConvertor.py | 74 +++++++++++++++--------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index 18c79fd6..353a0547 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -19,33 +19,13 @@ class QiboCircuitToEinsum: def __init__(self, circuit, dtype="complex128"): self.backend = cp self.dtype = getattr(self.backend, dtype) - - self.gate_tensors = [] - gates_qubits = [] - - for gate in circuit.queue: - gate_qubits = gate.control_qubits + gate.target_qubits - gates_qubits.extend(gate_qubits) - - # self.gate_tensors is to extract into a list the gate matrix together with the qubit id that it is acting on - # https://github.com/NVIDIA/cuQuantum/blob/6b6339358f859ea930907b79854b90b2db71ab92/python/cuquantum/cutensornet/_internal/circuit_parser_utils_cirq.py#L32 - required_shape = self.op_shape_from_qubits(len(gate_qubits)) - self.gate_tensors.append( - ( - cp.asarray(gate.matrix).reshape(required_shape), - gate_qubits, - ) - ) - - # self.active_qubits is to identify qubits with at least 1 gate acting on it in the whole circuit. - self.active_qubits = np.unique(gates_qubits) + self.init_basis_map(self.backend, dtype) + self.init_intermediate_circuit(circuit) def state_vector_operands(self): - input_tensor_count = len(self.active_qubits) + input_bitstring = "0" * len(self.active_qubits) - input_operands = self._get_bitstring_tensors( - "0" * input_tensor_count, self.dtype, backend=self.backend - ) + input_operands = self._get_bitstring_tensors(input_bitstring) ( mode_labels, @@ -69,21 +49,13 @@ def state_vector_operands(self): return operand_exp_interleave def _init_mode_labels_from_qubits(self, qubits): - frontier_dict = {} n = len(qubits) - for x in range(n): - frontier_dict[qubits[x]] = x - return [[i] for i in range(n)], frontier_dict, n - - def _get_bitstring_tensors(self, bitstring, dtype=np.complex128, backend=cp): - asarray = backend.asarray - state_0 = asarray([1, 0], dtype=dtype) - state_1 = asarray([0, 1], dtype=dtype) - - basis_map = {"0": state_0, "1": state_1} + frontier_dict = {qubits[x]: x for x in range(n)} + mode_labels = [[i] for i in range(n)] + return mode_labels, frontier_dict, n - operands = [basis_map[ibit] for ibit in bitstring] - return operands + def _get_bitstring_tensors(self, bitstring): + return [self.basis_map[ibit] for ibit in bitstring] def _parse_gates_to_mode_labels_operands( self, gates, qubits_frontier, next_frontier @@ -108,3 +80,31 @@ def op_shape_from_qubits(self, nqubits): (qubit_states,input_output) * qubits_involved """ return (2, 2) * nqubits + + def init_intermediate_circuit(self, circuit): + self.gate_tensors = [] + gates_qubits = [] + + for gate in circuit.queue: + gate_qubits = gate.control_qubits + gate.target_qubits + gates_qubits.extend(gate_qubits) + + # self.gate_tensors is to extract into a list the gate matrix together with the qubit id that it is acting on + # https://github.com/NVIDIA/cuQuantum/blob/6b6339358f859ea930907b79854b90b2db71ab92/python/cuquantum/cutensornet/_internal/circuit_parser_utils_cirq.py#L32 + required_shape = self.op_shape_from_qubits(len(gate_qubits)) + self.gate_tensors.append( + ( + cp.asarray(gate.matrix).reshape(required_shape), + gate_qubits, + ) + ) + + # self.active_qubits is to identify qubits with at least 1 gate acting on it in the whole circuit. + self.active_qubits = np.unique(gates_qubits) + + def init_basis_map(self, backend, dtype): + asarray = backend.asarray + state_0 = asarray([1, 0], dtype=dtype) + state_1 = asarray([0, 1], dtype=dtype) + + self.basis_map = {"0": state_0, "1": state_1} From 2113e8a4528a2403b237dc5c2996538420823f24 Mon Sep 17 00:00:00 2001 From: Liwei Yang Date: Fri, 21 Apr 2023 16:55:34 +0800 Subject: [PATCH 41/42] Minor summary update --- src/qibotn/QiboCircuitConvertor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index 353a0547..b0272c33 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -3,7 +3,7 @@ class QiboCircuitToEinsum: - """Convert a circuit to Tensor Network(TN) representation. + """Convert a circuit to a Tensor Network (TN) representation. The circuit is first processed to an intermediate form by grouping each gate matrix with its corresponding qubit it is acting on to a list. It is then converted it to an equivalent TN expression through the class function @@ -44,7 +44,8 @@ def state_vector_operands(self): for key in qubits_frontier: out_list.append(qubits_frontier[key]) - operand_exp_interleave = [x for y in zip(operands, mode_labels) for x in y] + operand_exp_interleave = [x for y in zip( + operands, mode_labels) for x in y] operand_exp_interleave.append(out_list) return operand_exp_interleave From 2c7fa5d6f117851aeef98cca1a7fd2fc6b26174f Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 21 Apr 2023 17:11:55 +0800 Subject: [PATCH 42/42] Updated frontier_dict to enumerate --- src/qibotn/QiboCircuitConvertor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index b0272c33..c30cfb64 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -51,7 +51,7 @@ def state_vector_operands(self): def _init_mode_labels_from_qubits(self, qubits): n = len(qubits) - frontier_dict = {qubits[x]: x for x in range(n)} + frontier_dict = {q: i for i, q in enumerate(qubits)} mode_labels = [[i] for i in range(n)] return mode_labels, frontier_dict, n