From 4982fbf9262aaac28550b4ddc696da1989b7f04f Mon Sep 17 00:00:00 2001 From: Alessandro Candido Date: Wed, 7 Feb 2024 11:13:55 +0100 Subject: [PATCH 1/5] build: Add Nix files --- .envrc | 9 ++ .gitignore | 1 + flake.lock | 323 +++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 62 ++++++++++ 4 files changed, 395 insertions(+) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..01f5f41 --- /dev/null +++ b/.envrc @@ -0,0 +1,9 @@ +if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs=" +fi + +nix_direnv_watch_file flake.nix +nix_direnv_watch_file flake.lock +if ! use flake . --impure; then + echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2 +fi diff --git a/.gitignore b/.gitignore index d903da0..7f051b8 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,4 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +.devenv diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..0f48ef0 --- /dev/null +++ b/flake.lock @@ -0,0 +1,323 @@ +{ + "nodes": { + "devenv": { + "inputs": { + "flake-compat": "flake-compat", + "nix": "nix", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks" + }, + "locked": { + "lastModified": 1707004164, + "narHash": "sha256-9Hr8onWtvLk5A8vCEkaE9kxA0D7PR62povFokM1oL5Q=", + "owner": "cachix", + "repo": "devenv", + "rev": "0e68853bb27981a4ffd7a7225b59ed84f7180fc7", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1685518550, + "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "id": "flake-utils", + "type": "indirect" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "devenv", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "lowdown-src": { + "flake": false, + "locked": { + "lastModified": 1633514407, + "narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=", + "owner": "kristapsdz", + "repo": "lowdown", + "rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8", + "type": "github" + }, + "original": { + "owner": "kristapsdz", + "repo": "lowdown", + "type": "github" + } + }, + "nix": { + "inputs": { + "lowdown-src": "lowdown-src", + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-regression": "nixpkgs-regression" + }, + "locked": { + "lastModified": 1676545802, + "narHash": "sha256-EK4rZ+Hd5hsvXnzSzk2ikhStJnD63odF7SzsQ8CuSPU=", + "owner": "domenkozar", + "repo": "nix", + "rev": "7c91803598ffbcfe4a55c44ac6d49b2cf07a527f", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "relaxed-flakes", + "repo": "nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1678875422, + "narHash": "sha256-T3o6NcQPwXjxJMn2shz86Chch4ljXgZn746c2caGxd8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "126f49a01de5b7e35a43fd43f891ecf6d3a51459", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-python": { + "inputs": { + "flake-compat": "flake-compat_2", + "flake-utils": "flake-utils_2", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1707114737, + "narHash": "sha256-ZXqv2epXAjDjfWbYn+yy4VOmW+C7SuUBoiZkkDoSqA4=", + "owner": "cachix", + "repo": "nixpkgs-python", + "rev": "f34ed02276bc08fe1c91c1bf0ef3589d68028878", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "nixpkgs-python", + "type": "github" + } + }, + "nixpkgs-regression": { + "locked": { + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1685801374, + "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1707092692, + "narHash": "sha256-ZbHsm+mGk/izkWtT4xwwqz38fdlwu7nUUKXTOmm4SyE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "faf912b086576fd1a15fca610166c98d47bc667e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "flake-utils": "flake-utils", + "gitignore": "gitignore", + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1704725188, + "narHash": "sha256-qq8NbkhRZF1vVYQFt1s8Mbgo8knj+83+QlL5LBnYGpI=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "ea96f0c05924341c551a797aaba8126334c505d2", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "nixpkgs": "nixpkgs_2", + "nixpkgs-python": "nixpkgs-python", + "systems": "systems_3" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..92f53ee --- /dev/null +++ b/flake.nix @@ -0,0 +1,62 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + systems.url = "github:nix-systems/default"; + devenv.url = "github:cachix/devenv"; + nixpkgs-python = { + url = "github:cachix/nixpkgs-python"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { + self, + nixpkgs, + devenv, + systems, + ... + } @ inputs: let + forEachSystem = nixpkgs.lib.genAttrs (import systems); + in { + # packages = forEachSystem (system: { + # default = + # nixpkgs.legacyPackages.${system}.poetry2nix.mkPoetryApplication + # { + # projectDir = self; + # preferWheels = true; + # }; + # }); + + devShells = + forEachSystem + (system: let + pkgs = nixpkgs.legacyPackages.${system}; + in { + default = devenv.lib.mkShell { + inherit inputs pkgs; + + modules = [ + { + packages = with pkgs; [pre-commit poethepoet stdenv.cc.cc.lib]; + + languages.python = { + enable = true; + # poetry = { + # enable = true; + # install.enable = true; + # install.groups = ["dev" "tests"]; + # install.allExtras = true; + # }; + version = "3.11"; + }; + } + ]; + }; + }); + }; + + nixConfig = { + extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="; + extra-substituters = "https://devenv.cachix.org"; + }; +} From c3a4a544b5e899af7f20c3a0f3fc5e373062515f Mon Sep 17 00:00:00 2001 From: Alessandro Candido Date: Wed, 7 Feb 2024 11:20:58 +0100 Subject: [PATCH 2/5] fix: Fix dependencies issues introducing additional ones --- .pre-commit-config.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9d8b368..b65aeed 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: rev: v1.7.5 hooks: - id: docformatter - additional_dependencies: [tomli] + additional_dependencies: [tomli, charset-normalizer] args: [--in-place, --config, ./pyproject.toml] - repo: https://github.com/asottile/pyupgrade rev: v3.15.0 @@ -39,6 +39,8 @@ repos: rev: 1.16.0 hooks: - id: blacken-docs + additional_dependencies: + - platformdirs - repo: https://github.com/pycqa/pydocstyle rev: 6.3.0 hooks: From e496f9f156d5d9bc1270cf8aebeae9811f26ce0b Mon Sep 17 00:00:00 2001 From: Alessandro Candido Date: Wed, 7 Feb 2024 11:21:27 +0100 Subject: [PATCH 3/5] chore: Run pre-commit on all files --- README.md | 4 +- setup.py | 9 ++- src/qibotn/MPSUtils.py | 13 +--- src/qibotn/QiboCircuitConvertor.py | 16 ++-- src/qibotn/QiboCircuitToMPS.py | 4 +- src/qibotn/backends/__init__.py | 2 +- src/qibotn/backends/gpu.py | 4 +- src/qibotn/eval.py | 93 ++++++++++++++++-------- src/qibotn/mps_contraction_helper.py | 17 ++--- tests/test_cuquantum_cutensor_backend.py | 2 +- 10 files changed, 92 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 070ccb3..9f60ad3 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ import qibo # Below shows how to set the computation_settings # Note that for MPS_enabled and expectation_enabled parameters the accepted inputs are boolean or a dictionary with the format shown below. -# If computation_settings is not specified, the default setting is used in which all booleans will be False. +# If computation_settings is not specified, the default setting is used in which all booleans will be False. # This will trigger the dense vector computation of the tensornet. computation_settings = { @@ -92,4 +92,4 @@ Multi-node is enabled by setting either the MPI or NCCL enabled flag to True in ```sh mpirun -n 4 -hostfile $node_list python test.py -``` \ No newline at end of file +``` diff --git a/setup.py b/setup.py index 13285eb..0f619a5 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ -from setuptools import setup, find_packages -import re import pathlib +import re + +from setuptools import find_packages, setup HERE = pathlib.Path(__file__).parent.absolute() PACKAGE = "qibotn" @@ -8,8 +9,8 @@ PACKAGE = "qibotn" # Returns the qibotn version def version(): - """Gets the version from the package's __init__ file - if there is some problem, let it happily fail""" + """Gets the version from the package's __init__ file if there is some + problem, let it happily fail.""" version_file = HERE / "src" / PACKAGE / "__init__.py" version_regex = r"^__version__ = ['\"]([^'\"]*)['\"]" diff --git a/src/qibotn/MPSUtils.py b/src/qibotn/MPSUtils.py index 4f84f67..e8068f7 100644 --- a/src/qibotn/MPSUtils.py +++ b/src/qibotn/MPSUtils.py @@ -1,23 +1,19 @@ import cupy as cp -from cuquantum.cutensornet.experimental import contract_decompose from cuquantum import contract +from cuquantum.cutensornet.experimental import contract_decompose # Reference: https://github.com/NVIDIA/cuQuantum/blob/main/python/samples/cutensornet/tn_algorithms/mps_algorithms.ipynb def initial(num_qubits, dtype): - """ - Generate the MPS with an initial state of |00...00> - """ + """Generate the MPS with an initial state of |00...00>""" state_tensor = cp.asarray([1, 0], dtype=dtype).reshape(1, 2, 1) mps_tensors = [state_tensor] * num_qubits return mps_tensors def mps_site_right_swap(mps_tensors, i, **kwargs): - """ - Perform the swap operation between the ith and i+1th MPS tensors. - """ + """Perform the swap operation between the ith and i+1th MPS tensors.""" # contraction followed by QR decomposition a, _, b = contract_decompose( "ipj,jqk->iqj,jpk", @@ -30,8 +26,7 @@ def mps_site_right_swap(mps_tensors, i, **kwargs): def apply_gate(mps_tensors, gate, qubits, **kwargs): - """ - Apply the gate operand to the MPS tensors in-place. + """Apply the gate operand to the MPS tensors in-place. Args: mps_tensors: A list of rank-3 ndarray-like tensor objects. diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index 5ac2fee..f67fb8e 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -5,9 +5,9 @@ import numpy as np class QiboCircuitToEinsum: - """Convert a circuit to a Tensor Network (TN) representation. - The circuit is first processed to an intermediate form by grouping each gate - matrix with its corresponding qubit it is acting on to a list. It is then + """Convert a circuit to a Tensor Network (TN) representation. The circuit + is first processed to an intermediate form by grouping each gate matrix + with its corresponding qubit it is acting on to a list. It is then converted to an equivalent TN expression through the class function state_vector_operands() following the Einstein summation convention in the interleave format. @@ -79,9 +79,8 @@ class QiboCircuitToEinsum: return mode_labels, operands def op_shape_from_qubits(self, nqubits): - """Modify tensor to cuQuantum shape - (qubit_states,input_output) * qubits_involved - """ + """Modify tensor to cuQuantum shape (qubit_states,input_output) * + qubits_involved.""" return (2, 2) * nqubits def init_intermediate_circuit(self, circuit): @@ -134,8 +133,7 @@ class QiboCircuitToEinsum: self.active_qubits_inverse = np.unique(gates_qubits_inverse) def get_pauli_gates(self, pauli_map, dtype="complex128", backend=cp): - """ - Populate the gates for all pauli operators. + """Populate the gates for all pauli operators. Args: pauli_map: A dictionary mapping qubits to pauli operators. @@ -161,7 +159,7 @@ class QiboCircuitToEinsum: return gates def expectation_operands(self, pauli_string): - input_bitstring = "0" * self.circuit.nqubits + input_bitstring = "0" * self.circuit.nqubits input_operands = self._get_bitstring_tensors(input_bitstring) pauli_string = dict(zip(range(self.circuit.nqubits), pauli_string)) diff --git a/src/qibotn/QiboCircuitToMPS.py b/src/qibotn/QiboCircuitToMPS.py index 816b17c..b1d847f 100644 --- a/src/qibotn/QiboCircuitToMPS.py +++ b/src/qibotn/QiboCircuitToMPS.py @@ -1,9 +1,9 @@ import cupy as cp import numpy as np - from cuquantum import cutensornet as cutn + +from qibotn.MPSUtils import apply_gate, initial from qibotn.QiboCircuitConvertor import QiboCircuitToEinsum -from qibotn.MPSUtils import initial, apply_gate class QiboCircuitToMPS: diff --git a/src/qibotn/backends/__init__.py b/src/qibotn/backends/__init__.py index f927932..e5d68de 100644 --- a/src/qibotn/backends/__init__.py +++ b/src/qibotn/backends/__init__.py @@ -1,2 +1,2 @@ -from qibotn.backends.gpu import CuTensorNet from qibotn.backends.cpu import QuTensorNet +from qibotn.backends.gpu import CuTensorNet diff --git a/src/qibotn/backends/gpu.py b/src/qibotn/backends/gpu.py index 5777fe9..2c3f8d4 100644 --- a/src/qibotn/backends/gpu.py +++ b/src/qibotn/backends/gpu.py @@ -1,8 +1,7 @@ import numpy as np - from qibo.backends.numpy import NumpyBackend -from qibo.states import CircuitResult from qibo.config import raise_error +from qibo.states import CircuitResult class CuTensorNet(NumpyBackend): # pragma: no cover @@ -107,7 +106,6 @@ class CuTensorNet(NumpyBackend): # pragma: no cover Returns: xxx. - """ import qibotn.eval as eval diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 96fd488..5fcb66f 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -1,20 +1,22 @@ -from qibotn.QiboCircuitConvertor import QiboCircuitToEinsum -from cuquantum import contract -from cupy.cuda.runtime import getDeviceCount import cupy as cp +from cupy.cuda.runtime import getDeviceCount +from cuquantum import contract -from qibotn.QiboCircuitToMPS import QiboCircuitToMPS from qibotn.mps_contraction_helper import MPSContractionHelper +from qibotn.QiboCircuitConvertor import QiboCircuitToEinsum +from qibotn.QiboCircuitToMPS import QiboCircuitToMPS def dense_vector_tn(qibo_circ, datatype): - """Convert qibo circuit to tensornet (TN) format and perform contraction to dense vector.""" + """Convert qibo circuit to tensornet (TN) format and perform contraction to + dense vector.""" myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) return contract(*myconvertor.state_vector_operands()) 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.""" + """Convert qibo circuit to tensornet (TN) format and perform contraction to + expectation of given Pauli string.""" myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) return contract( *myconvertor.expectation_operands( @@ -24,14 +26,19 @@ def expectation_pauli_tn(qibo_circ, datatype, pauli_string_pattern): def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): - """Convert qibo circuit to tensornet (TN) format and perform contraction 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 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 a dense vector representation of the TN. + """Convert qibo circuit to tensornet (TN) format and perform contraction + 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 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 a dense vector representation of the TN. """ - from mpi4py import MPI from cuquantum import Network + from mpi4py import MPI root = 0 comm = MPI.COMM_WORLD @@ -86,14 +93,19 @@ def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): - """Convert qibo circuit to tensornet (TN) format and perform contraction using multi node and multi GPU through NCCL. - The conversion is performed by QiboCircuitToEinsum(), after which it goes through 2 steps: pathfinder and execution. - 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 a dense vector representation of the TN. + """Convert qibo circuit to tensornet (TN) format and perform contraction + using multi node and multi GPU through NCCL. + + The conversion is performed by QiboCircuitToEinsum(), after which it + goes through 2 steps: pathfinder and execution. 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 a dense vector representation of the TN. """ - from mpi4py import MPI - from cuquantum import Network from cupy.cuda import nccl + from cuquantum import Network + from mpi4py import MPI root = 0 comm_mpi = MPI.COMM_WORLD @@ -159,15 +171,22 @@ def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): def expectation_pauli_tn_nccl(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 NCCL. - 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. + """Convert qibo circuit to tensornet (TN) format and perform contraction to + expectation of given Pauli string using multi node and multi GPU through + NCCL. + + 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. """ - from mpi4py import MPI - from cuquantum import Network from cupy.cuda import nccl + from cuquantum import Network + from mpi4py import MPI root = 0 comm_mpi = MPI.COMM_WORLD @@ -235,14 +254,21 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl def expectation_pauli_tn_MPI(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. + """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. """ - from mpi4py import MPI # this line initializes MPI from cuquantum import Network + from mpi4py import MPI # this line initializes MPI root = 0 comm = MPI.COMM_WORLD @@ -299,7 +325,8 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample def dense_vector_mps(qibo_circ, gate_algo, datatype): - """Convert qibo circuit to matrix product state (MPS) format and perform contraction to dense vector.""" + """Convert qibo circuit to matrix product state (MPS) format and perform + contraction to dense vector.""" myconvertor = QiboCircuitToMPS(qibo_circ, gate_algo, dtype=datatype) mps_helper = MPSContractionHelper(myconvertor.num_qubits) @@ -309,7 +336,9 @@ def dense_vector_mps(qibo_circ, gate_algo, datatype): def pauli_string_gen(nqubits, pauli_string_pattern): - """Used internally to generate the string based on given pattern and number of qubit. + """Used internally to generate the string based on given pattern and number + of qubit. + Example: pattern: "XZ", number of qubit: 7, output = XZXZXZX """ if nqubits <= 0: diff --git a/src/qibotn/mps_contraction_helper.py b/src/qibotn/mps_contraction_helper.py index 29d5e25..1c004de 100644 --- a/src/qibotn/mps_contraction_helper.py +++ b/src/qibotn/mps_contraction_helper.py @@ -1,11 +1,10 @@ -from cuquantum import contract, contract_path, CircuitToEinsum, tensor +from cuquantum import contract, contract_path # Reference: https://github.com/NVIDIA/cuQuantum/blob/main/python/samples/cutensornet/tn_algorithms/mps_algorithms.ipynb class MPSContractionHelper: - """ - A helper class to compute various quantities for a given MPS. + """A helper class to compute various quantities for a given MPS. Interleaved format is used to construct the input args for `cuquantum.contract`. A concrete example on how the modes are populated for a 7-site MPS is provided below: @@ -43,8 +42,8 @@ class MPSContractionHelper: ] def contract_norm(self, mps_tensors, options=None): - """ - Contract the corresponding tensor network to form the norm of the MPS. + """Contract the corresponding tensor network to form the norm of the + MPS. Args: mps_tensors: A list of rank-3 ndarray-like tensor objects. @@ -64,8 +63,8 @@ class MPSContractionHelper: return self._contract(interleaved_inputs, options=options).real def contract_state_vector(self, mps_tensors, options=None): - """ - Contract the corresponding tensor network to form the state vector representation of the MPS. + """Contract the corresponding tensor network to form the state vector + representation of the MPS. Args: mps_tensors: A list of rank-3 ndarray-like tensor objects. @@ -86,8 +85,8 @@ class MPSContractionHelper: def contract_expectation( self, mps_tensors, operator, qubits, options=None, normalize=False ): - """ - Contract the corresponding tensor network to form the expectation of the MPS. + """Contract the corresponding tensor network to form the expectation of + the MPS. Args: mps_tensors: A list of rank-3 ndarray-like tensor objects. diff --git a/tests/test_cuquantum_cutensor_backend.py b/tests/test_cuquantum_cutensor_backend.py index 57b3f34..c8f1e19 100644 --- a/tests/test_cuquantum_cutensor_backend.py +++ b/tests/test_cuquantum_cutensor_backend.py @@ -1,8 +1,8 @@ from timeit import default_timer as timer import config -import numpy as np import cupy as cp +import numpy as np import pytest import qibo from qibo.models import QFT From 665cec42b2903609e4c6641e611889b6c0407536 Mon Sep 17 00:00:00 2001 From: Alessandro Candido Date: Wed, 7 Feb 2024 18:58:01 +0100 Subject: [PATCH 4/5] chore: Revert additional deps in pre-commit hooks, apparently not needed The problem could have been caused by a corrupted cache. However, it does not persist after a force clean --- .pre-commit-config.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b65aeed..9d8b368 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: rev: v1.7.5 hooks: - id: docformatter - additional_dependencies: [tomli, charset-normalizer] + additional_dependencies: [tomli] args: [--in-place, --config, ./pyproject.toml] - repo: https://github.com/asottile/pyupgrade rev: v3.15.0 @@ -39,8 +39,6 @@ repos: rev: 1.16.0 hooks: - id: blacken-docs - additional_dependencies: - - platformdirs - repo: https://github.com/pycqa/pydocstyle rev: 6.3.0 hooks: From c69fd5f04520f74c7dc84eb882a5d0d2c8e6db0e Mon Sep 17 00:00:00 2001 From: Alessandro Candido Date: Thu, 8 Feb 2024 10:17:22 +0100 Subject: [PATCH 5/5] chore: Pre-commit all files once more --- README.md | 56 ++++++++++++++++++++++--------------- src/qibotn/backends/cpu.py | 5 +--- src/qibotn/eval_qu.py | 20 +++++++------ tests/test_quimb_backend.py | 29 ++++++++++--------- 4 files changed, 60 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 9f60ad3..d22503c 100644 --- a/README.md +++ b/README.md @@ -5,26 +5,33 @@ To get started, `python setup.py install` to install the tools and dependencies. # Supported Computation Tensor Network Types: + - Tensornet (TN) - Matrix Product States (MPS) Tensor Network contractions to: + - dense vectors - expecation values of given Pauli string The supported HPC configurations are: + - single-node CPU - single-node GPU or GPUs - multi-node multi-GPU with Message Passing Interface (MPI) - multi-node multi-GPU with NVIDIA Collective Communications Library (NCCL) Currently, the supported tensor network libraries are: - - [cuQuantum](https://github.com/NVIDIA/cuQuantum), an NVIDIA SDK of optimized libraries and tools for accelerating quantum computing workflows. - - [quimb](https://quimb.readthedocs.io/en/latest/), an easy but fast python library for ‘quantum information many-body’ calculations, focusing primarily on tensor networks. + +- [cuQuantum](https://github.com/NVIDIA/cuQuantum), an NVIDIA SDK of optimized libraries and tools for accelerating quantum computing workflows. +- [quimb](https://quimb.readthedocs.io/en/latest/), an easy but fast python library for ‘quantum information many-body’ calculations, focusing primarily on tensor networks. # Sample Codes + ## Single-Node Example + The code below shows an example of how to activate the Cuquantum TensorNetwork backend of Qibo. + ```py import numpy as np from qibo import Circuit, gates @@ -36,20 +43,22 @@ import qibo # This will trigger the dense vector computation of the tensornet. computation_settings = { - 'MPI_enabled': False, - 'MPS_enabled': { - "qr_method": False, - "svd_method": { - "partition": "UV", - "abs_cutoff": 1e-12, - }, - } , - 'NCCL_enabled': False, - 'expectation_enabled': False + "MPI_enabled": False, + "MPS_enabled": { + "qr_method": False, + "svd_method": { + "partition": "UV", + "abs_cutoff": 1e-12, + }, + }, + "NCCL_enabled": False, + "expectation_enabled": False, } -qibo.set_backend(backend="qibotn", platform="cutensornet", runcard=computation_settings) #cuQuantum +qibo.set_backend( + backend="qibotn", platform="cutensornet", runcard=computation_settings +) # cuQuantum # qibo.set_backend(backend="qibotn", platform="qutensornet", runcard=computation_settings) #quimb @@ -70,25 +79,26 @@ Other examples of setting the computation_settings ```py # Expectation computation with specific Pauli String pattern computation_settings = { - 'MPI_enabled': False, - 'MPS_enabled': False, - 'NCCL_enabled': False, - 'expectation_enabled': { - 'pauli_string_pattern': "IXZ" + "MPI_enabled": False, + "MPS_enabled": False, + "NCCL_enabled": False, + "expectation_enabled": { + "pauli_string_pattern": "IXZ", + }, } # Dense vector computation using multi node through MPI computation_settings = { - 'MPI_enabled': True, - 'MPS_enabled': False, - 'NCCL_enabled': False, - 'expectation_enabled': False + "MPI_enabled": True, + "MPS_enabled": False, + "NCCL_enabled": False, + "expectation_enabled": False, } ``` ## Multi-Node Example -Multi-node is enabled by setting either the MPI or NCCL enabled flag to True in the computation settings. Below shows the script to launch on 2 nodes with 2 GPUs each. $node_list contains the IP of the nodes assigned. +Multi-node is enabled by setting either the MPI or NCCL enabled flag to True in the computation settings. Below shows the script to launch on 2 nodes with 2 GPUs each. $node_list contains the IP of the nodes assigned. ```sh mpirun -n 4 -hostfile $node_list python test.py diff --git a/src/qibotn/backends/cpu.py b/src/qibotn/backends/cpu.py index 7115b39..a85dfd9 100644 --- a/src/qibotn/backends/cpu.py +++ b/src/qibotn/backends/cpu.py @@ -1,8 +1,6 @@ -import numpy as np - from qibo.backends.numpy import NumpyBackend -from qibo.states import CircuitResult from qibo.config import raise_error +from qibo.states import CircuitResult class QuTensorNet(NumpyBackend): @@ -60,7 +58,6 @@ class QuTensorNet(NumpyBackend): Returns: xxx. - """ import qibotn.eval_qu as eval diff --git a/src/qibotn/eval_qu.py b/src/qibotn/eval_qu.py index 579a42a..7b603b5 100644 --- a/src/qibotn/eval_qu.py +++ b/src/qibotn/eval_qu.py @@ -3,9 +3,15 @@ import quimb.tensor as qtn from qibo.models import Circuit as QiboCircuit -def from_qibo(circuit: QiboCircuit, is_mps: False, psi0=None, method='svd', - cutoff=1e-6, cutoff_mode='abs'): - """Create a tensornetwork representation of the circuit""" +def from_qibo( + circuit: QiboCircuit, + is_mps: False, + psi0=None, + method="svd", + cutoff=1e-6, + cutoff_mode="abs", +): + """Create a tensornetwork representation of the circuit.""" nqubits = circuit.nqubits gate_opt = {} @@ -30,19 +36,17 @@ def from_qibo(circuit: QiboCircuit, is_mps: False, psi0=None, method='svd', def init_state_tn(nqubits, init_state_sv): - - """Create a matrixproductstate directly from a dense vector""" + """Create a matrixproductstate directly from a dense vector.""" dims = tuple(2 * np.ones(nqubits, dtype=int)) return qtn.tensor_1d.MatrixProductState.from_dense(init_state_sv, dims) -def dense_vector_tn_qu(qasm: str, initial_state, is_mps, backend="numpy"): - """Evaluate QASM with Quimb +def dense_vector_tn_qu(qasm: str, initial_state, is_mps, backend="numpy"): + """Evaluate QASM with Quimb. backend (quimb): numpy, cupy, jax. Passed to ``opt_einsum``. - """ circuit = QiboCircuit.from_qasm(qasm) if initial_state is not None: diff --git a/tests/test_quimb_backend.py b/tests/test_quimb_backend.py index 81a0e2b..15ba652 100644 --- a/tests/test_quimb_backend.py +++ b/tests/test_quimb_backend.py @@ -1,5 +1,6 @@ import copy import os + import config import numpy as np import pytest @@ -8,8 +9,7 @@ from qibo.models import QFT def create_init_state(nqubits): - init_state = np.random.random(2**nqubits) + \ - 1j * np.random.random(2**nqubits) + init_state = np.random.random(2**nqubits) + 1j * np.random.random(2**nqubits) init_state = init_state / np.sqrt((np.abs(init_state) ** 2).sum()) return init_state @@ -20,10 +20,11 @@ def qibo_qft(nqubits, init_state, swaps): return circ_qibo, state_vec -@pytest.mark.parametrize("nqubits, tolerance, is_mps", - [(1, 1e-6, True), (2, 1e-6, False), (5, 1e-3, True), (10, 1e-3, False)]) +@pytest.mark.parametrize( + "nqubits, tolerance, is_mps", + [(1, 1e-6, True), (2, 1e-6, False), (5, 1e-3, True), (10, 1e-3, False)], +) def test_eval(nqubits: int, tolerance: float, is_mps: bool): - """Evaluate circuit with Quimb backend. Args: @@ -41,20 +42,18 @@ def test_eval(nqubits: int, tolerance: float, is_mps: bool): init_state_tn = copy.deepcopy(init_state) # Test qibo - qibo.set_backend(backend=config.qibo.backend, - platform=config.qibo.platform) - - qibo_circ, result_sv= qibo_qft(nqubits, init_state, swaps=True) - + qibo.set_backend(backend=config.qibo.backend, platform=config.qibo.platform) + + qibo_circ, result_sv = qibo_qft(nqubits, init_state, swaps=True) # Convert to qasm for other backends qasm_circ = qibo_circ.to_qasm() # Test quimb result_tn = qibotn.eval_qu.dense_vector_tn_qu( - qasm_circ, init_state_tn, is_mps, backend=config.quimb.backend - ).flatten() - + qasm_circ, init_state_tn, is_mps, backend=config.quimb.backend + ).flatten() - assert np.allclose(result_sv, result_tn, - atol=tolerance), "Resulting dense vectors do not match" + assert np.allclose( + result_sv, result_tn, atol=tolerance + ), "Resulting dense vectors do not match"