From 22a55281a5959d1dcf79c181a4f8b241d7f73344 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Thu, 4 Jul 2024 13:40:09 +0800 Subject: [PATCH 01/40] Fix bug --- src/qibotn/backends/cutensornet.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qibotn/backends/cutensornet.py b/src/qibotn/backends/cutensornet.py index 553fc51..4f1c409 100644 --- a/src/qibotn/backends/cutensornet.py +++ b/src/qibotn/backends/cutensornet.py @@ -14,8 +14,9 @@ class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover def __init__(self, runcard): super().__init__() + import cuquantum from cuquantum import cutensornet as cutn # pylint: disable=import-error - + if runcard is not None: self.MPI_enabled = runcard.get("MPI_enabled", False) self.NCCL_enabled = runcard.get("NCCL_enabled", False) From ef565eefc4fb97de65f297a0bf442c34f5f5d997 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Thu, 4 Jul 2024 13:41:10 +0800 Subject: [PATCH 02/40] Add configuration and free memory explicitly --- src/qibotn/eval.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 245aa5e..23624aa 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -325,25 +325,29 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample comm = MPI.COMM_WORLD rank = comm.Get_rank() size = comm.Get_size() - - device_id = rank % getDeviceCount() - - # Perform circuit conversion - myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) - - operands = myconvertor.expectation_operands( - pauli_string_gen(qibo_circ.nqubits, pauli_string_pattern) - ) - + # Assign the device for each process. device_id = rank % getDeviceCount() + cp.cuda.Device(device_id).use() + # Perform circuit conversion + if rank==0: + myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) + + operands = myconvertor.expectation_operands( + pauli_string_gen(qibo_circ.nqubits, pauli_string_pattern) + ) + else: + operands = None + + operands = comm.bcast(operands, root) + # Create network object. network = Network(*operands, options={"device_id": device_id}) # Compute the path on all ranks with 8 samples for hyperoptimization. Force slicing to enable parallel contraction. path, info = network.contract_path( - optimize={"samples": n_samples, "slicing": {"min_slices": max(32, size)}} + optimize={"samples": n_samples, "slicing": {"min_slices": max(32, size),"memory_model":cutn.MemoryModel.CUTENSOR}} ) # Select the best path from all ranks. @@ -371,6 +375,9 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample # Sum the partial contribution from each process on root. result = comm.reduce(sendobj=result, op=MPI.SUM, root=root) + + del network + mempool.free_all_blocks() return result, rank From a5640a9d4505f5149a798a9cc42c9448311cb0b9 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Thu, 4 Jul 2024 16:10:43 +0800 Subject: [PATCH 03/40] correct missing mempool initialization --- src/qibotn/eval.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 23624aa..5a012bb 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -320,6 +320,7 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample """ from cuquantum import Network from mpi4py import MPI # this line initializes MPI + import cuquantum.cutensornet as cutn root = 0 comm = MPI.COMM_WORLD @@ -329,6 +330,7 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample # Assign the device for each process. device_id = rank % getDeviceCount() cp.cuda.Device(device_id).use() + mempool = cp.get_default_memory_pool() # Perform circuit conversion if rank==0: From ff034eb3554ab07c0e2a6a845aa023e135c7e48e Mon Sep 17 00:00:00 2001 From: tankya2 Date: Mon, 29 Jul 2024 14:52:01 +0800 Subject: [PATCH 04/40] Update NCCL --- src/qibotn/eval.py | 102 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 6 deletions(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 5a012bb..93b6227 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -229,6 +229,7 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl from cupy.cuda import nccl from cuquantum import Network from mpi4py import MPI + import cuquantum.cutensornet as cutn root = 0 comm_mpi = MPI.COMM_WORLD @@ -238,6 +239,7 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl device_id = rank % getDeviceCount() cp.cuda.Device(device_id).use() + mempool = cp.get_default_memory_pool() # Set up the NCCL communicator. nccl_id = nccl.get_unique_id() if rank == root else None @@ -245,16 +247,22 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl comm_nccl = nccl.NcclCommunicator(size, nccl_id, rank) # Perform circuit conversion - myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) - operands = myconvertor.expectation_operands( - pauli_string_gen(qibo_circ.nqubits, pauli_string_pattern) - ) + if rank==0: + + myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) + operands = myconvertor.expectation_operands( + pauli_string_gen(qibo_circ.nqubits, pauli_string_pattern) + ) + else: + operands = None + + operands = comm_mpi.bcast(operands, root) network = Network(*operands) # Compute the path on all ranks with 8 samples for hyperoptimization. Force slicing to enable parallel contraction. path, info = network.contract_path( - optimize={"samples": n_samples, "slicing": {"min_slices": max(32, size)}} + optimize={"samples": n_samples, "slicing": {"min_slices": max(32, size),"memory_model":cutn.MemoryModel.CUTENSOR}} ) # Select the best path from all ranks. @@ -291,7 +299,10 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl root, stream_ptr, ) - + + del network + mempool.free_all_blocks() + return result, rank @@ -426,3 +437,82 @@ def pauli_string_gen(nqubits, pauli_string_pattern): char_to_add = pauli_string_pattern[i % len(pauli_string_pattern)] result += char_to_add return result + +def expectation_pauli_tn_MPI_pathfinding(qibo_circ, datatype, pauli_string_pattern, n_samples=8): + """Convert qibo circuit to tensornet (TN) format and perform contraction to + expectation of given Pauli string using multi node and multi GPU through + MPI. + + The conversion is performed by QiboCircuitToEinsum(), after which it + goes through 2 steps: pathfinder and execution. The + pauli_string_pattern is used to generate the pauli string + corresponding to the number of qubits of the system. The pathfinder + looks at user defined number of samples (n_samples) iteratively to + select the least costly contraction path. This is sped up with multi + thread. After pathfinding the optimal path is used in the actual + contraction to give an expectation value. + + Parameters: + qibo_circ: The quantum circuit object. + datatype (str): Either single ("complex64") or double (complex128) precision. + pauli_string_pattern(str): pauli string pattern. + n_samples(int): Number of samples for pathfinding. + + Returns: + Expectation of quantum circuit due to pauli string. + """ + from cuquantum import Network + from mpi4py import MPI # this line initializes MPI + import cuquantum.cutensornet as cutn + import time + import numpy as np + + root = 0 + comm = MPI.COMM_WORLD + rank = comm.Get_rank() + size = comm.Get_size() + + # Assign the device for each process. + device_id = rank % getDeviceCount() + cp.cuda.Device(device_id).use() + mempool = cp.get_default_memory_pool() + + # Perform circuit conversion + if rank==0: + myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) + + operands = myconvertor.expectation_operands( + pauli_string_gen(qibo_circ.nqubits, pauli_string_pattern) + ) + else: + operands = None + + operands = comm.bcast(operands, root) + + # Create network object. + network = Network(*operands, options={"device_id": device_id}) + start_time = time.time() + # Compute the path on all ranks with 8 samples for hyperoptimization. Force slicing to enable parallel contraction. + path, info = network.contract_path( + optimize={"samples": n_samples, "slicing": {"min_slices": max(32, size),"memory_model":cutn.MemoryModel.CUTENSOR}} + ) + end_time = time.time() + + # print("Andy rank",rank,"info",info, info.num_slices, info.opt_cost, info.largest_intermediate, end_time-start_time) + local_data = np.array([info.num_slices, info.opt_cost, info.largest_intermediate, end_time-start_time]) + + + # Initialize a list to store the gathered data on rank 0 + if rank == 0: + gathered_data = np.zeros((size, 4)) + + else: + gathered_data = None + + # Gather data from all ranks to rank 0 + comm.Gather(local_data, gathered_data, root=0) + # print("Andy rank",rank,"gathered data",gathered_data) + del network + mempool.free_all_blocks() + + return gathered_data, rank From 4cc59564cf08a8ff338c157f3e50df78756f8a76 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 4 Oct 2024 14:33:30 +0800 Subject: [PATCH 05/40] Update dense_vector_tn_MPI --- src/qibotn/eval.py | 148 ++++++++++++++++----------------------------- 1 file changed, 51 insertions(+), 97 deletions(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 93b6227..8c4bdff 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -64,6 +64,7 @@ def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): from cuquantum import Network from mpi4py import MPI + import cuquantum.cutensornet as cutn root = 0 comm = MPI.COMM_WORLD @@ -71,21 +72,31 @@ def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): size = comm.Get_size() device_id = rank % getDeviceCount() - + cp.cuda.Device(device_id).use() + mempool = cp.get_default_memory_pool() + # Perform circuit conversion - myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) + if rank == 0: + myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) - operands = myconvertor.state_vector_operands() + operands = myconvertor.state_vector_operands() + else: + operands = None - # Assign the device for each process. - device_id = rank % getDeviceCount() + operands = comm.bcast(operands, root) # Create network object. network = Network(*operands, options={"device_id": device_id}) # Compute the path on all ranks with 8 samples for hyperoptimization. Force slicing to enable parallel contraction. path, info = network.contract_path( - optimize={"samples": n_samples, "slicing": {"min_slices": max(32, size)}} + optimize={ + "samples": n_samples, + "slicing": { + "min_slices": max(32, size), + "memory_model": cutn.MemoryModel.CUTENSOR, + }, + } ) # Select the best path from all ranks. @@ -114,6 +125,9 @@ def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): # Sum the partial contribution from each process on root. result = comm.reduce(sendobj=result, op=MPI.SUM, root=root) + del network + mempool.free_all_blocks() + return result, rank @@ -139,6 +153,7 @@ def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): from cupy.cuda import nccl from cuquantum import Network from mpi4py import MPI + import cuquantum.cutensornet as cutn root = 0 comm_mpi = MPI.COMM_WORLD @@ -162,7 +177,13 @@ def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): # Compute the path on all ranks with 8 samples for hyperoptimization. Force slicing to enable parallel contraction. path, info = network.contract_path( - optimize={"samples": n_samples, "slicing": {"min_slices": max(32, size)}} + optimize={ + "samples": n_samples, + "slicing": { + "min_slices": max(32, size), + "memory_model": cutn.MemoryModel.CUTENSOR, + }, + } ) # Select the best path from all ranks. @@ -247,7 +268,7 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl comm_nccl = nccl.NcclCommunicator(size, nccl_id, rank) # Perform circuit conversion - if rank==0: + if rank == 0: myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) operands = myconvertor.expectation_operands( @@ -255,14 +276,20 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl ) else: operands = None - + operands = comm_mpi.bcast(operands, root) network = Network(*operands) # Compute the path on all ranks with 8 samples for hyperoptimization. Force slicing to enable parallel contraction. path, info = network.contract_path( - optimize={"samples": n_samples, "slicing": {"min_slices": max(32, size),"memory_model":cutn.MemoryModel.CUTENSOR}} + optimize={ + "samples": n_samples, + "slicing": { + "min_slices": max(32, size), + "memory_model": cutn.MemoryModel.CUTENSOR, + }, + } ) # Select the best path from all ranks. @@ -299,10 +326,10 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl root, stream_ptr, ) - + del network mempool.free_all_blocks() - + return result, rank @@ -337,14 +364,14 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample comm = MPI.COMM_WORLD rank = comm.Get_rank() size = comm.Get_size() - + # Assign the device for each process. device_id = rank % getDeviceCount() cp.cuda.Device(device_id).use() mempool = cp.get_default_memory_pool() # Perform circuit conversion - if rank==0: + if rank == 0: myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) operands = myconvertor.expectation_operands( @@ -352,15 +379,21 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample ) else: operands = None - + operands = comm.bcast(operands, root) - + # Create network object. network = Network(*operands, options={"device_id": device_id}) # Compute the path on all ranks with 8 samples for hyperoptimization. Force slicing to enable parallel contraction. path, info = network.contract_path( - optimize={"samples": n_samples, "slicing": {"min_slices": max(32, size),"memory_model":cutn.MemoryModel.CUTENSOR}} + optimize={ + "samples": n_samples, + "slicing": { + "min_slices": max(32, size), + "memory_model": cutn.MemoryModel.CUTENSOR, + }, + } ) # Select the best path from all ranks. @@ -388,7 +421,7 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample # Sum the partial contribution from each process on root. result = comm.reduce(sendobj=result, op=MPI.SUM, root=root) - + del network mempool.free_all_blocks() @@ -437,82 +470,3 @@ def pauli_string_gen(nqubits, pauli_string_pattern): char_to_add = pauli_string_pattern[i % len(pauli_string_pattern)] result += char_to_add return result - -def expectation_pauli_tn_MPI_pathfinding(qibo_circ, datatype, pauli_string_pattern, n_samples=8): - """Convert qibo circuit to tensornet (TN) format and perform contraction to - expectation of given Pauli string using multi node and multi GPU through - MPI. - - The conversion is performed by QiboCircuitToEinsum(), after which it - goes through 2 steps: pathfinder and execution. The - pauli_string_pattern is used to generate the pauli string - corresponding to the number of qubits of the system. The pathfinder - looks at user defined number of samples (n_samples) iteratively to - select the least costly contraction path. This is sped up with multi - thread. After pathfinding the optimal path is used in the actual - contraction to give an expectation value. - - Parameters: - qibo_circ: The quantum circuit object. - datatype (str): Either single ("complex64") or double (complex128) precision. - pauli_string_pattern(str): pauli string pattern. - n_samples(int): Number of samples for pathfinding. - - Returns: - Expectation of quantum circuit due to pauli string. - """ - from cuquantum import Network - from mpi4py import MPI # this line initializes MPI - import cuquantum.cutensornet as cutn - import time - import numpy as np - - root = 0 - comm = MPI.COMM_WORLD - rank = comm.Get_rank() - size = comm.Get_size() - - # Assign the device for each process. - device_id = rank % getDeviceCount() - cp.cuda.Device(device_id).use() - mempool = cp.get_default_memory_pool() - - # Perform circuit conversion - if rank==0: - myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) - - operands = myconvertor.expectation_operands( - pauli_string_gen(qibo_circ.nqubits, pauli_string_pattern) - ) - else: - operands = None - - operands = comm.bcast(operands, root) - - # Create network object. - network = Network(*operands, options={"device_id": device_id}) - start_time = time.time() - # Compute the path on all ranks with 8 samples for hyperoptimization. Force slicing to enable parallel contraction. - path, info = network.contract_path( - optimize={"samples": n_samples, "slicing": {"min_slices": max(32, size),"memory_model":cutn.MemoryModel.CUTENSOR}} - ) - end_time = time.time() - - # print("Andy rank",rank,"info",info, info.num_slices, info.opt_cost, info.largest_intermediate, end_time-start_time) - local_data = np.array([info.num_slices, info.opt_cost, info.largest_intermediate, end_time-start_time]) - - - # Initialize a list to store the gathered data on rank 0 - if rank == 0: - gathered_data = np.zeros((size, 4)) - - else: - gathered_data = None - - # Gather data from all ranks to rank 0 - comm.Gather(local_data, gathered_data, root=0) - # print("Andy rank",rank,"gathered data",gathered_data) - del network - mempool.free_all_blocks() - - return gathered_data, rank From 05cd1001caa9045cd93e0db7e4aa7b1fd852c48d Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 4 Oct 2024 14:38:39 +0800 Subject: [PATCH 06/40] Update dense vector tn nccl --- src/qibotn/eval.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 8c4bdff..2d7c441 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -74,7 +74,7 @@ def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): device_id = rank % getDeviceCount() cp.cuda.Device(device_id).use() mempool = cp.get_default_memory_pool() - + # Perform circuit conversion if rank == 0: myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) @@ -127,7 +127,7 @@ def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): del network mempool.free_all_blocks() - + return result, rank @@ -163,6 +163,7 @@ def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): device_id = rank % getDeviceCount() cp.cuda.Device(device_id).use() + mempool = cp.get_default_memory_pool() # Set up the NCCL communicator. nccl_id = nccl.get_unique_id() if rank == root else None @@ -172,6 +173,14 @@ def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): # Perform circuit conversion myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) operands = myconvertor.state_vector_operands() + # Perform circuit conversion + if rank == 0: + myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) + operands = myconvertor.state_vector_operands() + else: + operands = None + + operands = comm_mpi.bcast(operands, root) network = Network(*operands) @@ -221,6 +230,9 @@ def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): stream_ptr, ) + del network + mempool.free_all_blocks() + return result, rank From dda17d3ba6b181798a7e75ab99a9e8bc5ce3e7a4 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 4 Oct 2024 15:13:44 +0800 Subject: [PATCH 07/40] Comment --- src/qibotn/eval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 2d7c441..b3d3669 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -51,7 +51,7 @@ def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): at user defined number of samples (n_samples) iteratively to select the least costly contraction path. This is sped up with multi thread. After pathfinding the optimal path is used in the actual - contraction to give a dense vector representation of the TN. + contraction to give a dense vector representation of the TN . Parameters: qibo_circ: The quantum circuit object. From c3a42f87608699076a541b62134a7f5dde35470b Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 4 Oct 2024 15:15:18 +0800 Subject: [PATCH 08/40] Format --- src/qibotn/eval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index b3d3669..2d7c441 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -51,7 +51,7 @@ def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): at user defined number of samples (n_samples) iteratively to select the least costly contraction path. This is sped up with multi thread. After pathfinding the optimal path is used in the actual - contraction to give a dense vector representation of the TN . + contraction to give a dense vector representation of the TN. Parameters: qibo_circ: The quantum circuit object. From 616446cecc6a4050c5ac0cc464e85dcd971ae70a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Oct 2024 07:26:39 +0000 Subject: [PATCH 09/40] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibotn/backends/cutensornet.py | 2 +- src/qibotn/eval.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/qibotn/backends/cutensornet.py b/src/qibotn/backends/cutensornet.py index 4f1c409..a813a7e 100644 --- a/src/qibotn/backends/cutensornet.py +++ b/src/qibotn/backends/cutensornet.py @@ -16,7 +16,7 @@ class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover super().__init__() import cuquantum from cuquantum import cutensornet as cutn # pylint: disable=import-error - + if runcard is not None: self.MPI_enabled = runcard.get("MPI_enabled", False) self.NCCL_enabled = runcard.get("NCCL_enabled", False) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 2d7c441..9d34275 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -62,9 +62,9 @@ def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): Dense vector of quantum circuit. """ + import cuquantum.cutensornet as cutn from cuquantum import Network from mpi4py import MPI - import cuquantum.cutensornet as cutn root = 0 comm = MPI.COMM_WORLD @@ -150,10 +150,10 @@ def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): Returns: Dense vector of quantum circuit. """ + import cuquantum.cutensornet as cutn from cupy.cuda import nccl from cuquantum import Network from mpi4py import MPI - import cuquantum.cutensornet as cutn root = 0 comm_mpi = MPI.COMM_WORLD @@ -259,10 +259,10 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl Returns: Expectation of quantum circuit due to pauli string. """ + import cuquantum.cutensornet as cutn from cupy.cuda import nccl from cuquantum import Network from mpi4py import MPI - import cuquantum.cutensornet as cutn root = 0 comm_mpi = MPI.COMM_WORLD @@ -368,9 +368,9 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample Returns: Expectation of quantum circuit due to pauli string. """ + import cuquantum.cutensornet as cutn from cuquantum import Network from mpi4py import MPI # this line initializes MPI - import cuquantum.cutensornet as cutn root = 0 comm = MPI.COMM_WORLD From 9890a45fb4fd28c4ba905b34f27e4cc3458028a4 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Wed, 30 Oct 2024 11:06:23 +0800 Subject: [PATCH 10/40] Remove import quantum --- src/qibotn/backends/cutensornet.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/qibotn/backends/cutensornet.py b/src/qibotn/backends/cutensornet.py index a813a7e..25d294c 100644 --- a/src/qibotn/backends/cutensornet.py +++ b/src/qibotn/backends/cutensornet.py @@ -14,7 +14,7 @@ class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover def __init__(self, runcard): super().__init__() - import cuquantum + from cuquantum import cudaDataType, ComputeType, __version__ # pylint: disable=import-error from cuquantum import cutensornet as cutn # pylint: disable=import-error if runcard is not None: @@ -61,22 +61,21 @@ class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover self.expectation_enabled = False self.name = "qibotn" - self.cuquantum = cuquantum self.cutn = cutn self.platform = "cutensornet" - self.versions["cuquantum"] = self.cuquantum.__version__ + self.versions["cuquantum"] = __version__ self.supports_multigpu = True self.handle = self.cutn.create() global CUDA_TYPES CUDA_TYPES = { "complex64": ( - self.cuquantum.cudaDataType.CUDA_C_32F, - self.cuquantum.ComputeType.COMPUTE_32F, + cudaDataType.CUDA_C_32F, + ComputeType.COMPUTE_32F, ), "complex128": ( - self.cuquantum.cudaDataType.CUDA_C_64F, - self.cuquantum.ComputeType.COMPUTE_64F, + cudaDataType.CUDA_C_64F, + ComputeType.COMPUTE_64F, ), } From 17717ae37a9c69e2216d5794feaa231fab644b47 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Wed, 30 Oct 2024 11:19:23 +0800 Subject: [PATCH 11/40] Remove duplication --- src/qibotn/eval.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 9d34275..9aa0136 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -170,9 +170,6 @@ def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): nccl_id = comm_mpi.bcast(nccl_id, root) comm_nccl = nccl.NcclCommunicator(size, nccl_id, rank) - # Perform circuit conversion - myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) - operands = myconvertor.state_vector_operands() # Perform circuit conversion if rank == 0: myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) From 9381ae6b885b43d6dd21532b533d51e6aab97787 Mon Sep 17 00:00:00 2001 From: yangliwei Date: Wed, 20 Nov 2024 17:49:34 +0800 Subject: [PATCH 12/40] Remove the unnecessary deletion because automatic deletion is implicitly already there at the function end --- src/qibotn/eval.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 9aa0136..d9d93c4 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -125,9 +125,6 @@ def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): # Sum the partial contribution from each process on root. result = comm.reduce(sendobj=result, op=MPI.SUM, root=root) - del network - mempool.free_all_blocks() - return result, rank @@ -227,9 +224,6 @@ def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): stream_ptr, ) - del network - mempool.free_all_blocks() - return result, rank @@ -336,9 +330,6 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl stream_ptr, ) - del network - mempool.free_all_blocks() - return result, rank @@ -431,9 +422,6 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample # Sum the partial contribution from each process on root. result = comm.reduce(sendobj=result, op=MPI.SUM, root=root) - del network - mempool.free_all_blocks() - return result, rank From 84ec5cfdfed5436a7efbc306cf88f1d2713a437c Mon Sep 17 00:00:00 2001 From: tankya2 Date: Wed, 18 Dec 2024 14:41:41 +0800 Subject: [PATCH 13/40] Remove redundant mem pool --- src/qibotn/eval.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index d9d93c4..ebef103 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -73,7 +73,6 @@ def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): device_id = rank % getDeviceCount() cp.cuda.Device(device_id).use() - mempool = cp.get_default_memory_pool() # Perform circuit conversion if rank == 0: @@ -160,7 +159,6 @@ def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): device_id = rank % getDeviceCount() cp.cuda.Device(device_id).use() - mempool = cp.get_default_memory_pool() # Set up the NCCL communicator. nccl_id = nccl.get_unique_id() if rank == root else None @@ -263,7 +261,6 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl device_id = rank % getDeviceCount() cp.cuda.Device(device_id).use() - mempool = cp.get_default_memory_pool() # Set up the NCCL communicator. nccl_id = nccl.get_unique_id() if rank == root else None @@ -368,7 +365,6 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample # Assign the device for each process. device_id = rank % getDeviceCount() cp.cuda.Device(device_id).use() - mempool = cp.get_default_memory_pool() # Perform circuit conversion if rank == 0: From 13d4c9c04f884fa4ac8387c24f7fcc85b7570485 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 06:06:30 +0000 Subject: [PATCH 14/40] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibotn/backends/cutensornet.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/qibotn/backends/cutensornet.py b/src/qibotn/backends/cutensornet.py index 25d294c..71419cd 100644 --- a/src/qibotn/backends/cutensornet.py +++ b/src/qibotn/backends/cutensornet.py @@ -14,7 +14,11 @@ class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover def __init__(self, runcard): super().__init__() - from cuquantum import cudaDataType, ComputeType, __version__ # pylint: disable=import-error + from cuquantum import ( # pylint: disable=import-error + ComputeType, + __version__, + cudaDataType, + ) from cuquantum import cutensornet as cutn # pylint: disable=import-error if runcard is not None: From ac396a35dbf2f1b31a0f8eef64ebf6a07e4e5dcd Mon Sep 17 00:00:00 2001 From: tankya2 Date: Tue, 18 Feb 2025 11:34:55 +0800 Subject: [PATCH 15/40] Refactor to reduce repeating codes --- src/qibotn/eval.py | 390 ++++++++++++++++----------------------------- 1 file changed, 139 insertions(+), 251 deletions(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index ebef103..618bdf7 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -6,40 +6,88 @@ from qibotn.circuit_convertor import QiboCircuitToEinsum from qibotn.circuit_to_mps import QiboCircuitToMPS from qibotn.mps_contraction_helper import MPSContractionHelper - -def dense_vector_tn(qibo_circ, datatype): - """Convert qibo circuit to tensornet (TN) format and perform contraction to - dense vector. - - Parameters: - qibo_circ: The quantum circuit object. - datatype (str): Either single ("complex64") or double (complex128) precision. - - Returns: - Dense vector of quantum circuit. - """ - myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) - return contract(*myconvertor.state_vector_operands()) +import cuquantum.cutensornet as cutn +from cuquantum import Network +from mpi4py import MPI +from cupy.cuda import nccl -def expectation_pauli_tn(qibo_circ, datatype, pauli_string_pattern): - """Convert qibo circuit to tensornet (TN) format and perform contraction to - expectation of given Pauli string. +def initialize_mpi(): + """Initialize MPI communication and device selection.""" + comm = MPI.COMM_WORLD + rank = comm.Get_rank() + size = comm.Get_size() + device_id = rank % getDeviceCount() + cp.cuda.Device(device_id).use() + return comm, rank, size, device_id - Parameters: - qibo_circ: The quantum circuit object. - datatype (str): Either single ("complex64") or double (complex128) precision. - pauli_string_pattern(str): pauli string pattern. - Returns: - Expectation of quantum circuit due to pauli string. - """ - myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) - return contract( - *myconvertor.expectation_operands( - pauli_string_gen(qibo_circ.nqubits, pauli_string_pattern) - ) +def initialize_nccl(comm_mpi, rank, size): + """Initialize NCCL communication.""" + nccl_id = nccl.get_unique_id() if rank == 0 else None + nccl_id = comm_mpi.bcast(nccl_id, root=0) + return nccl.NcclCommunicator(size, nccl_id, rank) + + +def get_operands(qibo_circ, datatype, rank, comm): + """Perform circuit conversion and broadcast operands.""" + if rank == 0: + myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) + operands = myconvertor.state_vector_operands() + else: + operands = None + return comm.bcast(operands, root=0) + + +def compute_optimal_path(network, n_samples, size, comm): + """Compute contraction path and broadcast optimal selection.""" + path, info = network.contract_path( + optimize={ + "samples": n_samples, + "slicing": { + "min_slices": max(32, size), + "memory_model": cutn.MemoryModel.CUTENSOR, + }, + } ) + opt_cost, sender = comm.allreduce( + sendobj=(info.opt_cost, comm.Get_rank()), op=MPI.MINLOC + ) + return comm.bcast(info, sender) + + +def compute_contraction(network, slices): + """Perform tensor contraction.""" + return network.contract(slices=slices) + + +def compute_slices(info, rank, size): + """Determine the slice range each process should compute.""" + num_slices = info.num_slices + chunk, extra = num_slices // size, num_slices % size + slice_begin = rank * chunk + min(rank, extra) + slice_end = ( + num_slices if rank == size - 1 else (rank + 1) * chunk + min(rank + 1, extra) + ) + return range(slice_begin, slice_end) + + +def reduce_result(result, comm, method="MPI", root=0): + """Reduce results across processes.""" + if method == "MPI": + return comm.reduce(sendobj=result, op=MPI.SUM, root=root) + elif method == "NCCL": + stream_ptr = cp.cuda.get_current_stream().ptr + comm.reduce( + result.data.ptr, + result.data.ptr, + result.size, + nccl.NCCL_FLOAT64, + nccl.NCCL_SUM, + root, + stream_ptr, + ) + return result def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): @@ -61,70 +109,16 @@ def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): Returns: Dense vector of quantum circuit. """ - - import cuquantum.cutensornet as cutn - from cuquantum import Network - from mpi4py import MPI - - root = 0 - comm = MPI.COMM_WORLD - rank = comm.Get_rank() - size = comm.Get_size() - - device_id = rank % getDeviceCount() - cp.cuda.Device(device_id).use() - - # Perform circuit conversion - if rank == 0: - myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) - - operands = myconvertor.state_vector_operands() - else: - operands = None - - operands = comm.bcast(operands, root) - - # Create network object. + comm, rank, size, device_id = initialize_mpi() + operands = get_operands(qibo_circ, datatype, rank, comm) network = Network(*operands, options={"device_id": device_id}) - - # Compute the path on all ranks with 8 samples for hyperoptimization. Force slicing to enable parallel contraction. - path, info = network.contract_path( - optimize={ - "samples": n_samples, - "slicing": { - "min_slices": max(32, size), - "memory_model": cutn.MemoryModel.CUTENSOR, - }, - } - ) - - # Select the best path from all ranks. - opt_cost, sender = comm.allreduce(sendobj=(info.opt_cost, rank), op=MPI.MINLOC) - - # Broadcast info from the sender to all other ranks. - info = comm.bcast(info, sender) - - # Set path and slices. + info = compute_optimal_path(network, n_samples, size, comm) path, info = network.contract_path( optimize={"path": info.path, "slicing": info.slices} ) - - # Calculate this process's share of the slices. - num_slices = info.num_slices - chunk, extra = num_slices // size, num_slices % size - slice_begin = rank * chunk + min(rank, extra) - slice_end = ( - num_slices if rank == size - 1 else (rank + 1) * chunk + min(rank + 1, extra) - ) - slices = range(slice_begin, slice_end) - - # Contract the group of slices the process is responsible for. - result = network.contract(slices=slices) - - # Sum the partial contribution from each process on root. - result = comm.reduce(sendobj=result, op=MPI.SUM, root=root) - - return result, rank + slices = compute_slices(info, rank, size) + result = compute_contraction(network, slices) + return reduce_result(result, comm, method="MPI"), rank def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): @@ -146,83 +140,32 @@ def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): Returns: Dense vector of quantum circuit. """ - import cuquantum.cutensornet as cutn - from cupy.cuda import nccl - from cuquantum import Network - from mpi4py import MPI - - root = 0 - comm_mpi = MPI.COMM_WORLD - rank = comm_mpi.Get_rank() - size = comm_mpi.Get_size() - - device_id = rank % getDeviceCount() - - cp.cuda.Device(device_id).use() - - # Set up the NCCL communicator. - nccl_id = nccl.get_unique_id() if rank == root else None - nccl_id = comm_mpi.bcast(nccl_id, root) - comm_nccl = nccl.NcclCommunicator(size, nccl_id, rank) - - # Perform circuit conversion - if rank == 0: - myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) - operands = myconvertor.state_vector_operands() - else: - operands = None - - operands = comm_mpi.bcast(operands, root) - + comm_mpi, rank, size, device_id = initialize_mpi() + comm_nccl = initialize_nccl(comm_mpi, rank, size) + operands = get_operands(qibo_circ, datatype, rank, comm_mpi) network = Network(*operands) - - # Compute the path on all ranks with 8 samples for hyperoptimization. Force slicing to enable parallel contraction. - path, info = network.contract_path( - optimize={ - "samples": n_samples, - "slicing": { - "min_slices": max(32, size), - "memory_model": cutn.MemoryModel.CUTENSOR, - }, - } - ) - - # Select the best path from all ranks. - opt_cost, sender = comm_mpi.allreduce(sendobj=(info.opt_cost, rank), op=MPI.MINLOC) - - # Broadcast info from the sender to all other ranks. - info = comm_mpi.bcast(info, sender) - - # Set path and slices. + info = compute_optimal_path(network, n_samples, size, comm_mpi) path, info = network.contract_path( optimize={"path": info.path, "slicing": info.slices} ) + slices = compute_slices(info, rank, size) + result = compute_contraction(network, slices) + return reduce_result(result, comm_nccl, method="NCCL"), rank - # Calculate this process's share of the slices. - num_slices = info.num_slices - chunk, extra = num_slices // size, num_slices % size - slice_begin = rank * chunk + min(rank, extra) - slice_end = ( - num_slices if rank == size - 1 else (rank + 1) * chunk + min(rank + 1, extra) - ) - slices = range(slice_begin, slice_end) - # Contract the group of slices the process is responsible for. - result = network.contract(slices=slices) +def dense_vector_tn(qibo_circ, datatype): + """Convert qibo circuit to tensornet (TN) format and perform contraction to + dense vector. - # Sum the partial contribution from each process on root. - stream_ptr = cp.cuda.get_current_stream().ptr - comm_nccl.reduce( - result.data.ptr, - result.data.ptr, - result.size, - nccl.NCCL_FLOAT64, - nccl.NCCL_SUM, - root, - stream_ptr, - ) + Parameters: + qibo_circ: The quantum circuit object. + datatype (str): Either single ("complex64") or double (complex128) precision. - return result, rank + Returns: + Dense vector of quantum circuit. + """ + myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) + return contract(*myconvertor.state_vector_operands()) def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_samples=8): @@ -248,28 +191,13 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl Returns: Expectation of quantum circuit due to pauli string. """ - import cuquantum.cutensornet as cutn - from cupy.cuda import nccl - from cuquantum import Network - from mpi4py import MPI - root = 0 - comm_mpi = MPI.COMM_WORLD - rank = comm_mpi.Get_rank() - size = comm_mpi.Get_size() + comm_mpi, rank, size, device_id = initialize_mpi() - device_id = rank % getDeviceCount() - - cp.cuda.Device(device_id).use() - - # Set up the NCCL communicator. - nccl_id = nccl.get_unique_id() if rank == root else None - nccl_id = comm_mpi.bcast(nccl_id, root) - comm_nccl = nccl.NcclCommunicator(size, nccl_id, rank) + comm_nccl = initialize_nccl(comm_mpi, rank, size) # Perform circuit conversion if rank == 0: - myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) operands = myconvertor.expectation_operands( pauli_string_gen(qibo_circ.nqubits, pauli_string_pattern) @@ -277,55 +205,25 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl else: operands = None - operands = comm_mpi.bcast(operands, root) + operands = comm_mpi.bcast(operands, root=0) network = Network(*operands) # Compute the path on all ranks with 8 samples for hyperoptimization. Force slicing to enable parallel contraction. - path, info = network.contract_path( - optimize={ - "samples": n_samples, - "slicing": { - "min_slices": max(32, size), - "memory_model": cutn.MemoryModel.CUTENSOR, - }, - } - ) + info = compute_optimal_path(network, n_samples, size, comm_mpi) - # Select the best path from all ranks. - opt_cost, sender = comm_mpi.allreduce(sendobj=(info.opt_cost, rank), op=MPI.MINLOC) - - # Broadcast info from the sender to all other ranks. - info = comm_mpi.bcast(info, sender) - - # Set path and slices. + # Recompute path with the selected optimal settings path, info = network.contract_path( optimize={"path": info.path, "slicing": info.slices} ) - # Calculate this process's share of the slices. - num_slices = info.num_slices - chunk, extra = num_slices // size, num_slices % size - slice_begin = rank * chunk + min(rank, extra) - slice_end = ( - num_slices if rank == size - 1 else (rank + 1) * chunk + min(rank + 1, extra) - ) - slices = range(slice_begin, slice_end) + slices = compute_slices(info, rank, size) # Contract the group of slices the process is responsible for. - result = network.contract(slices=slices) + result = compute_contraction(network, slices) # Sum the partial contribution from each process on root. - stream_ptr = cp.cuda.get_current_stream().ptr - comm_nccl.reduce( - result.data.ptr, - result.data.ptr, - result.size, - nccl.NCCL_FLOAT64, - nccl.NCCL_SUM, - root, - stream_ptr, - ) + result = reduce_result(result, comm_nccl, method="NCCL", root=0) return result, rank @@ -353,18 +251,8 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample Returns: Expectation of quantum circuit due to pauli string. """ - import cuquantum.cutensornet as cutn - from cuquantum import Network - from mpi4py import MPI # this line initializes MPI - - root = 0 - comm = MPI.COMM_WORLD - rank = comm.Get_rank() - size = comm.Get_size() - - # Assign the device for each process. - device_id = rank % getDeviceCount() - cp.cuda.Device(device_id).use() + # Initialize MPI and device + comm, rank, size, device_id = initialize_mpi() # Perform circuit conversion if rank == 0: @@ -376,51 +264,51 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample else: operands = None - operands = comm.bcast(operands, root) + operands = comm.bcast(operands, root=0) # Create network object. network = Network(*operands, options={"device_id": device_id}) - # Compute the path on all ranks with 8 samples for hyperoptimization. Force slicing to enable parallel contraction. - path, info = network.contract_path( - optimize={ - "samples": n_samples, - "slicing": { - "min_slices": max(32, size), - "memory_model": cutn.MemoryModel.CUTENSOR, - }, - } - ) - - # Select the best path from all ranks. - opt_cost, sender = comm.allreduce(sendobj=(info.opt_cost, rank), op=MPI.MINLOC) - - # Broadcast info from the sender to all other ranks. - info = comm.bcast(info, sender) + # Compute optimal contraction path + info = compute_optimal_path(network, n_samples, size, comm) # Set path and slices. path, info = network.contract_path( optimize={"path": info.path, "slicing": info.slices} ) - # Calculate this process's share of the slices. - num_slices = info.num_slices - chunk, extra = num_slices // size, num_slices % size - slice_begin = rank * chunk + min(rank, extra) - slice_end = ( - num_slices if rank == size - 1 else (rank + 1) * chunk + min(rank + 1, extra) - ) - slices = range(slice_begin, slice_end) + # Compute slice range for each rank + slices = compute_slices(info, rank, size) - # Contract the group of slices the process is responsible for. - result = network.contract(slices=slices) + # Perform contraction + result = compute_contraction(network, slices) # Sum the partial contribution from each process on root. - result = comm.reduce(sendobj=result, op=MPI.SUM, root=root) + result = reduce_result(result, comm, method="MPI", root=0) return result, rank +def expectation_pauli_tn(qibo_circ, datatype, pauli_string_pattern): + """Convert qibo circuit to tensornet (TN) format and perform contraction to + expectation of given Pauli string. + + Parameters: + qibo_circ: The quantum circuit object. + datatype (str): Either single ("complex64") or double (complex128) precision. + pauli_string_pattern(str): pauli string pattern. + + Returns: + Expectation of quantum circuit due to pauli string. + """ + myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) + return contract( + *myconvertor.expectation_operands( + pauli_string_gen(qibo_circ.nqubits, pauli_string_pattern) + ) + ) + + def dense_vector_mps(qibo_circ, gate_algo, datatype): """Convert qibo circuit to matrix product state (MPS) format and perform contraction to dense vector. From 4b44b10d6bb0d3d1ea3224bfc1775f2d1dc1d8f7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Feb 2025 03:35:06 +0000 Subject: [PATCH 16/40] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibotn/eval.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 618bdf7..3a5ff49 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -1,16 +1,14 @@ import cupy as cp +import cuquantum.cutensornet as cutn +from cupy.cuda import nccl from cupy.cuda.runtime import getDeviceCount -from cuquantum import contract +from cuquantum import Network, contract +from mpi4py import MPI from qibotn.circuit_convertor import QiboCircuitToEinsum from qibotn.circuit_to_mps import QiboCircuitToMPS from qibotn.mps_contraction_helper import MPSContractionHelper -import cuquantum.cutensornet as cutn -from cuquantum import Network -from mpi4py import MPI -from cupy.cuda import nccl - def initialize_mpi(): """Initialize MPI communication and device selection.""" From 106dfcb50ed0975a5da1740d9cd15fa1e581dc2a Mon Sep 17 00:00:00 2001 From: tankya2 Date: Thu, 20 Feb 2025 16:37:03 +0800 Subject: [PATCH 17/40] Add test for expectation and updates --- tests/test_cuquantum_cutensor_backend.py | 47 +++++++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index c8f1e19..dffea58 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -1,11 +1,13 @@ from timeit import default_timer as timer -import config import cupy as cp import numpy as np import pytest import qibo from qibo.models import QFT +from qibo import Circuit, gates, hamiltonians, construct_backend +from qibo.symbols import X, Z +import math def qibo_qft(nqubits, swaps): @@ -22,6 +24,16 @@ def time(func): return time, res +def build_observable(nqubits): + """Helper function to construct a target observable.""" + hamiltonian_form = 0 + for i in range(nqubits): + hamiltonian_form += 0.5 * X(i % nqubits) * Z((i + 1) % nqubits) + + hamiltonian = hamiltonians.SymbolicHamiltonian(form=hamiltonian_form) + return hamiltonian, hamiltonian_form + + @pytest.mark.gpu @pytest.mark.parametrize("nqubits", [1, 2, 5, 10]) def test_eval(nqubits: int, dtype="complex128"): @@ -35,16 +47,19 @@ def test_eval(nqubits: int, dtype="complex128"): import qibotn.eval # Test qibo - qibo.set_backend(backend=config.qibo.backend, platform=config.qibo.platform) + # qibo.set_backend(backend=config.qibo.backend, platform=config.qibo.platform) + qibo.set_backend(backend="numpy") qibo_time, (qibo_circ, result_sv) = time(lambda: qibo_qft(nqubits, swaps=True)) + result_sv_cp = cp.asarray(result_sv) # Test Cuquantum cutn_time, result_tn = time( lambda: qibotn.eval.dense_vector_tn(qibo_circ, dtype).flatten() ) - assert 1e-2 * qibo_time < cutn_time < 1e2 * qibo_time - assert np.allclose(result_sv, result_tn), "Resulting dense vectors do not match" + print(f"State vector difference: {abs(result_tn - result_sv_cp).max():0.3e}") + + assert cp.allclose(result_sv_cp, result_tn), "Resulting dense vectors do not match" @pytest.mark.gpu @@ -60,7 +75,7 @@ def test_mps(nqubits: int, dtype="complex128"): import qibotn.eval # Test qibo - qibo.set_backend(backend=config.qibo.backend, platform=config.qibo.platform) + qibo.set_backend(backend="numpy") qibo_time, (circ_qibo, result_sv) = time(lambda: qibo_qft(nqubits, swaps=True)) @@ -81,4 +96,24 @@ def test_mps(nqubits: int, dtype="complex128"): print(f"State vector difference: {abs(result_tn - result_sv_cp).max():0.3e}") - assert cp.allclose(result_tn, result_sv_cp) + assert cp.allclose(result_tn, result_sv_cp), "Resulting dense vectors do not match" + + +@pytest.mark.gpu +@pytest.mark.parametrize("nqubits", [2, 5, 10]) +def test_expectation(nqubits: int, dtype="complex128"): + import qibotn.eval + + circ_qibo, state_vec_qibo = qibo_qft(nqubits, swaps=True) + ham, ham_form = build_observable(nqubits) + + numpy_backend = construct_backend("numpy") + exact_expval = numpy_backend.calculate_expectation_state( + hamiltonian=ham, + state=state_vec_qibo, + normalize=False, + ) + + tn_expval = qibotn.eval.expectation_tn(circ_qibo, dtype, ham).flatten() + + assert math.isclose(exact_expval.item(), tn_expval.real.get().item(), abs_tol=1e-7) From 4cb6edc0130e256a72f0c74cb9607034f8af0435 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Thu, 20 Feb 2025 16:37:55 +0800 Subject: [PATCH 18/40] Change to expectation calculation to accept hamiltonian object --- src/qibotn/backends/cutensornet.py | 45 +++-- src/qibotn/circuit_convertor.py | 12 +- src/qibotn/eval.py | 303 +++++++++++++++++++++-------- 3 files changed, 257 insertions(+), 103 deletions(-) diff --git a/src/qibotn/backends/cutensornet.py b/src/qibotn/backends/cutensornet.py index 71419cd..7696919 100644 --- a/src/qibotn/backends/cutensornet.py +++ b/src/qibotn/backends/cutensornet.py @@ -1,9 +1,10 @@ import numpy as np from qibo.backends import NumpyBackend from qibo.config import raise_error -from qibo.result import QuantumState +from qibotn.result import TensorNetworkResult from qibotn.backends.abstract import QibotnBackend +from qibo import hamiltonians CUDA_TYPES = {} @@ -28,15 +29,17 @@ class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover expectation_enabled_value = runcard.get("expectation_enabled") if expectation_enabled_value is True: self.expectation_enabled = True - self.pauli_string_pattern = "XXXZ" + self.observable = None elif expectation_enabled_value is False: self.expectation_enabled = False elif isinstance(expectation_enabled_value, dict): self.expectation_enabled = True - expectation_enabled_dict = runcard.get("expectation_enabled", {}) - self.pauli_string_pattern = expectation_enabled_dict.get( - "pauli_string_pattern", None - ) + self.observable = runcard.get("expectation_enabled", {}) + elif isinstance( + expectation_enabled_value, hamiltonians.SymbolicHamiltonian + ): + self.expectation_enabled = True + self.observable = expectation_enabled_value else: raise TypeError("expectation_enabled has an unexpected type") @@ -158,17 +161,15 @@ class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover and self.NCCL_enabled == False and self.expectation_enabled == True ): - state = eval.expectation_pauli_tn( - circuit, self.dtype, self.pauli_string_pattern - ) + state = eval.expectation_tn(circuit, self.dtype, self.observable) elif ( self.MPI_enabled == True and self.MPS_enabled == False and self.NCCL_enabled == False and self.expectation_enabled == True ): - state, rank = eval.expectation_pauli_tn_MPI( - circuit, self.dtype, self.pauli_string_pattern, 32 + state, rank = eval.expectation_tn_MPI( + circuit, self.dtype, self.observable, 32 ) if rank > 0: state = np.array(0) @@ -178,15 +179,27 @@ class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover and self.NCCL_enabled == True and self.expectation_enabled == True ): - state, rank = eval.expectation_pauli_tn_nccl( - circuit, self.dtype, self.pauli_string_pattern, 32 + state, rank = eval.expectation_tn_nccl( + circuit, self.dtype, self.observable, 32 ) if rank > 0: state = np.array(0) else: raise_error(NotImplementedError, "Compute type not supported.") - if return_array: - return state.flatten() + if self.expectation_enabled: + return state.flatten().real else: - return QuantumState(state.flatten()) + if return_array: + statevector = state.flatten() + else: + statevector = state + + return TensorNetworkResult( + nqubits=circuit.nqubits, + backend=self, + measures=None, + measured_probabilities=None, + prob_type=None, + statevector=statevector, + ) diff --git a/src/qibotn/circuit_convertor.py b/src/qibotn/circuit_convertor.py index 03e96fa..1c8b3ee 100644 --- a/src/qibotn/circuit_convertor.py +++ b/src/qibotn/circuit_convertor.py @@ -195,12 +195,12 @@ class QiboCircuitToEinsum: gates.append((operand, (qubit,))) return gates - def expectation_operands(self, pauli_string): + def expectation_operands(self, ham_gates): """Create the operands for pauli string expectation computation in the interleave format. Parameters: - pauli_string: A string representating the list of pauli gates. + ham_gates: A list of gates derived from Qibo hamiltonian object. Returns: Operands for the contraction in the interleave format. @@ -208,8 +208,6 @@ class QiboCircuitToEinsum: input_bitstring = "0" * self.circuit.nqubits input_operands = self._get_bitstring_tensors(input_bitstring) - pauli_string = dict(zip(range(self.circuit.nqubits), pauli_string)) - pauli_map = pauli_string ( mode_labels, @@ -228,11 +226,7 @@ class QiboCircuitToEinsum: next_frontier = max(qubits_frontier.values()) + 1 - pauli_gates = self.get_pauli_gates( - pauli_map, dtype=self.dtype, backend=self.backend - ) - - gates_inverse = pauli_gates + self.gate_tensors_inverse + gates_inverse = ham_gates + self.gate_tensors_inverse ( gate_mode_labels_inverse, diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 3a5ff49..0aae2ca 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -9,6 +9,154 @@ from qibotn.circuit_convertor import QiboCircuitToEinsum from qibotn.circuit_to_mps import QiboCircuitToMPS from qibotn.mps_contraction_helper import MPSContractionHelper +import cuquantum.cutensornet as cutn +from cuquantum import Network +from mpi4py import MPI +from cupy.cuda import nccl +from qibo import hamiltonians +from qibo.symbols import X, Y, Z, I + + +def check_observable(observable, circuit_nqubit): + """Checks the type of observable and returns the appropriate Hamiltonian.""" + if observable is None: + return build_observable(circuit_nqubit) + elif isinstance(observable, dict): + return create_hamiltonian_from_dict(observable, circuit_nqubit) + elif isinstance(observable, hamiltonians.SymbolicHamiltonian): + # TODO: check if the observable is compatible with the circuit + return observable + else: + raise TypeError("Invalid observable type.") + + +def build_observable(circuit_nqubit): + """Helper function to construct a target observable.""" + hamiltonian_form = 0 + for i in range(circuit_nqubit): + hamiltonian_form += 0.5 * X(i % circuit_nqubit) * Z((i + 1) % circuit_nqubit) + + print("Default hamiltonian: ", hamiltonian_form) + hamiltonian = hamiltonians.SymbolicHamiltonian(form=hamiltonian_form) + return hamiltonian + + +def create_hamiltonian_from_dict(data, circuit_nqubit): + """Create a Qibo SymbolicHamiltonian from a dictionary representation. + + Ensures that each Hamiltonian term explicitly acts on all circuit qubits + by adding identity (`I`) gates where needed. + + Args: + data (dict): Dictionary containing Hamiltonian terms. + circuit_nqubit (int): Total number of qubits in the quantum circuit. + + Returns: + hamiltonians.SymbolicHamiltonian: The constructed Hamiltonian. + """ + PAULI_GATES = {"X": X, "Y": Y, "Z": Z} + + terms = [] + + for term in data["terms"]: + coeff = term["coefficient"] + operators = term["operators"] # List of tuples like [("Z", 0), ("X", 1)] + + # Convert the operator list into a dictionary {qubit_index: gate} + operator_dict = {q: PAULI_GATES[g] for g, q in operators} + + # Build the full term ensuring all qubits are covered + full_term_expr = [ + operator_dict[q](q) if q in operator_dict else I(q) + for q in range(circuit_nqubit) + ] + + # Multiply all operators together to form a single term + term_expr = full_term_expr[0] + for op in full_term_expr[1:]: + term_expr *= op + + # Scale by the coefficient + final_term = coeff * term_expr + # print(f"Adding term: {final_term}") # Debugging output + terms.append(final_term) + + if not terms: + raise ValueError("No valid Hamiltonian terms were added.") + + # Combine all terms + hamiltonian_form = sum(terms) + # print(f"Hamiltonian Form After Summation: {hamiltonian_form}") + + return hamiltonians.SymbolicHamiltonian(hamiltonian_form) + + +def get_ham_gates(pauli_map, dtype="complex128", backend=cp): + """Populate the gates for all pauli operators. + + Parameters: + pauli_map: A dictionary mapping qubits to pauli operators. + dtype: Data type for the tensor operands. + backend: The package the tensor operands belong to. + + Returns: + A sequence of pauli gates. + """ + asarray = backend.asarray + pauli_i = asarray([[1, 0], [0, 1]], dtype=dtype) + pauli_x = asarray([[0, 1], [1, 0]], dtype=dtype) + pauli_y = asarray([[0, -1j], [1j, 0]], dtype=dtype) + pauli_z = asarray([[1, 0], [0, -1]], dtype=dtype) + + operand_map = {"I": pauli_i, "X": pauli_x, "Y": pauli_y, "Z": pauli_z} + gates = [] + for qubit, pauli_char, coeff in pauli_map: + operand = operand_map.get(pauli_char) + if operand is None: + raise ValueError("pauli string character must be one of I/X/Y/Z") + operand = coeff * operand + gates.append((operand, (qubit,))) + return gates + + +def extract_gates_and_qubits(hamiltonian): + """ + Extracts the gates and their corresponding qubits from a Qibo Hamiltonian. + + Parameters: + hamiltonian (qibo.hamiltonians.Hamiltonian or qibo.hamiltonians.SymbolicHamiltonian): + A Qibo Hamiltonian object. + + Returns: + list of tuples: [(coefficient, [(gate, qubit), ...]), ...] + - coefficient: The prefactor of the term. + - list of (gate, qubit): Each term's gates and the qubits they act on. + """ + extracted_terms = [] + + if isinstance(hamiltonian, hamiltonians.SymbolicHamiltonian): + for term in hamiltonian.terms: + coeff = term.coefficient # Extract coefficient + gate_qubit_list = [] + + # Extract gate and qubit information + for factor in term.factors: + gate_name = str(factor)[ + 0 + ] # Extract the gate type (X, Y, Z) from 'X0', 'Z1' + qubit = int(str(factor)[1:]) # Extract the qubit index + gate_qubit_list.append((qubit, gate_name, coeff)) + coeff = 1.0 + + extracted_terms.append(gate_qubit_list) + + else: + raise ValueError( + "Unsupported Hamiltonian type. Must be SymbolicHamiltonian or Hamiltonian." + ) + + return extracted_terms + def initialize_mpi(): """Initialize MPI communication and device selection.""" @@ -166,7 +314,7 @@ def dense_vector_tn(qibo_circ, datatype): return contract(*myconvertor.state_vector_operands()) -def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_samples=8): +def expectation_tn_nccl(qibo_circ, datatype, observable, n_samples=8): """Convert qibo circuit to tensornet (TN) format and perform contraction to expectation of given Pauli string using multi node and multi GPU through NCCL. @@ -194,39 +342,48 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl comm_nccl = initialize_nccl(comm_mpi, rank, size) - # Perform circuit conversion + observable = check_observable(observable, qibo_circ.nqubits) + + ham_gate_map = extract_gates_and_qubits(observable) + if rank == 0: myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) - operands = myconvertor.expectation_operands( - pauli_string_gen(qibo_circ.nqubits, pauli_string_pattern) + + exp = 0 + for each_ham in ham_gate_map: + ham_gates = get_ham_gates(each_ham) + # Perform circuit conversion + if rank == 0: + operands = myconvertor.expectation_operands(ham_gates) + else: + operands = None + + operands = comm_mpi.bcast(operands, root=0) + + network = Network(*operands) + + # Compute the path on all ranks with 8 samples for hyperoptimization. Force slicing to enable parallel contraction. + info = compute_optimal_path(network, n_samples, size, comm_mpi) + + # Recompute path with the selected optimal settings + path, info = network.contract_path( + optimize={"path": info.path, "slicing": info.slices} ) - else: - operands = None - operands = comm_mpi.bcast(operands, root=0) + slices = compute_slices(info, rank, size) - network = Network(*operands) + # Contract the group of slices the process is responsible for. + result = compute_contraction(network, slices) - # Compute the path on all ranks with 8 samples for hyperoptimization. Force slicing to enable parallel contraction. - info = compute_optimal_path(network, n_samples, size, comm_mpi) + # Sum the partial contribution from each process on root. + result = reduce_result(result, comm_nccl, method="NCCL", root=0) - # Recompute path with the selected optimal settings - path, info = network.contract_path( - optimize={"path": info.path, "slicing": info.slices} - ) + exp += result - slices = compute_slices(info, rank, size) - - # Contract the group of slices the process is responsible for. - result = compute_contraction(network, slices) - - # Sum the partial contribution from each process on root. - result = reduce_result(result, comm_nccl, method="NCCL", root=0) - - return result, rank + return exp, rank -def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_samples=8): +def expectation_tn_MPI(qibo_circ, datatype, observable, n_samples=8): """Convert qibo circuit to tensornet (TN) format and perform contraction to expectation of given Pauli string using multi node and multi GPU through MPI. @@ -252,42 +409,51 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample # Initialize MPI and device comm, rank, size, device_id = initialize_mpi() - # Perform circuit conversion + observable = check_observable(observable, qibo_circ.nqubits) + + ham_gate_map = extract_gates_and_qubits(observable) + if rank == 0: myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) + exp = 0 + for each_ham in ham_gate_map: + ham_gates = get_ham_gates(each_ham) + # Perform circuit conversion + # Perform circuit conversion + if rank == 0: + operands = myconvertor.expectation_operands(ham_gates) + else: + operands = None - operands = myconvertor.expectation_operands( - pauli_string_gen(qibo_circ.nqubits, pauli_string_pattern) + operands = comm.bcast(operands, root=0) + + # Create network object. + network = Network(*operands, options={"device_id": device_id}) + + # Compute optimal contraction path + info = compute_optimal_path(network, n_samples, size, comm) + + # Set path and slices. + path, info = network.contract_path( + optimize={"path": info.path, "slicing": info.slices} ) - else: - operands = None - operands = comm.bcast(operands, root=0) + # Compute slice range for each rank + slices = compute_slices(info, rank, size) - # Create network object. - network = Network(*operands, options={"device_id": device_id}) + # Perform contraction + result = compute_contraction(network, slices) - # Compute optimal contraction path - info = compute_optimal_path(network, n_samples, size, comm) + # Sum the partial contribution from each process on root. + result = reduce_result(result, comm, method="MPI", root=0) - # Set path and slices. - path, info = network.contract_path( - optimize={"path": info.path, "slicing": info.slices} - ) + if rank == 0: + exp += result - # Compute slice range for each rank - slices = compute_slices(info, rank, size) - - # Perform contraction - result = compute_contraction(network, slices) - - # Sum the partial contribution from each process on root. - result = reduce_result(result, comm, method="MPI", root=0) - - return result, rank + return exp, rank -def expectation_pauli_tn(qibo_circ, datatype, pauli_string_pattern): +def expectation_tn(qibo_circ, datatype, observable): """Convert qibo circuit to tensornet (TN) format and perform contraction to expectation of given Pauli string. @@ -300,11 +466,16 @@ def expectation_pauli_tn(qibo_circ, datatype, pauli_string_pattern): Expectation of quantum circuit due to pauli string. """ myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) - return contract( - *myconvertor.expectation_operands( - pauli_string_gen(qibo_circ.nqubits, pauli_string_pattern) - ) - ) + + observable = check_observable(observable, qibo_circ.nqubits) + + ham_gate_map = extract_gates_and_qubits(observable) + exp = 0 + for each_ham in ham_gate_map: + ham_gates = get_ham_gates(each_ham) + expectation_operands = myconvertor.expectation_operands(ham_gates) + exp += contract(*expectation_operands) + return exp def dense_vector_mps(qibo_circ, gate_algo, datatype): @@ -325,27 +496,3 @@ def dense_vector_mps(qibo_circ, gate_algo, datatype): return mps_helper.contract_state_vector( myconvertor.mps_tensors, {"handle": myconvertor.handle} ) - - -def pauli_string_gen(nqubits, pauli_string_pattern): - """Used internally to generate the string based on given pattern and number - of qubit. - - Parameters: - nqubits(int): Number of qubits of Quantum Circuit - pauli_string_pattern(str): Strings representing sequence of pauli gates. - - Returns: - String representation of the actual pauli string from the pattern. - - Example: pattern: "XZ", number of qubit: 7, output = XZXZXZX - """ - if nqubits <= 0: - return "Invalid input. N should be a positive integer." - - result = "" - - for i in range(nqubits): - char_to_add = pauli_string_pattern[i % len(pauli_string_pattern)] - result += char_to_add - return result From 872312d00b2014b6ff5fedad1d1668630d3c7729 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 20 Feb 2025 08:47:11 +0000 Subject: [PATCH 19/40] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibotn/backends/cutensornet.py | 4 ++-- src/qibotn/eval.py | 9 ++------- tests/test_cuquantum_cutensor_backend.py | 4 ++-- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/qibotn/backends/cutensornet.py b/src/qibotn/backends/cutensornet.py index 7696919..7dd1091 100644 --- a/src/qibotn/backends/cutensornet.py +++ b/src/qibotn/backends/cutensornet.py @@ -1,10 +1,10 @@ import numpy as np +from qibo import hamiltonians from qibo.backends import NumpyBackend from qibo.config import raise_error -from qibotn.result import TensorNetworkResult from qibotn.backends.abstract import QibotnBackend -from qibo import hamiltonians +from qibotn.result import TensorNetworkResult CUDA_TYPES = {} diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 0aae2ca..bb5b104 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -4,18 +4,13 @@ from cupy.cuda import nccl from cupy.cuda.runtime import getDeviceCount from cuquantum import Network, contract from mpi4py import MPI +from qibo import hamiltonians +from qibo.symbols import I, X, Y, Z from qibotn.circuit_convertor import QiboCircuitToEinsum from qibotn.circuit_to_mps import QiboCircuitToMPS from qibotn.mps_contraction_helper import MPSContractionHelper -import cuquantum.cutensornet as cutn -from cuquantum import Network -from mpi4py import MPI -from cupy.cuda import nccl -from qibo import hamiltonians -from qibo.symbols import X, Y, Z, I - def check_observable(observable, circuit_nqubit): """Checks the type of observable and returns the appropriate Hamiltonian.""" diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index dffea58..5130dd5 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -1,13 +1,13 @@ +import math from timeit import default_timer as timer import cupy as cp import numpy as np import pytest import qibo +from qibo import Circuit, construct_backend, gates, hamiltonians from qibo.models import QFT -from qibo import Circuit, gates, hamiltonians, construct_backend from qibo.symbols import X, Z -import math def qibo_qft(nqubits, swaps): From 791c5d20204ba0e83387243952b8ff9d9f88f9b8 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 22 Aug 2025 15:39:35 +0800 Subject: [PATCH 20/40] Temporary diasble qmatchatea as it is giving error --- tests/conftest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0a18bfa..75acd9c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,15 +9,18 @@ import pytest # backends to be tested # TODO: add cutensornet and quimb here as well -BACKENDS = ["qmatchatea"] +BACKENDS = ["cutensornet"] +# BACKENDS = ["qmatchatea"] def get_backend(backend_name): from qibotn.backends.qmatchatea import QMatchaTeaBackend + from qibotn.backends.cutensornet import CuTensorNet NAME2BACKEND = { "qmatchatea": QMatchaTeaBackend, + "cutensornet": CuTensorNet } return NAME2BACKEND[backend_name]() From 410a742cc35d8485a601784e18da496f9572a4b8 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 22 Aug 2025 15:41:03 +0800 Subject: [PATCH 21/40] Make runcard optional in init plus some refactoring --- src/qibotn/backends/cutensornet.py | 55 +++++------------------------- 1 file changed, 9 insertions(+), 46 deletions(-) diff --git a/src/qibotn/backends/cutensornet.py b/src/qibotn/backends/cutensornet.py index 7dd1091..ed1c7f2 100644 --- a/src/qibotn/backends/cutensornet.py +++ b/src/qibotn/backends/cutensornet.py @@ -6,22 +6,22 @@ from qibo.config import raise_error from qibotn.backends.abstract import QibotnBackend from qibotn.result import TensorNetworkResult -CUDA_TYPES = {} - class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover # CI does not test for GPU """Creates CuQuantum backend for QiboTN.""" - def __init__(self, runcard): + def __init__(self, runcard=None): super().__init__() - from cuquantum import ( # pylint: disable=import-error - ComputeType, - __version__, - cudaDataType, - ) - from cuquantum import cutensornet as cutn # pylint: disable=import-error + from cuquantum import __version__ # pylint: disable=import-error + self.name = "qibotn" + self.platform = "cutensornet" + self.versions["cuquantum"] = __version__ + self.supports_multigpu = True + self.configure_tn_simulation(runcard) + + def configure_tn_simulation(self, runcard): if runcard is not None: self.MPI_enabled = runcard.get("MPI_enabled", False) self.NCCL_enabled = runcard.get("NCCL_enabled", False) @@ -67,43 +67,6 @@ class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover self.NCCL_enabled = False self.expectation_enabled = False - self.name = "qibotn" - self.cutn = cutn - self.platform = "cutensornet" - self.versions["cuquantum"] = __version__ - self.supports_multigpu = True - self.handle = self.cutn.create() - - global CUDA_TYPES - CUDA_TYPES = { - "complex64": ( - cudaDataType.CUDA_C_32F, - ComputeType.COMPUTE_32F, - ), - "complex128": ( - cudaDataType.CUDA_C_64F, - ComputeType.COMPUTE_64F, - ), - } - - def __del__(self): - if hasattr(self, "cutn"): - self.cutn.destroy(self.handle) - - def cuda_type(self, dtype="complex64"): - """Get CUDA Type. - - Parameters: - dtype (str, optional): Either single ("complex64") or double (complex128) precision. Defaults to "complex64". - - Returns: - CUDA Type: tuple of cuquantum.cudaDataType and cuquantum.ComputeType - """ - if dtype in CUDA_TYPES: - return CUDA_TYPES[dtype] - else: - raise TypeError("Type can be either complex64 or complex128") - def execute_circuit( self, circuit, initial_state=None, nshots=None, return_array=False ): # pragma: no cover From 77c9cd9cd622876b3820cf0034da14bd8021f0f6 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 22 Aug 2025 15:42:04 +0800 Subject: [PATCH 22/40] Rewrite test functions into higher level --- tests/test_cuquantum_cutensor_backend.py | 167 ++++++++++++++++------- 1 file changed, 118 insertions(+), 49 deletions(-) diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index 5130dd5..d5b2ffb 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -1,11 +1,8 @@ import math -from timeit import default_timer as timer - import cupy as cp -import numpy as np import pytest import qibo -from qibo import Circuit, construct_backend, gates, hamiltonians +from qibo import construct_backend, hamiltonians from qibo.models import QFT from qibo.symbols import X, Z @@ -16,14 +13,6 @@ def qibo_qft(nqubits, swaps): return circ_qibo, state_vec -def time(func): - start = timer() - res = func() - end = timer() - time = end - start - return time, res - - def build_observable(nqubits): """Helper function to construct a target observable.""" hamiltonian_form = 0 @@ -34,35 +23,64 @@ def build_observable(nqubits): return hamiltonian, hamiltonian_form -@pytest.mark.gpu +def build_observable_dict(nqubits): + """Construct a target observable as a dictionary representation. + + Returns a dictionary suitable for `create_hamiltonian_from_dict`. + """ + terms = [] + + for i in range(nqubits): + term = { + "coefficient": 0.5, + "operators": [("X", i % nqubits), ("Z", (i + 1) % nqubits)], + } + terms.append(term) + + return {"terms": terms} + + @pytest.mark.parametrize("nqubits", [1, 2, 5, 10]) 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.eval - # Test qibo - # qibo.set_backend(backend=config.qibo.backend, platform=config.qibo.platform) qibo.set_backend(backend="numpy") - qibo_time, (qibo_circ, result_sv) = time(lambda: qibo_qft(nqubits, swaps=True)) + qibo_circ, result_sv = qibo_qft(nqubits, swaps=True) result_sv_cp = cp.asarray(result_sv) - # Test Cuquantum - cutn_time, result_tn = time( - lambda: qibotn.eval.dense_vector_tn(qibo_circ, dtype).flatten() + # Test cutensornet + backend = construct_backend(backend="qibotn", platform="cutensornet") + # Test 1: no computation settings specified. Use default. + result_tn = backend.execute_circuit(circuit=qibo_circ) + print( + f"State vector difference: {abs(result_tn.statevector.flatten() - result_sv_cp).max():0.3e}" ) + assert cp.allclose( + result_sv_cp, result_tn.statevector.flatten() + ), "Resulting dense vectors do not match" - print(f"State vector difference: {abs(result_tn - result_sv_cp).max():0.3e}") - - assert cp.allclose(result_sv_cp, result_tn), "Resulting dense vectors do not match" + # Test 2: Explicit computation settings specified (same as default). + computation_settings = { + "MPI_enabled": False, + "MPS_enabled": False, + "NCCL_enabled": False, + "expectation_enabled": False, + } + backend.configure_tn_simulation(computation_settings) + result_tn = backend.execute_circuit(circuit=qibo_circ) + print( + f"State vector difference: {abs(result_tn.statevector.flatten() - result_sv_cp).max():0.3e}" + ) + assert cp.allclose( + result_sv_cp, result_tn.statevector.flatten() + ), "Resulting dense vectors do not match" -@pytest.mark.gpu @pytest.mark.parametrize("nqubits", [2, 5, 10]) def test_mps(nqubits: int, dtype="complex128"): """Evaluate MPS with cuQuantum. @@ -72,41 +90,59 @@ def test_mps(nqubits: int, dtype="complex128"): dtype (str): The data type for precision, 'complex64' for single, 'complex128' for double. """ - import qibotn.eval # Test qibo qibo.set_backend(backend="numpy") - - qibo_time, (circ_qibo, result_sv) = time(lambda: qibo_qft(nqubits, swaps=True)) - + qibo_circ, result_sv = qibo_qft(nqubits, swaps=True) result_sv_cp = cp.asarray(result_sv) - # Test of MPS - gate_algo = { - "qr_method": False, - "svd_method": { - "partition": "UV", - "abs_cutoff": 1e-12, - }, + # Test cutensornet + backend = construct_backend(backend="qibotn", platform="cutensornet") + # Test 1: No MPS computation settings specified. Use default. + computation_settings_1 = { + "MPI_enabled": False, + "MPS_enabled": True, + "NCCL_enabled": False, + "expectation_enabled": False, } - - cutn_time, result_tn = time( - lambda: qibotn.eval.dense_vector_mps(circ_qibo, gate_algo, dtype).flatten() + backend.configure_tn_simulation(computation_settings_1) + result_tn = backend.execute_circuit(circuit=qibo_circ) + print( + f"State vector difference: {abs(result_tn.statevector.flatten() - result_sv_cp).max():0.3e}" ) + assert cp.allclose( + result_tn.statevector.flatten(), result_sv_cp + ), "Resulting dense vectors do not match" - print(f"State vector difference: {abs(result_tn - result_sv_cp).max():0.3e}") - - assert cp.allclose(result_tn, result_sv_cp), "Resulting dense vectors do not match" + # Test 2: Explicit MPS computation settings specified (same as default). + computation_settings_2 = { + "MPI_enabled": False, + "MPS_enabled": { + "qr_method": False, + "svd_method": { + "partition": "UV", + "abs_cutoff": 1e-12, + }, + }, + "NCCL_enabled": False, + "expectation_enabled": False, + } + backend.configure_tn_simulation(computation_settings_2) + result_tn = backend.execute_circuit(circuit=qibo_circ) + print( + f"State vector difference: {abs(result_tn.statevector.flatten() - result_sv_cp).max():0.3e}" + ) + assert cp.allclose( + result_tn.statevector.flatten(), result_sv_cp + ), "Resulting dense vectors do not match" -@pytest.mark.gpu @pytest.mark.parametrize("nqubits", [2, 5, 10]) def test_expectation(nqubits: int, dtype="complex128"): - import qibotn.eval - circ_qibo, state_vec_qibo = qibo_qft(nqubits, swaps=True) + # Test qibo + qibo_circ, state_vec_qibo = qibo_qft(nqubits, swaps=True) ham, ham_form = build_observable(nqubits) - numpy_backend = construct_backend("numpy") exact_expval = numpy_backend.calculate_expectation_state( hamiltonian=ham, @@ -114,6 +150,39 @@ def test_expectation(nqubits: int, dtype="complex128"): normalize=False, ) - tn_expval = qibotn.eval.expectation_tn(circ_qibo, dtype, ham).flatten() + # Test cutensornet + backend = construct_backend(backend="qibotn", platform="cutensornet") - assert math.isclose(exact_expval.item(), tn_expval.real.get().item(), abs_tol=1e-7) + # Test 1: No Hamilitonian computation settings specified. Use default. + computation_settings_1 = { + "MPI_enabled": False, + "MPS_enabled": False, + "NCCL_enabled": False, + "expectation_enabled": True, + } + backend.configure_tn_simulation(computation_settings_1) + result_tn = backend.execute_circuit(circuit=qibo_circ) + assert math.isclose(exact_expval.item(), result_tn.real.get().item(), abs_tol=1e-7) + + # Test 2: hamiltonians.SymbolicHamiltonian object in computation settings specified. + computation_settings_2 = { + "MPI_enabled": False, + "MPS_enabled": False, + "NCCL_enabled": False, + "expectation_enabled": ham, + } + backend.configure_tn_simulation(computation_settings_2) + result_tn = backend.execute_circuit(circuit=qibo_circ) + assert math.isclose(exact_expval.item(), result_tn.real.get().item(), abs_tol=1e-7) + + # Test 3: Dictionary object form of hamiltonian in computation settings specified. + ham_dict = build_observable_dict(nqubits) + computation_settings_3 = { + "MPI_enabled": False, + "MPS_enabled": False, + "NCCL_enabled": False, + "expectation_enabled": ham_dict, + } + backend.configure_tn_simulation(computation_settings_3) + result_tn = backend.execute_circuit(circuit=qibo_circ) + assert math.isclose(exact_expval.item(), result_tn.real.get().item(), abs_tol=1e-7) From cf0a539d3d52c09cf28d3d7ed17de1cf0b0d4263 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 07:42:19 +0000 Subject: [PATCH 23/40] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/conftest.py | 7 ++----- tests/test_cuquantum_cutensor_backend.py | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 75acd9c..c5e9ed4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,13 +15,10 @@ BACKENDS = ["cutensornet"] def get_backend(backend_name): - from qibotn.backends.qmatchatea import QMatchaTeaBackend from qibotn.backends.cutensornet import CuTensorNet + from qibotn.backends.qmatchatea import QMatchaTeaBackend - NAME2BACKEND = { - "qmatchatea": QMatchaTeaBackend, - "cutensornet": CuTensorNet - } + NAME2BACKEND = {"qmatchatea": QMatchaTeaBackend, "cutensornet": CuTensorNet} return NAME2BACKEND[backend_name]() diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index d5b2ffb..01d8d6e 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -1,4 +1,5 @@ import math + import cupy as cp import pytest import qibo From 39dad0fd88495bd3db007e9f5fd358b9c4f9e660 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Tue, 26 Aug 2025 17:10:39 +0800 Subject: [PATCH 24/40] Fix bug on NCCL reduce. Need to change size to 2x for complex128 dtype --- src/qibotn/eval.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index bb5b104..8f74770 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -219,11 +219,20 @@ def reduce_result(result, comm, method="MPI", root=0): return comm.reduce(sendobj=result, op=MPI.SUM, root=root) elif method == "NCCL": stream_ptr = cp.cuda.get_current_stream().ptr + if result.dtype == cp.complex128: + count = result.size * 2 # complex128 has 2 float64 numbers + nccl_type = nccl.NCCL_FLOAT64 + elif result.dtype == cp.complex64: + count = result.size * 2 # complex64 has 2 float32 numbers + nccl_type = nccl.NCCL_FLOAT32 + else: + raise TypeError(f"Unsupported dtype for NCCL reduce: {result.dtype}") + comm.reduce( result.data.ptr, result.data.ptr, - result.size, - nccl.NCCL_FLOAT64, + count, + nccl_type, nccl.NCCL_SUM, root, stream_ptr, From 65a04c32fa38e4c00e2b4785e8f2f4117d0d45ba Mon Sep 17 00:00:00 2001 From: tankya2 Date: Tue, 26 Aug 2025 17:11:08 +0800 Subject: [PATCH 25/40] Make rank class attribute --- src/qibotn/backends/cutensornet.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/qibotn/backends/cutensornet.py b/src/qibotn/backends/cutensornet.py index ed1c7f2..616cda9 100644 --- a/src/qibotn/backends/cutensornet.py +++ b/src/qibotn/backends/cutensornet.py @@ -22,6 +22,7 @@ class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover self.configure_tn_simulation(runcard) def configure_tn_simulation(self, runcard): + self.rank = None if runcard is not None: self.MPI_enabled = runcard.get("MPI_enabled", False) self.NCCL_enabled = runcard.get("NCCL_enabled", False) @@ -106,8 +107,8 @@ class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover and self.NCCL_enabled == False and self.expectation_enabled == False ): - state, rank = eval.dense_vector_tn_MPI(circuit, self.dtype, 32) - if rank > 0: + state, self.rank = eval.dense_vector_tn_MPI(circuit, self.dtype, 32) + if self.rank > 0: state = np.array(0) elif ( self.MPI_enabled == False @@ -115,8 +116,8 @@ class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover and self.NCCL_enabled == True and self.expectation_enabled == False ): - state, rank = eval.dense_vector_tn_nccl(circuit, self.dtype, 32) - if rank > 0: + state, self.rank = eval.dense_vector_tn_nccl(circuit, self.dtype, 32) + if self.rank > 0: state = np.array(0) elif ( self.MPI_enabled == False @@ -131,10 +132,10 @@ class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover and self.NCCL_enabled == False and self.expectation_enabled == True ): - state, rank = eval.expectation_tn_MPI( + state, self.rank = eval.expectation_tn_MPI( circuit, self.dtype, self.observable, 32 ) - if rank > 0: + if self.rank > 0: state = np.array(0) elif ( self.MPI_enabled == False @@ -142,10 +143,10 @@ class CuTensorNet(QibotnBackend, NumpyBackend): # pragma: no cover and self.NCCL_enabled == True and self.expectation_enabled == True ): - state, rank = eval.expectation_tn_nccl( + state, self.rank = eval.expectation_tn_nccl( circuit, self.dtype, self.observable, 32 ) - if rank > 0: + if self.rank > 0: state = np.array(0) else: raise_error(NotImplementedError, "Compute type not supported.") From f2f84e79fd7ce1207fbe897fa19f604fc4b15836 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 09:23:25 +0000 Subject: [PATCH 26/40] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibotn/eval.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 8f74770..0629c93 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -220,10 +220,10 @@ def reduce_result(result, comm, method="MPI", root=0): elif method == "NCCL": stream_ptr = cp.cuda.get_current_stream().ptr if result.dtype == cp.complex128: - count = result.size * 2 # complex128 has 2 float64 numbers + count = result.size * 2 # complex128 has 2 float64 numbers nccl_type = nccl.NCCL_FLOAT64 elif result.dtype == cp.complex64: - count = result.size * 2 # complex64 has 2 float32 numbers + count = result.size * 2 # complex64 has 2 float32 numbers nccl_type = nccl.NCCL_FLOAT32 else: raise TypeError(f"Unsupported dtype for NCCL reduce: {result.dtype}") From 304ae7a6a309c142f8cdc22bc8aac3357addb36c Mon Sep 17 00:00:00 2001 From: tankya2 Date: Tue, 26 Aug 2025 17:34:06 +0800 Subject: [PATCH 27/40] pytest for mpi function --- tests/test_cuquantum_cutensor_mpi_backend.py | 321 +++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 tests/test_cuquantum_cutensor_mpi_backend.py diff --git a/tests/test_cuquantum_cutensor_mpi_backend.py b/tests/test_cuquantum_cutensor_mpi_backend.py new file mode 100644 index 0000000..c6810f9 --- /dev/null +++ b/tests/test_cuquantum_cutensor_mpi_backend.py @@ -0,0 +1,321 @@ +# mpirun --allow-run-as-root -np 2 python -m pytest --with-mpi test_cuquantum_cutensor_backend_mpi.py + +import math + +import cupy as cp +import numpy as np +import pytest +import qibo +from qibo import construct_backend, hamiltonians +from qibo.models import QFT +from qibo.symbols import X, Z + + +def qibo_qft(nqubits, swaps): + circ_qibo = QFT(nqubits, swaps) + state_vec = circ_qibo().state(numpy=True) + return circ_qibo, state_vec + + +def build_observable(nqubits): + """Helper function to construct a target observable.""" + hamiltonian_form = 0 + for i in range(nqubits): + hamiltonian_form += 0.5 * X(i % nqubits) * Z((i + 1) % nqubits) + + hamiltonian = hamiltonians.SymbolicHamiltonian(form=hamiltonian_form) + return hamiltonian, hamiltonian_form + + +def build_observable_dict(nqubits): + """Construct a target observable as a dictionary representation. + + Returns a dictionary suitable for `create_hamiltonian_from_dict`. + """ + terms = [] + + for i in range(nqubits): + term = { + "coefficient": 0.5, + "operators": [("X", i % nqubits), ("Z", (i + 1) % nqubits)], + } + terms.append(term) + + return {"terms": terms} + + +@pytest.mark.mpi +@pytest.mark.parametrize("nqubits", [1, 2, 5, 7, 10]) +def test_eval_mpi(nqubits: int, dtype="complex128"): + """ + Args: + nqubits (int): Total number of qubits in the system. + dtype (str): The data type for precision, 'complex64' for single, + 'complex128' for double. + """ + # Test qibo + qibo.set_backend(backend="numpy") + qibo_circ, result_sv = qibo_qft(nqubits, swaps=True) + result_sv_cp = cp.asarray(result_sv) + + # Test cutensornet + backend = construct_backend(backend="qibotn", platform="cutensornet") + + # Test 1: Explicit computation settings specified (same as default). + computation_settings = { + "MPI_enabled": True, + "MPS_enabled": False, + "NCCL_enabled": False, + "expectation_enabled": False, + } + backend.configure_tn_simulation(computation_settings) + result_tn = backend.execute_circuit(circuit=qibo_circ) + result_tn_cp = cp.asarray(result_tn.statevector.flatten()) + + print(f"State vector difference: {abs(result_tn_cp - result_sv_cp).max():0.3e}") + print( + f"AndyRank {backend.rank} qibo {result_sv_cp} tn {result_tn_cp} State vector difference: {abs(result_tn_cp - result_sv_cp).max():0.3e}" + ) + + if backend.rank == 0: + + assert cp.allclose( + result_sv_cp, result_tn_cp + ), "Resulting dense vectors do not match" + else: + assert ( + isinstance(result_tn_cp, cp.ndarray) + and result_tn_cp.size == 1 + and result_tn_cp.item() == 0 + ), f"Rank {backend.rank}: result_tn_cp should be scalar/array with 0, got {result_tn_cp}" + + +@pytest.mark.mpi +@pytest.mark.parametrize("nqubits", [1, 2, 5, 7, 10]) +def test_expectation_mpi(nqubits: int, dtype="complex128"): + + # Test qibo + qibo_circ, state_vec_qibo = qibo_qft(nqubits, swaps=True) + ham, ham_form = build_observable(nqubits) + numpy_backend = construct_backend("numpy") + exact_expval = numpy_backend.calculate_expectation_state( + hamiltonian=ham, + state=state_vec_qibo, + normalize=False, + ) + + # Test cutensornet + backend = construct_backend(backend="qibotn", platform="cutensornet") + + # Test 1: No Hamilitonian computation settings specified. Use default. + computation_settings_1 = { + "MPI_enabled": True, + "MPS_enabled": False, + "NCCL_enabled": False, + "expectation_enabled": True, + } + backend.configure_tn_simulation(computation_settings_1) + result_tn = backend.execute_circuit(circuit=qibo_circ) + if backend.rank == 0: + # Compare numerical values + assert math.isclose( + exact_expval.item(), float(result_tn[0]), abs_tol=1e-7 + ), f"Rank {backend.rank}: mismatch, expected {exact_expval}, got {result_tn}" + + else: + # Rank > 0: must be hardcoded [0] (int) + assert ( + isinstance(result_tn, (np.ndarray, cp.ndarray)) + and result_tn.size == 1 + and np.issubdtype(result_tn.dtype, np.integer) + and result_tn.item() == 0 + ), f"Rank {backend.rank}: expected int array [0], got {result_tn}" + + # Test 2: hamiltonians.SymbolicHamiltonian object in computation settings specified. + computation_settings_2 = { + "MPI_enabled": True, + "MPS_enabled": False, + "NCCL_enabled": False, + "expectation_enabled": ham, + } + backend.configure_tn_simulation(computation_settings_2) + result_tn = backend.execute_circuit(circuit=qibo_circ) + if backend.rank == 0: + # Compare numerical values + assert math.isclose( + exact_expval.item(), float(result_tn[0]), abs_tol=1e-7 + ), f"Rank {backend.rank}: mismatch, expected {exact_expval}, got {result_tn}" + + else: + # Rank > 0: must be hardcoded [0] (int) + assert ( + isinstance(result_tn, (np.ndarray, cp.ndarray)) + and result_tn.size == 1 + and np.issubdtype(result_tn.dtype, np.integer) + and result_tn.item() == 0 + ), f"Rank {backend.rank}: expected int array [0], got {result_tn}" + + # Test 3: Dictionary object form of hamiltonian in computation settings specified. + ham_dict = build_observable_dict(nqubits) + computation_settings_3 = { + "MPI_enabled": True, + "MPS_enabled": False, + "NCCL_enabled": False, + "expectation_enabled": ham_dict, + } + backend.configure_tn_simulation(computation_settings_3) + result_tn = backend.execute_circuit(circuit=qibo_circ) + if backend.rank == 0: + # Compare numerical values + assert math.isclose( + exact_expval.item(), float(result_tn[0]), abs_tol=1e-7 + ), f"Rank {backend.rank}: mismatch, expected {exact_expval}, got {result_tn}" + + else: + # Rank > 0: must be hardcoded [0] (int) + assert ( + isinstance(result_tn, (np.ndarray, cp.ndarray)) + and result_tn.size == 1 + and np.issubdtype(result_tn.dtype, np.integer) + and result_tn.item() == 0 + ), f"Rank {backend.rank}: expected int array [0], got {result_tn}" + + +@pytest.mark.mpi +@pytest.mark.parametrize("nqubits", [1, 2, 5, 7, 10]) +def test_eval_nccl(nqubits: int, dtype="complex128"): + """ + Args: + nqubits (int): Total number of qubits in the system. + dtype (str): The data type for precision, 'complex64' for single, + 'complex128' for double. + """ + # Test qibo + qibo.set_backend(backend="numpy") + qibo_circ, result_sv = qibo_qft(nqubits, swaps=True) + result_sv_cp = cp.asarray(result_sv) + + # Test cutensornet + backend = construct_backend(backend="qibotn", platform="cutensornet") + + # Test 1: Explicit computation settings specified (same as default). + computation_settings = { + "MPI_enabled": False, + "MPS_enabled": False, + "NCCL_enabled": True, + "expectation_enabled": False, + } + backend.configure_tn_simulation(computation_settings) + result_tn = backend.execute_circuit(circuit=qibo_circ) + result_tn_cp = cp.asarray(result_tn.statevector.flatten()) + + # print( + # f"Rank {backend.rank} qibo {result_sv_cp} tn {result_sv_cp} State vector difference: {abs(result_tn_cp - result_sv_cp).max():0.3e}" + # ) + if backend.rank == 0: + print( + f"Rank {backend.rank} qibo {result_sv_cp} tn {result_tn_cp} State vector difference: {abs(result_tn_cp - result_sv_cp).max():0.3e}" + ) + assert cp.allclose( + result_sv_cp, result_tn_cp + ), "Resulting dense vectors do not match" + else: + print( + f"Rank {backend.rank} qibo {result_sv_cp} tn {result_tn_cp} State vector difference: {abs(result_tn_cp - result_sv_cp).max():0.3e}" + ) + assert ( + isinstance(result_tn_cp, cp.ndarray) + and result_tn_cp.size == 1 + and result_tn_cp.item() == 0 + ), f"Rank {backend.rank}: result_tn_cp should be scalar/array with 0, got {result_tn_cp}" + + +@pytest.mark.mpi +@pytest.mark.parametrize("nqubits", [1, 2, 5, 7, 10]) +def test_expectation_NCCL(nqubits: int, dtype="complex128"): + + # Test qibo + qibo_circ, state_vec_qibo = qibo_qft(nqubits, swaps=True) + ham, ham_form = build_observable(nqubits) + numpy_backend = construct_backend("numpy") + exact_expval = numpy_backend.calculate_expectation_state( + hamiltonian=ham, + state=state_vec_qibo, + normalize=False, + ) + + # Test cutensornet + backend = construct_backend(backend="qibotn", platform="cutensornet") + + # Test 1: No Hamilitonian computation settings specified. Use default. + computation_settings_1 = { + "MPI_enabled": False, + "MPS_enabled": False, + "NCCL_enabled": True, + "expectation_enabled": True, + } + backend.configure_tn_simulation(computation_settings_1) + result_tn = backend.execute_circuit(circuit=qibo_circ) + if backend.rank == 0: + # Compare numerical values + assert math.isclose( + exact_expval.item(), float(result_tn[0]), abs_tol=1e-7 + ), f"Rank {backend.rank}: mismatch, expected {exact_expval}, got {result_tn}" + + else: + # Rank > 0: must be hardcoded [0] (int) + assert ( + isinstance(result_tn, (np.ndarray, cp.ndarray)) + and result_tn.size == 1 + and np.issubdtype(result_tn.dtype, np.integer) + and result_tn.item() == 0 + ), f"Rank {backend.rank}: expected int array [0], got {result_tn}" + + # Test 2: hamiltonians.SymbolicHamiltonian object in computation settings specified. + computation_settings_2 = { + "MPI_enabled": False, + "MPS_enabled": False, + "NCCL_enabled": True, + "expectation_enabled": ham, + } + backend.configure_tn_simulation(computation_settings_2) + result_tn = backend.execute_circuit(circuit=qibo_circ) + if backend.rank == 0: + # Compare numerical values + assert math.isclose( + exact_expval.item(), float(result_tn[0]), abs_tol=1e-7 + ), f"Rank {backend.rank}: mismatch, expected {exact_expval}, got {result_tn}" + + else: + # Rank > 0: must be hardcoded [0] (int) + assert ( + isinstance(result_tn, (np.ndarray, cp.ndarray)) + and result_tn.size == 1 + and np.issubdtype(result_tn.dtype, np.integer) + and result_tn.item() == 0 + ), f"Rank {backend.rank}: expected int array [0], got {result_tn}" + + # Test 3: Dictionary object form of hamiltonian in computation settings specified. + ham_dict = build_observable_dict(nqubits) + computation_settings_3 = { + "MPI_enabled": False, + "MPS_enabled": False, + "NCCL_enabled": True, + "expectation_enabled": ham_dict, + } + backend.configure_tn_simulation(computation_settings_3) + result_tn = backend.execute_circuit(circuit=qibo_circ) + if backend.rank == 0: + # Compare numerical values + assert math.isclose( + exact_expval.item(), float(result_tn[0]), abs_tol=1e-7 + ), f"Rank {backend.rank}: mismatch, expected {exact_expval}, got {result_tn}" + + else: + # Rank > 0: must be hardcoded [0] (int) + assert ( + isinstance(result_tn, (np.ndarray, cp.ndarray)) + and result_tn.size == 1 + and np.issubdtype(result_tn.dtype, np.integer) + and result_tn.item() == 0 + ), f"Rank {backend.rank}: expected int array [0], got {result_tn}" From 947a2a158353cdf0798eba3c775782f394ee1874 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Tue, 26 Aug 2025 17:40:50 +0800 Subject: [PATCH 28/40] Remove debug print --- tests/test_cuquantum_cutensor_mpi_backend.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/test_cuquantum_cutensor_mpi_backend.py b/tests/test_cuquantum_cutensor_mpi_backend.py index c6810f9..f3dd1c6 100644 --- a/tests/test_cuquantum_cutensor_mpi_backend.py +++ b/tests/test_cuquantum_cutensor_mpi_backend.py @@ -73,9 +73,6 @@ def test_eval_mpi(nqubits: int, dtype="complex128"): result_tn_cp = cp.asarray(result_tn.statevector.flatten()) print(f"State vector difference: {abs(result_tn_cp - result_sv_cp).max():0.3e}") - print( - f"AndyRank {backend.rank} qibo {result_sv_cp} tn {result_tn_cp} State vector difference: {abs(result_tn_cp - result_sv_cp).max():0.3e}" - ) if backend.rank == 0: @@ -209,20 +206,11 @@ def test_eval_nccl(nqubits: int, dtype="complex128"): result_tn = backend.execute_circuit(circuit=qibo_circ) result_tn_cp = cp.asarray(result_tn.statevector.flatten()) - # print( - # f"Rank {backend.rank} qibo {result_sv_cp} tn {result_sv_cp} State vector difference: {abs(result_tn_cp - result_sv_cp).max():0.3e}" - # ) if backend.rank == 0: - print( - f"Rank {backend.rank} qibo {result_sv_cp} tn {result_tn_cp} State vector difference: {abs(result_tn_cp - result_sv_cp).max():0.3e}" - ) assert cp.allclose( result_sv_cp, result_tn_cp ), "Resulting dense vectors do not match" else: - print( - f"Rank {backend.rank} qibo {result_sv_cp} tn {result_tn_cp} State vector difference: {abs(result_tn_cp - result_sv_cp).max():0.3e}" - ) assert ( isinstance(result_tn_cp, cp.ndarray) and result_tn_cp.size == 1 From 58721ca9749e5c20881f8248bfa786fc1624def0 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Wed, 27 Aug 2025 11:08:16 +0800 Subject: [PATCH 29/40] Add pytest gpu mark --- tests/test_cuquantum_cutensor_backend.py | 4 ++-- tests/test_cuquantum_cutensor_mpi_backend.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index 01d8d6e..c492f9f 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -40,7 +40,7 @@ def build_observable_dict(nqubits): return {"terms": terms} - +@pytest.mark.gpu @pytest.mark.parametrize("nqubits", [1, 2, 5, 10]) def test_eval(nqubits: int, dtype="complex128"): """ @@ -81,7 +81,7 @@ def test_eval(nqubits: int, dtype="complex128"): result_sv_cp, result_tn.statevector.flatten() ), "Resulting dense vectors do not match" - +@pytest.mark.gpu @pytest.mark.parametrize("nqubits", [2, 5, 10]) def test_mps(nqubits: int, dtype="complex128"): """Evaluate MPS with cuQuantum. diff --git a/tests/test_cuquantum_cutensor_mpi_backend.py b/tests/test_cuquantum_cutensor_mpi_backend.py index f3dd1c6..4b2deef 100644 --- a/tests/test_cuquantum_cutensor_mpi_backend.py +++ b/tests/test_cuquantum_cutensor_mpi_backend.py @@ -1,4 +1,4 @@ -# mpirun --allow-run-as-root -np 2 python -m pytest --with-mpi test_cuquantum_cutensor_backend_mpi.py +# mpirun --allow-run-as-root -np 2 python -m pytest --with-mpi test_cuquantum_cutensor_mpi_backend.py import math @@ -43,7 +43,7 @@ def build_observable_dict(nqubits): return {"terms": terms} - +@pytest.mark.gpu @pytest.mark.mpi @pytest.mark.parametrize("nqubits", [1, 2, 5, 7, 10]) def test_eval_mpi(nqubits: int, dtype="complex128"): @@ -86,7 +86,7 @@ def test_eval_mpi(nqubits: int, dtype="complex128"): and result_tn_cp.item() == 0 ), f"Rank {backend.rank}: result_tn_cp should be scalar/array with 0, got {result_tn_cp}" - +@pytest.mark.gpu @pytest.mark.mpi @pytest.mark.parametrize("nqubits", [1, 2, 5, 7, 10]) def test_expectation_mpi(nqubits: int, dtype="complex128"): @@ -177,7 +177,7 @@ def test_expectation_mpi(nqubits: int, dtype="complex128"): and result_tn.item() == 0 ), f"Rank {backend.rank}: expected int array [0], got {result_tn}" - +@pytest.mark.gpu @pytest.mark.mpi @pytest.mark.parametrize("nqubits", [1, 2, 5, 7, 10]) def test_eval_nccl(nqubits: int, dtype="complex128"): @@ -217,7 +217,7 @@ def test_eval_nccl(nqubits: int, dtype="complex128"): and result_tn_cp.item() == 0 ), f"Rank {backend.rank}: result_tn_cp should be scalar/array with 0, got {result_tn_cp}" - +@pytest.mark.gpu @pytest.mark.mpi @pytest.mark.parametrize("nqubits", [1, 2, 5, 7, 10]) def test_expectation_NCCL(nqubits: int, dtype="complex128"): From b654e8177661af8f5b35214c15aff60058737ca7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 03:08:35 +0000 Subject: [PATCH 30/40] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_cuquantum_cutensor_backend.py | 2 ++ tests/test_cuquantum_cutensor_mpi_backend.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index c492f9f..79bd36c 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -40,6 +40,7 @@ def build_observable_dict(nqubits): return {"terms": terms} + @pytest.mark.gpu @pytest.mark.parametrize("nqubits", [1, 2, 5, 10]) def test_eval(nqubits: int, dtype="complex128"): @@ -81,6 +82,7 @@ def test_eval(nqubits: int, dtype="complex128"): result_sv_cp, result_tn.statevector.flatten() ), "Resulting dense vectors do not match" + @pytest.mark.gpu @pytest.mark.parametrize("nqubits", [2, 5, 10]) def test_mps(nqubits: int, dtype="complex128"): diff --git a/tests/test_cuquantum_cutensor_mpi_backend.py b/tests/test_cuquantum_cutensor_mpi_backend.py index 4b2deef..0fc86e8 100644 --- a/tests/test_cuquantum_cutensor_mpi_backend.py +++ b/tests/test_cuquantum_cutensor_mpi_backend.py @@ -43,6 +43,7 @@ def build_observable_dict(nqubits): return {"terms": terms} + @pytest.mark.gpu @pytest.mark.mpi @pytest.mark.parametrize("nqubits", [1, 2, 5, 7, 10]) @@ -86,6 +87,7 @@ def test_eval_mpi(nqubits: int, dtype="complex128"): and result_tn_cp.item() == 0 ), f"Rank {backend.rank}: result_tn_cp should be scalar/array with 0, got {result_tn_cp}" + @pytest.mark.gpu @pytest.mark.mpi @pytest.mark.parametrize("nqubits", [1, 2, 5, 7, 10]) @@ -177,6 +179,7 @@ def test_expectation_mpi(nqubits: int, dtype="complex128"): and result_tn.item() == 0 ), f"Rank {backend.rank}: expected int array [0], got {result_tn}" + @pytest.mark.gpu @pytest.mark.mpi @pytest.mark.parametrize("nqubits", [1, 2, 5, 7, 10]) @@ -217,6 +220,7 @@ def test_eval_nccl(nqubits: int, dtype="complex128"): and result_tn_cp.item() == 0 ), f"Rank {backend.rank}: result_tn_cp should be scalar/array with 0, got {result_tn_cp}" + @pytest.mark.gpu @pytest.mark.mpi @pytest.mark.parametrize("nqubits", [1, 2, 5, 7, 10]) From 6e164b57c5f66a3f6ad85afdab64318b9ccb22fb Mon Sep 17 00:00:00 2001 From: tankya2 Date: Wed, 3 Sep 2025 09:27:07 +0800 Subject: [PATCH 31/40] Remove debugging printout --- src/qibotn/eval.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 0629c93..8a0250b 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -73,7 +73,6 @@ def create_hamiltonian_from_dict(data, circuit_nqubit): # Scale by the coefficient final_term = coeff * term_expr - # print(f"Adding term: {final_term}") # Debugging output terms.append(final_term) if not terms: @@ -81,7 +80,6 @@ def create_hamiltonian_from_dict(data, circuit_nqubit): # Combine all terms hamiltonian_form = sum(terms) - # print(f"Hamiltonian Form After Summation: {hamiltonian_form}") return hamiltonians.SymbolicHamiltonian(hamiltonian_form) From 99fbabb9c8904af69b4d968b6153653964b3f63f Mon Sep 17 00:00:00 2001 From: tankya2 Date: Wed, 3 Sep 2025 10:06:39 +0800 Subject: [PATCH 32/40] Make subtest description more self-explanatory --- tests/test_cuquantum_cutensor_backend.py | 14 +++++++------- tests/test_cuquantum_cutensor_mpi_backend.py | 16 ++++++++-------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index 79bd36c..6344843 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -57,7 +57,7 @@ def test_eval(nqubits: int, dtype="complex128"): # Test cutensornet backend = construct_backend(backend="qibotn", platform="cutensornet") - # Test 1: no computation settings specified. Use default. + # Test with no settings specified. Default is dense vector calculation without MPI or NCCL. result_tn = backend.execute_circuit(circuit=qibo_circ) print( f"State vector difference: {abs(result_tn.statevector.flatten() - result_sv_cp).max():0.3e}" @@ -66,7 +66,7 @@ def test_eval(nqubits: int, dtype="complex128"): result_sv_cp, result_tn.statevector.flatten() ), "Resulting dense vectors do not match" - # Test 2: Explicit computation settings specified (same as default). + # Test with explicit settings specified. computation_settings = { "MPI_enabled": False, "MPS_enabled": False, @@ -101,7 +101,7 @@ def test_mps(nqubits: int, dtype="complex128"): # Test cutensornet backend = construct_backend(backend="qibotn", platform="cutensornet") - # Test 1: No MPS computation settings specified. Use default. + # Test with simple MPS settings specified using bool. Uses the default MPS parameters. computation_settings_1 = { "MPI_enabled": False, "MPS_enabled": True, @@ -117,7 +117,7 @@ def test_mps(nqubits: int, dtype="complex128"): result_tn.statevector.flatten(), result_sv_cp ), "Resulting dense vectors do not match" - # Test 2: Explicit MPS computation settings specified (same as default). + # Test with explicit MPS computation settings specified using Dict. Users able to specify parameters like qr_method etc. computation_settings_2 = { "MPI_enabled": False, "MPS_enabled": { @@ -156,7 +156,7 @@ def test_expectation(nqubits: int, dtype="complex128"): # Test cutensornet backend = construct_backend(backend="qibotn", platform="cutensornet") - # Test 1: No Hamilitonian computation settings specified. Use default. + # Test with simple settings using bool. Uses default Hamilitonian for expectation calculation. computation_settings_1 = { "MPI_enabled": False, "MPS_enabled": False, @@ -167,7 +167,7 @@ def test_expectation(nqubits: int, dtype="complex128"): result_tn = backend.execute_circuit(circuit=qibo_circ) assert math.isclose(exact_expval.item(), result_tn.real.get().item(), abs_tol=1e-7) - # Test 2: hamiltonians.SymbolicHamiltonian object in computation settings specified. + # Test with user defined hamiltonian using "hamiltonians.SymbolicHamiltonian" object. computation_settings_2 = { "MPI_enabled": False, "MPS_enabled": False, @@ -178,7 +178,7 @@ def test_expectation(nqubits: int, dtype="complex128"): result_tn = backend.execute_circuit(circuit=qibo_circ) assert math.isclose(exact_expval.item(), result_tn.real.get().item(), abs_tol=1e-7) - # Test 3: Dictionary object form of hamiltonian in computation settings specified. + # Test with user defined hamiltonian using Dictionary object form of hamiltonian. ham_dict = build_observable_dict(nqubits) computation_settings_3 = { "MPI_enabled": False, diff --git a/tests/test_cuquantum_cutensor_mpi_backend.py b/tests/test_cuquantum_cutensor_mpi_backend.py index 0fc86e8..14eb494 100644 --- a/tests/test_cuquantum_cutensor_mpi_backend.py +++ b/tests/test_cuquantum_cutensor_mpi_backend.py @@ -62,7 +62,7 @@ def test_eval_mpi(nqubits: int, dtype="complex128"): # Test cutensornet backend = construct_backend(backend="qibotn", platform="cutensornet") - # Test 1: Explicit computation settings specified (same as default). + # Test with explicit settings specified. computation_settings = { "MPI_enabled": True, "MPS_enabled": False, @@ -106,7 +106,7 @@ def test_expectation_mpi(nqubits: int, dtype="complex128"): # Test cutensornet backend = construct_backend(backend="qibotn", platform="cutensornet") - # Test 1: No Hamilitonian computation settings specified. Use default. + # Test with simple settings using bool. Uses default Hamilitonian for expectation calculation. computation_settings_1 = { "MPI_enabled": True, "MPS_enabled": False, @@ -130,7 +130,7 @@ def test_expectation_mpi(nqubits: int, dtype="complex128"): and result_tn.item() == 0 ), f"Rank {backend.rank}: expected int array [0], got {result_tn}" - # Test 2: hamiltonians.SymbolicHamiltonian object in computation settings specified. + # Test with user defined hamiltonian using "hamiltonians.SymbolicHamiltonian" object. computation_settings_2 = { "MPI_enabled": True, "MPS_enabled": False, @@ -154,7 +154,7 @@ def test_expectation_mpi(nqubits: int, dtype="complex128"): and result_tn.item() == 0 ), f"Rank {backend.rank}: expected int array [0], got {result_tn}" - # Test 3: Dictionary object form of hamiltonian in computation settings specified. + # Test with user defined hamiltonian using Dictionary object form of hamiltonian. ham_dict = build_observable_dict(nqubits) computation_settings_3 = { "MPI_enabled": True, @@ -198,7 +198,7 @@ def test_eval_nccl(nqubits: int, dtype="complex128"): # Test cutensornet backend = construct_backend(backend="qibotn", platform="cutensornet") - # Test 1: Explicit computation settings specified (same as default). + # Test with explicit settings specified. computation_settings = { "MPI_enabled": False, "MPS_enabled": False, @@ -239,7 +239,7 @@ def test_expectation_NCCL(nqubits: int, dtype="complex128"): # Test cutensornet backend = construct_backend(backend="qibotn", platform="cutensornet") - # Test 1: No Hamilitonian computation settings specified. Use default. + # Test with simple settings using bool. Uses default Hamilitonian for expectation calculation. computation_settings_1 = { "MPI_enabled": False, "MPS_enabled": False, @@ -263,7 +263,7 @@ def test_expectation_NCCL(nqubits: int, dtype="complex128"): and result_tn.item() == 0 ), f"Rank {backend.rank}: expected int array [0], got {result_tn}" - # Test 2: hamiltonians.SymbolicHamiltonian object in computation settings specified. + # Test with user defined hamiltonian using "hamiltonians.SymbolicHamiltonian" object. computation_settings_2 = { "MPI_enabled": False, "MPS_enabled": False, @@ -287,7 +287,7 @@ def test_expectation_NCCL(nqubits: int, dtype="complex128"): and result_tn.item() == 0 ), f"Rank {backend.rank}: expected int array [0], got {result_tn}" - # Test 3: Dictionary object form of hamiltonian in computation settings specified. + # Test with user defined hamiltonian using Dictionary object form of hamiltonian. ham_dict = build_observable_dict(nqubits) computation_settings_3 = { "MPI_enabled": False, From b72f6afe49f871f13e0f9f5435feb143232de0cd Mon Sep 17 00:00:00 2001 From: tankya2 Date: Wed, 3 Sep 2025 10:18:13 +0800 Subject: [PATCH 33/40] Make tolerance a variable --- tests/test_cuquantum_cutensor_backend.py | 7 ++++--- tests/test_cuquantum_cutensor_mpi_backend.py | 13 +++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index 6344843..19a81a8 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -7,6 +7,7 @@ from qibo import construct_backend, hamiltonians from qibo.models import QFT from qibo.symbols import X, Z +ABS_TOL = 1e-7 def qibo_qft(nqubits, swaps): circ_qibo = QFT(nqubits, swaps) @@ -165,7 +166,7 @@ def test_expectation(nqubits: int, dtype="complex128"): } backend.configure_tn_simulation(computation_settings_1) result_tn = backend.execute_circuit(circuit=qibo_circ) - assert math.isclose(exact_expval.item(), result_tn.real.get().item(), abs_tol=1e-7) + assert math.isclose(exact_expval.item(), result_tn.real.get().item(), abs_tol=ABS_TOL) # Test with user defined hamiltonian using "hamiltonians.SymbolicHamiltonian" object. computation_settings_2 = { @@ -176,7 +177,7 @@ def test_expectation(nqubits: int, dtype="complex128"): } backend.configure_tn_simulation(computation_settings_2) result_tn = backend.execute_circuit(circuit=qibo_circ) - assert math.isclose(exact_expval.item(), result_tn.real.get().item(), abs_tol=1e-7) + assert math.isclose(exact_expval.item(), result_tn.real.get().item(), abs_tol=ABS_TOL) # Test with user defined hamiltonian using Dictionary object form of hamiltonian. ham_dict = build_observable_dict(nqubits) @@ -188,4 +189,4 @@ def test_expectation(nqubits: int, dtype="complex128"): } backend.configure_tn_simulation(computation_settings_3) result_tn = backend.execute_circuit(circuit=qibo_circ) - assert math.isclose(exact_expval.item(), result_tn.real.get().item(), abs_tol=1e-7) + assert math.isclose(exact_expval.item(), result_tn.real.get().item(), abs_tol=ABS_TOL) diff --git a/tests/test_cuquantum_cutensor_mpi_backend.py b/tests/test_cuquantum_cutensor_mpi_backend.py index 14eb494..0e2fcef 100644 --- a/tests/test_cuquantum_cutensor_mpi_backend.py +++ b/tests/test_cuquantum_cutensor_mpi_backend.py @@ -10,6 +10,7 @@ from qibo import construct_backend, hamiltonians from qibo.models import QFT from qibo.symbols import X, Z +ABS_TOL = 1e-7 def qibo_qft(nqubits, swaps): circ_qibo = QFT(nqubits, swaps) @@ -118,7 +119,7 @@ def test_expectation_mpi(nqubits: int, dtype="complex128"): if backend.rank == 0: # Compare numerical values assert math.isclose( - exact_expval.item(), float(result_tn[0]), abs_tol=1e-7 + exact_expval.item(), float(result_tn[0]), abs_tol=ABS_TOL ), f"Rank {backend.rank}: mismatch, expected {exact_expval}, got {result_tn}" else: @@ -142,7 +143,7 @@ def test_expectation_mpi(nqubits: int, dtype="complex128"): if backend.rank == 0: # Compare numerical values assert math.isclose( - exact_expval.item(), float(result_tn[0]), abs_tol=1e-7 + exact_expval.item(), float(result_tn[0]), abs_tol=ABS_TOL ), f"Rank {backend.rank}: mismatch, expected {exact_expval}, got {result_tn}" else: @@ -167,7 +168,7 @@ def test_expectation_mpi(nqubits: int, dtype="complex128"): if backend.rank == 0: # Compare numerical values assert math.isclose( - exact_expval.item(), float(result_tn[0]), abs_tol=1e-7 + exact_expval.item(), float(result_tn[0]), abs_tol=ABS_TOL ), f"Rank {backend.rank}: mismatch, expected {exact_expval}, got {result_tn}" else: @@ -251,7 +252,7 @@ def test_expectation_NCCL(nqubits: int, dtype="complex128"): if backend.rank == 0: # Compare numerical values assert math.isclose( - exact_expval.item(), float(result_tn[0]), abs_tol=1e-7 + exact_expval.item(), float(result_tn[0]), abs_tol=ABS_TOL ), f"Rank {backend.rank}: mismatch, expected {exact_expval}, got {result_tn}" else: @@ -275,7 +276,7 @@ def test_expectation_NCCL(nqubits: int, dtype="complex128"): if backend.rank == 0: # Compare numerical values assert math.isclose( - exact_expval.item(), float(result_tn[0]), abs_tol=1e-7 + exact_expval.item(), float(result_tn[0]), abs_tol=ABS_TOL ), f"Rank {backend.rank}: mismatch, expected {exact_expval}, got {result_tn}" else: @@ -300,7 +301,7 @@ def test_expectation_NCCL(nqubits: int, dtype="complex128"): if backend.rank == 0: # Compare numerical values assert math.isclose( - exact_expval.item(), float(result_tn[0]), abs_tol=1e-7 + exact_expval.item(), float(result_tn[0]), abs_tol=ABS_TOL ), f"Rank {backend.rank}: mismatch, expected {exact_expval}, got {result_tn}" else: From 256138d8759f2774af1eb4951efb288a7b09022c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 02:18:44 +0000 Subject: [PATCH 34/40] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_cuquantum_cutensor_backend.py | 13 ++++++++++--- tests/test_cuquantum_cutensor_mpi_backend.py | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index 19a81a8..0cbfcc4 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -9,6 +9,7 @@ from qibo.symbols import X, Z ABS_TOL = 1e-7 + def qibo_qft(nqubits, swaps): circ_qibo = QFT(nqubits, swaps) state_vec = circ_qibo().state(numpy=True) @@ -166,7 +167,9 @@ def test_expectation(nqubits: int, dtype="complex128"): } backend.configure_tn_simulation(computation_settings_1) result_tn = backend.execute_circuit(circuit=qibo_circ) - assert math.isclose(exact_expval.item(), result_tn.real.get().item(), abs_tol=ABS_TOL) + assert math.isclose( + exact_expval.item(), result_tn.real.get().item(), abs_tol=ABS_TOL + ) # Test with user defined hamiltonian using "hamiltonians.SymbolicHamiltonian" object. computation_settings_2 = { @@ -177,7 +180,9 @@ def test_expectation(nqubits: int, dtype="complex128"): } backend.configure_tn_simulation(computation_settings_2) result_tn = backend.execute_circuit(circuit=qibo_circ) - assert math.isclose(exact_expval.item(), result_tn.real.get().item(), abs_tol=ABS_TOL) + assert math.isclose( + exact_expval.item(), result_tn.real.get().item(), abs_tol=ABS_TOL + ) # Test with user defined hamiltonian using Dictionary object form of hamiltonian. ham_dict = build_observable_dict(nqubits) @@ -189,4 +194,6 @@ def test_expectation(nqubits: int, dtype="complex128"): } backend.configure_tn_simulation(computation_settings_3) result_tn = backend.execute_circuit(circuit=qibo_circ) - assert math.isclose(exact_expval.item(), result_tn.real.get().item(), abs_tol=ABS_TOL) + assert math.isclose( + exact_expval.item(), result_tn.real.get().item(), abs_tol=ABS_TOL + ) diff --git a/tests/test_cuquantum_cutensor_mpi_backend.py b/tests/test_cuquantum_cutensor_mpi_backend.py index 0e2fcef..459a5cc 100644 --- a/tests/test_cuquantum_cutensor_mpi_backend.py +++ b/tests/test_cuquantum_cutensor_mpi_backend.py @@ -12,6 +12,7 @@ from qibo.symbols import X, Z ABS_TOL = 1e-7 + def qibo_qft(nqubits, swaps): circ_qibo = QFT(nqubits, swaps) state_vec = circ_qibo().state(numpy=True) From 17f7ec343158a518550b7c0b35281fc28b9f5df9 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Thu, 4 Sep 2025 10:35:27 +0800 Subject: [PATCH 35/40] Update variable naming --- tests/test_cuquantum_cutensor_backend.py | 24 +++++++-------- tests/test_cuquantum_cutensor_mpi_backend.py | 32 ++++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index 0cbfcc4..2bd4c26 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -69,13 +69,13 @@ def test_eval(nqubits: int, dtype="complex128"): ), "Resulting dense vectors do not match" # Test with explicit settings specified. - computation_settings = { + comp_set_w_bool = { "MPI_enabled": False, "MPS_enabled": False, "NCCL_enabled": False, "expectation_enabled": False, } - backend.configure_tn_simulation(computation_settings) + backend.configure_tn_simulation(comp_set_w_bool) result_tn = backend.execute_circuit(circuit=qibo_circ) print( f"State vector difference: {abs(result_tn.statevector.flatten() - result_sv_cp).max():0.3e}" @@ -104,13 +104,13 @@ def test_mps(nqubits: int, dtype="complex128"): # Test cutensornet backend = construct_backend(backend="qibotn", platform="cutensornet") # Test with simple MPS settings specified using bool. Uses the default MPS parameters. - computation_settings_1 = { + comp_set_w_bool = { "MPI_enabled": False, "MPS_enabled": True, "NCCL_enabled": False, "expectation_enabled": False, } - backend.configure_tn_simulation(computation_settings_1) + backend.configure_tn_simulation(comp_set_w_bool) result_tn = backend.execute_circuit(circuit=qibo_circ) print( f"State vector difference: {abs(result_tn.statevector.flatten() - result_sv_cp).max():0.3e}" @@ -120,7 +120,7 @@ def test_mps(nqubits: int, dtype="complex128"): ), "Resulting dense vectors do not match" # Test with explicit MPS computation settings specified using Dict. Users able to specify parameters like qr_method etc. - computation_settings_2 = { + comp_set_w_MPS_config_para = { "MPI_enabled": False, "MPS_enabled": { "qr_method": False, @@ -132,7 +132,7 @@ def test_mps(nqubits: int, dtype="complex128"): "NCCL_enabled": False, "expectation_enabled": False, } - backend.configure_tn_simulation(computation_settings_2) + backend.configure_tn_simulation(comp_set_w_MPS_config_para) result_tn = backend.execute_circuit(circuit=qibo_circ) print( f"State vector difference: {abs(result_tn.statevector.flatten() - result_sv_cp).max():0.3e}" @@ -159,26 +159,26 @@ def test_expectation(nqubits: int, dtype="complex128"): backend = construct_backend(backend="qibotn", platform="cutensornet") # Test with simple settings using bool. Uses default Hamilitonian for expectation calculation. - computation_settings_1 = { + comp_set_w_bool = { "MPI_enabled": False, "MPS_enabled": False, "NCCL_enabled": False, "expectation_enabled": True, } - backend.configure_tn_simulation(computation_settings_1) + backend.configure_tn_simulation(comp_set_w_bool) result_tn = backend.execute_circuit(circuit=qibo_circ) assert math.isclose( exact_expval.item(), result_tn.real.get().item(), abs_tol=ABS_TOL ) # Test with user defined hamiltonian using "hamiltonians.SymbolicHamiltonian" object. - computation_settings_2 = { + comp_set_w_hamiltonian_obj = { "MPI_enabled": False, "MPS_enabled": False, "NCCL_enabled": False, "expectation_enabled": ham, } - backend.configure_tn_simulation(computation_settings_2) + backend.configure_tn_simulation(comp_set_w_hamiltonian_obj) result_tn = backend.execute_circuit(circuit=qibo_circ) assert math.isclose( exact_expval.item(), result_tn.real.get().item(), abs_tol=ABS_TOL @@ -186,13 +186,13 @@ def test_expectation(nqubits: int, dtype="complex128"): # Test with user defined hamiltonian using Dictionary object form of hamiltonian. ham_dict = build_observable_dict(nqubits) - computation_settings_3 = { + comp_set_w_hamiltonian_dict = { "MPI_enabled": False, "MPS_enabled": False, "NCCL_enabled": False, "expectation_enabled": ham_dict, } - backend.configure_tn_simulation(computation_settings_3) + backend.configure_tn_simulation(comp_set_w_hamiltonian_dict) result_tn = backend.execute_circuit(circuit=qibo_circ) assert math.isclose( exact_expval.item(), result_tn.real.get().item(), abs_tol=ABS_TOL diff --git a/tests/test_cuquantum_cutensor_mpi_backend.py b/tests/test_cuquantum_cutensor_mpi_backend.py index 459a5cc..0645800 100644 --- a/tests/test_cuquantum_cutensor_mpi_backend.py +++ b/tests/test_cuquantum_cutensor_mpi_backend.py @@ -65,13 +65,13 @@ def test_eval_mpi(nqubits: int, dtype="complex128"): backend = construct_backend(backend="qibotn", platform="cutensornet") # Test with explicit settings specified. - computation_settings = { + comp_set_w_bool = { "MPI_enabled": True, "MPS_enabled": False, "NCCL_enabled": False, "expectation_enabled": False, } - backend.configure_tn_simulation(computation_settings) + backend.configure_tn_simulation(comp_set_w_bool) result_tn = backend.execute_circuit(circuit=qibo_circ) result_tn_cp = cp.asarray(result_tn.statevector.flatten()) @@ -109,13 +109,13 @@ def test_expectation_mpi(nqubits: int, dtype="complex128"): backend = construct_backend(backend="qibotn", platform="cutensornet") # Test with simple settings using bool. Uses default Hamilitonian for expectation calculation. - computation_settings_1 = { + comp_set_w_bool = { "MPI_enabled": True, "MPS_enabled": False, "NCCL_enabled": False, "expectation_enabled": True, } - backend.configure_tn_simulation(computation_settings_1) + backend.configure_tn_simulation(comp_set_w_bool) result_tn = backend.execute_circuit(circuit=qibo_circ) if backend.rank == 0: # Compare numerical values @@ -133,13 +133,13 @@ def test_expectation_mpi(nqubits: int, dtype="complex128"): ), f"Rank {backend.rank}: expected int array [0], got {result_tn}" # Test with user defined hamiltonian using "hamiltonians.SymbolicHamiltonian" object. - computation_settings_2 = { + comp_set_w_hamiltonian_obj = { "MPI_enabled": True, "MPS_enabled": False, "NCCL_enabled": False, "expectation_enabled": ham, } - backend.configure_tn_simulation(computation_settings_2) + backend.configure_tn_simulation(comp_set_w_hamiltonian_obj) result_tn = backend.execute_circuit(circuit=qibo_circ) if backend.rank == 0: # Compare numerical values @@ -158,13 +158,13 @@ def test_expectation_mpi(nqubits: int, dtype="complex128"): # Test with user defined hamiltonian using Dictionary object form of hamiltonian. ham_dict = build_observable_dict(nqubits) - computation_settings_3 = { + comp_set_w_hamiltonian_dict = { "MPI_enabled": True, "MPS_enabled": False, "NCCL_enabled": False, "expectation_enabled": ham_dict, } - backend.configure_tn_simulation(computation_settings_3) + backend.configure_tn_simulation(comp_set_w_hamiltonian_dict) result_tn = backend.execute_circuit(circuit=qibo_circ) if backend.rank == 0: # Compare numerical values @@ -201,13 +201,13 @@ def test_eval_nccl(nqubits: int, dtype="complex128"): backend = construct_backend(backend="qibotn", platform="cutensornet") # Test with explicit settings specified. - computation_settings = { + comp_set_w_bool = { "MPI_enabled": False, "MPS_enabled": False, "NCCL_enabled": True, "expectation_enabled": False, } - backend.configure_tn_simulation(computation_settings) + backend.configure_tn_simulation(comp_set_w_bool) result_tn = backend.execute_circuit(circuit=qibo_circ) result_tn_cp = cp.asarray(result_tn.statevector.flatten()) @@ -242,13 +242,13 @@ def test_expectation_NCCL(nqubits: int, dtype="complex128"): backend = construct_backend(backend="qibotn", platform="cutensornet") # Test with simple settings using bool. Uses default Hamilitonian for expectation calculation. - computation_settings_1 = { + comp_set_w_bool = { "MPI_enabled": False, "MPS_enabled": False, "NCCL_enabled": True, "expectation_enabled": True, } - backend.configure_tn_simulation(computation_settings_1) + backend.configure_tn_simulation(comp_set_w_bool) result_tn = backend.execute_circuit(circuit=qibo_circ) if backend.rank == 0: # Compare numerical values @@ -266,13 +266,13 @@ def test_expectation_NCCL(nqubits: int, dtype="complex128"): ), f"Rank {backend.rank}: expected int array [0], got {result_tn}" # Test with user defined hamiltonian using "hamiltonians.SymbolicHamiltonian" object. - computation_settings_2 = { + comp_set_w_hamiltonian_obj = { "MPI_enabled": False, "MPS_enabled": False, "NCCL_enabled": True, "expectation_enabled": ham, } - backend.configure_tn_simulation(computation_settings_2) + backend.configure_tn_simulation(comp_set_w_hamiltonian_obj) result_tn = backend.execute_circuit(circuit=qibo_circ) if backend.rank == 0: # Compare numerical values @@ -291,13 +291,13 @@ def test_expectation_NCCL(nqubits: int, dtype="complex128"): # Test with user defined hamiltonian using Dictionary object form of hamiltonian. ham_dict = build_observable_dict(nqubits) - computation_settings_3 = { + comp_set_w_hamiltonian_dict = { "MPI_enabled": False, "MPS_enabled": False, "NCCL_enabled": True, "expectation_enabled": ham_dict, } - backend.configure_tn_simulation(computation_settings_3) + backend.configure_tn_simulation(comp_set_w_hamiltonian_dict) result_tn = backend.execute_circuit(circuit=qibo_circ) if backend.rank == 0: # Compare numerical values From a5b266ed460c1fc3cb08552a6ef7cb5803d5fde1 Mon Sep 17 00:00:00 2001 From: tankya2 Date: Fri, 5 Sep 2025 14:56:40 +0800 Subject: [PATCH 36/40] clean up reduce_result: minor refactor --- src/qibotn/eval.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 8a0250b..afba962 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -31,7 +31,6 @@ def build_observable(circuit_nqubit): for i in range(circuit_nqubit): hamiltonian_form += 0.5 * X(i % circuit_nqubit) * Z((i + 1) % circuit_nqubit) - print("Default hamiltonian: ", hamiltonian_form) hamiltonian = hamiltonians.SymbolicHamiltonian(form=hamiltonian_form) return hamiltonian @@ -195,11 +194,6 @@ def compute_optimal_path(network, n_samples, size, comm): return comm.bcast(info, sender) -def compute_contraction(network, slices): - """Perform tensor contraction.""" - return network.contract(slices=slices) - - def compute_slices(info, rank, size): """Determine the slice range each process should compute.""" num_slices = info.num_slices @@ -215,6 +209,7 @@ def reduce_result(result, comm, method="MPI", root=0): """Reduce results across processes.""" if method == "MPI": return comm.reduce(sendobj=result, op=MPI.SUM, root=root) + elif method == "NCCL": stream_ptr = cp.cuda.get_current_stream().ptr if result.dtype == cp.complex128: @@ -236,6 +231,8 @@ def reduce_result(result, comm, method="MPI", root=0): stream_ptr, ) return result + else: + raise ValueError(f"Unknown reduce method: {method}") def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): @@ -265,7 +262,7 @@ def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): optimize={"path": info.path, "slicing": info.slices} ) slices = compute_slices(info, rank, size) - result = compute_contraction(network, slices) + result = network.contract(slices=slices) return reduce_result(result, comm, method="MPI"), rank @@ -297,7 +294,7 @@ def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): optimize={"path": info.path, "slicing": info.slices} ) slices = compute_slices(info, rank, size) - result = compute_contraction(network, slices) + result = network.contract(slices=slices) return reduce_result(result, comm_nccl, method="NCCL"), rank @@ -375,7 +372,7 @@ def expectation_tn_nccl(qibo_circ, datatype, observable, n_samples=8): slices = compute_slices(info, rank, size) # Contract the group of slices the process is responsible for. - result = compute_contraction(network, slices) + result = network.contract(slices=slices) # Sum the partial contribution from each process on root. result = reduce_result(result, comm_nccl, method="NCCL", root=0) @@ -444,7 +441,7 @@ def expectation_tn_MPI(qibo_circ, datatype, observable, n_samples=8): slices = compute_slices(info, rank, size) # Perform contraction - result = compute_contraction(network, slices) + result = network.contract(slices=slices) # Sum the partial contribution from each process on root. result = reduce_result(result, comm, method="MPI", root=0) From 5165e787e613d2a2203147fed37ad57f4b88efe7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 05:37:43 +0000 Subject: [PATCH 37/40] chore(deps): bump setuptools from 78.1.0 to 78.1.1 Bumps [setuptools](https://github.com/pypa/setuptools) from 78.1.0 to 78.1.1. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v78.1.0...v78.1.1) --- updated-dependencies: - dependency-name: setuptools dependency-version: 78.1.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index ef97f38..0406f0d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -2464,15 +2464,14 @@ test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis [[package]] name = "setuptools" -version = "78.1.0" +version = "78.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = true +optional = false python-versions = ">=3.9" groups = ["main"] -markers = "extra == \"qmatchatea\"" files = [ - {file = "setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8"}, - {file = "setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54"}, + {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"}, + {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"}, ] [package.extras] From 2a89968d92b9adfa6a6f04b3b61ce9baed0a6779 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 05:45:49 +0000 Subject: [PATCH 38/40] chore(deps-dev): bump urllib3 from 2.3.0 to 2.5.0 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.3.0 to 2.5.0. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.3.0...2.5.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.5.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index ef97f38..95a6d2a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -3020,14 +3020,14 @@ markers = {dev = "python_version == \"3.11\""} [[package]] name = "urllib3" -version = "2.3.0" +version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["docs"] +groups = ["dev", "docs"] files = [ - {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, - {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] From 8cbad550105ebbdd4009d9ea88acf198e7e79303 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 05:48:34 +0000 Subject: [PATCH 39/40] chore(deps-dev): bump requests from 2.32.3 to 2.32.4 Bumps [requests](https://github.com/psf/requests) from 2.32.3 to 2.32.4. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.3...v2.32.4) --- updated-dependencies: - dependency-name: requests dependency-version: 2.32.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/poetry.lock b/poetry.lock index ef97f38..35e4173 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -132,7 +132,7 @@ version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" -groups = ["docs"] +groups = ["dev", "docs"] files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, @@ -144,7 +144,7 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["docs"] +groups = ["dev", "docs"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -1074,7 +1074,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["docs"] +groups = ["dev", "docs"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -2348,19 +2348,19 @@ tests = ["coverage", "pytest", "pytest-cov"] [[package]] name = "requests" -version = "2.32.3" +version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" -groups = ["docs"] +groups = ["dev", "docs"] files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, + {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, + {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, ] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" +charset_normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" @@ -3024,7 +3024,7 @@ version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["docs"] +groups = ["dev", "docs"] files = [ {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, From 44ce1d6d174bd904ac143ba3b8b9430f4948ddc7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 21:32:59 +0000 Subject: [PATCH 40/40] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 25.1.0 → 25.9.0](https://github.com/psf/black/compare/25.1.0...25.9.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8bd9d03..d6bfe67 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: check-toml - id: debug-statements - repo: https://github.com/psf/black - rev: 25.1.0 + rev: 25.9.0 hooks: - id: black - repo: https://github.com/pycqa/isort