diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 356b6401f85..5d3c94c5aa8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -234,7 +234,7 @@ jobs: strategy: matrix: platform: [amd64, arm64] - python_version: ['3.12'] # FIXME: add '3.10' back + python_version: ['3.10', '3.12'] cuda_version: ["11.8", "12.0"] fail-fast: false uses: ./.github/workflows/python_wheels.yml @@ -255,7 +255,7 @@ jobs: uses: ./.github/workflows/python_metapackages.yml with: cudaq_version: ${{ needs.python_wheels.outputs.cudaq_version }} - python_versions: "['3.12']" # fixme: reenable 3.10 + python_versions: "['3.10', '3.12']" cuda_versions: "['', '11.8', '12.0']" wheel_artifacts: 'pycudaq-*' diff --git a/.github/workflows/config/gitlab_commits.txt b/.github/workflows/config/gitlab_commits.txt index d0a266f8bf5..9176f70126f 100644 --- a/.github/workflows/config/gitlab_commits.txt +++ b/.github/workflows/config/gitlab_commits.txt @@ -1,2 +1,2 @@ nvidia-mgpu-repo: cuda-quantum/cuquantum-mgpu.git -nvidia-mgpu-commit: de32bf2abae42f118a0da36be0066296d76b0dbc +nvidia-mgpu-commit: ddfaacf2ffd7dc1a9a4333e06474b213887d437c diff --git a/.github/workflows/deployments.yml b/.github/workflows/deployments.yml index 235bfbe6e1f..30cd6d978e6 100644 --- a/.github/workflows/deployments.yml +++ b/.github/workflows/deployments.yml @@ -370,7 +370,7 @@ jobs: cuda_version=${{ matrix.cuda_version }} base_image=${{ fromJson(needs.config.outputs.json).image_hash[format('{0}-gcc11', matrix.platform)] }} ompidev_image=${{ fromJson(needs.config.outputs.json).image_hash[format('{0}-cu{1}-ompi', matrix.platform, matrix.cuda_version)] }} - ${{ matrix.cuda_version != '11.8' && 'cuda_packages=cuda-cudart cuda-nvrtc cuda-compiler libcublas-dev libcusolver libnvjitlink' || '' }} + ${{ matrix.cuda_version != '11.8' && 'cuda_packages=cuda-cudart cuda-nvrtc cuda-compiler libcublas-dev libcurand-dev libcusolver libnvjitlink' || '' }} registry_cache_from: ${{ needs.metadata.outputs.cache_base }} update_registry_cache: ${{ needs.metadata.outputs.cache_target }} environment: ${{ needs.metadata.outputs.environment }} diff --git a/.github/workflows/docker_images.yml b/.github/workflows/docker_images.yml index 20af9f85956..3486ff2dd44 100644 --- a/.github/workflows/docker_images.yml +++ b/.github/workflows/docker_images.yml @@ -165,7 +165,7 @@ jobs: driver-opts: | network=host image=moby/buildkit:v0.19.0 - + - name: Build Open MPI id: docker_build uses: docker/build-push-action@v5 @@ -483,6 +483,9 @@ jobs: platform_tag=${{ needs.metadata.outputs.platform_tag }} cuda_major=`echo ${{ inputs.cuda_version }} | cut -d . -f1` + if [ "$cuda_major" == "11" ]; then + deprecation_notice="**Note**: Support for CUDA 11 will be removed in future releases. Please update to CUDA 12." + fi image_tag=${platform_tag:+$platform_tag-}${cuda_major:+cu${cuda_major}-} if ${{ github.event.pull_request.number != '' }} || [ -n "$(echo ${{ github.ref_name }} | grep pull-request/)" ]; then pr_number=`echo ${{ github.ref_name }} | grep -o [0-9]*` @@ -502,6 +505,7 @@ jobs: echo "base_image=$base_image" >> $GITHUB_OUTPUT echo "devdeps_image=$devdeps_image" >> $GITHUB_OUTPUT echo "dev_image_name=$dev_image_name" >> $GITHUB_OUTPUT + echo "deprecation_notice=$deprecation_notice" >> $GITHUB_OUTPUT if ${{ inputs.environment == '' }}; then tar_archive=/tmp/cuda-quantum.tar @@ -602,6 +606,7 @@ jobs: cudaqdev_image=${{ steps.prereqs.outputs.dev_image_name }}@${{ steps.release_build.outputs.digest }} base_image=${{ steps.prereqs.outputs.base_image }} release_version=${{ steps.prereqs.outputs.image_tag }} + deprecation_notice=${{ steps.prereqs.outputs.deprecation_notice }} tags: ${{ steps.cudaq_metadata.outputs.tags }} labels: ${{ steps.cudaq_metadata.outputs.labels }} platforms: ${{ inputs.platforms }} @@ -719,7 +724,7 @@ jobs: # Unfortunately, we need to install cuquantum for docs generation to work properly, # since using mock imports in the autodocs configuration doesn't work properly. # See also https://github.com/sphinx-doc/sphinx/issues/11211. - docker exec cuda-quantum-dev bash -c "python3 -m pip cache purge && python3 -m pip install cuquantum-python-cu12~=24.11" + docker exec cuda-quantum-dev bash -c "python3 -m pip install cuquantum-python-cu12~=25.03" (docker exec cuda-quantum-dev bash -c "export $docs_version && bash scripts/build_docs.sh" && built=true) || built=false if $built; then docker cp cuda-quantum-dev:"/usr/local/cudaq/docs/." docs; \ @@ -820,7 +825,7 @@ jobs: docker cp docs/notebook_validation.py cuda-quantum:"/home/cudaq/notebook_validation.py" # In containers without GPU support, UCX does not work properly since it is configured to work with GPU-support. # Hence, don't enforce UCX when running these tests. - docker exec cuda-quantum bash -c "python3 -m pip cache purge && python3 -m pip install pandas scipy pandas seaborn 'h5py<3.11' contfrac" + docker exec cuda-quantum bash -c "python3 -m pip install pandas scipy pandas seaborn 'h5py<3.11' contfrac" (docker exec cuda-quantum bash -c "unset OMPI_MCA_pml && set -o pipefail && bash validate_container.sh | tee /tmp/validation.out") && passed=true || passed=false docker cp cuda-quantum:"/tmp/validation.out" /tmp/validation.out docker stop cuda-quantum diff --git a/.github/workflows/publishing.yml b/.github/workflows/publishing.yml index a798d850fff..466916d0275 100644 --- a/.github/workflows/publishing.yml +++ b/.github/workflows/publishing.yml @@ -682,7 +682,7 @@ jobs: echo "docker_output=type=local,dest=/tmp/wheels" >> $GITHUB_OUTPUT env: GH_TOKEN: ${{ secrets.REPO_BOT_ACCESS_TOKEN }} - + - name: Log in to DockerHub uses: docker/login-action@v3 with: @@ -942,6 +942,7 @@ jobs: cuda_version_suffix="$(echo ${{ matrix.cuda_version }} | tr . -)" apt-get update && apt-get install -y --no-install-recommends \ libcublas-$cuda_version_suffix \ + libcurand-$cuda_version_suffix \ cuda-cudart-$cuda_version_suffix \ libcusolver-$cuda_version_suffix \ cuda-nvrtc-$cuda_version_suffix @@ -1000,15 +1001,17 @@ jobs: wheel_validation_piponly: name: Wheel validation, pip only needs: [assets, cudaq_wheels, cudaq_metapackages] - runs-on: linux-amd64-gpu-v100-latest-1 permissions: contents: read strategy: matrix: + platform: ['amd64-gpu-v100', 'arm64-gpu-a100'] cuda_major: ['', '11', '12'] fail-fast: false + runs-on: linux-${{ matrix.platform }}-latest-1 + container: image: ubuntu:22.04 options: --user root @@ -1036,11 +1039,11 @@ jobs: name: ${{ needs.cudaq_metapackages.outputs.artifact_name }} path: /tmp/packages - - name: Run x86 validation + - name: Run basic validation shell: bash run: | - # These simple steps are only expected to work for x86 and only for - # targets and test cases that don't require MPI. + # These simple steps are only expected to work for + # test cases that don't require MPI. # Create clean python3 environment. apt-get update && apt-get install -y --no-install-recommends python3 python3-pip mkdir -p /tmp/packages && mv /tmp/wheels/* /tmp/packages && rmdir /tmp/wheels @@ -1124,7 +1127,7 @@ jobs: pattern: '*-wheels' path: /tmp/wheels merge-multiple: true - + - name: Load metapackage uses: actions/download-artifact@v4 with: @@ -1211,10 +1214,16 @@ jobs: for installer in `find installers/ -type f -not -name '*.whl'`; do mv -v "$installer" "$(basename -- "$installer")" done - for dir in `ls wheelhouse/`; do - mv -v "wheelhouse/$dir"/* wheelhouse/ && rmdir "wheelhouse/$dir" + # A single wheelhouse.zip file is too big. Split it up by platform. + platforms=$(ls wheelhouse | cut -d- -f1 | sort | uniq) + for p in $platforms; do + mkdir wheelhouse-${p} + for fulldir in wheelhouse/${p}-*; do + dir=$(basename $fulldir) + mv -v "wheelhouse/$dir"/* wheelhouse-${p}/ && rmdir "wheelhouse/$dir" + done + zip -r wheelhouse-${p}.zip wheelhouse-${p} done - zip -r wheelhouse.zip wheelhouse zip -r metapackages.zip metapackages release_id=${{ inputs.assets_from_run || github.run_id }} @@ -1231,7 +1240,7 @@ jobs: --target $github_commit --draft $prerelease \ --generate-notes --notes-start-tag $latest_tag --notes "$rel_notes" gh release upload $release_id -R ${{ github.repository }} install_cuda_quantum* --clobber - gh release upload $release_id -R ${{ github.repository }} wheelhouse.zip --clobber + gh release upload $release_id -R ${{ github.repository }} wheelhouse-*.zip --clobber gh release upload $release_id -R ${{ github.repository }} metapackages.zip --clobber gh release edit $release_id -R ${{ github.repository }} \ --title "$release_title" --tag $version $prerelease # --draft=false diff --git a/.github/workflows/python_metapackages.yml b/.github/workflows/python_metapackages.yml index bb548387bd3..ea369c942dc 100644 --- a/.github/workflows/python_metapackages.yml +++ b/.github/workflows/python_metapackages.yml @@ -59,9 +59,10 @@ jobs: echo "Creating README.md for cudaq package" package_name=cudaq cuda_version_requirement="11.x (where x \>= 8) or 12.x" - cuda_version_conda=11.8.0 # only used as example in the install script + cuda_version_conda=12.4.0 # only used as example in the install script + deprecation_notice="**Note**: Support for CUDA 11 will be removed in future releases. Please update to CUDA 12." cat python/README.md.in > python/metapackages/README.md - for variable in package_name cuda_version_requirement cuda_version_conda; do + for variable in package_name cuda_version_requirement cuda_version_conda deprecation_notice; do sed -i "s/.{{[ ]*$variable[ ]*}}/${!variable}/g" python/metapackages/README.md done if [ -n "$(cat python/metapackages/README.md | grep -e '.{{.*}}')" ]; then diff --git a/.github/workflows/python_wheels.yml b/.github/workflows/python_wheels.yml index e7bdc4a60e5..b6543c33be7 100644 --- a/.github/workflows/python_wheels.yml +++ b/.github/workflows/python_wheels.yml @@ -188,13 +188,6 @@ jobs: matrix: os_image: ${{ fromJSON(needs.create_test_config.outputs.json).os_images }} pip_install_flags: ['', '--user'] - exclude: - - os_image: ubuntu:22.04 - pip_install_flags: '--user' - - os_image: ubuntu:24.04 - pip_install_flags: '--user' - - os_image: fedora:39 - pip_install_flags: '' fail-fast: false steps: @@ -257,7 +250,7 @@ jobs: echo "::error file=python_wheel.yml::Python tests failed with status $pytest_status." exit 1 fi - python${{ inputs.python_version }} -m pip install --user fastapi uvicorn llvmlite openfermionpyscf==0.5 + python${{ inputs.python_version }} -m pip install --user fastapi uvicorn llvmlite for backendTest in /tmp/tests/backends/*.py; do python${{ inputs.python_version }} -m pytest $backendTest pytest_status=$? diff --git a/.github/workflows/test_in_devenv.yml b/.github/workflows/test_in_devenv.yml index 0973670821e..eeaf26f5ff4 100644 --- a/.github/workflows/test_in_devenv.yml +++ b/.github/workflows/test_in_devenv.yml @@ -215,6 +215,9 @@ jobs: -t dev_env:local -f docker/build/cudaq.dev.Dockerfile . \ --build-arg base_image=$base_image + - name: Setup proxy cache + uses: nv-gha-runners/setup-proxy-cache@main + - name: Build and test CUDA Quantum (Python) uses: ./.github/actions/run-in-docker with: @@ -222,9 +225,8 @@ jobs: shell: bash run: | cd $CUDAQ_REPO_ROOT - pip cache purge pip install iqm_client==16.1 --user -vvv - pip install . -vvv # FIXME: --user causes package hash mismatch on ubuntu... + pip install . --user -vvv pyinstall_status=$? if [ ! $pyinstall_status -eq 0 ]; then echo "::error file=test_in_devenv.yml:: Pip install of CUDA Quantum failed with status $pyinstall_status." diff --git a/docker/build/assets.Dockerfile b/docker/build/assets.Dockerfile index a89b2fd1475..8106ccee479 100644 --- a/docker/build/assets.Dockerfile +++ b/docker/build/assets.Dockerfile @@ -224,6 +224,7 @@ RUN echo "Patching up wheel using auditwheel..." && \ --plat ${MANYLINUX_PLATFORM} \ --exclude libcublas.so.11 \ --exclude libcublasLt.so.11 \ + --exclude libcurand.so.10 \ --exclude libcusolver.so.11 \ --exclude libcutensor.so.2 \ --exclude libcutensornet.so.2 \ @@ -262,7 +263,7 @@ RUN gcc_packages=$(dnf list installed "gcc*" | sed '/Installed Packages/d' | cut ## [Python MLIR tests] RUN cd /cuda-quantum && source scripts/configure_build.sh && \ - python3 -m pip install lit pytest scipy cuquantum-python-cu$(echo ${CUDA_VERSION} | cut -d . -f1)~=24.11 && \ + python3 -m pip install lit pytest scipy cuquantum-python-cu$(echo ${CUDA_VERSION} | cut -d . -f1)~=25.03 && \ "${LLVM_INSTALL_PREFIX}/bin/llvm-lit" -v _skbuild/python/tests/mlir \ --param nvqpp_site_config=_skbuild/python/tests/mlir/lit.site.cfg.py # The other tests for the Python wheel are run post-installation. @@ -322,7 +323,8 @@ RUN . /cuda-quantum/scripts/configure_build.sh install-gcc && \ dnf install -y --nobest --setopt=install_weak_deps=False \ cuda-compiler-$(echo ${CUDA_VERSION} | tr . -) \ cuda-cudart-devel-$(echo ${CUDA_VERSION} | tr . -) \ - libcublas-devel-$(echo ${CUDA_VERSION} | tr . -) && \ + libcublas-devel-$(echo ${CUDA_VERSION} | tr . -) \ + libcurand-devel-$(echo ${CUDA_VERSION} | tr . -) && \ if [ $(echo $CUDA_VERSION | cut -d "." -f1) -ge 12 ]; then \ dnf install -y --nobest --setopt=install_weak_deps=False \ libnvjitlink-$(echo ${CUDA_VERSION} | tr . -); \ diff --git a/docker/build/devdeps.ext.Dockerfile b/docker/build/devdeps.ext.Dockerfile index a04f82ddfae..e19749f2800 100644 --- a/docker/build/devdeps.ext.Dockerfile +++ b/docker/build/devdeps.ext.Dockerfile @@ -130,7 +130,7 @@ ENV UCX_TLS=rc,cuda_copy,cuda_ipc,gdr_copy,sm # Install CUDA -ARG cuda_packages="cuda-cudart cuda-nvrtc cuda-compiler libcublas-dev libcusolver" +ARG cuda_packages="cuda-cudart cuda-nvrtc cuda-compiler libcublas-dev libcurand-dev libcusolver" RUN if [ -n "$cuda_packages" ]; then \ arch_folder=$([ "$(uname -m)" == "aarch64" ] && echo sbsa || echo x86_64) \ && cuda_packages=`printf '%s\n' $cuda_packages | xargs -I {} echo {}-$(echo ${CUDA_VERSION} | tr . -)` \ @@ -164,7 +164,7 @@ ENV PATH="${CUDA_INSTALL_PREFIX}/lib64/:${CUDA_INSTALL_PREFIX}/bin:${PATH}" RUN apt-get update && apt-get install -y --no-install-recommends \ python3 python3-pip && \ apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* && \ - python3 -m pip install cuquantum-python-cu$(echo $CUDA_VERSION | cut -d . -f1)~=24.11 && \ + python3 -m pip install cuquantum-python-cu$(echo $CUDA_VERSION | cut -d . -f1)~=25.03 && \ if [ "$(python3 --version | grep -o [0-9\.]* | cut -d . -f -2)" != "3.10" ]; then \ echo "expecting Python version 3.10"; \ fi diff --git a/docker/build/devdeps.manylinux.Dockerfile b/docker/build/devdeps.manylinux.Dockerfile index 910baeed79b..8761f71eef0 100644 --- a/docker/build/devdeps.manylinux.Dockerfile +++ b/docker/build/devdeps.manylinux.Dockerfile @@ -104,7 +104,8 @@ RUN arch_folder=$([ "$(uname -m)" == "aarch64" ] && echo sbsa || echo x86_64) \ && dnf install -y --nobest --setopt=install_weak_deps=False wget \ cuda-compiler-$(echo ${CUDA_VERSION} | tr . -) \ cuda-cudart-devel-$(echo ${CUDA_VERSION} | tr . -) \ - libcublas-devel-$(echo ${CUDA_VERSION} | tr . -) + libcublas-devel-$(echo ${CUDA_VERSION} | tr . -) \ + libcurand-devel-$(echo ${CUDA_VERSION} | tr . -) ENV CUDA_INSTALL_PREFIX=/usr/local/cuda-$CUDA_VERSION ENV CUDA_HOME="$CUDA_INSTALL_PREFIX" diff --git a/docker/release/cudaq.Dockerfile b/docker/release/cudaq.Dockerfile index ef37192f850..4a44bcc4061 100644 --- a/docker/release/cudaq.Dockerfile +++ b/docker/release/cudaq.Dockerfile @@ -99,7 +99,9 @@ Version: ${CUDA_QUANTUM_VERSION}\n\n\ Copyright (c) 2025 NVIDIA Corporation & Affiliates \n\ All rights reserved.\n\n\ To run a command as administrator (user `root`), use `sudo `.\n" -RUN echo -e "$COPYRIGHT_NOTICE" > "$CUDA_QUANTUM_PATH/Copyright.txt" +ARG deprecation_notice="" +RUN echo -e "$COPYRIGHT_NOTICE" > "$CUDA_QUANTUM_PATH/Copyright.txt" && \ + echo -e "$deprecation_notice" >> "$CUDA_QUANTUM_PATH/Copyright.txt" RUN echo 'cat "$CUDA_QUANTUM_PATH/Copyright.txt"' > /etc/profile.d/welcome.sh # See also https://github.com/microsoft/vscode-remote-release/issues/4781 diff --git a/docker/release/cudaq.ext.Dockerfile b/docker/release/cudaq.ext.Dockerfile index ca7b593119f..0ec4a4b85ff 100644 --- a/docker/release/cudaq.ext.Dockerfile +++ b/docker/release/cudaq.ext.Dockerfile @@ -30,7 +30,7 @@ RUN if [ -d "$CUDA_QUANTUM_PATH/assets/documentation" ]; then \ # Install additional runtime dependencies. RUN cuda_version_suffix=$(echo ${CUDA_VERSION} | tr . -) && \ - for cudart_dependency in libcusolver libcublas cuda-cudart cuda-nvrtc; do \ + for cudart_dependency in libcusolver libcublas libcurand cuda-cudart cuda-nvrtc; do \ if [ -z "$(apt list --installed | grep -o ${cudart_dependency}-${cuda_version_suffix})" ]; then \ apt-get install -y --no-install-recommends \ ${cudart_dependency}-${cuda_version_suffix}; \ diff --git a/docker/release/cudaq.wheel.Dockerfile b/docker/release/cudaq.wheel.Dockerfile index 6aa15070eda..67b44a76f34 100644 --- a/docker/release/cudaq.wheel.Dockerfile +++ b/docker/release/cudaq.wheel.Dockerfile @@ -52,7 +52,10 @@ RUN cd cuda-quantum && cat python/README.md.in > python/README.md && \ package_name=cuda-quantum-cu$(echo ${CUDA_VERSION} | cut -d . -f1) && \ cuda_version_requirement="\>= ${CUDA_VERSION}" && \ cuda_version_conda=${CUDA_VERSION}.0 && \ - for variable in package_name cuda_version_requirement cuda_version_conda; do \ + if [ "${CUDA_VERSION#11.}" != "${CUDA_VERSION}" ]; then \ + deprecation_notice="**Note**: Support for CUDA 11 will be removed in future releases. Please update to CUDA 12."; \ + fi && \ + for variable in package_name cuda_version_requirement cuda_version_conda deprecation_notice; do \ sed -i "s/.{{[ ]*$variable[ ]*}}/${!variable}/g" python/README.md; \ done && \ if [ -n "$(cat python/README.md | grep -e '.{{.*}}')" ]; then \ @@ -79,6 +82,7 @@ RUN echo "Building wheel for python${python_version}." \ --exclude libcutensornet.so.2 \ --exclude libcublas.so.$cudaq_major \ --exclude libcublasLt.so.$cudaq_major \ + --exclude libcurand.so.10 \ --exclude libcusolver.so.$cudaq_major \ --exclude libcutensor.so.2 \ --exclude libnvToolsExt.so.1 \ diff --git a/docker/release/installer.Dockerfile b/docker/release/installer.Dockerfile index 58179d3b7f6..7078f80ede0 100644 --- a/docker/release/installer.Dockerfile +++ b/docker/release/installer.Dockerfile @@ -54,6 +54,17 @@ RUN source /cuda-quantum/scripts/configure_build.sh && \ ## [> install.sh && echo "$key" >> install.sh && echo "$prompt" >> install.sh; \ + fi RUN bash /makeself/makeself.sh --gzip --sha256 --license /cuda-quantum/LICENSE \ /cuda_quantum_assets install_cuda_quantum_cu$(echo ${CUDA_VERSION} | cut -d . -f1).$(uname -m) \ "CUDA-Q toolkit for heterogeneous quantum-classical workflows" \ diff --git a/docker/test/installer/runtime_dependencies.sh b/docker/test/installer/runtime_dependencies.sh index 7b7a017beaa..c983ddc5a19 100644 --- a/docker/test/installer/runtime_dependencies.sh +++ b/docker/test/installer/runtime_dependencies.sh @@ -34,7 +34,7 @@ esac CUDA_DOWNLOAD_URL=https://developer.download.nvidia.com/compute/cuda/repos CUDA_ARCH_FOLDER=$([ "$(uname -m)" == "aarch64" ] && echo sbsa || echo x86_64) CUDA_VERSION_SUFFIX=$(echo ${CUDART_VERSION:-'12.0'} | tr . -) -CUDA_PACKAGES=$(echo "cuda-cudart cuda-nvrtc libcusolver libcublas" | sed "s/[^ ]*/&-${CUDA_VERSION_SUFFIX} /g") +CUDA_PACKAGES=$(echo "cuda-cudart cuda-nvrtc libcusolver libcublas libcurand" | sed "s/[^ ]*/&-${CUDA_VERSION_SUFFIX} /g") if [ $(echo ${CUDART_VERSION} | cut -d . -f1) -gt 11 ]; then CUDA_PACKAGES+=" libnvjitlink-${CUDA_VERSION_SUFFIX}" fi diff --git a/docker/test/wheels/debian.Dockerfile b/docker/test/wheels/debian.Dockerfile index 4731f44b689..fffcc58f424 100644 --- a/docker/test/wheels/debian.Dockerfile +++ b/docker/test/wheels/debian.Dockerfile @@ -15,7 +15,7 @@ ARG pip_install_flags="" ARG preinstalled_modules="numpy pytest nvidia-cublas-cu12" ARG DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y --no-install-recommends \ +RUN apt-get update && apt-get install -y --no-install-recommends wget \ python${python_version} python${python_version}-venv # We need to make sure the virtual Python environment remains @@ -24,7 +24,6 @@ ENV VIRTUAL_ENV=/opt/venv RUN python${python_version} -m venv "$VIRTUAL_ENV" ENV PATH="$VIRTUAL_ENV/bin:$PATH" RUN if [ -n "$preinstalled_modules" ]; then \ - python${python_version} -m pip cache purge && \ echo $preinstalled_modules | xargs python${python_version} -m pip install; \ fi @@ -43,9 +42,15 @@ RUN if [ -n "$pip_install_flags" ]; then \ sed -i 's/include-system-site-packages = false/include-system-site-packages = true/' $VIRTUAL_ENV/pyvenv.cfg; \ fi -RUN python${python_version} -m pip cache purge && \ - python${python_version} -m pip install ${pip_install_flags} /tmp/$cuda_quantum_wheel + +# Working around issue https://github.com/pypa/pip/issues/11153. +RUN wget https://github.com/rapidsai/gha-tools/releases/latest/download/tools.tar.gz -O - | tar -xz -C /usr/local/bin && \ + RAPIDS_PIP_EXE="python${python_version} -m pip" \ + /usr/local/bin/rapids-pip-retry install ${pip_install_flags} /tmp/$cuda_quantum_wheel + + RUN if [ -n "$optional_dependencies" ]; then \ cudaq_package=$(echo $cuda_quantum_wheel | cut -d '-' -f1 | tr _ -) && \ - python${python_version} -m pip install ${pip_install_flags} $cudaq_package[$optional_dependencies]; \ + RAPIDS_PIP_EXE="python${python_version} -m pip" \ + /usr/local/bin/rapids-pip-retry install ${pip_install_flags} $cudaq_package[$optional_dependencies]; \ fi diff --git a/docker/test/wheels/fedora.Dockerfile b/docker/test/wheels/fedora.Dockerfile index c54ae424851..f7ed7c645cc 100644 --- a/docker/test/wheels/fedora.Dockerfile +++ b/docker/test/wheels/fedora.Dockerfile @@ -18,11 +18,10 @@ ARG DEBIAN_FRONTEND=noninteractive # Some Python versions need the latest version of libexpat on Fedora, and the # base fedora:38 image doesn't have the latest version installed. RUN dnf update -y --nobest expat \ - && dnf install -y --nobest --setopt=install_weak_deps=False \ + && dnf install -y --nobest --setopt=install_weak_deps=False wget \ python$(echo $python_version | tr -d .) \ && python${python_version} -m ensurepip --upgrade RUN if [ -n "$preinstalled_modules" ]; then \ - python${python_version} -m pip cache purge && \ echo $preinstalled_modules | xargs python${python_version} -m pip install; \ fi @@ -36,9 +35,15 @@ COPY docs/sphinx/snippets/python /tmp/snippets/ COPY python/tests /tmp/tests/ COPY python/README*.md /tmp/ -RUN python${python_version} -m pip cache purge && \ - python${python_version} -m pip install ${pip_install_flags} /tmp/$cuda_quantum_wheel + +# Working around issue https://github.com/pypa/pip/issues/11153. +RUN wget https://github.com/rapidsai/gha-tools/releases/latest/download/tools.tar.gz -O - | tar -xz -C /usr/local/bin && \ + RAPIDS_PIP_EXE="python${python_version} -m pip" \ + /usr/local/bin/rapids-pip-retry install ${pip_install_flags} /tmp/$cuda_quantum_wheel + + RUN if [ -n "$optional_dependencies" ]; then \ cudaq_package=$(echo $cuda_quantum_wheel | cut -d '-' -f1 | tr _ -) && \ - python${python_version} -m pip install ${pip_install_flags} $cudaq_package[$optional_dependencies]; \ + RAPIDS_PIP_EXE="python${python_version} -m pip" \ + /usr/local/bin/rapids-pip-retry install ${pip_install_flags} $cudaq_package[$optional_dependencies]; \ fi diff --git a/docker/test/wheels/opensuse.Dockerfile b/docker/test/wheels/opensuse.Dockerfile index 4f9aea8e271..400d5632ed8 100644 --- a/docker/test/wheels/opensuse.Dockerfile +++ b/docker/test/wheels/opensuse.Dockerfile @@ -15,11 +15,10 @@ ARG preinstalled_modules="numpy pytest nvidia-cublas-cu12" ARG DEBIAN_FRONTEND=noninteractive RUN zypper clean --all && zypper ref && zypper --non-interactive up --no-recommends \ - && zypper --non-interactive in --no-recommends \ + && zypper --non-interactive in --no-recommends wget \ python$(echo ${python_version} | tr -d .) \ && python${python_version} -m ensurepip --upgrade RUN if [ -n "$preinstalled_modules" ]; then \ - python${python_version} -m pip cache purge && \ echo $preinstalled_modules | xargs python${python_version} -m pip install; \ fi @@ -33,9 +32,15 @@ COPY docs/sphinx/snippets/python /tmp/snippets/ COPY python/tests /tmp/tests/ COPY python/README*.md /tmp/ -RUN python${python_version} -m pip cache purge && \ - python${python_version} -m pip install ${pip_install_flags} /tmp/$cuda_quantum_wheel + +# Working around issue https://github.com/pypa/pip/issues/11153. +RUN wget https://github.com/rapidsai/gha-tools/releases/latest/download/tools.tar.gz -O - | tar -xz -C /usr/local/bin && \ + RAPIDS_PIP_EXE="python${python_version} -m pip" \ + /usr/local/bin/rapids-pip-retry install ${pip_install_flags} /tmp/$cuda_quantum_wheel + + RUN if [ -n "$optional_dependencies" ]; then \ cudaq_package=$(echo $cuda_quantum_wheel | cut -d '-' -f1 | tr _ -) && \ - python${python_version} -m pip install ${pip_install_flags} $cudaq_package[$optional_dependencies]; \ + RAPIDS_PIP_EXE="python${python_version} -m pip" \ + /usr/local/bin/rapids-pip-retry install ${pip_install_flags} $cudaq_package[$optional_dependencies]; \ fi diff --git a/docker/test/wheels/redhat.Dockerfile b/docker/test/wheels/redhat.Dockerfile index 31a6020ec9c..51af53597d2 100644 --- a/docker/test/wheels/redhat.Dockerfile +++ b/docker/test/wheels/redhat.Dockerfile @@ -14,11 +14,10 @@ ARG pip_install_flags="--user" ARG preinstalled_modules="numpy pytest nvidia-cublas-cu12" ARG DEBIAN_FRONTEND=noninteractive -RUN dnf install -y --nobest --setopt=install_weak_deps=False \ +RUN dnf install -y --nobest --setopt=install_weak_deps=False wget \ python${python_version} \ && python${python_version} -m ensurepip --upgrade RUN if [ -n "$preinstalled_modules" ]; then \ - python${python_version} -m pip cache purge && \ echo $preinstalled_modules | xargs python${python_version} -m pip install; \ fi @@ -32,9 +31,15 @@ COPY docs/sphinx/snippets/python /tmp/snippets/ COPY python/tests /tmp/tests/ COPY python/README*.md /tmp/ -RUN python${python_version} -m pip cache purge && \ - python${python_version} -m pip install ${pip_install_flags} /tmp/$cuda_quantum_wheel + +# Working around issue https://github.com/pypa/pip/issues/11153. +RUN wget https://github.com/rapidsai/gha-tools/releases/latest/download/tools.tar.gz -O - | tar -xz -C /usr/local/bin && \ + RAPIDS_PIP_EXE="python${python_version} -m pip" \ + /usr/local/bin/rapids-pip-retry install ${pip_install_flags} /tmp/$cuda_quantum_wheel + + RUN if [ -n "$optional_dependencies" ]; then \ cudaq_package=$(echo $cuda_quantum_wheel | cut -d '-' -f1 | tr _ -) && \ - python${python_version} -m pip install ${pip_install_flags} $cudaq_package[$optional_dependencies]; \ + RAPIDS_PIP_EXE="python${python_version} -m pip" \ + /usr/local/bin/rapids-pip-retry install ${pip_install_flags} $cudaq_package[$optional_dependencies]; \ fi diff --git a/docker/test/wheels/ubuntu.Dockerfile b/docker/test/wheels/ubuntu.Dockerfile index ba993f3527a..eafa558a80c 100644 --- a/docker/test/wheels/ubuntu.Dockerfile +++ b/docker/test/wheels/ubuntu.Dockerfile @@ -14,7 +14,7 @@ ARG pip_install_flags="--user" ARG preinstalled_modules="numpy pytest nvidia-cublas-cu12" ARG DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y --no-install-recommends \ +RUN apt-get update && apt-get install -y --no-install-recommends wget \ python${python_version} python${python_version}-venv # We need to make sure the virtual Python environment remains @@ -23,7 +23,6 @@ ENV VIRTUAL_ENV=/opt/venv RUN python${python_version} -m venv "$VIRTUAL_ENV" ENV PATH="$VIRTUAL_ENV/bin:$PATH" RUN if [ -n "$preinstalled_modules" ]; then \ - python${python_version} -m pip cache purge && \ echo $preinstalled_modules | xargs python${python_version} -m pip install; \ fi @@ -39,9 +38,15 @@ COPY python/README*.md /tmp/ RUN sed -ie 's/include-system-site-packages\s*=\s*false/include-system-site-packages = true/g' "$VIRTUAL_ENV/pyvenv.cfg" -RUN python${python_version} -m pip cache purge && \ - python${python_version} -m pip install ${pip_install_flags} /tmp/$cuda_quantum_wheel + +# Working around issue https://github.com/pypa/pip/issues/11153. +RUN wget https://github.com/rapidsai/gha-tools/releases/latest/download/tools.tar.gz -O - | tar -xz -C /usr/local/bin && \ + RAPIDS_PIP_EXE="python${python_version} -m pip" \ + /usr/local/bin/rapids-pip-retry install ${pip_install_flags} /tmp/$cuda_quantum_wheel + + RUN if [ -n "$optional_dependencies" ]; then \ cudaq_package=$(echo $cuda_quantum_wheel | cut -d '-' -f1 | tr _ -) && \ - python${python_version} -m pip install ${pip_install_flags} $cudaq_package[$optional_dependencies]; \ + RAPIDS_PIP_EXE="python${python_version} -m pip" \ + /usr/local/bin/rapids-pip-retry install ${pip_install_flags} $cudaq_package[$optional_dependencies]; \ fi diff --git a/docs/notebook_validation.py b/docs/notebook_validation.py index 90e020fc8f3..d462ebe8273 100644 --- a/docs/notebook_validation.py +++ b/docs/notebook_validation.py @@ -103,11 +103,9 @@ def print_results(success, failed, skipped=[]): notebooks_success, notebooks_skipped, notebooks_failed = ( [] for i in range(3)) - ## `afqmc`: - ## See: https://github.com/NVIDIA/cuda-quantum/issues/2577 ## `quantum_transformer`: ## See: https://github.com/NVIDIA/cuda-quantum/issues/2689 - notebooks_skipped = ['afqmc.ipynb', 'quantum_transformer.ipynb'] + notebooks_skipped = ['quantum_transformer.ipynb'] for notebook_filename in notebook_filenames: base_name = os.path.basename(notebook_filename) diff --git a/docs/sphinx/applications/python/afqmc.ipynb b/docs/sphinx/applications/python/afqmc.ipynb deleted file mode 100644 index 6f150dca1dc..00000000000 --- a/docs/sphinx/applications/python/afqmc.ipynb +++ /dev/null @@ -1,533 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Quantum Enhanced Auxiliary Field Quantum Monte Carlo\n", - "\n", - "This work was done in collaboration with the Next Generation Computing team at [BASF](https://www.basf.com/global/en.html).\n", - "\n", - "In this tutorial we implement a quantum-classical hybrid workflow for computing the ground state energies of a strongly interacting molecular system. The algorithm consists of two parts:\n", - "\n", - "\n", - "1. A variational quantum eigensolver that uses the quantum-number-preserving ansatz proposed by [Anselmetti et al. (2021)](https://doi.org/10.1088/1367-2630/ac2cb3) to generate a quantum trial wave function $|\\Psi_T\\rangle$ using CUDA Quantum.\n", - "\n", - "2. An Auxiliary-Field Quantum Monte Carlo simulation that realizes a classical imaginary time evolution and collects the ground state energy estimates.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable.It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning.\u001b[0m\u001b[33m\n", - "\u001b[0m" - ] - } - ], - "source": [ - "# Package installs\n", - "!pip install pyscf==2.6.2 openfermion==1.6.1 ipie==0.7.1 -q" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/lib/python3.10/dist-packages/qutip/__init__.py:66: UserWarning: The new version of Cython, (>= 3.0.0) is not supported.\n", - " warnings.warn(\n" - ] - } - ], - "source": [ - "# Relevant imports\n", - "\n", - "import cudaq\n", - "\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "\n", - "from pyscf import gto, scf, ao2mo, mcscf\n", - "\n", - "from afqmc_src.vqe_cudaq_qnp import VQE, get_cudaq_hamiltonian\n", - "from afqmc_src.utils_ipie import get_coeff_wf, gen_ipie_input_from_pyscf_chk\n", - "\n", - "from ipie.hamiltonians.generic import Generic as HamGeneric\n", - "from ipie.qmc.afqmc import AFQMC\n", - "from ipie.systems.generic import Generic\n", - "from ipie.trial_wavefunction.particle_hole import ParticleHole\n", - "from ipie.analysis.extraction import extract_observable\n", - "\n", - "from ipie.config import config\n", - "\n", - "cudaq.set_target(\"nvidia\")\n", - "\n", - "# Ipie has recently added GPU support however this remains a bit tricky to use as it requires manual installation of several packages.\n", - "# Once this is streamlined, we can set the GPU option to True in the tutorial.\n", - "config.update_option(\"use_gpu\", False)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We start by defining the structure of the molecule, the basis set, and its spin. We build the molecule object with PySCF and run a preliminary Hartree-Fock computation. Here we choose as an example a [chelating agent](https://doi.org/10.1021/acs.jctc.3c01375) representing a relevant class of substances industrially produced at large scales. Their use ranges, among the others, from water softeners in cleaning applications, modulators of redox behaviour in oxidative bleaching, scale suppressants, soil remediation and ligands for catalysts. In particular we focus here in a Fe(III)-NTA complex whose structure is given in the file imported below.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# Define the molecular structure and the basis set for the Fenta molecule.\n", - "\n", - "atom = \"afqmc_src/geo_fenta.xyz\"\n", - "basis = \"cc-pVTZ\"\n", - "spin = 1\n", - "num_active_orbitals = 5\n", - "num_active_electrons = 5\n", - "\n", - "# You can swap to O3 which is a smaller system and takes less computational resources and time to run.\n", - "\n", - "atom = \"afqmc_src/geo_o3.xyz\"\n", - "basis = \"cc-pVTZ\"\n", - "spin = 0\n", - "num_active_orbitals = 9\n", - "num_active_electrons = 12" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "-224.34048064812222" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# PYSCF helps to build the molecule and run Hartree-Fock.\n", - "\n", - "# Define the molecule.\n", - "molecule = gto.M(atom=atom, spin=spin, basis=basis, verbose=0)\n", - "\n", - "# Restriced open shell HF.\n", - "hartee_fock = scf.ROHF(molecule)\n", - "hartee_fock.chkfile = \"afqmc_src/output.chk\"\n", - "\n", - "# Run Hartree-Fock.\n", - "hartee_fock.kernel()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Hamiltonian preparation for VQE\n", - "\n", - "Since this molecule contains of around 600 orbitals (which would correspond to 1200 qubits) and 143 total electrons, it is impossible to perform a full VQE with full statevector simulation. Therefore, we need to identify an active space with fewer orbitals and electrons that contribute to the strongly interacting part of the whole molecule. We then run a post Hartree-Fock computation with the PySCF's built-in CASCI method in order to obtain the one-body ($t_{pq}$) and two-body \n", - "($V_{prqs}$) integrals that define the molecular Hamiltonian in the active space:\n", - "\n", - "$$ H= \\sum_{pq}t_{pq}\\hat{a}_{p}^\\dagger \\hat {a}_{q}+\\sum_{pqrs} V_{prqs}\\hat a_{p}^\\dagger \\hat a_{q}^\\dagger \\hat a_{s}\\hat a_{r} \\tag{1}$$\n" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "from openfermion.transforms import jordan_wigner\n", - "from openfermion import generate_hamiltonian\n", - "\n", - "# Run a CASCI simulation for computing the Hamiltonian in the active space.\n", - "casci = mcscf.CASCI(hartee_fock, num_active_orbitals, num_active_electrons)\n", - "casci.fix_spin_(ss=(molecule.spin / 2 * (molecule.spin / 2 + 1)))\n", - "\n", - "# Executes the kernel to compute the hamiltonian in the active space.\n", - "casci.kernel()\n", - "\n", - "# Compute the one-body (h1) and two-body integrals (tbi) as shown in equation 1.\n", - "h1, energy_core = casci.get_h1eff()\n", - "\n", - "h2 = casci.get_h2eff()\n", - "h2_no_symmetry = ao2mo.restore('1', h2, num_active_orbitals)\n", - "\n", - "# V_pqrs terms in H.\n", - "tbi = np.asarray(h2_no_symmetry.transpose(0, 2, 3, 1), order='C')\n", - "\n", - "# Compute the hamiltonian and convert it to a CUDA-Q operator.\n", - "mol_ham = generate_hamiltonian(h1, tbi, energy_core.item())\n", - "jw_hamiltonian = jordan_wigner(mol_ham)\n", - "hamiltonian, constant_term = get_cudaq_hamiltonian(jw_hamiltonian)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Run VQE with CUDA-Q\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can now execute the VQE algorithm using the quantum number preserving ansatz. At the end of the VQE, we store the final statevector that will be used in the classical AFQMC computation as an initial guess.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "# Using cudaq optimizer\n", - "# Num Params: 16\n", - "# Qubits: 18\n", - "# N_layers: 1\n", - "# Energy after the VQE: -224.3881035525103\n" - ] - } - ], - "source": [ - "# Define some options for the VQE.\n", - "options = {\n", - " 'n_vqe_layers': 1,\n", - " 'maxiter': 100,\n", - " 'energy_core': constant_term,\n", - " 'return_final_state_vec': True\n", - "}\n", - "\n", - "n_qubits = 2 * num_active_orbitals\n", - "\n", - "vqe = VQE(n_qubits=n_qubits,\n", - " num_active_electrons=num_active_electrons,\n", - " spin=spin,\n", - " options=options)\n", - "\n", - "results = vqe.execute(hamiltonian)\n", - "\n", - "# Best energy from VQE.\n", - "optimized_energy = results['energy_optimized']\n", - "\n", - "# Final state vector.\n", - "final_state_vector = results[\"state_vec\"]\n", - "\n", - "# Energies during the optimization loop.\n", - "vqe_energies = results[\"callback_energies\"]" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Auxiliary Field Quantum Monte Carlo (AFQMC)\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "AFQMC is a numerical method for computing relevant properties of strongly interacting molecules. AFQMC is a type of Quantum Monte Carlo method that combines the use of random walks with an auxiliary field to simulate the imaginary-time evolution of a quantum system and drive it to the lowest energy state. This method can provide accurate results for ground-state properties of a wide range of physical systems, including atoms, molecules, and solids. Here we summarize the main features of AFQMC while a detailed introduction to can be found [here](https://www.cond-mat.de/events/correl13/manuscripts/zhang.pdf).\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We consider the electronic Hamiltonian in the second quantization\n", - "\\begin{equation}\n", - "H = {H}_1 + {H}_2 \n", - "=\\sum_{pq} h_{pq} {a}_{p}^{\\dagger} {a}_{q} + \\frac{1}{2} \\sum_{pqrs} v_{pqrs}{a}_{p}^{\\dagger} {a}_r {a}^{\\dagger}_{q} {a}_s \\tag{2}\n", - "\\end{equation}\n", - "where ${a}_{p}^{\\dagger}$ and ${a}_{q}$ are fermionic creation and annihilation operators of orbitals $p$ and $q$, respectively. The terms $h_{pq} $ and \n", - "$v_{pqrs}$ are the matrix elements of the one-body, $H_1$, and two-body, $H_2$, interactions of $H$, respectively. Here, we omit the spin indices for simplicity.\n", - "\n", - "AFQMC realizes an imaginary time propagation of an initial state (chosen as a Slater determinant) $\\ket{\\Psi_{I}}$ towards the ground state $\\ket{\\Psi_0}$ of a given hamiltonian, $H$, with\n", - "\\begin{equation}\n", - "\\ket{\\Psi_0} \\sim\\lim_{n \\to \\infty} \\left[ e^{-\\Delta\\tau H } \\right]^{n} \\ket{\\Psi_{I}}\n", - "\\tag{3}\n", - "\\end{equation} \n", - "where $\\Delta\\tau$ is the imaginary time step.\n", - "\n", - "AFQMC relies on decomposing the two-body interactions $H_2$ in terms of sum of squares of one-body operators ${v}_\\gamma$ such that the Hamiltonian ${H}$ becomes\n", - "\\begin{equation}\n", - "H = v_0 - \\frac{1}{2}\\sum_{\\gamma=1}^{N_\\gamma} {v}_\\gamma^2\n", - "\\tag{4}\n", - "\\end{equation}\n", - "with ${v}_0 = {H}_1 $ and $\n", - "{v}_\\gamma = i \\sum_{pq} L^{\\gamma}_{pq} {a}_{p}^{\\dagger}{a}_{q}.\n", - "$\n", - "The $N_\\gamma$ matrices $L^{\\gamma}_{pq}$ are called Cholesky vectors as they are obtained via a Cholesky decomposition of the two-body matrix elements \n", - "$v_{pqrs}$ via $v_{pqrs} = \\sum_{\\gamma=1}^{N_\\gamma} L^{\\gamma}_{pr} L^{\\gamma}_{qs}$.\n", - "\n", - "The imaginary time propagation evolves an ensemble of walkers $\\{\\phi^{(n)}\\}$ (that are Slater determinants) and allows one to access observables of the system. For example, the local energy\n", - "\\begin{equation}\n", - "\\mathcal{E}_{\\text{loc}}(\\phi^{(n)}) = \\frac{\\bra{\\Psi_\\mathrm{T}}H\\ket{\\phi^{(n)}}}{\\braket{\\Psi_\\mathrm{T}| \\phi^{(n)}}}\n", - "\\tag{5}\n", - "\\end{equation}\n", - "defined as the mixed expectation value of the Hamiltonian with the trial wave function $\\ket{\\Psi_\\mathrm{T}}$.\n", - "\n", - "\n", - "The trial wavefunction can be in general a single or a multi-Slater determinant coming from VQE for example. This might help in achieving more accurate ground state energy estimates.\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "The implementation of AFQMC we use here is from [ipie](https://github.com/JoonhoLee-Group/ipie) that supports both CPUs and GPUs and requires the following steps:\n", - "\n", - "\n", - "1. Preparation of the molecular Hamiltonian by performing the Cholesky decomposition\n", - "\n", - "2. Preparation of the trial state from the VQE wavefunction\n", - "\n", - "3. Executing AFQMC\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Preparation of the molecular Hamiltonian\n" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "# Number of electrons in simulation: (12, 12)\n" - ] - } - ], - "source": [ - "# AFQMC.\n", - "\n", - "# Generate the input Hamiltonian for ipie from the checkpoint file from pyscf.\n", - "ipie_hamiltonian = gen_ipie_input_from_pyscf_chk(hartee_fock.chkfile,\n", - " mcscf=True,\n", - " chol_cut=1e-5)\n", - "\n", - "h1e, cholesky_vectors, e0 = ipie_hamiltonian\n", - "\n", - "num_basis = cholesky_vectors.shape[1]\n", - "num_chol = cholesky_vectors.shape[0]\n", - "\n", - "system = Generic(nelec=molecule.nelec)\n", - "\n", - "afqmc_hamiltonian = HamGeneric(\n", - " np.array([h1e, h1e]),\n", - " cholesky_vectors.transpose((1, 2, 0)).reshape(\n", - " (num_basis * num_basis, num_chol)), e0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Preparation of the trial wave function\n" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "# Build the trial wavefunction from the state vector computed via VQE.\n", - "wavefunction = get_coeff_wf(final_state_vector,\n", - " n_active_elec=num_active_electrons,\n", - " spin=spin)\n", - "\n", - "trial = ParticleHole(wavefunction,\n", - " molecule.nelec,\n", - " num_basis,\n", - " num_dets_for_props=len(wavefunction[0]),\n", - " verbose=False)\n", - "\n", - "trial.compute_trial_energy = True\n", - "trial.build()\n", - "trial.half_rotate(afqmc_hamiltonian)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Setup of the AFQMC parameters\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here we can choose the input options like the timestep $\\Delta\\tau$, the total number of walkers `num_walkers` and the total number of AFQMC iterations `num_blocks`.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "# random seed is 96264512\n", - " Block Weight WeightFactor HybridEnergy ENumer EDenom ETotal E1Body E2Body\n", - " 0 1.0000000000000000e+02 1.0000000000000000e+02 0.0000000000000000e+00 -2.2437583763935545e+04 1.0000000000000000e+02 -2.2437583763935547e+02 -3.7639365190228011e+02 1.5201781426292453e+02\n", - " 1 4.2276634193515412e+02 1.4127560668989827e+03 -1.1711742028818304e+02 -2.2473358126540003e+04 9.9999999999999986e+01 -2.2473358126540006e+02 -3.7646854013277283e+02 1.5173495886737268e+02\n", - " 2 1.0031922288872407e+02 3.8320523739865604e+02 -1.1743088014788954e+02 -2.2489226882493567e+04 1.0000000000000001e+02 -2.2489226882493563e+02 -3.7650504938463922e+02 1.5161278055970348e+02\n", - " 3 9.9900990681040355e+01 1.0008400623205630e+02 -1.1736864885170948e+02 -2.2495677577437204e+04 9.9999999999999972e+01 -2.2495677577437212e+02 -3.7659644834889821e+02 1.5163967257452603e+02\n", - " 4 1.0009188692360159e+02 1.0005173726372723e+02 -1.1748969527283802e+02 -2.2495531836556856e+04 1.0000000000000001e+02 -2.2495531836556853e+02 -3.7675907314082951e+02 1.5180375477526098e+02\n", - " 5 9.9997269300807844e+01 1.0010618465796188e+02 -1.1752703012577417e+02 -2.2502732667629320e+04 1.0000000000000001e+02 -2.2502732667629317e+02 -3.7663343013337044e+02 1.5160610345707727e+02\n", - " 6 1.0012131352337956e+02 1.0019003056579172e+02 -1.1770170647504112e+02 -2.2513369839216481e+04 1.0000000000000000e+02 -2.2513369839216480e+02 -3.7660812717909516e+02 1.5147442878693036e+02\n", - " 7 9.9936984461419740e+01 9.9929966800671224e+01 -1.1765353928750643e+02 -2.2516138533920657e+04 1.0000000000000000e+02 -2.2516138533920659e+02 -3.7660292355465600e+02 1.5144153821544941e+02\n", - " 8 9.9902337463172714e+01 9.9910800755312891e+01 -1.1761532255317621e+02 -2.2518524275281430e+04 9.9999999999999986e+01 -2.2518524275281433e+02 -3.7674246483479845e+02 1.5155722208198404e+02\n", - " 9 1.0012943675389775e+02 1.0013880643723378e+02 -1.1780913595074867e+02 -2.2512465963277762e+04 1.0000000000000000e+02 -2.2512465963277762e+02 -3.7677999264623367e+02 1.5165533301345607e+02\n", - " 10 9.9628730363609819e+01 9.9223106824565718e+01 -1.1749814144939067e+02 -2.2517668156221851e+04 1.0000000000000000e+02 -2.2517668156221850e+02 -3.7688306341863290e+02 1.5170638185641434e+02\n" - ] - } - ], - "source": [ - "# Setup the AFQMC parameters.\n", - "afqmc_msd = AFQMC.build(molecule.nelec,\n", - " afqmc_hamiltonian,\n", - " trial,\n", - " num_walkers=100,\n", - " num_steps_per_block=25,\n", - " num_blocks=10,\n", - " timestep=0.005,\n", - " stabilize_freq=5,\n", - " seed=96264512,\n", - " pop_control_freq=5,\n", - " verbose=False)\n", - "\n", - "# Run the AFQMC.\n", - "afqmc_msd.run(estimator_filename='afqmc_src/estimates.0.h5')\n", - "afqmc_msd.finalise(verbose=False)\n", - "\n", - "# Extract the energies.\n", - "qmc_data = extract_observable(afqmc_msd.estimators.filename, \"energy\")" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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6XR48cUXkZCQAD8/P3Ts2BHz58+3WIeN19K+X3/9FaNHj0ZsbCwEQcCGDRss9jtz3S5duoT7778fwcHBCA0NxbRp01BWVtaMn8KzGFC1UP/73//w5JNPYt68efjjjz9w7bXXYsSIESgoKPD00Lza9u3b8eijj2LPnj3YvHkzamtr8be//Q3l5eXyMU888QS+++47rF+/Htu3b0dOTg7uuusuD47au+3btw/Lli1D7969LbbzOjrn8uXLuPHGG+Hj44Mff/wRx48fx1tvvYWwsDD5mDfeeAPvvvsuPvzwQ/z+++8ICAjAiBEjUFVV5cGRe58FCxZg6dKleO+993DixAksWLAAb7zxBpYsWSIfw2tpX3l5Oa699lq8//77dvc7c93uv/9+HDt2DJs3b8bGjRvx66+/YsaMGc31ETxPpBZpwIAB4qOPPio/1+l0YmxsrJicnOzBUbU8BQUFIgBx+/btoiiKYlFRkejj4yOuX79ePubEiRMiAHH37t2eGqbXKi0tFTt37ixu3rxZHDJkiDhz5kxRFHkdXfHMM8+IN910k8P9er1ejI6OFt988015W1FRkajRaMTPP/+8OYbYYowaNUqcOnWqxba77rpLvP/++0VR5LV0FgDx66+/lp87c92OHz8uAhD37dsnH/Pjjz+KgiCI2dnZzTZ2T2KGqgWqqanBgQMHMHz4cHmbQqHA8OHDsXv3bg+OrOUpLi4GAISHhwMADhw4gNraWotr27VrV7Rt25bX1o5HH30Uo0aNsrheAK+jK7799lv0798fd999NyIjI9G3b18sX75c3n/mzBnk5eVZXMuQkBAMHDiQ19LKoEGDsGXLFpw8eRIA8Oeff2Lnzp24/fbbAfBaNpQz12337t0IDQ1F//795WOGDx8OhUKB33//vdnH7AlcHLkFunjxInQ6HaKioiy2R0VFIS0tzUOjann0ej1mzZqFG2+8ET179gQA5OXlQa1WIzQ01OLYqKgo5OXleWCU3mvt2rX4448/sG/fPpt9vI7OO336NJYuXYonn3wSzz33HPbt24fHH38carUaDz74oHy97P33zmtp6dlnn0VJSQm6du0KpVIJnU6HV199Fffffz8A8Fo2kDPXLS8vD5GRkRb7VSoVwsPDr5pry4CKrlqPPvoojh49ip07d3p6KC3OuXPnMHPmTGzevBm+vr6eHk6Lptfr0b9/f7z22msAgL59++Lo0aP48MMP8eCDD3p4dC3LunXrsGbNGnz22Wfo0aMHDh06hFmzZiE2NpbXkpocp/xaoNatW0OpVNrcMZWfn4/o6GgPjapl+de//oWNGzdi27ZtiIuLk7dHR0ejpqYGRUVFFsfz2lo6cOAACgoKcN1110GlUkGlUmH79u149913oVKpEBUVxevopJiYGHTv3t1iW7du3ZCVlQUA8vXif+/1mzNnDp599lnce++96NWrFx544AE88cQTSE5OBsBr2VDOXLfo6Gibm6K0Wi0uXbp01VxbBlQtkFqtRr9+/bBlyxZ5m16vx5YtW5CYmOjBkXk/URTxr3/9C19//TW2bt2KhIQEi/39+vWDj4+PxbVNT09HVlYWr62ZW2+9FUeOHMGhQ4fkn/79++P++++XH/M6OufGG2+0ad1x8uRJtGvXDgCQkJCA6Ohoi2tZUlKC33//ndfSSkVFBRQKy681pVIJvV4PgNeyoZy5bomJiSgqKsKBAwfkY7Zu3Qq9Xo+BAwc2+5g9wtNV8dQwa9euFTUajZiSkiIeP35cnDFjhhgaGirm5eV5emhe7f/+7//EkJAQMTU1VczNzZV/Kioq5GMeeeQRsW3btuLWrVvF/fv3i4mJiWJiYqIHR90ymN/lJ4q8js7au3evqFKpxFdffVX866+/xDVr1oj+/v7ip59+Kh/z+uuvi6GhoeI333wjHj58WBwzZoyYkJAgVlZWenDk3ufBBx8U27RpI27cuFE8c+aM+NVXX4mtW7cWn376afkYXkv7SktLxYMHD4oHDx4UAYiLFi0SDx48KJ49e1YUReeu28iRI8W+ffuKv//+u7hz506xc+fO4oQJEzz1kZodA6oWbMmSJWLbtm1FtVotDhgwQNyzZ4+nh+T1ANj9WbVqlXxMZWWl+M9//lMMCwsT/f39xb///e9ibm6u5wbdQlgHVLyOzvvuu+/Enj17ihqNRuzatav40UcfWezX6/Xiiy++KEZFRYkajUa89dZbxfT0dA+N1nuVlJSIM2fOFNu2bSv6+vqKHTp0EJ9//nmxurpaPobX0r5t27bZ/X/jgw8+KIqic9etsLBQnDBhghgYGCgGBweLU6ZMEUtLSz3waTxDEEWzFrJERERE5DLWUBERERE1EgMqIiIiokZiQEVERETUSAyoiIiIiBqJARURERFRIzGgIiIiImokBlREREREjcSAioiIiKiRGFARUYvw73//G3369GnUOTIzMyEIAg4dOuSWMTkydOhQzJo1q0nfg4i8CwMqInKLc+fOYerUqYiNjYVarUa7du0wc+ZMFBYWunwuQRCwYcMGi21PPfWUxeKsDREfH4/c3Fz07NmzUeeRpKamQhAEFBUVWWz/6quvMH/+fLe8R0M0V+BIRCYMqIio0U6fPo3+/fvjr7/+wueff46MjAx8+OGH2LJlCxITE3Hp0qVGv0dgYCBatWrVqHMolUpER0dDpVI1ejx1CQ8PR1BQUJO+BxF5FwZURNRojz76KNRqNX7++WcMGTIEbdu2xe23345ffvkF2dnZeP755+Vj27dvj/nz52PChAkICAhAmzZt8P7771vsB4C///3vEARBfm495Td58mSMHTsWr732GqKiohAaGopXXnkFWq0Wc+bMQXh4OOLi4rBq1Sr5NdaZm8mTJ0MQBJuf1NRUAMB///tf9O/fH0FBQYiOjsZ9992HgoIC+VzDhg0DAISFhUEQBEyePBmA7ZTf5cuXMWnSJISFhcHf3x+33347/vrrL3l/SkoKQkNDsWnTJnTr1g2BgYEYOXIkcnNzHV7zy5cv4/7770dERAT8/PzQuXNn+bMmJCQAAPr27QtBEDB06FD5dR9//DG6desGX19fdO3aFR988IHN9Vm7di0GDRoEX19f9OzZE9u3b3fqfYmuap5enZmIWrbCwkJREATxtddes7t/+vTpYlhYmKjX60VRFMV27dqJQUFBYnJyspieni6+++67olKpFH/++WdRFEWxoKBABCCuWrVKzM3NFQsKCkRRFMV58+aJ1157rXzeBx98UAwKChIfffRRMS0tTVyxYoUIQBwxYoT46quviidPnhTnz58v+vj4iOfOnRNFURTPnDkjAhAPHjwoiqIoFhUVibm5ufLPzJkzxcjISDE3N1cURVFcsWKF+MMPP4inTp0Sd+/eLSYmJoq33367KIqiqNVqxS+//FIEIKanp4u5ubliUVGRKIqiOGTIEHHmzJnyWO+8806xW7du4q+//ioeOnRIHDFihNipUyexpqZGFEVRXLVqlejj4yMOHz5c3Ldvn3jgwAGxW7du4n333efwuj/66KNinz59xH379olnzpwRN2/eLH777beiKIri3r17RQDiL7/8Iubm5oqFhYWiKIri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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Plot the energies.\n", - "\n", - "vqe_y = vqe_energies\n", - "vqe_x = list(range(len(vqe_y)))\n", - "plt.plot(vqe_x, vqe_y, label=\"VQE\")\n", - "\n", - "afqmc_y = list(qmc_data[\"ETotal\"])\n", - "afqmc_x = [i + vqe_x[-1] for i in list(range(len(afqmc_y)))]\n", - "plt.plot(afqmc_x, afqmc_y, label=\"AFQMC\")\n", - "\n", - "plt.xlabel(\"Optimization steps\")\n", - "plt.ylabel(\"Energy [Ha]\")\n", - "plt.legend()" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CUDA-Q Version latest (https://github.com/NVIDIA/cuda-quantum 176f1e7df8a58c2dc3d6b1b47bf7f63b4b8d3b63)\n" - ] - } - ], - "source": [ - "print(cudaq.__version__)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/sphinx/examples/cpp/dynamics/cavity_qed.cpp b/docs/sphinx/examples/cpp/dynamics/cavity_qed.cpp index c56d9ca7ad5..43ea3a1f8b1 100644 --- a/docs/sphinx/examples/cpp/dynamics/cavity_qed.cpp +++ b/docs/sphinx/examples/cpp/dynamics/cavity_qed.cpp @@ -28,21 +28,21 @@ int main() { // For the cavity subsystem 1 // We create the annihilation (a) and creation (a+) operators. // These operators lower and raise the photon number, respectively. - auto a = cudaq::boson_operator::annihilate(1); - auto a_dag = cudaq::boson_operator::create(1); + auto a = cudaq::boson_op::annihilate(1); + auto a_dag = cudaq::boson_op::create(1); // For the atom subsystem 0 // We create the annihilation (`sm`) and creation (`sm_dag`) operators. // These operators lower and raise the excitation number, respectively. - auto sm = cudaq::boson_operator::annihilate(0); - auto sm_dag = cudaq::boson_operator::create(0); + auto sm = cudaq::boson_op::annihilate(0); + auto sm_dag = cudaq::boson_op::create(0); // Number operators // These operators count the number of excitations. // For the atom (`subsytem` 0) and the cavity (`subsystem` 1) they give the // population in each subsystem. - auto atom_occ_op = cudaq::matrix_operator::number(0); - auto cavity_occ_op = cudaq::matrix_operator::number(1); + auto atom_occ_op = cudaq::matrix_op::number(0); + auto cavity_occ_op = cudaq::matrix_op::number(1); // Hamiltonian // The `hamiltonian` models the dynamics of the atom-cavity (cavity QED) diff --git a/docs/sphinx/examples/cpp/dynamics/cross_resonance.cpp b/docs/sphinx/examples/cpp/dynamics/cross_resonance.cpp index da8b8033503..02df481de62 100644 --- a/docs/sphinx/examples/cpp/dynamics/cross_resonance.cpp +++ b/docs/sphinx/examples/cpp/dynamics/cross_resonance.cpp @@ -40,14 +40,14 @@ int main() { // operators allow transitions between the spin states (|0> and |1>). auto spin_plus = [](int degree) { return 0.5 * - (cudaq::spin_operator::x(degree) + - std::complex(0.0, 1.0) * cudaq::spin_operator::y(degree)); + (cudaq::spin_handler::x(degree) + + std::complex(0.0, 1.0) * cudaq::spin_handler::y(degree)); }; auto spin_minus = [](int degree) { return 0.5 * - (cudaq::spin_operator::x(degree) - - std::complex(0.0, 1.0) * cudaq::spin_operator::y(degree)); + (cudaq::spin_handler::x(degree) - + std::complex(0.0, 1.0) * cudaq::spin_handler::y(degree)); }; // The Hamiltonian describes the energy and dynamics of our 2-qubit system. @@ -61,10 +61,10 @@ int main() { // 4. `Crosstalk` drive on qubit 1: m_12 * Omega * X. A reduces drive on qubit // 1 due to electromagnetic `crosstalk`. auto hamiltonian = - (delta / 2.0) * cudaq::spin_operator::z(0) + + (delta / 2.0) * cudaq::spin_handler::z(0) + J * (spin_minus(1) * spin_plus(0) + spin_plus(1) * spin_minus(0)) + - Omega * cudaq::spin_operator::x(0) + - m_12 * Omega * cudaq::spin_operator::x(1); + Omega * cudaq::spin_handler::x(0) + + m_12 * Omega * cudaq::spin_handler::x(1); // Each qubit is a 2-level system (dimension 2). // The composite system (two qubits) has a total Hilbert space dimension of 2 @@ -98,9 +98,9 @@ int main() { // The observables are the spin components along the x, y, and z directions // for both qubits. These observables will be measured during the evolution. - auto observables = {cudaq::spin_operator::x(0), cudaq::spin_operator::y(0), - cudaq::spin_operator::z(0), cudaq::spin_operator::x(1), - cudaq::spin_operator::y(1), cudaq::spin_operator::z(1)}; + auto observables = {cudaq::spin_handler::x(0), cudaq::spin_handler::y(0), + cudaq::spin_handler::z(0), cudaq::spin_handler::x(1), + cudaq::spin_handler::y(1), cudaq::spin_handler::z(1)}; // Evolution with 2 initial states // We evolve the system under the defined Hamiltonian for both initial states diff --git a/docs/sphinx/examples/cpp/dynamics/heisenberg_model.cpp b/docs/sphinx/examples/cpp/dynamics/heisenberg_model.cpp index c8850b12654..8f0b55f2bd9 100644 --- a/docs/sphinx/examples/cpp/dynamics/heisenberg_model.cpp +++ b/docs/sphinx/examples/cpp/dynamics/heisenberg_model.cpp @@ -44,9 +44,9 @@ int main() { // The staggered magnetization operator is used to measure antiferromagnetic // order. It is defined as a sum over all spins of the Z operator, alternating // in sign. For even sites, we add `sz`; for odd sites, we subtract `sz`. - auto staggered_magnetization_t = cudaq::matrix_operator::empty(); + auto staggered_magnetization_t = cudaq::sum_op::empty(); for (int i = 0; i < num_spins; i++) { - auto sz = cudaq::spin_operator::z(i); + auto sz = cudaq::spin_handler::z(i); if (i % 2 == 0) { staggered_magnetization_t += sz; } else { @@ -83,14 +83,14 @@ int main() { // H = H + `Jy` * `Sy`_i * `Sy`_{i+1} // H = H + `Jz` * `Sz`_i * `Sz`_{i+1} // This is a form of the `anisotropic` Heisenberg (or `XYZ`) model. - auto hamiltonian = cudaq::spin_operator::empty(); + auto hamiltonian = cudaq::sum_op::empty(); for (int i = 0; i < num_spins - 1; i++) { - hamiltonian = hamiltonian + Jx * cudaq::spin_operator::x(i) * - cudaq::spin_operator::x(i + 1); - hamiltonian = hamiltonian + Jy * cudaq::spin_operator::y(i) * - cudaq::spin_operator::y(i + 1); - hamiltonian = hamiltonian + Jz * cudaq::spin_operator::z(i) * - cudaq::spin_operator::z(i + 1); + hamiltonian = hamiltonian + Jx * cudaq::spin_handler::x(i) * + cudaq::spin_handler::x(i + 1); + hamiltonian = hamiltonian + Jy * cudaq::spin_handler::y(i) * + cudaq::spin_handler::y(i + 1); + hamiltonian = hamiltonian + Jz * cudaq::spin_handler::z(i) * + cudaq::spin_handler::z(i + 1); } // Initial state vector diff --git a/docs/sphinx/examples/cpp/dynamics/qubit_control.cpp b/docs/sphinx/examples/cpp/dynamics/qubit_control.cpp index 9b704d88f75..c2c38ea9ce5 100644 --- a/docs/sphinx/examples/cpp/dynamics/qubit_control.cpp +++ b/docs/sphinx/examples/cpp/dynamics/qubit_control.cpp @@ -46,8 +46,8 @@ int main() { // 2. A time-dependent driving term: omega_x * cos(omega_drive * t) * `Sx`_0, // which induces rotations about the X-axis. The scalar_operator(mod_`func`) // allows the drive term to vary in time according to mod_`func`. - auto hamiltonian = 0.5 * omega_z * cudaq::spin_operator::z(0) + - mod_func * cudaq::spin_operator::x(0) * omega_x; + auto hamiltonian = 0.5 * omega_z * cudaq::spin_handler::z(0) + + mod_func * cudaq::spin_handler::x(0) * omega_x; // A single qubit with dimension 2. cudaq::dimension_map dimensions = {{0, 2}}; @@ -76,8 +76,8 @@ int main() { // Measure the expectation values of the `qubit's` spin components along the // X, Y, and Z directions. - auto observables = {cudaq::spin_operator::x(0), cudaq::spin_operator::y(0), - cudaq::spin_operator::z(0)}; + auto observables = {cudaq::spin_handler::x(0), cudaq::spin_handler::y(0), + cudaq::spin_handler::z(0)}; // Simulation without decoherence // Evolve the system under the Hamiltonian, using the specified schedule and @@ -93,7 +93,7 @@ int main() { double gamma_sz = 1.0; auto evolve_result_decay = cudaq::evolve( hamiltonian, dimensions, schedule, psi0, integrator, - {std::sqrt(gamma_sz) * cudaq::spin_operator::z(0)}, observables, true); + {std::sqrt(gamma_sz) * cudaq::spin_handler::z(0)}, observables, true); // Lambda to extract expectation values for a given observable index auto get_expectation = [](int idx, auto &result) -> std::vector { diff --git a/docs/sphinx/examples/cpp/dynamics/qubit_dynamics.cpp b/docs/sphinx/examples/cpp/dynamics/qubit_dynamics.cpp index e60fae108a5..256dbf9ada7 100644 --- a/docs/sphinx/examples/cpp/dynamics/qubit_dynamics.cpp +++ b/docs/sphinx/examples/cpp/dynamics/qubit_dynamics.cpp @@ -21,7 +21,7 @@ int main() { // Qubit `hamiltonian`: 2 * pi * 0.1 * sigma_x // Physically, this represents a qubit (a two-level system) driven by a weak // transverse field along the x-axis. - auto hamiltonian = 2.0 * M_PI * 0.1 * cudaq::spin_operator::x(0); + auto hamiltonian = 2.0 * M_PI * 0.1 * cudaq::spin_handler::x(0); // Dimensions: one subsystem of dimension 2 (a two-level system). const cudaq::dimension_map dimensions = {{0, 2}}; @@ -40,15 +40,15 @@ int main() { // Run the simulation without collapse operators (ideal evolution) auto evolve_result = cudaq::evolve( hamiltonian, dimensions, schedule, psi0, integrator, {}, - {cudaq::spin_operator::y(0), cudaq::spin_operator::z(0)}, true); + {cudaq::spin_handler::y(0), cudaq::spin_handler::z(0)}, true); constexpr double decay_rate = 0.05; - auto collapse_operator = std::sqrt(decay_rate) * cudaq::spin_operator::x(0); + auto collapse_operator = std::sqrt(decay_rate) * cudaq::spin_handler::x(0); // Evolve with collapse operators cudaq::evolve_result evolve_result_decay = cudaq::evolve( hamiltonian, dimensions, schedule, psi0, integrator, {collapse_operator}, - {cudaq::spin_operator::y(0), cudaq::spin_operator::z(0)}, true); + {cudaq::spin_handler::y(0), cudaq::spin_handler::z(0)}, true); // Lambda to extract expectation values for a given observable index auto get_expectation = [](int idx, auto &result) -> std::vector { diff --git a/docs/sphinx/snippets/cpp/using/backends/trajectory_batching.cpp b/docs/sphinx/snippets/cpp/using/backends/trajectory_batching.cpp new file mode 100644 index 00000000000..42db95fa01e --- /dev/null +++ b/docs/sphinx/snippets/cpp/using/backends/trajectory_batching.cpp @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// [Begin Documentation] +#include +#include +#include + +struct xOp { + void operator()(int qubit_count) __qpu__ { + cudaq::qvector q(qubit_count); + x(q); + mz(q); + } +}; + +int main() { + // Add a simple bit-flip noise channel to X gate + const double error_probability = 0.01; + + cudaq::bit_flip_channel bit_flip(error_probability); + // Add noise channels to our noise model. + cudaq::noise_model noise_model; + // Apply the bitflip channel to any X-gate on any qubits + noise_model.add_all_qubit_channel(bit_flip); + + const int qubit_count = 10; + const auto start_time = std::chrono::high_resolution_clock::now(); + // Due to the impact of noise, our measurements will no longer be uniformly in + // the |1...1> state. + auto counts = + cudaq::sample({.shots = 1000, .noise = noise_model}, xOp{}, qubit_count); + const auto end_time = std::chrono::high_resolution_clock::now(); + counts.dump(); + const std::chrono::duration elapsed_time = + end_time - start_time; + std::cout << "Simulation elapsed time: " << elapsed_time.count() << "ms\n"; + return 0; +} diff --git a/docs/sphinx/snippets/python/using/backends/trajectory.py b/docs/sphinx/snippets/python/using/backends/trajectory.py index f64fed15352..bb0afa367fa 100644 --- a/docs/sphinx/snippets/python/using/backends/trajectory.py +++ b/docs/sphinx/snippets/python/using/backends/trajectory.py @@ -10,6 +10,9 @@ import cudaq # Use the `nvidia` target +# Other targets capable of trajectory simulation are: +# - `tensornet` +# - `tensornet-mps` cudaq.set_target("nvidia") # Let's define a simple kernel that we will add noise to. diff --git a/docs/sphinx/snippets/python/using/backends/trajectory_batching.py b/docs/sphinx/snippets/python/using/backends/trajectory_batching.py new file mode 100644 index 00000000000..a42c17324b4 --- /dev/null +++ b/docs/sphinx/snippets/python/using/backends/trajectory_batching.py @@ -0,0 +1,46 @@ +# ============================================================================ # +# Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. # +# All rights reserved. # +# # +# This source code and the accompanying materials are made available under # +# the terms of the Apache License 2.0 which accompanies this distribution. # +# ============================================================================ # + +#[Begin Docs] +import time +import cudaq +# Use the `nvidia` target +cudaq.set_target("nvidia") + +# Let's define a simple kernel that we will add noise to. +qubit_count = 10 + + +@cudaq.kernel +def kernel(qubit_count: int): + qvector = cudaq.qvector(qubit_count) + x(qvector) + mz(qvector) + + +# Add a simple bit-flip noise channel to X gate +error_probability = 0.01 +bit_flip = cudaq.BitFlipChannel(error_probability) + +# Add noise channels to our noise model. +noise_model = cudaq.NoiseModel() +# Apply the bit-flip channel to any X-gate on any qubits +noise_model.add_all_qubit_channel("x", bit_flip) + +ideal_counts = cudaq.sample(kernel, qubit_count, shots_count=1000) + +start = time.time() +# Due to the impact of noise, our measurements will no longer be uniformly +# in the |1...1> state. +noisy_counts = cudaq.sample(kernel, + qubit_count, + noise_model=noise_model, + shots_count=1000) +end = time.time() +noisy_counts.dump() +print(f"Simulation elapsed time: {(end - start) * 1000} ms") \ No newline at end of file diff --git a/docs/sphinx/snippets/python/using/backends/trajectory_observe.py b/docs/sphinx/snippets/python/using/backends/trajectory_observe.py index b025640e231..b3332b68d93 100644 --- a/docs/sphinx/snippets/python/using/backends/trajectory_observe.py +++ b/docs/sphinx/snippets/python/using/backends/trajectory_observe.py @@ -11,6 +11,9 @@ from cudaq import spin # Use the `nvidia` target +# Other targets capable of trajectory simulation are: +# - `tensornet` +# - `tensornet-mps` cudaq.set_target("nvidia") diff --git a/docs/sphinx/using/applications.rst b/docs/sphinx/using/applications.rst index 9d1dc00cc1c..1d010e3b274 100644 --- a/docs/sphinx/using/applications.rst +++ b/docs/sphinx/using/applications.rst @@ -19,7 +19,6 @@ Applications that give an in depth view of CUDA-Q and its applications in Python /applications/python/qaoa.ipynb /applications/python/digitized_counterdiabatic_qaoa.ipynb /applications/python/krylov.ipynb - /applications/python/afqmc.ipynb /applications/python/quantum_fourier_transform.ipynb /applications/python/quantum_teleportation.ipynb /applications/python/quantum_transformer.ipynb diff --git a/docs/sphinx/using/backends/sims/noisy.rst b/docs/sphinx/using/backends/sims/noisy.rst index 54b397dee10..81371c8bbeb 100644 --- a/docs/sphinx/using/backends/sims/noisy.rst +++ b/docs/sphinx/using/backends/sims/noisy.rst @@ -4,15 +4,12 @@ Noisy Simulators Trajectory Noisy Simulation ++++++++++++++++++++++++++++++++++ -The :code:`nvidia` target supports noisy quantum circuit simulations using -quantum trajectory method across all configurations: single GPU, multi-node -multi-GPU, and with host memory. When simulating many trajectories with small -state vectors, the simulation is batched for optimal performance. +CUDA-Q GPU simulator backends, :code:`nvidia`, :code:`tensornet`, and :code:`tensornet-mps`, +supports noisy quantum circuit simulations using quantum trajectory method. -When a :code:`noise_model` is provided to CUDA-Q, the :code:`nvidia` target +When a :code:`noise_model` is provided to CUDA-Q, the backend target will incorporate quantum noise into the quantum circuit simulation according -to the noise model specified. - +to the noise model specified, as shown in the below example. .. tab:: Python @@ -33,14 +30,70 @@ to the noise model specified. .. code:: bash + # nvidia target nvq++ --target nvidia program.cpp [...] -o program.x ./program.x { 00:15 01:92 10:81 11:812 } + # tensornet target + nvq++ --target tensornet program.cpp [...] -o program.x + ./program.x + { 00:10 01:108 10:73 11:809 } + # tensornet-mps target + nvq++ --target tensornet-mps program.cpp [...] -o program.x + ./program.x + { 00:5 01:86 10:102 11:807 } + + +In the case of bit-string measurement sampling as in the above example, each measurement 'shot' is executed as a trajectory, +whereby Kraus operators specified in the noise model are sampled. + + +Unitary Mixture vs. General Noise Channel +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Quantum noise channels can be classified into two categories: -In the case of bit-string measurement sampling as in the above example, each measurement 'shot' is executed as a trajectory, whereby Kraus operators specified in the noise model are sampled. +(1) Unitary mixture -For observable expectation value estimation, the statistical error scales asymptotically as :math:`1/\sqrt{N_{trajectories}}`, where :math:`N_{trajectories}` is the number of trajectories. +The noise channel can be defined by a set of unitary matrices along with list of probabilities associated with those matrices. +The depolarizing channel is an example of unitary mixture, whereby `I` (no noise), `X`, `Y`, or `Z` unitaries may occur to the +quantum state at pre-defined probabilities. + +(2) General noise channel + +The channel is defined as a set of non-unitary Kraus matrices, satisfying the completely positive and trace preserving (CPTP) condition. +An example of this type of channels is the amplitude damping noise channel. + +In trajectory simulation method, simulating unitary mixture noise channels is more efficient than +general noise channels since the trajectory sampling of the latter requires probability calculation based +on the immediate quantum state. + +.. note:: + CUDA-Q noise channel utility automatically detects whether a list of Kraus matrices can be converted to + the unitary mixture representation for more efficient simulation. + +.. list-table:: **Noise Channel Support** + :widths: 20 30 50 + + * - Backend + - Unitary Mixture + - General Channel + * - :code:`nvidia` + - YES + - YES + * - :code:`tensornet` + - YES + - NO + * - :code:`tensornet-mps` + - YES + - YES (number of qubits > 1) + + +Trajectory Expectation Value Calculation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In trajectory simulation method, the statistical error of observable expectation value estimation scales asymptotically +as :math:`1/\sqrt{N_{trajectories}}`, where :math:`N_{trajectories}` is the number of trajectories. Hence, depending on the required level of accuracy, the number of trajectories can be specified accordingly. .. tab:: Python @@ -63,14 +116,47 @@ Hence, depending on the required level of accuracy, the number of trajectories c .. code:: bash + # nvidia target nvq++ --target nvidia program.cpp [...] -o program.x ./program.x Noisy with 1024 trajectories = -0.810547 Noisy with 8192 trajectories = -0.800049 + # tensornet target + nvq++ --target tensornet program.cpp [...] -o program.x + ./program.x + Noisy with 1024 trajectories = -0.777344 + Noisy with 8192 trajectories = -0.800537 + + # tensornet-mps target + nvq++ --target tensornet-mps program.cpp [...] -o program.x + ./program.x + Noisy with 1024 trajectories = -0.828125 + Noisy with 8192 trajectories = -0.801758 + +In the above example, as we increase the number of trajectories, +the result of CUDA-Q `observe` approaches the true value. + +.. note:: + With trajectory noisy simulation, the result of CUDA-Q `observe` is inherently stochastic. + For a small number of qubits, the true expectation value can be simulated by the :ref:`density matrix ` simulator. + +Batched Trajectory Simulation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +On the :code:`nvidia` target, when simulating many trajectories with small +state vectors, the simulation is batched for optimal performance. -The following environment variable options are applicable to the :code:`nvidia` target for trajectory noisy simulation. Any environment variables must be set -prior to setting the target. +.. note:: + + Batched trajectory simulation is only available on the single-GPU execution mode of the :code:`nvidia` target. + + If batched trajectory simulation is not activated, e.g., due to problem size, number of trajectories, + or the nature of the circuit (dynamic circuits with mid-circuit measurements and conditional branching), + the required number of trajectories will be executed sequentially. + +The following environment variable options are applicable to the :code:`nvidia` target for batched trajectory noisy simulation. +Any environment variables must be set prior to setting the target or running "`import cudaq`". .. list-table:: **Additional environment variable options for trajectory simulation** :widths: 20 30 50 @@ -78,9 +164,6 @@ prior to setting the target. * - Option - Value - Description - * - ``CUDAQ_OBSERVE_NUM_TRAJECTORIES`` - - positive integer - - The default number of trajectories for observe simulation if none was provided in the `observe` call. The default value is 1000. * - ``CUDAQ_BATCH_SIZE`` - positive integer or `NONE` - The number of state vectors in the batched mode. If `NONE`, the batch size will be calculated based on the available device memory. Default is `NONE`. @@ -95,11 +178,45 @@ prior to setting the target. - The minimum number of trajectories for batching. If the number of trajectories is less than this value, batched trajectory simulation will be disabled. Default value is 4. .. note:: - - Batched trajectory simulation is only available on the single-GPU execution mode of the :code:`nvidia` target. - - If batched trajectory simulation is not activated, e.g., due to problem size, number of trajectories, or the nature of the circuit (dynamic circuits with mid-circuit measurements and conditional branching), the required number of trajectories will be executed sequentially. + The default batched trajectory simulation parameters have been chosen for optimal performance. + +In the below example, we demonstrate the use of these parameters to control trajectory batching. + +.. tab:: Python + + .. literalinclude:: ../../../snippets/python/using/backends/trajectory_batching.py + :language: python + :start-after: [Begin Docs] + + .. code:: bash + + # Default batching parameter + python3 program.py + Simulation elapsed time: 45.75657844543457 ms + + # Disable batching by setting batch size to 1 + CUDAQ_BATCH_SIZE=1 python3 program.py + Simulation elapsed time: 716.090202331543 ms + +.. tab:: C++ + + .. literalinclude:: ../../../snippets/cpp/using/backends/trajectory_batching.cpp + :language: cpp + :start-after: [Begin Documentation] + + .. code:: bash + + nvq++ --target nvidia program.cpp [...] -o program.x + # Default batching parameter + ./program.x + Simulation elapsed time: 45.47ms + # Disable batching by setting batch size to 1 + Simulation elapsed time: 558.66ms + +.. note:: + The :code:`CUDAQ_LOG_LEVEL` :doc:`environment variable <../../basics/troubleshooting>` can be used to + view detailed logs of batched trajectory simulation, e.g., the batch size. Density Matrix diff --git a/docs/sphinx/using/backends/sims/svsims.rst b/docs/sphinx/using/backends/sims/svsims.rst index b4a4c875b7d..b90a4464d30 100644 --- a/docs/sphinx/using/backends/sims/svsims.rst +++ b/docs/sphinx/using/backends/sims/svsims.rst @@ -96,8 +96,8 @@ To execute a program on the :code:`nvidia` backend, use the following commands: In the single-GPU mode, the :code:`nvidia` backend provides the following -environment variable options. Any environment variables must be set prior to -setting the target. It is worth drawing attention to gate fusion, a powerful tool for improving simulation performance which is discussed in greater detail `here `__. +environment variable options. Any environment variables must be set prior to setting the target or running "`import cudaq`". +It is worth drawing attention to gate fusion, a powerful tool for improving simulation performance which is discussed in greater detail `here `__. .. list-table:: **Environment variable options supported in single-GPU mode** :widths: 20 30 50 @@ -121,6 +121,7 @@ setting the target. It is worth drawing attention to gate fusion, a powerful too - positive integer, or `NONE` - GPU memory (in GB) allowed for on-device state-vector allocation. As the state-vector size exceeds this limit, host memory will be utilized for migration. `NONE` means unlimited (up to physical memory constraints). This is the default. + .. deprecated:: 0.8 The :code:`nvidia-fp64` targets, which is equivalent setting the `fp64` option on the :code:`nvidia` target, is deprecated and will be removed in a future release. @@ -212,8 +213,8 @@ See the `Divisive Clustering { ]; } -def GetConcreteMatrix : Pass<"get-concrete-matrix", "mlir::func::FuncOp"> { +def GetConcreteMatrix : Pass<"get-concrete-matrix", "mlir::ModuleOp"> { let summary = "Replace the unitary matrix generator function with a constant matrix."; let description = [{ diff --git a/lib/Optimizer/Transforms/GetConcreteMatrix.cpp b/lib/Optimizer/Transforms/GetConcreteMatrix.cpp index 27ab539f56f..1399f9e637e 100644 --- a/lib/Optimizer/Transforms/GetConcreteMatrix.cpp +++ b/lib/Optimizer/Transforms/GetConcreteMatrix.cpp @@ -7,13 +7,9 @@ ******************************************************************************/ #include "PassDetails.h" -#include "cudaq/Optimizer/Builder/Intrinsics.h" #include "cudaq/Optimizer/Dialect/CC/CCOps.h" #include "cudaq/Optimizer/Dialect/Quake/QuakeOps.h" #include "cudaq/Optimizer/Transforms/Passes.h" -#include "mlir/Dialect/LLVMIR/LLVMDialect.h" -#include "mlir/Dialect/LLVMIR/LLVMTypes.h" -#include "mlir/Transforms/DialectConversion.h" #include "mlir/Transforms/GreedyPatternRewriteDriver.h" #include "mlir/Transforms/Passes.h" @@ -68,7 +64,6 @@ class CustomUnitaryPattern parentModule.lookupSymbol(concreteMatrix); if (ccGlobalOp) { - rewriter.replaceOpWithNewOp( customOp, FlatSymbolRefAttr::get(parentModule.getContext(), concreteMatrix), @@ -86,12 +81,11 @@ class GetConcreteMatrixPass using GetConcreteMatrixBase::GetConcreteMatrixBase; void runOnOperation() override { - func::FuncOp func = getOperation(); auto *ctx = &getContext(); RewritePatternSet patterns(ctx); patterns.insert(ctx); - if (failed(applyPatternsAndFoldGreedily(func.getOperation(), - std::move(patterns)))) + if (failed( + applyPatternsAndFoldGreedily(getOperation(), std::move(patterns)))) signalPassFailure(); } }; diff --git a/pyproject.toml b/pyproject.toml index 766693a595b..4a072f0eaf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,11 +18,12 @@ requires-python = ">=3.10" license = { file="LICENSE" } dependencies = [ 'astpretty ~= 3.0', - 'cuquantum-python-cu12 >= 24.11', + 'cuquantum-python-cu12 >= 25.03', 'numpy >= 1.24', 'scipy >= 1.10.1', 'requests >= 2.31', 'nvidia-cublas-cu12 ~= 12.0', + 'nvidia-curand-cu12 ~= 10.3', 'nvidia-cuda-runtime-cu12 ~= 12.0', 'nvidia-cusolver-cu12 ~= 11.4', 'nvidia-cuda-nvrtc-cu12 ~= 12.0' diff --git a/python/README.md.in b/python/README.md.in index e2250e878cf..5bb5f96a948 100644 --- a/python/README.md.in +++ b/python/README.md.in @@ -36,6 +36,8 @@ ${{ package_name }}`. Please make sure your `pip` version is \>= 24.0. If you have an NVIDIA GPU on your host system, you will be able to use it without any further installation steps. +${{ deprecation_notice }} + > **Important:** > Please check if you have an existing installation of the `cuda-quantum`, `cudaq-quantum-cu11`, or `cuda-quantum-cu12` package, and uninstall it prior to installation. Different CUDA-Q binary distributions may conflict with each other causing issues. diff --git a/python/cudaq/__init__.py b/python/cudaq/__init__.py index 16fbd448fbc..3a6cffe22f6 100644 --- a/python/cudaq/__init__.py +++ b/python/cudaq/__init__.py @@ -27,25 +27,18 @@ cutensor_libs = get_library_path(f"cutensor-cu{cuda_major}") cutensor_path = os.path.join(cutensor_libs, "libcutensor.so.2") + curand_libs = get_library_path(f"nvidia-curand-cu{cuda_major}") + curand_path = os.path.join(curand_libs, "libcurand.so.10") + + cudart_libs = get_library_path(f"nvidia-cuda_runtime-cu{cuda_major}") + cudart_path = os.path.join(cudart_libs, f"libcudart.so.{cuda_major}") + + cuda_nvrtc_libs = get_library_path(f"nvidia-cuda_nvrtc-cu{cuda_major}") + cuda_nvrtc_path = os.path.join(cuda_nvrtc_libs, + f"libnvrtc.so.{cuda_major}") + os.environ[ - "CUDAQ_DYNLIBS"] = f"{custatevec_path}:{cutensornet_path}:{cutensor_path}" - - # The following package is only available on `x86_64` (not `aarch64`). For - # `aarch64`, the library must be provided another way (likely with - # LD_LIBRARY_PATH). - # Note: platform.processor does not work in all cases (if `uname -p` returns - # unknown, e.g. on WSL) - if platform.processor() == "x86_64" or platform.uname( - ).machine == "x86_64": - cudart_libs = get_library_path( - f"nvidia-cuda_runtime-cu{cuda_major}") - cudart_path = os.path.join(cudart_libs, - f"libcudart.so.{cuda_major}") - cuda_nvrtc_libs = get_library_path( - f"nvidia-cuda_nvrtc-cu{cuda_major}") - cuda_nvrtc_path = os.path.join(cuda_nvrtc_libs, - f"libnvrtc.so.{cuda_major}") - os.environ["CUDAQ_DYNLIBS"] += f":{cudart_path}:{cuda_nvrtc_path}" + "CUDAQ_DYNLIBS"] = f"{custatevec_path}:{cutensornet_path}:{cutensor_path}:{cudart_path}:{curand_path}:{cuda_nvrtc_path}" except: import importlib.util package_spec = importlib.util.find_spec(f"cuda-quantum-cu{cuda_major}") diff --git a/python/cudaq/operator/cudm_helpers.py b/python/cudaq/operator/cudm_helpers.py index 6dc49992c5a..382aa01af31 100644 --- a/python/cudaq/operator/cudm_helpers.py +++ b/python/cudaq/operator/cudm_helpers.py @@ -13,12 +13,18 @@ from .manipulation import OperatorArithmetics import logging from .schedule import Schedule +import warnings cudm = None CudmStateType = None try: - from cuquantum import densitymat as cudm - from cuquantum.densitymat._internal.callbacks import CallbackCoefficient + # Suppress deprecation warnings on `cuquantum` import. + # FIXME: remove this after `cuquantum` no longer warns on import. + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + from cuquantum import densitymat as cudm + from cuquantum.densitymat.callbacks import Callback as CallbackCoefficient + from cuquantum.densitymat.callbacks import CPUCallback CudmStateType = Union[cudm.DensePureState, cudm.DenseMixedState] CudmOperator = cudm.Operator CudmOperatorTerm = cudm.OperatorTerm @@ -45,17 +51,35 @@ def __init__(self, self._dimensions = dimensions self._schedule = schedule - def _callback_mult_op(self, scalar: CallbackCoefficient, - op: CudmOperatorTerm) -> CudmOperatorTerm: - new_opterm = CudmOperatorTerm(dtype=op.dtype) - for term, modes, duals, coeff in zip(op.terms, op.modes, op.duals, - op._coefficients): - combined_terms = [] - for sub_op, degrees, duals in zip(term, modes, duals): - combined_terms.append((sub_op, degrees, duals)) - new_opterm += cudm.tensor_product(*combined_terms, - coeff=coeff * scalar) - return new_opterm + def _scalar_mult( + self, scalar1: CallbackCoefficient | Number, + scalar2: CallbackCoefficient | Number + ) -> CallbackCoefficient | Number: + if isinstance(scalar1, Number) and isinstance(scalar2, Number): + return scalar1 * scalar2 + + if isinstance(scalar1, CallbackCoefficient) and isinstance( + scalar2, CallbackCoefficient): + return CPUCallback(lambda t, args: scalar1.callback(t, args) * + scalar2.callback(t, args)) + + if isinstance(scalar1, CallbackCoefficient): + if not isinstance(scalar2, Number): + raise ValueError( + f"Unexpected scalar type {type(scalar2)} in multiplication") + return CPUCallback( + lambda t, args: scalar1.callback(t, args) * scalar2) + + if isinstance(scalar2, CallbackCoefficient): + if not isinstance(scalar1, Number): + raise ValueError( + f"Unexpected scalar type {type(scalar1)} in multiplication") + return CPUCallback( + lambda t, args: scalar2.callback(t, args) * scalar1) + + raise ValueError( + f"Unexpected scalar types: {type(scalar1)} and {type(scalar2)} in multiplication" + ) def tensor( self, op1: CudmOperatorTerm | CallbackCoefficient | Number, @@ -70,29 +94,12 @@ def tensor( logger.info(f" {op2}:") for term, coeff in zip(op2.terms, op2._coefficients): logger.info(f" {coeff} * {term}") - if isinstance(op1, Number) or isinstance(op2, Number): - return op1 * op2 - if isinstance(op1, CallbackCoefficient) and isinstance( - op2, CallbackCoefficient): - return op1 * op2 - if isinstance(op1, CallbackCoefficient): - return self._callback_mult_op(op1, op2) - if isinstance(op2, CallbackCoefficient): - return self._callback_mult_op(op2, op1) - new_opterm = CudmOperatorTerm(dtype=op1.dtype) - for term2, modes2, duals2, coeff2 in zip(op2.terms, op2.modes, - op2.duals, op2._coefficients): - for term1, modes1, duals1, coeff1 in zip(op1.terms, op1.modes, - op1.duals, - op1._coefficients): - combined_terms = [] - for sub_op, degrees, duals in zip(term1, modes1, duals1): - combined_terms.append((sub_op, degrees, duals)) - for sub_op, degrees, duals in zip(term2, modes2, duals2): - combined_terms.append((sub_op, degrees, duals)) - new_opterm += cudm.tensor_product(*combined_terms, - coeff=coeff1 * coeff2) - return new_opterm + if isinstance(op1, Number | CallbackCoefficient) and isinstance( + op2, Number | CallbackCoefficient): + return self._scalar_mult(op1, op2) + + # IMPORTANT: `op1 * op2` as written means `op2` to be applied first then `op1`. + return op2 * op1 def mul( self, op1: CudmOperatorTerm | CallbackCoefficient | Number, @@ -108,31 +115,12 @@ def mul( for term, coeff in zip(op2.terms, op2._coefficients): logger.info(f" {coeff} * {term}") - if isinstance(op1, Number) or isinstance(op2, Number): - return op1 * op2 - if isinstance(op1, CallbackCoefficient) and isinstance( - op2, CallbackCoefficient): - return op1 * op2 - if isinstance(op1, CallbackCoefficient): - return self._callback_mult_op(op1, op2) - if isinstance(op2, CallbackCoefficient): - return self._callback_mult_op(op2, op1) - new_opterm = CudmOperatorTerm(dtype=op1.dtype) - for term2, modes2, duals2, coeff2 in zip(op2.terms, op2.modes, - op2.duals, op2._coefficients): - for term1, modes1, duals1, coeff1 in zip(op1.terms, op1.modes, - op1.duals, - op1._coefficients): - combined_terms = [] - for sub_op, degrees, duals in zip(term2, modes2, duals2): - combined_terms.append((sub_op, degrees, duals)) - - for sub_op, degrees, duals in zip(term1, modes1, duals1): - combined_terms.append((sub_op, degrees, duals)) + if isinstance(op1, Number | CallbackCoefficient) and isinstance( + op2, Number | CallbackCoefficient): + return self._scalar_mult(op1, op2) - new_opterm += cudm.tensor_product(*combined_terms, - coeff=coeff1 * coeff2) - return new_opterm + # IMPORTANT: `op1 * op2` as written means `op2` to be applied first then `op1`. + return op2 * op1 def _scalar_to_op(self, scalar: CallbackCoefficient | Number) -> CudmOperatorTerm: @@ -210,7 +198,7 @@ def evaluate( logger.info(f"Evaluating {op}") if isinstance(op, ScalarOperator): if op._constant_value is None: - return CallbackCoefficient( + return CPUCallback( self._wrap_callback(op.generator, op.parameters)) else: return op._constant_value @@ -223,7 +211,7 @@ def evaluate( raise ValueError( f"Parameter '{param}' of operator '{op._id}' not found in schedule. Valid schedule parameters are: {self._schedule._parameters}" ) - cudm_callback = self._wrap_callback_tensor(op) + cudm_callback = CPUCallback(self._wrap_callback_tensor(op)) c_representative_tensor = cudm_callback(0.0, ()) return cudm.tensor_product((cudm.DenseOperator( c_representative_tensor, cudm_callback), op.degrees), @@ -234,73 +222,15 @@ def evaluate( (cudm.DenseOperator(op_mat), op.degrees), coeff=1.0) -def computeLindladOp(hilbert_space_dims: List[int], l1: CudmOperatorTerm, - l2: CudmOperatorTerm): +def computeLindladOp(hilbert_space_dims: List[int], l_op: CudmOperatorTerm): """ Helper function to compute the Lindlad (super-)operator """ - D_terms = [] - - def conjugate_coeff(coeff: CallbackCoefficient): - if coeff.is_callable: - return CallbackCoefficient( - lambda t, args: numpy.conjugate(coeff.callback(t, args))) - return numpy.conjugate(coeff.scalar) - - for term1, modes1, duals1, coeff1 in zip(l1.terms, l1.modes, l1.duals, - l1._coefficients): - for term2, modes2, duals2, coeff2 in zip(l2.terms, l2.modes, l2.duals, - l2._coefficients): - coeff = coeff1 * conjugate_coeff(coeff2) - d1_terms = [] - for sub_op_1, degrees, duals in zip(term1, modes1, duals1): - op_mat = sub_op_1.to_array() - d1_terms.append((op_mat, degrees, duals)) - for sub_op_2, degrees, duals in zip(reversed(term2), - reversed(modes2), - reversed(duals2)): - op_mat = sub_op_2.to_array() - flipped_duals = tuple((not elem for elem in duals)) - d1_terms.append( - (numpy.ascontiguousarray(numpy.conj(op_mat).T).copy(), - degrees, flipped_duals)) - D1 = cudm.tensor_product(*d1_terms, coeff=coeff) - D_terms.append(tuple((D1, 1.0))) - - d2_terms = [] - for sub_op_2, degrees, duals in zip(reversed(term2), - reversed(modes2), - reversed(duals2)): - op_mat = sub_op_2.to_array() - flipped_duals = tuple((not elem for elem in duals)) - d2_terms.append( - (numpy.ascontiguousarray(numpy.conj(op_mat).T).copy(), - degrees, flipped_duals)) - for sub_op_1, degrees, duals in zip(term1, modes1, duals1): - op_mat = sub_op_1.to_array() - flipped_duals = tuple((not elem for elem in duals)) - d2_terms.append((op_mat, degrees, flipped_duals)) - D2 = cudm.tensor_product(*d2_terms, - coeff=-0.5 * coeff1 * - conjugate_coeff(coeff2)) - D_terms.append(tuple((D2, 1.0))) - - d3_terms = [] - for sub_op_1, degrees, duals in zip(term1, modes1, duals1): - op_mat = sub_op_1.to_array() - d3_terms.append((op_mat, degrees, duals)) - for sub_op_2, degrees, duals in zip(reversed(term2), - reversed(modes2), - reversed(duals2)): - op_mat = sub_op_2.to_array() - d3_terms.append( - (numpy.ascontiguousarray(numpy.conj(op_mat).T).copy(), - degrees, duals)) - D3 = cudm.tensor_product(*d3_terms, - coeff=-0.5 * coeff1 * - conjugate_coeff(coeff2)) - D_terms.append(tuple((D3, 1.0))) - lindblad = cudm.Operator(hilbert_space_dims, *D_terms) + term_d1 = l_op * l_op.dag().dual() + term_d2 = l_op * l_op.dag() + term_d3 = (term_d2).dual() + lindblad = cudm.Operator(hilbert_space_dims, (term_d1, 1.0), + (term_d2, -0.5), (term_d3, -0.5)) return lindblad @@ -319,7 +249,7 @@ def constructLiouvillian(hilbert_space_dims: List[int], ham: CudmOperatorTerm, liouvillian = hamiltonian - hamiltonian.dual() for c_op in c_ops: - lindbladian = computeLindladOp(hilbert_space_dims, c_op, c_op) + lindbladian = computeLindladOp(hilbert_space_dims, c_op) liouvillian += lindbladian else: # Schrodinger equation: `d/dt psi = -iH psi` diff --git a/python/cudaq/operator/cudm_state.py b/python/cudaq/operator/cudm_state.py index bfe058fe074..d43fc04bf39 100644 --- a/python/cudaq/operator/cudm_state.py +++ b/python/cudaq/operator/cudm_state.py @@ -6,13 +6,19 @@ # the terms of the Apache License 2.0 which accompanies this distribution. # # ============================================================================ # -from cuquantum.densitymat import DenseMixedState, DensePureState, WorkStream import numpy, cupy, atexit from typing import Sequence from cupy.cuda.memory import MemoryPointer, UnownedMemory from ..mlir._mlir_libs._quakeDialects import cudaq_runtime -from cuquantum.bindings import cudensitymat as cudm from .helpers import InitialState +import warnings + +# Suppress deprecation warnings on `cuquantum` import. +# FIXME: remove this after `cuquantum` no longer warns on import. +with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + from cuquantum.densitymat import DenseMixedState, DensePureState, WorkStream + from cuquantum.bindings import cudensitymat as cudm def is_multi_processes(): @@ -49,13 +55,8 @@ def __init__(self, data): dev = cupy.cuda.Device(rank % NUM_DEVICES) dev.use() self.__ctx = WorkStream(device_id=cupy.cuda.runtime.getDevice()) - # FIXME: use the below once `cudensitymat` supports raw MPI Comm pointer. - # `ctx.set_communicator(comm=cudaq_runtime.mpi.comm_dup(), provider="MPI")` - # At the moment, only `mpi4py` communicator objects are supported, thus we use the underlying `reset_distributed_configuration` API. - _comm_ptr, _size = cudaq_runtime.mpi.comm_dup() - cudm.reset_distributed_configuration( - self.__ctx._handle._validated_ptr, - cudm.DistributedProvider.MPI, _comm_ptr, _size) + self.__ctx.set_communicator(comm=cudaq_runtime.mpi.comm_dup(), + provider="MPI") CuDensityMatState.__is_multi_process = True else: self.__ctx = WorkStream() diff --git a/python/runtime/cudaq/platform/py_alt_launch_kernel.cpp b/python/runtime/cudaq/platform/py_alt_launch_kernel.cpp index b9d21ded785..34057eddcbf 100644 --- a/python/runtime/cudaq/platform/py_alt_launch_kernel.cpp +++ b/python/runtime/cudaq/platform/py_alt_launch_kernel.cpp @@ -661,7 +661,7 @@ std::string getASM(const std::string &name, MlirModule module, pm.addNestedPass(cudaq::opt::createLiftArrayAlloc()); pm.addPass(cudaq::opt::createGlobalizeArrayValues()); pm.addNestedPass(cudaq::opt::createStatePreparation()); - pm.addNestedPass(cudaq::opt::createGetConcreteMatrix()); + pm.addPass(cudaq::opt::createGetConcreteMatrix()); pm.addPass(cudaq::opt::createUnitarySynthesis()); pm.addPass(cudaq::opt::createApplyOpSpecializationPass()); cudaq::opt::addAggressiveEarlyInlining(pm); diff --git a/python/tests/domains/test_qnn.py b/python/tests/domains/test_qnn.py index 7c53b4ff826..61660338bb2 100644 --- a/python/tests/domains/test_qnn.py +++ b/python/tests/domains/test_qnn.py @@ -59,7 +59,8 @@ def test_observeAsync_QNN(): target = cudaq.get_target('nvidia-mqpu') cudaq.set_target(target) - num_qpus = target.num_qpus() + # The number of parameters can only be split between a maximum of 2 QPUs. + num_qpus = min(target.num_qpus(), 2) n_qubits = 2 n_samples = 2 @@ -80,7 +81,6 @@ def test_observeAsync_QNN(): kernel.ry(params[i + n_qubits], qubits[i]) for i in range(n_qubits): kernel.rz(params[i + n_qubits * 2], qubits[i]) - xi = np.split(parameters, num_qpus) asyncresults = [] for i in range(len(xi)): diff --git a/runtime/common/EvolveResult.h b/runtime/common/EvolveResult.h index be308b8e479..ac1ac4c3037 100644 --- a/runtime/common/EvolveResult.h +++ b/runtime/common/EvolveResult.h @@ -34,19 +34,21 @@ class evolve_result { std::optional sampling_result = std::nullopt; // Construct from single final state. - evolve_result(state state) {} + evolve_result(state state) + : states(std::make_optional>( + std::vector{std::move(state)})) {} // Construct from single final observe result. - evolve_result(state state, const std::vector &expectations) { - if (!expectation_values.has_value()) { - expectation_values = - std::make_optional>>(); - } - - expectation_values->emplace_back(expectations); - } + evolve_result(state state, const std::vector &expectations) + : states(std::make_optional>( + std::vector{std::move(state)})), + expectation_values( + std::make_optional>>( + std::vector>{expectations})) {} - evolve_result(state state, const std::vector &expectations) { + evolve_result(state state, const std::vector &expectations) + : states(std::make_optional>( + std::vector{std::move(state)})) { std::vector result; const spin_op emptyOp( std::unordered_map>{}); @@ -54,16 +56,14 @@ class evolve_result { result.push_back(observe_result(e, emptyOp)); } - if (!expectation_values.has_value()) { - expectation_values = - std::make_optional>>(); - } - - expectation_values->emplace_back(result); + expectation_values = + std::make_optional>>( + std::vector>{result}); } // Construct from system states. - evolve_result(const std::vector &states) {} + evolve_result(const std::vector &states) + : states(std::make_optional>(states)) {} // Construct from intermediate system states and observe results. evolve_result(const std::vector &states, diff --git a/runtime/cudaq/platform/default/opt-test.yml b/runtime/cudaq/platform/default/opt-test.yml index 0ce986da703..af30c0f737d 100644 --- a/runtime/cudaq/platform/default/opt-test.yml +++ b/runtime/cudaq/platform/default/opt-test.yml @@ -22,19 +22,19 @@ configuration-matrix: config: nvqir-simulation-backend: cusvsim-fp32, custatevec-fp32 preprocessor-defines: ["-D CUDAQ_SIMULATION_SCALAR_FP32"] - target-pass-pipeline: "func.func(unwind-lowering),canonicalize,lambda-lifting,func.func(memtoreg{quantum=0}),canonicalize,apply-op-specialization,kernel-execution,aggressive-early-inlining,func.func(quake-add-metadata,const-prop-complex,lift-array-alloc),globalize-array-values,func.func(get-concrete-matrix),device-code-loader{use-quake=1},canonicalize,cse,func.func(add-dealloc,combine-quantum-alloc,canonicalize,factor-quantum-alloc,memtoreg),canonicalize,cse,add-wireset,func.func(assign-wire-indices),dep-analysis,func.func(regtomem),symbol-dce" + target-pass-pipeline: "func.func(unwind-lowering),canonicalize,lambda-lifting,func.func(memtoreg{quantum=0}),canonicalize,apply-op-specialization,kernel-execution,aggressive-early-inlining,func.func(quake-add-metadata,const-prop-complex,lift-array-alloc),globalize-array-values,get-concrete-matrix,device-code-loader{use-quake=1},canonicalize,cse,func.func(add-dealloc,combine-quantum-alloc,canonicalize,factor-quantum-alloc,memtoreg),canonicalize,cse,add-wireset,func.func(assign-wire-indices),dep-analysis,func.func(regtomem),symbol-dce" library-mode: false - name: dep-analysis-fp64 option-flags: [dep-analysis, fp64] config: nvqir-simulation-backend: cusvsim-fp64, custatevec-fp64 preprocessor-defines: ["-D CUDAQ_SIMULATION_SCALAR_FP64"] - target-pass-pipeline: "func.func(unwind-lowering),canonicalize,lambda-lifting,func.func(memtoreg{quantum=0}),canonicalize,apply-op-specialization,kernel-execution,aggressive-early-inlining,func.func(quake-add-metadata,const-prop-complex,lift-array-alloc),globalize-array-values,func.func(get-concrete-matrix),device-code-loader{use-quake=1},canonicalize,cse,func.func(add-dealloc,combine-quantum-alloc,canonicalize,factor-quantum-alloc,memtoreg),canonicalize,cse,add-wireset,func.func(assign-wire-indices),dep-analysis,func.func(regtomem),symbol-dce" + target-pass-pipeline: "func.func(unwind-lowering),canonicalize,lambda-lifting,func.func(memtoreg{quantum=0}),canonicalize,apply-op-specialization,kernel-execution,aggressive-early-inlining,func.func(quake-add-metadata,const-prop-complex,lift-array-alloc),globalize-array-values,get-concrete-matrix,device-code-loader{use-quake=1},canonicalize,cse,func.func(add-dealloc,combine-quantum-alloc,canonicalize,factor-quantum-alloc,memtoreg),canonicalize,cse,add-wireset,func.func(assign-wire-indices),dep-analysis,func.func(regtomem),symbol-dce" library-mode: false - name: dep-analysis-qpp option-flags: [dep-analysis, qpp] config: nvqir-simulation-backend: qpp preprocessor-defines: ["-D CUDAQ_SIMULATION_SCALAR_FP64"] - target-pass-pipeline: "func.func(unwind-lowering),canonicalize,lambda-lifting,func.func(memtoreg{quantum=0}),canonicalize,apply-op-specialization,kernel-execution,aggressive-early-inlining,func.func(quake-add-metadata,const-prop-complex,lift-array-alloc),globalize-array-values,func.func(get-concrete-matrix),device-code-loader{use-quake=1},canonicalize,cse,func.func(add-dealloc,combine-quantum-alloc,canonicalize,factor-quantum-alloc,memtoreg),canonicalize,cse,add-wireset,func.func(assign-wire-indices),dep-analysis,func.func(regtomem),symbol-dce" + target-pass-pipeline: "func.func(unwind-lowering),canonicalize,lambda-lifting,func.func(memtoreg{quantum=0}),canonicalize,apply-op-specialization,kernel-execution,aggressive-early-inlining,func.func(quake-add-metadata,const-prop-complex,lift-array-alloc),globalize-array-values,get-concrete-matrix,device-code-loader{use-quake=1},canonicalize,cse,func.func(add-dealloc,combine-quantum-alloc,canonicalize,factor-quantum-alloc,memtoreg),canonicalize,cse,add-wireset,func.func(assign-wire-indices),dep-analysis,func.func(regtomem),symbol-dce" library-mode: false diff --git a/runtime/nvqir/cudensitymat/CuDensityMatExpectation.cpp b/runtime/nvqir/cudensitymat/CuDensityMatExpectation.cpp index a08c0b296c6..5a026113ad3 100644 --- a/runtime/nvqir/cudensitymat/CuDensityMatExpectation.cpp +++ b/runtime/nvqir/cudensitymat/CuDensityMatExpectation.cpp @@ -56,7 +56,7 @@ std::complex CuDensityMatExpectation::compute(cudensitymatState_t state, auto *expectationValue_d = cudaq::dynamics::createArrayGpu( std::vector>(1, {0.0, 0.0})); HANDLE_CUDM_ERROR(cudensitymatExpectationCompute( - m_handle, m_expectation, time, 0, nullptr, state, expectationValue_d, + m_handle, m_expectation, time, 1, 0, nullptr, state, expectationValue_d, m_workspace, 0x0)); std::complex result; HANDLE_CUDA_ERROR(cudaMemcpy(&result, expectationValue_d, diff --git a/runtime/nvqir/cudensitymat/CuDensityMatOpConverter.cpp b/runtime/nvqir/cudensitymat/CuDensityMatOpConverter.cpp index 7c7585d6980..22c0a80cfa7 100644 --- a/runtime/nvqir/cudensitymat/CuDensityMatOpConverter.cpp +++ b/runtime/nvqir/cudensitymat/CuDensityMatOpConverter.cpp @@ -155,7 +155,8 @@ cudaq::dynamics::CuDensityMatOpConverter::constructLiouvillian( const std::vector keys{ks.begin(), ks.end()}; for (auto &[coeff, term] : convertToCudensitymat(ham, parameters, modeExtents)) { - cudensitymatWrappedScalarCallback_t wrappedCallback = {nullptr, nullptr}; + cudensitymatWrappedScalarCallback_t wrappedCallback = + cudensitymatScalarCallbackNone; if (coeff.is_constant()) { const auto coeffVal = coeff.evaluate(); const auto leftCoeff = std::complex(0.0, -1.0) * coeffVal; @@ -189,8 +190,8 @@ cudaq::dynamics::CuDensityMatOpConverter::constructLiouvillian( for (auto &collapseOperator : collapseOperators) { for (auto &[coeff, term] : computeLindbladTerms(collapseOperator, modeExtents, parameters)) { - cudensitymatWrappedScalarCallback_t wrappedCallback = {nullptr, - nullptr}; + cudensitymatWrappedScalarCallback_t wrappedCallback = + cudensitymatScalarCallbackNone; if (coeff.is_constant()) { const auto coeffVal = coeff.evaluate(); HANDLE_CUDM_ERROR(cudensitymatOperatorAppendTerm( @@ -228,8 +229,8 @@ cudaq::dynamics::CuDensityMatOpConverter::createElementaryOperator( const std::vector &modeExtents) { auto subspaceExtents = getSubspaceExtents(modeExtents, elemOp.degrees()); std::unordered_map dimensions = convertDimensions(modeExtents); - cudensitymatWrappedTensorCallback_t wrappedTensorCallback = {nullptr, - nullptr}; + cudensitymatWrappedTensorCallback_t wrappedTensorCallback = + cudensitymatTensorCallbackNone; static const std::vector g_knownNonParametricOps = []() { std::vector opNames; @@ -356,7 +357,7 @@ cudaq::dynamics::CuDensityMatOpConverter::createProductOperatorTerm( HANDLE_CUDM_ERROR(cudensitymatOperatorTermAppendElementaryProduct( m_handle, term, static_cast(elemOps.size()), elemOps.data(), allDegrees.data(), allModeActionDuality.data(), - make_cuDoubleComplex(1.0, 0.0), {nullptr, nullptr})); + make_cuDoubleComplex(1.0, 0.0), cudensitymatScalarCallbackNone)); return term; } @@ -379,7 +380,8 @@ cudaq::dynamics::CuDensityMatOpConverter::convertToCudensitymatOperator( const std::vector keys{ks.begin(), ks.end()}; for (auto &[coeff, term] : convertToCudensitymat(op, parameters, modeExtents)) { - cudensitymatWrappedScalarCallback_t wrappedCallback = {nullptr, nullptr}; + cudensitymatWrappedScalarCallback_t wrappedCallback = + cudensitymatScalarCallbackNone; if (coeff.is_constant()) { const auto coeffVal = coeff.evaluate(); @@ -548,11 +550,11 @@ cudaq::dynamics::CuDensityMatOpConverter::wrapScalarCallback( m_scalarCallbacks.push_back(ScalarCallBackContext(scalarOp, paramNames)); ScalarCallBackContext *storedCallbackContext = &m_scalarCallbacks.back(); using WrapperFuncType = - int32_t (*)(cudensitymatScalarCallback_t, double, int32_t, const double[], - cudaDataType_t, void *); + int32_t (*)(cudensitymatScalarCallback_t, double, int64_t, int32_t, + const double[], cudaDataType_t, void *); auto wrapper = [](cudensitymatScalarCallback_t callback, double time, - int32_t numParams, const double params[], + int64_t batchSize, int32_t numParams, const double params[], cudaDataType_t dataType, void *scalarStorage) -> int32_t { try { ScalarCallBackContext *context = @@ -588,6 +590,7 @@ cudaq::dynamics::CuDensityMatOpConverter::wrapScalarCallback( cudensitymatWrappedScalarCallback_t wrappedCallback; wrappedCallback.callback = reinterpret_cast(storedCallbackContext); + wrappedCallback.device = CUDENSITYMAT_CALLBACK_DEVICE_CPU; wrappedCallback.wrapper = reinterpret_cast(static_cast(wrapper)); return wrappedCallback; @@ -601,16 +604,16 @@ cudaq::dynamics::CuDensityMatOpConverter::wrapTensorCallback( TensorCallBackContext *storedCallbackContext = &m_tensorCallbacks.back(); using WrapperFuncType = int32_t (*)( cudensitymatTensorCallback_t, cudensitymatElementaryOperatorSparsity_t, - int32_t, const int64_t[], const int32_t[], double, int32_t, + int32_t, const int64_t[], const int32_t[], double, int64_t, int32_t, const double[], cudaDataType_t, void *, cudaStream_t); auto wrapper = [](cudensitymatTensorCallback_t callback, cudensitymatElementaryOperatorSparsity_t sparsity, int32_t num_modes, const int64_t modeExtents[], const int32_t diagonal_offsets[], double time, - int32_t num_params, const double params[], - cudaDataType_t data_type, void *tensor_storage, - cudaStream_t stream) -> int32_t { + int64_t batchSize, int32_t num_params, + const double params[], cudaDataType_t data_type, + void *tensor_storage, cudaStream_t stream) -> int32_t { try { auto *context = reinterpret_cast(callback); matrix_handler &storedOp = context->tensorOp; @@ -686,6 +689,7 @@ cudaq::dynamics::CuDensityMatOpConverter::wrapTensorCallback( cudensitymatWrappedTensorCallback_t wrappedCallback; wrappedCallback.callback = reinterpret_cast(storedCallbackContext); + wrappedCallback.device = CUDENSITYMAT_CALLBACK_DEVICE_CPU; wrappedCallback.wrapper = reinterpret_cast(static_cast(wrapper)); diff --git a/runtime/nvqir/cudensitymat/CuDensityMatTimeStepper.cpp b/runtime/nvqir/cudensitymat/CuDensityMatTimeStepper.cpp index 8e7e36a8c40..82b2ad65508 100644 --- a/runtime/nvqir/cudensitymat/CuDensityMatTimeStepper.cpp +++ b/runtime/nvqir/cudensitymat/CuDensityMatTimeStepper.cpp @@ -9,6 +9,7 @@ #include "CuDensityMatTimeStepper.h" #include "CuDensityMatContext.h" #include "CuDensityMatErrorHandling.h" +#include "CuDensityMatUtils.h" namespace cudaq { CuDensityMatTimeStepper::CuDensityMatTimeStepper( cudensitymatHandle_t handle, cudensitymatOperator_t liouvillian) @@ -67,18 +68,20 @@ state CuDensityMatTimeStepper::compute( // Apply the operator action std::map> sortedParameters( parameters.begin(), parameters.end()); - std::vector paramValues; + std::vector> paramValues; for (const auto &[k, v] : sortedParameters) { - paramValues.emplace_back(v.real()); - paramValues.emplace_back(v.imag()); + paramValues.emplace_back(v); } + double *param_d = + static_cast(cudaq::dynamics::createArrayGpu(paramValues)); HANDLE_CUDA_ERROR(cudaDeviceSynchronize()); HANDLE_CUDM_ERROR(cudensitymatOperatorComputeAction( - m_handle, m_liouvillian, t, paramValues.size(), paramValues.data(), + m_handle, m_liouvillian, t, 1, paramValues.size() * 2, param_d, state.get_impl(), next_state.get_impl(), workspace, 0x0)); HANDLE_CUDA_ERROR(cudaDeviceSynchronize()); // Cleanup + cudaq::dynamics::destroyArrayGpu(param_d); HANDLE_CUDM_ERROR(cudensitymatDestroyWorkspace(workspace)); return cudaq::state( diff --git a/runtime/nvqir/cutensornet/CMakeLists.txt b/runtime/nvqir/cutensornet/CMakeLists.txt index 6490e16d36e..789bc2b795e 100644 --- a/runtime/nvqir/cutensornet/CMakeLists.txt +++ b/runtime/nvqir/cutensornet/CMakeLists.txt @@ -60,8 +60,8 @@ set(CUTENSORNET_PATCH ${CMAKE_MATCH_1}) set(CUTENSORNET_VERSION ${CUTENSORNET_MAJOR}.${CUTENSORNET_MINOR}.${CUTENSORNET_PATCH}) message(STATUS "Found cutensornet version: ${CUTENSORNET_VERSION}") -# We need cutensornet v2.6.0+ (cutensornetStateApplyUnitaryChannel) -if (${CUTENSORNET_VERSION} VERSION_GREATER_EQUAL "2.6") +# We need cutensornet v2.7.0+ (cutensornetStateApplyGeneralChannel) +if (${CUTENSORNET_VERSION} VERSION_GREATER_EQUAL "2.7") set (BASE_TENSOR_BACKEND_SRS simulator_cutensornet.cpp tensornet_spin_op.cpp diff --git a/runtime/nvqir/cutensornet/simulator_cutensornet.cpp b/runtime/nvqir/cutensornet/simulator_cutensornet.cpp index aec841531a2..4a0c8b3bf12 100644 --- a/runtime/nvqir/cutensornet/simulator_cutensornet.cpp +++ b/runtime/nvqir/cutensornet/simulator_cutensornet.cpp @@ -166,7 +166,18 @@ void SimulatorTensorNetBase::applyKrausChannel( m_state->applyUnitaryChannel(qubits, channelMats, krausChannel.probabilities); } else { - throw std::runtime_error("Non-unitary noise channels are not supported."); + if (!canHandleGeneralNoiseChannel()) + throw std::runtime_error( + "General noise channels are not supported on simulator " + name()); + + std::vector channelMats; + for (const auto &op : krausChannel.get_ops()) { + const std::string cacheKey = fmt::format( + "GeneralKrausMat_{}", std::to_string(vecComplexHash(op.data))); + channelMats.emplace_back( + getOrCacheMat(cacheKey, op.data, m_gateDeviceMemCache)); + } + m_state->applyGeneralChannel(qubits, channelMats); } } @@ -183,14 +194,14 @@ bool SimulatorTensorNetBase::isValidNoiseChannel( case cudaq::noise_model_type::pauli2: case cudaq::noise_model_type::depolarization1: case cudaq::noise_model_type::depolarization2: + case cudaq::noise_model_type::phase_damping: case cudaq::noise_model_type::unknown: // may be unitary, so return true return true; - // These are explicitly non-unitary and unsupported + // These are explicitly non-unitary case cudaq::noise_model_type::amplitude_damping_channel: case cudaq::noise_model_type::amplitude_damping: - case cudaq::noise_model_type::phase_damping: default: - return false; + return canHandleGeneralNoiseChannel(); } } diff --git a/runtime/nvqir/cutensornet/simulator_cutensornet.h b/runtime/nvqir/cutensornet/simulator_cutensornet.h index 0f7568c4311..e13296fb005 100644 --- a/runtime/nvqir/cutensornet/simulator_cutensornet.h +++ b/runtime/nvqir/cutensornet/simulator_cutensornet.h @@ -104,6 +104,10 @@ class SimulatorTensorNetBase : public nvqir::CircuitSimulatorBase { /// intermediate tensors) virtual bool requireCacheWorkspace() const = 0; + /// @brief Return true if this simulator handle general noise channel + /// (non-unitary). + virtual bool canHandleGeneralNoiseChannel() const = 0; + private: // Helper to apply a Kraus channel void applyKrausChannel(const std::vector &qubits, diff --git a/runtime/nvqir/cutensornet/simulator_mps_register.cpp b/runtime/nvqir/cutensornet/simulator_mps_register.cpp index e9246a49949..53d03d55da9 100644 --- a/runtime/nvqir/cutensornet/simulator_mps_register.cpp +++ b/runtime/nvqir/cutensornet/simulator_mps_register.cpp @@ -180,6 +180,11 @@ class SimulatorMPS : public SimulatorTensorNetBase { for (auto &tensor : m_mpsTensors_d) { HANDLE_CUDA_ERROR(cudaFree(tensor.deviceData)); } + + if (m_state->hasGeneralChannelApplied() && m_state->getNumQubits() <= 1) + throw std::runtime_error( + "MPS noisy simulation currently does not support the case where " + "number of qubit is equal to 1"); m_mpsTensors_d.clear(); m_mpsTensors_d = m_state->setupMPSFactorize(m_settings.maxBond, m_settings.absCutoff, @@ -411,7 +416,7 @@ class SimulatorMPS : public SimulatorTensorNetBase { } bool requireCacheWorkspace() const override { return false; } - + bool canHandleGeneralNoiseChannel() const override { return true; } virtual ~SimulatorMPS() noexcept { for (auto &tensor : m_mpsTensors_d) { HANDLE_CUDA_ERROR(cudaFree(tensor.deviceData)); diff --git a/runtime/nvqir/cutensornet/simulator_tensornet_register.cpp b/runtime/nvqir/cutensornet/simulator_tensornet_register.cpp index 649a8775b2e..31ec5cc1019 100644 --- a/runtime/nvqir/cutensornet/simulator_tensornet_register.cpp +++ b/runtime/nvqir/cutensornet/simulator_tensornet_register.cpp @@ -135,6 +135,11 @@ class SimulatorTensorNet : public SimulatorTensorNetBase { } } bool requireCacheWorkspace() const override { return true; } + bool canHandleGeneralNoiseChannel() const override { + // Full tensornet simulator doesn't support general noise channels (only + // unitary mixture channels) + return false; + } private: friend nvqir::CircuitSimulator * ::getCircuitSimulator_tensornet(); diff --git a/runtime/nvqir/cutensornet/tensornet_state.cpp b/runtime/nvqir/cutensornet/tensornet_state.cpp index 1e6f83f8199..895df764ab6 100644 --- a/runtime/nvqir/cutensornet/tensornet_state.cpp +++ b/runtime/nvqir/cutensornet/tensornet_state.cpp @@ -110,6 +110,19 @@ void TensorNetState::applyUnitaryChannel( m_hasNoiseChannel = true; } +void TensorNetState::applyGeneralChannel(const std::vector &qubits, + const std::vector &krausOps) { + LOG_API_TIME(); + HANDLE_CUTN_ERROR(cutensornetStateApplyGeneralChannel( + m_cutnHandle, m_quantumState, /*numStateModes=*/qubits.size(), + /*stateModes=*/qubits.data(), + /*numTensors=*/krausOps.size(), + /*tensorData=*/const_cast(krausOps.data()), + /*tensorModeStrides=*/nullptr, &m_tensorId)); + m_tensorOps.emplace_back(AppliedTensorOp{qubits, krausOps, {}}); + m_hasNoiseChannel = true; +} + void TensorNetState::applyQubitProjector(void *proj_d, const std::vector &qubitIdx) { LOG_API_TIME(); @@ -155,16 +168,28 @@ void TensorNetState::addQubits(std::size_t numQubits) { /*adjoint*/ static_cast(op.isAdjoint), /*unitary*/ static_cast(op.isUnitary), &m_tensorId)); } - } else if (op.unitaryChannel.has_value()) { - HANDLE_CUTN_ERROR(cutensornetStateApplyUnitaryChannel( - m_cutnHandle, m_quantumState, - /*numStateModes=*/op.targetQubitIds.size(), - /*stateModes=*/op.targetQubitIds.data(), - /*numTensors=*/op.unitaryChannel->tensorData.size(), - /*tensorData=*/op.unitaryChannel->tensorData.data(), - /*tensorModeStrides=*/nullptr, - /*probabilities=*/op.unitaryChannel->probabilities.data(), - &m_tensorId)); + } else if (op.noiseChannel.has_value()) { + const bool isGeneralChannel = op.noiseChannel->tensorData.size() != + op.noiseChannel->probabilities.size(); + if (isGeneralChannel) { + HANDLE_CUTN_ERROR(cutensornetStateApplyGeneralChannel( + m_cutnHandle, m_quantumState, + /*numStateModes=*/op.targetQubitIds.size(), + /*stateModes=*/op.targetQubitIds.data(), + /*numTensors=*/op.noiseChannel->tensorData.size(), + /*tensorData=*/op.noiseChannel->tensorData.data(), + /*tensorModeStrides=*/nullptr, &m_tensorId)); + } else { + HANDLE_CUTN_ERROR(cutensornetStateApplyUnitaryChannel( + m_cutnHandle, m_quantumState, + /*numStateModes=*/op.targetQubitIds.size(), + /*stateModes=*/op.targetQubitIds.data(), + /*numTensors=*/op.noiseChannel->tensorData.size(), + /*tensorData=*/op.noiseChannel->tensorData.data(), + /*tensorModeStrides=*/nullptr, + /*probabilities=*/op.noiseChannel->probabilities.data(), + &m_tensorId)); + } } else { throw std::runtime_error("Invalid AppliedTensorOp encountered."); } @@ -942,6 +967,14 @@ TensorNetState::reverseQubitOrder(std::span> stateVec) { return ket; } +bool TensorNetState::hasGeneralChannelApplied() const { + for (const auto &op : m_tensorOps) + if (op.noiseChannel.has_value() && op.noiseChannel->probabilities.empty()) + return true; + + return false; +} + void TensorNetState::applyCachedOps() { int64_t tensorId = 0; for (auto &op : m_tensorOps) @@ -965,16 +998,28 @@ void TensorNetState::applyCachedOps() { /*adjoint*/ static_cast(op.isAdjoint), /*unitary*/ static_cast(op.isUnitary), &m_tensorId)); } - } else if (op.unitaryChannel.has_value()) { - HANDLE_CUTN_ERROR(cutensornetStateApplyUnitaryChannel( - m_cutnHandle, m_quantumState, - /*numStateModes=*/op.targetQubitIds.size(), - /*stateModes=*/op.targetQubitIds.data(), - /*numTensors=*/op.unitaryChannel->tensorData.size(), - /*tensorData=*/op.unitaryChannel->tensorData.data(), - /*tensorModeStrides=*/nullptr, - /*probabilities=*/op.unitaryChannel->probabilities.data(), - &m_tensorId)); + } else if (op.noiseChannel.has_value()) { + const bool isGeneralChannel = op.noiseChannel->tensorData.size() != + op.noiseChannel->probabilities.size(); + if (isGeneralChannel) { + HANDLE_CUTN_ERROR(cutensornetStateApplyGeneralChannel( + m_cutnHandle, m_quantumState, + /*numStateModes=*/op.targetQubitIds.size(), + /*stateModes=*/op.targetQubitIds.data(), + /*numTensors=*/op.noiseChannel->tensorData.size(), + /*tensorData=*/op.noiseChannel->tensorData.data(), + /*tensorModeStrides=*/nullptr, &m_tensorId)); + } else { + HANDLE_CUTN_ERROR(cutensornetStateApplyUnitaryChannel( + m_cutnHandle, m_quantumState, + /*numStateModes=*/op.targetQubitIds.size(), + /*stateModes=*/op.targetQubitIds.data(), + /*numTensors=*/op.noiseChannel->tensorData.size(), + /*tensorData=*/op.noiseChannel->tensorData.data(), + /*tensorModeStrides=*/nullptr, + /*probabilities=*/op.noiseChannel->probabilities.data(), + &m_tensorId)); + } } else { throw std::runtime_error("Invalid AppliedTensorOp encountered."); } diff --git a/runtime/nvqir/cutensornet/tensornet_state.h b/runtime/nvqir/cutensornet/tensornet_state.h index 5400b0565f8..443225c4678 100644 --- a/runtime/nvqir/cutensornet/tensornet_state.h +++ b/runtime/nvqir/cutensornet/tensornet_state.h @@ -29,15 +29,20 @@ struct MPSTensor { std::vector extents; }; -struct UnitaryChannel { +// Struct captures noise channel data. +struct NoiseChannelData { + // Device memory tensors represent general Kraus ops or unitary matrices. std::vector tensorData; + // If tensorData represents unitary matrices, a list of probabilities (same + // length) can be supplied in this field. If empty, the tensors are treated as + // general Kraus ops. std::vector probabilities; }; /// Track gate tensors that were appended to the tensor network. struct AppliedTensorOp { void *deviceData = nullptr; - std::optional unitaryChannel; + std::optional noiseChannel; std::vector targetQubitIds; std::vector controlQubitIds; bool isAdjoint; @@ -53,7 +58,7 @@ struct AppliedTensorOp { const std::vector &krausOps, const std::vector &probabilities) : targetQubitIds(qubits), - unitaryChannel(UnitaryChannel(krausOps, probabilities)) {} + noiseChannel(NoiseChannelData(krausOps, probabilities)) {} }; /// @brief Wrapper of cutensornetState_t to provide convenient API's for CUDA-Q @@ -133,7 +138,9 @@ class TensorNetState { void applyUnitaryChannel(const std::vector &qubits, const std::vector &krausOps, const std::vector &probabilities); - + /// @brief Apply a general noise channel + void applyGeneralChannel(const std::vector &qubits, + const std::vector &krausOps); /// @brief Apply a projector matrix (non-unitary) /// @param proj_d Projector matrix (expected a 2x2 matrix in column major) /// @param qubitIdx Qubit operand @@ -209,6 +216,9 @@ class TensorNetState { /// @brief Set the state to a zero state void setZeroState(); + /// @brief Returns true if the state has at least one general channel applied. + bool hasGeneralChannelApplied() const; + /// @brief Destructor ~TensorNetState(); diff --git a/runtime/nvqir/cutensornet/tn_simulation_state.cpp b/runtime/nvqir/cutensornet/tn_simulation_state.cpp index baf18a163cf..8320f8b5cc3 100644 --- a/runtime/nvqir/cutensornet/tn_simulation_state.cpp +++ b/runtime/nvqir/cutensornet/tn_simulation_state.cpp @@ -95,16 +95,28 @@ TensorNetSimulationState::overlap(const cudaq::SimulationState &other) { /*adjoint*/ static_cast(op.isAdjoint), /*unitary*/ static_cast(op.isUnitary), &tensorId)); } - } else if (op.unitaryChannel.has_value()) { - HANDLE_CUTN_ERROR(cutensornetStateApplyUnitaryChannel( - cutnHandle, tempQuantumState, - /*numStateModes=*/op.targetQubitIds.size(), - /*stateModes=*/op.targetQubitIds.data(), - /*numTensors=*/op.unitaryChannel->tensorData.size(), - /*tensorData=*/op.unitaryChannel->tensorData.data(), - /*tensorModeStrides=*/nullptr, - /*probabilities=*/op.unitaryChannel->probabilities.data(), - &tensorId)); + } else if (op.noiseChannel.has_value()) { + const bool isGeneralChannel = op.noiseChannel->tensorData.size() != + op.noiseChannel->probabilities.size(); + if (isGeneralChannel) { + HANDLE_CUTN_ERROR(cutensornetStateApplyGeneralChannel( + cutnHandle, tempQuantumState, + /*numStateModes=*/op.targetQubitIds.size(), + /*stateModes=*/op.targetQubitIds.data(), + /*numTensors=*/op.noiseChannel->tensorData.size(), + /*tensorData=*/op.noiseChannel->tensorData.data(), + /*tensorModeStrides=*/nullptr, &tensorId)); + } else { + HANDLE_CUTN_ERROR(cutensornetStateApplyUnitaryChannel( + cutnHandle, tempQuantumState, + /*numStateModes=*/op.targetQubitIds.size(), + /*stateModes=*/op.targetQubitIds.data(), + /*numTensors=*/op.noiseChannel->tensorData.size(), + /*tensorData=*/op.noiseChannel->tensorData.data(), + /*tensorModeStrides=*/nullptr, + /*probabilities=*/op.noiseChannel->probabilities.data(), + &tensorId)); + } } else { throw std::runtime_error("Invalid AppliedTensorOp encountered."); } diff --git a/scripts/build_docs.sh b/scripts/build_docs.sh index d256cd0e1f2..9445e9cd438 100644 --- a/scripts/build_docs.sh +++ b/scripts/build_docs.sh @@ -145,9 +145,10 @@ fi echo "Creating README.md for cudaq package" package_name=cudaq cuda_version_requirement="11.x (where x >= 8) or 12.x" -cuda_version_conda=11.8.0 # only used as example in the install script +cuda_version_conda=12.4.0 # only used as example in the install script +deprecation_notice="**Note**: Support for CUDA 11 will be removed in future releases. Please update to CUDA 12." cat "$repo_root/python/README.md.in" > "$repo_root/python/README.md" -for variable in package_name cuda_version_requirement cuda_version_conda; do +for variable in package_name cuda_version_requirement cuda_version_conda deprecation_notice; do sed -i "s/.{{[ ]*$variable[ ]*}}/${!variable}/g" "$repo_root/python/README.md" done if [ -n "$(cat "$repo_root/python/README.md" | grep -e '.{{.*}}')" ]; then diff --git a/scripts/configure_build.sh b/scripts/configure_build.sh index 1e439ee8544..470f5378c7a 100644 --- a/scripts/configure_build.sh +++ b/scripts/configure_build.sh @@ -57,7 +57,8 @@ if [ "$1" == "install-cudart" ]; then cuda-cudart-${version_suffix} \ cuda-nvrtc-${version_suffix} \ libcusolver-${version_suffix} \ - libcublas-${version_suffix} + libcublas-${version_suffix} \ + libcurand-${version_suffix} if [ $(echo ${CUDA_VERSION} | cut -d . -f1) -gt 11 ]; then dnf install -y --nobest --setopt=install_weak_deps=False \ libnvjitlink-${version_suffix} @@ -70,7 +71,7 @@ if [ "$1" == "install-cuquantum" ]; then CUDA_ARCH_FOLDER=$([ "$(uname -m)" == "aarch64" ] && echo sbsa || echo x86_64) # [>cuQuantumInstall] - CUQUANTUM_VERSION=24.11.0.21 + CUQUANTUM_VERSION=25.03.0.11 CUQUANTUM_DOWNLOAD_URL=https://developer.download.nvidia.com/compute/cuquantum/redist/cuquantum cuquantum_archive=cuquantum-linux-${CUDA_ARCH_FOLDER}-${CUQUANTUM_VERSION}_cuda$(echo ${CUDA_VERSION} | cut -d . -f1)-archive.tar.xz @@ -86,7 +87,7 @@ if [ "$1" == "install-cutensor" ]; then CUDA_ARCH_FOLDER=$([ "$(uname -m)" == "aarch64" ] && echo sbsa || echo x86_64) # [>cuTensorInstall] - CUTENSOR_VERSION=2.0.2.5 + CUTENSOR_VERSION=2.2.0.0 CUTENSOR_DOWNLOAD_URL=https://developer.download.nvidia.com/compute/cutensor/redist/libcutensor cutensor_archive=libcutensor-linux-${CUDA_ARCH_FOLDER}-${CUTENSOR_VERSION}-archive.tar.xz diff --git a/scripts/migrate_assets.sh b/scripts/migrate_assets.sh index 0953edaf9e5..2ecd47a8bb3 100644 --- a/scripts/migrate_assets.sh +++ b/scripts/migrate_assets.sh @@ -214,7 +214,7 @@ if $install; then chmod a+rX "$plugin_path/libcudaq_distributed_interface_mpi.so" else echo -e "\e[01;31mWarning: Failed to build MPI plugin.\e[0m" >&2 - echo -e "Please make sure the necessary libraries and header files are discoverable and then build the MPI plugin by running the script `${plugin_path}/activate_custom_mpi.sh`." + echo -e "\e[01;31mPlease make sure the necessary libraries and header files are discoverable and then build the MPI plugin by running the script `${plugin_path}/activate_custom_mpi.sh`.\e[0m" >&2 fi fi @@ -234,4 +234,6 @@ if [ ! ${#remaining_files[@]} -eq 0 ]; then rel_paths=(${remaining_files[@]##$assets/}) components=(`echo "${rel_paths[@]%%/*}" | tr ' ' '\n' | uniq`) echo -e "\e[01;31mWarning: Some files in $assets have not been migrated since they already exit in their intended destination. To avoid compatibility issues, please make sure the following packages are not already installed on your system: ${components[@]}\e[0m" >&2 +elif $install; then + echo "Successfully installed CUDA-Q." fi diff --git a/scripts/validate_container.sh b/scripts/validate_container.sh index 9b41f707518..51208987dae 100644 --- a/scripts/validate_container.sh +++ b/scripts/validate_container.sh @@ -160,7 +160,7 @@ do do # Skipping dynamics examples if target is not dynamics and ex is dynamics # or gpu is unavailable - if [ "$t" != "dynamics" ] && [[ "$ex" == *"dynamics"* ]]; then + if { [ "$t" != "dynamics" ] && [[ "$ex" == *"dynamics"* ]]; } || { [ "$t" == "dynamics" ] && [[ "$ex" != *"dynamics"* ]]; }; then let "skipped+=1" echo "Skipping $t target for $ex."; echo ":white_flag: $filename: Not intended for this target. Test skipped." >> "${tmpFile}_$(echo $t | tr - _)" @@ -295,7 +295,6 @@ dynamics_backend_skipped_examples=(\ # purposes of the container validation. The divisive_clustering_src Python # files are used by the Divisive_clustering.ipynb notebook, so they are tested # elsewhere and should be excluded from this test. -# Same with afqmc. # Note: piping the `find` results through `sort` guarantees repeatable ordering. for ex in `find examples/ targets/ -name '*.py' | sort`; do diff --git a/tools/nvqpp/nvq++.in b/tools/nvqpp/nvq++.in index ae6a6ab265d..edce8967c3f 100644 --- a/tools/nvqpp/nvq++.in +++ b/tools/nvqpp/nvq++.in @@ -715,7 +715,7 @@ if ${ENABLE_AGGRESSIVE_EARLY_INLINE}; then fi if ${ENABLE_DEVICE_CODE_LOADERS}; then RUN_OPT=true - OPT_PASSES=$(add_pass_to_pipeline "${OPT_PASSES}" "func.func(quake-add-metadata,const-prop-complex,lift-array-alloc),globalize-array-values,func.func(get-concrete-matrix),device-code-loader") + OPT_PASSES=$(add_pass_to_pipeline "${OPT_PASSES}" "func.func(quake-add-metadata,const-prop-complex,lift-array-alloc),globalize-array-values,get-concrete-matrix,device-code-loader") fi if ${ENABLE_LOWER_TO_CFG}; then RUN_OPT=true diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 728f84e220a..510597e58fa 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -273,6 +273,7 @@ set(CUDAQ_OPERATOR_TEST_SOURCES dynamics/product_operator.cpp dynamics/operator_sum.cpp dynamics/rydberg_hamiltonian.cpp + dynamics/test_Helpers.cpp ) add_executable(test_operators main.cpp ${CUDAQ_OPERATOR_TEST_SOURCES}) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT APPLE) @@ -301,7 +302,6 @@ if (CUDA_FOUND) # Create an executable for dynamics UnitTests set(CUDAQ_DYNAMICS_TEST_SOURCES dynamics/test_RungeKuttaIntegrator.cpp - dynamics/test_Helpers.cpp dynamics/test_CuDensityMatState.cpp dynamics/test_CuDensityMatTimeStepper.cpp dynamics/test_CuDensityMatExpectation.cpp diff --git a/unittests/dynamics/test_Helpers.cpp b/unittests/dynamics/test_Helpers.cpp index 0a5d1a1174b..cf200af21b5 100644 --- a/unittests/dynamics/test_Helpers.cpp +++ b/unittests/dynamics/test_Helpers.cpp @@ -17,7 +17,7 @@ TEST(OperatorHelpersTest, GenerateAllStates_TwoQubits) { cudaq::dimension_map dimensions = {{0, 2}, {1, 2}}; auto states = generate_all_states(degrees, dimensions); - std::vector expected_states = {"00", "01", "10", "11"}; + std::vector expected_states = {"00", "10", "01", "11"}; EXPECT_EQ(states, expected_states); } @@ -27,8 +27,8 @@ TEST(OperatorHelpersTest, GenerateAllStates_ThreeQubits) { cudaq::dimension_map dimensions = {{0, 2}, {1, 2}, {2, 2}}; auto states = generate_all_states(degrees, dimensions); - std::vector expected_states = {"000", "001", "010", "011", - "100", "101", "110", "111"}; + std::vector expected_states = {"000", "100", "010", "110", + "001", "101", "011", "111"}; EXPECT_EQ(states, expected_states); } diff --git a/unittests/integration/noise_tester.cpp b/unittests/integration/noise_tester.cpp index 699c3b66974..2e86e74abcd 100644 --- a/unittests/integration/noise_tester.cpp +++ b/unittests/integration/noise_tester.cpp @@ -20,6 +20,13 @@ struct xOp { } }; +struct xOp2 { + void operator()() __qpu__ { + cudaq::qvector q(2); + x(q); + } +}; + struct bell { void operator()() __qpu__ { cudaq::qubit q, r; @@ -253,6 +260,27 @@ CUDAQ_TEST(NoiseTest, checkAmplitudeDamping) { } #endif + +#if defined(CUDAQ_BACKEND_DM) || defined(CUDAQ_BACKEND_TENSORNET_MPS) +CUDAQ_TEST(NoiseTest, checkAmplitudeDamping2) { + cudaq::set_random_seed(13); + cudaq::kraus_channel amplitudeDamping{{1., 0., 0., .8660254037844386}, + {0., 0.5, 0.0, 0.}}; + cudaq::noise_model noise; + noise.add_all_qubit_channel(amplitudeDamping); + cudaq::set_noise(noise); + + auto counts = cudaq::sample(xOp2{}); + counts.dump(); + + EXPECT_NEAR(counts.probability("00"), 0.0625, .1); + EXPECT_NEAR(counts.probability("10"), 0.1875, .1); + EXPECT_NEAR(counts.probability("01"), 0.1875, .1); + EXPECT_NEAR(counts.probability("11"), 0.5625, .1); + cudaq::unset_noise(); // clear for subsequent tests +} +#endif + #if defined(CUDAQ_BACKEND_DM) || defined(CUDAQ_BACKEND_TENSORNET) // Stim does not support arbitrary cudaq::kraus_op specification.