Merge pull request #24 from qiboteam/qibotn_integration
Qibotn integration to Qibo
This commit is contained in:
9
.envrc
Normal file
9
.envrc
Normal file
@@ -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
|
||||||
30
.github/workflows/rules.yml
vendored
30
.github/workflows/rules.yml
vendored
@@ -1,22 +1,38 @@
|
|||||||
# A single CI script with github workflow
|
# A single CI script with github workflow
|
||||||
name: Tests
|
name: Tests
|
||||||
|
|
||||||
|
env:
|
||||||
|
CUDA_PATH:
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [labeled]
|
types: [labeled]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
check:
|
||||||
|
# job to check cuda availability
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- id: step1
|
||||||
|
run: echo "test=${{ env.CUDA_PATH != ''}}" >> "$GITHUB_OUTPUT"
|
||||||
|
- id: step2
|
||||||
|
run: echo "test=${{ contains(github.event.pull_request.labels.*.name, 'run-workflow') || github.event_name == 'push' }}" >> "$GITHUB_OUTPUT"
|
||||||
|
outputs:
|
||||||
|
cuda_avail: ${{ fromJSON(steps.step1.outputs.test) && fromJSON(steps.step2.outputs.test) }}
|
||||||
|
|
||||||
build:
|
build:
|
||||||
if: contains(github.event.pull_request.labels.*.name, 'run-workflow') || github.event_name == 'push' && {{ $CUDA_PATH != '' }}
|
# job to build
|
||||||
|
needs: check
|
||||||
|
if: ${{fromJSON(needs.check.outputs.cuda_avail)}}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
python-version: [3.8, 3.9, "3.10"]
|
python-version: [3.8, 3.9, "3.10", "3.11"]
|
||||||
uses: qiboteam/workflows/.github/workflows/rules.yml@main
|
uses: qiboteam/workflows/.github/workflows/rules-poetry.yml@main
|
||||||
with:
|
with:
|
||||||
os: ${{ matrix.os }}
|
os: ${{ matrix.os }}
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
environment: "qibotn"
|
poetry-extras: "--with analysis,tests"
|
||||||
pip-extras: "analysis,tests"
|
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -159,3 +159,4 @@ cython_debug/
|
|||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# 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.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
.devenv
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# See https://pre-commit.com for more information
|
ci:
|
||||||
# See https://pre-commit.com/hooks.html for more hooks
|
autofix_prs: true
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.5.0
|
rev: v4.5.0
|
||||||
@@ -8,10 +8,9 @@ repos:
|
|||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: check-toml
|
- id: check-toml
|
||||||
- id: check-merge-conflict
|
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 24.2.0
|
rev: 24.1.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
- repo: https://github.com/pycqa/isort
|
- repo: https://github.com/pycqa/isort
|
||||||
@@ -19,7 +18,31 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
args: ["--profile", "black"]
|
args: ["--profile", "black"]
|
||||||
|
- repo: https://github.com/PyCQA/docformatter
|
||||||
|
rev: v1.7.5
|
||||||
|
hooks:
|
||||||
|
- id: docformatter
|
||||||
|
additional_dependencies: [tomli]
|
||||||
|
args: [--in-place, --config, ./pyproject.toml]
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v3.15.1
|
rev: v3.15.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
|
- repo: https://github.com/hadialqattan/pycln
|
||||||
|
rev: v2.4.0
|
||||||
|
hooks:
|
||||||
|
- id: pycln
|
||||||
|
args:
|
||||||
|
- --config=pyproject.toml
|
||||||
|
- --all
|
||||||
|
- repo: https://github.com/adamchainz/blacken-docs
|
||||||
|
rev: 1.16.0
|
||||||
|
hooks:
|
||||||
|
- id: blacken-docs
|
||||||
|
- repo: https://github.com/pycqa/pydocstyle
|
||||||
|
rev: 6.3.0
|
||||||
|
hooks:
|
||||||
|
- id: pydocstyle
|
||||||
|
args:
|
||||||
|
- --select=D103,D200,D206,D300,D301
|
||||||
|
files: ^src/
|
||||||
|
|||||||
119
README.md
119
README.md
@@ -1,3 +1,118 @@
|
|||||||
Qibotn is the tensor-network translation module for Qibo to support large-scale simulation of quantum circuits and acceleration.
|
# Qibotn
|
||||||
|
|
||||||
To get started, `python setup.py install` to install the tools and dependencies.
|
The tensor network translation module for Qibo to support large-scale simulation of quantum circuits and acceleration.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
To get started:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python setup.py install
|
||||||
|
```
|
||||||
|
|
||||||
|
to install the tools and dependencies. A few extras are provided, check `setup.py` in
|
||||||
|
case you need them.
|
||||||
|
|
||||||
|
<!-- TODO: describe extras, after Poetry adoption and its groups -->
|
||||||
|
|
||||||
|
## 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
|
||||||
|
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.
|
||||||
|
# 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
qibo.set_backend(
|
||||||
|
backend="qibotn", platform="cutensornet", runcard=computation_settings
|
||||||
|
) # cuQuantum
|
||||||
|
# qibo.set_backend(backend="qibotn", platform="QuimbBackend", runcard=computation_settings) #quimb
|
||||||
|
|
||||||
|
|
||||||
|
# Construct the circuit
|
||||||
|
c = Circuit(2)
|
||||||
|
# Add some gates
|
||||||
|
c.add(gates.H(0))
|
||||||
|
c.add(gates.H(1))
|
||||||
|
|
||||||
|
# Execute the circuit and obtain the final state
|
||||||
|
result = c()
|
||||||
|
|
||||||
|
print(result.state())
|
||||||
|
```
|
||||||
|
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Dense vector computation using multi node through MPI
|
||||||
|
computation_settings = {
|
||||||
|
"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.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mpirun -n 4 -hostfile $node_list python test.py
|
||||||
|
```
|
||||||
|
|||||||
323
flake.lock
generated
Normal file
323
flake.lock
generated
Normal file
@@ -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
|
||||||
|
}
|
||||||
61
flake.nix
Normal file
61
flake.nix
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
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"];
|
||||||
|
};
|
||||||
|
version = "3.11";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
nixConfig = {
|
||||||
|
extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=";
|
||||||
|
extra-substituters = "https://devenv.cachix.org";
|
||||||
|
};
|
||||||
|
}
|
||||||
1735
poetry.lock
generated
Normal file
1735
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,59 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
[tool.poetry]
|
||||||
|
name = "qibotn"
|
||||||
|
version = "0.0.1"
|
||||||
|
description = "A tensor-network translation module for Qibo"
|
||||||
|
authors = ["The Qibo team"]
|
||||||
|
license = "Apache License 2.0"
|
||||||
|
readme = "README.md"
|
||||||
|
homepage = "https://qibo.science/"
|
||||||
|
repository = "https://github.com/qiboteam/qibotn/"
|
||||||
|
documentation = "https://qibo.science/docs/qibotn/stable"
|
||||||
|
keywords = []
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Topic :: Scientific/Engineering :: Physics",
|
||||||
|
]
|
||||||
|
packages = [{ include = "qibotn", from = "src" }]
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.9,<3.12"
|
||||||
|
qibo = "^0.2.4"
|
||||||
|
quimb = { version = "^1.6.0", extras = ["tensor"] }
|
||||||
|
cupy = { version = "^11.6.0", optional = true }
|
||||||
|
cuquantum-python-cu11 = { version = "^23.3.0", optional = true }
|
||||||
|
|
||||||
|
[tool.poetry.extras]
|
||||||
|
cuda = ["cupy", "cuquantum-python-cu11"]
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
ipython = "^7.0.0"
|
||||||
|
|
||||||
|
[tool.poetry.group.tests]
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[tool.poetry.group.tests.dependencies]
|
||||||
|
pytest = "^8.0.0"
|
||||||
|
pytest-cov = "^4.1.0"
|
||||||
|
pytest-env = "^1.1.3"
|
||||||
|
|
||||||
|
[tool.poetry.group.analysis]
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[tool.poetry.group.analysis.dependencies]
|
||||||
|
pylint = "^3.0.3"
|
||||||
|
|
||||||
|
[tool.poe.tasks]
|
||||||
|
test = "pytest"
|
||||||
|
lint = "pylint src --errors-only"
|
||||||
|
lint-warnings = "pylint src --exit-zero"
|
||||||
|
|
||||||
|
[tool.pylint.main]
|
||||||
|
ignored-modules = ["cupy", "cuquantum", "mpi4py"]
|
||||||
|
|
||||||
[tool.pylint.reports]
|
[tool.pylint.reports]
|
||||||
output-format = "colorized"
|
output-format = "colorized"
|
||||||
|
|
||||||
|
|||||||
65
setup.py
65
setup.py
@@ -1,65 +0,0 @@
|
|||||||
import pathlib
|
|
||||||
import re
|
|
||||||
|
|
||||||
from setuptools import find_packages, setup
|
|
||||||
|
|
||||||
HERE = pathlib.Path(__file__).parent.absolute()
|
|
||||||
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"""
|
|
||||||
version_file = HERE / "src" / PACKAGE / "__init__.py"
|
|
||||||
version_regex = r"^__version__ = ['\"]([^'\"]*)['\"]"
|
|
||||||
|
|
||||||
initfile = version_file.read_text(encoding="utf-8")
|
|
||||||
matched = re.search(version_regex, initfile, re.M)
|
|
||||||
|
|
||||||
if matched is not None:
|
|
||||||
return matched.group(1)
|
|
||||||
return "0.0.0"
|
|
||||||
|
|
||||||
|
|
||||||
# load long description from README
|
|
||||||
setup(
|
|
||||||
name="qibotn",
|
|
||||||
version=version(),
|
|
||||||
description="A tensor-network translation module for quantum computing",
|
|
||||||
author="The Qibo team",
|
|
||||||
author_email="",
|
|
||||||
url="https://github.com/qiboteam/qibotn",
|
|
||||||
packages=find_packages("src"),
|
|
||||||
package_dir={"": "src"},
|
|
||||||
package_data={"": ["*.out", "*.yml"]},
|
|
||||||
include_package_data=True,
|
|
||||||
zip_safe=False,
|
|
||||||
classifiers=[
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Topic :: Scientific/Engineering :: Physics",
|
|
||||||
],
|
|
||||||
install_requires=[
|
|
||||||
"qibo>=0.1.10",
|
|
||||||
"qibojit>=0.0.7",
|
|
||||||
"quimb[tensor]>=1.6.0",
|
|
||||||
],
|
|
||||||
extras_require={
|
|
||||||
"docs": [],
|
|
||||||
"tests": [
|
|
||||||
"pytest>=7.2.0",
|
|
||||||
"pytest-cov>=4.0.0",
|
|
||||||
"pytest-env>=0.8.1",
|
|
||||||
],
|
|
||||||
"analysis": [
|
|
||||||
"pylint>=2.16.0",
|
|
||||||
],
|
|
||||||
"cuda": [
|
|
||||||
"cupy>=11.6.0",
|
|
||||||
"cuquantum-python-cu11>=23.3.0",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
python_requires=">=3.8.0",
|
|
||||||
long_description=(HERE / "README.md").read_text(encoding="utf-8"),
|
|
||||||
long_description_content_type="text/markdown",
|
|
||||||
)
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import cupy as cp
|
|
||||||
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
|
|
||||||
converted to an equivalent TN expression through the class function
|
|
||||||
state_vector_operands() following the Einstein summation convention in the
|
|
||||||
interleave format.
|
|
||||||
|
|
||||||
See document for detail of the format: https://docs.nvidia.com/cuda/cuquantum/python/api/generated/cuquantum.contract.html
|
|
||||||
|
|
||||||
The output is to be used by cuQuantum's contract() for computation of the
|
|
||||||
state vectors of the circuit.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, circuit, dtype="complex128"):
|
|
||||||
self.backend = cp
|
|
||||||
self.dtype = getattr(self.backend, dtype)
|
|
||||||
self.init_basis_map(self.backend, dtype)
|
|
||||||
self.init_intermediate_circuit(circuit)
|
|
||||||
|
|
||||||
def state_vector_operands(self):
|
|
||||||
input_bitstring = "0" * len(self.active_qubits)
|
|
||||||
|
|
||||||
input_operands = self._get_bitstring_tensors(input_bitstring)
|
|
||||||
|
|
||||||
(
|
|
||||||
mode_labels,
|
|
||||||
qubits_frontier,
|
|
||||||
next_frontier,
|
|
||||||
) = self._init_mode_labels_from_qubits(self.active_qubits)
|
|
||||||
|
|
||||||
gate_mode_labels, gate_operands = self._parse_gates_to_mode_labels_operands(
|
|
||||||
self.gate_tensors, qubits_frontier, next_frontier
|
|
||||||
)
|
|
||||||
|
|
||||||
operands = input_operands + gate_operands
|
|
||||||
mode_labels += gate_mode_labels
|
|
||||||
|
|
||||||
out_list = []
|
|
||||||
for key in qubits_frontier:
|
|
||||||
out_list.append(qubits_frontier[key])
|
|
||||||
|
|
||||||
operand_exp_interleave = [x for y in zip(operands, mode_labels) for x in y]
|
|
||||||
operand_exp_interleave.append(out_list)
|
|
||||||
return operand_exp_interleave
|
|
||||||
|
|
||||||
def _init_mode_labels_from_qubits(self, qubits):
|
|
||||||
n = len(qubits)
|
|
||||||
frontier_dict = {q: i for i, q in enumerate(qubits)}
|
|
||||||
mode_labels = [[i] for i in range(n)]
|
|
||||||
return mode_labels, frontier_dict, n
|
|
||||||
|
|
||||||
def _get_bitstring_tensors(self, bitstring):
|
|
||||||
return [self.basis_map[ibit] for ibit in bitstring]
|
|
||||||
|
|
||||||
def _parse_gates_to_mode_labels_operands(
|
|
||||||
self, gates, qubits_frontier, next_frontier
|
|
||||||
):
|
|
||||||
mode_labels = []
|
|
||||||
operands = []
|
|
||||||
|
|
||||||
for tensor, gate_qubits in gates:
|
|
||||||
operands.append(tensor)
|
|
||||||
input_mode_labels = []
|
|
||||||
output_mode_labels = []
|
|
||||||
for q in gate_qubits:
|
|
||||||
input_mode_labels.append(qubits_frontier[q])
|
|
||||||
output_mode_labels.append(next_frontier)
|
|
||||||
qubits_frontier[q] = next_frontier
|
|
||||||
next_frontier += 1
|
|
||||||
mode_labels.append(output_mode_labels + input_mode_labels)
|
|
||||||
return mode_labels, operands
|
|
||||||
|
|
||||||
def op_shape_from_qubits(self, nqubits):
|
|
||||||
"""Modify tensor to cuQuantum shape
|
|
||||||
(qubit_states,input_output) * qubits_involved
|
|
||||||
"""
|
|
||||||
return (2, 2) * nqubits
|
|
||||||
|
|
||||||
def init_intermediate_circuit(self, circuit):
|
|
||||||
self.gate_tensors = []
|
|
||||||
gates_qubits = []
|
|
||||||
|
|
||||||
for gate in circuit.queue:
|
|
||||||
gate_qubits = gate.control_qubits + gate.target_qubits
|
|
||||||
gates_qubits.extend(gate_qubits)
|
|
||||||
|
|
||||||
# self.gate_tensors is to extract into a list the gate matrix together with the qubit id that it is acting on
|
|
||||||
# https://github.com/NVIDIA/cuQuantum/blob/6b6339358f859ea930907b79854b90b2db71ab92/python/cuquantum/cutensornet/_internal/circuit_parser_utils_cirq.py#L32
|
|
||||||
required_shape = self.op_shape_from_qubits(len(gate_qubits))
|
|
||||||
self.gate_tensors.append(
|
|
||||||
(
|
|
||||||
cp.asarray(gate.matrix(), dtype=self.dtype).reshape(required_shape),
|
|
||||||
gate_qubits,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# self.active_qubits is to identify qubits with at least 1 gate acting on it in the whole circuit.
|
|
||||||
self.active_qubits = np.unique(gates_qubits)
|
|
||||||
|
|
||||||
def init_basis_map(self, backend, dtype):
|
|
||||||
asarray = backend.asarray
|
|
||||||
state_0 = asarray([1, 0], dtype=dtype)
|
|
||||||
state_1 = asarray([0, 1], dtype=dtype)
|
|
||||||
|
|
||||||
self.basis_map = {"0": state_0, "1": state_1}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import argparse
|
|
||||||
|
|
||||||
import qibotn.quimb
|
|
||||||
|
|
||||||
|
|
||||||
def parser():
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument(
|
|
||||||
"--nqubits", default=10, type=int, help="Number of quibits in the circuits."
|
|
||||||
)
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def main(args: argparse.Namespace):
|
|
||||||
print("Testing for %d nqubits" % (args.nqubits))
|
|
||||||
qibotn.quimb.eval(args.nqubits, args.qasm_circ, args.init_state)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main(parser())
|
|
||||||
2
src/qibotn/backends/__init__.py
Normal file
2
src/qibotn/backends/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from qibotn.backends.cutensornet import CuTensorNet
|
||||||
|
from qibotn.backends.quimb import QuimbBackend
|
||||||
188
src/qibotn/backends/cutensornet.py
Normal file
188
src/qibotn/backends/cutensornet.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import cuquantum # pylint: disable=import-error
|
||||||
|
import numpy as np
|
||||||
|
from qibo.backends.numpy import NumpyBackend
|
||||||
|
from qibo.config import raise_error
|
||||||
|
from qibo.result import QuantumState
|
||||||
|
|
||||||
|
CUDA_TYPES = {
|
||||||
|
"complex64": (
|
||||||
|
cuquantum.cudaDataType.CUDA_C_32F,
|
||||||
|
cuquantum.ComputeType.COMPUTE_32F,
|
||||||
|
),
|
||||||
|
"complex128": (
|
||||||
|
cuquantum.cudaDataType.CUDA_C_64F,
|
||||||
|
cuquantum.ComputeType.COMPUTE_64F,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CuTensorNet(NumpyBackend): # pragma: no cover
|
||||||
|
# CI does not test for GPU
|
||||||
|
|
||||||
|
def __init__(self, runcard):
|
||||||
|
super().__init__()
|
||||||
|
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)
|
||||||
|
|
||||||
|
expectation_enabled_value = runcard.get("expectation_enabled")
|
||||||
|
if expectation_enabled_value is True:
|
||||||
|
self.expectation_enabled = True
|
||||||
|
self.pauli_string_pattern = "XXXZ"
|
||||||
|
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
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise TypeError("expectation_enabled has an unexpected type")
|
||||||
|
|
||||||
|
mps_enabled_value = runcard.get("MPS_enabled")
|
||||||
|
if mps_enabled_value is True:
|
||||||
|
self.MPS_enabled = True
|
||||||
|
self.gate_algo = {
|
||||||
|
"qr_method": False,
|
||||||
|
"svd_method": {
|
||||||
|
"partition": "UV",
|
||||||
|
"abs_cutoff": 1e-12,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
elif mps_enabled_value is False:
|
||||||
|
self.MPS_enabled = False
|
||||||
|
elif isinstance(mps_enabled_value, dict):
|
||||||
|
self.MPS_enabled = True
|
||||||
|
self.gate_algo = mps_enabled_value
|
||||||
|
else:
|
||||||
|
raise TypeError("MPS_enabled has an unexpected type")
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.MPI_enabled = False
|
||||||
|
self.MPS_enabled = False
|
||||||
|
self.NCCL_enabled = False
|
||||||
|
self.expectation_enabled = False
|
||||||
|
|
||||||
|
self.name = "qibotn"
|
||||||
|
self.cuquantum = cuquantum
|
||||||
|
self.cutn = cutn
|
||||||
|
self.platform = "cutensornet"
|
||||||
|
self.versions["cuquantum"] = self.cuquantum.__version__
|
||||||
|
self.supports_multigpu = True
|
||||||
|
self.handle = self.cutn.create()
|
||||||
|
|
||||||
|
def apply_gate(self, gate, state, nqubits): # pragma: no cover
|
||||||
|
raise_error(NotImplementedError, "QiboTN cannot apply gates directly.")
|
||||||
|
|
||||||
|
def apply_gate_density_matrix(self, gate, state, nqubits): # pragma: no cover
|
||||||
|
raise_error(NotImplementedError, "QiboTN cannot apply gates directly.")
|
||||||
|
|
||||||
|
def assign_measurements(self, measurement_map, circuit_result):
|
||||||
|
raise_error(NotImplementedError, "Not implemented in QiboTN.")
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if hasattr(self, "cutn"):
|
||||||
|
self.cutn.destroy(self.handle)
|
||||||
|
|
||||||
|
def set_precision(self, precision):
|
||||||
|
if precision != self.precision:
|
||||||
|
super().set_precision(precision)
|
||||||
|
|
||||||
|
def cuda_type(self, dtype="complex64"):
|
||||||
|
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
|
||||||
|
"""Executes a quantum circuit.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
circuit (:class:`qibo.models.circuit.Circuit`): Circuit to execute.
|
||||||
|
initial_state (:class:`qibo.models.circuit.Circuit`): Circuit to prepare the initial state.
|
||||||
|
If ``None`` the default ``|00...0>`` state is used.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
xxx.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import qibotn.eval as eval
|
||||||
|
|
||||||
|
if initial_state is not None:
|
||||||
|
raise_error(NotImplementedError, "QiboTN cannot support initial state.")
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.MPI_enabled == False
|
||||||
|
and self.MPS_enabled == False
|
||||||
|
and self.NCCL_enabled == False
|
||||||
|
and self.expectation_enabled == False
|
||||||
|
):
|
||||||
|
state = eval.dense_vector_tn(circuit, self.dtype)
|
||||||
|
elif (
|
||||||
|
self.MPI_enabled == False
|
||||||
|
and self.MPS_enabled == True
|
||||||
|
and self.NCCL_enabled == False
|
||||||
|
and self.expectation_enabled == False
|
||||||
|
):
|
||||||
|
state = eval.dense_vector_mps(circuit, self.gate_algo, self.dtype)
|
||||||
|
elif (
|
||||||
|
self.MPI_enabled == True
|
||||||
|
and self.MPS_enabled == False
|
||||||
|
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 = np.array(0)
|
||||||
|
elif (
|
||||||
|
self.MPI_enabled == False
|
||||||
|
and self.MPS_enabled == False
|
||||||
|
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 = np.array(0)
|
||||||
|
elif (
|
||||||
|
self.MPI_enabled == False
|
||||||
|
and self.MPS_enabled == False
|
||||||
|
and self.NCCL_enabled == False
|
||||||
|
and self.expectation_enabled == True
|
||||||
|
):
|
||||||
|
state = eval.expectation_pauli_tn(
|
||||||
|
circuit, self.dtype, self.pauli_string_pattern
|
||||||
|
)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
if rank > 0:
|
||||||
|
state = np.array(0)
|
||||||
|
elif (
|
||||||
|
self.MPI_enabled == False
|
||||||
|
and self.MPS_enabled == False
|
||||||
|
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
|
||||||
|
)
|
||||||
|
if rank > 0:
|
||||||
|
state = np.array(0)
|
||||||
|
else:
|
||||||
|
raise_error(NotImplementedError, "Compute type not supported.")
|
||||||
|
|
||||||
|
if return_array:
|
||||||
|
return state.flatten()
|
||||||
|
else:
|
||||||
|
return QuantumState(state.flatten())
|
||||||
85
src/qibotn/backends/quimb.py
Normal file
85
src/qibotn/backends/quimb.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
from qibo.backends.numpy import NumpyBackend
|
||||||
|
from qibo.config import raise_error
|
||||||
|
from qibo.result import QuantumState
|
||||||
|
|
||||||
|
|
||||||
|
class QuimbBackend(NumpyBackend):
|
||||||
|
|
||||||
|
def __init__(self, runcard):
|
||||||
|
super().__init__()
|
||||||
|
import quimb # 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)
|
||||||
|
self.expectation_enabled = runcard.get("expectation_enabled", False)
|
||||||
|
|
||||||
|
mps_enabled_value = runcard.get("MPS_enabled")
|
||||||
|
if mps_enabled_value is True:
|
||||||
|
self.mps_opts = {"method": "svd", "cutoff": 1e-6, "cutoff_mod": "abs"}
|
||||||
|
elif mps_enabled_value is False:
|
||||||
|
self.mps_opts = False
|
||||||
|
elif isinstance(mps_enabled_value, dict):
|
||||||
|
self.mps_opts = mps_enabled_value
|
||||||
|
else:
|
||||||
|
raise TypeError("MPS_enabled has an unexpected type")
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.MPI_enabled = False
|
||||||
|
self.MPS_enabled = False
|
||||||
|
self.NCCL_enabled = False
|
||||||
|
self.expectation_enabled = False
|
||||||
|
|
||||||
|
self.name = "qibotn"
|
||||||
|
self.quimb = quimb
|
||||||
|
self.platform = "QuimbBackend"
|
||||||
|
self.versions["quimb"] = self.quimb.__version__
|
||||||
|
|
||||||
|
def apply_gate(self, gate, state, nqubits): # pragma: no cover
|
||||||
|
raise_error(NotImplementedError, "QiboTN cannot apply gates directly.")
|
||||||
|
|
||||||
|
def apply_gate_density_matrix(self, gate, state, nqubits): # pragma: no cover
|
||||||
|
raise_error(NotImplementedError, "QiboTN cannot apply gates directly.")
|
||||||
|
|
||||||
|
def assign_measurements(self, measurement_map, circuit_result):
|
||||||
|
raise_error(NotImplementedError, "Not implemented in QiboTN.")
|
||||||
|
|
||||||
|
def set_precision(self, precision):
|
||||||
|
if precision != self.precision:
|
||||||
|
super().set_precision(precision)
|
||||||
|
|
||||||
|
def execute_circuit(
|
||||||
|
self, circuit, initial_state=None, nshots=None, return_array=False
|
||||||
|
): # pragma: no cover
|
||||||
|
"""Executes a quantum circuit.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
circuit (:class:`qibo.models.circuit.Circuit`): Circuit to execute.
|
||||||
|
initial_state (:class:`qibo.models.circuit.Circuit`): Circuit to prepare the initial state.
|
||||||
|
If ``None`` the default ``|00...0>`` state is used.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
xxx.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import qibotn.eval_qu as eval
|
||||||
|
|
||||||
|
if self.MPI_enabled == True:
|
||||||
|
raise_error(NotImplementedError, "QiboTN quimb backend cannot support MPI.")
|
||||||
|
if self.NCCL_enabled == True:
|
||||||
|
raise_error(
|
||||||
|
NotImplementedError, "QiboTN quimb backend cannot support NCCL."
|
||||||
|
)
|
||||||
|
if self.expectation_enabled == True:
|
||||||
|
raise_error(
|
||||||
|
NotImplementedError, "QiboTN quimb backend cannot support expectation"
|
||||||
|
)
|
||||||
|
|
||||||
|
state = eval.dense_vector_tn_qu(
|
||||||
|
circuit.to_qasm(), initial_state, self.mps_opts, backend="numpy"
|
||||||
|
)
|
||||||
|
|
||||||
|
if return_array:
|
||||||
|
return state.flatten()
|
||||||
|
else:
|
||||||
|
return QuantumState(state.flatten())
|
||||||
206
src/qibotn/circuit_convertor.py
Normal file
206
src/qibotn/circuit_convertor.py
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
import cupy as cp
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# Reference: https://github.com/NVIDIA/cuQuantum/tree/main/python/samples/cutensornet/circuit_converter
|
||||||
|
|
||||||
|
|
||||||
|
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 converted to an
|
||||||
|
equivalent TN expression through the class function state_vector_operands()
|
||||||
|
following the Einstein summation convention in the interleave format.
|
||||||
|
|
||||||
|
See document for detail of the format: https://docs.nvidia.com/cuda/cuquantum/python/api/generated/cuquantum.contract.html
|
||||||
|
|
||||||
|
The output is to be used by cuQuantum's contract() for computation of the
|
||||||
|
state vectors of the circuit.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, circuit, dtype="complex128"):
|
||||||
|
self.backend = cp
|
||||||
|
self.dtype = getattr(self.backend, dtype)
|
||||||
|
self.init_basis_map(self.backend, dtype)
|
||||||
|
self.init_intermediate_circuit(circuit)
|
||||||
|
self.circuit = circuit
|
||||||
|
|
||||||
|
def state_vector_operands(self):
|
||||||
|
input_bitstring = "0" * len(self.active_qubits)
|
||||||
|
|
||||||
|
input_operands = self._get_bitstring_tensors(input_bitstring)
|
||||||
|
|
||||||
|
(
|
||||||
|
mode_labels,
|
||||||
|
qubits_frontier,
|
||||||
|
next_frontier,
|
||||||
|
) = self._init_mode_labels_from_qubits(self.active_qubits)
|
||||||
|
|
||||||
|
gate_mode_labels, gate_operands = self._parse_gates_to_mode_labels_operands(
|
||||||
|
self.gate_tensors, qubits_frontier, next_frontier
|
||||||
|
)
|
||||||
|
|
||||||
|
operands = input_operands + gate_operands
|
||||||
|
mode_labels += gate_mode_labels
|
||||||
|
|
||||||
|
out_list = []
|
||||||
|
for key in qubits_frontier:
|
||||||
|
out_list.append(qubits_frontier[key])
|
||||||
|
|
||||||
|
operand_exp_interleave = [x for y in zip(operands, mode_labels) for x in y]
|
||||||
|
operand_exp_interleave.append(out_list)
|
||||||
|
return operand_exp_interleave
|
||||||
|
|
||||||
|
def _init_mode_labels_from_qubits(self, qubits):
|
||||||
|
n = len(qubits)
|
||||||
|
frontier_dict = {q: i for i, q in enumerate(qubits)}
|
||||||
|
mode_labels = [[i] for i in range(n)]
|
||||||
|
return mode_labels, frontier_dict, n
|
||||||
|
|
||||||
|
def _get_bitstring_tensors(self, bitstring):
|
||||||
|
return [self.basis_map[ibit] for ibit in bitstring]
|
||||||
|
|
||||||
|
def _parse_gates_to_mode_labels_operands(
|
||||||
|
self, gates, qubits_frontier, next_frontier
|
||||||
|
):
|
||||||
|
mode_labels = []
|
||||||
|
operands = []
|
||||||
|
|
||||||
|
for tensor, gate_qubits in gates:
|
||||||
|
operands.append(tensor)
|
||||||
|
input_mode_labels = []
|
||||||
|
output_mode_labels = []
|
||||||
|
for q in gate_qubits:
|
||||||
|
input_mode_labels.append(qubits_frontier[q])
|
||||||
|
output_mode_labels.append(next_frontier)
|
||||||
|
qubits_frontier[q] = next_frontier
|
||||||
|
next_frontier += 1
|
||||||
|
mode_labels.append(output_mode_labels + input_mode_labels)
|
||||||
|
return mode_labels, operands
|
||||||
|
|
||||||
|
def op_shape_from_qubits(self, nqubits):
|
||||||
|
"""Modify tensor to cuQuantum shape (qubit_states,input_output) *
|
||||||
|
qubits_involved."""
|
||||||
|
return (2, 2) * nqubits
|
||||||
|
|
||||||
|
def init_intermediate_circuit(self, circuit):
|
||||||
|
self.gate_tensors = []
|
||||||
|
gates_qubits = []
|
||||||
|
|
||||||
|
for gate in circuit.queue:
|
||||||
|
gate_qubits = gate.control_qubits + gate.target_qubits
|
||||||
|
gates_qubits.extend(gate_qubits)
|
||||||
|
|
||||||
|
# self.gate_tensors is to extract into a list the gate matrix together with the qubit id that it is acting on
|
||||||
|
# https://github.com/NVIDIA/cuQuantum/blob/6b6339358f859ea930907b79854b90b2db71ab92/python/cuquantum/cutensornet/_internal/circuit_parser_utils_cirq.py#L32
|
||||||
|
required_shape = self.op_shape_from_qubits(len(gate_qubits))
|
||||||
|
self.gate_tensors.append(
|
||||||
|
(
|
||||||
|
cp.asarray(gate.matrix(), dtype=self.dtype).reshape(required_shape),
|
||||||
|
gate_qubits,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# self.active_qubits is to identify qubits with at least 1 gate acting on it in the whole circuit.
|
||||||
|
self.active_qubits = np.unique(gates_qubits)
|
||||||
|
|
||||||
|
def init_basis_map(self, backend, dtype):
|
||||||
|
asarray = backend.asarray
|
||||||
|
state_0 = asarray([1, 0], dtype=dtype)
|
||||||
|
state_1 = asarray([0, 1], dtype=dtype)
|
||||||
|
|
||||||
|
self.basis_map = {"0": state_0, "1": state_1}
|
||||||
|
|
||||||
|
def init_inverse_circuit(self, circuit):
|
||||||
|
self.gate_tensors_inverse = []
|
||||||
|
gates_qubits_inverse = []
|
||||||
|
|
||||||
|
for gate in circuit.queue:
|
||||||
|
gate_qubits = gate.control_qubits + gate.target_qubits
|
||||||
|
gates_qubits_inverse.extend(gate_qubits)
|
||||||
|
|
||||||
|
# self.gate_tensors is to extract into a list the gate matrix together with the qubit id that it is acting on
|
||||||
|
# https://github.com/NVIDIA/cuQuantum/blob/6b6339358f859ea930907b79854b90b2db71ab92/python/cuquantum/cutensornet/_internal/circuit_parser_utils_cirq.py#L32
|
||||||
|
required_shape = self.op_shape_from_qubits(len(gate_qubits))
|
||||||
|
self.gate_tensors_inverse.append(
|
||||||
|
(
|
||||||
|
cp.asarray(gate.matrix()).reshape(required_shape),
|
||||||
|
gate_qubits,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# self.active_qubits is to identify qubits with at least 1 gate acting on it in the whole circuit.
|
||||||
|
self.active_qubits_inverse = np.unique(gates_qubits_inverse)
|
||||||
|
|
||||||
|
def get_pauli_gates(self, pauli_map, dtype="complex128", backend=cp):
|
||||||
|
"""Populate the gates for all pauli operators.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
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 in pauli_map.items():
|
||||||
|
operand = operand_map.get(pauli_char)
|
||||||
|
if operand is None:
|
||||||
|
raise ValueError("pauli string character must be one of I/X/Y/Z")
|
||||||
|
gates.append((operand, (qubit,)))
|
||||||
|
return gates
|
||||||
|
|
||||||
|
def expectation_operands(self, pauli_string):
|
||||||
|
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,
|
||||||
|
qubits_frontier,
|
||||||
|
next_frontier,
|
||||||
|
) = self._init_mode_labels_from_qubits(range(self.circuit.nqubits))
|
||||||
|
|
||||||
|
gate_mode_labels, gate_operands = self._parse_gates_to_mode_labels_operands(
|
||||||
|
self.gate_tensors, qubits_frontier, next_frontier
|
||||||
|
)
|
||||||
|
|
||||||
|
operands = input_operands + gate_operands
|
||||||
|
mode_labels += gate_mode_labels
|
||||||
|
|
||||||
|
self.init_inverse_circuit(self.circuit.invert())
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
(
|
||||||
|
gate_mode_labels_inverse,
|
||||||
|
gate_operands_inverse,
|
||||||
|
) = self._parse_gates_to_mode_labels_operands(
|
||||||
|
gates_inverse, qubits_frontier, next_frontier
|
||||||
|
)
|
||||||
|
mode_labels = (
|
||||||
|
mode_labels
|
||||||
|
+ gate_mode_labels_inverse
|
||||||
|
+ [[qubits_frontier[ix]] for ix in range(self.circuit.nqubits)]
|
||||||
|
)
|
||||||
|
operands = operands + gate_operands_inverse + operands[: self.circuit.nqubits]
|
||||||
|
|
||||||
|
operand_exp_interleave = [x for y in zip(operands, mode_labels) for x in y]
|
||||||
|
|
||||||
|
return operand_exp_interleave
|
||||||
@@ -2,8 +2,8 @@ import cupy as cp
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from cuquantum import cutensornet as cutn
|
from cuquantum import cutensornet as cutn
|
||||||
|
|
||||||
from qibotn.MPSUtils import apply_gate, initial
|
from qibotn.circuit_convertor import QiboCircuitToEinsum
|
||||||
from qibotn.QiboCircuitConvertor import QiboCircuitToEinsum
|
from qibotn.mps_utils import apply_gate, initial
|
||||||
|
|
||||||
|
|
||||||
class QiboCircuitToMPS:
|
class QiboCircuitToMPS:
|
||||||
@@ -21,7 +21,7 @@ class QiboCircuitToMPS:
|
|||||||
self.handle = cutn.create()
|
self.handle = cutn.create()
|
||||||
self.dtype = dtype
|
self.dtype = dtype
|
||||||
self.mps_tensors = initial(self.num_qubits, dtype=dtype)
|
self.mps_tensors = initial(self.num_qubits, dtype=dtype)
|
||||||
circuitconvertor = QiboCircuitToEinsum(circ_qibo)
|
circuitconvertor = QiboCircuitToEinsum(circ_qibo, dtype=dtype)
|
||||||
|
|
||||||
for gate, qubits in circuitconvertor.gate_tensors:
|
for gate, qubits in circuitconvertor.gate_tensors:
|
||||||
# mapping from qubits to qubit indices
|
# mapping from qubits to qubit indices
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import multiprocessing
|
|
||||||
|
|
||||||
import cupy as cp
|
|
||||||
from cupy.cuda.runtime import getDeviceCount
|
|
||||||
from cuquantum import contract
|
|
||||||
from cuquantum import cutensornet as cutn
|
|
||||||
|
|
||||||
from qibotn.mps_contraction_helper import MPSContractionHelper
|
|
||||||
from qibotn.QiboCircuitConvertor import QiboCircuitToEinsum
|
|
||||||
from qibotn.QiboCircuitToMPS import QiboCircuitToMPS
|
|
||||||
|
|
||||||
|
|
||||||
def eval(qibo_circ, datatype):
|
|
||||||
myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype)
|
|
||||||
return contract(*myconvertor.state_vector_operands())
|
|
||||||
|
|
||||||
|
|
||||||
def eval_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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from mpi4py import MPI # this line initializes MPI
|
|
||||||
|
|
||||||
ncpu_threads = multiprocessing.cpu_count() // 2
|
|
||||||
|
|
||||||
comm = MPI.COMM_WORLD
|
|
||||||
rank = comm.Get_rank()
|
|
||||||
device_id = rank % getDeviceCount()
|
|
||||||
cp.cuda.Device(device_id).use()
|
|
||||||
|
|
||||||
handle = cutn.create()
|
|
||||||
cutn.distributed_reset_configuration(handle, *cutn.get_mpi_comm_pointer(comm))
|
|
||||||
network_opts = cutn.NetworkOptions(handle=handle, blocking="auto")
|
|
||||||
|
|
||||||
# Perform circuit conversion
|
|
||||||
myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype)
|
|
||||||
operands_interleave = myconvertor.state_vector_operands()
|
|
||||||
|
|
||||||
# Pathfinder: To search for the optimal path. Optimal path are assigned to path and info attribute of the network object.
|
|
||||||
network = cutn.Network(*operands_interleave, options=network_opts)
|
|
||||||
network.contract_path(optimize={"samples": n_samples, "threads": ncpu_threads})
|
|
||||||
|
|
||||||
# Execution: To execute the contraction using the optimal path found previously
|
|
||||||
result = network.contract()
|
|
||||||
|
|
||||||
cutn.destroy(handle)
|
|
||||||
|
|
||||||
return result, rank
|
|
||||||
|
|
||||||
|
|
||||||
def eval_mps(qibo_circ, gate_algo, datatype):
|
|
||||||
myconvertor = QiboCircuitToMPS(qibo_circ, gate_algo, dtype=datatype)
|
|
||||||
mps_helper = MPSContractionHelper(myconvertor.num_qubits)
|
|
||||||
|
|
||||||
return mps_helper.contract_state_vector(
|
|
||||||
myconvertor.mps_tensors, {"handle": myconvertor.handle}
|
|
||||||
)
|
|
||||||
352
src/qibotn/eval.py
Normal file
352
src/qibotn/eval.py
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
import cupy as cp
|
||||||
|
from cupy.cuda.runtime import getDeviceCount
|
||||||
|
from cuquantum import contract
|
||||||
|
|
||||||
|
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."""
|
||||||
|
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."""
|
||||||
|
myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype)
|
||||||
|
return contract(
|
||||||
|
*myconvertor.expectation_operands(
|
||||||
|
pauli_string_gen(qibo_circ.nqubits, 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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
# Perform circuit conversion
|
||||||
|
myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype)
|
||||||
|
|
||||||
|
operands = myconvertor.state_vector_operands()
|
||||||
|
|
||||||
|
# Assign the device for each process.
|
||||||
|
device_id = rank % getDeviceCount()
|
||||||
|
|
||||||
|
# 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)}}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype)
|
||||||
|
operands = myconvertor.state_vector_operands()
|
||||||
|
|
||||||
|
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)}}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
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.
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
return result, rank
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype)
|
||||||
|
operands = myconvertor.expectation_operands(
|
||||||
|
pauli_string_gen(qibo_circ.nqubits, pauli_string_pattern)
|
||||||
|
)
|
||||||
|
|
||||||
|
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)}}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
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.
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
return result, rank
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
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()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
# 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)}}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def dense_vector_mps(qibo_circ, gate_algo, datatype):
|
||||||
|
"""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)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
31
src/qibotn/eval_qu.py
Normal file
31
src/qibotn/eval_qu.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import numpy as np
|
||||||
|
import quimb.tensor as qtn
|
||||||
|
|
||||||
|
|
||||||
|
def init_state_tn(nqubits, init_state_sv):
|
||||||
|
"""Create a matrix product state 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, mps_opts, backend="numpy"):
|
||||||
|
"""Evaluate QASM with Quimb.
|
||||||
|
|
||||||
|
backend (quimb): numpy, cupy, jax. Passed to ``opt_einsum``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if initial_state is not None:
|
||||||
|
nqubits = int(np.log2(len(initial_state)))
|
||||||
|
initial_state = init_state_tn(nqubits, initial_state)
|
||||||
|
|
||||||
|
circ_cls = qtn.circuit.CircuitMPS if mps_opts else qtn.circuit.Circuit
|
||||||
|
circ_quimb = circ_cls.from_openqasm2_str(
|
||||||
|
qasm, psi0=initial_state, gate_opts=mps_opts
|
||||||
|
)
|
||||||
|
|
||||||
|
interim = circ_quimb.psi.full_simplify(seq="DRC")
|
||||||
|
amplitudes = interim.to_dense(backend=backend)
|
||||||
|
|
||||||
|
return amplitudes
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
from cuquantum import CircuitToEinsum, contract, contract_path, 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:
|
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`.
|
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:
|
A concrete example on how the modes are populated for a 7-site MPS is provided below:
|
||||||
@@ -41,8 +42,8 @@ class MPSContractionHelper:
|
|||||||
]
|
]
|
||||||
|
|
||||||
def contract_norm(self, mps_tensors, options=None):
|
def contract_norm(self, mps_tensors, options=None):
|
||||||
"""
|
"""Contract the corresponding tensor network to form the norm of the
|
||||||
Contract the corresponding tensor network to form the norm of the MPS.
|
MPS.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mps_tensors: A list of rank-3 ndarray-like tensor objects.
|
mps_tensors: A list of rank-3 ndarray-like tensor objects.
|
||||||
@@ -62,8 +63,8 @@ class MPSContractionHelper:
|
|||||||
return self._contract(interleaved_inputs, options=options).real
|
return self._contract(interleaved_inputs, options=options).real
|
||||||
|
|
||||||
def contract_state_vector(self, mps_tensors, options=None):
|
def contract_state_vector(self, mps_tensors, options=None):
|
||||||
"""
|
"""Contract the corresponding tensor network to form the state vector
|
||||||
Contract the corresponding tensor network to form the state vector representation of the MPS.
|
representation of the MPS.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mps_tensors: A list of rank-3 ndarray-like tensor objects.
|
mps_tensors: A list of rank-3 ndarray-like tensor objects.
|
||||||
@@ -84,8 +85,8 @@ class MPSContractionHelper:
|
|||||||
def contract_expectation(
|
def contract_expectation(
|
||||||
self, mps_tensors, operator, qubits, options=None, normalize=False
|
self, mps_tensors, operator, qubits, options=None, normalize=False
|
||||||
):
|
):
|
||||||
"""
|
"""Contract the corresponding tensor network to form the expectation of
|
||||||
Contract the corresponding tensor network to form the state vector representation of the MPS.
|
the MPS.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mps_tensors: A list of rank-3 ndarray-like tensor objects.
|
mps_tensors: A list of rank-3 ndarray-like tensor objects.
|
||||||
|
|||||||
@@ -2,20 +2,18 @@ import cupy as cp
|
|||||||
from cuquantum import contract
|
from cuquantum import contract
|
||||||
from cuquantum.cutensornet.experimental import contract_decompose
|
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):
|
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)
|
state_tensor = cp.asarray([1, 0], dtype=dtype).reshape(1, 2, 1)
|
||||||
mps_tensors = [state_tensor] * num_qubits
|
mps_tensors = [state_tensor] * num_qubits
|
||||||
return mps_tensors
|
return mps_tensors
|
||||||
|
|
||||||
|
|
||||||
def mps_site_right_swap(mps_tensors, i, **kwargs):
|
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
|
# contraction followed by QR decomposition
|
||||||
a, _, b = contract_decompose(
|
a, _, b = contract_decompose(
|
||||||
"ipj,jqk->iqj,jpk",
|
"ipj,jqk->iqj,jpk",
|
||||||
@@ -28,8 +26,7 @@ def mps_site_right_swap(mps_tensors, i, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def apply_gate(mps_tensors, gate, qubits, **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:
|
Args:
|
||||||
mps_tensors: A list of rank-3 ndarray-like tensor objects.
|
mps_tensors: A list of rank-3 ndarray-like tensor objects.
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import numpy as np
|
|
||||||
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",
|
|
||||||
):
|
|
||||||
nqubits = circuit.nqubits
|
|
||||||
gate_opt = {}
|
|
||||||
if is_mps:
|
|
||||||
tncirc = qtn.CircuitMPS(nqubits, psi0=psi0)
|
|
||||||
gate_opt["method"] = method
|
|
||||||
gate_opt["cutoff"] = cutoff
|
|
||||||
gate_opt["cutoff_mode"] = cutoff_mode
|
|
||||||
else:
|
|
||||||
tncirc = qtn.Circuit(nqubits, psi0=psi0)
|
|
||||||
|
|
||||||
for gate in circuit.queue:
|
|
||||||
tncirc.apply_gate(
|
|
||||||
gate.name,
|
|
||||||
*gate.parameters,
|
|
||||||
*gate.qubits,
|
|
||||||
parametrize=False if is_mps else (len(gate.parameters) > 0),
|
|
||||||
**gate_opt
|
|
||||||
)
|
|
||||||
|
|
||||||
return tncirc
|
|
||||||
|
|
||||||
|
|
||||||
def init_state_tn(nqubits, init_state_sv):
|
|
||||||
dims = tuple(2 * np.ones(nqubits, dtype=int))
|
|
||||||
|
|
||||||
return qtn.tensor_1d.MatrixProductState.from_dense(init_state_sv, dims)
|
|
||||||
|
|
||||||
|
|
||||||
def eval(qasm: str, init_state, is_mps, backend="numpy"):
|
|
||||||
"""Evaluate QASM with Quimb
|
|
||||||
|
|
||||||
backend (quimb): numpy, cupy, jax. Passed to ``opt_einsum``.
|
|
||||||
|
|
||||||
"""
|
|
||||||
circuit = QiboCircuit.from_qasm(qasm)
|
|
||||||
init_state_mps = init_state_tn(circuit.nqubits, init_state)
|
|
||||||
circ_quimb = from_qibo(circuit, is_mps, psi0=init_state_mps)
|
|
||||||
interim = circ_quimb.psi.full_simplify(seq="DRC")
|
|
||||||
amplitudes = interim.to_dense(backend=backend).flatten()
|
|
||||||
|
|
||||||
return amplitudes
|
|
||||||
@@ -32,14 +32,16 @@ def test_eval(nqubits: int, dtype="complex128"):
|
|||||||
dtype (str): The data type for precision, 'complex64' for single,
|
dtype (str): The data type for precision, 'complex64' for single,
|
||||||
'complex128' for double.
|
'complex128' for double.
|
||||||
"""
|
"""
|
||||||
import qibotn.cutn
|
import qibotn.eval
|
||||||
|
|
||||||
# Test qibo
|
# 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_time, (qibo_circ, result_sv) = time(lambda: qibo_qft(nqubits, swaps=True))
|
qibo_time, (qibo_circ, result_sv) = time(lambda: qibo_qft(nqubits, swaps=True))
|
||||||
|
|
||||||
# Test Cuquantum
|
# Test Cuquantum
|
||||||
cutn_time, result_tn = time(lambda: qibotn.cutn.eval(qibo_circ, dtype).flatten())
|
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 1e-2 * qibo_time < cutn_time < 1e2 * qibo_time
|
||||||
assert np.allclose(result_sv, result_tn), "Resulting dense vectors do not match"
|
assert np.allclose(result_sv, result_tn), "Resulting dense vectors do not match"
|
||||||
@@ -55,7 +57,7 @@ def test_mps(nqubits: int, dtype="complex128"):
|
|||||||
dtype (str): The data type for precision, 'complex64' for single,
|
dtype (str): The data type for precision, 'complex64' for single,
|
||||||
'complex128' for double.
|
'complex128' for double.
|
||||||
"""
|
"""
|
||||||
import qibotn.cutn
|
import qibotn.eval
|
||||||
|
|
||||||
# Test qibo
|
# Test qibo
|
||||||
qibo.set_backend(backend=config.qibo.backend, platform=config.qibo.platform)
|
qibo.set_backend(backend=config.qibo.backend, platform=config.qibo.platform)
|
||||||
@@ -74,7 +76,7 @@ def test_mps(nqubits: int, dtype="complex128"):
|
|||||||
}
|
}
|
||||||
|
|
||||||
cutn_time, result_tn = time(
|
cutn_time, result_tn = time(
|
||||||
lambda: qibotn.cutn.eval_mps(circ_qibo, gate_algo, dtype).flatten()
|
lambda: qibotn.eval.dense_vector_mps(circ_qibo, gate_algo, dtype).flatten()
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"State vector difference: {abs(result_tn - result_sv_cp).max():0.3e}")
|
print(f"State vector difference: {abs(result_tn - result_sv_cp).max():0.3e}")
|
||||||
|
|||||||
@@ -25,29 +25,41 @@ def qibo_qft(nqubits, init_state, swaps):
|
|||||||
[(1, 1e-6, True), (2, 1e-6, False), (5, 1e-3, True), (10, 1e-3, False)],
|
[(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):
|
def test_eval(nqubits: int, tolerance: float, is_mps: bool):
|
||||||
|
"""Evaluate circuit with Quimb backend.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
nqubits (int): Total number of qubits in the system.
|
||||||
|
tolerance (float): Maximum limit allowed for difference in results
|
||||||
|
is_mps (bool): True if state is MPS and False for tensor network structure
|
||||||
|
"""
|
||||||
# hack quimb to use the correct number of processes
|
# hack quimb to use the correct number of processes
|
||||||
# TODO: remove completely, or at least delegate to the backend
|
# TODO: remove completely, or at least delegate to the backend
|
||||||
# implementation
|
# implementation
|
||||||
os.environ["QUIMB_NUM_PROCS"] = str(os.cpu_count())
|
os.environ["QUIMB_NUM_PROCS"] = str(os.cpu_count())
|
||||||
import qibotn.quimb
|
import qibotn.eval_qu
|
||||||
|
|
||||||
init_state = create_init_state(nqubits=nqubits)
|
init_state = create_init_state(nqubits=nqubits)
|
||||||
init_state_tn = copy.deepcopy(init_state)
|
init_state_tn = copy.deepcopy(init_state)
|
||||||
|
|
||||||
# Test qibo
|
# 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_time, (qibo_circ, result_sv) = time(
|
|
||||||
# lambda: qibo_qft(nqubits, init_state, swaps=True)
|
|
||||||
# )
|
|
||||||
qibo_circ, result_sv = qibo_qft(nqubits, init_state, swaps=True)
|
qibo_circ, result_sv = qibo_qft(nqubits, init_state, swaps=True)
|
||||||
|
|
||||||
# Convert to qasm for other backends
|
# Convert to qasm for other backends
|
||||||
qasm_circ = qibo_circ.to_qasm()
|
qasm_circ = qibo_circ.to_qasm()
|
||||||
|
|
||||||
# Test quimb
|
# Test quimb
|
||||||
result_tn = qibotn.quimb.eval(
|
if is_mps:
|
||||||
qasm_circ, init_state_tn, is_mps, backend=config.quimb.backend
|
gate_opt = {}
|
||||||
)
|
gate_opt["method"] = "svd"
|
||||||
|
gate_opt["cutoff"] = 1e-6
|
||||||
|
gate_opt["cutoff_mode"] = "abs"
|
||||||
|
else:
|
||||||
|
gate_opt = None
|
||||||
|
result_tn = qibotn.eval_qu.dense_vector_tn_qu(
|
||||||
|
qasm_circ, init_state_tn, gate_opt, backend=config.quimb.backend
|
||||||
|
).flatten()
|
||||||
|
|
||||||
assert np.allclose(
|
assert np.allclose(
|
||||||
result_sv, result_tn, atol=tolerance
|
result_sv, result_tn, atol=tolerance
|
||||||
Reference in New Issue
Block a user