From f433d74e8e23a65fe5199062b7fdd7527c218740 Mon Sep 17 00:00:00 2001 From: Bettina Heim Date: Tue, 3 Dec 2024 13:18:19 -0800 Subject: [PATCH 01/44] Build AWS SDK as part of the prerequisites (#2412) Signed-off-by: Bettina Heim --- .gitmodules | 3 - CMakeLists.txt | 67 +++++-------------- docker/build/devdeps.Dockerfile | 3 + docker/build/devdeps.manylinux.Dockerfile | 1 + pyproject.toml | 2 +- runtime/cudaq/platform/CMakeLists.txt | 4 +- .../platform/default/rest/CMakeLists.txt | 8 ++- .../default/rest/helpers/CMakeLists.txt | 4 +- scripts/configure_build.sh | 2 + scripts/install_prerequisites.sh | 36 ++++++++++ targettests/execution/angled_gate.cpp | 2 +- targettests/execution/bug_qubit.cpp | 2 +- targettests/execution/callable_kernel_arg.cpp | 2 +- targettests/execution/cudaq_observe.cpp | 2 +- .../execution/custom_operation_adj.cpp | 2 +- .../execution/custom_operation_basic.cpp | 2 +- targettests/execution/graph_coloring-1.cpp | 2 +- targettests/execution/graph_coloring.cpp | 2 +- targettests/execution/if_jit.cpp | 2 +- targettests/execution/int8_t.cpp | 2 +- targettests/execution/int8_t_free_func.cpp | 2 +- targettests/execution/load_value.cpp | 2 +- targettests/execution/state_preparation.cpp | 2 +- .../state_preparation_vector_sizes.cpp | 2 +- targettests/execution/swap_gate.cpp | 2 +- targettests/execution/variable_size_qreg.cpp | 2 +- targettests/lit.site.cfg.py.in | 7 ++ tpls/aws-sdk-cpp | 1 - 28 files changed, 96 insertions(+), 74 deletions(-) delete mode 160000 tpls/aws-sdk-cpp diff --git a/.gitmodules b/.gitmodules index 1eb1006f28a..622993890c6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -48,6 +48,3 @@ [submodule "tpls/Stim"] path = tpls/Stim url = https://github.com/quantumlib/Stim -[submodule "tpls/aws-sdk-cpp"] - path = tpls/aws-sdk-cpp - url = https://github.com/aws/aws-sdk-cpp.git diff --git a/CMakeLists.txt b/CMakeLists.txt index a8e394c142b..496447a8b6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,11 @@ if (CUDAQ_ENABLE_REST AND NOT DEFINED CUDAQ_ENABLE_REMOTE_SIM) endif() endif() +# Enable AWS backends by default. +if (NOT DEFINED CUDAQ_ENABLE_BRAKET_BACKEND) + set(CUDAQ_ENABLE_BRAKET_BACKEND ON CACHE BOOL "Enable building AWS backends.") +endif() + # Generate a CompilationDatabase (compile_commands.json file) for our build, # for use by clang_complete, YouCompleteMe, etc. set(CMAKE_EXPORT_COMPILE_COMMANDS 1) @@ -136,6 +141,10 @@ option(CUDAQ_REQUIRE_OPENMP "Fail the build if OpenMP is not found." OFF) if(NOT LLVM_DIR AND EXISTS "$ENV{LLVM_INSTALL_PREFIX}/lib/cmake/llvm") SET(LLVM_DIR "$ENV{LLVM_INSTALL_PREFIX}/lib/cmake/llvm") endif() +if(NOT BLAS_LIBRARIES AND EXISTS "$ENV{BLAS_INSTALL_PREFIX}/libblas.a") + # CACHE INTERNAL is needed due to how FindBLAS.cmake works... + SET(BLAS_LIBRARIES "$ENV{BLAS_INSTALL_PREFIX}/libblas.a" CACHE INTERNAL "") +endif() if(NOT CUSTATEVEC_ROOT) SET(CUSTATEVEC_ROOT "$ENV{CUQUANTUM_INSTALL_PREFIX}") endif() @@ -151,9 +160,13 @@ endif() if(NOT OPENSSL_ROOT_DIR) SET(OPENSSL_ROOT_DIR "$ENV{OPENSSL_INSTALL_PREFIX}") endif() -if(NOT BLAS_LIBRARIES AND EXISTS "$ENV{BLAS_INSTALL_PREFIX}/libblas.a") - # CACHE INTERNAL is needed due to how FindBLAS.cmake works... - SET(BLAS_LIBRARIES "$ENV{BLAS_INSTALL_PREFIX}/libblas.a" CACHE INTERNAL "") +if (NOT crypto_LIBRARY AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND EXISTS "$ENV{OPENSSL_INSTALL_PREFIX}/lib64/libcrypto.a") + SET(crypto_LIBRARY "$ENV{OPENSSL_INSTALL_PREFIX}/lib64/libcrypto.a" CACHE INTERNAL "") +elseif(NOT crypto_LIBRARY AND NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND EXISTS "$ENV{OPENSSL_INSTALL_PREFIX}/lib/libcrypto.a") + SET(crypto_LIBRARY "$ENV{OPENSSL_INSTALL_PREFIX}/lib/libcrypto.a" CACHE INTERNAL "") +endif() +if (NOT crypto_INCLUDE_DIR AND EXISTS "$ENV{OPENSSL_INSTALL_PREFIX}/include") + SET(crypto_INCLUDE_DIR "$ENV{OPENSSL_INSTALL_PREFIX}/include" CACHE INTERNAL "") endif() if(NOT CURL_LIBRARY AND EXISTS "$ENV{CURL_INSTALL_PREFIX}/lib/libcurl.a") SET(CURL_LIBRARY "$ENV{CURL_INSTALL_PREFIX}/lib/libcurl.a") @@ -162,13 +175,8 @@ if(NOT CURL_LIBRARY AND EXISTS "$ENV{CURL_INSTALL_PREFIX}/lib/libcurl.a") SET(CMAKE_USE_SYSTEM_CURL TRUE) SET(CURL_NO_CURL_CMAKE ON) endif() -if (NOT crypto_LIBRARY AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND EXISTS "$ENV{OPENSSL_INSTALL_PREFIX}/lib64/libcrypto.a") - SET(crypto_LIBRARY "$ENV{OPENSSL_INSTALL_PREFIX}/lib64/libcrypto.a" CACHE INTERNAL "") -elseif(NOT crypto_LIBRARY AND NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND EXISTS "$ENV{OPENSSL_INSTALL_PREFIX}/lib/libcrypto.a") - SET(crypto_LIBRARY "$ENV{OPENSSL_INSTALL_PREFIX}/lib/libcrypto.a" CACHE INTERNAL "") -endif() -if (NOT crypto_INCLUDE_DIR AND EXISTS "$ENV{OPENSSL_INSTALL_PREFIX}/include") - SET(crypto_INCLUDE_DIR "$ENV{OPENSSL_INSTALL_PREFIX}/include" CACHE INTERNAL "") +if(NOT AWSSDK_ROOT AND CUDAQ_ENABLE_BRAKET_BACKEND) + SET(AWSSDK_ROOT "$ENV{AWS_INSTALL_PREFIX}") endif() if(NOT CUDAQ_EXTERNAL_NVQIR_SIMS) SET(CUDAQ_EXTERNAL_NVQIR_SIMS $ENV{CUDAQ_EXTERNAL_NVQIR_SIMS}) @@ -484,45 +492,6 @@ if (OPENSSL_FOUND AND CUDAQ_ENABLE_REST) # The asio submodule doesn't use cmake, so use find_path for it. find_path(ASIO_INCLUDE_DIR asio.hpp PATHS tpls/asio/asio/include) add_subdirectory(tpls/Crow) - # AWS SDK for C++ - set(SERVICE_COMPONENTS braket s3-crt sts) - # Call find_package without REQUIRED flag to see whether AWSSDK is installed. - find_package(AWSSDK COMPONENTS ${SERVICE_COMPONENTS}) - if (NOT AWSSDK_FOUND) - message(STATUS "AWS SDK not found. Building it.") - include(ProcessorCount) - ProcessorCount(N) - if(CMAKE_MAKE_PROGRAM MATCHES "make$") - set(MAKE_PARALLEL ${CMAKE_MAKE_PROGRAM} -j${N}) - else() - set(MAKE_PARALLEL ${CMAKE_MAKE_PROGRAM}) - endif() - execute_process(RESULT_VARIABLE result COMMAND cmake "-B${CMAKE_CURRENT_BINARY_DIR}/tpls/aws-sdk-cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tpls/aws-sdk-cpp" - "-DAUTORUN_UNIT_TESTS=OFF" - "-DAWS_SDK_WARNINGS_ARE_ERRORS=OFF" - "-DAWS_USER_AGENT_CUSTOMIZATION=CUDA-Q/${CUDA_QUANTUM_VERSION}" - "-DBUILD_ONLY=braket;s3-crt;sts" - "-DBUILD_SHARED_LIBS=OFF" - "-DCMAKE_COMPILE_WARNING_AS_ERROR=OFF" - "-Dcrypto_LIBRARY=${crypto_LIBRARY}" - "-Dcrypto_INCLUDE_DIR=${crypto_INCLUDE_DIR}" - "-DCURL_LIBRARY=${CURL_LIBRARY}" - "-DCURL_INCLUDE_DIR=${CURL_INCLUDE_DIR}" - "-DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR}" - "-DENABLE_TESTING=OFF" - "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" - "-G ${CMAKE_GENERATOR}" - ) - if(NOT ${result} STREQUAL "0") - message(FATAL_ERROR "Configuring aws-sdk-cpp failed.") - endif() - execute_process(RESULT_VARIABLE result COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_CURRENT_BINARY_DIR}/tpls/aws-sdk-cpp ${MAKE_PARALLEL} install) - if(NOT ${result} STREQUAL "0") - message(FATAL_ERROR "Building aws-sdk-cpp failed.") - endif() - endif() - # AWSSDK should be installed now. Call find_package with REQUIRED flag. - find_package(AWSSDK REQUIRED COMPONENTS ${SERVICE_COMPONENTS}) endif() # Check for CUDA Support diff --git a/docker/build/devdeps.Dockerfile b/docker/build/devdeps.Dockerfile index 200980c9bd2..9be229826a6 100644 --- a/docker/build/devdeps.Dockerfile +++ b/docker/build/devdeps.Dockerfile @@ -50,6 +50,7 @@ ENV BLAS_INSTALL_PREFIX=/usr/local/blas ENV ZLIB_INSTALL_PREFIX=/usr/local/zlib ENV OPENSSL_INSTALL_PREFIX=/usr/local/openssl ENV CURL_INSTALL_PREFIX=/usr/local/curl +ENV AWS_INSTALL_PREFIX=/usr/local/aws ## [Build Dependencies] RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -135,10 +136,12 @@ ENV BLAS_INSTALL_PREFIX=/usr/local/blas ENV ZLIB_INSTALL_PREFIX=/usr/local/zlib ENV OPENSSL_INSTALL_PREFIX=/usr/local/openssl ENV CURL_INSTALL_PREFIX=/usr/local/curl +ENV AWS_INSTALL_PREFIX=/usr/local/aws COPY --from=prereqs /usr/local/blas "$BLAS_INSTALL_PREFIX" COPY --from=prereqs /usr/local/zlib "$ZLIB_INSTALL_PREFIX" COPY --from=prereqs /usr/local/openssl "$OPENSSL_INSTALL_PREFIX" COPY --from=prereqs /usr/local/curl "$CURL_INSTALL_PREFIX" +COPY --from=prereqs /usr/local/aws "$AWS_INSTALL_PREFIX" # Install additional dependencies required to build and test CUDA-Q. RUN apt-get update && apt-get install --no-install-recommends -y wget ca-certificates \ diff --git a/docker/build/devdeps.manylinux.Dockerfile b/docker/build/devdeps.manylinux.Dockerfile index 7b134a3c5b0..ed10b57faf7 100644 --- a/docker/build/devdeps.manylinux.Dockerfile +++ b/docker/build/devdeps.manylinux.Dockerfile @@ -118,6 +118,7 @@ ENV BLAS_INSTALL_PREFIX=/usr/local/blas ENV ZLIB_INSTALL_PREFIX=/usr/local/zlib ENV OPENSSL_INSTALL_PREFIX=/usr/local/openssl ENV CURL_INSTALL_PREFIX=/usr/local/curl +ENV AWS_INSTALL_PREFIX=/usr/local/aws ENV CUQUANTUM_INSTALL_PREFIX=/usr/local/cuquantum ENV CUTENSOR_INSTALL_PREFIX=/usr/local/cutensor RUN bash /scripts/install_prerequisites.sh diff --git a/pyproject.toml b/pyproject.toml index d0d635b6219..b5582ca4eb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ wheel.packages = ["python/cudaq"] wheel.license-files = [ "LICENSE", "NOTICE", "CITATION.cff" ] build-dir = "_skbuild" metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" -cmake.minimum-version = "3.26" +cmake.minimum-version = "3.27" cmake.build-type = "Release" cmake.verbose = false cmake.args = [ diff --git a/runtime/cudaq/platform/CMakeLists.txt b/runtime/cudaq/platform/CMakeLists.txt index 4d2138e0a03..f6b8d51b9dd 100644 --- a/runtime/cudaq/platform/CMakeLists.txt +++ b/runtime/cudaq/platform/CMakeLists.txt @@ -16,4 +16,6 @@ if (CUDAQ_ENABLE_REST) endif() add_subdirectory(fermioniq) -add_subdirectory(quera) \ No newline at end of file +if (AWSSDK_ROOT) + add_subdirectory(quera) +endif() \ No newline at end of file diff --git a/runtime/cudaq/platform/default/rest/CMakeLists.txt b/runtime/cudaq/platform/default/rest/CMakeLists.txt index d8389ca95c1..7d636f302f6 100644 --- a/runtime/cudaq/platform/default/rest/CMakeLists.txt +++ b/runtime/cudaq/platform/default/rest/CMakeLists.txt @@ -19,8 +19,12 @@ target_include_directories(cudaq-rest-qpu PRIVATE . $ $) -set(SERVICE_COMPONENTS braket s3-crt sts) -find_package(AWSSDK REQUIRED COMPONENTS ${SERVICE_COMPONENTS}) +if (AWSSDK_ROOT) + set(SERVICE_COMPONENTS braket s3-crt sts) + find_package(AWSSDK REQUIRED COMPONENTS ${SERVICE_COMPONENTS}) +else() + set(AWSSDK_LINK_LIBRARIES "") +endif() target_link_libraries(cudaq-rest-qpu PUBLIC diff --git a/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt b/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt index ad8b05c8884..123434de52c 100644 --- a/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt +++ b/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt @@ -6,8 +6,10 @@ # the terms of the Apache License 2.0 which accompanies this distribution. # # ============================================================================ # add_subdirectory(anyon) -add_subdirectory(braket) add_subdirectory(oqc) add_subdirectory(ionq) add_subdirectory(quantinuum) add_subdirectory(iqm) +if (AWSSDK_ROOT) + add_subdirectory(braket) +endif() diff --git a/scripts/configure_build.sh b/scripts/configure_build.sh index fc5d3b24625..aa8c087e71d 100644 --- a/scripts/configure_build.sh +++ b/scripts/configure_build.sh @@ -19,6 +19,8 @@ export BLAS_INSTALL_PREFIX=/usr/local/blas export ZLIB_INSTALL_PREFIX=/usr/local/zlib export OPENSSL_INSTALL_PREFIX=/usr/local/openssl export CURL_INSTALL_PREFIX=/usr/local/curl +export AWS_INSTALL_PREFIX=/usr/local/aws + # [ /dev/null)" ]; then + aws_service_components='braket s3-crt sts' + git clone --filter=tree:0 https://github.com/aws/aws-sdk-cpp aws-sdk-cpp + cd aws-sdk-cpp && git checkout 1.11.454 && git submodule update --init --recursive + + # FIXME: CUDAQ VERSION? + mkdir build && cd build + cmake -G Ninja .. \ + -DCMAKE_INSTALL_PREFIX="${AWS_INSTALL_PREFIX}" \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_COMPILE_WARNING_AS_ERROR=OFF \ + -DAWS_SDK_WARNINGS_ARE_ERRORS=OFF \ + -DAWS_USER_AGENT_CUSTOMIZATION=CUDA-Q/${CUDA_QUANTUM_VERSION} \ + -DBUILD_ONLY="$(echo $aws_service_components | tr ' ' ';')" \ + -DBUILD_SHARED_LIBS=OFF \ + -DZLIB_ROOT="${ZLIB_INSTALL_PREFIX}" \ + -DZLIB_USE_STATIC_LIBS=ON \ + -DOPENSSL_ROOT_DIR="${OPENSSL_INSTALL_PREFIX}" \ + -DCURL_LIBRARY="${CURL_INSTALL_PREFIX}/lib/libcurl.a" \ + -DCURL_INCLUDE_DIR="${CURL_INSTALL_PREFIX}/include" \ + -Dcrypto_LIBRARY="$(find "$OPENSSL_INSTALL_PREFIX" -name libcrypto.a)" \ + -Dcrypto_INCLUDE_DIR="${OPENSSL_INSTALL_PREFIX}/include" \ + -DENABLE_TESTING=OFF \ + -DAUTORUN_UNIT_TESTS=OFF + cmake --build . --config=Release + cmake --install . --config=Release + cd ../.. && rm -rf 1.11.454.tar.gz aws-sdk-cpp + remove_temp_installs + else + echo "AWS SDK already installed in $AWS_INSTALL_PREFIX." + fi +fi + # [cuQuantum and cuTensor] Needed for GPU-accelerated components cuda_driver=${CUDACXX:-${CUDA_HOME:-/usr/local/cuda}/bin/nvcc} cuda_version=`"$cuda_driver" --version 2>/dev/null | grep -o 'release [0-9]*\.[0-9]*' | cut -d ' ' -f 2` diff --git a/targettests/execution/angled_gate.cpp b/targettests/execution/angled_gate.cpp index cbafde60120..d3ac5c7e92b 100644 --- a/targettests/execution/angled_gate.cpp +++ b/targettests/execution/angled_gate.cpp @@ -6,7 +6,7 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -// RUN: nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi #include diff --git a/targettests/execution/bug_qubit.cpp b/targettests/execution/bug_qubit.cpp index 02d64b53648..29ef77ad33f 100644 --- a/targettests/execution/bug_qubit.cpp +++ b/targettests/execution/bug_qubit.cpp @@ -10,11 +10,11 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t // RUN: cudaq-quake %cpp_std %s | cudaq-opt --promote-qubit-allocation | FileCheck --check-prefixes=MLIR %s diff --git a/targettests/execution/callable_kernel_arg.cpp b/targettests/execution/callable_kernel_arg.cpp index f6b91088f9e..4842a253de4 100644 --- a/targettests/execution/callable_kernel_arg.cpp +++ b/targettests/execution/callable_kernel_arg.cpp @@ -8,11 +8,11 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t // clang-format on diff --git a/targettests/execution/cudaq_observe.cpp b/targettests/execution/cudaq_observe.cpp index b3527ad3193..edba980cab5 100644 --- a/targettests/execution/cudaq_observe.cpp +++ b/targettests/execution/cudaq_observe.cpp @@ -8,13 +8,13 @@ // REQUIRES: c++20 // clang-format off -// RUN: nvq++ --target braket --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // 2 different IQM machines for 2 different topologies // RUN: nvq++ --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ --target braket --emulate %s -o %t && %t | FileCheck %s; fi // clang-format on #include diff --git a/targettests/execution/custom_operation_adj.cpp b/targettests/execution/custom_operation_adj.cpp index 314527fae5a..3de4646cac4 100644 --- a/targettests/execution/custom_operation_adj.cpp +++ b/targettests/execution/custom_operation_adj.cpp @@ -9,11 +9,11 @@ // clang-format off // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // clang-format on #include diff --git a/targettests/execution/custom_operation_basic.cpp b/targettests/execution/custom_operation_basic.cpp index d567bd968a5..05e6fbe15ae 100644 --- a/targettests/execution/custom_operation_basic.cpp +++ b/targettests/execution/custom_operation_basic.cpp @@ -9,11 +9,11 @@ // clang-format off // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // clang-format on #include diff --git a/targettests/execution/graph_coloring-1.cpp b/targettests/execution/graph_coloring-1.cpp index c669d054a4d..baa89670c29 100644 --- a/targettests/execution/graph_coloring-1.cpp +++ b/targettests/execution/graph_coloring-1.cpp @@ -8,8 +8,8 @@ // REQUIRES: c++20 // clang-format off -// RUN: nvq++ %s -o %t --target braket --emulate && %t | FileCheck %s // RUN: nvq++ %s -o %t --target quantinuum --emulate && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %s -o %t --target braket --emulate && %t | FileCheck %s; fi // clang-format on #include diff --git a/targettests/execution/graph_coloring.cpp b/targettests/execution/graph_coloring.cpp index 262743ad996..f6a98d7bc5c 100644 --- a/targettests/execution/graph_coloring.cpp +++ b/targettests/execution/graph_coloring.cpp @@ -8,8 +8,8 @@ // REQUIRES: c++20 // clang-format off -// RUN: nvq++ %s -o %t --target braket --emulate && %t | FileCheck %s // RUN: nvq++ %s -o %t --target quantinuum --emulate && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %s -o %t --target braket --emulate && %t | FileCheck %s; fi // clang-format on #include diff --git a/targettests/execution/if_jit.cpp b/targettests/execution/if_jit.cpp index 14a1f20d1d2..b076a33ab26 100644 --- a/targettests/execution/if_jit.cpp +++ b/targettests/execution/if_jit.cpp @@ -10,11 +10,11 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t // clang-format on diff --git a/targettests/execution/int8_t.cpp b/targettests/execution/int8_t.cpp index 68a614bea18..38e0319bf5b 100644 --- a/targettests/execution/int8_t.cpp +++ b/targettests/execution/int8_t.cpp @@ -8,11 +8,11 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t // clang-format on diff --git a/targettests/execution/int8_t_free_func.cpp b/targettests/execution/int8_t_free_func.cpp index a17bd83197c..cec6a2439ec 100644 --- a/targettests/execution/int8_t_free_func.cpp +++ b/targettests/execution/int8_t_free_func.cpp @@ -8,11 +8,11 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t // clang-format on diff --git a/targettests/execution/load_value.cpp b/targettests/execution/load_value.cpp index 39450fe054a..2c453910ac8 100644 --- a/targettests/execution/load_value.cpp +++ b/targettests/execution/load_value.cpp @@ -8,12 +8,12 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t // clang-format on diff --git a/targettests/execution/state_preparation.cpp b/targettests/execution/state_preparation.cpp index 5ee7a8671fc..9eae1ae35f6 100644 --- a/targettests/execution/state_preparation.cpp +++ b/targettests/execution/state_preparation.cpp @@ -10,13 +10,13 @@ // RUN: nvq++ %cpp_std --enable-mlir %s -o %t && %t | FileCheck %s // Quantum emulators -// RUN: nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // 2 different IQM machines for 2 different topologies // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi #include #include diff --git a/targettests/execution/state_preparation_vector_sizes.cpp b/targettests/execution/state_preparation_vector_sizes.cpp index 2c67e21d611..7fbc7214159 100644 --- a/targettests/execution/state_preparation_vector_sizes.cpp +++ b/targettests/execution/state_preparation_vector_sizes.cpp @@ -10,13 +10,13 @@ // RUN: nvq++ %cpp_std --enable-mlir %s -o %t && %t | FileCheck %s // Quantum emulators -// RUN: nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // 2 different IQM machines for 2 different topologies // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi #include #include diff --git a/targettests/execution/swap_gate.cpp b/targettests/execution/swap_gate.cpp index 0d12f6fa168..2026f75ee28 100644 --- a/targettests/execution/swap_gate.cpp +++ b/targettests/execution/swap_gate.cpp @@ -8,11 +8,11 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t && %t | FileCheck %s #include "cudaq.h" diff --git a/targettests/execution/variable_size_qreg.cpp b/targettests/execution/variable_size_qreg.cpp index fa46e6b06b7..3d53fca0c25 100644 --- a/targettests/execution/variable_size_qreg.cpp +++ b/targettests/execution/variable_size_qreg.cpp @@ -8,11 +8,11 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t // clang-format on diff --git a/targettests/lit.site.cfg.py.in b/targettests/lit.site.cfg.py.in index 6dba2a04272..fd5ad765f51 100644 --- a/targettests/lit.site.cfg.py.in +++ b/targettests/lit.site.cfg.py.in @@ -43,6 +43,13 @@ config.test_remote_sim = "@CUDAQ_TEST_REMOTE_SIM@" if cmake_boolvar_to_bool(config.test_remote_sim): config.available_features.add('remote-sim') +config.cudaq_backends_braket = "@CUDAQ_ENABLE_BRAKET_BACKEND@" +if cmake_boolvar_to_bool(config.cudaq_backends_braket): + config.available_features.add('braket') + config.substitutions.append(('%braket_avail', 'true')) +else: + config.substitutions.append(('%braket_avail', 'false')) + import lit.llvm lit.llvm.initialize(lit_config, config) diff --git a/tpls/aws-sdk-cpp b/tpls/aws-sdk-cpp deleted file mode 160000 index a72c30acc31..00000000000 --- a/tpls/aws-sdk-cpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a72c30acc317b24d282640f0a40ef137acbeedec From f7a0b1280e271d8a2a8a749b5b585bc1782211dd Mon Sep 17 00:00:00 2001 From: Thien Nguyen <58006629+1tnguyen@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:47:33 +1100 Subject: [PATCH 02/44] Allow setting noise on measurement operation and apply it accordingly (#2447) Signed-off-by: Thien Nguyen --- runtime/common/NoiseModel.h | 2 +- runtime/nvqir/CircuitSimulator.h | 6 ++++ unittests/integration/noise_tester.cpp | 41 ++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/runtime/common/NoiseModel.h b/runtime/common/NoiseModel.h index fa7d3d4fd62..d52bc118b0b 100644 --- a/runtime/common/NoiseModel.h +++ b/runtime/common/NoiseModel.h @@ -256,7 +256,7 @@ class noise_model { std::unordered_map gatePredicates; static constexpr const char *availableOps[] = { - "x", "y", "z", "h", "s", "t", "rx", "ry", "rz", "r1", "u3"}; + "x", "y", "z", "h", "s", "t", "rx", "ry", "rz", "r1", "u3", "mz"}; public: /// @brief default constructor diff --git a/runtime/nvqir/CircuitSimulator.h b/runtime/nvqir/CircuitSimulator.h index bb0d4cda74d..f5ad4dae763 100644 --- a/runtime/nvqir/CircuitSimulator.h +++ b/runtime/nvqir/CircuitSimulator.h @@ -1315,6 +1315,12 @@ class CircuitSimulatorBase : public CircuitSimulator { // Flush the Gate Queue flushGateQueue(); + // Apply measurement noise (if any) + // Note: gate noises are applied during flushGateQueue + if (executionContext && executionContext->noiseModel) + applyNoiseChannel(/*gateName=*/"mz", /*controls=*/{}, + /*targets=*/{qubitIdx}, /*params=*/{}); + // If sampling, just store the bit, do nothing else. if (handleBasicSampling(qubitIdx, registerName)) return true; diff --git a/unittests/integration/noise_tester.cpp b/unittests/integration/noise_tester.cpp index 221a69d9458..b3bd5934d24 100644 --- a/unittests/integration/noise_tester.cpp +++ b/unittests/integration/noise_tester.cpp @@ -641,3 +641,44 @@ CUDAQ_TEST(NoiseTest, checkCustomOperation) { } } #endif + +#if defined(CUDAQ_BACKEND_DM) || defined(CUDAQ_BACKEND_STIM) + +CUDAQ_TEST(NoiseTest, checkMeasurementNoise) { + cudaq::set_random_seed(13); + constexpr double bitFlipRate = 0.1; + cudaq::bit_flip_channel bf(bitFlipRate); + cudaq::noise_model noise; + // 10% bit flipping during measurement + noise.add_channel("mz", {0}, bf); + cudaq::set_noise(noise); + { + auto kernel = []() { + cudaq::qubit q; + x(q); + mz(q); + }; + auto counts = cudaq::sample(10000, kernel); + counts.dump(); + // Due to measurement errors, we have both 0/1 results. + EXPECT_EQ(2, counts.size()); + EXPECT_NEAR(counts.probability("0"), bitFlipRate, 0.01); + EXPECT_NEAR(counts.probability("1"), 1.0 - bitFlipRate, 0.01); + } + { + auto kernel = []() { + cudaq::qvector q(2); + x(q); + mz(q); + }; + auto counts = cudaq::sample(10000, kernel); + counts.dump(); + // We only have measurement noise on the first qubit. + EXPECT_EQ(2, counts.size()); + EXPECT_NEAR(counts.probability("01"), bitFlipRate, 0.01); + EXPECT_NEAR(counts.probability("11"), 1.0 - bitFlipRate, 0.01); + } + cudaq::unset_noise(); // clear for subsequent tests +} + +#endif From b8f44916433357e1bd39ba5ec3230f59f4f47a6f Mon Sep 17 00:00:00 2001 From: Thien Nguyen <58006629+1tnguyen@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:31:57 +1100 Subject: [PATCH 03/44] MGPU downstream updates (#2424) * [WIP] Build adjustments for new mgpu backends As the new libs will depend on runtime libcuda.so.1, we exclude it from the wheel packaging. Note: libcuda.so.1 comes with the NVIDIA driver (i.e., should be available on systems with GPU). Signed-off-by: Thien Nguyen * Update mgpu sha and fix spelling Signed-off-by: Thien Nguyen * Sort spelling_allowlist.txt Signed-off-by: Thien Nguyen --------- Signed-off-by: Thien Nguyen --- .github/workflows/config/gitlab_commits.txt | 2 +- .github/workflows/config/spelling_allowlist.txt | 1 + docker/build/assets.Dockerfile | 3 ++- docker/release/cudaq.wheel.Dockerfile | 3 ++- docs/sphinx/using/backends/simulators.rst | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/config/gitlab_commits.txt b/.github/workflows/config/gitlab_commits.txt index 07b9709655b..16e13926a67 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: 5ecebd6b7642e8526baf5930634f2f854aef9ea7 +nvidia-mgpu-commit: dadce3edc10564e94cd260590344d5840880087a diff --git a/.github/workflows/config/spelling_allowlist.txt b/.github/workflows/config/spelling_allowlist.txt index 5e60e19fb38..fdb4b0c5f11 100644 --- a/.github/workflows/config/spelling_allowlist.txt +++ b/.github/workflows/config/spelling_allowlist.txt @@ -45,6 +45,7 @@ Hadamards Hamiltonian Hamiltonians IQM +InfiniBand IonQ JIT JSON diff --git a/docker/build/assets.Dockerfile b/docker/build/assets.Dockerfile index 8b6acee43e0..1d01ab62d3a 100644 --- a/docker/build/assets.Dockerfile +++ b/docker/build/assets.Dockerfile @@ -230,7 +230,8 @@ RUN echo "Patching up wheel using auditwheel..." && \ --exclude libcustatevec.so.1 \ --exclude libcudart.so.11.0 \ --exclude libnvToolsExt.so.1 \ - --exclude libnvidia-ml.so.1 + --exclude libnvidia-ml.so.1 \ + --exclude libcuda.so.1 ## [ Date: Wed, 4 Dec 2024 19:08:27 -0800 Subject: [PATCH 04/44] Add support for logging matrices when running simulators (#2451) Signed-off-by: Ben Howe --- runtime/nvqir/CircuitSimulator.h | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/runtime/nvqir/CircuitSimulator.h b/runtime/nvqir/CircuitSimulator.h index f5ad4dae763..5b7c26dd7bd 100644 --- a/runtime/nvqir/CircuitSimulator.h +++ b/runtime/nvqir/CircuitSimulator.h @@ -10,6 +10,7 @@ #include "Gates.h" #include "QIRTypes.h" +#include "common/Environment.h" #include "common/Logger.h" #include "common/MeasureCounts.h" #include "common/NoiseModel.h" @@ -725,6 +726,19 @@ class CircuitSimulatorBase : public CircuitSimulator { return; } + // Use static variables to reduce the number of calls to cudaq::getEnvBool + // since this is a frequently called piece of code, and we don't expect it + // to change in the middle of a run. + static bool z_env_var_checked = false; + static bool z_matrix_logging = false; + if (!z_env_var_checked) { + z_matrix_logging = cudaq::getEnvBool("CUDAQ_LOG_GATE_MATRIX", false); + z_env_var_checked = true; + } + if (z_matrix_logging) + cudaq::log("{}: matrix={}, controls={}, targets={}, params={}", name, + matrix, controls, targets, params); + gateQueue.emplace(name, matrix, controls, targets, params); } @@ -788,17 +802,7 @@ class CircuitSimulatorBase : public CircuitSimulator { /// The environment variable "CUDAQ_OBSERVE_FROM_SAMPLING" can be used to turn /// on or off this setting. bool shouldObserveFromSampling(bool defaultConfig = true) { - if (auto envVar = std::getenv(observeSamplingEnvVar); envVar) { - std::string asString = envVar; - std::transform(asString.begin(), asString.end(), asString.begin(), - [](auto c) { return std::tolower(c); }); - if (asString == "false" || asString == "off" || asString == "0") - return false; - if (asString == "true" || asString == "on" || asString == "1") - return true; - } - - return defaultConfig; + return cudaq::getEnvBool(observeSamplingEnvVar, defaultConfig); } bool isSinglePrecision() const override { From 5ccbcd24da7822afd6e102314b0c1731af030d0d Mon Sep 17 00:00:00 2001 From: Pradnya Khalate <148914294+khalatepradnya@users.noreply.github.com> Date: Thu, 5 Dec 2024 01:13:09 -0800 Subject: [PATCH 05/44] [testing] Skip Python tests if dependency package missing (#2450) * Skip the 'test_evolve_dynamics_torch_integrators.py' tests if `torch` package not found. Signed-off-by: Pradnya Khalate --- .../integrators/test_evolve_dynamics_torch_integrators.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/tests/operator/integrators/test_evolve_dynamics_torch_integrators.py b/python/tests/operator/integrators/test_evolve_dynamics_torch_integrators.py index 9a2281de264..9c51965a94c 100644 --- a/python/tests/operator/integrators/test_evolve_dynamics_torch_integrators.py +++ b/python/tests/operator/integrators/test_evolve_dynamics_torch_integrators.py @@ -7,6 +7,9 @@ # ============================================================================ # import pytest import cudaq + +torch = pytest.importorskip("torch") + # Note: the test model may create state, hence need to set the target to "dynamics" cudaq.set_target("dynamics") From 9d7e20a632b61f8ea4a103b06c3c75a8987d7de0 Mon Sep 17 00:00:00 2001 From: Pradnya Khalate <148914294+khalatepradnya@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:53:19 -0800 Subject: [PATCH 06/44] [docs] [testing] Follow-up for `braket` target and minor clean-up (#2448) * Update docs to show the `braket` examples * Remove the unsupported tests (tracked in issue#2325) * Emacs compatible banner * Update the C++ tests to reflect appropriate reason for skipping. (Note that `observe` API is not supported) --------- Signed-off-by: Pradnya Khalate --- CMakeLists.txt | 4 +- .../using/examples/hardware_providers.rst | 22 ++++- runtime/common/BraketExecutor.h | 23 ++--- runtime/common/BraketServerHelper.h | 2 +- targettests/braket/sudoku_2x2-1.cpp | 1 - targettests/braket/sudoku_2x2-bit_names.cpp | 1 - targettests/braket/sudoku_2x2-reg_name.cpp | 1 - targettests/braket/sudoku_2x2.cpp | 1 - unittests/backends/braket/BraketTester.cpp | 87 ++----------------- 9 files changed, 37 insertions(+), 105 deletions(-) delete mode 120000 targettests/braket/sudoku_2x2-1.cpp delete mode 120000 targettests/braket/sudoku_2x2-bit_names.cpp delete mode 120000 targettests/braket/sudoku_2x2-reg_name.cpp delete mode 120000 targettests/braket/sudoku_2x2.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 496447a8b6c..ebbbbd1654c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,9 +93,9 @@ if (CUDAQ_ENABLE_REST AND NOT DEFINED CUDAQ_ENABLE_REMOTE_SIM) endif() endif() -# Enable AWS backends by default. +# Enable Amazon Braket backends by default. if (NOT DEFINED CUDAQ_ENABLE_BRAKET_BACKEND) - set(CUDAQ_ENABLE_BRAKET_BACKEND ON CACHE BOOL "Enable building AWS backends.") + set(CUDAQ_ENABLE_BRAKET_BACKEND ON CACHE BOOL "Enable building AWS SDK for Amazon Braket backends.") endif() # Generate a CompilationDatabase (compile_commands.json file) for our build, diff --git a/docs/sphinx/using/examples/hardware_providers.rst b/docs/sphinx/using/examples/hardware_providers.rst index 076a4fed43d..96fbc559f22 100644 --- a/docs/sphinx/using/examples/hardware_providers.rst +++ b/docs/sphinx/using/examples/hardware_providers.rst @@ -1,9 +1,25 @@ Using Quantum Hardware Providers ----------------------------------- -CUDA-Q contains support for using a set of hardware providers (IonQ, IQM, OQC, ORCA Computing and Quantinuum). -For more information about executing quantum kernels on different hardware backends, please take a look -at :doc:`hardware <../backends/hardware>`. +CUDA-Q contains support for using a set of hardware providers (Amazon Braket, +IonQ, IQM, OQC, ORCA Computing, Quantinuum and QuEra Computing). +For more information about executing quantum kernels on different hardware +backends, please take a look at :doc:`hardware <../backends/hardware>`. + +Amazon Braket +================================== + +The following code illustrates how to run kernels on Amazon Braket's backends. + +.. tab:: Python + + .. literalinclude:: ../../targets/python/braket.py + :language: python + +.. tab:: C++ + + .. literalinclude:: ../../targets/cpp/braket.cpp + :language: cpp IonQ ================================== diff --git a/runtime/common/BraketExecutor.h b/runtime/common/BraketExecutor.h index a3d432d5ad4..05a13656d8a 100644 --- a/runtime/common/BraketExecutor.h +++ b/runtime/common/BraketExecutor.h @@ -1,4 +1,4 @@ -/******************************************************************************* +/****************************************************************-*- C++ -*-**** * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * @@ -7,27 +7,22 @@ ******************************************************************************/ #pragma once + +#include "common/BraketServerHelper.h" #include "common/Executor.h" #include "common/FmtCore.h" +#include "common/Logger.h" #include "common/MeasureCounts.h" #include "cudaq.h" - -#include -#include - -#include - #include -#include -#include - +#include #include #include #include - -#include "common/BraketServerHelper.h" -#include "common/Logger.h" - +#include +#include +#include +#include #include #include #include diff --git a/runtime/common/BraketServerHelper.h b/runtime/common/BraketServerHelper.h index 32f0d4695ee..5f008f1a5b5 100644 --- a/runtime/common/BraketServerHelper.h +++ b/runtime/common/BraketServerHelper.h @@ -1,4 +1,4 @@ -/******************************************************************************* +/****************************************************************-*- C++ -*-**** * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * * All rights reserved. * * * diff --git a/targettests/braket/sudoku_2x2-1.cpp b/targettests/braket/sudoku_2x2-1.cpp deleted file mode 120000 index 09aa81848d4..00000000000 --- a/targettests/braket/sudoku_2x2-1.cpp +++ /dev/null @@ -1 +0,0 @@ -../execution/sudoku_2x2-1.cpp \ No newline at end of file diff --git a/targettests/braket/sudoku_2x2-bit_names.cpp b/targettests/braket/sudoku_2x2-bit_names.cpp deleted file mode 120000 index 15e2b47ae2e..00000000000 --- a/targettests/braket/sudoku_2x2-bit_names.cpp +++ /dev/null @@ -1 +0,0 @@ -../execution/sudoku_2x2-bit_names.cpp \ No newline at end of file diff --git a/targettests/braket/sudoku_2x2-reg_name.cpp b/targettests/braket/sudoku_2x2-reg_name.cpp deleted file mode 120000 index 367a2098f43..00000000000 --- a/targettests/braket/sudoku_2x2-reg_name.cpp +++ /dev/null @@ -1 +0,0 @@ -../execution/sudoku_2x2-reg_name.cpp \ No newline at end of file diff --git a/targettests/braket/sudoku_2x2.cpp b/targettests/braket/sudoku_2x2.cpp deleted file mode 120000 index 23748412f1f..00000000000 --- a/targettests/braket/sudoku_2x2.cpp +++ /dev/null @@ -1 +0,0 @@ -../execution/sudoku_2x2.cpp \ No newline at end of file diff --git a/unittests/backends/braket/BraketTester.cpp b/unittests/backends/braket/BraketTester.cpp index 89fea281644..b0d04753ae0 100644 --- a/unittests/backends/braket/BraketTester.cpp +++ b/unittests/backends/braket/BraketTester.cpp @@ -25,8 +25,7 @@ bool isValidExpVal(double value) { } CUDAQ_TEST(BraketTester, checkSampleSync) { - GTEST_SKIP() << "Fails with: Please make sure all qubits in the qubit " - "register are used for tasks submitted to simulators"; + GTEST_SKIP() << "Amazon Braket credentials required"; std::string home = std::getenv("HOME"); std::string fileName = home + "/FakeCppBraket.config"; auto backendString = @@ -68,8 +67,7 @@ CUDAQ_TEST(BraketTester, checkSampleSyncEmulate) { } CUDAQ_TEST(BraketTester, checkSampleAsync) { - GTEST_SKIP() << "Fails with: Please make sure all qubits in the qubit " - "register are used for tasks submitted to simulators"; + GTEST_SKIP() << "Amazon Braket credentials required"; std::string home = std::getenv("HOME"); std::string fileName = home + "/FakeCppBraket.config"; auto backendString = fmt::format(fmt::runtime(backendStringTemplate), @@ -111,8 +109,8 @@ CUDAQ_TEST(BraketTester, checkSampleAsyncEmulate) { } CUDAQ_TEST(BraketTester, checkSampleAsyncLoadFromFile) { - GTEST_SKIP() << "Fails with: Please make sure all qubits in the qubit " - "register are used for tasks submitted to simulators"; + GTEST_SKIP() << "Fails with: Cannot persist a cudaq::future for a local " + "kernel execution."; std::string home = std::getenv("HOME"); std::string fileName = home + "/FakeCppBraket.config"; auto backendString = fmt::format(fmt::runtime(backendStringTemplate), @@ -148,9 +146,7 @@ CUDAQ_TEST(BraketTester, checkSampleAsyncLoadFromFile) { } CUDAQ_TEST(BraketTester, checkObserveSync) { - GTEST_SKIP() << "Fails with: Device requires all qubits in the program to be " - "measured. This may be caused by declaring non-contiguous " - "qubits or measuring partial qubits"; + GTEST_SKIP() << "Fails with: Cannot observe kernel with measures in it"; std::string home = std::getenv("HOME"); std::string fileName = home + "/FakeCppBraket.config"; auto backendString = fmt::format(fmt::runtime(backendStringTemplate), @@ -164,6 +160,7 @@ CUDAQ_TEST(BraketTester, checkObserveSync) { kernel.x(qubit[0]); kernel.ry(theta, qubit[1]); kernel.x(qubit[1], qubit[0]); + kernel.mz(qubit); using namespace cudaq::spin; cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + @@ -232,78 +229,6 @@ CUDAQ_TEST(BraketTester, checkObserveAsync) { EXPECT_TRUE(isValidExpVal(result.expectation())); } -CUDAQ_TEST(BraketTester, checkObserveAsyncEmulate) { - std::string home = std::getenv("HOME"); - std::string fileName = home + "/FakeCppBraket.config"; - auto backendString = fmt::format(fmt::runtime(backendStringTemplate), - mockPort, fileName, machine); - backendString = - std::regex_replace(backendString, std::regex("false"), "true"); - - auto &platform = cudaq::get_platform(); - platform.setTargetBackend(backendString); - - auto [kernel, theta] = cudaq::make_kernel(); - auto qubit = kernel.qalloc(2); - kernel.x(qubit[0]); - kernel.ry(theta, qubit[1]); - kernel.x(qubit[1], qubit[0]); - - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - auto future = cudaq::observe_async(100000, 0, kernel, h, .59); - - auto result = future.get(); - result.dump(); - - printf("ENERGY: %lf\n", result.expectation()); - EXPECT_TRUE(isValidExpVal(result.expectation())); -} - -CUDAQ_TEST(BraketTester, checkObserveAsyncLoadFromFile) { - GTEST_SKIP() << "Fails with: Device requires all qubits in the program to be " - "measured. This may be caused by declaring non-contiguous " - "qubits or measuring partial qubits"; - std::string home = std::getenv("HOME"); - std::string fileName = home + "/FakeCppBraket.config"; - auto backendString = fmt::format(fmt::runtime(backendStringTemplate), - mockPort, fileName, machine); - - auto &platform = cudaq::get_platform(); - platform.setTargetBackend(backendString); - - auto [kernel, theta] = cudaq::make_kernel(); - auto qubit = kernel.qalloc(2); - kernel.x(qubit[0]); - kernel.ry(theta, qubit[1]); - kernel.x(qubit[1], qubit[0]); - - using namespace cudaq::spin; - cudaq::spin_op h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) + - .21829 * z(0) - 6.125 * z(1); - auto future = cudaq::observe_async(kernel, h, .59); - - { - std::ofstream out("saveMeObserve.json"); - out << future; - } - - // Later you can come back and read it in - cudaq::async_result readIn(&h); - std::ifstream in("saveMeObserve.json"); - in >> readIn; - - // Get the results of the read in future. - auto result = readIn.get(); - - std::remove("saveMeObserve.json"); - result.dump(); - - printf("ENERGY: %lf\n", result.expectation()); - EXPECT_TRUE(isValidExpVal(result.expectation())); -} - int main(int argc, char **argv) { std::string home = std::getenv("HOME"); std::string fileName = home + "/FakeCppBraket.config"; From bee25dc0b7061387ea988d2563bf4e58492872c5 Mon Sep 17 00:00:00 2001 From: Ben Howe <141149032+bmhowe23@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:03:19 -0800 Subject: [PATCH 07/44] Update docs for stim target (#2449) * Update docs for stim target * Spelling * Slight rewording --------- Signed-off-by: Ben Howe --- .../workflows/config/spelling_allowlist.txt | 1 + docs/sphinx/using/backends/simulators.rst | 77 ++++++++++--------- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/.github/workflows/config/spelling_allowlist.txt b/.github/workflows/config/spelling_allowlist.txt index fdb4b0c5f11..e74c23ba43a 100644 --- a/.github/workflows/config/spelling_allowlist.txt +++ b/.github/workflows/config/spelling_allowlist.txt @@ -105,6 +105,7 @@ SLED SLES SLURM SVD +Stim Superpositions TBI TCP diff --git a/docs/sphinx/using/backends/simulators.rst b/docs/sphinx/using/backends/simulators.rst index e2457ff270b..70a94343a5c 100644 --- a/docs/sphinx/using/backends/simulators.rst +++ b/docs/sphinx/using/backends/simulators.rst @@ -300,41 +300,6 @@ use the following commands: nvq++ --target qpp-cpu program.cpp [...] -o program.x ./program.x - -Clifford-Only Simulation (CPU) -++++++++++++++++++++++++++++++++++ - -.. _stim-backend: - -This target provides a fast simulator for circuits containing *only* Clifford -gates. Any non-Clifford gates (such as T gates and Toffoli gates) are not -supported. This simulator is based on the `Stim `_ -library. - -To execute a program on the :code:`stim` target, use the following commands: - -.. tab:: Python - - .. code:: bash - - python3 program.py [...] --target stim - - The target can also be defined in the application code by calling - - .. code:: python - - cudaq.set_target('stim') - - If a target is set in the application code, this target will override the :code:`--target` command line flag given during program invocation. - -.. tab:: C++ - - .. code:: bash - - nvq++ --target stim program.cpp [...] -o program.x - ./program.x - - Tensor Network Simulators ================================== @@ -479,6 +444,48 @@ Specific aspects of the simulation can be configured by defining the following e The parallelism of Jacobi method (the default `CUDAQ_MPS_SVD_ALGO` setting) gives GPU better performance on small and medium size matrices. If you expect a large number of singular values (e.g., increasing the `CUDAQ_MPS_MAX_BOND` setting), please adjust the `CUDAQ_MPS_SVD_ALGO` setting accordingly. +Clifford-Only Simulator +================================== + +Stim (CPU) +++++++++++++++++++++++++++++++++++ + +.. _stim-backend: + +This target provides a fast simulator for circuits containing *only* Clifford +gates. Any non-Clifford gates (such as T gates and Toffoli gates) are not +supported. This simulator is based on the `Stim `_ +library. + +To execute a program on the :code:`stim` target, use the following commands: + +.. tab:: Python + + .. code:: bash + + python3 program.py [...] --target stim + + The target can also be defined in the application code by calling + + .. code:: python + + cudaq.set_target('stim') + + If a target is set in the application code, this target will override the :code:`--target` command line flag given during program invocation. + +.. tab:: C++ + + .. code:: bash + + nvq++ --target stim program.cpp [...] -o program.x + ./program.x + +.. note:: + CUDA-Q currently executes kernels using a "shot-by-shot" execution approach. + This allows for conditional gate execution (i.e. full control flow), but it + can be slower than executing Stim a single time and generating all the shots + from that single execution. + Fermioniq ================================== From b6d5339399d40d7b3c719b0e686759f93a9dcf91 Mon Sep 17 00:00:00 2001 From: Bruno Schmitt <7152025+boschmitt@users.noreply.github.com> Date: Fri, 6 Dec 2024 23:40:37 +0100 Subject: [PATCH 08/44] [nvq++] Remove use of QTX. (#2439) RIP `qtx` dialect. Signed-off-by: boschmitt <7152025+boschmitt@users.noreply.github.com> --- lib/Optimizer/Transforms/PassDetails.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/Optimizer/Transforms/PassDetails.h b/lib/Optimizer/Transforms/PassDetails.h index 80d2331d0bb..95d6b69476c 100644 --- a/lib/Optimizer/Transforms/PassDetails.h +++ b/lib/Optimizer/Transforms/PassDetails.h @@ -19,10 +19,6 @@ #include "mlir/Pass/Pass.h" #include "mlir/Pass/PassRegistry.h" -namespace qtx { -class CircuitOp; -} - namespace cudaq::opt { #define GEN_PASS_CLASSES From de0e7d63db81d8ba814d10a619fec649d860c5f1 Mon Sep 17 00:00:00 2001 From: Bruno Schmitt <7152025+boschmitt@users.noreply.github.com> Date: Mon, 9 Dec 2024 20:13:30 +0100 Subject: [PATCH 09/44] [nvq++] Removes MLIR's `scf` dialect (#2435) We don't use this dialect and its presence is a historical artifact. This change triggered the removal of two tests: * `test/Quake/ghz.qke` * `test/Quake/iqft.qke` Both tests are a reminder of a past when we had to write quantum kernels directly in MLIR because of a lack of frontend. Both no longer test aything useful. The commit modifies `test/Quake/canonical-2.qke`, which was only testing the canonicalization of `cc.scope` operations. The new form is removes the clutter, making the test more precise. `test/Translate/ghz.qke` had to be modified because it uses MLIR's `affined.for` and its conversion to LLVMDialect requires `scf.for`. Signed-off-by: boschmitt <7152025+boschmitt@users.noreply.github.com> Co-authored-by: Eric Schweitz --- include/cudaq/Optimizer/InitAllDialects.h | 2 - lib/Frontend/nvqpp/ConvertExpr.cpp | 1 - lib/Optimizer/CodeGen/CMakeLists.txt | 1 - lib/Optimizer/CodeGen/ConvertCCToLLVM.cpp | 2 - lib/Optimizer/CodeGen/ConvertToQIR.cpp | 2 - .../Transforms/ApplyOpSpecialization.cpp | 6 - test/Quake/canonical-2.qke | 65 +++------ test/Quake/ghz.qke | 45 ------ test/Quake/iqft.qke | 138 ------------------ test/Translate/ghz.qke | 77 +++++----- tools/cudaq-quake/CMakeLists.txt | 1 - 11 files changed, 64 insertions(+), 276 deletions(-) delete mode 100644 test/Quake/ghz.qke delete mode 100644 test/Quake/iqft.qke diff --git a/include/cudaq/Optimizer/InitAllDialects.h b/include/cudaq/Optimizer/InitAllDialects.h index fdb41114d5b..c6df70d88f4 100644 --- a/include/cudaq/Optimizer/InitAllDialects.h +++ b/include/cudaq/Optimizer/InitAllDialects.h @@ -18,7 +18,6 @@ #include "mlir/Dialect/LLVMIR/LLVMDialect.h" #include "mlir/Dialect/Math/IR/Math.h" #include "mlir/Dialect/MemRef/IR/MemRef.h" -#include "mlir/Dialect/SCF/IR/SCF.h" namespace cudaq { @@ -35,7 +34,6 @@ inline void registerAllDialects(mlir::DialectRegistry ®istry) { mlir::LLVM::LLVMDialect, mlir::math::MathDialect, mlir::memref::MemRefDialect, - mlir::scf::SCFDialect, // NVQ++ dialects cudaq::cc::CCDialect, diff --git a/lib/Frontend/nvqpp/ConvertExpr.cpp b/lib/Frontend/nvqpp/ConvertExpr.cpp index 8b1f2a2638e..780fbe6acd4 100644 --- a/lib/Frontend/nvqpp/ConvertExpr.cpp +++ b/lib/Frontend/nvqpp/ConvertExpr.cpp @@ -13,7 +13,6 @@ #include "cudaq/Optimizer/Dialect/CC/CCOps.h" #include "cudaq/Optimizer/Dialect/Quake/QuakeOps.h" #include "llvm/Support/Debug.h" -#include "mlir/Dialect/SCF/IR/SCF.h" #define DEBUG_TYPE "lower-ast-expr" diff --git a/lib/Optimizer/CodeGen/CMakeLists.txt b/lib/Optimizer/CodeGen/CMakeLists.txt index d555ce99a93..56951cd07a7 100644 --- a/lib/Optimizer/CodeGen/CMakeLists.txt +++ b/lib/Optimizer/CodeGen/CMakeLists.txt @@ -62,7 +62,6 @@ add_cudaq_library(OptCodeGen MLIRFuncToLLVM MLIRMathToFuncs MLIRMathToLLVM - MLIRSCFToControlFlow # Translation MLIRTargetLLVMIRExport diff --git a/lib/Optimizer/CodeGen/ConvertCCToLLVM.cpp b/lib/Optimizer/CodeGen/ConvertCCToLLVM.cpp index 3faa3711563..7ad2618a4f3 100644 --- a/lib/Optimizer/CodeGen/ConvertCCToLLVM.cpp +++ b/lib/Optimizer/CodeGen/ConvertCCToLLVM.cpp @@ -22,7 +22,6 @@ #include "mlir/Conversion/LLVMCommon/Pattern.h" #include "mlir/Conversion/LLVMCommon/TypeConverter.h" #include "mlir/Conversion/MathToLLVM/MathToLLVM.h" -#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h" #include "mlir/Dialect/Arith/Transforms/Passes.h" #include "mlir/Target/LLVMIR/TypeToLLVM.h" @@ -121,7 +120,6 @@ struct CCToLLVM : public cudaq::opt::impl::CCToLLVMBase { arith::populateArithToLLVMConversionPatterns(ccTypeConverter, patterns); populateMathToLLVMConversionPatterns(ccTypeConverter, patterns); - populateSCFToControlFlowConversionPatterns(patterns); cf::populateControlFlowToLLVMConversionPatterns(ccTypeConverter, patterns); populateFuncToLLVMConversionPatterns(ccTypeConverter, patterns); cudaq::opt::populateCCToLLVMPatterns(ccTypeConverter, patterns); diff --git a/lib/Optimizer/CodeGen/ConvertToQIR.cpp b/lib/Optimizer/CodeGen/ConvertToQIR.cpp index c5b4606e2d3..e23691af943 100644 --- a/lib/Optimizer/CodeGen/ConvertToQIR.cpp +++ b/lib/Optimizer/CodeGen/ConvertToQIR.cpp @@ -30,7 +30,6 @@ #include "mlir/Conversion/LLVMCommon/Pattern.h" #include "mlir/Conversion/LLVMCommon/TypeConverter.h" #include "mlir/Conversion/MathToLLVM/MathToLLVM.h" -#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h" #include "mlir/Dialect/Arith/Transforms/Passes.h" #include "mlir/Dialect/Complex/IR/Complex.h" #include "mlir/Target/LLVMIR/ModuleTranslation.h" @@ -172,7 +171,6 @@ class ConvertToQIR : public cudaq::opt::impl::ConvertToQIRBase { arith::populateArithToLLVMConversionPatterns(typeConverter, patterns); populateMathToLLVMConversionPatterns(typeConverter, patterns); - populateSCFToControlFlowConversionPatterns(patterns); cf::populateControlFlowToLLVMConversionPatterns(typeConverter, patterns); populateFuncToLLVMConversionPatterns(typeConverter, patterns); cudaq::opt::populateCCToLLVMPatterns(typeConverter, patterns); diff --git a/lib/Optimizer/Transforms/ApplyOpSpecialization.cpp b/lib/Optimizer/Transforms/ApplyOpSpecialization.cpp index c308206cefb..b8a66f52c1e 100644 --- a/lib/Optimizer/Transforms/ApplyOpSpecialization.cpp +++ b/lib/Optimizer/Transforms/ApplyOpSpecialization.cpp @@ -13,7 +13,6 @@ #include "cudaq/Optimizer/Transforms/Passes.h" #include "cudaq/Todo.h" #include "llvm/Support/Debug.h" -#include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/IR/Dominance.h" #include "mlir/IR/IRMapping.h" #include "mlir/Transforms/DialectConversion.h" @@ -558,11 +557,6 @@ class ApplySpecializationPass invert(newIfOp.getElseRegion()); continue; } - if (auto forOp = dyn_cast(op)) { - LLVM_DEBUG(llvm::dbgs() << "moving for: " << forOp << ".\n"); - TODO_loc(loc, "cannot make adjoint of kernel with scf.for"); - // should we convert to cc.loop and use code below? - } if (auto loopOp = dyn_cast(op)) { LLVM_DEBUG(llvm::dbgs() << "moving loop: " << loopOp << ".\n"); auto newLoopOp = cloneReversedLoop(builder, loopOp); diff --git a/test/Quake/canonical-2.qke b/test/Quake/canonical-2.qke index b37012ad7ed..8dacf4c7f89 100644 --- a/test/Quake/canonical-2.qke +++ b/test/Quake/canonical-2.qke @@ -8,54 +8,29 @@ // RUN: cudaq-opt -canonicalize %s | FileCheck %s - func.func @__nvqpp__mlirgen__reflect_about_uniform(%arg0: !quake.veq) attributes {"cudaq-kernel"} { - %0 = quake.veq_size %arg0 : (!quake.veq) -> i64 - %c1_i32 = arith.constant 1 : i32 - %1 = arith.extsi %c1_i32 : i32 to i64 - %2 = arith.subi %0, %1 : i64 - %c0_i64 = arith.constant 0 : i64 - %c1_i64 = arith.constant 1 : i64 - %3 = arith.subi %2, %c1_i64 : i64 - %4 = quake.subveq %arg0, %c0_i64, %3 : (!quake.veq, i64, i64) -> !quake.veq - %5 = quake.veq_size %arg0 : (!quake.veq) -> i64 - %c1_i64_0 = arith.constant 1 : i64 - %6 = arith.subi %5, %c1_i64_0 : i64 - %7 = quake.extract_ref %arg0[%6] : (!quake.veq,i64) -> !quake.ref - %8 = cc.create_lambda { - cc.scope { - %c0 = arith.constant 0 : index - %c1 = arith.constant 1 : index - %10 = quake.veq_size %arg0 : (!quake.veq) -> i64 - %11 = arith.index_cast %10 : i64 to index - scf.for %arg1 = %c0 to %11 step %c1 { - %12 = quake.extract_ref %arg0[%arg1] : (!quake.veq,index) -> !quake.ref - quake.h %12 : (!quake.ref) -> () - } - } - } : !cc.callable<() -> ()> - %9 = cc.create_lambda { - cc.scope { - quake.z [%4] %7 : (!quake.veq, !quake.ref) -> () - } - } : !cc.callable<() -> ()> - quake.compute_action %8, %9 : !cc.callable<() -> ()>, !cc.callable<() -> ()> - return - } +func.func @canonicalize_scope(%arg0: !quake.ref) attributes {"cudaq-kernel"} { + %0 = cc.create_lambda { + cc.scope { + quake.h %arg0 : (!quake.ref) -> () + } + } : !cc.callable<() -> ()> + %1 = cc.create_lambda { + cc.scope { + quake.z %arg0 : (!quake.ref) -> () + } + } : !cc.callable<() -> ()> + quake.compute_action %0, %1 : !cc.callable<() -> ()>, !cc.callable<() -> ()> + return +} -// CHECK-LABEL: func.func @__nvqpp__mlirgen__reflect_about_uniform( -// CHECK: %[[VAL_12:.*]] = cc.create_lambda { +// CHECK-LABEL: func.func @canonicalize_scope( +// CHECK: %[[VAL_0:.*]] = cc.create_lambda { // CHECK-NOT: cc.scope -// CHECK: %[[VAL_13:.*]] = quake.veq_size %{{.*}} : (!quake.veq) -> i64 -// CHECK: %[[VAL_14:.*]] = arith.index_cast %[[VAL_13]] : i64 to index -// CHECK: scf.for %[[VAL_15:.*]] = %{{.*}} to %[[VAL_14]] step % -// CHECK: %[[VAL_16:.*]] = quake.extract_ref -// CHECK: quake.h %[[VAL_16]] -// CHECK: } +// CHECK: quake.h %{{.*}} : // CHECK: } : !cc.callable<() -> ()> -// CHECK: %[[VAL_17:.*]] = cc.create_lambda { +// CHECK: %[[VAL_1:.*]] = cc.create_lambda { // CHECK-NOT: cc.scope -// CHECK: quake.z [%{{.*}}] %{{.*}} : +// CHECK: quake.z %{{.*}} : // CHECK: } : !cc.callable<() -> ()> -// CHECK: quake.compute_action -// CHECK: return +// CHECK: quake.compute_action %[[VAL_0]], %[[VAL_1]] diff --git a/test/Quake/ghz.qke b/test/Quake/ghz.qke deleted file mode 100644 index ad6e662deac..00000000000 --- a/test/Quake/ghz.qke +++ /dev/null @@ -1,45 +0,0 @@ -// ========================================================================== // -// 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. // -// ========================================================================== // - -// RUN: cudaq-opt %s --canonicalize | FileCheck %s -module { - // CHECK: func.func @ghz(%[[arg0:.*]]: i32) { - // CHECK: %[[C1:.*]] = arith.constant 1 : i32 - // CHECK: %0 = quake.alloca !quake.veq[%[[arg0]] : i32] - // CHECK: %1 = quake.extract_ref %0[0] : (!quake.veq) -> !quake.ref - // CHECK: quake.h %1 : - // CHECK: %2 = arith.subi %arg0, %[[C1]] : i32 - // CHECK: %3 = arith.index_cast %2 : i32 to index - // CHECK: affine.for %arg1 = 0 to %3 { - // CHECK: %4 = arith.index_cast %arg1 : index to i32 - // CHECK: %5 = arith.addi %4, %[[C1]] : i32 - // CHECK: %6 = quake.extract_ref %0[%arg1] : (!quake.veq, index) -> !quake.ref - // CHECK: %7 = quake.extract_ref %0[%5] : (!quake.veq, i32) -> !quake.ref - // CHECK: quake.x [%6] %7 : (!quake.ref, !quake.ref) -> () - // CHECK: } - // CHECK: return - // CHECK: } - func.func @ghz(%arg0 : i32) { - // %size = arith.constant 3 : i32 - %c0 = arith.constant 0 : i32 - %one = arith.constant 1 : i32 - %q = quake.alloca !quake.veq[%arg0 : i32] - %q0 = quake.extract_ref %q[%c0] : (!quake.veq, i32) -> !quake.ref - quake.h %q0 : (!quake.ref) -> () - %size_m_1 = arith.subi %arg0, %one : i32 - %upper = arith.index_cast %size_m_1 : i32 to index - affine.for %i = 0 to %upper { - %i_int = arith.index_cast %i : index to i32 - %ip1 = arith.addi %i_int, %one : i32 - %qi = quake.extract_ref %q[%i] : (!quake.veq, index) -> !quake.ref - %qi1 = quake.extract_ref %q[%ip1] : (!quake.veq, i32) -> !quake.ref - quake.x [%qi] %qi1 : (!quake.ref, !quake.ref) -> () - } - return - } -} diff --git a/test/Quake/iqft.qke b/test/Quake/iqft.qke deleted file mode 100644 index 60adfbe5457..00000000000 --- a/test/Quake/iqft.qke +++ /dev/null @@ -1,138 +0,0 @@ -// ========================================================================== // -// 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. // -// ========================================================================== // - -// CUDA-Q code -// struct iqft { -// void operator()(cudaq::qreg q) __qpu__ { -// int N = q.size(); -// // Swap qubits -// for (int i = 0; i < N / 2; ++i) { -// swap(q[i], q[N - i - 1]); -// } - -// for (int i = 0; i < N - 1; ++i) { -// h(q[i]); -// int j = i + 1; -// for (int y = i; y >= 0; --y) { // for (int y = -i; y < 1; y++) -// const double theta = -M_PI / std::pow(2.0, j - y); -// cphase(theta, q[j], q[y]); -// } -// } - -// h(q[N - 1]); -// } -// }; - -// RUN: cudaq-opt %s --canonicalize | FileCheck %s - -// CHECK: #map = affine_map<(d0) -> (-d0)> -// CHECK: module { -// CHECK: func.func @iqft(%arg0: !quake.veq) { -// CHECK: %[[CF0:.*]] = arith.constant 2.000000e+00 : f64 -// CHECK: %[[CF1:.*]] = arith.constant -3.1415926535897931 : f64 -// CHECK: %[[CI1:.*]] = arith.constant 1 : index -// CHECK: %[[CI0:.*]] = arith.constant 0 : index -// CHECK: %[[C1:.*]] = arith.constant 1 : i32 -// CHECK: %[[C2:.*]] = arith.constant 2 : i32 -// CHECK: %c-1_i32 = arith.constant -1 : i32 -// CHECK: %0 = quake.veq_size %arg0 : (!quake.veq) -> i64 -// CHECK: %1 = arith.trunci %0 : i64 to i32 -// CHECK: %2 = arith.subi %1, %[[C1]] : i32 -// CHECK: %3 = arith.index_cast %2 : i32 to index -// CHECK: %4 = arith.divsi %1, %[[C2]] : i32 -// CHECK: %5 = arith.index_cast %4 : i32 to index -// CHECK: scf.for %arg1 = %[[CI0]] to %5 step %[[CI1]] { -// CHECK: %7 = arith.index_cast %arg1 : index to i32 -// CHECK: %8 = arith.subi %1, %7 : i32 -// CHECK: %9 = arith.subi %8, %[[C1]] : i32 -// CHECK: %10 = quake.extract_ref %arg0[%7] : (!quake.veq, i32) -> !quake.ref -// CHECK: %11 = quake.extract_ref %arg0[%9] : (!quake.veq, i32) -> !quake.ref -// CHECK: quake.swap %10, %11 : (!quake.ref, !quake.ref) -> () -// CHECK: } -// CHECK: affine.for %arg1 = 0 to %3 { -// CHECK: %7 = arith.index_cast %arg1 : index to i32 -// CHECK: %8 = quake.extract_ref %arg0[%7] : (!quake.veq, i32) -> !quake.ref -// CHECK: quake.h %8 : (!quake.ref) -> () -// CHECK: %9 = arith.addi %7, %[[C1]] : i32 -// CHECK: affine.for %arg2 = #map(%arg1) to 1 { -// CHECK: %10 = arith.index_cast %arg2 : index to i32 -// CHECK: %11 = arith.muli %10, %c-1_i32 : i32 -// CHECK: %12 = arith.subi %9, %11 : i32 -// CHECK: %13 = arith.sitofp %12 : i32 to f64 -// CHECK: %14 = math.powf %[[CF0]], %13 : f64 -// CHECK: %15 = arith.divf %[[CF1]], %14 : f64 -// CHECK: %16 = quake.extract_ref %arg0[%9] : (!quake.veq, i32) -> !quake.ref -// CHECK: %17 = quake.extract_ref %arg0[%11] : (!quake.veq, i32) -> !quake.ref -// CHECK: quake.r1 (%15) [%16] %17 : (f64, !quake.ref, !quake.ref) -> () -// CHECK: } -// CHECK: } -// CHECK: %6 = quake.extract_ref %arg0[%2] : (!quake.veq, i32) -> !quake.ref -// CHECK: quake.h %6 : (!quake.ref) -> () -// CHECK: return -// CHECK: } -// CHECK: } - - -#lb = affine_map<(d0) -> (-1*d0)> - -module { - func.func @iqft(%arg0 : !quake.veq) { - %c1 = arith.constant 1 : i32 - %c0 = arith.constant 0 : i32 - %c2 = arith.constant 2 : i32 - %cn1 = arith.constant -1 : i32 - %nn = quake.veq_size %arg0 : (!quake.veq) -> i64 - %n = arith.trunci %nn : i64 to i32 - %nm1 = arith.subi %n, %c1 : i32 - %nm1idx = arith.index_cast %nm1 : i32 to index - %upper = arith.divsi %n, %c2 : i32 - %upper_cast = arith.index_cast %upper : i32 to index - %lower = arith.index_cast %c0 : i32 to index - %c1idx = arith.index_cast %c1 : i32 to index - - scf.for %arg2 = %lower to %upper_cast step %c1idx { - %7 = arith.index_cast %arg2 : index to i32 - %9 = arith.subi %n, %7 : i32 - %10 = arith.subi %9, %c1 : i32 - %qi = quake.extract_ref %arg0 [%7] : (!quake.veq,i32) -> !quake.ref - %qi1 = quake.extract_ref %arg0 [%10] : (!quake.veq,i32) -> !quake.ref - quake.swap %qi, %qi1 : (!quake.ref, !quake.ref) -> () - } - - affine.for %arg3 = 0 to %nm1idx { - %11 = arith.index_cast %arg3 : index to i32 - %qi = quake.extract_ref %arg0[%11] : (!quake.veq, i32) -> !quake.ref - quake.h %qi : (!quake.ref) -> () - %13 = arith.addi %11, %c1 : i32 - %12 = memref.alloca() : memref - memref.store %13, %12[] : memref - - %lb = arith.muli %11, %cn1 : i32 - %lbidx = arith.index_cast %lb : i32 to index - affine.for %arg4 = #lb(%arg3) to %c1idx { - %14 = arith.index_cast %arg4 : index to i32 - %15 = arith.muli %14, %cn1 : i32 - %cst = arith.constant 3.1415926535897931 : f64 - %cst_3 = arith.constant -1.000000e+00 : f64 - %16 = arith.mulf %cst_3, %cst : f64 - %c2f = arith.sitofp %c2 : i32 to f64 - %jmy = arith.subi %13, %15 : i32 - %s2f = arith.sitofp %jmy : i32 to f64 - %denom = math.powf %c2f, %s2f : f64 - %24 = arith.divf %16, %denom : f64 - %qj = quake.extract_ref %arg0[%13] : (!quake.veq, i32) -> !quake.ref - %qy = quake.extract_ref %arg0[%15] : (!quake.veq, i32) -> !quake.ref - quake.r1 (%24)[%qj] %qy : (f64,!quake.ref,!quake.ref) -> () - } - } - %qnm1 = quake.extract_ref %arg0[%nm1] : (!quake.veq,i32) -> !quake.ref - quake.h %qnm1 : (!quake.ref) -> () - return - } -} - diff --git a/test/Translate/ghz.qke b/test/Translate/ghz.qke index da78b2ec130..498750ae071 100644 --- a/test/Translate/ghz.qke +++ b/test/Translate/ghz.qke @@ -9,48 +9,59 @@ // RUN: cudaq-opt %s --canonicalize --add-dealloc | cudaq-translate --convert-to=qir | FileCheck %s module { // CHECK: %[[VAL_0:.*]] = zext i32 -// CHECK: %[[VAL_1:.*]] to i64 +// CHECK-SAME: %[[VAL_1:.*]] to i64 // CHECK: %[[VAL_2:.*]] = tail call %[[VAL_3:.*]]* @__quantum__rt__qubit_allocate_array(i64 %[[VAL_0]]) // CHECK: %[[VAL_4:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_3]]* %[[VAL_2]], i64 0) // CHECK: %[[VAL_5:.*]] = bitcast i8* %[[VAL_4]] to %[[VAL_6:.*]]** // CHECK: %[[VAL_7:.*]] = load %[[VAL_6]]*, %[[VAL_6]]** %[[VAL_5]], align 8 // CHECK: tail call void @__quantum__qis__h(%[[VAL_6]]* %[[VAL_7]]) // CHECK: %[[VAL_8:.*]] = add i32 %[[VAL_1]], -1 -// CHECK: %[[VAL_9:.*]] = sext i32 %[[VAL_8]] to i64 -// CHECK: %[[VAL_10:.*]] = icmp sgt i32 %[[VAL_8]], 0 -// CHECK: br i1 %[[VAL_10]], label %[[VAL_11:.*]], label %[[VAL_12:[^,]*]] +// CHECK: %[[VAL_9:.*]] = icmp eq i32 %[[VAL_8]], 0 +// CHECK: br i1 %[[VAL_9]], label %[[VAL_10:.*]], label %[[VAL_11:.*]] +// CHECK: .lr.ph.preheader: +// CHECK-SAME: ; preds = %[[VAL_12:.*]] +// CHECK: %[[VAL_13:.*]] = zext i32 %[[VAL_8]] to i64 +// CHECK: br label %[[VAL_14:.*]] // CHECK: .lr.ph: -// CHECK-SAME: ; preds = %[[VAL_13:.*]], %[[VAL_11]] -// CHECK: %[[VAL_14:.*]] = phi i64 [ %[[VAL_15:.*]], %[[VAL_11]] ], [ 0, %[[VAL_13]] ] -// CHECK: %[[VAL_15]] = add nuw nsw i64 %[[VAL_14]], 1 -// CHECK: %[[VAL_16:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_3]]* %[[VAL_2]], i64 %[[VAL_14]]) -// CHECK: %[[VAL_17:.*]] = bitcast i8* %[[VAL_16]] to %[[VAL_6]]** -// CHECK: %[[VAL_18:.*]] = load %[[VAL_6]]*, %[[VAL_6]]** %[[VAL_17]], align 8 -// CHECK: %[[VAL_19:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_3]]* %[[VAL_2]], i64 %[[VAL_15]]) -// CHECK: %[[VAL_20:.*]] = bitcast i8* %[[VAL_19]] to %[[VAL_6]]** -// CHECK: %[[VAL_21:.*]] = load %[[VAL_6]]*, %[[VAL_6]]** %[[VAL_20]], align 8 -// CHECK: tail call void (i64, void (%Array*, %Qubit*)*, ...) @invokeWithControlQubits(i64 1, void (%Array*, %Qubit*)* nonnull @__quantum__qis__x__ctl, %Qubit* %[[VAL_18]], %Qubit* %[[VAL_21]]) -// CHECK: %[[VAL_22:.*]] = icmp eq i64 %[[VAL_15]], %[[VAL_9]] -// CHECK: br i1 %[[VAL_22]], label %[[VAL_12]], label %[[VAL_11]] +// CHECK-SAME: ; preds = %[[VAL_11]], %[[VAL_14]] +// CHECK: %[[VAL_15:.*]] = phi i64 [ 0, %[[VAL_11]] ], [ %[[VAL_16:.*]], %[[VAL_14]] ] +// CHECK: %[[VAL_17:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_3]]* %[[VAL_2]], i64 %[[VAL_15]]) +// CHECK: %[[VAL_18:.*]] = bitcast i8* %[[VAL_17]] to %[[VAL_6]]** +// CHECK: %[[VAL_19:.*]] = load %[[VAL_6]]*, %[[VAL_6]]** %[[VAL_18]], align 8 +// CHECK: %[[VAL_16]] = add nuw nsw i64 %[[VAL_15]], 1 +// CHECK: %[[VAL_20:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_3]]* %[[VAL_2]], i64 %[[VAL_16]]) +// CHECK: %[[VAL_21:.*]] = bitcast i8* %[[VAL_20]] to %[[VAL_6]]** +// CHECK: %[[VAL_22:.*]] = load %[[VAL_6]]*, %[[VAL_6]]** %[[VAL_21]], align 8 +// CHECK: tail call void (i64, void (%[[VAL_3]]*, %[[VAL_6]]*)*, ...) @invokeWithControlQubits(i64 1, void (%[[VAL_3]]*, %[[VAL_6]]*)* nonnull @__quantum__qis__x__ctl, %[[VAL_6]]* %[[VAL_19]], %[[VAL_6]]* %[[VAL_22]]) +// CHECK: %[[VAL_23:.*]] = icmp eq i64 %[[VAL_16]], %[[VAL_13]] +// CHECK: br i1 %[[VAL_23]], label %[[VAL_10]], label %[[VAL_14]] // CHECK: ._crit_edge: -// CHECK-SAME: ; preds = %[[VAL_11]], %[[VAL_13]] +// CHECK-SAME: ; preds = %[[VAL_14]], %[[VAL_12]] // CHECK: tail call void @__quantum__rt__qubit_release_array(%[[VAL_3]]* %[[VAL_2]]) // CHECK: ret void - func.func @ghz(%arg0 : i32) { - %c0 = arith.constant 0 : i32 - %one = arith.constant 1 : i32 - %q = quake.alloca !quake.veq[%arg0 : i32] - %q0 = quake.extract_ref %q [%c0] : (!quake.veq,i32) -> !quake.ref - quake.h %q0 : (!quake.ref) -> () - %size_m_1 = arith.subi %arg0, %one : i32 - %upper = arith.index_cast %size_m_1 : i32 to index - affine.for %i = 0 to %upper { - %i_int = arith.index_cast %i : index to i32 - %ip1 = arith.addi %i_int, %one : i32 - %qi = quake.extract_ref %q [%i] : (!quake.veq,index) -> !quake.ref - %qi1 = quake.extract_ref %q [%ip1] : (!quake.veq,i32) -> !quake.ref - quake.x [%qi] %qi1 : (!quake.ref,!quake.ref) -> () - } - return + + func.func @ghz(%arg0: i32){ + %c1_i32 = arith.constant 1 : i32 + %c0_i32 = arith.constant 0 : i32 + %0 = quake.alloca !quake.veq[%arg0 : i32] + %1 = quake.extract_ref %0[0] : (!quake.veq) -> !quake.ref + quake.h %1 : (!quake.ref) -> () + %2 = cc.loop while ((%arg1 = %c0_i32) -> (i32)) { + %4 = arith.subi %arg0, %c1_i32 : i32 + %5 = arith.cmpi ult, %arg1, %4 : i32 + cc.condition %5(%arg1 : i32) + } do { + ^bb0(%arg1: i32): + %4 = quake.extract_ref %0[%arg1] : (!quake.veq, i32) -> !quake.ref + %5 = arith.addi %arg1, %c1_i32 : i32 + %6 = quake.extract_ref %0[%5] : (!quake.veq, i32) -> !quake.ref + quake.x [%4] %6 : (!quake.ref, !quake.ref) -> () + cc.continue %arg1 : i32 + } step { + ^bb0(%arg1: i32): + %3 = arith.addi %arg1, %c1_i32 : i32 + cc.continue %3 : i32 } + return + } } diff --git a/tools/cudaq-quake/CMakeLists.txt b/tools/cudaq-quake/CMakeLists.txt index 094378da761..f5f2960ed61 100644 --- a/tools/cudaq-quake/CMakeLists.txt +++ b/tools/cudaq-quake/CMakeLists.txt @@ -27,7 +27,6 @@ target_link_libraries(cudaq-quake MLIRAffineDialect MLIRMemRefDialect - MLIRSCFDialect clangCodeGen clangFrontendTool From e882fff31a4da716a9433ca834c2ed4c16183768 Mon Sep 17 00:00:00 2001 From: Bruno Schmitt <7152025+boschmitt@users.noreply.github.com> Date: Mon, 9 Dec 2024 23:37:25 +0100 Subject: [PATCH 10/44] [nvq++] Removes MLIR's `affine` dialect (#2436) * [nvq++] Removes MLIR's `scf` dialect We don't use this dialect and its presence is a historical artifact. This change triggered the removal of two tests: * `test/Quake/ghz.qke` * `test/Quake/iqft.qke` Both tests are a reminder of a past when we had to write quantum kernels directly in MLIR because of a lack of frontend. Both no longer test aything useful. The commit modifies `test/Quake/canonical-2.qke`, which was only testing the canonicalization of `cc.scope` operations. The new form is removes the clutter, making the test more precise. `test/Translate/ghz.qke` had to be modified because it uses MLIR's `affined.for` and its conversion to LLVMDialect requires `scf.for`. Signed-off-by: boschmitt <7152025+boschmitt@users.noreply.github.com> * [nvq++] Remove MLIR's `affine` dialect. We don't use this dialect. The removal triggered the removel of one test: * `test/Quake/ccnot.qke` The test was not testing anything useful, only kernel inlining, which is already covered in other tests. Furthermore, the test is misleading because, contrary to what the kernel name might indicate, it is not implementing a `ccnot`. Signed-off-by: boschmitt <7152025+boschmitt@users.noreply.github.com> --------- Signed-off-by: boschmitt <7152025+boschmitt@users.noreply.github.com> Co-authored-by: Eric Schweitz --- include/cudaq/Frontend/nvqpp/ASTBridge.h | 1 - include/cudaq/Optimizer/InitAllDialects.h | 2 - include/cudaq/Optimizer/Transforms/Passes.h | 1 - lib/Optimizer/CodeGen/CMakeLists.txt | 1 - lib/Optimizer/CodeGen/ConvertToQIR.cpp | 2 - lib/Optimizer/Transforms/PassDetails.h | 1 - lib/Optimizer/Transforms/QuakeAddMetadata.cpp | 1 - runtime/cudaq/builder/kernel_builder.cpp | 2 - test/Quake/ccnot.qke | 52 ------------------- tools/cudaq-quake/CMakeLists.txt | 1 - 10 files changed, 64 deletions(-) delete mode 100644 test/Quake/ccnot.qke diff --git a/include/cudaq/Frontend/nvqpp/ASTBridge.h b/include/cudaq/Frontend/nvqpp/ASTBridge.h index baf4518fcef..3b257dc51ec 100644 --- a/include/cudaq/Frontend/nvqpp/ASTBridge.h +++ b/include/cudaq/Frontend/nvqpp/ASTBridge.h @@ -20,7 +20,6 @@ #include "clang/Frontend/FrontendAction.h" #include "clang/Rewrite/Core/Rewriter.h" #include "llvm/ADT/ScopedHashTable.h" -#include "mlir/Dialect/Affine/IR/AffineOps.h" #include "mlir/Dialect/LLVMIR/LLVMDialect.h" #include "mlir/Dialect/LLVMIR/LLVMTypes.h" #include "mlir/IR/Builders.h" diff --git a/include/cudaq/Optimizer/InitAllDialects.h b/include/cudaq/Optimizer/InitAllDialects.h index c6df70d88f4..0748b5866a9 100644 --- a/include/cudaq/Optimizer/InitAllDialects.h +++ b/include/cudaq/Optimizer/InitAllDialects.h @@ -10,7 +10,6 @@ #include "cudaq/Optimizer/Dialect/CC/CCDialect.h" #include "cudaq/Optimizer/Dialect/Quake/QuakeDialect.h" -#include "mlir/Dialect/Affine/IR/AffineOps.h" #include "mlir/Dialect/Arith/IR/Arith.h" #include "mlir/Dialect/Complex/IR/Complex.h" #include "mlir/Dialect/ControlFlow/IR/ControlFlow.h" @@ -26,7 +25,6 @@ inline void registerAllDialects(mlir::DialectRegistry ®istry) { // clang-format off registry.insert< // MLIR dialects - mlir::AffineDialect, mlir::arith::ArithDialect, mlir::cf::ControlFlowDialect, mlir::complex::ComplexDialect, diff --git a/include/cudaq/Optimizer/Transforms/Passes.h b/include/cudaq/Optimizer/Transforms/Passes.h index 77f461bec2a..d699e25355e 100644 --- a/include/cudaq/Optimizer/Transforms/Passes.h +++ b/include/cudaq/Optimizer/Transforms/Passes.h @@ -42,7 +42,6 @@ std::unique_ptr createQuakeSynthesizer(std::string_view, const void *, std::size_t startingArgIdx = 0, bool sameAddressSpace = false); -std::unique_ptr createRaiseToAffinePass(); std::unique_ptr createUnwindLoweringPass(); std::unique_ptr diff --git a/lib/Optimizer/CodeGen/CMakeLists.txt b/lib/Optimizer/CodeGen/CMakeLists.txt index 56951cd07a7..5c056e0e11d 100644 --- a/lib/Optimizer/CodeGen/CMakeLists.txt +++ b/lib/Optimizer/CodeGen/CMakeLists.txt @@ -54,7 +54,6 @@ add_cudaq_library(OptCodeGen MLIRTransforms # Conversions - MLIRAffineToStandard MLIRArithToLLVM MLIRComplexToLibm MLIRComplexToLLVM diff --git a/lib/Optimizer/CodeGen/ConvertToQIR.cpp b/lib/Optimizer/CodeGen/ConvertToQIR.cpp index e23691af943..738ee66ea1a 100644 --- a/lib/Optimizer/CodeGen/ConvertToQIR.cpp +++ b/lib/Optimizer/CodeGen/ConvertToQIR.cpp @@ -20,7 +20,6 @@ #include "llvm/ADT/Hashing.h" #include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/FormatVariadic.h" -#include "mlir/Conversion/AffineToStandard/AffineToStandard.h" #include "mlir/Conversion/ArithToLLVM/ArithToLLVM.h" #include "mlir/Conversion/ComplexToLLVM/ComplexToLLVM.h" #include "mlir/Conversion/ComplexToLibm/ComplexToLibm.h" @@ -166,7 +165,6 @@ class ConvertToQIR : public cudaq::opt::impl::ConvertToQIRBase { populateComplexToLibmConversionPatterns(patterns, 1); populateComplexToLLVMConversionPatterns(typeConverter, patterns); - populateAffineToStdConversionPatterns(patterns); arith::populateCeilFloorDivExpandOpsPatterns(patterns); arith::populateArithToLLVMConversionPatterns(typeConverter, patterns); populateMathToLLVMConversionPatterns(typeConverter, patterns); diff --git a/lib/Optimizer/Transforms/PassDetails.h b/lib/Optimizer/Transforms/PassDetails.h index 95d6b69476c..36b45aa69ea 100644 --- a/lib/Optimizer/Transforms/PassDetails.h +++ b/lib/Optimizer/Transforms/PassDetails.h @@ -11,7 +11,6 @@ #include "cudaq/Optimizer/Dialect/CC/CCDialect.h" #include "cudaq/Optimizer/Dialect/Quake/QuakeDialect.h" #include "cudaq/Optimizer/Dialect/Quake/QuakeOps.h" -#include "mlir/Dialect/Affine/IR/AffineOps.h" #include "mlir/Dialect/Complex/IR/Complex.h" #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" #include "mlir/Dialect/Func/IR/FuncOps.h" diff --git a/lib/Optimizer/Transforms/QuakeAddMetadata.cpp b/lib/Optimizer/Transforms/QuakeAddMetadata.cpp index 6f174463f3d..2c8dbc284ba 100644 --- a/lib/Optimizer/Transforms/QuakeAddMetadata.cpp +++ b/lib/Optimizer/Transforms/QuakeAddMetadata.cpp @@ -12,7 +12,6 @@ #include "cudaq/Optimizer/Transforms/Passes.h" #include "cudaq/Todo.h" #include "llvm/Support/Debug.h" -#include "mlir/Dialect/Affine/IR/AffineOps.h" #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/LLVMIR/LLVMDialect.h" diff --git a/runtime/cudaq/builder/kernel_builder.cpp b/runtime/cudaq/builder/kernel_builder.cpp index be415c6620a..6870ebef2a4 100644 --- a/runtime/cudaq/builder/kernel_builder.cpp +++ b/runtime/cudaq/builder/kernel_builder.cpp @@ -17,8 +17,6 @@ #include "cudaq/Optimizer/Dialect/Quake/QuakeDialect.h" #include "cudaq/Optimizer/Dialect/Quake/QuakeOps.h" #include "cudaq/Optimizer/Transforms/Passes.h" -#include "mlir/Dialect/Affine/IR/AffineOps.h" -#include "mlir/Dialect/Affine/Passes.h" #include "mlir/Dialect/LLVMIR/LLVMDialect.h" #include "mlir/Dialect/Math/IR/Math.h" #include "mlir/ExecutionEngine/ExecutionEngine.h" diff --git a/test/Quake/ccnot.qke b/test/Quake/ccnot.qke deleted file mode 100644 index a74536dfd68..00000000000 --- a/test/Quake/ccnot.qke +++ /dev/null @@ -1,52 +0,0 @@ -// ========================================================================== // -// 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. // -// ========================================================================== // - -// RUN: cudaq-opt %s --inline --canonicalize | FileCheck %s - -module { - - // CHECK-LABEL: func.func @apply_x( - // CHECK-SAME: %[[arg0:.*]]: !quake.ref) { - // CHECK: quake.x %[[arg0]] : - // CHECK: return - // CHECK: } - - // CHECK-LABEL: func.func @ccnot() { - // CHECK: %[[a0:.*]] = quake.alloca !quake.veq<3> - // CHECK: affine.for %[[arg0:.*]] = 0 to 3 { - // CHECK: %[[a2:.*]] = quake.extract_ref %[[a0]][%[[arg0]]] : (!quake.veq<3>, index) -> !quake.ref - // CHECK: quake.x %[[a2]] : - // CHECK: } - // CHECK: %[[a1:.*]] = quake.extract_ref %[[a0]][1] : (!quake.veq<3>) -> !quake.ref - // CHECK: quake.x %[[a1]] : - // CHECK: return - // CHECK: } - - func.func @apply_x(%q : !quake.ref) { - quake.x %q : (!quake.ref) -> () - return - } - - func.func @ccnot() { - %c_3 = arith.constant 3 : i32 - %c_0 = arith.constant 0 : i32 - %c_1 = arith.constant 1 : i32 - %c_2 = arith.constant 2 : i32 - %qubits = quake.alloca !quake.veq [ %c_3 : i32 ] - %c_3_idx = arith.index_cast %c_3 : i32 to index - affine.for %i = 0 to %c_3_idx { - %q0 = quake.extract_ref %qubits [%i] : (!quake.veq, index) -> !quake.ref - quake.x %q0 : (!quake.ref) -> () - } - - %q1 = quake.extract_ref %qubits [%c_1] : (!quake.veq, i32) -> !quake.ref - func.call @apply_x(%q1) : (!quake.ref) -> () - - return - } -} diff --git a/tools/cudaq-quake/CMakeLists.txt b/tools/cudaq-quake/CMakeLists.txt index f5f2960ed61..277df39c221 100644 --- a/tools/cudaq-quake/CMakeLists.txt +++ b/tools/cudaq-quake/CMakeLists.txt @@ -25,7 +25,6 @@ target_link_libraries(cudaq-quake MLIRLLVMCommonConversion MLIRLLVMToLLVMIRTranslation - MLIRAffineDialect MLIRMemRefDialect clangCodeGen From aa760d7da5545fa5fd529a31cf110c7aae5ac603 Mon Sep 17 00:00:00 2001 From: Bruno Schmitt <7152025+boschmitt@users.noreply.github.com> Date: Tue, 10 Dec 2024 02:47:11 +0100 Subject: [PATCH 11/44] [cmake] Remove unnecessary install commands. (#2440) In the case of the libraries, there is no need to install them twice. I opted to remove the install command that contains `EXPORT` because we don't use/install these export files, and so far they don't seem to be missed. (Even if we want to install these exports, the current way of doing this seems overly complicated, we can generate a single runtime export with everything that was built---something to be done on a later commit.) In the case of headers files: their installation is already covered by the installation of the `cudaq` directory and all `*.h` within. Signed-off-by: boschmitt <7152025+boschmitt@users.noreply.github.com> Co-authored-by: Eric Schweitz --- runtime/cudaq/algorithms/CMakeLists.txt | 3 +-- runtime/cudaq/algorithms/gradients/CMakeLists.txt | 11 ----------- .../algorithms/optimizers/ensmallen/CMakeLists.txt | 2 -- .../cudaq/algorithms/optimizers/nlopt/CMakeLists.txt | 2 -- runtime/cudaq/platform/default/CMakeLists.txt | 1 - runtime/cudaq/platform/default/rest/CMakeLists.txt | 1 - runtime/cudaq/platform/fermioniq/CMakeLists.txt | 3 +-- runtime/cudaq/platform/mqpu/CMakeLists.txt | 2 -- runtime/cudaq/platform/mqpu/remote/CMakeLists.txt | 1 - runtime/cudaq/platform/orca/CMakeLists.txt | 1 - runtime/cudaq/platform/quera/CMakeLists.txt | 1 - 11 files changed, 2 insertions(+), 26 deletions(-) delete mode 100644 runtime/cudaq/algorithms/gradients/CMakeLists.txt diff --git a/runtime/cudaq/algorithms/CMakeLists.txt b/runtime/cudaq/algorithms/CMakeLists.txt index 34c265dafb0..2e7c67f5361 100644 --- a/runtime/cudaq/algorithms/CMakeLists.txt +++ b/runtime/cudaq/algorithms/CMakeLists.txt @@ -6,5 +6,4 @@ # the terms of the Apache License 2.0 which accompanies this distribution. # # ============================================================================ # -add_subdirectory(gradients) -add_subdirectory(optimizers) \ No newline at end of file +add_subdirectory(optimizers) diff --git a/runtime/cudaq/algorithms/gradients/CMakeLists.txt b/runtime/cudaq/algorithms/gradients/CMakeLists.txt deleted file mode 100644 index 78a1174af15..00000000000 --- a/runtime/cudaq/algorithms/gradients/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -# ============================================================================ # -# 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. # -# ============================================================================ # - -install (FILES central_difference.h DESTINATION include/cudaq/gradients/) -install (FILES parameter_shift.h DESTINATION include/cudaq/gradients/) -install (FILES forward_difference.h DESTINATION include/cudaq/gradients/) diff --git a/runtime/cudaq/algorithms/optimizers/ensmallen/CMakeLists.txt b/runtime/cudaq/algorithms/optimizers/ensmallen/CMakeLists.txt index a4107b24007..14b47bb316e 100644 --- a/runtime/cudaq/algorithms/optimizers/ensmallen/CMakeLists.txt +++ b/runtime/cudaq/algorithms/optimizers/ensmallen/CMakeLists.txt @@ -38,8 +38,6 @@ if (NOT APPLE) target_link_options(${LIBRARY_NAME} PRIVATE -Wl,--exclude-libs,ALL) endif() -install (FILES ensmallen.h DESTINATION include/cudaq/algorithms/optimizers/) - install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-ensmallen-targets DESTINATION lib) install(EXPORT cudaq-ensmallen-targets FILE CUDAQEnsmallenTargets.cmake diff --git a/runtime/cudaq/algorithms/optimizers/nlopt/CMakeLists.txt b/runtime/cudaq/algorithms/optimizers/nlopt/CMakeLists.txt index e7490947ab0..29fa224dca3 100644 --- a/runtime/cudaq/algorithms/optimizers/nlopt/CMakeLists.txt +++ b/runtime/cudaq/algorithms/optimizers/nlopt/CMakeLists.txt @@ -16,8 +16,6 @@ target_include_directories(${LIBRARY_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/runtime) target_include_directories(${LIBRARY_NAME} SYSTEM PRIVATE nlopt-src/ nlopt-src/src/api) target_link_libraries(${LIBRARY_NAME} PRIVATE nlopt) -install (FILES nlopt.h DESTINATION include/cudaq/algorithms/optimizers/) - install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-nlopt-targets DESTINATION lib) install(EXPORT cudaq-nlopt-targets diff --git a/runtime/cudaq/platform/default/CMakeLists.txt b/runtime/cudaq/platform/default/CMakeLists.txt index 98bfd4be746..a4a65eaa184 100644 --- a/runtime/cudaq/platform/default/CMakeLists.txt +++ b/runtime/cudaq/platform/default/CMakeLists.txt @@ -29,7 +29,6 @@ target_link_libraries(${LIBRARY_NAME} PRIVATE fmt::fmt-header-only cudaq CUDAQTargetConfigUtil) -install(TARGETS ${LIBRARY_NAME} DESTINATION lib) install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-platform-default-targets DESTINATION lib) diff --git a/runtime/cudaq/platform/default/rest/CMakeLists.txt b/runtime/cudaq/platform/default/rest/CMakeLists.txt index 7d636f302f6..7046be51ded 100644 --- a/runtime/cudaq/platform/default/rest/CMakeLists.txt +++ b/runtime/cudaq/platform/default/rest/CMakeLists.txt @@ -40,4 +40,3 @@ target_link_libraries(cudaq-rest-qpu install(TARGETS cudaq-rest-qpu DESTINATION lib) -install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-rest-qpu-targets DESTINATION lib) diff --git a/runtime/cudaq/platform/fermioniq/CMakeLists.txt b/runtime/cudaq/platform/fermioniq/CMakeLists.txt index 74bc34ad547..1d667b404c5 100644 --- a/runtime/cudaq/platform/fermioniq/CMakeLists.txt +++ b/runtime/cudaq/platform/fermioniq/CMakeLists.txt @@ -28,7 +28,6 @@ target_link_libraries(${LIBRARY_NAME} cudaq-platform-default) install(TARGETS ${LIBRARY_NAME} DESTINATION lib) -install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-fermioniq-qpu-targets DESTINATION lib) # install(EXPORT cudaq-fermioniq-qpu-targets # FILE CUDAQQPUFermioniqTargets.cmake @@ -37,4 +36,4 @@ install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-fermioniq-qpu-targets DESTINATION l add_target_config(fermioniq) -add_subdirectory(helpers) \ No newline at end of file +add_subdirectory(helpers) diff --git a/runtime/cudaq/platform/mqpu/CMakeLists.txt b/runtime/cudaq/platform/mqpu/CMakeLists.txt index ee1bfcbcaca..5969d342b2b 100644 --- a/runtime/cudaq/platform/mqpu/CMakeLists.txt +++ b/runtime/cudaq/platform/mqpu/CMakeLists.txt @@ -44,7 +44,5 @@ if (CUDA_FOUND AND CUSTATEVEC_ROOT) endif() install(TARGETS ${LIBRARY_NAME} DESTINATION lib) -install(TARGETS ${LIBRARY_NAME} - EXPORT cudaq-platform-mqpu-targets DESTINATION lib) add_target_config(remote-mqpu) add_target_config(nvqc) diff --git a/runtime/cudaq/platform/mqpu/remote/CMakeLists.txt b/runtime/cudaq/platform/mqpu/remote/CMakeLists.txt index 1cdcc8ff5e5..622b1d2388a 100644 --- a/runtime/cudaq/platform/mqpu/remote/CMakeLists.txt +++ b/runtime/cudaq/platform/mqpu/remote/CMakeLists.txt @@ -18,4 +18,3 @@ target_link_libraries(cudaq-remote-simulator-qpu PUBLIC install(TARGETS cudaq-remote-simulator-qpu DESTINATION lib) -install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-remote-simulator-qpu-targets DESTINATION lib) diff --git a/runtime/cudaq/platform/orca/CMakeLists.txt b/runtime/cudaq/platform/orca/CMakeLists.txt index 3610b902a39..e8bd88e2995 100644 --- a/runtime/cudaq/platform/orca/CMakeLists.txt +++ b/runtime/cudaq/platform/orca/CMakeLists.txt @@ -34,6 +34,5 @@ target_link_libraries(${LIBRARY_NAME} cudaq-platform-default) install(TARGETS ${LIBRARY_NAME} DESTINATION lib) -install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-orca-qpu-targets DESTINATION lib) add_target_config(orca) diff --git a/runtime/cudaq/platform/quera/CMakeLists.txt b/runtime/cudaq/platform/quera/CMakeLists.txt index 11e5b2f3e40..c59d72cab85 100644 --- a/runtime/cudaq/platform/quera/CMakeLists.txt +++ b/runtime/cudaq/platform/quera/CMakeLists.txt @@ -40,6 +40,5 @@ target_link_libraries(${LIBRARY_NAME} cudaq-platform-default) install(TARGETS ${LIBRARY_NAME} DESTINATION lib) -install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-quera-qpu-targets DESTINATION lib) add_target_config(quera) From 95a4da75141a18220344e24db10d26712062ff75 Mon Sep 17 00:00:00 2001 From: Thien Nguyen <58006629+1tnguyen@users.noreply.github.com> Date: Tue, 10 Dec 2024 19:01:01 +1100 Subject: [PATCH 12/44] Add number of trajectories to `observe_options` struct (#2456) * Add number of trajectories to observe option Signed-off-by: Thien Nguyen * Code format fix Signed-off-by: Thien Nguyen * Fix a compiler warning: we just need , no need to construct the whole observe_options Signed-off-by: Thien Nguyen * Update runtime/common/ExecutionContext.h Co-authored-by: Eric Schweitz Signed-off-by: Thien Nguyen <58006629+1tnguyen@users.noreply.github.com> * Update runtime/cudaq/algorithms/observe.h Co-authored-by: Eric Schweitz Signed-off-by: Thien Nguyen <58006629+1tnguyen@users.noreply.github.com> * Update runtime/cudaq/algorithms/observe.h Co-authored-by: Eric Schweitz Signed-off-by: Thien Nguyen <58006629+1tnguyen@users.noreply.github.com> * Code format fix Signed-off-by: Thien Nguyen --------- Signed-off-by: Thien Nguyen Signed-off-by: Thien Nguyen <58006629+1tnguyen@users.noreply.github.com> Co-authored-by: Eric Schweitz --- runtime/common/ExecutionContext.h | 4 ++++ runtime/cudaq/algorithms/observe.h | 20 ++++++++++++++++---- runtime/cudaq/algorithms/vqe.h | 3 +-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/runtime/common/ExecutionContext.h b/runtime/common/ExecutionContext.h index 933aa31b6ea..8fca5fdee2a 100644 --- a/runtime/common/ExecutionContext.h +++ b/runtime/common/ExecutionContext.h @@ -109,6 +109,10 @@ class ExecutionContext { /// `sample_result`. std::vector invocationResultBuffer; + /// @brief The number of trajectories to be used for an expectation + /// calculation on simulation backends that support trajectory simulation. + std::optional numberTrajectories; + /// @brief The Constructor, takes the name of the context /// @param n The name of the context ExecutionContext(const std::string n) : name(n) {} diff --git a/runtime/cudaq/algorithms/observe.h b/runtime/cudaq/algorithms/observe.h index 7b7254871e6..6e5abe392b4 100644 --- a/runtime/cudaq/algorithms/observe.h +++ b/runtime/cudaq/algorithms/observe.h @@ -60,9 +60,14 @@ concept ObserveCallValid = /// \p shots is the number of shots to run for the given kernel. The default of /// -1 means direct calculations for simulation backends. \p noise is the noise /// model to use for the observe operation. +/// \p num_trajectories is the optional number of trajectories to be used when +/// computing the expectation values in the presence of noise. This parameter is +/// only applied to simulation backends that support noisy +/// simulation of trajectories. struct observe_options { int shots = -1; cudaq::noise_model noise; + std::optional num_trajectories; }; namespace details { @@ -75,14 +80,17 @@ std::optional runObservation(KernelFunctor &&k, cudaq::spin_op &h, quantum_platform &platform, int shots, const std::string &kernelName, std::size_t qpu_id = 0, details::future *futureResult = nullptr, - std::size_t batchIteration = 0, - std::size_t totalBatchIters = 0) { + std::size_t batchIteration = 0, std::size_t totalBatchIters = 0, + std::optional numTrajectories = {}) { auto ctx = std::make_unique("observe", shots); ctx->kernelName = kernelName; ctx->spin = &h; if (shots > 0) ctx->shots = shots; + if (numTrajectories.has_value()) + ctx->numberTrajectories = *numTrajectories; + ctx->batchIteration = batchIteration; ctx->totalIterations = totalBatchIters; @@ -469,7 +477,10 @@ observe_result observe(const observe_options &options, QuantumKernel &&kernel, cudaq::invokeKernel(std::forward(kernel), std::forward(args)...); }, - H, platform, shots, kernelName) + H, platform, shots, kernelName, /*qpu_id=*/0, + /*futureResult=*/nullptr, + /*batchIteration=*/0, + /*totalBatchIters=*/0, options.num_trajectories) .value(); platform.reset_noise(); @@ -690,7 +701,8 @@ std::vector observe(cudaq::observe_options &options, [&kernel, &singleIterParameters...]() mutable { kernel(std::forward(singleIterParameters)...); }, - H, platform, shots, kernelName, qpuId, nullptr, counter, N) + H, platform, shots, kernelName, qpuId, nullptr, counter, N, + options.num_trajectories) .value(); return ret; }; diff --git a/runtime/cudaq/algorithms/vqe.h b/runtime/cudaq/algorithms/vqe.h index 9df1f7b8481..030a0b3e4ed 100644 --- a/runtime/cudaq/algorithms/vqe.h +++ b/runtime/cudaq/algorithms/vqe.h @@ -185,8 +185,7 @@ optimization_result vqe(std::size_t shots, QuantumKernel &&kernel, return optimizer.optimize(n_params, [&](const std::vector &x, std::vector &grad_vec) { - observe_options options{static_cast(shots), cudaq::noise_model{}}; - double e = cudaq::observe(options, kernel, H, x, args...); + double e = cudaq::observe(shots, kernel, H, x, args...); return e; }); } From 917146db3594eaa844214523df2c593456688241 Mon Sep 17 00:00:00 2001 From: Eric Schweitz Date: Tue, 10 Dec 2024 05:22:45 -0800 Subject: [PATCH 13/44] Fixes a bug with C++ argument handling in the front end. (#2459) This one particular path was not making sure the arguments were relaxed when they needed to be resulting in a type error. As a bonus, corrects the bernstein_vazirani example. Signed-off-by: Eric Schweitz --- docs/sphinx/applications/cpp/bernstein_vazirani.cpp | 2 +- lib/Frontend/nvqpp/ConvertExpr.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/applications/cpp/bernstein_vazirani.cpp b/docs/sphinx/applications/cpp/bernstein_vazirani.cpp index 5371d0d576c..7ec7741eed6 100644 --- a/docs/sphinx/applications/cpp/bernstein_vazirani.cpp +++ b/docs/sphinx/applications/cpp/bernstein_vazirani.cpp @@ -34,7 +34,7 @@ std::vector random_bits(int seed) { template struct oracle { - auto operator()(std::vector bitvector, cudaq::qview<> qs, + auto operator()(std::vector &bitvector, cudaq::qview<> qs, cudaq::qubit &aux) __qpu__ { for (size_t i = 0; i < nrOfBits; i++) { diff --git a/lib/Frontend/nvqpp/ConvertExpr.cpp b/lib/Frontend/nvqpp/ConvertExpr.cpp index 780fbe6acd4..5de2e9cd2fc 100644 --- a/lib/Frontend/nvqpp/ConvertExpr.cpp +++ b/lib/Frontend/nvqpp/ConvertExpr.cpp @@ -2310,8 +2310,10 @@ bool QuakeBridgeVisitor::VisitCXXOperatorCallExpr( // replace it with an indirect call to the func::ConstantOp. auto indirect = popValue(); auto funcTy = cast(indirect.getType()); + auto convertedArgs = + convertKernelArgs(builder, loc, 0, args, funcTy.getInputs()); auto call = builder.create( - loc, funcTy.getResults(), indirect, args); + loc, funcTy.getResults(), indirect, convertedArgs); if (call.getResults().empty()) return true; return pushValue(call.getResult(0)); From d87720e234504da3bc84d2152b61351027d8eeab Mon Sep 17 00:00:00 2001 From: Victory Omole Date: Tue, 10 Dec 2024 10:44:48 -0600 Subject: [PATCH 14/44] `Cudaq <> Superstaq` integration (#2423) * poc infleqtion server files Signed-off-by: Bharath * c++ existing tests links for server Signed-off-by: Bharath * add new directory Signed-off-by: Bharath * some debug prints in c++ tests Signed-off-by: Bharath * draft mock qpu sketch Signed-off-by: Bharath * add license header Signed-off-by: Bharath * Add infleqtion python test with mockserver * Ready to built testing wheel * Revert testIonQ * More reversions * DCO Remediation Commit for vtomole I, vtomole , hereby add my Signed-off-by to this commit: bc10bec9c9e36e009bf30cd4985badef449f1b53 I, vtomole , hereby add my Signed-off-by to this commit: 6f444ddfc9a3942a1c12b4a9fbb7f0a80342d308 I, vtomole , hereby add my Signed-off-by to this commit: 64463c1b1261a05cb22a988406b138a7728f8a96 I, vtomole , hereby add my Signed-off-by to this commit: ad36074592e0358af7c535fb3a3f8a61ecc911e2 Signed-off-by: vtomole * Run clang formatter Signed-off-by: vtomole * Format with yapf Signed-off-by: vtomole * Run yapf with Google style Signed-off-by: vtomole * add newline at end of some new files Signed-off-by: Bharath * remove debug bell test Signed-off-by: Bharath * Update infleqtion.yml Signed-off-by: vtomole * Fix a typo in cmake * Use unused port '62447' * Add the new sub-directory to cmake Signed-off-by: Pradnya Khalate * some review responses + updates Signed-off-by: Bharath * fix: commit format Signed-off-by: Bharath * Test for all gates, modifiers * Check observe call * Skip the `observe` tests on C++ since the mock server returns hard-coded values * Python `observe` test works but the expectation value may be out-of-range due to low number of shots * `cu3` is not (yet) supported * Updated C++ tests * Add state prep pass in lowering pipeline * Fix symbolic links Signed-off-by: Pradnya Khalate * update backend name Signed-off-by: Bharath * remove unused shots assignment Signed-off-by: Bharath * override polling seconds and error for canceled job Signed-off-by: Bharath * Additional tests for multiple qvectors and multiple measurements Signed-off-by: Pradnya Khalate --------- Signed-off-by: Bharath Signed-off-by: vtomole Signed-off-by: Pradnya Khalate Co-authored-by: Bharath Co-authored-by: Bharath Thotakura <113555655+bharat-thotakura@users.noreply.github.com> Co-authored-by: Pradnya Khalate --- lib/Optimizer/CodeGen/TranslateToOpenQASM.cpp | 1 + .../Transforms/DecompositionPatterns.cpp | 35 ++ python/tests/backends/test_Infleqtion.py | 159 +++++++++ .../default/rest/helpers/CMakeLists.txt | 5 +- .../rest/helpers/infleqtion/CMakeLists.txt | 17 + .../infleqtion/InfleqtionServerHelper.cpp | 314 ++++++++++++++++++ .../rest/helpers/infleqtion/infleqtion.yml | 36 ++ targettests/execution/bug_qubit.cpp | 1 + targettests/execution/callable_kernel_arg.cpp | 1 + targettests/execution/cudaq_observe-cpp17.cpp | 1 + targettests/execution/cudaq_observe.cpp | 1 + .../execution/custom_operation_adj.cpp | 9 +- .../execution/custom_operation_basic.cpp | 9 +- .../execution/custom_operation_ctrl.cpp | 12 +- targettests/execution/graph_coloring-1.cpp | 1 + targettests/execution/graph_coloring.cpp | 1 + targettests/execution/if_jit.cpp | 1 + targettests/execution/int8_t.cpp | 1 + targettests/execution/int8_t_free_func.cpp | 1 + targettests/execution/load_value.cpp | 1 + targettests/execution/state_preparation.cpp | 1 + targettests/execution/sudoku_2x2-1.cpp | 1 + .../execution/sudoku_2x2-bit_names.cpp | 1 + targettests/execution/sudoku_2x2-reg_name.cpp | 1 + targettests/execution/sudoku_2x2.cpp | 1 + targettests/execution/swap_gate.cpp | 1 + targettests/execution/variable_size_qreg.cpp | 1 + targettests/infleqtion/bug_qubit.cpp | 1 + .../infleqtion/callable_kernel_arg.cpp | 1 + .../infleqtion/cudaq_observe-cpp17.cpp | 1 + targettests/infleqtion/cudaq_observe.cpp | 1 + .../infleqtion/custom_operation_adj.cpp | 1 + .../infleqtion/custom_operation_basic.cpp | 1 + targettests/infleqtion/graph_coloring-1.cpp | 1 + targettests/infleqtion/graph_coloring.cpp | 1 + targettests/infleqtion/if_jit.cpp | 1 + targettests/infleqtion/load_value.cpp | 1 + targettests/infleqtion/state_preparation.cpp | 1 + targettests/infleqtion/sudoku_2x2-1.cpp | 1 + .../infleqtion/sudoku_2x2-bit_names.cpp | 1 + .../infleqtion/sudoku_2x2-reg_name.cpp | 1 + targettests/infleqtion/sudoku_2x2.cpp | 1 + targettests/infleqtion/swap_gate.cpp | 1 + targettests/infleqtion/test-int8_t.cpp | 1 + .../infleqtion/test-int8_t_free_func.cpp | 1 + targettests/infleqtion/variable_size_qreg.cpp | 1 + targettests/lit.cfg.py | 2 +- unittests/backends/CMakeLists.txt | 1 + unittests/backends/infleqtion/CMakeLists.txt | 28 ++ .../InfleqtionStartServerAndTest.sh.in | 43 +++ .../backends/infleqtion/InfleqtionTester.cpp | 97 ++++++ utils/mock_qpu/__init__.py | 1 + utils/mock_qpu/infleqtion/__init__.py | 95 ++++++ 53 files changed, 884 insertions(+), 16 deletions(-) create mode 100644 python/tests/backends/test_Infleqtion.py create mode 100644 runtime/cudaq/platform/default/rest/helpers/infleqtion/CMakeLists.txt create mode 100644 runtime/cudaq/platform/default/rest/helpers/infleqtion/InfleqtionServerHelper.cpp create mode 100644 runtime/cudaq/platform/default/rest/helpers/infleqtion/infleqtion.yml create mode 120000 targettests/infleqtion/bug_qubit.cpp create mode 120000 targettests/infleqtion/callable_kernel_arg.cpp create mode 120000 targettests/infleqtion/cudaq_observe-cpp17.cpp create mode 120000 targettests/infleqtion/cudaq_observe.cpp create mode 120000 targettests/infleqtion/custom_operation_adj.cpp create mode 120000 targettests/infleqtion/custom_operation_basic.cpp create mode 120000 targettests/infleqtion/graph_coloring-1.cpp create mode 120000 targettests/infleqtion/graph_coloring.cpp create mode 120000 targettests/infleqtion/if_jit.cpp create mode 120000 targettests/infleqtion/load_value.cpp create mode 120000 targettests/infleqtion/state_preparation.cpp create mode 120000 targettests/infleqtion/sudoku_2x2-1.cpp create mode 120000 targettests/infleqtion/sudoku_2x2-bit_names.cpp create mode 120000 targettests/infleqtion/sudoku_2x2-reg_name.cpp create mode 120000 targettests/infleqtion/sudoku_2x2.cpp create mode 120000 targettests/infleqtion/swap_gate.cpp create mode 120000 targettests/infleqtion/test-int8_t.cpp create mode 120000 targettests/infleqtion/test-int8_t_free_func.cpp create mode 120000 targettests/infleqtion/variable_size_qreg.cpp create mode 100644 unittests/backends/infleqtion/CMakeLists.txt create mode 100644 unittests/backends/infleqtion/InfleqtionStartServerAndTest.sh.in create mode 100644 unittests/backends/infleqtion/InfleqtionTester.cpp create mode 100644 utils/mock_qpu/infleqtion/__init__.py diff --git a/lib/Optimizer/CodeGen/TranslateToOpenQASM.cpp b/lib/Optimizer/CodeGen/TranslateToOpenQASM.cpp index c5b98642c7a..528492e6f87 100644 --- a/lib/Optimizer/CodeGen/TranslateToOpenQASM.cpp +++ b/lib/Optimizer/CodeGen/TranslateToOpenQASM.cpp @@ -41,6 +41,7 @@ static LogicalResult translateOperatorName(quake::OperatorInterface optor, .Case("ry", "cry") .Case("rz", "crz") .Case("swap", "cswap") + .Case("u3", "cu3") .Default(qkeName); } else if (optor.getControls().size() == 2) { name = StringSwitch(qkeName).Case("x", "ccx").Default(""); diff --git a/lib/Optimizer/Transforms/DecompositionPatterns.cpp b/lib/Optimizer/Transforms/DecompositionPatterns.cpp index b776c8461c1..40680f6c2a9 100644 --- a/lib/Optimizer/Transforms/DecompositionPatterns.cpp +++ b/lib/Optimizer/Transforms/DecompositionPatterns.cpp @@ -480,6 +480,40 @@ struct R1ToU3 : public OpRewritePattern { } }; +// quake.r1 (θ) target +// ───────────────────────────────── +// quake.r1(-θ) target +struct R1AdjToR1 : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + void initialize() { setDebugName("R1AdjToR1"); } + + LogicalResult matchAndRewrite(quake::R1Op op, + PatternRewriter &rewriter) const override { + if (!op.getControls().empty()) + return failure(); + if (!op.isAdj()) + return failure(); + + // Op info + Location loc = op->getLoc(); + Value target = op.getTarget(); + Value angle = op.getParameter(); + angle = rewriter.create(loc, angle); + + // Necessary/Helpful constants + SmallVector noControls; + SmallVector parameters = {angle}; + + QuakeOperatorCreator qRewriter(rewriter); + qRewriter.create(loc, parameters, noControls, target); + + qRewriter.selectWiresAndReplaceUses(op, target); + rewriter.eraseOp(op); + return success(); + } +}; + // quake.swap a, b // ─────────────────────────────────── // quake.cnot b, a; @@ -1575,6 +1609,7 @@ void cudaq::populateWithAllDecompositionPatterns(RewritePatternSet &patterns) { R1ToPhasedRx, R1ToRz, R1ToU3, + R1AdjToR1, // RxOp patterns CRxToCX, RxToPhasedRx, diff --git a/python/tests/backends/test_Infleqtion.py b/python/tests/backends/test_Infleqtion.py new file mode 100644 index 00000000000..24c4a11d241 --- /dev/null +++ b/python/tests/backends/test_Infleqtion.py @@ -0,0 +1,159 @@ +# ============================================================================ # +# 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. # +# ============================================================================ # + +import cudaq, pytest, os +from cudaq import spin +import numpy as np + +## NOTE: Comment the following line which skips these tests in order to run in +# local dev environment after setting the API key +## NOTE: Superstaq costs apply +pytestmark = pytest.mark.skip("Infleqtion / Superstaq API key required") + + +@pytest.fixture(scope="session", autouse=True) +def do_something(): + cudaq.set_target("infleqtion") + yield "Running the tests." + cudaq.__clearKernelRegistries() + cudaq.reset_target() + + +def assert_close(got) -> bool: + return got < -1.5 and got > -1.9 + + +def test_simple_kernel(): + + @cudaq.kernel + def bell(): + qubits = cudaq.qvector(2) + h(qubits[0]) + x.ctrl(qubits[0], qubits[1]) + mz(qubits) + + counts = cudaq.sample(bell) + assert len(counts) == 2 + assert "00" in counts + assert "11" in counts + + +def test_all_gates(): + + @cudaq.kernel + def all_gates(): + q = cudaq.qubit() + h(q) + x(q) + y(q) + z(q) + r1(np.pi, q) + rx(np.pi, q) + ry(np.pi, q) + rz(np.pi, q) + s(q) + t(q) + u3(0.0, np.pi / 2, np.pi, q) + mz(q) + + qvec = cudaq.qvector(2) + x(qvec[0]) + swap(qvec[0], qvec[1]) + mz(qvec) + + ## control modifiers + qubits = cudaq.qvector(2) + h.ctrl(qubits[0], qubits[1]) + x.ctrl(qubits[1], qubits[0]) + y.ctrl(qubits[0], qubits[1]) + z.ctrl(qubits[1], qubits[0]) + r1.ctrl(np.pi / 2, qubits[0], qubits[1]) + rx.ctrl(np.pi / 4, qubits[1], qubits[0]) + ry.ctrl(np.pi / 8, qubits[0], qubits[1]) + rz.ctrl(np.pi, qubits[1], qubits[0]) + s.ctrl(qubits[0], qubits[1]) + t.ctrl(qubits[1], qubits[0]) + # u3.ctrl(0.0, np.pi / 2, np.pi, qubits[0], qubits[1]) + mz(qubits) + + qreg = cudaq.qvector(3) + x(qreg[0]) + x(qreg[1]) + swap.ctrl(qreg[0], qreg[1], qreg[2]) + mz(qreg) + + ## adjoint modifiers + r = cudaq.qubit() + r1.adj(np.pi, r) + rx.adj(np.pi / 2, r) + ry.adj(np.pi / 4, r) + rz.adj(np.pi / 8, r) + s.adj(r) + t.adj(r) + mz(r) + + # Test here is that this runs + cudaq.sample(all_gates).dump() + + +def test_multiple_qvector(): + + @cudaq.kernel + def kernel(): + qubits = cudaq.qvector(2) + ancilla = cudaq.qvector(2) + x(qubits) + h(ancilla) + mz(ancilla) + + # Test here is that this runs + cudaq.sample(kernel).dump() + + +def test_multiple_measure(): + + @cudaq.kernel + def kernel(): + q = cudaq.qvector(4) + a = cudaq.qvector(2) + h(q[0]) + cx(q[0], q[1]) + h(a) + cx(q[1], a[0]) + mz(q[1]) + mz(q[0]) + mz(a) + + # Test here is that this runs + cudaq.sample(kernel).dump() + + +def test_observe(): + cudaq.set_random_seed(13) + + @cudaq.kernel + def ansatz(theta: float): + qreg = cudaq.qvector(2) + x(qreg[0]) + ry(theta, qreg[1]) + x.ctrl(qreg[1], qreg[0]) + + # Define its spin Hamiltonian. + hamiltonian = 5.907 - 2.1433 * spin.x(0) * spin.x(1) - 2.1433 * spin.y( + 0) * spin.y(1) + .21829 * spin.z(0) - 6.125 * spin.z(1) + + res = cudaq.observe(ansatz, hamiltonian, .59, shots_count=2048) + ## Need to adjust expectation value range + # assert assert_close(res.expectation()) + print(res.expectation()) + + +# leave for gdb debugging +if __name__ == "__main__": + loc = os.path.abspath(__file__) + pytest.main([loc, "-s"]) diff --git a/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt b/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt index 123434de52c..ff602ad246e 100644 --- a/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt +++ b/runtime/cudaq/platform/default/rest/helpers/CMakeLists.txt @@ -6,10 +6,11 @@ # the terms of the Apache License 2.0 which accompanies this distribution. # # ============================================================================ # add_subdirectory(anyon) -add_subdirectory(oqc) +add_subdirectory(infleqtion) add_subdirectory(ionq) -add_subdirectory(quantinuum) add_subdirectory(iqm) +add_subdirectory(oqc) +add_subdirectory(quantinuum) if (AWSSDK_ROOT) add_subdirectory(braket) endif() diff --git a/runtime/cudaq/platform/default/rest/helpers/infleqtion/CMakeLists.txt b/runtime/cudaq/platform/default/rest/helpers/infleqtion/CMakeLists.txt new file mode 100644 index 00000000000..b4ef5e4bbc4 --- /dev/null +++ b/runtime/cudaq/platform/default/rest/helpers/infleqtion/CMakeLists.txt @@ -0,0 +1,17 @@ +# ============================================================================ # +# 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. # +# ============================================================================ # +target_sources(cudaq-rest-qpu PRIVATE InfleqtionServerHelper.cpp) +add_target_config(infleqtion) + +add_library(cudaq-serverhelper-infleqtion SHARED InfleqtionServerHelper.cpp ) +target_link_libraries(cudaq-serverhelper-infleqtion + PUBLIC + cudaq-common + fmt::fmt-header-only +) +install(TARGETS cudaq-serverhelper-infleqtion DESTINATION lib) diff --git a/runtime/cudaq/platform/default/rest/helpers/infleqtion/InfleqtionServerHelper.cpp b/runtime/cudaq/platform/default/rest/helpers/infleqtion/InfleqtionServerHelper.cpp new file mode 100644 index 00000000000..7a157f6014d --- /dev/null +++ b/runtime/cudaq/platform/default/rest/helpers/infleqtion/InfleqtionServerHelper.cpp @@ -0,0 +1,314 @@ +/******************************************************************************* + * 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. * + ******************************************************************************/ + +#include "common/Logger.h" +#include "common/RestClient.h" +#include "common/ServerHelper.h" +#include "cudaq/Support/Version.h" +#include "cudaq/utils/cudaq_utils.h" +#include +#include +#include +#include +#include +#include +#include +#include +using json = nlohmann::json; + +bool isValidTarget(const std::string &input) { + static const std::unordered_set validTargets = { + "cq_sqale_qpu", "cq_sqale_simulator"}; + return validTargets.find(input) != validTargets.end(); +} + +std::string formatOpenQasm(const std::string &input_string) { + std::string escaped_string = + std::regex_replace(input_string, std::regex("\\\\"), "\\\\"); + escaped_string = std::regex_replace(escaped_string, std::regex("\""), "\\\""); + escaped_string = std::regex_replace(escaped_string, std::regex("\n"), "\\n"); + + std::ostringstream json_stream; + json_stream << "[\"" << escaped_string << "\"]"; + return json_stream.str(); +} + +namespace cudaq { + +/// @brief The InfleqtionServerHelper class extends the ServerHelper class to +/// handle interactions with the Infleqtion server for submitting and retrieving +/// quantum computation jobs. +class InfleqtionServerHelper : public ServerHelper { + static constexpr const char *DEFAULT_URL = "https://superstaq.infleqtion.com"; + static constexpr const char *DEFAULT_VERSION = "v0.2.0"; + +public: + /// @brief Returns the name of the server helper. + const std::string name() const override { return "infleqtion"; } + + /// @brief Returns the headers for the server requests. + RestHeaders getHeaders() override; + + /// @brief Initializes the server helper with the provided backend + /// configuration. + void initialize(BackendConfig config) override; + + /// @brief Creates a quantum computation job using the provided kernel + /// executions and returns the corresponding payload. + ServerJobPayload + createJob(std::vector &circuitCodes) override; + + /// @brief Extracts the job ID from the server's response to a job submission. + std::string extractJobId(ServerMessage &postResponse) override; + + /// @brief Constructs the URL for retrieving a job based on the server's + /// response to a job submission. + std::string constructGetJobPath(ServerMessage &postResponse) override; + + /// @brief Constructs the URL for retrieving a job based on a job ID. + std::string constructGetJobPath(std::string &jobId) override; + + /// @brief Checks if a job is done based on the server's response to a job + /// retrieval request. + bool jobIsDone(ServerMessage &getJobResponse) override; + + /// @brief Processes the server's response to a job retrieval request and + /// maps the results back to sample results. + cudaq::sample_result processResults(ServerMessage &getJobResponse, + std::string &jobId) override; + + /// @brief Override the polling interval method + std::chrono::microseconds + nextResultPollingInterval(ServerMessage &postResponse) override { + return std::chrono::seconds(1); + } + +private: + /// @brief RestClient used for HTTP requests. + RestClient client; + + /// @brief Helper method to retrieve the value of an environment variable. + std::string getEnvVar(const std::string &key, const std::string &defaultVal, + const bool isRequired) const; + + /// @brief Helper function to get value from config or return a default value. + std::string getValueOrDefault(const BackendConfig &config, + const std::string &key, + const std::string &defaultValue) const; + + /// @brief Helper method to check if a key exists in the configuration. + bool keyExists(const std::string &key) const; +}; + +// Initialize the Infleqtion server helper with a given backend configuration +void InfleqtionServerHelper::initialize(BackendConfig config) { + cudaq::info("Initializing Infleqtion Backend."); + + // Move the passed config into the member variable backendConfig + backendConfig = config; + + // Set default URL and version if not provided + backendConfig["url"] = getValueOrDefault(config, "url", DEFAULT_URL); + backendConfig["version"] = + getValueOrDefault(config, "version", DEFAULT_VERSION); + backendConfig["user_agent"] = getValueOrDefault( + config, "user_agent", "cudaq/" + std::string(cudaq::getVersion())); + backendConfig["machine"] = + getValueOrDefault(config, "machine", "cq_sqale_simulator"); + backendConfig["method"] = config["method"]; + + // Validate machine name client-side + if (!isValidTarget(backendConfig["machine"])) { + throw std::runtime_error("Invalid Infleqtion machine specified."); + } + + // Determine if token is required + bool isTokenRequired = [&]() { + auto it = config.find("emulate"); + return !(it != config.end() && it->second == "true"); + }(); + + // Get the API token from environment variable if not provided + if (!keyExists("token")) { + backendConfig["token"] = + getEnvVar("SUPERSTAQ_API_KEY", "0", isTokenRequired); + } + + // Construct the API job path + backendConfig["job_path"] = + backendConfig["url"] + '/' + backendConfig["version"] + "/jobs"; + + // Set shots if provided + if (config.find("shots") != config.end()) + this->setShots(std::stoul(config["shots"])); + + // Parse common parameters + parseConfigForCommonParams(config); +} + +// Get the headers for the API requests +RestHeaders InfleqtionServerHelper::getHeaders() { + // Check if the necessary keys exist in the configuration + if (!keyExists("token") || !keyExists("user_agent")) + throw std::runtime_error( + "Required keys 'token' or 'user_agent' not found in backendConfig."); + + // Construct the headers + RestHeaders headers; + headers["Authorization"] = backendConfig.at("token"); + headers["Content-Type"] = "application/json"; + headers["User-Agent"] = backendConfig.at("user_agent"); + + // Return the headers + return headers; +} + +// Create a job for the Infleqtion quantum computer +ServerJobPayload +InfleqtionServerHelper::createJob(std::vector &circuitCodes) { + // Check if the necessary keys exist in the configuration + if (!keyExists("machine") || !keyExists("job_path")) + throw std::runtime_error("Key doesn't exist in backendConfig."); + + auto &circuitCode = circuitCodes[0]; + + // Construct the job message + ServerMessage job; + job["qasm_strs"] = formatOpenQasm( + circuitCode.code); // Assuming code is in OpenQASM 2.0 format + job["target"] = backendConfig.at("machine"); + job["shots"] = shots; + + if (backendConfig.count("method")) + job["method"] = backendConfig.at("method"); + + // Store output names and reorder indices if necessary + OutputNamesType outputNamesMap; + for (auto &item : circuitCode.output_names.items()) { + std::size_t idx = std::stoul(item.key()); + ResultInfoType info; + info.qubitNum = idx; + info.registerName = item.value(); + outputNamesMap[idx] = info; + } + outputNames[circuitCode.name] = outputNamesMap; + reorderIdx[circuitCode.name] = circuitCode.mapping_reorder_idx; + + // Prepare headers + RestHeaders headers = getHeaders(); + + // Return a tuple containing the job path, headers, and the job message + auto ret = std::make_tuple(backendConfig.at("job_path"), headers, + std::vector{job}); + return ret; +} + +// Extract the job ID from the server's response +std::string InfleqtionServerHelper::extractJobId(ServerMessage &postResponse) { + // Check if the response contains 'job_ids' key + if (!postResponse.contains("job_ids") || !postResponse["job_ids"].is_array()) + throw std::runtime_error("ServerMessage doesn't contain 'job_ids' key."); + + // Extract job ID from list response + std::string jobId = postResponse["job_ids"][0]; + // Return the job ID + return jobId; +} + +// Construct the path to get a job based on the server's response +std::string +InfleqtionServerHelper::constructGetJobPath(ServerMessage &postResponse) { + // Extract job ID + std::string jobId = extractJobId(postResponse); + + // Construct the path + return constructGetJobPath(jobId); +} + +// Construct the path to get a job based on job ID +std::string InfleqtionServerHelper::constructGetJobPath(std::string &jobId) { + if (!keyExists("url") || !keyExists("version")) + throw std::runtime_error( + "Keys 'url' or 'version' don't exist in backendConfig."); + + // Construct the job path + std::string jobPath = backendConfig.at("url") + '/' + + backendConfig.at("version") + "/job/" + jobId; + return jobPath; +} + +// Check if a job is done +bool InfleqtionServerHelper::jobIsDone(ServerMessage &getJobResponse) { + // Check if the response contains 'status' key + if (!getJobResponse.contains("status")) + throw std::runtime_error("ServerMessage is missing job 'status' key."); + + std::string status = getJobResponse["status"]; + if (status == "Canceled") { + throw std::runtime_error("The submitted job was canceled."); + } + // Returns whether the job is done + return status == "Done"; +} + +// Process the results from a job +cudaq::sample_result +InfleqtionServerHelper::processResults(ServerMessage &getJobResponse, + std::string &jobId) { + // Check if the response contains 'samples' key + if (!getJobResponse.contains("samples")) + throw std::runtime_error("Samples not found in the job results."); + + // Extract samples + auto samplesJson = getJobResponse["samples"]; + cudaq::CountsDictionary counts; + for (auto &item : samplesJson.items()) { + std::string bitstring = item.key(); + std::size_t count = item.value(); + counts[bitstring] = count; + } + // Create an ExecutionResult + cudaq::ExecutionResult execResult{counts}; + + // Return the sample_result + return cudaq::sample_result{execResult}; +} + +// Helper method to retrieve an environment variable +std::string InfleqtionServerHelper::getEnvVar(const std::string &key, + const std::string &defaultVal, + const bool isRequired) const { + const char *env_var = std::getenv(key.c_str()); + if (env_var == nullptr) { + if (isRequired) + throw std::runtime_error("Environment variable " + key + + " is required but not set."); + else + return defaultVal; + } + return std::string(env_var); +} + +// Helper function to get a value from config or return a default +std::string InfleqtionServerHelper::getValueOrDefault( + const BackendConfig &config, const std::string &key, + const std::string &defaultValue) const { + auto it = config.find(key); + return (it != config.end()) ? it->second : defaultValue; +} + +// Check if a key exists in the backend configuration +bool InfleqtionServerHelper::keyExists(const std::string &key) const { + return backendConfig.find(key) != backendConfig.end(); +} + +} // namespace cudaq + +// Register the Infleqtion server helper in the CUDA-Q server helper factory +CUDAQ_REGISTER_TYPE(cudaq::ServerHelper, cudaq::InfleqtionServerHelper, + infleqtion) diff --git a/runtime/cudaq/platform/default/rest/helpers/infleqtion/infleqtion.yml b/runtime/cudaq/platform/default/rest/helpers/infleqtion/infleqtion.yml new file mode 100644 index 00000000000..5370880e276 --- /dev/null +++ b/runtime/cudaq/platform/default/rest/helpers/infleqtion/infleqtion.yml @@ -0,0 +1,36 @@ +# ============================================================================ # +# 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. # +# ============================================================================ # + +name: "infleqtion" +description: "CUDA-Q target for Infleqtion." + +config: + # Tell DefaultQuantumPlatform what QPU subtype to use + platform-qpu: remote_rest + # Add the rest-qpu library to the link list + link-libs: ["-lcudaq-rest-qpu"] + # Tell NVQ++ to generate glue code to set the target backend name + gen-target-backend: true + # Define the lowering pipeline + platform-lowering-config: "func.func(const-prop-complex,canonicalize,cse,lift-array-alloc),globalize-array-values,func.func(state-prep),unitary-synthesis,canonicalize,apply-op-specialization,aggressive-early-inlining,unrolling-pipeline,func.func(lower-to-cfg),canonicalize,func.func(multicontrol-decomposition),decomposition{enable-patterns=SToR1,TToR1,SwapToCX,CCZToCX,CR1ToCX,CRyToCX,CRxToCX,R1AdjToR1,RxAdjToRx,RyAdjToRy,RzAdjToRz},func.func(memtoreg{quantum=0}),symbol-dce" + # Tell the rest-qpu that we are generating OpenQASM 2.0. + codegen-emission: qasm2 + # Library mode is only for simulators, physical backends must turn this off + library-mode: false + +target-arguments: + - key: machine + required: false + type: string + platform-arg: machine + help-string: "Specify the Infleqtion backend to run on." + - key: method + required: false + type: string + platform-arg: method + help-string: "Specify the circuit execution type, either: dry-run (ideal simulation) or noise-sim (noisy-simulation)." diff --git a/targettests/execution/bug_qubit.cpp b/targettests/execution/bug_qubit.cpp index 29ef77ad33f..d33409b9c81 100644 --- a/targettests/execution/bug_qubit.cpp +++ b/targettests/execution/bug_qubit.cpp @@ -10,6 +10,7 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s diff --git a/targettests/execution/callable_kernel_arg.cpp b/targettests/execution/callable_kernel_arg.cpp index 4842a253de4..7ffa47ebc98 100644 --- a/targettests/execution/callable_kernel_arg.cpp +++ b/targettests/execution/callable_kernel_arg.cpp @@ -8,6 +8,7 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s diff --git a/targettests/execution/cudaq_observe-cpp17.cpp b/targettests/execution/cudaq_observe-cpp17.cpp index b4fd29d195e..c8015602564 100644 --- a/targettests/execution/cudaq_observe-cpp17.cpp +++ b/targettests/execution/cudaq_observe-cpp17.cpp @@ -8,6 +8,7 @@ // REQUIRES: c++17 // clang-format off +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // 2 different IQM machines for 2 different topologies // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s diff --git a/targettests/execution/cudaq_observe.cpp b/targettests/execution/cudaq_observe.cpp index edba980cab5..4db6b8c644a 100644 --- a/targettests/execution/cudaq_observe.cpp +++ b/targettests/execution/cudaq_observe.cpp @@ -8,6 +8,7 @@ // REQUIRES: c++20 // clang-format off +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // 2 different IQM machines for 2 different topologies // RUN: nvq++ --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s diff --git a/targettests/execution/custom_operation_adj.cpp b/targettests/execution/custom_operation_adj.cpp index 3de4646cac4..16dfefc2182 100644 --- a/targettests/execution/custom_operation_adj.cpp +++ b/targettests/execution/custom_operation_adj.cpp @@ -8,11 +8,12 @@ // clang-format off // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // clang-format on diff --git a/targettests/execution/custom_operation_basic.cpp b/targettests/execution/custom_operation_basic.cpp index 05e6fbe15ae..9ba007c8b79 100644 --- a/targettests/execution/custom_operation_basic.cpp +++ b/targettests/execution/custom_operation_basic.cpp @@ -8,11 +8,12 @@ // clang-format off // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // clang-format on diff --git a/targettests/execution/custom_operation_ctrl.cpp b/targettests/execution/custom_operation_ctrl.cpp index 5119cc5099e..079ce196815 100644 --- a/targettests/execution/custom_operation_ctrl.cpp +++ b/targettests/execution/custom_operation_ctrl.cpp @@ -6,12 +6,14 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ +// clang-format off // RUN: nvq++ %cpp_std --enable-mlir %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target iqm --emulate --iqm-machine Apollo %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s +// clang-format on #include diff --git a/targettests/execution/graph_coloring-1.cpp b/targettests/execution/graph_coloring-1.cpp index baa89670c29..3257c9a9d3f 100644 --- a/targettests/execution/graph_coloring-1.cpp +++ b/targettests/execution/graph_coloring-1.cpp @@ -8,6 +8,7 @@ // REQUIRES: c++20 // clang-format off +// RUN: nvq++ %s -o %t --target infleqtion --emulate && %t | FileCheck %s // RUN: nvq++ %s -o %t --target quantinuum --emulate && %t | FileCheck %s // RUN: if $braket_avail; then nvq++ %s -o %t --target braket --emulate && %t | FileCheck %s; fi // clang-format on diff --git a/targettests/execution/graph_coloring.cpp b/targettests/execution/graph_coloring.cpp index f6a98d7bc5c..fd11d40f425 100644 --- a/targettests/execution/graph_coloring.cpp +++ b/targettests/execution/graph_coloring.cpp @@ -8,6 +8,7 @@ // REQUIRES: c++20 // clang-format off +// RUN: nvq++ %s -o %t --target infleqtion --emulate && %t | FileCheck %s // RUN: nvq++ %s -o %t --target quantinuum --emulate && %t | FileCheck %s // RUN: if $braket_avail; then nvq++ %s -o %t --target braket --emulate && %t | FileCheck %s; fi // clang-format on diff --git a/targettests/execution/if_jit.cpp b/targettests/execution/if_jit.cpp index b076a33ab26..ee820f3cdbd 100644 --- a/targettests/execution/if_jit.cpp +++ b/targettests/execution/if_jit.cpp @@ -10,6 +10,7 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s diff --git a/targettests/execution/int8_t.cpp b/targettests/execution/int8_t.cpp index 38e0319bf5b..2ff858d5ebf 100644 --- a/targettests/execution/int8_t.cpp +++ b/targettests/execution/int8_t.cpp @@ -8,6 +8,7 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s diff --git a/targettests/execution/int8_t_free_func.cpp b/targettests/execution/int8_t_free_func.cpp index cec6a2439ec..5df3f812498 100644 --- a/targettests/execution/int8_t_free_func.cpp +++ b/targettests/execution/int8_t_free_func.cpp @@ -8,6 +8,7 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s diff --git a/targettests/execution/load_value.cpp b/targettests/execution/load_value.cpp index 2c453910ac8..f4296931d4c 100644 --- a/targettests/execution/load_value.cpp +++ b/targettests/execution/load_value.cpp @@ -8,6 +8,7 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s diff --git a/targettests/execution/state_preparation.cpp b/targettests/execution/state_preparation.cpp index 9eae1ae35f6..c1e74889f12 100644 --- a/targettests/execution/state_preparation.cpp +++ b/targettests/execution/state_preparation.cpp @@ -10,6 +10,7 @@ // RUN: nvq++ %cpp_std --enable-mlir %s -o %t && %t | FileCheck %s // Quantum emulators +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // 2 different IQM machines for 2 different topologies diff --git a/targettests/execution/sudoku_2x2-1.cpp b/targettests/execution/sudoku_2x2-1.cpp index da170e1316c..339f6352a04 100644 --- a/targettests/execution/sudoku_2x2-1.cpp +++ b/targettests/execution/sudoku_2x2-1.cpp @@ -9,6 +9,7 @@ // REQUIRES: c++20 // clang-format off // RUN: nvq++ --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s diff --git a/targettests/execution/sudoku_2x2-bit_names.cpp b/targettests/execution/sudoku_2x2-bit_names.cpp index 601815bc479..740265a964b 100644 --- a/targettests/execution/sudoku_2x2-bit_names.cpp +++ b/targettests/execution/sudoku_2x2-bit_names.cpp @@ -9,6 +9,7 @@ // REQUIRES: c++20 // clang-format off // RUN: nvq++ --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s diff --git a/targettests/execution/sudoku_2x2-reg_name.cpp b/targettests/execution/sudoku_2x2-reg_name.cpp index 113a44d5409..ff29a69a268 100644 --- a/targettests/execution/sudoku_2x2-reg_name.cpp +++ b/targettests/execution/sudoku_2x2-reg_name.cpp @@ -9,6 +9,7 @@ // REQUIRES: c++20 // clang-format off // RUN: nvq++ --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s diff --git a/targettests/execution/sudoku_2x2.cpp b/targettests/execution/sudoku_2x2.cpp index 962651af24c..34a117d8e51 100644 --- a/targettests/execution/sudoku_2x2.cpp +++ b/targettests/execution/sudoku_2x2.cpp @@ -9,6 +9,7 @@ // REQUIRES: c++20 // clang-format off // RUN: nvq++ --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s diff --git a/targettests/execution/swap_gate.cpp b/targettests/execution/swap_gate.cpp index 2026f75ee28..4cbab8facf3 100644 --- a/targettests/execution/swap_gate.cpp +++ b/targettests/execution/swap_gate.cpp @@ -8,6 +8,7 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s diff --git a/targettests/execution/variable_size_qreg.cpp b/targettests/execution/variable_size_qreg.cpp index 3d53fca0c25..dfabe164987 100644 --- a/targettests/execution/variable_size_qreg.cpp +++ b/targettests/execution/variable_size_qreg.cpp @@ -8,6 +8,7 @@ // clang-format off // RUN: nvq++ %cpp_std --target anyon --emulate %s -o %t && %t | FileCheck %s +// RUN: nvq++ %cpp_std --target infleqtion --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target ionq --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s diff --git a/targettests/infleqtion/bug_qubit.cpp b/targettests/infleqtion/bug_qubit.cpp new file mode 120000 index 00000000000..375dd9e56e0 --- /dev/null +++ b/targettests/infleqtion/bug_qubit.cpp @@ -0,0 +1 @@ +../execution/bug_qubit.cpp \ No newline at end of file diff --git a/targettests/infleqtion/callable_kernel_arg.cpp b/targettests/infleqtion/callable_kernel_arg.cpp new file mode 120000 index 00000000000..fe1ee7fe77b --- /dev/null +++ b/targettests/infleqtion/callable_kernel_arg.cpp @@ -0,0 +1 @@ +../execution/callable_kernel_arg.cpp \ No newline at end of file diff --git a/targettests/infleqtion/cudaq_observe-cpp17.cpp b/targettests/infleqtion/cudaq_observe-cpp17.cpp new file mode 120000 index 00000000000..f59c756d1d0 --- /dev/null +++ b/targettests/infleqtion/cudaq_observe-cpp17.cpp @@ -0,0 +1 @@ +../execution/cudaq_observe-cpp17.cpp \ No newline at end of file diff --git a/targettests/infleqtion/cudaq_observe.cpp b/targettests/infleqtion/cudaq_observe.cpp new file mode 120000 index 00000000000..df437da453a --- /dev/null +++ b/targettests/infleqtion/cudaq_observe.cpp @@ -0,0 +1 @@ +../execution/cudaq_observe.cpp \ No newline at end of file diff --git a/targettests/infleqtion/custom_operation_adj.cpp b/targettests/infleqtion/custom_operation_adj.cpp new file mode 120000 index 00000000000..fd31ccab349 --- /dev/null +++ b/targettests/infleqtion/custom_operation_adj.cpp @@ -0,0 +1 @@ +../execution/custom_operation_adj.cpp \ No newline at end of file diff --git a/targettests/infleqtion/custom_operation_basic.cpp b/targettests/infleqtion/custom_operation_basic.cpp new file mode 120000 index 00000000000..17b469fc6f7 --- /dev/null +++ b/targettests/infleqtion/custom_operation_basic.cpp @@ -0,0 +1 @@ +../execution/custom_operation_basic.cpp \ No newline at end of file diff --git a/targettests/infleqtion/graph_coloring-1.cpp b/targettests/infleqtion/graph_coloring-1.cpp new file mode 120000 index 00000000000..101c19fe2bb --- /dev/null +++ b/targettests/infleqtion/graph_coloring-1.cpp @@ -0,0 +1 @@ +../execution/graph_coloring-1.cpp \ No newline at end of file diff --git a/targettests/infleqtion/graph_coloring.cpp b/targettests/infleqtion/graph_coloring.cpp new file mode 120000 index 00000000000..c9e81401b77 --- /dev/null +++ b/targettests/infleqtion/graph_coloring.cpp @@ -0,0 +1 @@ +../execution/graph_coloring.cpp \ No newline at end of file diff --git a/targettests/infleqtion/if_jit.cpp b/targettests/infleqtion/if_jit.cpp new file mode 120000 index 00000000000..74ef9c8ee55 --- /dev/null +++ b/targettests/infleqtion/if_jit.cpp @@ -0,0 +1 @@ +../execution/if_jit.cpp \ No newline at end of file diff --git a/targettests/infleqtion/load_value.cpp b/targettests/infleqtion/load_value.cpp new file mode 120000 index 00000000000..f4588e8c7ba --- /dev/null +++ b/targettests/infleqtion/load_value.cpp @@ -0,0 +1 @@ +../execution/load_value.cpp \ No newline at end of file diff --git a/targettests/infleqtion/state_preparation.cpp b/targettests/infleqtion/state_preparation.cpp new file mode 120000 index 00000000000..6ca60382929 --- /dev/null +++ b/targettests/infleqtion/state_preparation.cpp @@ -0,0 +1 @@ +../execution/state_preparation.cpp \ No newline at end of file diff --git a/targettests/infleqtion/sudoku_2x2-1.cpp b/targettests/infleqtion/sudoku_2x2-1.cpp new file mode 120000 index 00000000000..09aa81848d4 --- /dev/null +++ b/targettests/infleqtion/sudoku_2x2-1.cpp @@ -0,0 +1 @@ +../execution/sudoku_2x2-1.cpp \ No newline at end of file diff --git a/targettests/infleqtion/sudoku_2x2-bit_names.cpp b/targettests/infleqtion/sudoku_2x2-bit_names.cpp new file mode 120000 index 00000000000..15e2b47ae2e --- /dev/null +++ b/targettests/infleqtion/sudoku_2x2-bit_names.cpp @@ -0,0 +1 @@ +../execution/sudoku_2x2-bit_names.cpp \ No newline at end of file diff --git a/targettests/infleqtion/sudoku_2x2-reg_name.cpp b/targettests/infleqtion/sudoku_2x2-reg_name.cpp new file mode 120000 index 00000000000..367a2098f43 --- /dev/null +++ b/targettests/infleqtion/sudoku_2x2-reg_name.cpp @@ -0,0 +1 @@ +../execution/sudoku_2x2-reg_name.cpp \ No newline at end of file diff --git a/targettests/infleqtion/sudoku_2x2.cpp b/targettests/infleqtion/sudoku_2x2.cpp new file mode 120000 index 00000000000..23748412f1f --- /dev/null +++ b/targettests/infleqtion/sudoku_2x2.cpp @@ -0,0 +1 @@ +../execution/sudoku_2x2.cpp \ No newline at end of file diff --git a/targettests/infleqtion/swap_gate.cpp b/targettests/infleqtion/swap_gate.cpp new file mode 120000 index 00000000000..2b441fc25d8 --- /dev/null +++ b/targettests/infleqtion/swap_gate.cpp @@ -0,0 +1 @@ +../execution/swap_gate.cpp \ No newline at end of file diff --git a/targettests/infleqtion/test-int8_t.cpp b/targettests/infleqtion/test-int8_t.cpp new file mode 120000 index 00000000000..0f314155980 --- /dev/null +++ b/targettests/infleqtion/test-int8_t.cpp @@ -0,0 +1 @@ +../execution/int8_t.cpp \ No newline at end of file diff --git a/targettests/infleqtion/test-int8_t_free_func.cpp b/targettests/infleqtion/test-int8_t_free_func.cpp new file mode 120000 index 00000000000..bd446321b63 --- /dev/null +++ b/targettests/infleqtion/test-int8_t_free_func.cpp @@ -0,0 +1 @@ +../execution/int8_t_free_func.cpp \ No newline at end of file diff --git a/targettests/infleqtion/variable_size_qreg.cpp b/targettests/infleqtion/variable_size_qreg.cpp new file mode 120000 index 00000000000..e2a775d5c56 --- /dev/null +++ b/targettests/infleqtion/variable_size_qreg.cpp @@ -0,0 +1 @@ +../execution/variable_size_qreg.cpp \ No newline at end of file diff --git a/targettests/lit.cfg.py b/targettests/lit.cfg.py index c0f831c0b5a..697ac662af2 100644 --- a/targettests/lit.cfg.py +++ b/targettests/lit.cfg.py @@ -24,7 +24,7 @@ # Exclude a list of directories from the test suite: # - 'Inputs' contain auxiliary inputs for various tests. -local_excludes = ['anyon', 'ionq', 'iqm', 'oqc', 'quantinuum', 'fermioniq' +local_excludes = ['anyon', 'ionq', 'iqm', 'oqc', 'quantinuum', 'fermioniq', 'infleqtion', 'Inputs', 'CMakeLists.txt', 'README.txt', 'LICENSE.txt'] config.excludes = [exclude for exclude in config.excludes] + local_excludes diff --git a/unittests/backends/CMakeLists.txt b/unittests/backends/CMakeLists.txt index 56dc3a3d2fa..2c8250a55b6 100644 --- a/unittests/backends/CMakeLists.txt +++ b/unittests/backends/CMakeLists.txt @@ -10,6 +10,7 @@ find_package(Python COMPONENTS Interpreter) if (OPENSSL_FOUND AND CUDAQ_ENABLE_PYTHON AND CUDAQ_TEST_MOCK_SERVERS) add_subdirectory(anyon) add_subdirectory(braket) + add_subdirectory(infleqtion) add_subdirectory(ionq) add_subdirectory(iqm) add_subdirectory(oqc) diff --git a/unittests/backends/infleqtion/CMakeLists.txt b/unittests/backends/infleqtion/CMakeLists.txt new file mode 100644 index 00000000000..f06f48a5c3e --- /dev/null +++ b/unittests/backends/infleqtion/CMakeLists.txt @@ -0,0 +1,28 @@ +# ============================================================================ # +# 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. # +# ============================================================================ # + +add_executable(test_infleqtion InfleqtionTester.cpp) +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT APPLE) + target_link_options(test_infleqtion PRIVATE -Wl,--no-as-needed) +endif() +target_compile_definitions(test_infleqtion PRIVATE -DNVQIR_BACKEND_NAME=infleqtion) +target_include_directories(test_infleqtion PRIVATE ../..) +target_link_libraries(test_infleqtion + PRIVATE fmt::fmt-header-only + cudaq-common + cudaq + cudaq-builder + cudaq-mlir-runtime + cudaq-rest-qpu + cudaq-spin + cudaq-platform-default + gtest_main) + + +configure_file("InfleqtionStartServerAndTest.sh.in" "${CMAKE_BINARY_DIR}/unittests/backends/infleqtion/InfleqtionStartServerAndTest.sh" @ONLY) +add_test(NAME infleqtion-tests COMMAND bash InfleqtionStartServerAndTest.sh WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/unittests/backends/infleqtion/) diff --git a/unittests/backends/infleqtion/InfleqtionStartServerAndTest.sh.in b/unittests/backends/infleqtion/InfleqtionStartServerAndTest.sh.in new file mode 100644 index 00000000000..7c2bd596199 --- /dev/null +++ b/unittests/backends/infleqtion/InfleqtionStartServerAndTest.sh.in @@ -0,0 +1,43 @@ +#!/bin/bash + +# ============================================================================ # +# 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. # +# ============================================================================ # + +checkServerConnection() { + PYTHONPATH=@CMAKE_BINARY_DIR@/python @Python_EXECUTABLE@ - << EOF +import socket +try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(("localhost", 62447)) + s.close() +except Exception: + exit(1) +EOF +} + +# Launch the fake server +PYTHONPATH=@CMAKE_BINARY_DIR@/python @Python_EXECUTABLE@ @CMAKE_SOURCE_DIR@/utils/mock_qpu/infleqtion/__init__.py & +# we'll need the process id to kill it +pid=$(echo "$!") +n=0 +while ! checkServerConnection; do + sleep 1 + n=$((n+1)) + if [ "$n" -eq "10" ]; then + kill -INT $pid + exit 99 + fi +done +# Run the tests +./test_infleqtion +# Did they fail? +testsPassed=$? +# kill the server +kill -INT $pid +# return success / failure +exit $testsPassed diff --git a/unittests/backends/infleqtion/InfleqtionTester.cpp b/unittests/backends/infleqtion/InfleqtionTester.cpp new file mode 100644 index 00000000000..85125c5a21b --- /dev/null +++ b/unittests/backends/infleqtion/InfleqtionTester.cpp @@ -0,0 +1,97 @@ +/******************************************************************************* + * 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. * + ******************************************************************************/ + +#include "CUDAQTestUtils.h" +#include "common/FmtCore.h" +#include "cudaq/algorithm.h" +#include +#include +#include + +std::string mockPort = "62447"; +std::string backendStringTemplate = + "infleqtion;emulate;false;url;http://localhost:{}"; + +bool isValidExpVal(double value) { + // give us some wiggle room while keep the tests fast + return value < -1.1 && value > -2.3; +} + +CUDAQ_TEST(InfleqtionTester, checkSampleSync) { + auto backendString = + fmt::format(fmt::runtime(backendStringTemplate), mockPort); + + auto &platform = cudaq::get_platform(); + platform.setTargetBackend(backendString); + + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + + auto counts = cudaq::sample(kernel); + counts.dump(); + EXPECT_EQ(counts.size(), 2); +} + +CUDAQ_TEST(InfleqtionTester, checkSampleAsync) { + auto backendString = + fmt::format(fmt::runtime(backendStringTemplate), mockPort); + + auto &platform = cudaq::get_platform(); + platform.setTargetBackend(backendString); + + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + + auto future = cudaq::sample_async(kernel); + auto counts = future.get(); + EXPECT_EQ(counts.size(), 2); +} + +CUDAQ_TEST(InfleqtionTester, checkSampleAsyncLoadFromFile) { + auto backendString = + fmt::format(fmt::runtime(backendStringTemplate), mockPort); + + auto &platform = cudaq::get_platform(); + platform.setTargetBackend(backendString); + + auto kernel = cudaq::make_kernel(); + auto qubit = kernel.qalloc(2); + kernel.h(qubit[0]); + kernel.mz(qubit[0]); + + // Can sample asynchronously and get a future + auto future = cudaq::sample_async(kernel); + + // Future can be persisted for later + { + std::ofstream out("saveMe.json"); + out << future; + } + + // Later you can come back and read it in + cudaq::async_result readIn; + std::ifstream in("saveMe.json"); + in >> readIn; + + // Get the results of the read in future. + auto counts = readIn.get(); + EXPECT_EQ(counts.size(), 2); + + std::remove("saveMe.json"); +} + +int main(int argc, char **argv) { + setenv("SUPERSTAQ_API_KEY", "00000000000000000000000000000000", 0); + ::testing::InitGoogleTest(&argc, argv); + auto ret = RUN_ALL_TESTS(); + return ret; +} diff --git a/utils/mock_qpu/__init__.py b/utils/mock_qpu/__init__.py index 1f3801628ae..f28edfeb69a 100644 --- a/utils/mock_qpu/__init__.py +++ b/utils/mock_qpu/__init__.py @@ -8,6 +8,7 @@ from .anyon import * from .braket import * +from .infleqtion import * from .ionq import * from .iqm import * from .quantinuum import * diff --git a/utils/mock_qpu/infleqtion/__init__.py b/utils/mock_qpu/infleqtion/__init__.py new file mode 100644 index 00000000000..7c71e8264e2 --- /dev/null +++ b/utils/mock_qpu/infleqtion/__init__.py @@ -0,0 +1,95 @@ +# ============================================================================ # +# 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. # +# ============================================================================ # + +from fastapi import FastAPI, HTTPException, Header +from typing import Union +import uvicorn, uuid +from pydantic import BaseModel + +# Define the REST Server App +app = FastAPI() + + +class Input(BaseModel): + format: str + data: str + + +# Jobs look like the following type +class Job(BaseModel): + qasm_strs: str + shots: int + target: str + + +# Keep track of Job Ids to their Names +createdJobs = {} + +# Could how many times the client has requested the Job +countJobGetRequests = 0 + +# Save how many qubits were needed for each test (emulates real backend) +numQubitsRequired = 0 + + +# Here we test that the login endpoint works +@app.post("/login") +async def login(token: Union[str, None] = Header(alias="Authorization", + default=None)): + if token == None: + raise HTTPException(status_code(401), detail="Credentials not provided") + return {"id-token": "hello", "refresh-token": "refreshToken"} + + +# Here we expose a way to post jobs, +# Must have a Access Token +# with entry_point tag +@app.post("/v0.2.0/jobs") +async def postJob(job: Job, + token: Union[str, None] = Header(alias="Authorization", + default=None)): + global createdJobs, shots, numQubitsRequired + + if token == None: + raise HTTPException(status_code(401), detail="Credentials not provided") + + print('Posting job with shots = ', job.shots) + newId = str(uuid.uuid4()) + # Job "created", return the id + return {"job_ids": [newId]} + + +# Retrieve the job, simulate having to wait by counting to 3 +# until we return the job results +@app.get("/v0.2.0/job/{id}") +async def getJob(id: str): + print("Getting Job") + global countJobGetRequests, createdJobs, numQubitsRequired + + # Simulate asynchronous execution + if countJobGetRequests < 3: + countJobGetRequests += 1 + return {"status": "running"} + + countJobGetRequests = 0 + res = { + 'status': 'Done', + "samples": { + "11": 49, + "00": 51 + }, + } + return res + + +def startServer(port): + uvicorn.run(app, port=port, host='0.0.0.0', log_level="info") + + +if __name__ == '__main__': + startServer(62447) From 8f288a3dff6dbe0c4067b311742a5e6f86c9fc51 Mon Sep 17 00:00:00 2001 From: Bettina Heim Date: Tue, 10 Dec 2024 19:21:21 +0100 Subject: [PATCH 15/44] Fixing nightly integration tests (#2460) Signed-off-by: Bettina Heim --- .github/workflows/integration_tests.yml | 82 ++++++++++++++++++++++--- docker/release/cudaq.nvqc.Dockerfile | 2 +- scripts/release.sh | 13 +++- 3 files changed, 86 insertions(+), 11 deletions(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index e4bd272293d..55d329b93f2 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -63,7 +63,6 @@ on: - cron: 0 3 * * * env: - # NGC nv-quantum organization: pnyjrcojiblh NGC_QUANTUM_ORG: pnyjrcojiblh NGC_QUANTUM_TEAM: cuda-quantum NVQC_FUNCTION_ID: 3bfa0342-7d2a-4f1b-8e81-b6608d28ca7d @@ -75,30 +74,90 @@ jobs: # We need this job purely to choose the container image values because the # `env` context is unavailable outside of "steps" contexts. setup: - name: Set variables + name: Configure jobs runs-on: ubuntu-latest + permissions: + packages: write + + environment: + name: ghcr-deployment + url: ${{ vars.deployment_url }} + outputs: - cudaq_test_image: ${{ steps.vars.outputs.cudaq_test_image }} - cudaq_nvqc_deploy_image: ${{ steps.vars.outputs.cudaq_nvqc_deploy_image }} + cudaq_test_image: ${{ steps.vars.outputs.cudaq_nightly_image }}@${{ steps.test_image.outputs.digest }} + cudaq_nvqc_deploy_image: ${{ inputs.cudaq_nvqc_deploy_image || format('{0}@{1}', steps.vars.outputs.cudaq_nightly_image, steps.test_image.outputs.digest) }} + steps: - name: Set variables id: vars run: | - echo "cudaq_test_image=${{ inputs.cudaq_test_image || vars.cudaq_test_image }}" >> $GITHUB_OUTPUT - echo "cudaq_nvqc_deploy_image=${{ inputs.cudaq_nvqc_deploy_image || vars.cudaq_test_image }}" >> $GITHUB_OUTPUT + cudaq_test_image=${{ inputs.cudaq_test_image || vars.cudaq_test_image }} + cudaq_nightly_image=ghcr.io/nvidia/${{ vars.packages_prefix }}cuda-quantum + + sudo apt-get update && sudo apt-get install -y --no-install-recommends curl + curl -L https://github.com/regclient/regclient/releases/latest/download/regctl-linux-amd64 > regctl + chmod 755 regctl + + manifest=`./regctl image manifest $cudaq_test_image --format "{{ json . }}"` + platforms=`echo $manifest | jq -r '.manifests | map("\(.platform.os)/\(.platform.architecture)") | .[]'` + echo "FROM $cudaq_test_image" >> test_image.Dockerfile + + echo "platforms=$(echo $platforms | tr ' ' ,)" >> $GITHUB_OUTPUT + echo "cudaq_nightly_image=$cudaq_nightly_image" >> $GITHUB_OUTPUT + + - name: Log in to GitHub CR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} + + - name: Set up context for buildx + run: | + docker context create builder_context + + - name: Set up buildx runner + uses: docker/setup-buildx-action@v3 + with: + endpoint: builder_context + + - name: Extract metadata + id: metadata + uses: docker/metadata-action@v5 + with: + images: ${{ steps.vars.outputs.cudaq_nightly_image }} + flavor: latest=false + tags: type=raw,value=nightly + labels: | + org.opencontainers.image.title=cuda-quantum + org.opencontainers.image.description=CUDA-Q image used for nightly integration tests + + - name: Update nightly image on GHCR + id: test_image + uses: docker/build-push-action@v5 + with: + context: . + file: test_image.Dockerfile + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + platforms: ${{ steps.vars.outputs.platforms }} + push: true metadata: name: Retrieve commit info runs-on: ubuntu-latest needs: setup + container: image: ${{ needs.setup.outputs.cudaq_test_image }} options: --user root credentials: username: ${{ github.actor }} password: ${{ github.token }} + outputs: cudaq_commit: ${{ steps.commit-sha.outputs.sha }} + steps: - name: Get commit SHA id: commit-sha @@ -248,6 +307,9 @@ jobs: container: image: ${{ needs.setup.outputs.cudaq_test_image }} options: --user root + credentials: + username: ${{ github.actor }} + password: ${{ github.token }} steps: - name: Get code @@ -620,10 +682,16 @@ jobs: packages: read # Must have environment protection - environment: ghcr-deployment + environment: + name: ghcr-deployment + url: ${{ vars.deployment_url }} + container: image: ${{ needs.setup.outputs.cudaq_test_image }} options: --user root + credentials: + username: ${{ github.actor }} + password: ${{ github.token }} steps: - name: Get code diff --git a/docker/release/cudaq.nvqc.Dockerfile b/docker/release/cudaq.nvqc.Dockerfile index e5d3a9fcf5a..56348e7aa34 100644 --- a/docker/release/cudaq.nvqc.Dockerfile +++ b/docker/release/cudaq.nvqc.Dockerfile @@ -10,7 +10,7 @@ # # Usage: # Must be built from the repo root with: -# DOCKER_BUILDKIT=1 docker build -f docker/release/cudaq.nvqc.Dockerfile . --output out +# docker build -f docker/release/cudaq.nvqc.Dockerfile . # Base image is CUDA-Q image ARG base_image=nvcr.io/nvidia/nightly/cuda-quantum:cu12-latest diff --git a/scripts/release.sh b/scripts/release.sh index 38a17346cb6..c8bc12abf8b 100644 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -16,10 +16,17 @@ version_regex="([0-9]{1,}\.)+[0-9]{1,}\S*" versions=`gh release list -R nvidia/cuda-quantum --exclude-drafts --exclude-pre-releases | egrep -o "$version_regex" | sort -r -V` last_release=`echo $versions | cut -d ' ' -f 1` -current_release="0.9.0" +current_release="$1" +if [ -z "$current_release" ]; then + echo "Warning: no version number specified for the new release." + rel_branch=main +else + rel_branch=releases/v$current_release +fi author="" # insert your name to show only your PRs -git pull && git log releases/v$current_release...releases/v$last_release --cherry-pick --left-only --no-merges --oneline --author="$author" | egrep -o '\(#[0-9]*\)$' > commits.txt +git pull || echo "Warning: failed to pull updates" +git log $rel_branch...releases/v$last_release --cherry-pick --left-only --no-merges --oneline --author="$author" | egrep -o '\(#[0-9]*\)$' > commits.txt for pr in `cat commits.txt`; do maintenance=`gh pr view ${pr: 2: -1} --json labels --jq 'any(.labels.[]; .name == "maintenance")'` @@ -29,7 +36,7 @@ for pr in `cat commits.txt`; do if [ -z "$milestone" ]; then echo "Missing milestone for PR ${pr: 2: -1} by $pr_author." - elif [ "$(echo "$milestone" | egrep -o "$version_regex" || true)" == "$current_release" ]; then + elif [ -z "$current_release" ] || [ "$(echo "$milestone" | egrep -o "$version_regex" || true)" == "$current_release" ]; then labels=`gh pr view ${pr: 2: -1} --json labels --jq '.labels.[].name'` echo "Labels for PR ${pr: 2: -1} by $pr_author: $labels" fi From 1e383a65b56d3e7c91f4c6641b7baa8a895d66bb Mon Sep 17 00:00:00 2001 From: caiyunh Date: Wed, 11 Dec 2024 04:52:35 +0800 Subject: [PATCH 16/44] Generate coverage data when pusing to main (#2442) Signed-off-by: Iris Huang Signed-off-by: Bettina Heim Co-authored-by: Iris Huang Co-authored-by: Bettina Heim --- .github/workflows/deployments.yml | 49 +++++++++++++++++++++++-------- .github/workflows/generate_cc.yml | 23 ++++++++------- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/.github/workflows/deployments.yml b/.github/workflows/deployments.yml index caaf5358b58..235bfbe6e1f 100644 --- a/.github/workflows/deployments.yml +++ b/.github/workflows/deployments.yml @@ -63,12 +63,14 @@ jobs: pull_request_number: ${{ steps.pr_info.outputs.pr_number }} pull_request_base: ${{ steps.pr_info.outputs.pr_base }} pull_request_commit: ${{ steps.pr_info.outputs.merge_commit }} - llvm_commit: ${{ steps.repo_info.outputs.llvm_commit }} - pybind11_commit: ${{ steps.repo_info.outputs.pybind11_commit }} + llvm_commit: ${{ steps.build_config.outputs.llvm_commit }} + pybind11_commit: ${{ steps.build_config.outputs.pybind11_commit }} cache_base: ${{ steps.build_info.outputs.cache_base }} cache_target: ${{ steps.build_info.outputs.cache_target }} multi_platform: ${{ steps.build_info.outputs.multi_platform }} platforms: ${{ steps.build_info.outputs.platforms }} + build_dependencies: ${{ steps.build_config.outputs.build_dependencies }} + create_packages: ${{ steps.build_config.outputs.create_packages }} environment: ${{ steps.build_info.outputs.environment }} steps: @@ -159,15 +161,22 @@ jobs: ref: "${{ steps.pr_info.outputs.merge_commit }}" - name: Configure build - id: repo_info + id: build_config run: | echo "llvm_commit=$(git rev-parse @:./tpls/llvm)" >> $GITHUB_OUTPUT echo "pybind11_commit=$(git rev-parse @:./tpls/pybind11)" >> $GITHUB_OUTPUT + if ${{ github.event_name != 'workflow_run' || steps.pr_info.outputs.pr_number != '' }}; then + echo "build_dependencies=true" >> $GITHUB_OUTPUT + fi + if ${{ github.event_name == 'workflow_dispatch' && ! inputs.update_registry_cache }}; then + echo "create_packages=true" >> $GITHUB_OUTPUT + fi + devdeps: name: Build dev dependencies needs: metadata - if: github.event_name != 'workflow_run' || needs.metadata.outputs.pull_request_number != '' + if: needs.metadata.outputs.build_dependencies == 'true' strategy: matrix: platform: ${{ fromJson(needs.metadata.outputs.multi_platform || needs.metadata.outputs.platforms).ids }} @@ -195,7 +204,7 @@ jobs: wheeldeps: name: Build wheel dependencies needs: metadata - if: github.event_name != 'workflow_run' || needs.metadata.outputs.pull_request_number != '' + if: needs.metadata.outputs.build_dependencies == 'true' strategy: matrix: # There are currently no multi-platform manylinux images available. @@ -229,7 +238,7 @@ jobs: source_build: name: Build cross-platform dependencies needs: metadata - if: github.event_name != 'workflow_run' || needs.metadata.outputs.pull_request_number != '' + if: needs.metadata.outputs.build_dependencies == 'true' strategy: matrix: platform: ${{ fromJson(needs.metadata.outputs.platforms).ids }} @@ -260,7 +269,7 @@ jobs: openmpi: name: Build Open MPI needs: metadata - if: github.event_name != 'workflow_run' || needs.metadata.outputs.pull_request_number != '' + if: needs.metadata.outputs.build_dependencies == 'true' strategy: matrix: platform: ${{ fromJson(needs.metadata.outputs.multi_platform || needs.metadata.outputs.platforms).ids }} @@ -311,13 +320,12 @@ jobs: # https://github.com/actions/runner/pull/2477 config: name: Configure build - needs: [devdeps, wheeldeps, source_build, openmpi] - if: github.event_name == 'workflow_dispatch' && ! inputs.update_registry_cache + needs: [metadata, devdeps, wheeldeps, source_build, openmpi] + if: needs.metadata.outputs.create_packages == 'true' runs-on: ubuntu-latest outputs: json: "${{ steps.read_json.outputs.result }}" - devdeps_toolchain: gcc11 steps: - uses: cloudposse/github-action-matrix-outputs-read@1.0.0 @@ -325,6 +333,23 @@ jobs: with: matrix-step-name: dev_environment + coverage: + name: Update code coverage + needs: [metadata, config] + if: needs.metadata.outputs.multi_platform != '' + strategy: + matrix: + platform: [amd64] + toolchain: [clang16] + fail-fast: false + uses: ./.github/workflows/generate_cc.yml + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + platform: linux/${{ matrix.platform }} + devdeps_image: ${{ fromJson(needs.config.outputs.json).image_hash[format('{0}-{1}', fromJson(needs.metadata.outputs.multi_platform).ids[0], matrix.toolchain)] }} + export_environment: false + extdevdeps: name: Create dev environment needs: [metadata, config, openmpi] @@ -340,10 +365,10 @@ jobs: with: platforms: ${{ fromJson(needs.metadata.outputs.multi_platform || needs.metadata.outputs.platforms)[format('{0}', matrix.platform)].docker_flag }} dockerfile: build/devdeps.ext.Dockerfile - build_config_id: cu${{ matrix.cuda_version }}-${{ needs.config.outputs.devdeps_toolchain }} + build_config_id: cu${{ matrix.cuda_version }}-gcc11 build_args: | cuda_version=${{ matrix.cuda_version }} - base_image=${{ fromJson(needs.config.outputs.json).image_hash[format('{0}-{1}', matrix.platform, needs.config.outputs.devdeps_toolchain)] }} + 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' || '' }} registry_cache_from: ${{ needs.metadata.outputs.cache_base }} diff --git a/.github/workflows/generate_cc.yml b/.github/workflows/generate_cc.yml index 463d0e0c6af..8df4b23a49e 100644 --- a/.github/workflows/generate_cc.yml +++ b/.github/workflows/generate_cc.yml @@ -9,10 +9,10 @@ on: required: false type: string devdeps_cache: - required: true + required: false type: string devdeps_archive: - required: true + required: false type: string export_environment: required: false @@ -33,24 +33,25 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 - - - name: Restore environment - id: restore_devdeps - if: inputs.devdeps_image == '' - uses: actions/cache/restore@v4 with: - path: ${{ inputs.devdeps_archive }} - key: ${{ inputs.devdeps_cache }} - fail-on-cache-miss: true + persist-credentials: false - name: Log in to GitHub CR - if: inputs.devdeps_image != '' uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ github.token }} + - name: Restore environment + id: restore_devdeps + if: inputs.devdeps_cache && inputs.devdeps_archive + uses: actions/cache/restore@v4 + with: + path: ${{ inputs.devdeps_archive }} + key: ${{ inputs.devdeps_cache }} + fail-on-cache-miss: true + - name: Set up context for buildx run: | docker context create builder_context From b089b03cafeafc2619712565c8c7f03136346118 Mon Sep 17 00:00:00 2001 From: Pradnya Khalate <148914294+khalatepradnya@users.noreply.github.com> Date: Tue, 10 Dec 2024 19:06:26 -0800 Subject: [PATCH 17/44] Follow-up for the `infleqtion` target - `cu1` and `cu3` are supported (#2461) * Decomposition pattern no longer required * Added test Signed-off-by: Pradnya Khalate --- python/tests/backends/test_Infleqtion.py | 2 +- .../platform/default/rest/helpers/infleqtion/infleqtion.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/tests/backends/test_Infleqtion.py b/python/tests/backends/test_Infleqtion.py index 24c4a11d241..49b2eebc66d 100644 --- a/python/tests/backends/test_Infleqtion.py +++ b/python/tests/backends/test_Infleqtion.py @@ -78,7 +78,7 @@ def all_gates(): rz.ctrl(np.pi, qubits[1], qubits[0]) s.ctrl(qubits[0], qubits[1]) t.ctrl(qubits[1], qubits[0]) - # u3.ctrl(0.0, np.pi / 2, np.pi, qubits[0], qubits[1]) + u3.ctrl(0.0, np.pi / 2, np.pi, qubits[0], qubits[1]) mz(qubits) qreg = cudaq.qvector(3) diff --git a/runtime/cudaq/platform/default/rest/helpers/infleqtion/infleqtion.yml b/runtime/cudaq/platform/default/rest/helpers/infleqtion/infleqtion.yml index 5370880e276..31c10e38c68 100644 --- a/runtime/cudaq/platform/default/rest/helpers/infleqtion/infleqtion.yml +++ b/runtime/cudaq/platform/default/rest/helpers/infleqtion/infleqtion.yml @@ -17,7 +17,7 @@ config: # Tell NVQ++ to generate glue code to set the target backend name gen-target-backend: true # Define the lowering pipeline - platform-lowering-config: "func.func(const-prop-complex,canonicalize,cse,lift-array-alloc),globalize-array-values,func.func(state-prep),unitary-synthesis,canonicalize,apply-op-specialization,aggressive-early-inlining,unrolling-pipeline,func.func(lower-to-cfg),canonicalize,func.func(multicontrol-decomposition),decomposition{enable-patterns=SToR1,TToR1,SwapToCX,CCZToCX,CR1ToCX,CRyToCX,CRxToCX,R1AdjToR1,RxAdjToRx,RyAdjToRy,RzAdjToRz},func.func(memtoreg{quantum=0}),symbol-dce" + platform-lowering-config: "func.func(const-prop-complex,canonicalize,cse,lift-array-alloc),globalize-array-values,func.func(state-prep),unitary-synthesis,canonicalize,apply-op-specialization,aggressive-early-inlining,unrolling-pipeline,func.func(lower-to-cfg),canonicalize,func.func(multicontrol-decomposition),decomposition{enable-patterns=SToR1,TToR1,CCZToCX,CRyToCX,CRxToCX,R1AdjToR1,RxAdjToRx,RyAdjToRy,RzAdjToRz},func.func(memtoreg{quantum=0}),symbol-dce" # Tell the rest-qpu that we are generating OpenQASM 2.0. codegen-emission: qasm2 # Library mode is only for simulators, physical backends must turn this off From 72eab568a4ccefcf30ec50c6136744a49f5c1021 Mon Sep 17 00:00:00 2001 From: Pradnya Khalate <148914294+khalatepradnya@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:28:56 -0800 Subject: [PATCH 18/44] [bug-fix] Custom operations - disallow `qvector` arguments (#2454) Signed-off-by: Pradnya Khalate --- python/cudaq/kernel/ast_bridge.py | 12 +++++- python/tests/custom/test_custom_operations.py | 39 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/python/cudaq/kernel/ast_bridge.py b/python/cudaq/kernel/ast_bridge.py index 5f566768c24..d76a802d725 100644 --- a/python/cudaq/kernel/ast_bridge.py +++ b/python/cudaq/kernel/ast_bridge.py @@ -1826,7 +1826,11 @@ def bodyBuilder(iterVal): targets = [self.popValue() for _ in range(numTargets)] targets.reverse() - self.checkControlAndTargetTypes([], targets) + for i, t in enumerate(targets): + if not quake.RefType.isinstance(t.type): + self.emitFatalError( + f'invalid target operand {i}, broadcasting is not supported on custom operations.' + ) globalName = f'{nvqppPrefix}{node.func.id}_generator_{numTargets}.rodata' @@ -2678,6 +2682,12 @@ def bodyBuilder(iterVal): targets = [self.popValue() for _ in range(numTargets)] targets.reverse() + for i, t in enumerate(targets): + if not quake.RefType.isinstance(t.type): + self.emitFatalError( + f'invalid target operand {i}, broadcasting is not supported on custom operations.' + ) + globalName = f'{nvqppPrefix}{node.func.value.id}_generator_{numTargets}.rodata' currentST = SymbolTable(self.module.operation) diff --git a/python/tests/custom/test_custom_operations.py b/python/tests/custom/test_custom_operations.py index ed2f3cfa493..e1772b2ebe5 100644 --- a/python/tests/custom/test_custom_operations.py +++ b/python/tests/custom/test_custom_operations.py @@ -230,6 +230,45 @@ def bell(): error) +def test_bug_2452(): + cudaq.register_operation("custom_i", np.array([1, 0, 0, 1])) + + @cudaq.kernel + def kernel1(): + qubits = cudaq.qvector(2) + custom_i(qubits) + + with pytest.raises(RuntimeError) as error: + kernel1.compile() + assert 'broadcasting is not supported on custom operations' in repr(error) + + cudaq.register_operation("custom_x", np.array([0, 1, 1, 0])) + + @cudaq.kernel + def kernel2(): + qubit = cudaq.qubit() + ancilla = cudaq.qvector(2) + x(ancilla) + custom_x.ctrl(ancilla, qubit) # `controls` can be `qvector` + + counts = cudaq.sample(kernel2) + assert len(counts) == 1 and '111' in counts + + cudaq.register_operation( + "custom_cz", np.array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, + -1])) + + @cudaq.kernel + def kernel3(): + qubits = cudaq.qvector(2) + custom_cz(qubits) + + with pytest.raises(RuntimeError) as error: + cudaq.sample(kernel3) + assert 'invalid number of arguments (1) passed to custom_cz (requires 2 arguments)' in repr( + error) + + # leave for gdb debugging if __name__ == "__main__": loc = os.path.abspath(__file__) From 4ea2f733bf025c18435aa79b8e6eca3959cfd34d Mon Sep 17 00:00:00 2001 From: Markus Pfundstein Date: Wed, 11 Dec 2024 11:45:26 +0100 Subject: [PATCH 19/44] Fix wrong parameters in docs for Fermioniq backend (#2367) * DCO Remediation Commit for Markus Pfundstein I, Markus Pfundstein , hereby add my Signed-off-by to this commit: a68a8d99eb3e2f7a34cce42f9d87213f23072b04 Signed-off-by: Markus Pfundstein --- docs/sphinx/using/backends/simulators.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sphinx/using/backends/simulators.rst b/docs/sphinx/using/backends/simulators.rst index 70a94343a5c..a3dba35d929 100644 --- a/docs/sphinx/using/backends/simulators.rst +++ b/docs/sphinx/using/backends/simulators.rst @@ -522,7 +522,7 @@ compute expectation values of observables. .. code:: python cudaq.set_target("fermioniq", **{ - "remote-config": remote_config_id + "remote_config": remote_config_id }) For a comprehensive list of all remote configurations, please contact Fermioniq directly. @@ -533,15 +533,15 @@ compute expectation values of observables. .. code:: python cudaq.set_target("fermioniq", **{ - "project-id": project_id + "project_id": project_id }) - To specify the bond dimension, you can pass the ``fermioniq-bond-dim`` parameter. + To specify the bond dimension, you can pass the ``bond_dim`` parameter. .. code:: python cudaq.set_target("fermioniq", **{ - "bond-dim": 5 + "bond_dim": 5 }) .. tab:: C++ From 538458cbeecb7d091cc105821a931ef97600ec7b Mon Sep 17 00:00:00 2001 From: Ben Howe <141149032+bmhowe23@users.noreply.github.com> Date: Wed, 11 Dec 2024 04:31:56 -0800 Subject: [PATCH 20/44] Add unintentionally excluded tests to Publishing workflow (#2038) Signed-off-by: Bettina Heim Co-authored-by: Bettina Heim --- .github/workflows/publishing.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publishing.yml b/.github/workflows/publishing.yml index 80a21942f9f..cfdb5068b21 100644 --- a/.github/workflows/publishing.yml +++ b/.github/workflows/publishing.yml @@ -1128,14 +1128,14 @@ jobs: tar xf /tmp/packages/${cudaq_metapackage}.tar.gz && mv -v ${cudaq_metapackage}/README.md . rm -rf ${cudaq_metapackage} && readme=README.md - # Setup links for validate_pycudaq.sh script - ln -s $GITHUB_WORKSPACE/scripts/validate_pycudaq.sh . - ln -s $GITHUB_WORKSPACE/docs/sphinx/examples/python /tmp/examples - ln -s $GITHUB_WORKSPACE/docs/sphinx/applications/python /tmp/applications - ln -s $GITHUB_WORKSPACE/docs/sphinx/targets/python /tmp/targets - ln -s $GITHUB_WORKSPACE/docs/sphinx/snippets/python /tmp/snippets - ln -s $GITHUB_WORKSPACE/python/tests /tmp/tests - ln -s $GITHUB_WORKSPACE/$readme /tmp/README.md + # Setup files for validate_pycudaq.sh script + cp $GITHUB_WORKSPACE/scripts/validate_pycudaq.sh . + cp -r $GITHUB_WORKSPACE/docs/sphinx/examples/python /tmp/examples/ + cp -r $GITHUB_WORKSPACE/docs/sphinx/applications/python /tmp/applications/ + cp -r $GITHUB_WORKSPACE/docs/sphinx/targets/python /tmp/targets/ + cp -r $GITHUB_WORKSPACE/docs/sphinx/snippets/python /tmp/snippets/ + cp -r $GITHUB_WORKSPACE/python/tests /tmp/tests/ + cp $GITHUB_WORKSPACE/$readme /tmp/README.md # Run the script w/ -q to run a shortened test set +e # Allow script to keep going through errors (needed for skipped tests) From 77801540850e1f40c67865600edb748dcc6fff81 Mon Sep 17 00:00:00 2001 From: Bharath Thotakura <113555655+bharat-thotakura@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:58:45 -0600 Subject: [PATCH 21/44] Documentation for `infleqtion` target (#2464) Signed-off-by: Bharath Signed-off-by: Bettina Heim Co-authored-by: Bettina Heim --- .../workflows/config/spelling_allowlist.txt | 1 + .../python/logical_aim_sqale.ipynb | 1356 +++++++++++++++++ docs/sphinx/targets/cpp/infleqtion.cpp | 65 + docs/sphinx/targets/python/infleqtion.py | 54 + docs/sphinx/using/applications.rst | 5 +- docs/sphinx/using/backends/backends.rst | 5 +- docs/sphinx/using/backends/hardware.rst | 122 ++ .../using/examples/hardware_providers.rst | 17 +- 8 files changed, 1620 insertions(+), 5 deletions(-) create mode 100644 docs/sphinx/applications/python/logical_aim_sqale.ipynb create mode 100644 docs/sphinx/targets/cpp/infleqtion.cpp create mode 100644 docs/sphinx/targets/python/infleqtion.py diff --git a/.github/workflows/config/spelling_allowlist.txt b/.github/workflows/config/spelling_allowlist.txt index e74c23ba43a..19fcb2d34f0 100644 --- a/.github/workflows/config/spelling_allowlist.txt +++ b/.github/workflows/config/spelling_allowlist.txt @@ -46,6 +46,7 @@ Hamiltonian Hamiltonians IQM InfiniBand +Infleqtion IonQ JIT JSON diff --git a/docs/sphinx/applications/python/logical_aim_sqale.ipynb b/docs/sphinx/applications/python/logical_aim_sqale.ipynb new file mode 100644 index 00000000000..f51bd102940 --- /dev/null +++ b/docs/sphinx/applications/python/logical_aim_sqale.ipynb @@ -0,0 +1,1356 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Anderson Impurity Model ground state solver on Infleqtion's Sqale" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ground state quantum chemistry—computing total energies of molecular configurations to within chemical accuracy—is perhaps the most highly-touted industrial application of fault-tolerant quantum computers. Strongly correlated materials, for example, are particularly interesting, and tools like dynamical mean-field theory (DMFT) allow one to account for the effect of their strong, localized electronic correlations. These DMFT models help predict material properties by approximating the system as a single site impurity inside a “bath” that encompasses the rest of the system. Simulating such dynamics can be a tough task using classical methods, but can be done efficiently on a quantum computer via quantum simulation.\n", + "\n", + "In this notebook, we showcase a workflow for preparing the ground state of the minimal single-impurity Anderson model (SIAM) using the Hamiltonian Variational Ansatz for a range of realistic parameters. As a first step towards running DMFT on a fault-tolerant quantum computer, we will use logical qubits encoded in the `[[4, 2, 2]]` code. Using this workflow, we will obtain the ground state energy estimates via noisy simulation, and then also execute the corresponding optimized circuits on Infleqtion's gate-based neutral-atom quantum computer, making the benefits of logical qubits apparent. More details can be found in our [paper](https://arxiv.org/abs/2412.07670)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This demo notebook uses CUDA-Q (`cudaq`) and a CUDA-QX library, `cudaq-solvers`; let us first begin by importing (and installing as needed) these packages:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " import cudaq_solvers as solvers\n", + " import cudaq\n", + " import matplotlib.pyplot as plt\n", + "except ImportError:\n", + " print(\"Installing required packages...\")\n", + " %pip install --quiet 'cudaq-solvers' 'matplotlib'\n", + " print(\"Installed `cudaq`, `cudaq-solvers`, and `matplotlib` packages.\")\n", + " print(\"You may need to restart the kernel to import newly installed packages.\")\n", + " import cudaq_solvers as solvers\n", + " import cudaq\n", + " import matplotlib.pyplot as plt\n", + "\n", + "from collections.abc import Mapping, Sequence\n", + "import numpy as np\n", + "from scipy.optimize import minimize\n", + "import os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Performing logical Variational Quantum Eigensolver (VQE) with CUDA-QX" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To prepare our ground state quantum Anderson impurity model circuits (referred to as AIM circuits in this notebook for short), we use VQE to train an ansatz to minimize a Hamiltonian and obtain optimal angles that can be used to set the AIM circuits. As described in our [paper](https://arxiv.org/abs/2412.07670), the associated restricted Hamiltonian for our SIAM can be reduced to,\n", + "$$ \n", + "\\begin{equation}\n", + "H_{(U, V)} = U (Z_0 Z_2 - 1) / 4 + V (X_0 + X_2),\n", + "\\end{equation}\n", + "$$\n", + "where $U$ is the Coulomb interaction and $V$ the hybridization strength. In this notebook workflow, we will optimize over a 2-dimensional grid of Hamiltonian parameter values, namely $U\\in \\{1, 5, 9\\}$ and $V\\in \\{-9, -1, 7\\}$ (with all values assumed to be in units of eV), to ensure that the ansatz is generally trainable and expressive, and obtain 9 different circuit layers identified by the key $(U, V)$. We will simulate the VQE on GPU (or optionally on CPU if you do not have GPU access), enabled by CUDA-Q, in the absence of noise:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "if cudaq.num_available_gpus() == 0:\n", + " cudaq.set_target(\"qpp-cpu\", option=\"fp64\")\n", + "else:\n", + " cudaq.set_target(\"nvidia\", option=\"fp64\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This workflow can be easily defined in CUDA-Q as shown in the cell below, using the CUDA-QX Solvers library (which accelerates quantum algorithms like the VQE):" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "def ansatz(n_qubits: int) -> cudaq.Kernel:\n", + " # Create a CUDA-Q parameterized kernel\n", + " paramterized_ansatz, variational_angles = cudaq.make_kernel(list)\n", + " qubits = paramterized_ansatz.qalloc(n_qubits)\n", + "\n", + " # Using |+> as the initial state:\n", + " paramterized_ansatz.h(qubits[0])\n", + " paramterized_ansatz.cx(qubits[0], qubits[1])\n", + "\n", + " paramterized_ansatz.rx(variational_angles[0], qubits[0])\n", + " paramterized_ansatz.cx(qubits[0], qubits[1])\n", + " paramterized_ansatz.rz(variational_angles[1], qubits[1])\n", + " paramterized_ansatz.cx(qubits[0], qubits[1])\n", + " return paramterized_ansatz\n", + "\n", + "\n", + "def run_logical_vqe(cudaq_hamiltonian: cudaq.SpinOperator) -> tuple[float, list[float]]:\n", + " # Set seed for easier reproduction\n", + " np.random.seed(42)\n", + "\n", + " # Initial angles for the optimizer\n", + " init_angles = np.random.random(2) * 1e-1\n", + "\n", + " # Obtain CUDA-Q Ansatz\n", + " num_qubits = cudaq_hamiltonian.get_qubit_count()\n", + " variational_kernel = ansatz(num_qubits)\n", + "\n", + " # Perform VQE optimization\n", + " energy, params, _ = solvers.vqe(\n", + " variational_kernel,\n", + " cudaq_hamiltonian,\n", + " init_angles,\n", + " optimizer=minimize,\n", + " method=\"SLSQP\",\n", + " tol=1e-10,\n", + " )\n", + " return energy, params" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Constructing circuits in the `[[4,2,2]]` encoding" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `[[4,2,2]]` code is a quantum error detection code that uses four physical qubits to encode two logical qubits. In this notebook, we will construct two variants of quantum circuits: physical (bare, unencoded) and logical (encoded). These circuits will be informed by the Hamiltonian Variational Ansatz described earlier. To measure all the terms in our Hamiltonian, we will measure the data qubits in both the $Z$- and $X$-basis, as allowed by the `[[4,2,2]]` logical gateset. Full details on the circuit constructions are outlined in our [paper](https://arxiv.org/abs/2412.07670)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Below, we create functions to build our CUDA-Q AIM circuits, both physical and logical versions. As we consider noisy simulation in this notebook, we will include some noisy gates. Here, for simplicity, we will just register a custom identity gate -- to be later used as a noisy operation to model readout error: " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "cudaq.register_operation(\"meas_id\", np.identity(2))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def aim_physical_circuit(\n", + " angles: list[float], basis: str, *, ignore_meas_id: bool = False\n", + ") -> cudaq.Kernel:\n", + " kernel = cudaq.make_kernel()\n", + " qubits = kernel.qalloc(2)\n", + "\n", + " # Bell state prep\n", + " kernel.h(qubits[0])\n", + " kernel.cx(qubits[0], qubits[1])\n", + "\n", + " # Rx Gate\n", + " kernel.rx(angles[0], qubits[0])\n", + "\n", + " # ZZ rotation\n", + " kernel.cx(qubits[0], qubits[1])\n", + " kernel.rz(angles[1], qubits[1])\n", + " kernel.cx(qubits[0], qubits[1])\n", + "\n", + " if basis == \"z_basis\":\n", + " if not ignore_meas_id:\n", + " kernel.for_loop(\n", + " start=0, stop=2, function=lambda q_idx: getattr(kernel, \"meas_id\")(qubits[q_idx])\n", + " )\n", + " kernel.mz(qubits)\n", + " elif basis == \"x_basis\":\n", + " kernel.h(qubits)\n", + " if not ignore_meas_id:\n", + " kernel.for_loop(\n", + " start=0, stop=2, function=lambda q_idx: getattr(kernel, \"meas_id\")(qubits[q_idx])\n", + " )\n", + " kernel.mz(qubits)\n", + " else:\n", + " raise ValueError(\"Unsupported basis provided:\", basis)\n", + " return kernel" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def aim_logical_circuit(\n", + " angles: list[float], basis: str, *, ignore_meas_id: bool = False\n", + ") -> cudaq.Kernel:\n", + " kernel = cudaq.make_kernel()\n", + " qubits = kernel.qalloc(6)\n", + "\n", + " kernel.for_loop(start=0, stop=3, function=lambda idx: kernel.h(qubits[idx]))\n", + " kernel.cx(qubits[1], qubits[4])\n", + " kernel.cx(qubits[2], qubits[3])\n", + " kernel.cx(qubits[0], qubits[1])\n", + " kernel.cx(qubits[0], qubits[3])\n", + "\n", + " # Rx teleportation\n", + " kernel.rx(angles[0], qubits[0])\n", + "\n", + " kernel.cx(qubits[0], qubits[1])\n", + " kernel.cx(qubits[0], qubits[3])\n", + " kernel.h(qubits[0])\n", + "\n", + " if basis == \"z_basis\":\n", + " if not ignore_meas_id:\n", + " kernel.for_loop(\n", + " start=0, stop=5, function=lambda idx: getattr(kernel, \"meas_id\")(qubits[idx])\n", + " )\n", + " kernel.mz(qubits)\n", + " elif basis == \"x_basis\":\n", + " # ZZ rotation and teleportation\n", + " kernel.cx(qubits[3], qubits[5])\n", + " kernel.cx(qubits[2], qubits[5])\n", + " kernel.rz(angles[1], qubits[5])\n", + " kernel.cx(qubits[1], qubits[5])\n", + " kernel.cx(qubits[4], qubits[5])\n", + " kernel.for_loop(start=1, stop=5, function=lambda idx: kernel.h(qubits[idx]))\n", + " if not ignore_meas_id:\n", + " kernel.for_loop(\n", + " start=0, stop=6, function=lambda idx: getattr(kernel, \"meas_id\")(qubits[idx])\n", + " )\n", + " kernel.mz(qubits)\n", + " else:\n", + " raise ValueError(\"Unsupported basis provided:\", basis)\n", + " return kernel" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With the circuit definitions above, we can now define a function that automatically runs the VQE and constructs a dictionary containing all the AIM circuits we want to submit to hardware (or noisily simulate):" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def generate_circuit_set(ignore_meas_id: bool = False) -> object:\n", + " u_vals = [1, 5, 9]\n", + " v_vals = [-9, -1, 7]\n", + " circuit_dict = {}\n", + " for u in u_vals:\n", + " for v in v_vals:\n", + " qubit_hamiltonian = (\n", + " 0.25 * u * cudaq.spin.z(0) * cudaq.spin.z(1)\n", + " - 0.25 * u\n", + " + v * cudaq.spin.x(0)\n", + " + v * cudaq.spin.x(1)\n", + " )\n", + " _, opt_params = run_logical_vqe(qubit_hamiltonian)\n", + " angles = [float(angle) for angle in opt_params]\n", + " print(f\"Computed optimal angles={angles} for U={u}, V={v}\")\n", + "\n", + " tmp_physical_dict = {}\n", + " tmp_logical_dict = {}\n", + " for basis in (\"z_basis\", \"x_basis\"):\n", + " tmp_physical_dict[basis] = aim_physical_circuit(\n", + " angles, basis, ignore_meas_id=ignore_meas_id\n", + " )\n", + " tmp_logical_dict[basis] = aim_logical_circuit(\n", + " angles, basis, ignore_meas_id=ignore_meas_id\n", + " )\n", + "\n", + " circuit_dict[f\"{u}:{v}\"] = {\n", + " \"physical\": tmp_physical_dict,\n", + " \"logical\": tmp_logical_dict,\n", + " }\n", + " print(\"\\nFinished building optimized circuits!\")\n", + " return circuit_dict" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Computed optimal angles=[1.5846845738799267, 1.5707961678256028] for U=1, V=-9\n", + "Computed optimal angles=[4.588033710930825, 4.712388365176642] for U=1, V=-1\n", + "Computed optimal angles=[-1.588651490745171, 1.5707962742876598] for U=1, V=7\n", + "Computed optimal angles=[1.64012940802256, 1.5707963354922125] for U=5, V=-9\n", + "Computed optimal angles=[2.1293956916868737, 1.5707963294715355] for U=5, V=-1\n", + "Computed optimal angles=[-1.6598458659836037, 1.570796331040382] for U=5, V=7\n", + "Computed optimal angles=[1.695151467539617, 1.5707960973500679] for U=9, V=-9\n", + "Computed optimal angles=[2.4149519241823376, 1.5707928509325972] for U=9, V=-1\n", + "Computed optimal angles=[-1.7301462729177735, 1.570796033796985] for U=9, V=7\n", + "\n", + "Finished building optimized circuits!\n" + ] + } + ], + "source": [ + "sim_circuit_dict = generate_circuit_set()\n", + "circuit_layers = sim_circuit_dict.keys()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up submission and decoding workflow " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this section, we define various helper functions that will play a role in generating the associated energies of the AIM circuits based on the circuit samples (in the different bases), as well as decode the logical circuits with post-selection informed by the `[[4,2,2]]` code:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "def _num_qubits(counts: Mapping[str, float]) -> int:\n", + " for key in counts:\n", + " if key.isdecimal():\n", + " return len(key)\n", + " return 0\n", + "\n", + "\n", + "def process_counts(\n", + " counts: Mapping[str, float],\n", + " data_qubits: Sequence[int],\n", + " flag_qubits: Sequence[int] = (),\n", + ") -> dict[str, float]:\n", + " new_data: dict[str, float] = {}\n", + " for key, val in counts.items():\n", + " if not all(key[i] == \"0\" for i in flag_qubits):\n", + " continue\n", + "\n", + " new_key = \"\".join(key[i] for i in data_qubits)\n", + "\n", + " if not set(\"01\").issuperset(new_key):\n", + " continue\n", + "\n", + " new_data.setdefault(new_key, 0)\n", + " new_data[new_key] += val\n", + "\n", + " return new_data\n", + "\n", + "\n", + "def decode(counts: Mapping[str, float]) -> dict[str, float]:\n", + " \"\"\"Decode physical counts into logical counts. Should be called after `process_counts`.\"\"\"\n", + "\n", + " if not counts:\n", + " return {}\n", + "\n", + " num_qubits = _num_qubits(counts)\n", + " assert num_qubits % 4 == 0\n", + "\n", + " physical_to_logical = {\n", + " \"0000\": \"00\",\n", + " \"1111\": \"00\",\n", + " \"0011\": \"01\",\n", + " \"1100\": \"01\",\n", + " \"0101\": \"10\",\n", + " \"1010\": \"10\",\n", + " \"0110\": \"11\",\n", + " \"1001\": \"11\",\n", + " }\n", + "\n", + " new_data: dict[str, float] = {}\n", + " for key, val in counts.items():\n", + " physical_keys = [key[i : i + 4] for i in range(0, num_qubits, 4)]\n", + " logical_keys = [physical_to_logical.get(physical_key) for physical_key in physical_keys]\n", + " if None not in logical_keys:\n", + " new_key = \"\".join(logical_keys)\n", + " new_data.setdefault(new_key, 0)\n", + " new_data[new_key] += val\n", + "\n", + " return new_data\n", + "\n", + "\n", + "def ev_x(counts: Mapping[str, float]) -> float:\n", + " ev = 0.0\n", + "\n", + " for k, val in counts.items():\n", + " ev += val * ((-1) ** int(k[0]) + (-1) ** int(k[1]))\n", + "\n", + " total = sum(counts.values())\n", + " ev /= total\n", + " return ev\n", + "\n", + "\n", + "def ev_xx(counts: Mapping[str, float]) -> float:\n", + " ev = 0.0\n", + "\n", + " for k, val in counts.items():\n", + " ev += val * (-1) ** k.count(\"1\")\n", + "\n", + " total = sum(counts.values())\n", + " ev /= total\n", + " return ev\n", + "\n", + "\n", + "def ev_zz(counts: Mapping[str, float]) -> float:\n", + " ev = 0.0\n", + "\n", + " for k, val in counts.items():\n", + " ev += val * (-1) ** k.count(\"1\")\n", + "\n", + " total = sum(counts.values())\n", + " ev /= total\n", + " return ev\n", + "\n", + "\n", + "def aim_logical_energies(\n", + " data_ordering: object, counts_list: Sequence[dict[str, float]]\n", + ") -> tuple[dict[tuple[int, int], float], dict[tuple[int, int], float]]:\n", + " counts_data = {\n", + " data_ordering[i]: decode(\n", + " process_counts(\n", + " counts,\n", + " data_qubits=[1, 2, 3, 4],\n", + " flag_qubits=[0, 5],\n", + " )\n", + " )\n", + " for i, counts in enumerate(counts_list)\n", + " }\n", + " return _aim_energies(counts_data)\n", + "\n", + "\n", + "def aim_physical_energies(\n", + " data_ordering: object, counts_list: Sequence[dict[str, float]]\n", + ") -> tuple[dict[tuple[int, int], float], dict[tuple[int, int], float]]:\n", + " counts_data = {\n", + " data_ordering[i]: process_counts(\n", + " counts,\n", + " data_qubits=[0, 1],\n", + " )\n", + " for i, counts in enumerate(counts_list)\n", + " }\n", + " return _aim_energies(counts_data)\n", + "\n", + "\n", + "def _aim_energies(\n", + " counts_data: Mapping[tuple[int, int, str], dict[str, float]],\n", + ") -> tuple[dict[tuple[int, int], float], dict[tuple[int, int], float]]:\n", + " evxs: dict[tuple[int, int], float] = {}\n", + " evxxs: dict[tuple[int, int], float] = {}\n", + " evzzs: dict[tuple[int, int], float] = {}\n", + " totals: dict[tuple[int, int], float] = {}\n", + "\n", + " for key, counts in counts_data.items():\n", + " h_params, basis = key\n", + " key_a, key_b = h_params.split(\":\")\n", + " u, v = int(key_a), int(key_b)\n", + " if basis.startswith(\"x\"):\n", + " evxs[u, v] = ev_x(counts)\n", + " evxxs[u, v] = ev_xx(counts)\n", + " else:\n", + " evzzs[u, v] = ev_zz(counts)\n", + "\n", + " totals.setdefault((u, v), 0)\n", + " totals[u, v] += sum(counts.values())\n", + "\n", + " energies = {}\n", + " uncertainties = {}\n", + " for u, v in evxs.keys() & evzzs.keys():\n", + " string_key = f\"{u}:{v}\"\n", + " energies[string_key] = u * (evzzs[u, v] - 1) / 4 + v * evxs[u, v]\n", + "\n", + " uncertainty_xx = 2 * v**2 * (1 + evxxs[u, v]) - u * v * evxs[u, v] / 2\n", + " uncertainty_zz = u**2 * (1 - evzzs[u, v]) / 2\n", + "\n", + " uncertainties[string_key] = np.sqrt(\n", + " (uncertainty_zz + uncertainty_xx - energies[string_key] ** 2) / (totals[u, v] / 2)\n", + " )\n", + "\n", + " return energies, uncertainties\n", + "\n", + "\n", + "def _get_energy_diff(\n", + " bf_energies: dict[str, float],\n", + " physical_energies: dict[str, float],\n", + " logical_energies: dict[str, float],\n", + ") -> tuple[list[float], list[float]]:\n", + " physical_energy_diff = []\n", + " logical_energy_diff = []\n", + "\n", + " # Data ordering following `bf_energies` keys\n", + " for layer in bf_energies.keys():\n", + " physical_sim_energy = physical_energies[layer]\n", + " logical_sim_energy = logical_energies[layer]\n", + " true_energy = bf_energies[layer]\n", + " u, v = layer.split(\":\")\n", + " print(f\"Layer=({u}, {v}) has brute-force energy of: {true_energy}\")\n", + " print(f\"Physical circuit of layer=({u}, {v}) got an energy of: {physical_sim_energy}\")\n", + " print(f\"Logical circuit of layer=({u}, {v}) got an energy of: {logical_sim_energy}\")\n", + " print(\"-\" * 72)\n", + "\n", + " if logical_sim_energy < physical_sim_energy:\n", + " print(\"Logical circuit achieved the lower energy!\")\n", + " else:\n", + " print(\"Physical circuit achieved the lower energy\")\n", + " print(\"-\" * 72, \"\\n\")\n", + "\n", + " physical_energy_diff.append(\n", + " -1 * (true_energy - physical_sim_energy)\n", + " ) # Multiply by -1 since negative energies\n", + " logical_energy_diff.append(-1 * (true_energy - logical_sim_energy))\n", + " return physical_energy_diff, logical_energy_diff" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "def submit_aim_circuits(\n", + " circuit_dict: object,\n", + " *,\n", + " folder_path: str = \"future_aim_results\",\n", + " shots_count: int = 1000,\n", + " noise_model: cudaq.mlir._mlir_libs._quakeDialects.cudaq_runtime.NoiseModel | None = None,\n", + " run_async: bool = False,\n", + ") -> dict[str, list[dict[str, int]]] | None:\n", + " if run_async:\n", + " os.makedirs(folder_path, exist_ok=True)\n", + " else:\n", + " aim_results = {\"physical\": [], \"logical\": []}\n", + "\n", + " for layer in circuit_dict.keys():\n", + " if run_async:\n", + " print(f\"Posting circuits associated with layer=('{layer}')\")\n", + " else:\n", + " print(f\"Running circuits associated with layer=('{layer}')\")\n", + "\n", + " for basis in (\"z_basis\", \"x_basis\"):\n", + " if run_async:\n", + " u, v = layer.split(\":\")\n", + "\n", + " tmp_physical_results = cudaq.sample_async(\n", + " circuit_dict[layer][\"physical\"][basis], shots_count=shots_count\n", + " )\n", + " file = open(f\"{folder_path}/physical_{basis}_job_u={u}_v={v}_result.txt\", \"w\")\n", + " file.write(str(tmp_physical_results))\n", + " file.close()\n", + "\n", + " tmp_logical_results = cudaq.sample_async(\n", + " circuit_dict[layer][\"logical\"][basis], shots_count=shots_count\n", + " )\n", + " file = open(f\"{folder_path}/logical_{basis}_job_u={u}_v={v}_result.txt\", \"w\")\n", + " file.write(str(tmp_logical_results))\n", + " file.close()\n", + " else:\n", + " tmp_physical_results = cudaq.sample(\n", + " circuit_dict[layer][\"physical\"][basis],\n", + " shots_count=shots_count,\n", + " noise_model=noise_model,\n", + " )\n", + " tmp_logical_results = cudaq.sample(\n", + " circuit_dict[layer][\"logical\"][basis],\n", + " shots_count=shots_count,\n", + " noise_model=noise_model,\n", + " )\n", + " aim_results[\"physical\"].append({k: v for k, v in tmp_physical_results.items()})\n", + " aim_results[\"logical\"].append({k: v for k, v in tmp_logical_results.items()})\n", + " if not run_async:\n", + " print(\"\\nCompleted all circuit sampling!\")\n", + " return aim_results\n", + " else:\n", + " print(\"\\nAll circuits submitted for async sampling!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "def _get_async_results(\n", + " layers: object, *, folder_path: str = \"future_aim_results\"\n", + ") -> dict[str, list[dict[str, int]]]:\n", + " aim_results = {\"physical\": [], \"logical\": []}\n", + " for layer in layers:\n", + " print(f\"Retrieving all circuits counts associated with layer=('{layer}')\")\n", + " u, v = layer.split(\":\")\n", + " for basis in (\"z_basis\", \"x_basis\"):\n", + " file = open(f\"{folder_path}/physical_{basis}_job_u={u}_v={v}_result.txt\", \"r\")\n", + " tmp_physical_results = cudaq.AsyncSampleResult(str(file.read()))\n", + " physical_counts = tmp_physical_results.get()\n", + "\n", + " file = open(f\"{folder_path}/logical_{basis}_job_u={u}_v={v}_result.txt\", \"r\")\n", + " tmp_logical_results = cudaq.AsyncSampleResult(str(file.read()))\n", + " logical_counts = tmp_logical_results.get()\n", + "\n", + " aim_results[\"physical\"].append({k: v for k, v in physical_counts.items()})\n", + " aim_results[\"logical\"].append({k: v for k, v in logical_counts.items()})\n", + "\n", + " print(\"\\nObtained all circuit samples!\")\n", + " return aim_results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running a CUDA-Q noisy simulation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this section, we will first explore the performance of the physical and logical circuits under the influence of a device noise model. This will help us predict experimental results, as well as understand the dominant error sources at play. Such a simulation can be achieved via CUDA-Q's density matrix simulator: " + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "cudaq.reset_target()\n", + "cudaq.set_target(\"density-matrix-cpu\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "def get_device_noise(\n", + " depolar_prob_1q: float,\n", + " depolar_prob_2q: float,\n", + " *,\n", + " readout_error_prob: float | None = None,\n", + " custom_gates: list[str] | None = None,\n", + ") -> cudaq.mlir._mlir_libs._quakeDialects.cudaq_runtime.NoiseModel:\n", + " noise = cudaq.NoiseModel()\n", + " depolar_noise = cudaq.DepolarizationChannel(depolar_prob_1q)\n", + "\n", + " noisy_ops = [\"z\", \"s\", \"x\", \"h\", \"rx\", \"rz\"]\n", + " for op in noisy_ops:\n", + " noise.add_all_qubit_channel(op, depolar_noise)\n", + "\n", + " if custom_gates:\n", + " custom_depolar_channel = cudaq.DepolarizationChannel(depolar_prob_1q)\n", + " for op in custom_gates:\n", + " noise.add_all_qubit_channel(op, custom_depolar_channel)\n", + "\n", + " # Two qubit depolarization error\n", + " p_0 = 1 - depolar_prob_2q\n", + " p_1 = np.sqrt((1 - p_0**2) / 3)\n", + "\n", + " k0 = np.array(\n", + " [[p_0, 0.0, 0.0, 0.0], [0.0, p_0, 0.0, 0.0], [0.0, 0.0, p_0, 0.0], [0.0, 0.0, 0.0, p_0]],\n", + " dtype=np.complex128,\n", + " )\n", + " k1 = np.array(\n", + " [[0.0, 0.0, p_1, 0.0], [0.0, 0.0, 0.0, p_1], [p_1, 0.0, 0.0, 0.0], [0.0, p_1, 0.0, 0.0]],\n", + " dtype=np.complex128,\n", + " )\n", + " k2 = np.array(\n", + " [\n", + " [0.0, 0.0, -1j * p_1, 0.0],\n", + " [0.0, 0.0, 0.0, -1j * p_1],\n", + " [1j * p_1, 0.0, 0.0, 0.0],\n", + " [0.0, 1j * p_1, 0.0, 0.0],\n", + " ],\n", + " dtype=np.complex128,\n", + " )\n", + " k3 = np.array(\n", + " [[p_1, 0.0, 0.0, 0.0], [0.0, p_1, 0.0, 0.0], [0.0, 0.0, -p_1, 0.0], [0.0, 0.0, 0.0, -p_1]],\n", + " dtype=np.complex128,\n", + " )\n", + " kraus_channel = cudaq.KrausChannel([k0, k1, k2, k3])\n", + "\n", + " noise.add_all_qubit_channel(\"cz\", kraus_channel)\n", + " noise.add_all_qubit_channel(\"cx\", kraus_channel)\n", + "\n", + " if readout_error_prob is not None:\n", + " # Readout error modeled with a Bit flip channel on identity before measurement\n", + " bit_flip = cudaq.BitFlipChannel(readout_error_prob)\n", + " noise.add_all_qubit_channel(\"meas_id\", bit_flip)\n", + " return noise" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, with our example noise model defined above, we can synchronously & noisily sample all of our AIM circuits by passing `noise_model=cudaq_noise_model` to the workflow containing function `submit_aim_circuits()`:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "# Example parameters that can model execution on hardware at the high, simulation, level:\n", + "# Take single-qubit gate depolarization rate: ~0.2% or better (fidelity ≥99.8%)\n", + "# Take two-qubit gate depolarization rate: ~1–2% (fidelity ~98–99%)\n", + "cudaq_noise_model = get_device_noise(0.002, 0.02, readout_error_prob=0.02)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running circuits associated with layer=('1:-9')\n", + "Running circuits associated with layer=('1:-1')\n", + "Running circuits associated with layer=('1:7')\n", + "Running circuits associated with layer=('5:-9')\n", + "Running circuits associated with layer=('5:-1')\n", + "Running circuits associated with layer=('5:7')\n", + "Running circuits associated with layer=('9:-9')\n", + "Running circuits associated with layer=('9:-1')\n", + "Running circuits associated with layer=('9:7')\n", + "\n", + "Completed all circuit sampling!\n" + ] + } + ], + "source": [ + "aim_sim_data = submit_aim_circuits(sim_circuit_dict, noise_model=cudaq_noise_model)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "data_ordering = []\n", + "for key in circuit_layers:\n", + " for basis in (\"z_basis\", \"x_basis\"):\n", + " data_ordering.append((key, basis))" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "sim_physical_energies, sim_physical_uncertainties = aim_physical_energies(\n", + " data_ordering, aim_sim_data[\"physical\"]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "sim_logical_energies, sim_logical_uncertainties = aim_logical_energies(\n", + " data_ordering, aim_sim_data[\"logical\"]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To analyze our simulated energy results in the above cells, we will compare them to the brute-force computed exact ground state energies for the AIM Hamiltonian. For simplicity, these are already stored in the dictionary `bf_energies` below:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "bf_energies = {\n", + " \"1:-9\": -18.251736027394713,\n", + " \"1:-1\": -2.265564437074638,\n", + " \"1:7\": -14.252231964940428,\n", + " \"5:-9\": -19.293350575766127,\n", + " \"5:-1\": -3.608495283014149,\n", + " \"5:7\": -15.305692796870582,\n", + " \"9:-9\": -20.39007993367173,\n", + " \"9:-1\": -5.260398644698076,\n", + " \"9:7\": -16.429650912487233,\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With the above metric, we can assess the performance of the logical circuits against the physical circuits by considering how far away the respective energies are from the brute-force expected energies. The cell below computes these energy deviations:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Layer=(1, -9) has brute-force energy of: -18.251736027394713\n", + "Physical circuit of layer=(1, -9) got an energy of: -15.929\n", + "Logical circuit of layer=(1, -9) got an energy of: -17.46016175277361\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(1, -1) has brute-force energy of: -2.265564437074638\n", + "Physical circuit of layer=(1, -1) got an energy of: -1.97\n", + "Logical circuit of layer=(1, -1) got an energy of: -2.176531948420889\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(1, 7) has brute-force energy of: -14.252231964940428\n", + "Physical circuit of layer=(1, 7) got an energy of: -12.268\n", + "Logical circuit of layer=(1, 7) got an energy of: -13.26321740664324\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(5, -9) has brute-force energy of: -19.293350575766127\n", + "Physical circuit of layer=(5, -9) got an energy of: -16.8495\n", + "Logical circuit of layer=(5, -9) got an energy of: -18.46681284816878\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(5, -1) has brute-force energy of: -3.608495283014149\n", + "Physical circuit of layer=(5, -1) got an energy of: -3.1965000000000003\n", + "Logical circuit of layer=(5, -1) got an energy of: -3.4531715120183297\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(5, 7) has brute-force energy of: -15.305692796870582\n", + "Physical circuit of layer=(5, 7) got an energy of: -13.336\n", + "Logical circuit of layer=(5, 7) got an energy of: -14.341784541550897\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(9, -9) has brute-force energy of: -20.39007993367173\n", + "Physical circuit of layer=(9, -9) got an energy of: -17.802\n", + "Logical circuit of layer=(9, -9) got an energy of: -19.339249509416753\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(9, -1) has brute-force energy of: -5.260398644698076\n", + "Physical circuit of layer=(9, -1) got an energy of: -4.8580000000000005\n", + "Logical circuit of layer=(9, -1) got an energy of: -5.1227150992242025\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(9, 7) has brute-force energy of: -16.429650912487233\n", + "Physical circuit of layer=(9, 7) got an energy of: -14.3635\n", + "Logical circuit of layer=(9, 7) got an energy of: -15.448422736181264\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n" + ] + } + ], + "source": [ + "sim_physical_energy_diff, sim_logical_energy_diff = _get_energy_diff(\n", + " bf_energies, sim_physical_energies, sim_logical_energies\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Both physical and logical circuits were subject to the same noise model, but the `[[4,2,2]]` provides additional information that can help overcome some errors. Visualizing the computed energy differences from the above the cell, our noisy simulation provides a preview of the benefits logical qubits can offer:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAACIMAAAVkCAYAAABNJ02+AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/TGe4hAAAACXBIWXMAAB7CAAAewgFu0HU+AAEAAElEQVR4nOzdd1RU1/c28GfovWNXsBCxKwhWBLEbe+81tqgxliTGxBLLV41Go9FYE3vXqLFrFDv2ggVsoGKnd6n3/cNXfg73DkxlAJ/PWq7l7Ln3nM30mbvvPjJBEAQQERERERERERERERERERERUZFgoO8EiIiIiIiIiIiIiIiIiIiIiEh7WAxCREREREREREREREREREREVISwGISIiIiIiIiIiIiIiIiIiIioCGExCBEREREREREREREREREREVERwmIQIiIiIiIiIiIiIiIiIiIioiKExSBERERERERERERERERERERERQiLQYiIiIiIiIiIiIiIiIiIiIiKEBaDEBERERERERERERERERERERUhLAYhIiIiIiIiIiIiIiIiIiIiKkJYDEJERERERERERERERERERERUhLAYhIiIiIiIiIiIiIiIiIiIiKgIYTEIERERERERERERERERERERURHCYhAiIiIiIiIiIiIiIiIiIiKiIoTFIERERERERERERERERERERERFCItBiIiIiIiIiIiIiIiIiIiIiIoQFoMQERERERERERERERERERERFSEsBiEiIiIiIiIiIiIiIiIiIiIqQlgMQkRERERERERERERERERERFSEsBiEiIiIiIiIiIiIiIiIiIiIqAhhMQgRERERERERERERERERERFREcJiECIiIiIios/IjBkzIJPJ5P7NmDFD32kRAD8/P9F9c/r0aX2nRVrA513B5+rqKrqPnj59qu+0CgQ+fgufsLAwmJuby91np06dUmpfvhdRQbd+/XrRY3TQoEH6Tkvn+FpMhUGTJk3kHqNfffWVvlMiIvrsGek7ASIiIso/oaGhePLkCZ4/f474+HgkJyfDxMQEdnZ2sLe3R/HixVGrVi3Y2NjoO1WiXCUmJuLevXsIDQ1FREQEkpKSIAgCLC0t4ezsjIoVK6Jq1aqwtrbWd6pESktMTERwcDCePXuGN2/eIDk5Genp6bCxsYG9vT3s7OzwxRdfwM3NDTKZTN/pEhV6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f+kxIyIi+tykpaVh0aJFcrE+ffqwEISISE9YDEJERERERdLEiRMxfvx4UTwyMhItW7bEixcv9JAVEREREZFuuLm5YdiwYXKxBQsWiJZPJCIi0pV169bh1atX2ZfNzMwwc+ZMPWZERPR5YzEIERERERVZv/32G3r16iWKP3/+HK1atUJ0dLQesiIiIiIi0o3Zs2fD0dEx+/Lz58+xfPlyPWZERESfi6SkJPzyyy9yse+//x7ly5fXU0ZERMRiECIiIiIqsmQyGTZs2AB/f3/Rdffv38eXX36J5ORkPWRGRERERKR9Dg4OmDt3rlxszpw5iIuL01NGRET0uVi0aBFev36dfdnV1RWTJ0/WY0ZERCQTBEHQdxJERERERLoUHx+P4cOHIzY2VnRdmzZtMG7cuPxPioiIiIhIB7KysrBgwQKkpKRkxzp06AAPDw89ZkVEREXdokWLEB8fn325RYsWaNSokR4zIiIiFoMQERERERERERERERERERERFSFcJoaIiIiIiIiIiIiIiIiIiIioCGExCBEREREREREREREREREREVERwmIQIiIiIiIiIiIiIiIiIiIioiKExSBERERERERERERERERERERERQiLQYiIiIiIiIiIiIiIiIiIiIiKECN9J0CkyPv373Hnzh0AgLOzM4yM+HAlIiIiIiIiIiIiIiIiIqKiJSMjAxEREQCAGjVqwMzMTOMxeXSdCqw7d+7A29tb32kQERERERERERERERERERHliytXrsDLy0vjcbhMDBEREREREREREREREREREVERws4gVGA5Oztn///KlSsoWbKkHrMhIiIiIiIiIiIiIiIiIiLSvtevX2evmvHpcXJNsBiECiwjo/97eJYsWRJlypTRYzZERERERERERERERERERES69elxck1wmRgiIiIiIiIiIiIiIiIiIiKiIoTFIERERERERERERERERERERERFCItBiIiIiIiIiIiIiIiIiIiIiIoQFoMQERERERERERERERERERERFSEsBiEiIiIiIiIiIiIiIiIiIiIqQlgMQkRERERERERERERERERERFSEsBiEiIiIiIiIiIiIiIiIiIiIqAgx0ncCRVlUVBQePnyI8PBwvH37FklJScjKyoKtrS2cnJxQq1YtVK5cGTKZTN+pAgAiIiIQGBiI0NBQJCYmwsLCAi4uLvD29kbZsmX1nR4REREREREREREREREREREpgcUgWnT79m0cOnQIFy5cwM2bN/H69es897Gzs0OvXr0watQo1KxZMx+yFDt79ixmz56NkydPIisrS3Kb+vXrY/LkyejYsWM+Z0dERERERERERERERERERESqkAmCIOg7iaKiX79+2LJli1r7GhgYYMyYMZg3bx7Mzc21nJm0jIwMTJgwAX/88YfS+/To0QN///03LC0tdZjZBy9evMjuSBIeHo4yZcrofE4iIiIiIiIiIiIiIiIiIqL8pItj4wYaj0BKMTExgaOjI6ysrCSXhcnKysLSpUvRpk0bJCYm6jyfrKws9O3bV2EhiK2trWR8586daNu2Ld6/f6/L9IiIiIiIiIiIiIiIiIiIiEhNLAbRgRIlSqBXr15YsWIFAgMDER0djdTUVERGRiIhIQGJiYm4cOECxo0bJ+oCcubMGYwYMULnOc6dOxc7d+6Ui9WoUQM7d+5EQkICYmNjkZycjMOHD6NRo0Zy2509exbffPONznMkIiIiIiIiIiIiIiIiIiIi1XGZGC3at28fnJ2d0bBhQ8nuH1KePHmC1q1b4/Hjx3Lxs2fPwsfHRxdpIjw8HG5ubkhNTc2OtWzZEnv37oWFhYVo+4yMDAwdOhQbN26Ui1+5cgVeXl46yRHgMjFERERERERERERERERERFT0cZmYAq5Tp05o1KiR0oUgAFCxYkUcPHgQJiYmcvFNmzZpO71ss2bNkisEKVmyJHbs2CFZCAIARkZGWLt2LapXry4X//nnn3WWIxEREREREREREREREREREamHxSAFQOXKldGhQwe52JkzZ3QyV0REBNatWycXmz17Nuzs7HLdz9jYGL///rtc7Pjx47h165Z2EyQiIiIiIiIiIiIiIiIiIiKNsBikgKhfv77c5VevXulkngMHDiAjIyP7sq2tLXr16qXUvv7+/qhUqZJcbO/evVrNj4iIiIiIiIiIiIiIiIiIiDTDYpACwtraWu5yVlaWTubZv3+/3OV27dopXB4mJ5lMhh49euQ6HhEREREREREREREREREREekXi0EKiJcvX8pdLlu2rE7mCQgIkLvcqFEjlfZv2LCh3OXbt28jKipK47yIiIiIiIiIiIiIiIiIiIhIO1gMUkAcPHhQ7rK/v7/W5wgPD0dCQoJcrF69eiqNkXM5GwAIDg7WKC8iIiIiIiIiIiIiIiIiIiLSHhaDFAArVqzAjRs3si8bGhpizJgxWp8nJCREFKtQoYJKYzg6OsLGxibPcYmIiIiIiIiIiIiIiIiIiEg/WAyiR4mJiZg6daqo8GP69OmoWrWq1ud7+PCh3GVra2vY2dmpPE7OJWwePHigSVpERERERERERERERERERESkRUb6TqAoEwQBq1atkoulpqYiKioKQUFB+O+//5CUlJR9nYGBAX766SdMnTpVJ/lER0fLXS5RooRa45QsWRL37t3LvhwTE6NRXkRERERERERERERERAWNIAh4//494uLikJqaiszMTGRmZuo7LSIi0jJDQ0MYGhrC1NQUtra2MDMzg0wm03daGmMxiA5lZmZi1KhReW5nZGSENm3a4Oeff4a3t7fO8klMTJS7bGFhodY45ubmuY5LRERERERERERERERUWGVkZCA2NhZxcXFIS0vTdzpERKRjGRkZAIDk5GTExMTAxMQEtra2sLOzg5FR4S2pKLyZFyGNGzdG165dUatWLZ3O82kXEgAwMzNTa5ycxSA5x1XWixcvcr3+9evXao1LRERERERERERERESkjvT0dDx79gzp6emi62QyGQwNDfWQFRER6VJmZiYEQci+nJaWhoiICMTGxsLFxQXGxsZ6zE59LAYpAE6fPo3Tp0/jxx9/xJ9//olOnTrpZJ6UlBS5yyYmJmqNY2pqmuu4yipbtqxa+xEREREREREREREREWlbZmYmwsPD5QpBLCwsYGtrC2traxaCEBEVYZmZmUhISEBcXBySk5MBfCgQDA8Ph4uLS6F8DzDQdwJFmZGREQRBkPuXkJCAJ0+eYNeuXejdu7dcQcbr16/RuXNnLFiwQCf55OwEom5rs9TU1FzHJSIiIiIiIiIiIiIiKkyysrIQHh6efQzE2NgYFStWhIuLC+zs7ArlQUAiIlKeoaEh7Ozs4OLigooVK2Z3A0lNTUV4eDiysrL0nKHq2Bkkn1lZWcHKygoVKlRAt27dEBISgt69e+PWrVvZ23z//fdwd3dH+/bttT73p96/f6/WODk7geQcV1nh4eG5Xv/69Wt4e3urNTYREREREREREREREZGyYmJiso9/GBoaoly5cmp3WCciosLNxMQE5cqVw9OnT5GZmYmUlBTExMTA0dFR36mphMUgeubu7o5Tp06hQYMGePDgQXZ87NixaNu2rVYrTXMWbXxsb6MqbRWDlClTRq39iIiIiIiIiIiIiIiItCkhISH7/2XKlGEhCBHRZ87ExARlypTBs2fPAACJiYmFrhiEy8QUAPb29vjjjz/kYs+ePcPhw4e1Ps+n3r59q9Y4r1+/znVcIiIiIiIiIiIiIiKiwiIjIyP7RFgTExNYWFjoOSMiIioILCwssosDk5OTkZmZqeeMVMNikAKiefPmKFWqlFwsICBAq3N88cUXcpfj4+MRGxur8jg5l3fJOS4REREREREREREREVFhkZSUlP1/a2trPWZCREQFzafvC4mJiXrMRHUsBikgZDIZateuLRcLCwvT6hzu7u6iWGhoqEpjREdHIz4+Ps9xiYiIiIiIiIiIiIiICoNPD+5ZWVnpMRMiIipoPn1fYDEIqc3W1lbucnJyslbHL1u2rOhDzOXLl1Ua49KlS6JYlSpVNMqLiIiIiIiIiIiIiIhIX9LT07P/b25ursdMiIiooPn0feHT94vCgMUgBUhUVJTcZWdnZ62OL5PJ0LRpU7nYhQsXVBoj5/Y1a9aEk5OTxrkRERERERERERERERHpQ1ZWFgDAwMAAMplMz9kQEVFBIpPJst8bPr5fFBYsBikgsrKycOPGDblYqVKltD5Px44d5S4fPHhQpQ4kO3fuzHU8IiIiIiIiIiIiIiKiwiQzMxPAh2IQIiKinAwNDQH83/tFYcF3tQLi4MGDiIyMlIs1b95c6/O0b98eRkZG2Zfj4uKwfft2pfY9deoUHj9+LBfr1KmTNtMjIiIiIiIiIiIiIiIiIiIiDbEYREtSU1PV3jciIgLjx4+Xizk4OMDPzy/X/VxdXbPb0shksjy3B4BixYph4MCBcrGff/4ZsbGxue6Xnp6Ob7/9Vi7WvHlzeHh45DknERERERERERERERERERER5R8Wg2jJ2LFjMXr0aDx//lyl/W7dugU/Pz+EhobKxWfOnAkTExNtppht2rRpcmO/fv0avXr1UrhcTEZGBoYNG4Y7d+7IxWfPnq2T/IiIiIiIiIiIiIiIiIiIiEh9RnlvQspIS0vDmjVrsGLFCjRq1AgdO3aEp6cnatasCUdHx+ztBEHA06dPcfXqVezYsQP79+8XrS3k7++PkSNH6izXcuXK4aeffsL06dOzY8eOHUP9+vUxbdo0tGnTBpaWlkhJScHZs2cxa9YsXLhwQW6MIUOGoF69ejrLkYiIiIiIiIiIiIiIiHRnxaqjWLH6qNbHHTW8NUaNaK31cYmISDUsBtEyQRBw/vx5nD9/PjtmaGgIGxsbZGVlISEhAVlZWQr3b9q0KQ4cOABDQ0Od5vnzzz/jzp072L17d3bszp076N69OwDA1tYW8fHxEARBtG/jxo2xbNkyneZHREREREREREREREREupOQmILXb2J0Mi4REekfi0HyQWZmJmJicn8ztbCwwC+//ILx48frvBAEAAwMDLB161YUK1YMf/75p+j6uLg4yf26du2K9evXw9zcXNcpEhERERERERERERERkY5YW5mjZAl7hddnZQl4+y5WLla8mB0MDGR5jktERPrHYhAtWbx4MVq3bo2jR4/iwoULePz4cZ77GBoaonbt2ujXrx/69esHJyenfMj0/xgbG2P58uXo3r07Zs+ejVOnTkl2AgEAb29vTJ48GZ07d87XHImIiIiIiIiIiIiIiEj7Ro3IfTmXyKh4VKk5Vi52+r9ZcHK00XVqRESkBSwG0RJ7e3v06tULvXr1AgDExMQgODgYz549w7t375CUlASZTAYbGxvY2tqiYsWKqFWrFiwsLNSe8+nTp1rJ3c/PD35+fnj79i0uXbqE0NBQJCUlwdzcHOXKlUO9evVQrlw5rcxFREREREREREREREREREREusViEB2xt7dHw4YN0bBhQ32norTixYujY8eO+k6DiIiIiIiIiIiIiIiIiIiINGCg7wSIiIiIiIiIiIiIiIiIiKhgWr9+PWQymdy/06dP6zutImfGjBmi21lbK0UUJXw8Ko+dQYiIiIiIiIiIiIiIiIiICjiZTKb0tgYGBrCxsYGdnR2KFSsGT09PeHt7o02bNihevLgOsySigoKdQYiIiIiIiIiIiIiIiIiIipCsrCzExsbi6dOnuHLlClasWIHBgwejXLly6Nu3L+7du6fvFIlIx1gMQkRERERERERERERERET0GUhLS8PWrVvh6emJ33//Xd/pEJEOcZkYIiIiIiIiIiIiIiIiIqJCyNraGgYG4vP/s7KykJCQoHC/1NRUjB8/HpGRkZg9e7YuUyQiPWFnECIiIiIiIiIiIiIiIiKiQigoKAixsbGif/Hx8UhPT0dISAiWLVsGNzc3yf3nzJmDrVu35nPWRJQfWAxCRERERERERERERERERFTEGBkZoXLlyhg9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nBxt9JQRkWJVq1ZFcHBw9mUzMzO8fPkSDg4OWpsjMzMToaGhePz4MV68eIH4+HgkJyfDwsICNjY2KFOmDGrVqoVSpUppbU5tyMrKwu3btxEaGorIyEhERUXByMgI1tbWKF26NKpUqYKKFSvCwED9PhLx8fG4cOECnj17hujoaBgYGMDe3h6VKlWCl5cXbGwK9+tGfrxP6OLYuJHGIxAREREREREREREREREREenBxYsX5QpBAKBr165aLQQBAENDQ7i5uRW6ojEDAwPUqVMHderU0dkcNjY2aNOmjc7GJ/VwmRgiIiIiIiIiIiIiIiIiIiqUVq5cKYoNHz5cD5kQFSwsBiEiIiIiIiIiIiIiIiIiokLn3bt32LVrl1ysevXqaNKkiZ4yIio4WAxCRERERERERERERERERESFzoIFC/D+/Xu52OjRo/WUDVHBwmIQIiIiIiIiIiIiIiIiIiIqVG7evIklS5bIxYoVK4YBAwboKSOigoXFIEREREREREREREREREREVCi8efMGy5YtQ4sWLZCeni533eTJk2FhYaGnzIgKFiN9J0BERERERERERERERJRfVqw6ihWrj2p93FHDW2PUiNZaH5dIV/J6LmRlCaKYX/OpMDCQ5TounwukbVu3bsXXX38NAEhNTRUtC/NR1apVs7cjIhaDEBERERERERERERHRZyQhMQWv38ToZFyiwkSd58Lbd7FKjUukTWlpaYiLi8t1GwsLC2zcuBGmpqb5lBVRwVegikHevn2Lhw8f4unTpwgPD0dCQgKSkpKQkZEBCwsLWFpaolixYnBxcUH58uXh7u4OQ0NDfadNRERERERERERERESFhLWVOUqWsFd4fVaWIDrgXbyYXZ7dEKytzLWRHlG+yeu5oMm4RPmpWLFi2LZtGzw9PfWdClGBotdikMePH+PIkSM4ffo0rl69ipcvX6q0v6mpKWrVqoV69eqhVatW8Pf3Z7UXEREREREREREREREpNGpE7ktYREbFo0rNsXKx0//NgpOjja5TI8pXeT0XiAoqQ0ND2Nvbo2rVqmjfvj2GDh0Ke3vtFzYRFXb5XgwSFhaGjRs3YuvWrXj8+HF2XBDE647l5f3797hy5QquXLmCP/74A2ZmZmjRogUGDBiA9u3bw9jYWJupExERERERERERERERERFRPho0aBAGDRqk7zSICp18KwbZu3cvlixZgnPnzgEQF3/IZLm3V8vNx7FSUlJw4MABHDhwAPb29hg8eDC++eYblC1bVv3EiYiIiIiIiIiIiIiIiIiIiAoRA10OnpGRgZUrV8LNzQ3dunXDuXPnIAgCBEGATCaT+/cxruo/AJLjREdHY9GiRahYsSJ69+6Ne/fu6fJPJSIiIiIiIiIiIiIiIiIiIioQdNIZRBAEbNq0Cb/88guePn0qKtr4uM1Hzs7OqFWrFmrUqAEXFxeUKVMGJUuWhIWFBczNzWFkZISUlBSkpKQgOjoaL168wMuXLxESEoLbt2/j4cOHyMjIyB7v0zkyMjKwc+dO7N69G71798aMGTNQoUIFXfzZRERERERERERERERERERERHqn9WKQixcvYsyYMbh9+7ZcEQjwfwUgzs7OaNOmDZo2bYqmTZuiXLlyGs2ZmpqKwMBABAQE4L///sOlS5dEc2dmZmLLli3YuXMnJkyYgJ9//hkWFhYazUtERERERERERERERERERERU0Gh1mZgBAwbAx8cnuxDk0yIQGxsbjBw5EidPnsTr16+xfv16DBw4UONCEAAwNTWFn58ffvnlF1y4cAEvXrzAH3/8gQYNGsgtSwMAaWlpmD9/PqpUqYJz585pPDcRERERERERERERERERERFRQaLVYpDNmzfLXRYEAfXq1cP69evx+vVr/Pnnn2jatCkMDLQ6rUjJkiUxevRoXLhwAXfu3MHYsWNhZWWVXRQiCAJevHiBgIAAneZBRERERERERERERERERERElN90UpUhCAJatWqF06dPIzAwEAMGDICZmZkupspTtWrVsGTJEjx//hyzZs2Ck5OTXvIgIiIiIiIiIiIiIiIiIiIiyg9aLwbx8fHBpUuXcOTIETRp0kTbw6vN1tYWP/30E54+fYpZs2bB2tpa3ykRERERERERERERERERERERaZ2RNgc7ePAg2rZtq80htc7c3Bz/j737Dq+qSt8+fu8kJyFAEkIJQarUgBIkVEUgtAmCgKJYR8ogSBFQnBn5iaIijjqOjg2lCYhiQUURBYKFIkqTGmGCEDokJJQUSEjd7x+8OXJIgJR9spPw/VzXuebstdd+1nOU0ci5WWvy5MkaNWqU9u/fb3c7AAAAAAAAAAAAAAAAlrI0DFLagyAXq1atmqpVq2Z3GwAAAAAAAAAAAAAAAJay/JgYAAAAAAAAAAAAAAAA2IcwCAAAAAAAAAAAAAAAQDlCGAQAAAAAAAAAAAAAAKAccUsYpHfv3vriiy+UmZnpjvIAAAAAAAAAAAAAAAC4DLeEQVauXKl7771X1113nSZOnKioqCh3LAMAAAAAAAAAAAAAAIBLuPWYmFOnTunNN9/UTTfdpPbt22vWrFlKSUlx55IAAAAAAAAAAAAAAADXNLeGQQzDkGmaMk1Tv/32m0aPHq1atWppyJAhWrNmjTuXBgAAAAAAAAAAAAAAuCa5NQwiXQiEGIYhSTJNU6mpqfroo4/UvXt3NWnSRC+//LJiY2Pd3QYAAAAAAAAAAAAAAMA1wS1hkOnTp6tNmzbOXUGkP0MhF+8WEhMTo8mTJ6t+/fq6/fbb9fXXXys7O9sdLQEAAAAAAAAAAAAAAFwTvNxRdPTo0Ro9erR27dql999/XwsXLlRCQoIk151CpAu7hWRlZWn58uVavny5atSoocGDB2vYsGFq3ry5O9oDAAAAAAAAAAAArmmvx+zU6zE7La87sVGoJjYKtbwuAKBw3HpMzA033KDXX39dx44d05dffqnbb79dnp6ezt1CJOXZLSQ+Pl6vvfaabrzxRt1yyy2aO3euzp075842AQAAAAAAAAAAgGtKcmaGjp0/Z/krOTPD7o8GAJCbdgbJs4iXl+68807deeedOnHihD744APNnz9f0dHRkvLfLUSSNm7cqI0bN+qxxx7TPffco2HDhqlTp04l0TIAAAAAAAAAAABQbvk7vFW7QqXL3s8xTcWmp7qM1fKpKI+LvtO7XF0AgP0M8+JtOkrY+vXrNXfuXC1atEgpKSkXGsonFHLxeNOmTTV8+HANHjxYQUFBJdswStTRo0dVt25dSdKRI0dUp04dmzsCAAAAAAAAAJR3J08lq3noOJex/+18W9Wr+dvUEdxt7969ysrKkpeXl5o0aWJ3O6VGQnqagiIXuIzFRwxWDR9fmzoCAHuUxL8n3PHduFuPibmam2++WbNnz1ZcXJzmzZunrl27SvozBJK7Y8jFx8js2bNHTz75pOrWras77rhD3377rXJycuz8GAAAAAAAAAAAAAAAAKWGrWGQXL6+vhoyZIhWrVqlvXv36qmnnlKdOnWcARApbzAkMzNTS5cu1YABA1SnTh099dRT2rt3r82fBAAAAAAAAAAAAAAAwF6lIgxysYYNG2ratGk6ePCgVqxYoXvuuUfe3t75BkNyx+Li4vTKK68oJCREXbp00YIFC5SWlmbzJwEAAAAAAAAAAAAAlDXz58932azAMAytXr3a7rZKndWrV+f56zR//ny728L/V+rCILkMw9Bf/vIXffrpp4qNjdVbb72lsLCwK+4WYpqmfvnlFw0bNky1atXSqFGjtHHjRps/CQAAAAAAAAAAAAAUz6VfuhuGoYMHD9rdFoBSqtSGQS5WpUoVPfroo/rtt9+0Y8cOjRs3TlWrVr3ibiHJycmaNWuWOnXqZHP3AAAAAAAAAAAAAAAAJadMhEEu1rJlS7355ps6fvy4Fi1apN69e8vDw8MZArk4CSfJGRYBAAAAAAAAAAAAAAC4FpS5MEguh8Ohu+++W8uWLdPhw4f1wgsvyNfX1+62AAAAAAAAAAAAAAAAbOVldwPFFRMTo7lz52rBggU6f/68JDl3CAEAAAAAAAAAAAAAALjWlMkwSGpqqhYtWqR58+Zp3bp1klyPgyEIAgAAAAAAAAAAAAAoiqFDh2ro0KF2twEUS5kKg/zyyy+aO3euPv/8c507d07S5UMgueNdunQp2SYBAAAAAAAAAAAAAABsVOrDILGxsfrggw80f/587d27V9LVAyC1atXSkCFD9Le//U2NGzcu2YYBAAAAAAAAAAAAAABsVCrDIJmZmVqyZInmzZunlStXKicn54rHwJimKS8vL/Xt21fDhw9Xnz595OHhUdJtAwAAAAAAAAAAAGVaRk62lsQd1NexB/Pc6/brUnUIDFLvoLoaENxA3h6eJd8gAKBASlUYZMeOHZo7d64+/vhjnT59WtKfu33kFwCRpGbNmulvf/ubBg8erJo1a5ZswwAAAAAAAAAAAEA5kJmTrf/GROn1/Tt1Ij0t3zm7Us5oV8oZzT28R8E+FfV4w5Z6vFFLOQiFXFNycnK0bds2HThwQPHx8UpMTFSVKlUUFBSk66+/Xq1bt3bbH9yPj4/Xpk2bdPz4cSUkJMjX11d16tRRWFhYmTkx4vDhw4qKitLJkyd18uRJpaeny8/PT0FBQWrevLlCQkLk7e1drDUyMzO1b98+RUdHKy4uTsnJyTJNU4GBgapataqaN2+uG264Ic938ChfbA+DnDlzRgsXLtS8efO0fft2SVc/BqZSpUoaNGiQhg8frk6dOpVovwAAAAAAAAAAAEB5siv5tAZvW6WtSScL/Exceqqe/N9GfXY8Rgtad9MN/lXd2CFKgy1btui///2vIiMjdfLk5X+t1KhRQxEREZo4caJat25tydpff/213nrrLa1du1bZ2dn5zgkJCdGECRP08MMPy8vrwtfgzz33nJ5//nmXeQcOHFCDBg2uuN78+fM1bNgwl7FVq1YpPDy8SP1HR0fr7bff1sqVK7Vv374rzvX19VWXLl3Ur18/Pfjgg6pSpUqB1ti6dau++eYbrV69Whs2bFB6evoV5wcGBqpnz5564okn1KFDh4J+FJQhtpylYpqmVqxYoXvvvVfXXXedJkyYoG3btsk0TZmmKcMwnK/c+aZpqkOHDpo1a5ZiY2M1d+5cgiAAAAAAAAAAAABAMfx6Ok63rFtSqCDIxbYmndQt65bo19NxFneG0iI+Pl4PPvig2rVrp4ULF14xCCJJCQkJ+uijj9SmTRsNHjz4qvOv5MSJExowYIDuvPNOrVq16rJBEOlC4GL06NHq0KGDDh06VOQ1rXT48GE98MADuuGGG/Tuu+9eNQgiSWlpaYqMjNSjjz6q6667Tlu3br3i/B9//FFNmzZVmzZt9Pzzz2vNmjVXDYJIFzZt+Pzzz9WxY0f17dtXp06dKvDnQtlQojuDxMTEaO7cuVqwYIGOHz8u6eq7gFSvXl0PPfSQhg8frhYtWpRkuwAAAAAAAAAAAEC5tSv5tG7bsFzJWRnFqpOclaHbNizX+s53qIVfoEXdoTSIiYlRRESEYmJiCv2saZr68MMPtXHjRkVGRl51N45LxcXFqVu3boqOji7Uc1u3btXNN9+sX3/9tVDPWe3nn3/WXXfdpYSEhCLXSEtLU3Jy8hXnREVFae/evUVeQ5KWLVumtm3basWKFWrWrFmxaqH0cHsYJDU1VYsWLdLcuXP1yy+/SLp8ACT3noeHhyIiIjR8+HD1799fDofD3W0CAAAAAAAAAAAA14zMnGwN3raq2EGQXMlZGXpo60/a0PkOOTw8LakJex0+fFidOnXSiRMn8tzz9vZWz5491aJFC9WoUUMnT57Url279MMPPygjw/XX1B9//KGbb75Zv/32m2rXrl2gtVNTU9WjR498gyAOh0Ph4eEKDQ1VUFCQEhMTtW/fPkVGRjqDE7GxsRowYID69u1bhE9efEuWLNGgQYOUmZmZ555hGAoNDVXHjh0VFBQkPz8/JSUlKS4uTlu3btXvv/+e73OFUb9+fd14441q0qSJ/P395efnp7S0NJ06dUo7d+7Uhg0blJaW5vLMwYMHNXDgQG3evFkVK1Ys1vooHdwWBlm3bp3mzZunzz//XOfOnZP0ZwgkvwCIJF1//fUaNmyYhg4dqjp16rirNQAAAAAAAAAAAOCa9t+YqCIfDXM5W5NO6r8xUfpnk5ssrYuSl5OTo4ceeihPEMQwDD3yyCP617/+pcDAvLvAnD59WpMmTdLs2bNdxuPi4jR48GD98MMPeb4rzs9TTz2l3bt35xkfNmyYXnnlFdWoUSPPvfT0dP33v//V1KlTlZaWpp07dzpPqyhJe/bs0UMPPZQn0OHl5aURI0boqaeeuuJ34UlJSVqyZIkWLFigH3/8sUBrGoahHj166O6779btt99+1dBNcnKy5s6dq+eee05JSUnO8d27d+upp57SG2+8UaB1Ubq5JQzSrFkz53lHVzsGpkKFCrrzzjs1fPhwde/e3R3tAAAAAABs9t7MFXpv1grL644e2VujH+lteV0AAAAAKM8ycrL13/1Rbqn93/1RerxRS3YHKePefvttrV271mXMMAzNnTtXQ4cOvexzVatW1axZs9SuXTuNHDnS5d5PP/2k6dOn69FHH73i2lu3btXbb7+dZ/zNN9/U+PHjL/ucj4+PJk2apE6dOql3795KTU3VyZPWBp6uJjs7W3fddZdSUlJcxgMDA7VkyRJ17tz5qjUCAgI0ePBgDR48WFu2bFH16tWvOP/WW2/Vrl271Lx58wL36e/vr8cee0wDBgxQ9+7ddfDgQee92bNn69lnn8037IOyxS1hkL1798owDJmmedldQG666SYNHz5cDz74oKpUqeKONgAAAAAApUTK2TTFxp1xS10AAAAAQOEsiTuouPRUt9SOS0/V13EHNei6Rm6pD/fLysrSa6+9lmf86aefvmIQ5GIjRoxQTEyMXnnlFZfx//znPxo9erQ8PS8fFnrzzTeVk5PjMvbII49cMQhysc6dO2vGjBkaPHhwgeZb6ZNPPtGuXbtcxnx8fPTDDz8oLCys0PXatGlz1Tlt27YtdN1c119/vRYtWqSOHTs6/5qnpqZq/vz5evzxx4tcF6WDhzuL5wZBTNOUaZoKCAjQmDFjtGXLFm3dulVjx44lCAIAAAAA1wC/yr6qFRx42VfNoCp5nqkZVOWKz9QKDpRfZd+S/zAAAAAAUMatiD/i1vqR8UfdWh/u9fXXX+vIEddfIw0bNtRTTz1VqDrPPvus6tev7zJ26NAhLVmy5LLPnDlzRosWLXIZq1Klil566aVCrf3QQw+pU6dOhXqmuEzT1Msvv5xnfNq0aUUKgpSUdu3aqVu3bi5jq1atsqkbWMktO4Pkyt0ZpFu3bho+fLjuuusu+fj4uHNJAAAAAEApNPqRKx/ncvJUspqHjnMZW/3DC6pezd/drQEAAADANWdLonuPztiSmODW+nCvxYsX5xkbO3asKlSoUKg6vr6+Gjt2rP75z3/mqT9w4MB8n/nhhx90/vx5l7H77ruvSEeWjBkzRr/88kuhnyuq7du359kVpEaNGho3btxlnig9wsPD9eOPPzqvN27caGM3sIrbdga57rrrNHnyZO3bt08//vijHnjgAYIgAAAAAAAAAAAAgM32nE10b/1zSW6tD/f69ddfXa4Nw9ADDzxQpFoPPfSQ8zSJy9W/WH4hhEGDBhVp7TvvvFMOh6NIzxbF6tWr84wNGzasTHxHHhwc7HIdHx+vkyfdGxqD+7llZ5DvvvtOERER8vBw6yk0AAAAAAAAAAAAAAopPSfbrfXPZ2e5tT7cJy4uTocOHXIZa9iwYZ6wQEEFBwerYcOGiomJcY4dOHBAJ06cUM2aNfPM37x5s8u1YRhq06ZNkdb29fVVixYttGPHjiI9X1hr167NM3bp8Ssl4dy5c/ruu++0efNm7dy5UzExMUpJSVFKSorS0tIKXOfMmTOqXr26GzuFu7klDHLbbbe5oywAAAAAAAAAAACAYvLx8NR5NwZCKni65StIlIDDhw/nGbvpppuKVTMsLMwlDCJJR44cyTcMcvz4cZfr2rVrKyAgoMhr33jjjSUWBtm/f3+esQ4dOpTI2tKFv6bPPPOMvvjiC507d67Y9RITE4vfFGzF1h0AAAAAAAAAAADANaRZ5SrurV+p6F/ew15nzpzJM5ZfaKMw8ns+v3XyG69atWqx1g4MDCzW84Vx6tQpl2sfH58SW3/OnDkKCQnRBx98YEkQRJJSU1MtqQP7lNpY3pEjR7Rt2zadPHlSp06dcm5ZM2XKFJs7AwAAAAAAAAAAAMquNlWqa0fyqatPLHL9Gm6rDffKL6Th7+9frJr57exx+vTpfOcmJSW5XPv5+RVr7eL2XhiXhkGqVKlSIuu+9dZbmjBhQoHmenp6qkKFCvLyco0JZGRk5DlCxjRNy3qEPUpVGOT48eN6/fXX9dVXX+ngwYP5zrlSGOTjjz9WXFyc87pz585q166d1W0CAAAAAAAAAAAAZVbvoLqae3iP2+pHBNVxW22Ubz4+PsrKynJeZ2RkFKtecZ8v7aKiovTEE0/kGffy8lK/fv3UrVs3tWnTRnXq1FGtWrXkcDjyrTN//nwNGzbM3e2ihJWKMEh2draeeuopvfXWW8rIyLhsysgwjCvWOXbsmCZNmuS87t69u77//ntLewUAAAAAAAAAAADKsgHBDRTsU1Fx6dYfAxHsU1F3BDewvC5KRn7HmiQnJxer5qW7fUiXP/6lSpUqLsecuGNtd6lWrZqOHTvmvE5MTHT7mpMmTXIJz0gXNkxYuHCh6tatW+A6Vh0tg9LFw+4GTp48qW7duuk///mP0tPTZZqmDMPI8yqIUaNGObf6MU1Tq1at0pEjR9zZPgAAAAAAAAAAAFCmeHt46vGGLd1S+/GGLeXw8HRLbbhffmGQEydOFKtmfs/nt05+48eOHSvWcSUl+V1xtWrVXK7T09PzPXbHKomJiVq5cqXLWMOGDbVs2bJCBUGkyx/bg7LN1jBIRkaG+vfvr3Xr1rmEQEzTdHkVlJ+fn+655x7nM6Zp6uuvv3ZT9wAAAAAAAAAAAEDZ9HijlgoLqG5pzTYB1TWxUailNVGy6tWrl2ds+/btxaq5bdu2PGOXCyu0aNHC5frs2bPau3evpWu7S6NGjfKMbdq0yW3rrV27Ns+uIOPHj1flypULXeuPP/6wqi2UIraGQSZMmKANGza4hECqVaumF154QVu3btXp06fVqlWrQtW85557JP15pMwPP/xged8AAAAAAAAAAABAWebw8NSC1t3k7+VtSb0AL28tCOsuLw/bDyZAMQQHB6t+/fouY/v37y/y7iDx8fGKiYlxGbv++utVs2bNfOd36NAhz9hPP/1UpLV3795d7F1NCqNLly55xlatWuW29Y4fP55n7JZbbilSrXXr1hW3HZRCtv3TeM+ePZozZ44zBCJJvXr10r59+zR58mTddNNNqlKlSqHrhoeHuxwVs2bNGivbBgAAAAAAAAAAAMqFG/yrannH24odCAnw8tayjrephV/+R3+gbOnUqZPLtWma+uSTT4pU66OPPspzEsSl9S/WuXPnPGMLFiwo0toffPBBkZ4rqm7duuUZmzdvnjIyMtyyXn5HuxTl+/Wff/5ZBw8eLH5DKHVsC4O89NJLys7OlnRhF482bdrou+++cwY5isrT01OtW7d2/kMlJSVFhw8fLna/AAAAAAAAAAAAQHlzS9Vgre98R5GPjAkLqK5fO9+hW6oGW9wZ7DJw4MA8Y9OnT1d6enqh6qSnp+vdd98tUP1c7dq1y3NUzPr167Vs2bJCrX3s2LF813anVq1aKTTU9Zik+Ph4TZ8+3S3r+fn55RmLjY0tdJ2XXnrJinZQCtkWBvnuu+9cdgWZM2eOvLy8LKndpk0bl+vo6GhL6gIAAAAAAAAAAADlTQu/QG3ofIdead5BwT4VC/RMsE9FvdK8gzZ0voMdQcqZO+64Q3Xr1nUZ27dvn15++eVC1XnxxRfzHBHToEED9e/f/4rPPfLII3nGxowZo7i4uAKtm5WVpYcfflhnz54teLMWefLJJ/OMPfXUU9qxY4fla9WuXTvP2NKlSwtVY86cOVq+fLlVLaGUsSUMsm3bNp06dUrShV1Bbr311jwpqeKoV6+ey/WxY8csqw0AAAAAAAAAAACUNw4PT/2zyU063OsBLWrbUw/Wbpxnzo1+gRpeL0SL2vbU4V4P6J9NbpLDw9OGbuFOnp6eeuKJJ/KMT506VQsXLixQjQ8++EDTpk3LM/7EE0/I0/PKv2YefvhhNWrUyGXs0KFD6tmzp/bu3XvFZ5OSknTvvfdqxYoVBerTavfee2+e773Pnz+vHj16aP369YWut23bNh06dCjfe126dJGHh+vX/dOnT9fvv/9eoNqfffaZxo4dW+ieUHbYEga59P+kPXr0sLT+pWchJScnW1ofAAAAAAAAAAAAKI8cHp4adF0j/ffGW/Lc++mWfppzU1cNuq4RIZBybty4cerSpYvLWE5OjgYPHqxx48YpMTEx3+fOnDmjsWPHatiwYc4TInJ1795dY8aMueraFStW1Jw5c2QYhsv4rl27FBoaqscff1wbNmxQamqqJCkjI0PR0dF6+eWXFRISosWLF0uSvLy8rroLidU8PT31+eef5znC5dSpU+rataseffTRq25kcPbsWX388cfq3bu3wsLCdODAgXznVa9eXb169XIZS0tLU/fu3fXVV19dtn5cXJxGjhypBx54QBkZGZKkWrVqFeTjoYyx5lyWQkpISJAkmaYpwzDUoEEDS+tXqlRJkpz/gDh37pyl9QEAAAAAAAAAAADAbqGhoXl2hyiKhQsXqm/fvs5rDw8Pffjhh2rXrp3i4+Od4zk5OXrnnXc0e/Zs9erVSy1atFC1atV06tQp7d69WytXrnQGDC4WHBysBQsWFLjX8PBwvfrqq/r73//uMn7+/Hm98cYbeuONNyRJvr6+SktLy7fGiy++qNTUVH3zzTcu45eGTKzWtGlTffjhhxo0aJAyMzOd45mZmZo+fbreffdd3XTTTerQoYOCgoLk5+en5ORkxcXFadu2bYqKilJ6enqB1nrhhRf0/fffKycnxzmWkJCggQMHqkmTJurevbvq1asnDw8PxcfH67ffftOvv/6q7Oxsl37Hjx+vRx991Lq/CCgVbAmDXLpTR+XKlS2tn5SUJOnPsInV9QEAAAAAAAAAAADAbikpKZbUuTi0kKtevXr65ZdfFBERof3797vcS09P17fffqtvv/32qrWbNGmiyMhI1a5du1A9PfHEE/Lw8NDf//53l7DDxS4XBHnyySf1z3/+U88880yee7kbC7jTgAED9P3332vgwIE6ffq0yz3TNLVt2zZt27at2Ou0a9dOr776ar7H+uzdu/eqx+pcd911Wr58udauXVvsXlD62HJMTGBgoMt1bnjDKhen0ySpWrVqltYHAAAAAAAAAAAAgPKucePG+vXXX3XfffcVekcNwzD04IMP6pdfftH1119fpPUff/xx/frrr7rpppsKNP+6667TokWL9PLLL0tSvsfZBAQEFKmXwuratas2b96sgQMHFnk3En9//zzfrV9q4sSJeuutt+RwOApVu1OnTtq0aZMaNmxYpN5Q+tkSBqlRo4akP7fgOXLkiKX1N27c6HJdvXp1S+sDAAAAAAAAAAAAwLWgZs2a+uSTT7R582Y9+OCDV/2D+NWrV9df//pXbdmyRR999JHzu+Gi6tChg7Zs2aJVq1ZpzJgxCgsLU3BwsLy8vFS5cmWFhITo/vvv18KFC3XgwAENGjTI+eylu3L4+voWOjRRHA0bNtSXX36p3377TcOGDSvQ7iiVK1dWv379NGfOHB0/flytWrW66jPjxo3Ttm3b9MADD8jb2/uy8wzD0M0336yFCxfq559/LvRuLShbbDkmpn79+i7Xl4Y3iiM9PV1r1qyRYRgyTVOSFBYWZll9AAAAAAAAAAAAAChpud992qVNmzb66KOPlJOToy1btujgwYOKj49XYmKiqlSpoqCgIF1//fUKCwuTh4e1exJ4eHgoPDxc4eHhhXpu+/btLtd169Yt0HNDhw7V0KFDC7XWlYSFhWnu3LmSpN27d+uPP/5QQkKCTp06JUny8/NTcHCwmjdvrqZNm8rLq/Bf499www1auHCh5syZo/Xr12vfvn06ffq0TNOUv7+/GjVqpLZt2+a7kUJRP294eLjtvy5xebaEQcLCwlSlShUlJSXJNE399NNPOnXqlCXHucydO1eJiYnOXUeaN29e7LQZAAAAAAAAAAAo3zIysrQ8cquWRW7Jc+/OQS8rrHUjdQ9vqdsiwuTtbcvXKwBQKnh4eKhdu3Zq166d3a1c0ZkzZxQdHe0yVhp6btGihVq0aOG2+r6+vurevbu6d+/utjVQNthyTIyHh4d69uzpTAllZGToP//5T7HrxsbG6vnnn3fuCmIYhiIiIopdFwAAAAAAAAAAlE+ZmVl6+93vdFP7iXp41HQt/mpDnjnRe47p40/X6uFR09W6w0S9/e53yszMsqFbAEBBzZ8/Xzk5OS5jbdu2takboOTZEgaRpJEjR0qSM7jx+uuva+3atUWul5iYqIEDByo+Pt455unpqfHjxxe7VwAAAAAAAAAAUP5E7zmq3v2mauqLi5SQkFSgZ+LjkzT1xUXq3W+qovccdXOHAICiSE5O1htvvOEyZhiG+vbta09DgA1sC4P07NlT3bp1c+7gkZmZqdtvv12LFy8udK3Vq1erbdu22rRpk8uuIPfff7/q16/vhu4BAAAAAAAAAEBZtmnzXvUZME07ow4V6fmdUYfUZ8A0bdq81+LOAADShdMliiI7O1vDhw/X4cOHXca7d++uJk2aWNEaUCbYFgaRpDfeeEOVKlWSdCGJdfbsWQ0aNEg9evTQp59+muf/oJKUmZmpuLg4bdy4Ua+++qpuvvlm9ejRQ/v373ceO2MYhoKDg/Xvf/+7RD8PAAAAAAAAAAAo/aL3HNV9D72mlJS0YtVJSUnTfQ+9pj1/HLOoMwBArnHjxmnkyJHat29fgZ+Ji4tT37599cUXX+S5N3HiRCvbA0o9LzsXb9mypT7++GPdeeedzt08TNPU6tWrtXr1aue83JCHaZqqUKFCnjq5z+a+dzgc+uyzz1SzZs0S+RwAAAAAAAAAAKBsyMzM0tgJs4odBMmVkpKmMeNnasXSKXI4bP3aBSiU12N26vWYnZe9n/P/v5+7WKvVX8jj/38ndzkTG4VqYqPQYvcHpKen64MPPtDs2bN18803a+DAgWrXrp1atmypwMBAGYah7OxsJSQkaMOGDVq2bJk+/PBDnT9/Pk+tIUOGqE+fPjZ8CsA+tv9U0q9fPy1YsEAjR45UWlqaS6gjP/mNX/yMn5+fFi5cqFtvvdV9TQMAAAAAAAAAgDJpxuzIIh8Nczk7ow5pxuxIjRvT19K6gDslZ2bo2PlzhXomNj21QHUBq61fv17r1693Xnt4eMjX11fnzl3913Dr1q319ttvu7M9oFSyPQwiSQ888IBatWql++67T7t27ZJhGM6AR0GZpqmQkBB9/vnnuuGGG9zUKQAAAAAAAAAAKKsyMrI0Y3akW2rPmB2pUSMi2B0EZYa/w1u1K1RyS13A3XJycgoUBOnXr58++eQTVapk/a91oLQrNT+R3HDDDdq5c6e++OIL/fvf/9aWLVvyzMk9RuZSTZo00eTJk/XXv/5VHh4eJdEuAAAAAAAAAAAoY5ZHblV8fJJbasfHJ2nZiq0a0K+9W+oDVuM4F5R2vXr10saNGxUdHV3oZ1u2bKmnn35agwYNKvQmBEB5UWrCINKFsMegQYM0aNAgHTp0SGvWrNEvv/yio0eP6tSpUzpz5ox8fX1VvXp11axZUx06dFCvXr3UvHlzu1sHAAAAAAAAAACl3E+ro9xaf9WaKMIgAGCRBx98UA8++KD27NmjdevWaePGjdq7d68OHTqk06dPKzU1VYZhKDAwUFWrVlWDBg3UuXNndevWTR06dCAEgmteqQqDXKx+/foaPHiwBg8ebHcrAAAAAAAAAACgHNix82CZrg8A16JmzZqpWbNmGj58uN2tAGUKZ6oAAAAAAAAAAIBrQsz+WPfWj4lza30AAICCIgwCAAAAAAAAAACuCenpWW6tfz490631AQAACoowCAAAAAAAAAAAuCb4+Hi5tX4FH4db6wMAABQUYRAAAAAAAAAAAHBNaNSwlnvrNwp2a30AAICCIgwCAAAAAAAAAACuCa1CG5Tp+gAAAAVFGAQAAAAAAAAAAFwTuoe3dGv9bl3dWx8AAKCgCIMAAAAAAAAAAIBrwm0RYQoKCnBL7aCgAPXpHeaW2gAAAIVFGAQAAAAAAAAAAFwTvL29NGpEhFtqjxoRIYfDyy21AQAACoswCAAAAAAAAAAAuGaMGhGh0Jb1La3ZKrSBRo/sbWlNAACA4iAMAgAAAAAAAAAArhkOh5emvzlSfn6+ltTz96+o6W+OlJeXpyX1AAAArEAYBAAAAAAAAAAAXFNCmtXRpx8+UexAiL9/RX2yYKKaNa1tUWewg6fnhSBPdna2zZ0AAEqj3H8/5P77oqwgDAIAAAAAAAAAAK457ds10fJvninykTGhLetr2ZKn1b5dE4s7Q0nz8LjwdZlpmsrJybG5GwBAaZKdnS3TNCURBgEAAAAAAAAAACgTmjWtrRVLp2jK5HsUFBRQoGeCggI0ZfI9WrF0CjuClBM+Pj7O92fPnrWxEwBAaXPu3Dnne29vbxs7KTwvuxsAAAAAAAAAAACwi8PhpXFj+mrUiAgtW7FVyyO36suv1rvMaR5SR2GtG6pb15bq0ztMDgdfr5Qn/v7+SkxMlCQlJyfL39/f3oYAAKVGcnKy831Z+/cDP624UXp6uqKjo7V7924lJCQoJSVFlSpVUtWqVdW0aVOFhYWVufQQAAAAAAAAAADlkcPhpQH92qvTLSF5wiCLFz2p6tXK1hdAKLiKFSvK09NT2dnZOnv2rLKzs8vcUQAAAOvl/ntBunBETMWKFW3uqHAIg1hsy5YtWrp0qX788Udt3LhRmZmZl53r4+OjiIgIjR8/Xj169CixHsPDw7VmzZpi1Xj22Wf13HPPWdMQAAAAAAAAAACATQzDkJ+fnxITE2Wapo4ePaq6devKw8PD7tYAADbJycnR0aNHZZqmJMnPz0+GYdjcVeHwbzGLLF68WI0bN1bbtm31/PPPa926dVcMgkgXdg755ptv1LNnT/Xv318nTpwooW4BAAAAAAAAAACQKzAw0Bn+SE1N1ZEjR5SdnW1zVwAAO2RnZ+vIkSNKTU2VJHl4eCgwMNDmrgqPMIhFNm3apJiYmMveNwxDgYGBqlChQr73ly5dqjZt2ujgwYNu6hAAAAAAAAAAAAD5qVChgurVq+cSCNm7d6+OHj2q5ORkgiEAUM5lZ2crOTlZR48e1d69e12CIPXq1bvs9/ylGcfEuEnlypU1cOBAde/eXV26dFHdunXl5XXhL/fhw4e1dOlSvfrqqzp06JDzmWPHjqlXr17atm2bKleuXGK9Pv7442ratGmhnmnbtq2bugEAAAAAAAAAACh5vr6+qlevng4fPqycnByZpqmUlBSlpKRIuvAHfz09PW3uEgBgtezsbOdxMBfLDYL4+vra0FXxEQaxWEhIiB5//HE98MADlw101KtXT2PHjtXgwYP10EMPacmSJc57+/bt04svvqiXXnqppFpW//79FR4eXmLrAQAAAAAAAAAAlEa+vr6qX7++zpw5o5SUFJcdQUzTVFZWlo3dAQBKgqenp/z8/K548kdZQBjEIvXq1dP777+vIUOGFDgV6ufnp88++0y33nqrfvvtN+f4W2+9pSlTppTZhBEAAAAAAAAAAEBZVaFCBdWqVUvBwcFKTU1VcnKyMjIylJ2dzXExAFAOeXp6ytPTU97e3vL391fFihVlGIbdbRVbqQ2DHDt2TElJSUpKSlJmZmaxanXp0sWiri5vzJgxRXrOx8dH//73v9W9e3fnWGpqqn744Qf169fPqvYAAAAAAAAAAABQCIZhqFKlSqpUqZLdrQAAUGilJgwSGxurefPmKTIyUtu3b9fZs2ctqWsYRqnfsqtr166qVq2aTp065RzbsWMHYRAAAAAAAAAAAAAAAFBotodBUlNTNWnSJM2YMcO5tZZpmjZ3VbI8PDx0/fXXu4RB4uLibOwIAAAAAAAAAAAAAACUVbaGQRISEtS1a1ft2bPHJQBi1fk7ZSlUkpGR4XJdHs4gAgAAAAAAAAAAAAAAJc+2MEhWVpZ69+6t6OhoSa7hh7IU4rBCTk6ODhw44DIWHBxsUzcAAAAAAAAAAAAAAKAssy0MMnPmTG3bti1PCKRChQrq06eP2rdvr0aNGikgIEAOh8OuNkvEypUrlZKS4jLWpk2bEu0hNjZWUVFRSkhIUFZWlqpWraoaNWqoVatW8vX1LdFeAAAAAAAAAAAAAABA0dkWBvnPf/7jDILk7gQyYsQIvfzyywoMDLSrLVu89957LteVKlVSt27dSmz9u+++W6dOncr3nsPhUNu2bfXQQw9p6NChBEMAAAAAAAAAAAAAACjlPOxYdM+ePTp06JCkC0EQwzA0adIkzZw585oLgvz444/65ptvXMaGDh0qHx+fEuvhckEQScrMzNT69es1ZswY1a9fX4sWLSqxvgAAAAAAAAAAAAAAQOHZEgbZunWry/X111+vqVOn2tGKrRITE/Xwww+7jPn5+enpp5+2qaMrS0hI0L333qvx48fb3QoAAAAAAAAAAAAAALgMW46JSUhIcL43DEN33XWXvLxsO7HGFqZpaujQoTp48KDL+Kuvvqrg4GC3r28Yhtq1a6fbbrtN7du3V4sWLVStWjVVqFBBZ86c0f79+7VmzRrNmTNH+/btc3n27bffVkBAgF544YVi9XD06NEr3o+NjS1WfQAAAAAAAAAAAAAArkW2JDDOnj0r6c8jYpo3b25HG7aaMmWKlixZ4jLWv39/PfLII25fe+jQoZo1a5aaNm2a7/2goCAFBQWpY8eO+sc//qE333xTkyZNUkZGhnPOtGnTFB4erh49ehS5j7p16xb5WQAAAAAAAAAAAAAAkD9bjokJCAhwua5cubIdbdhm1qxZmjZtmstYSEiI5s+fXyLrDx069LJBkEt5eHjo8ccf16JFi+Th4frLZdKkSe5oDwAAAAAAAAAAAAAAFIMtO4M0adJE0oWjSiTp5MmTdrRhiy+//FKjR492GatTp45WrlypwMBAm7q6ugEDBuixxx7T66+/7hz77bfftGnTJrVv375INY8cOXLF+7GxsUWuDQAAAAAAAAAAAADAtcqWMEjHjh3lcDiUlZUlSYqKirKjjRK3cuVKPfDAA8rJyXGOVa9eXd9//32ZODLlqaee0ttvv63MzEznWGRkZJEDG3Xq1LGqNQAAAAAAAAAAAAAA8P/ZckyMv7+/+vXrJ9M0ZZqmvvvuO5mmaUcrJebXX3/VwIEDlZGR4Rzz9/dXZGSkQkJCbOys4KpVq6abb77ZZWzTpk02dQMAAAAAAAAAAAAAAPJjSxhEkp555hl5enrKMAwdOXJE8+bNs6sVt9u+fbv69u2rc+fOOccqVqyob7/9VmFhYTZ2VngtWrRwuY6Pj7epEwAAAAAAAAAAAAAAkB/bwiCtWrXSpEmTnDuCTJw4Ub///rtd7bhNdHS0/vKXvygxMdE55u3trcWLF6tz5872NVZEVatWdbk+c+aMTZ0AAAAAAAAAAAAAAID82BYGkaQXXnhBDzzwgEzTVHJysrp166bvvvvOzpYsdfDgQfXs2VMJCQnOMU9PT33yySeKiIiwsbOiuzjUIkkBAQH2NAIAAAAAAAAAAAAAAPJlaxhEkj788ENNmjRJhmHo1KlT6t+/v7p3765PPvlEx48ft7u9Ijt+/Lh69OihY8eOOccMw9DcuXM1cOBAGzsrnr1797pcBwUF2dQJAAAAAAAAAAAAAADIj5ddCzds2NDl2uFwKDMzU6Zpas2aNVqzZo0kqUKFCqpataocDkeR1jEMQzExMcXutzBOnTqlXr16af/+/S7j77zzjgYPHlyivVjp7Nmz+vnnn13GQkNDbeoGAAAAQHmQkZGl5ZFbtSxyS557dw56WWGtG6l7eEvdFhEmb2/b/hMWAAAAAAAAKFNs+520gwcPyjAMmaYpwzCc47ljudLS0lx21yisi2uXhOTkZPXu3Vu7d+92GX/55Zc1ZsyYEu3Faq+99prOnz/vMta7d2+bugEAAABQlmVmZmnG7Ei9NytSCQlJ+c6J3nNM0XuO6eNP1yooKECjRkRo1IgIORyEQgAAAAAAAIArsf2YmPzCGoZhWPIqaWlpaerXr59+++03l/HJkyfrySeftHy9Bg0auHze8PDwK86/OGRTWGvXrtVLL73kMta4cWN16tSpyDUBAAAAXJui9xxV735TNfXFRZcNglwqPj5JU19cpN79pip6z1E3dwgAAAAAAACUbbaGQUzTdOurJGVmZuruu+/W2rVrXcbHjx+vadOmlWgvl3Po0CHdcsst+u6775STk1Pg5z799FPdfvvtSk9Pdxl/6aWX5OXFn8gDAAAAUHCbNu9VnwHTtDPqUJGe3xl1SH0GTNOmzXst7gwAAAAAAAAoP2z7Jn/IkCF2Le0WEyZM0LJly1zG6tatq5CQEM2YMaPQ9fz8/PTggw9a1Z7T+vXrdfvtt6tWrVq666671LVrV910001q0KCBS7Dj0KFDWrNmjWbOnKlff/01T51hw4bp7rvvtrw/AAAAAOVX9J6juu+h15SSklasOikpabrvode0/Jtn1KxpbYu6AwAAAAAAAMoP28Ig8+bNs2tpt9i9e3eesSNHjmjMmDFFqle/fn23hEFyxcbG6p133tE777zjHKtUqZIqVKigpKQkZWVlXfbZu+++W7NmzXJbbwAAAADKn8zMLI2dMKvYQZBcKSlpGjN+plYsnSKHgx0LAQAAAAAAgIvZekwMSpdz587p1KlTlw2CVK5cWe+9954+//xzjocBAAAAUCgzZkcW+WiYy9kZdUgzZkdaWhMAAAAAAAAoDwiDXENq1aqluXPnavDgwWrcuLEMw7jqMx4eHmrVqpX++9//6ujRoxo1alQJdAoAAACgPMnIyHJbaGPG7EhlZl5+Z0MAAAAAAADgWsT2DhZZvXp1ia958ODBQs338fHRsGHDNGzYMElSSkqK/ve//+nw4cOKi4vTuXPnlJmZKT8/PwUGBqpu3bpq27at/Pz83NA9AAAAgGvF8sitio9Pckvt+PgkLVuxVQP6tXdLfQAAAAAAAKAsIgxyDfPz81P79u3Vvj2/aQoAAADAfX5aHeXW+qvWRBEGAQAAAAAAAC7CMTEAAAAAALfasfNgma4PAAAAAAAAlDXsDAKUc+/NXKH3Zq2wvO7okb01+pHeltcFAABA+ROzP9a99WPi3FofAAAAAAAAKGsIgwDlXMrZNMXGnXFLXQAAAKAg0tOz3Fr/fHqmW+sDAAAAAAAAZY2lYZDDhw/nO16vXr0Cz3WH/NYHrhV+lX1VKzjwsvdzckydiE90GasZVEUeHsZV6wIAAAAF4ePjpfPn3RfYqODjcFttAAAAAAAAoCyyNAzSoEEDGYbrF8iGYSgrK++fAstvrjtcbn3gWjH6kSsf53LyVLKah45zGVv9wwuqXs3f3a0BAADgGtGoYS3t2u2+PxDQqFGw22oDAAAAAAAAZZGH1QVN08zzKsxcd7wAAAAAAPZpFdqgTNcHAAAAAAAAyhrLwyCGYThfhZnrjhcAAAAAwH7dw1u6tX63ru6tDwAAAAAAAJQ1lh4TI6lQO3GwawcAAAAAlH+3RYQpKChA8fFJltcOCgpQn95hltcFAAAAAAAAyjJLwyDz5s1zy1wAAAAAQNnl7e2lUSMiNPXFRZbXHjUiQg6H5X/OAQAAAAAAACjTLP0dsyFDhrhlLgAAAACgbBs1IkJff7NRO6MOWVazVWgDjR7Z27J6AAAAAAAAQHnhYXcDAAAAAIDyz+Hw0vQ3R8rPz9eSev7+FTX9zZHy8vK0pB4AAAAAAABQnhAGAQAAAACUiJBmdfTph08UOxDi719RnyyYqGZNa1vUGQAAAAAAAFC+EAYBAAAAAJSY9u2aaPk3zyi0Zf0iPR/asr6WLXla7ds1sbgzAAAAAAAAoPwgDAIAAAAAKFHNmtbWiqVTNGXyPQoKCijQM0FBAZoy+R6tWDqFHUEAAAAAAACAq/CyuwEAAAAAwLXH4fDSuDF9NWpEhJat2KrlkVv15VfrXeY0D6mjsNYN1a1rS/XpHSaHg/+EBQAAAAAAAAqC30kDAAAAANjG4fDSgH7t1emWkDxhkMWLnlT1av42dQYAAAAAAACUXRwTAwAAAAAAAAAAAAAAUI5YGga5//77dejQIStLuoVpmpo7d67mzJljdysAAAAAAAAAAAAAAACWsjQM8tlnnykkJERPPPGE4uPjrSxtmW+++UatWrXSiBEjdPz4cbvbAQAAAAAAAAAAAAAAsJTlx8RkZGTojTfeUIMGDfToo4+Wip1CsrOztXDhQoWGhurOO+/U77//bndLAAAAAAAAAAAAAAAAbmF5GCTX+fPn9d5776lx48YaMGCAli1bJtM03bVcvg4fPqxnnnlG9erV0+DBg/X777/LNE0ZhiFJzv8FAAAAAAAAAAAAAAAoLywNg0RGRqpJkyYugYvs7Gx9++236tevn+rUqaNx48ZpzZo1bguGHD16VG+++aa6dOmihg0b6l//+pdiY2NdevLw8NCjjz6qCRMmuKUHAAAAAAAAAAAAAAAAu3hZWaxXr16KiorSa6+9ppdeekkpKSnOAIZpmoqNjdW7776rd999V/7+/rr11lsVHh6utm3bKjQ0VIGBgYVaLzs7W9HR0dqxY4d+/vlnrVq1Snv37nXezw2cGIYh0zRlmqa6dOmit956S6GhodZ9cAAAAAAAAAAAAAAAgFLC0jCIJDkcDk2aNEkjR47Uv/71L7377rs6f/68SyhEkpKSkrRs2TItW7bM+WxwcLDq16+v2rVrKzg4WJUqVZKvr688PT11/vx5paWl6fTp0zp69KiOHj2qAwcOKDMz0/n8xbuNGIbhEgIJCwvTtGnT1Lt3b6s/MgAAAAAAAAAAAAAAQKlheRgkV9WqVfWf//xHTzzxhN544w3Nnj1biYmJzlCIpDxHxcTGxiouLq5A9fM7Zia/2l26dNETTzyhfv36FeVjAAAAAAAAAAAAAAAAlCke7l6gVq1aeuWVV3TkyBG9/fbbat26tXO3DunPHTxyX5Kc96/0uvS5i3cB8fPz0/Dhw7V582atXr2aIAgAAAAAAAAAAAAAALhmuG1nkEtVqlRJY8eO1dixY7V79259+umn+u6777R9+/Z8j3e5mkt3BqlataoiIiJ0xx13qH///vLx8bH8MwAAAAAAAAAAAAAAAJR2JRYGuViLFi00depUTZ06VSdOnNDatWu1efNm/fbbb/rjjz8UGxub7zEwufz8/HT99dcrNDRU7dq1U8eOHdW2bdsChUgAAAAAAAAAAAAAAADKM1vCIBerWbOmBg0apEGDBjnHMjMzdezYMSUnJys1NVXZ2dny9fVVpUqVFBQUpMDAQBs7BgAAAAAAAAAAAAAAKL1sD4Pkx+FwqEGDBna3AQAAAAAAAAAAAAAAUOaUyjAIAAAAAAAAAACAO7w3c4Xem7XisvdzcvIeYx/e8xl5eFz5qPrRI3tr9CO9i90fAACAFQiDAAAAAAAAAACAa0bK2TTFxp0p1DMn4hMLVBcAgLLkagHJoiIgWToQBgGuURkZWVoeuVXLIrfkuXfnoJcV1rqRuoe31G0RYfL25h8VAAAAAAAAAMoHv8q+qhUc6Ja6AACUJUUJSBa0LuzHN7zANSYzM0szZkfqvVmRSkhIyndO9J5jit5zTB9/ulZBQQEaNSJCo0ZEyOHgHxkAAAAAAAAAyrbRj/CnlQEAkK4ekMzJMfPsjlUzqMpVj04jIFk68M0ucA2J3nNUYyfM0s6oQwV+Jj4+SVNfXKSvv9mo6W+OVEizOm7sEAAAAAAAAAAAAEBJuFpA8uSpZDUPHecytvqHF1S9mr+7W4MFPOxuAEDJ2LR5r/oMmFaoIMjFdkYdUp8B07Rp816LOwMAAAAAAAAAAAAAWIkwCHANiN5zVPc99JpSUop3PldKSprue+g17fnjmEWdAQAAAAAAAAAAAACsRhgEKOcyM7M0dsKsYgdBcqWkpGnM+JnKzMyypB4AAAAAAAAAAAAAwFqEQYBybsbsyCIfDXM5O6MOacbsSEtrAgAAAAAAAAAAAACsQRgEKMcyMrLcFtqYMTuS3UEAAAAAAAAAAAAAoBQiDAKUY8sjtyo+PskttePjk7RsxVa31AYAAAAAAAAAAAAAFB1hEKAc+2l1lFvrr1rj3voAAAAAAAAAAAAAgMIjDAKUYzt2HizT9QEAAAAAAAAAAAAAhUcYBCjHYvbHurd+TJxb6wMAAAAAAAAAAAAACo8wCFCOpadnubX++fRMt9YHAAAAAAAAAAAAABQeYRCgHPPx8XJr/Qo+DrfWBwAAAAAAAAAAAAAUHmEQoBxr1LCWe+s3CnZrfQAAAAAAAAAAAABA4REGAcqxVqENynR9AAAAAAAAAAAAAEDhEQYByrHu4S3dWr9bV/fWBwAAAAAAAAAAAAAUHmEQoBy7LSJMQUEBbqkdFBSgPr3D3FIbAAAAAAAAAAAAAFB0hEGAcszb20ujRkS4pfaoERFyOLzcUhsAAAAAAAAAAAAAUHSlLgxy8uRJffjhhxo+fLjCwsJUr149VapUSZ6envLy4otnoLBGjYhQaMv6ltZsFdpAo0f2trQmAAAAAAAAAAAAAMAapSYMcvz4cT366KOqX7++hg4dqvnz52v79u06evSo0tLSZJqmTNO8Yo0HH3xQ/v7+ztf//d//lVD3QOnlcHhp+psj5efna0k9f/+Kmv7mSHl5eVpSDwAAAAAAAAAAAABgrVIRBlm+fLlCQ0P13nvvuQQ/DMNwvgpi7NixOnv2rPM1f/585eTkuLl7oPQLaVZHn374RLEDIf7+FfXJgolq1rS2RZ0BAAAAAAAAAAAAAKxmexhk3rx56tevn06fPu0SAJFUoN1ALnbLLbeoQ4cOzufj4+P1/fffu6VvoKxp366Jln/zTJGPjAltWV/Lljyt9u2aWNwZAAAAAAAAAAAAAMBKtoZB1q5dq1GjRiknJ8cZAjFNU5UrV1b//v01YcIE1axZs1A177//fmeoRJJWrFjhjtaBMqlZ09pasXSKpky+R0FBAQV6JigoQFMm36MVS6ewIwgAAAAAAAAAAAAAlAFedi2clZWlYcOGKTMz0xkCcTgcevbZZzVx4kRVqFBBkrR69WrFx8cXuO5dd92lxx9/XNKFnUV++OEHt/QPlFUOh5fGjemrUSMitGzFVi2P3Kovv1rvMqd5SB2FtW6obl1bqk/vMDkctv2jAgAAAAAAAAAAAABQSLZ9wzt37lwdOHDAGQTx9vbW0qVL1atXr2LVrV27tpo0aaK9e/dKknbv3q3U1FRVrFjRiraBcsPh8NKAfu3V6ZaQPGGQxYueVPVq/jZ1BgAAAAAAAAAAAAAoDtuOiZkzZ44kOY90mTp1arGDILnatGkj0zSd19HR0ZbUBQAAAAAAAAAAAAAAKO1sCYOcOXNGW7dulWEYkqQaNWpowoQJltW/8cYbXa5zdwkBAAAAAAAAAAAAAAAo72wJg2zYsEE5OTmSJMMw1KdPH/n4+FhWv1q1ai7XZ86csaw2AAAAAAAAAAAAAABAaWZLGOTEiROS5DzKpV27dpbWr1KliiQ5dx5JSUmxtD4AAAAAAAAAAAAAAEBpZUsYJCEhweW6evXqltbP3XXkctcAAAAAAAAAAAAAAADllS1hEA8P12WzsrIsrX/q1ClJf+48UrVqVUvrAwAAAAAAAAAAAAAAlFa2hEGCgoJcrk+fPm1p/b1797pcV6tWzdL6AAAAAAAAAAAAAAAApZUtYZAaNWpIkgzDkCTt3LnT0vpr1qxx1pak+vXrW1ofAAAAAAAAAAAAAACgtLIlDBIWFuYMa5imqVWrVllWOyoqSjt27HBe+/n5qXXr1pbVBwAAAAAAAAAAAAAAKM1sOybmpptucl7HxMTop59+sqT2888/73xvGIZuvfVWeXjY8jEBAAAAAAAAAAAAAABKnG0pif79+8s0TRmGIdM09dhjjykrK6tYNWfNmqXFixc7a0rSX//6VyvaBQAAAAAAAAAAAAAAKBNsC4NMmDBBVapUcV7v2rVL9913n7Kzs4tU780339S4ceOcx89IUsOGDXXvvfcWt1UAAAAAAAAAAAAAAIAyw7YwSJUqVfTPf/7TZXeQr776Su3bt9e6desKVMM0TUVGRio8PFwTJ05UZmamc9wwDE2dOtUlHAIAAAAAAAAAAAAAAFDeedm5+D//+U/9/PPPWr58uTMQsm3bNnXt2lVNmjTRLbfcori4OOeRL5L01FNP6fTp0zp06JB+/fVXnT17VtKfARBJMgxDf/vb33T//ffb8rkAAAAAAAAAAAAAAADsYmsYxMPDQ5999pm6du2qbdu2OcMcpmnqjz/+0N69e13mm6apV155xeU618XPdunSRdOnTy+BTwAAAAAAAAAAAAAAAFC62HZMTK7KlStr3bp1Gjx4sDPcYRiGc6eQiwMfkpxjuTuBXDp32LBh+v777+Xt7W3HxwEAAAAAAAAAAAAAALCV7WEQSfL19dX8+fP16aefqkWLFvmGPfJ7SX+GQxo3bqyFCxfq/fffl8PhsPkTAQAAAAAAAAAAAAAA2KNUhEFy3XPPPYqKitLSpUs1ZMgQ1a9f32UnkEtfgYGBGjRokD766CP973//0/3332/3RwAAAAAAAAAAAAAAALCVl90N5Kdv377q27evJCk2NlZHjx7VqVOndObMGfn6+qp69eqqWbOmGjdu7NwhBAAAAAAAAAAAAAAAAKU0DHKxWrVqqVatWna3AQAAAAAAAAAAAAAAUCaUqmNiAAAAAAAAAAAAAAAAUDyEQQAAAAAAAAAAAAAAAMoR246J+dvf/uZ8X69ePT333HOW1X7uued0+PBhSZJhGHr//fctqw0AAAAAAAAAAAAAAFCa2RYGmT9/vgzDkCS1atXK0jDIkiVLtHPnTpmmSRgEAAAAAAAAAAAAAABcU2w/JsY0zTJVFwAAAAAAAAAAAAAAoDSzPQziLrm7jgAAAAAAAAAAAAAAAFxLym0YBAAAAAAAAAAAAAAA4FpULsMgWVlZzvcOh8PGTgAAAAAAAAAAAAAAAEqWl90NuMPp06ed7ytXrmxjJ4D93pu5Qu/NWnHZ+zk5Zp6x8J7PyMPjykctjR7ZW6Mf6V3s/gAAAHBt4OdSAAAAAAAAoOSUuzBIQkKCYmNjZRgXfsOwatWqNncE2CvlbJpi484U6pkT8YkFqgsAAAAUFD+XAgAAAAAAACWn3IVB/vvf/zrfG4ah5s2b29gNYD+/yr6qFRzolroAAABAQfFzKQAAAAAAAFBy3BYGWbt2bYHnnj17tlDzL5adna2zZ89q//79WrZsmX744QcZhiHTNGUYhtq2bVukukB5MfoRts0GAACA/fi5FAAAAAAAACg5bguDhIeHO49quRzTvHAmdExMjLp162bJurkhkFz33XefJXUBAAAAAAAAAAAAAADKArcfE5Mb+CjunILKDYIYhqEHHnhATZo0saw2AAAAAAAAAAAAAABAaef2MMjldge5OABytR1ECiO3bo8ePTR9+nTL6gIAAKDsem/mCr03a4XldUeP5NgLAAAAFA4/mwIAAAAoCW4NgxR0xw+rdgYJCAjQzTffrL/97W+66667LA2ZAAAAoOxKOZum2LgzbqkLAAAAFAY/mwIAAAAoCW4Lg6xateqy90zTVPfu3WUYhkzTVOPGjTV79uwirePl5SU/Pz8FBgaqbt26RW0XAAAA5ZhfZV/VCg687P2cHFMn4hNdxmoGVZGHx5XDxX6Vfa1oDwAAANcQfjYFAAAAUBIM06ptOQrJw8PDuXNHq1attHXrVjvaQCl29OhRZ8DnyJEjqlOnjs0dAQCA8urkqWQ1Dx3nMva/nW+rejV/mzoCAADAtYqfTQEAAFBa8LNpyXHHd+NuPSbmamzKoQAAAAAAAAAAAAAAAJRbtoVBnn32Wef74OBgu9oAAAAAAAAAAAAAAAAoV0pFGAQAAAAAAAAAAAAAAADW8LC7AQAAAAAAAAAAAAAAAFiHMAgAAAAAAAAAAAAAAEA5QhgEAAAAAAAAAAAAAACgHCEMAgAAAAAAAAAAAAAAUI542d1Afvbt26cNGzbo8OHDSkxMVFJSkjIzM4tUyzAMvf/++xZ3CAAAAAAAAAAAAAAAUDqVmjBIbGyspk+frvfff1/x8fGW1DRNkzAIAAAAAAAAAAAAAAC4ppSKMMiMGTM0ceJEpaenyzRNS2oahmFJHQAAAAAAAAAAAAAAgLLE9jDI2LFjNWPGDGcIpLghDqvCJAAAAAAAAAAAAAAAAGWRrWGQd999V++9956kP0MguUe7BAUF6fTp08rKynKO1atXT2lpaTpz5owyMzOddS4OkFSqVEnVq1cv2Q8CAAAAAAAAAAAAAABQSnjYtfCpU6f0z3/+U4ZhyDAMmaYpPz8/vfPOOzpz5oxiY2PVokULl2cOHDiguLg4paen6/Dhw/rss880cOBAeXh4yDRNmaapjIwMPfzwwzpw4IDzBQAAAAAAAAAAAAAAcK2wLQzy1ltvKTU1VdKF3UACAgL0888/a8yYMfL397/q83Xq1NGgQYP0xRdfKDo6Wp07d5YkZWVlacqUKRo6dKg72wcAAAAAAAAAAAAAACiVbAuDfPzxx84dQQzD0LRp09SyZcsi1WrUqJFWr16t4cOHO3cI+fDDDzVlyhSLuwYAAAAAAAAAAAAAACjdbAmDnDhxQjExMc7rgIAAjRw5slg1DcPQrFmz1LNnT0kXdht5+eWXFRUVVay6AAAAAAAAAAAAAAAAZYktYZDffvvN+d4wDEVERMjhcBS7rmEYevvtt+Xl5SXDMJSdna1XX3212HUBAAAAAAAAAAAAAADKClvCIPHx8S7XN910U4GeO3/+/FXnNGvWTJ07d3YeF/PVV18pMzOzKG0CAAAAAAAAAAAAAACUObaEQc6cOSPpwlEuknTdddflO+/S3ULS09MLVL9Xr17O96mpqdq8eXNR2gQAAAAAAAAAAAAAAChzbAmDXBrqqFy5cr7z/P39nYERSUpISChQ/dq1a7tc79mzp5AdAgAAAAAAAAAAAAAAlE22hEH8/PxcrtPS0go078iRIwWqX6FCBUmSYRiSpJMnTxa2RQAAAAAAAAAAAAAAgDLJljBItWrVXK5TUlLynVenTh2X66ioqALVj4uLk/TnMTRZWVmFbREAAAAAAAAAAAAAAKBMsiUM0rRpU0l/7txx9OjRfOe1bNnSZd7atWsLVH/dunUu14GBgUXqEwAAAAAAAAAAAAAAoKyxJQwSEhLiDHhI0u7du/Od16ZNG+d70zT17bff6sSJE1esffjwYS1ZssSlfr169YrZMQAAAAAAAAAAAAAAQNlgSxikUqVKatmypUzTlGma2rZtW77z2rRpo/r16zuvMzMzNXz4cGVnZ+c7/+zZs7rvvvuUkZHhHPPw8NCtt95q7QcAAAAAAAAAAAAAAAAopWwJg0hSeHi48/2hQ4cUExOT77y//vWvMk1ThmHINE0tX75cHTt21FdffaWEhARlZ2frxIkT+vDDD9WmTRtt3LjROdcwDEVERMjf37+EPhUAAAAAAAAAAAAAAIC9bAuD9O3bV5Kcx7ksX74833n/+Mc/VLNmTedc0zS1ZcsW3X333QoODpa3t7euu+46DR06VHv37pVpms5nDcPQ008/7eZPAgAAAAAAAAAAAAAAUHrYFgbp3r27qlev7jwqZs6cOfnO8/f314wZM+ThcaHV3PBI7nMXvwzDcN43DEOTJ09Wx44dS+YDAQAAAAAAAAAAAABQxmVkZGnJ0k2a/OzCPPfuHPSyJjzxvpYs3aSMjCwbukNBedm1sKenp6ZPn67du3c7x1JSUuTn55dn7oABA/TBBx9o+PDhSk9PdwY+8pO7M8ikSZP0/PPPW984AAAAAAAAAAAAAADlTGZmlmbMjtR7syKVkJCU75zoPccUveeYPv50rYKCAjRqRIRGjYiQw2Fb9ACXYevfkUGDBhV47gMPPKCOHTvqySef1Lfffqv09PR859166616/vnn1a1bN6vaBAAAAAAAAAAAAACg3Irec1RjJ8zSzqhDBX4mPj5JU19cpK+/2ajpb45USLM6buwQhVWm4jkNGzbU559/rtTUVK1du1ZHjhzRyZMnValSJdWqVUudO3dWcHCw3W0CAAAAAAAAAAAAAFAmbNq8V/c99JpSUtKK9PzOqEPqM2CaPv3wCbVv18Ti7lBUZSoMkqtixYrq3bu33W0AAAAAAAAAAAAAAFBmRe85WqwgSK6UlDTd99BrWv7NM2rWtLZF3aE4POxuAAAAAAAAAAAAAAAAlKzMzCyNnTCr2EGQXCkpaRozfqYyM7MsqYfiIQwCAAAAAAAAAAAAAMA1ZsbsSO2MOmRpzZ1RhzRjdqSlNVE0th0Ts3btWuf7ypUrKywszLLaW7du1dmzZ53XXbp0saw2AAAAAAAAAAAAAABlWUZGlttCGzNmR2rUiAg5HLbFESAbwyDh4eEyDEOS1KpVK23dutWy2sOHD9fOnTslSYZhKCuLbWgAAAAAAAAAAAAAAJCk5ZFbFR+f5Jba8fFJWrZiqwb0a++W+igYW4+JMU1Tpmm6tba76gMAAAAAAAAAAAAAUBb9tDrKrfVXrXFvfVydrWEQwzCcu4O4ozYAAAAAAAAAAAAAAHC1Y+fBMl0fV2drGEQSO3cAAAAAAAAAAAAAAFCCYvbHurd+TJxb6+PqbA+DuMPFARMPj3L5EQEAAAAAAAAAAAAAKJL09Cy31j+fnunW+ri6cpmUOHfunPN9xYoVbewEAAAAAAAAAAAAAIDSxcfHy631K/g43FofV1fuwiBZWVk6cuSI89rf39/GbgAAAAAAAAAAAAAAKF0aNazl3vqNgt1aH1dX7sIgy5YtU0ZGhiTJMAw1bNjQ5o4AAAAAAAAAAAAAACg9WoU2KNP1cXXlJgySlJSkTz75RKNGjZJhGDJNU5IUGhpqc2cAAAAAAAAAAAAAAJQe3cNburV+t67urY+rc9tBQIXZkWP37t1F3sEjOztbZ8+eVWJioiTJNE0ZhuG8f/vttxepLgAAAAAAAAAAAAAA5dFtEWEKCgpQfHyS5bWDggLUp3eY5XVROG4Lgxw8eNBlh4785N7LyMjQwYMHLVk3NwhiGIYaN26sv/zlL5bULYr09HRFR0dr9+7dSkhIUEpKiipVqqSqVauqadOmCgsLk7e3t239XSohIUHr16/X/v37dfbsWVWsWFH169dX+/btVbduXbvbAwAAAAAAAAAAAABYwNvbS6NGRGjqi4ssrz1qRIQcDrdFEVBAbv87cPEuHRe7OCRyuTlFZZqmKlWqpAULFlhe+2q2bNmipUuX6scff9TGjRuVmZl52bk+Pj6KiIjQ+PHj1aNHjxLs0tXatWs1bdo0/fjjj8rJycl3TseOHTVp0iQNGDCghLsDAAAAAAAAAAAAAFht1IgIff3NRu2MOmRZzVahDTR6ZG/L6qHoPNxZ3DTNy74KOq+wL8Mw1KdPH23atEkdOnRw58dzsXjxYjVu3Fht27bV888/r3Xr1l0xCCJd2Dnkm2++Uc+ePdW/f3+dOHGihLq9ICsrS+PHj1fXrl31/fffXzYIIkkbNmzQHXfcoXvvvVfnzp0rwS4BAAAAAAAAAAAAAFZzOLw0/c2R8vPztaSev39FTX9zpLy8PC2ph+Jx284gQ4YMueL9Dz74wHmMTNWqVdWvX78irePl5SU/Pz8FBgaqZcuW6tixo4KDg4tUqzg2bdqkmJiYy943DENVqlRRWlqazp8/n+f+0qVL1aZNG61bt04NGjRwY6cX5OTk6MEHH9SiRflv+xMQEKCkpLznQy1atEhxcXGKjIxUhQoV3N0mAAAAAAAAAAAAAMBNQprV0acfPqH7HnpNKSlpRa7j719RnyyYqGZNa1vYHYrDbWGQefPmXfH+Bx984Hxfr169q84vaypXrqyBAweqe/fu6tKli+rWrSsvrwt/uQ8fPqylS5fq1Vdf1aFDf265c+zYMfXq1Uvbtm1T5cqV3drfSy+9lCcI0rJlSz3zzDO67bbbVLlyZaWlpWn16tV68cUX9csvvzjnrV27VuPHj9esWbPc2iMAAAAAAAAAAAAAwL3at2ui5d88ozHjZxbpyJjQlvX17luPEAQpZdx6TExBGIZhdwuWCgkJ0cyZMxUbG6sPPvhAQ4YM0fXXX+8MgkgXwi9jx45VVFSUBgwY4PL8vn379OKLL7q1xyNHjuiFF15wGfvLX/6iDRs2aNCgQc4giq+vr2677TatXr1agwcPdpk/e/Zsbd682a19AgAAAAAAAAAAAADcr1nT2lqxdIqmTL5HQUEBBXomKChAUybfoxVLpxAEKYVsC4PUq1fP+bruuuvsasMy9erV0/vvv6/ff/9dI0eOLNDOHn5+fvrss8/Utm1bl/G33npLaWlF34Lnal544QWlp6c7r2vVqqXPPvtMFStWzHe+l5eX5syZoxtvvNFl/Omnn3ZbjwAAAAAAAAAAAACAkuNweGncmL7avul1zZkxVnfdeXOeOc1D6ujB+7tozoyx2r7pdY0b01cOh9sOJEEx2PZ35eDBg3Yt7RZjxowp0nM+Pj7697//re7duzvHUlNT9cMPP6hfv35WteeUkJCQ50ieadOmqUqVKld8zuFw6I033lDPnj2dYytXrtT27dt10003Wd4nAAAAAAAAAAAAAKDkORxeGtCvvTrdEqIvv1rvcm/xoidVvZq/TZ2hMGw/JgZS165dVa1aNZexHTt2uGWtpUuXKisry3kdEBCg++67r0DPdu/eXY0bN3YZ++qrryztDwAAAAAAAAAAAAAAFA9hkFLAw8ND119/vctYXFycW9ZasmSJy/Xtt99+2eNhLmUYhu65554r1gMAAAAAAAAAAAAAAPYq04f3ZGZmKiYmRklJSapRo4bq168vT09Pu9sqkoyMDJdrwzDcss6qVatcrjt16lSo52+55RaX6x07dujUqVN5djYBAAAAAAAAAAAAAAD2KJM7g+zbt0/333+/qlatqhtuuEG33HKLmjRpoqCgII0ZM0bx8fF2t1goOTk5OnDggMtYcHCw5escOXJEKSkpLmMdOnQoVI2OHTvmGfvf//5XrL4AAADskpGRpSVLN2nyswvz3Ltz0Mua8MT7WrJ0kzIysvJ5GgAAAAAAAACA0sm2nUGOHTumQYMGOa99fHy0fPlyVahQ4YrPrVy5UnfffbfOnTsn0zRd7p05c0YzZ87UF198oa+++qrQu17YZeXKlXlCGm3atLF8nejo6DxjDRs2LFSNatWqyd/fX8nJyS51b7311mL3BwAAUFIyM7M0Y3ak3psVqYSEpHznRO85pug9x/Txp2sVFBSgUSMiNGpEhByOMr25HgAAAAAAAADgGmDbziBffPGFNmzYoI0bN2rjxo2qXbv2VYMgBw8e1D333KOzZ8/KNE0ZhpHnZZqmTp48qf79+2vPnj0l9GmK57333nO5rlSpkrp162b5On/88YfLtZ+fn6pUqVLoOnXr1nW5Lit/nQEAACQpes9R9e43VVNfXHTZIMil4uOTNPXFRerdb6qi9xx1c4cAAAAAAAAAABSPbWGQ7777TpKcu3sMGzbsqs/84x//UHJysjP4kft87kuSc/zMmTN65JFH3NG6pX788Ud98803LmNDhw6Vj4+P5WudPn3a5bqoR9HUqlXL5frMmTNF7gkAAKAkbdq8V30GTNPOqENFen5n1CH1GTBNmzbvtbgzAAAAAAAAAACsY8se16ZpatOmTc6dPCpVqqQuXbpc8Zk//vhDixcvdgmB+Pn5afjw4QoJCVFsbKzmz5+vQ4cOOev+/PPPWrZsmfr06VMSH6vQEhMT9fDDD7uM+fn56emnn3bLemfPnnW5rlixYpHq+Pr6XrFuQR09euU/VRsbG1ukugAAAPmJ3nNU9z30mlJS0opVJyUlTfc99JqWf/OMmjWtbVF3AAAAAAAAAABYx5YwSExMjMsOHzfffLMcDscVn1m4cKHzaBjTNBUYGKj169eradOmzjmPP/64evbsqS1btjjHPvjgg1IZBjFNU0OHDtXBgwddxl999dUi79hxNefOnXO5vtqxPJdzaRjk0roFdelxMwAAAO6SmZmlsRNmFTsIkislJU1jxs/UiqVT5HDY8iM1AAAAypmMjCwtj9yqZZFb8ty7c9DLCmvdSN3DW+q2iDB5e/MzKAAAAIArs+W/Gg4cOOByfeONN171mS+++MIZBDEMQ//3f//nEgSRJH9/f82bN0+hoaHOud99951ycnLk4WHbiTj5mjJlipYsWeIy1r9/f7cebZOW5vrlh7e3d5HqXHqEzaV1AQAASpsZsyOLfDTM5eyMOqQZsyM1bkxfS+sCAADg2pKZmaUZsyP13qxIJSQk5Tsnes8xRe85po8/XaugoACNGhGhUSMiCCYDAAAAuCxbEhJHjhyRdGF3DElq3LjxFefHx8frf//7n/Pay8tLw4cPz3fuDTfcoE6dOjlrp6WlKTo62oq2LTNr1ixNmzbNZSwkJETz589367qX7gSSkZFRpDrp6elXrFtQR44cueJr06ZNRaoLAABwsYyMC7+57g4zZkcqMzPLLbUBAABQ/kXvOare/aZq6ouLLhsEuVR8fJKmvrhIvftNVfSeKx/DDAAAAODaZUsYJDk52eXa39//ivPXrl3rfG8Yhm699VYFBgZedn7nzp1drnft2lWELt3jyy+/1OjRo13G6tSpo5UrV17xM1mhcuXKLtfnz58vUp1LdwK5tG5B1alT54qvWrVqFakuAADAxZZHblV8fMF+Y72w4uOTtGzFVrfUBgAAQPm2afNe9Rkwrcg72O2MOqQ+A6Zp0+a9FncGAAAAoDywJQySmprqcl2xYsUrzt+4caOkP3cSiYiIuOL8S3caOXnyZGFbdIuVK1fqgQceUE5OjnOsevXq+v7771W3bl23r39paOPSvw8FZVUYBAAAoCT8tDrKrfVXrXFvfQAAAJQ/0XuO6r6HXlNKSvGOX05JSdN9D72mPX8cs6gzAAAAAOWFLWEQT09Pl+ur7VCxYcMGl+tbb731ivNzdxoxDEOSlJKSUtgWLffrr79q4MCBLkez+Pv7KzIyUiEhISXSw6U7j5w4caJIdWJjY69YFwAAoDTZsfNgma4PAACA8iUzM0tjJ8wqdhAkV0pKmsaMn8nxhQAAAABc2BIGufRYmCuFEs6fP6/Nmzc7gx3e3t5q167dFetnZbn+h8/FO3HYYfv27erbt6/OnTvnHKtYsaK+/fZbhYWFlVgfTZs2dblOTk5WYmJioescOXLkinUBAABKk5j9sVefVJz6MXFurQ8AAIDyZcbsyCIfDXM5O6MOacbsSEtrAgAAACjbbAmD1KhRQ9KfO3fs3r37snN/+ukn524ahmHopptuksPhuGL93IBD7rEylSpVKm7LRRYdHa2//OUvLqELb29vLV68WJ07dy7RXvLbgWT//v2FqnH69GklJydftS4AAEBpkZ7u3j8heT490631AQAAUH5kZGS5LbQxY3Yku4MAAAAAcLIlDNKyZUvne9M0tXLlysvOXbRokXOeJHXp0uWq9S/daaRq1apFabPYDh48qJ49eyohIcE55unpqU8++UQREREl3k/dunVVuXJll7GNGzcWqsalR/ZIUvPmzYvVFwAAgDv5+Hi5tX4FnysHlQEAAIBcyyO3Kj4+yS214+OTtGzFVrfUBgAAAFD22BIGadKkiQICApzXR44c0YIFC/LMO3r0qBYtWuTcQUSSevbsedX6O3bscLlu0KBB0ZstouPHj6tHjx46duyYc8wwDM2dO1cDBw4s8X5y1+/WrZvL2C+//FKoGpfODw0NVfXq1YvdGwAAgLs0aljLvfUbBbu1PgAAAMqPn1ZHubX+qjXurQ8AAACg7LAlDOLh4aG77rpLpmnKMAyZpqmxY8fqo48+Uk5OjqQLx5cMHDhQ58+fdz5XvXp19ejR46r1t23b5hIgady4sfUf4gpOnTqlXr165TmC5Z133tHgwYNLtJdLDRgwwOX622+/VWpqaoGfz92p5XL1AAAASptWoQ3KdH0AAACUHzt2HizT9QEAAACUHbaEQSRpzJgx8vC4sLxhGDp37pyGDBkif39/1a5dW02bNtWWLVucYRHDMDRixAjnM5ezZ88eHTx40Hldu3Zt1axZ050fxUVycrJ69+6t3bt3u4y//PLLGjNmTIn1cTn9+vWTl9efW6UnJSXp008/LdCzP/30k/bt2+cydscdd1jZHgAAgOW6h7e8+qRi6NbVvfUBAABQfsTsj3Vv/Zg4t9YHAAAAUHbYFgYJCwvT8OHDZZqmJDlDH6mpqYqNjVVOTo7znnRhV5C///3vV627ePFi53vDMHTzzTdb3/xlpKWlqV+/fvrtt99cxidPnqwnn3zS8vUaNGggwzCcr/Dw8Ks+ExQUpCFDhriMPf3000pMTLzic5mZmXrsscdcxnr27KmwsLBCdg0AAFCybosIU1BQwNUnFkFQUID69ObnIQAAABRMenqWW+ufT890a30AAAAAZYdtYRBJevvtt9W9e3eXQMilL9M05ePjo08++URVqlS5as2PP/7Y+ZwkdevWzZ0fwSkzM1N333231q5d6zI+fvx4TZs2rUR6KKgpU6bI29vbeR0bG6v77rvvssfFZGVlacSIEYqKcj1ztLR9LgAAgPx4e3tp1IgIt9QeNSJCDofX1ScCAAAAknx83PuzYwUfh1vrAwAAACg7bP2da29vby1fvlz/+te/9Nprr+ns2bN55rRu3VrvvvuuOnTocNV6P/zwg3bt2iXDMJxjffv2tbTny5kwYYKWLVvmMla3bl2FhIRoxowZha7n5+enBx980Kr2XNSrV0+TJ0/Ws88+6xyLjIxUx44dNWXKFN12222qVKmS0tLStHbtWr3wwgv65ZdfXGr87W9/K9DfEwAAgNJg1IgIff3NRu2MOmRZzVahDTR6ZG/L6gEAAKD8a9SwlnbtPuy++o2C3VYbAAAAQNli+x9jdDgcevbZZzVp0iT9+OOP2r9/v1JSUlStWjV16NBBrVq1KnCt33//XQMGDHBeBwcHq27duu5oO4/du3fnGTty5IjGjBlTpHr169d3WxhEunA0TFRUlL744gvnWFRUlAYNGiRJCggIUHJysstRPbluvfVWvfPOO27rDQAAwGoOh5emvzlSfQZMU0pKWrHr+ftX1PQ3R8rLy9OC7gAAAHCtaBXawK1hkFahDdxWGwAAAEDZYnsYJJePj4/69OlTrBqPPfaYHnvsMWsaKuc8PDz08ccfKygoSO+++26e+0lJSfk+d9ddd2n+/Pny9fV1d4sAAACWCmlWR59++ITue+i1YgVC/P0r6pMFE9WsaW0LuwMAAMC1oHt4S3386dqrTyyibl1buq02AAAAgLLFw+4GYB+Hw6Hp06dr1apV6tGjh8vxOpdq3769Fi9erC+++EKVK1cuwS4BAACs075dEy3/5hmFtqxfpOdDW9bXsiVPq327JhZ3BgAAgGvBbRFhCgoKcEvtoKAA9ekd5pbaAAAAAMqeUrMzSFm3evXqEl/z4MGDltQJDw9XeHi4Tpw4oQ0bNmj//v06d+6cfH19Va9ePXXo0EH16tWzZC0AAAC7NWtaWyuWTtGM2ZGaMTtS8fH574h2saCgAI0aEaFRIyLkcPAjNAAAAIrG29tLo0ZEaOqLiyyvzc+qAAAAAC7Gfx3AqWbNmhowYIDdbQAAALidw+GlcWP6atSICC1bsVXLI7fqy6/Wu8xpHlJHYa0bqlvXlurTO4zfWAcAAIAlRo2I0NffbNTOqEOW1WwV2kCjR/a2rB4AAACAso/f0QYAAMA1y+Hw0oB+7dXplpA8YZDFi55U9Wr+NnUGAACA8srh8NL0N0eqz4BpSklJK3Y9f/+Kmv7mSHl5eVrQHQAAAIDywsPuBgAAAAAAAADgWhLSrI4+/fAJ+fn5FquOv39FfbJgopo1rW1RZwAAAADKC8IgAAAAAAAAAFDC2rdrouXfPKPQlvWL9Hxoy/patuRptW/XxOLOAAAAAJQHhEEAAAAAAAAAwAbNmtbWiqVTNGXyPQoKCijQM0FBAZoy+R6tWDqFHUEAAAAAXJaX3Q0AAAAAAAAAwLXK4fDSuDF9NWpEhJat2KrlkVv15VfrXeY0D6mjsNYN1a1rS/XpHSaHg9/WBQAAAHBl/FcDAAAAAAAAANjM4fDSgH7t1emWkDxhkMWLnlT1av42dQYAAACgLOKYGAAAAAAAAAAAAAAAgHKEMAgAAAAAAAAAAAAAAEA5QhgEAAAAAAAAAAAAAACgHCEMAgAAAAAAAAAAAAAAUI4QBgEAAAAAAAAAAAAAAChHCIMAAAAAAAAAAAAAAACUI4RBAAAAAAAAAAAAAAAAyhHCIAAAAAAAAAAAAAAAAOUIYRAAAAAAAAAAAAAAAIByhDAIAAAAAAAAAAAAAABAOUIYBAAAAAAAAAAAAAAAoBwhDAIAAAAAAAAAAAAAAFCOeFlZbO3atVaWs0yXLl3sbgEAAAAAAAAAAAAAAKBEWBoGCQ8Pl2EYVpYsNsMwlJWVZXcbAAAAAAAAAAAAAAAAJcLSMEgu0zTdURYAAAAAAAAAAAAAAABX4ZYwSGnZHYRQCgAAAAAAAAAAAAAAuNZYHgYpbgDj4iBJQWoVdj4AAAAAAAAAAAAAAEB5ZmkYZNWqVUV6btOmTXruued0/vx5SRdCHd7e3goPD1ebNm0UEhKigIAAVapUSefOnVNSUpKio6O1ZcsWrV69WhkZGc5QSMWKFfXss8+qffv2ln0uAAAAAAAAAAAAAACAssLSMEjXrl0L/czMmTM1efJkZWdnyzRNValSRc8884yGDh2qwMDAqz6fmJio+fPn64UXXtCZM2eUmpqqyZMn65133tHIkSOL8jEAAAAAAAAAAAAAAADKLA87F1+wYIHGjBmjrKwsmaapm2++WdHR0Xr88ccLFASRpCpVquixxx5TdHS0OnXqJEnKysrS6NGj9cEHH7izfQAAAAAAAAAAAAAAgFLHtjDIwYMHNWbMGJmmKcMw1LZtW/3www8KCgoqUr0aNWpo5cqVatu2rQzDkGmaGjt2rA4cOGBx5wAAAAAAAAAAAAAAAKWXbWGQF198UampqZIkT09PzZ07V76+vsWq6evrq7lz58rDw0OGYSgtLU3Tpk2zol0AAAAAAAAAAAAAAIAywZYwSHp6uj799FMZhiHDMNSlSxfdeOONltS+8cYbFR4eLtM0ZZqmPvvsM6Wnp1tSGwAAAAAAAAAAAAAAoLSzJQyyadMmnTt3znkdERFhaf2//OUvzvdpaWnauHGjpfUBAAAAAAAAAAAAAABKK1vCINHR0ZIk0zQlSXXq1LG0fu3atfNdDwAAAAAAAAAAAAAAoLyzJQxy+vRpl+usrCxL6+fk5EiSDMPIdz0AAAAAAAAAAAAAAIDyypYwiMPhcLk+cuSIpfVz6+XuPOLt7W1pfQAAAAAAAAAAAAAAgNLKljDIddddJ+nPnTuWLVtmaf1L69WqVcvS+gAAAAAAAAAAAAAAAKWVLWGQJk2aON+bpqkNGzZo48aNltTeuHGjfv31V2fQRJKaNm1qSW0AAAAAAAAAAAAAAIDSzpYwSJs2bVSvXj1JF3YHycnJ0bBhw3T69Oli1T19+rSGDRvmPB5GkurWras2bdoUqy4AAAAAAAAAAAAAAEBZYUsYRJL++te/uoQ2oqOj1bVrV/3xxx9Fqrd3716Fh4crOjpahmHINE0ZhqHBgwdb1TIAAAAAAAAAAAAAAECpZ1sY5KmnnnLZHcQwDO3atUuhoaH6xz/+oT179hSozh9//KF//OMfCg0N1a5du5zjhmGobt26mjRpklv6BwAAAAAAAAAAAAAAKI287Fq4YsWKev/999W3b19lZmY6xzMyMvT666/r9ddfV0hIiNq0aaNmzZopICBAlSpV0rlz55SUlKQ9e/Zoy5Ytio6OliTnLiO5u4L4+Pho7ty5qlixoi2fDwAAAAAAAAAAAAAAwA62hUEkqUePHvriiy909913KzMzU4ZhSPoz2PG///3PGfbIz8XHzFz8rLe3t7744gt1797djd0DAAAAAAAAAAAAAACUPrYdE5Pr9ttv16pVq9S0aVOX3T1yX6ZpXvZ18TzpQhCkWbNmWr16tfr27WvnxwIAAAAAAAAAAAAAALCF7WEQSbr55pu1fft2Pffcc6pZs6Yz7CG5BkMufUlyzg0KCtLzzz+v7du3q2PHjnZ+HAAAAAAAAAAAAAAAANvYekzMxXx8fDRlyhQ99dRT+vrrr7V8+XJt2LBB0dHRLsfB5DIMQyEhIerYsaNuu+023XHHHfLyKjUfBwAAAAAAAAAAAAAAwBalLj3h5eWlu+++W3fffbckKS0tTQkJCUpMTFRKSor8/PxUpUoV1ahRQ76+vjZ3CwAAAAAAAAAAAAAAULqUujDIpXx9fVWvXj3Vq1fP7lYAAAAAAAAAAAAAAABKPQ+7GwAAAAAAAAAAAAAAAIB1CIMAAAAAAAAAAAAAAACUI4RBAAAAAAAAAAAAAAAAyhHCIAAAAAAAAAAAAAAAAOUIYRAAAAAAAAAAAAAAAIByxMvuBi52/vx5rV+/Xlu2bNGePXuUlJSkpKQkZWZmFrmmYRj68ccfLewSAAAAAAAAAAAAAACg9CoVYZDDhw/r5Zdf1ieffKLk5GTL6pqmKcMwLKsHAAAAAAAAAAAAAABQ2tkeBnn//fc1ceJEnT17VqZp5rlPmAMAAAAAAAAAAAAAAKDgbA2DzJw5U2PGjHGGQPILfuQXEAEAAAAAAAAAAAAAAED+bAuDREdH69FHH5XkGgLJDX9UqlRJDRo0UEBAgBwOhy09AgAAAAAAAAAAAAAAlDW2hUEmT56s7OxsZxDENE35+vpq3Lhx+utf/6obbriBI2IAAAAAAAAAAAAAAAAKyZYwSGpqqr777juXIMj111+v77//Xg0bNrSjJQAAAAAAAAAAAAAAgHLBljDIunXrlJGRIcMwZJqmvLy8tGTJEoIgAAAAcIv3Zq7Qe7NWXPZ+To6ZZyy85zPy8LjyTnWjR/bW6Ed6F7s/AAAAAAAAAACsZEsY5MiRI873hmGob9++uvHGG+1oBQAAANeAlLNpio07U6hnTsQnFqguAAAAAAAAAACljS1hkJMnT0q6cDyMYRjq1q2bHW0AAADgGuFX2Ve1ggPdUhcAAAAAAAAAgNLGljCIp6eny3WtWrXsaAMAAADXiNGPcJwLAAAAAAAAAFyM47XLN1vCIMHBwS7X58+ft6MNAAAAAAAAAAAAAACuSRyvXb7ZEgZp3bq1JMkwLiSGjh07ZkcbAAAAAAAAAAAAAABckzheu3yzJQxyww03qEGDBjp06JAkadWqVZo0aZIdrQAAAAAAAAAAAAAAcM3heO3yzcOuhceNGyfTNGWaplavXq39+/fb1QoAAAAAAAAAAAAAAEC5YVsYZOzYsWrWrJkMw1BWVpYmTJhgVysAAAAAAAAAAAAAAADlhm1hEG9vb3355Zfy8/OTJC1btkxjxoxRTk6OXS0BAAAAAAAAAAAAAACUeV52Lt6iRQt9//336tu3r06ePKmZM2dq+/bteuWVV9S5c2c7WwMAAAAAAAAAy703c4Xem7Xisvdzcsw8Y+E9n5GHh3HFuqNHct47AAAAgD/ZFgZZsGCB8/2ECRP04osv6vz589qwYYPCw8PVpEkTdenSRU2aNFHVqlXlcDiKvNbgwYOtaBkAAAAAAAAAiiXlbJpi484U6pkT8YkFqgsAAAAAuWwLgwwdOlSG4ZpmNwxDpmnKNE398ccf2rt3ryVrEQYBAAAAAAAAUBr4VfZVreBAt9QFAAAAgFy2HhMjSabpuu3hxQGRS+8VxaWBEwAAAAAAAACwy+hHOM4FAAAAgPvZHga5UlijuEEOK8IkAAAAAAAAAAAAAAAAZYmtYRDCGgAAAAAAAAAAAAAAANayLQxy4MABu5YGAAAAAAAAAAAAAAAot2wLg9SvX9+upQEAAAAAAAAAAAAAAMotD7sbAAAAAAAAAAAAAAAAgHUIgwAAAAAAAAAAAAAAAJQjhEEAAAAAAAAAAAAAAADKEcIgAAAAAAAAAAAAAAAA5QhhEAAAAAAAAAAAAAAAgHKEMAgAAAAAAAAAAAAAAEA5QhgEAAAAAAAAAAAAAACgHPGya+GGDRuWyDqGYSgmJqZE1gIAAAAAAAAAAAAAALCbbWGQgwcPyjAMmabp1nUMw3BrfQAAAAAAAAAAAAAAgNLEtjBILneGNdwdNAEAAAAAAAAAAAAAAChtbA2DFDeskV+QhAAIAAAAAAAAAAAAAAC4ltkWBhkyZEiRn83MzNSpU6e0b98+xcTESJLzyBlfX1/ddddd8vT0tKpVAAAAAAAAAAAAAACAMsO2MMi8efMsszbExQAA9vlJREFUqXP8+HHNnDlTb7/9thITE3X+/HkdOnRIX331lapWrWrJGgAAAAAAAAAAAAAAAGWFh90NFNd1112n559/Xtu3b1fbtm1lmqbWrVunrl27KjEx0e72AAAAAAAAAAAAAAAASlSZD4PkqlevnlauXKlmzZrJNE3t3r1b999/v91tAQAAAAAAAAAAAAAAlKhyEwaRpCpVqujdd9+VJJmmqZUrV+rTTz+1uSsAAAAAAAAAAAAAAICSU67CIJLUrVs3tW7dWtKFQMi///1vmzsCAAAAAAAAAAAAAAAoOeUuDCJJERERzvc7duzQoUP/j707j66qPPcH/pyQEAYBwyQoKIIjCkVQGapWsQ7YqmgV22odb63W4Yq31f5qe9X2dlArqLdXrbVq9WqdinO1ojgjguIsTgwCykyYIQSyf394c8phkCHnZCfh81nrrObdw7OfTdu1IPnmfT5LsRsAAAAAAAAAgNrTIMMgu+yyS8567NixKXUCAAAAAAAAAFC7GmQYpGXLlhERkclkIiJi2rRpabYDAAAAAAAAAFBrGmQYpLy8PGddUVGRUicAAAAAAAAAALWrQYZBxo0bFxERSZJERERZWVma7QAAAAAAAAAA1JoGFwYpLy+PBx98MDsiJiKiY8eOKXYEAAAAAAAAAFB7GlQYZPXq1XHaaafFggULsscymUwccMAB6TUFAAAAAAAAAFCLGkwY5Nlnn41+/frFE088EZlMJpIkiUwmE/379482bdqk3R4AAAAAAAAAQK0oTuvBv/rVr2p0f2VlZSxatCgmTZoUr7/+esyePTsiIhsCqXbllVfW6DkAAAAAAAAAAPVJamGQK664Iie0URNJkmS/XrPmD3/4wxg4cGBengEAAAAAAAAAUB+kFgaptmaQY0utGQCprnfGGWfETTfdVOPaAAAAAAAAAAD1SVHaDWQymRp/kiTJfvbcc8946KGH4i9/+Uvedh4BAAAAAAAAAKgvUt0ZpCa7ghQXF0fLli1j2223jd133z369OkTgwYNiv79++exQwAAAAAAAACA+iW1MEhVVVVajwYAAAAAAAAAaLBSHxMDAAAAAAAAAED+CIMAAAAAAAAAADQgwiAAAAAAAAAAAA2IMAgAAAAAAAAAQAMiDAIAAAAAAAAA0IAIgwAAAAAAAAAANCDFaTewPnPmzInnn38+XnnllRg/fnzMnTs35s+fH4sXL44WLVpE69ato23bttGnT58YMGBAHHLIIdG2bdu02wYAAAAAAAAASF2dCoOMHz8+hg0bFg8++GBUVlZmjydJkv16+fLlMXv27MhkMvHKK6/EDTfcECUlJXHSSSfFRRddFPvss08arQMAAAAAAAAA1Al1YkxMZWVl/Md//Efsv//+8be//S1WrlwZSZJkP5lMZp3PmudXrlwZ//u//xv77bdf/PSnP80JkgAAAAAAAAAAbE1SD4MsWLAgBgwYENddd11UVVWtN/wRETnhj4hYbzikqqoqhg0bFl//+tdj4cKFab4WAAAAAAAAAEAqUh0Ts3Llyjj22GPjjTfeiIjIBj8i/jUapqioKDp37hxlZWXRvHnzWLp0aSxYsCCmTp0aVVVV2fvWDI288cYbceyxx8bTTz8djRs3ruW3AgAAAAAAAABIT6phkEsuuSReeumldUIgZWVl8b3vfS+GDBkSffr0iebNm69z77Jly+KNN96I+++/P/72t7/F/Pnzc3YJeemll+LSSy+N4cOH1+YrAQAAAAAAAACkKpNUb8FRyyZNmhR77rlnrFq1KiL+tRPIOeecE7/73e+iVatWm1xr0aJF8fOf/zxuuumm7LEkSaJx48YxYcKE2HnnnfPbPLVi+vTp0blz54iImDZtWnTq1CnljgAAAAAAAAAgvwrxs/GiGlfYQldddVVUVlZGxJfBjUaNGsWf/vSnuPHGGzcrCBIR0bJly/jjH/8Yf/7zn3N2GamsrIyrr746r30DAAAAAAAAANRlqYVBnnjiiexIl0wmEz/5yU/ihz/8YY1qnnnmmfHTn/40WzNJknjsscfy1DEAAAAAAAAAQN2XShjkvffeiy+++CK7btOmTVxxxRV5qX3FFVdE27Zts+sZM2bEe++9l5faAAAAAAAAAAB1XSphkI8//jj7dSaTieOOOy5KS0vzUru0tDSOO+64SJJkvc8DAAAAAAAAAGjIUgmDzJkzJyIiG9j42te+ltf6vXr1ylnPnj07r/UBAAAAAAAAAOqqVMIg8+fPz1m3b98+r/Wrx8RkMpmIiFiwYEFe6wMAAAAAAAAA1FWphEFatWqVs147HFJT1eGP6p1HWrZsmdf6AAAAAAAAAAB1VSphkHbt2kXEv3bumDBhQl7rr12v+nkAAAAAAAAAAA1dKmGQnXfeOft1kiQxYsSIvNWurlcdNImI6NKlS97qAwAAAAAAAADUZamEQXr37h1lZWXZ9fTp0+OGG27IS+0bb7wxpk6dml2XlZXFvvvum5faAAAAAAAAAAB1XSphkKKiojj88MMjSZLIZDKRJEn8/Oc/j+eff75GdV966aX42c9+lq2ZyWTi8MMPz9klBAAAAAAAAACgIUslDBIR8ZOf/CQb0shkMrFs2bL41re+FTfeeOMW1bv55ptj0KBBsXTp0uyxTCYTP/nJT/LSLwAAAAAAAABAfZBaGKRPnz5x4oknRpIkEfFlcGP58uVxwQUXRK9eveLmm2+O2bNnf2WNOXPmxE033RT77LNPnHfeebFs2bKcXUFOPPHE6N27d228DgAAAAAAAABAnZBJqtMYKZg3b170798/Jk6cmD22ZjgkIqJTp06x5557xrbbbhvNmzePpUuXxoIFC2LChAkxffr09d4TEbHLLrvE6NGjo02bNrX1OuTZ9OnTo3PnzhERMW3atOjUqVPKHQEAAAAAAABAfhXiZ+PFNa5QA23atImnnnoqDjjggJg5c2ZkMpnszh7VAY9p06ZlQx9rWjvDUh0ESZIkOnbsGE899ZQgCAAAAAAAAACw1UltTEy1rl27xttvvx1HHnlkzg4fa36qwyFrfjZ0zVFHHRVvvfVW7Lzzzim/GQAAAAAAAABA7Us9DBIR0a5du/jHP/4R//u//xu9e/fOCX1ErBsOWXMXkOrPvvvuG/fcc088/vjj0a5duzRfBwAAAAAAAAAgNamOiVnb97///fj+978fr732WowcOTJeeeWVGD9+fMybNy+qqqqy1xUVFUXbtm2jd+/eMWDAgDjiiCNiv/32S7FzAAAAAAAAAIC6oU6FQar17ds3+vbtm3Ns0aJFsXjx4mjRokW0bNkypc4AAAAAAAAAAOq2VMIgK1asiNmzZ+cc69SpUxQVbXhqTcuWLYVAAAAAAAAAAAA2IpUwyL333htnnXVWdt2hQ4eYPn16Gq0AAAAAAAAAADQoG96Ko4BmzZoVSZJEkiQREXHiiSdGJpNJoxUAAAAAAAAAgAYllTDI6tWrIyKyAZDddtstjTYAAAAAAAAAABqcVMIgLVq0iIjI7gzSvn37NNoAAAAAAAAAAGhwUgmD7LTTTjnr8vLyNNoouCRJ4pNPPom77747LrroohgwYEA0bdo0MplMzicNXbp0WaePzf3ccccdqfQOAAAAAAAAAGxYcRoP3WeffSLiX2NiJk6cmEYbBbFkyZL43e9+F+PGjYtx48bFggUL0m4JAAAAAAAAANiKpLIzSOfOnaNHjx4R8eXuGU8++WQabRTE3Llz47e//W2MHDlSEAQAAAAAAAAAqHWp7AwSEXHOOefEeeedFxER7733Xjz11FNx5JFHptXOVu+//uu/ok2bNpt1T//+/QvUDQAAAAAAAACwpVILg/zwhz+MG2+8MT744INIkiTOO++8GD16dGy33XZptVQQzZs3j969e8d+++0X++23X3z66afxy1/+Mu221nHyySdHly5d0m4DAAAAAAAAAKih1MIgxcXFMWLEiOjfv3/Mnz8/Jk+eHAMHDoy//e1v0bNnz7TaqrFmzZrFOeeckw1/dO/ePRo1apQ9f8cdd6TXHAAAAAAAAADQ4BWl+fBdd901Xnnlldh1110jImLChAmx3377xQ9/+MN49dVXI0mSNNvbIu3bt4+bbropzjzzzOjRo0dOEAQAAAAAAAAAoNBS2xnkzDPPzH7dq1evmDRpUlRVVUVlZWXcdtttcdttt0XTpk2jZ8+e0b59+2jZsmUUF29+u5lMJv7yl7/ks3UAAAAAAAAAgDortTDIHXfcEZlMZp3jmUwmuyPIsmXL4rXXXtviZyRJIgwCAAAAAAAAAGxVUguDVKsOfqwZDFk7JLIl42LWFzQBAAAAAAAAAGjoUg+DbEpoQ7ADAAAAAAAAAGDTpBYG2XHHHYU86pgpU6bEhAkTYs6cOZHJZKJNmzax3XbbRc+ePaOkpCTt9gAAAAAAAACATZBaGGTKlClpPZr12HfffWPevHnrPde0adPo379/nHXWWTFkyJAoLk59QxkAAAAAAAAAYAP8VJ+IiA0GQSIili9fHqNGjYpRo0bFZZddFrfddlsccsghNX7m9OnTv/L8jBkzavwMAAAAAAAAANjaCIOwWaZMmRLf/OY343e/+11ccsklNarVuXPnPHUFAAAAAAAAAFQTBtmKNWrUKL7+9a/HoEGDok+fPrHnnntGWVlZlJSUxPz58+PDDz+M5557Lv785z/n7NJRVVUVl156abRp0ybOOuusFN8AAAAAAAAAAFibMMhW6pJLLoljjz02dthhh/We79ChQ3To0CEOPvjguOyyy+KXv/xlXHPNNZEkSfaac845Jw444IDYfffdt6iHadOmfeX5GTNmxP77779FtQEAAAAAAABgayUMspX68Y9/vMnXNm7cOK666qro3LlzXHDBBdnjq1atissuuywefPDBLeqhU6dOW3QfAAAAAAAAALBhRWk3QP1x/vnnx/HHH59zbMSIETFr1qyUOgIAAAAAAAAA1iYMwma5/PLLc9ZJksTTTz+dUjcAAAAAAAAAwNpSGxMzderUgtVu1KhRtGzZMlq0aFGwZ2ytevbsGTvuuGPOf39jx46NH/zgByl2BQAAAAAAAABUSy0M0qVLl8hkMgV9RiaTiW233Tb23nvv2H///eOggw6Ko446KoqKbIhSE927d88Jg8yePTvFbgAAAAAAAACANaWaikiSpKCfqqqqmD9/frz00ktx7bXXxrHHHhtdunSJ3//+97Fy5co0X71ea926dc66vLw8pU4AAAAAAAAAgLWlGgbJZDK18on4V/Bk+vTpcdlll0Xfvn1jwoQJab5+vbVgwYKcdatWrdJpBAAAAAAAAABYR53bGWRj57f02jXDIUmSxNtvvx0HHHBAfPLJJ7X5yg3C2n9m7du3T6kTAAAAAAAAAGBtxWk9+Pbbb89+PWXKlLj66qtjxYoVEfFlsKOoqCh69+4d++yzT+y8887RqlWrKC0tjUWLFsW8efPinXfeiXHjxsWsWbMiIrI7gPTv3z/+7d/+LVavXh3l5eUxc+bMGDNmTLz++uuxcuXKnEBIeXl5HHPMMTFu3LjYZpttav8PoR769NNP1wmD9OzZM6VuAAAAAAAAAIC1pRYGOe200yIi4oknnogLL7wwVqxYEUmSRIsWLeLSSy+N008/PbbffvuvrJEkSYwcOTKuueaaePbZZyOTycSrr74abdq0ib/97W/RrFmz7LWzZ8+O6667Lq699tpYtWpVNhDy8ccfx/XXXx+XXXZZQd+3ofjNb36zzrEjjzwyhU4AAAAAAAAAgPVJdUzM008/Hccff3wsXrw4kiSJAQMGxIcffhg///nPNxoEifhyN5DDDz88Ro4cGbfddlsUF3+ZbXn88cfjmGOOiVWrVmWvbd++ffz2t7+N5557Llq2bJm9P0mSuP7662P58uWFeckCW3P8TSaTidNPP/0rr197vM7muPfee+Ovf/1rzrGDDz44dtpppy2uCQAAAAAAAADkV2phkLlz58Ypp5wSlZWVkclkYt99942nn346OnbsuEX1Tj/99LjrrrsiSZJIkiSee+65+PWvf73OdQMGDIi//vWvOaGIefPmxTPPPLPF71KfvPjii3HUUUfFSy+9tFn3XX/99XHqqafm/LllMpm4+uqr890iAAAAAAAAAFADqY2Jufrqq2Pu3LkREVFUVBS33nprzliXLTFkyJB44IEH4u9//3skSRLXXHNNnH/++dGuXbuc64455pg45JBD4rnnnssee/HFF+Poo4+u0fOrvf766/H666+v99yrr766zrGbb755g7VOPvnkaNGiRV76ivhyZ5Ann3wynnzyyejatWuccMIJ8fWvfz169eoVnTp1iqKioux1n3zySYwaNSr+53/+J9577711al1++eWx33775a03AAAAAAAAAKDmUgmDVFVVxW233RaZTCYiIg444IDo2bNnXmpfcMEF8fe//z0ymUxUVFTEXXfdFRdffPE6151zzjnx3HPPZXsYM2ZMXp4f8eWYmiuvvHKTrz/33HM3eO7II4/MaxhkTZMmTcrZ2SOTycQ222wTJSUlsWDBgqiqqtrgvRdddFFcfvnlBekLAAAAAAAAANhyqYyJef3112P+/PnZ9RFHHJG32gcccEA0bdo0u97Q+JeDDjoo+3WSJDFz5sy89VBfJUkSixcvjvnz528wCNKuXbsYMWJEDB8+vJa7AwAAAAAAAAA2RSphkPfffz8ivgwfRER06tQpb7WLioqiY8eO2frVz1rbdtttF61bt86uy8vL89ZDXdarV6+48cYbY8iQIdG5c+dNuqekpCT69+8ft956a3z22Wdx3HHHFbhLAAAAAAAAAGBLpTImZu7cublNFOe3jUaNGm3wWWtq3bp1NgSyaNGivD3/iiuuiCuuuCJv9b5KdaBmU2277bZx7rnnZkfTzJ8/Pz788MOYNm1azJo1K5YuXRpVVVXRsmXLKCsri5133jn69OkTTZo0KUT7AAAAAAAAAECepRIGWdsXX3yR13prjnzJZDIbvK60tDT7dUlJSV57qC9at24dAwYMSLsNAAAAAAAAACBPUhkTUz3GpTqo8dxzz+Wt9vjx42Px4sXZdYcOHTZ47Zq7gTRv3jxvPQAAAAAAAAAApCWVMMhOO+2U/TpJknjmmWdi2rRpeal96623Zr/OZDI5z1pTVVVVzJ49O7v+qtAIAAAAAAAAAEB9kUoYpH///lFWVpZdr1y5Ms4555xIkqRGdUePHh233HJLZDKZbK2jjjpqvdd+/PHHUVFRERFfhka6dOlSo2cDAAAAAAAAANQFqYRBiouLY/DgwZEkSTa48dRTT8X3v//9bEBjc7388svx7W9/OydQUlRUFCeccMJ6r3/11Vdz1j169Nii5wIAAAAAAAAA1CWphEEiIq644opo3rx5REQ2EHL//ffHXnvtFQ888EBUVlZuUp3JkyfHj3/84zjkkENiwYIFERHZkMm55567wTExjz76aPbaiC93KwEAAAAAAAAAqO8ySU1ns9TADTfcEBdddFFkMpmI+FcwI5PJRFlZWRx66KHRu3fv6NKlS7Rq1SoaN24cixcvjnnz5sW7774bY8aMibFjx2bvXbNOly5d4u23344WLVqs89x58+ZF586do6KiIpIkidLS0pg9e/Z6ryU906dPj86dO0dExLRp06JTp04pdwQAAAAAAAAA+VWIn40X17hCDVx44YUxa9as+N3vfheZTCYnzDF//vx48MEH48EHH/zKGmsGSKrXnTt3jmeffXaD4Y6bb745VqxYkV0feuihgiAAAAAAAAAAQIOQahgkIuI3v/lNtGvXLn7+85/HihUr1gmFbEz1tdXX9+/fP+6+++7o0qXLBu8577zz4t/+7d+y62222WbLXwAAAAAAAAAAoA4pSruBiIiLLroo3nrrrTjiiCMi4stQR/XYl419qq9t3759DBs2LF5++eWvDIJERGy77bax3XbbZT/NmzevhbcEAAAAAAAAACi81HcGqbbbbrvFk08+GRMnTow///nPMWrUqHj77bejsrJyg/d06NAh+vXrFyeddFIcf/zxUVJSUosdAwAAAAAAAADUPXUmDFKtW7du8fvf/z4iIioqKuLDDz+MefPmRXl5eVRUVESrVq2irKwsOnfuHJ07d065WwAAAAAAAACAuqXOhUHWVFpaGl/72tfSbgMAAAAAAAAAoN4oSrsBAAAAAAAAAADyRxgEAAAAAAAAAKABEQYBAAAAAAAAAGhAhEEAAAAAAAAAABoQYRAAAAAAAAAAgAZEGAQAAAAAAAAAoAERBgEAAAAAAAAAaECEQQAAAAAAAAAAGhBhEAAAAAAAAACABkQYBAAAAAAAAACgAREGAQAAAAAAAABoQIRBAAAAAAAAAAAaEGEQAAAAAAAAAIAGRBgEAAAAAAAAAKABEQYBAAAAAAAAAGhAhEEAAAAAAAAAABoQYRAA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CwAAm6qianVB669Yvaqg9QEANpUwCDmmTZsWr732Wnz22WexfPny2GabbaJr167Rv3//aNeuXdrtAQAA9VTLksaxQ5PmGzxflSQxo2JZzrGOpc2iKJPZaF0AANhUpUWNYkUBAyFNGvmxCwBQN/hbSQElSRKffvppjB07NsaNGxdjx46NN998M1asWLHOdWl75JFH4ve//32MGTNmveeLiori0EMPjV/84hdx0EEH1XJ3AABAfbexcS5zKpZH+3/emXPs7YNPiHalTQvdGgAAW5Hdt9k23l40r3D1m7cqWG0AgM0hDJJnS5Ysid/97ncxbty4GDduXCxYsCDtlr7S0qVL44wzzogHHnjgK6+rqqqKkSNHxsiRI+PCCy+Ma6+9NoqL/c8HAAAAAID6o8+2bQsaBumzrR22AYC6wU/z82zu3Lnx29/+Nu02Nsny5cvjqKOOihdffHGdc5lMJlq2bBkLFy5c59wNN9wQs2bNir/97W+R2ciWzQAAAAAAUFcc2b5z3Db1o4LVP6J9p4LVBgDYHEVpN0B6LrzwwnWCIAcccEA89dRTsXTp0liwYEEsWrQo7rvvvth7771zrrvvvvvid7/7XW22CwAAAAAANXJshy7RobRZQWp3KG0Wgzt0KUhtAIDNJQxSYM2bN48DDzwwLr744vjb3/4Wv/71r9NuKSIixo0bF7feemvOsdNPPz2ef/75OOKII6Jp0y/ncrdo0SKGDBkSY8aMicMOOyzn+l/96lcxffr0WusZAAAAAABqonFRoxjatUdBag/t2iNKihoVpDYAwOYyJibPmjVrFuecc07st99+sd9++0X37t2jUaN//eXvjjvuSK+5NVx22WU56x49esQtt9yS0+uamjdvHvfdd1907949Zs6cGRERFRUV8etf/zr+9Kc/FbxfAAAAAADIh6HdesR9X0yM8Qvn5q1mn1Zt4+JuPfNWDwCgpuwMkmft27ePm266Kc4888zo0aPHBsMVaXrzzTdj5MiROceuu+66KCkp+cr7ysrK4r/+679yjt12220xZ86cvPcIAAAAAACFUFLUKO7c55BoWdw4L/VaFTeOO3sPjOIiP3IBAOoOfzPZCj300EM561133TUGDhy4Sfd+97vfjRYtWmTXq1atiscffzyv/QEAAAAAQCHt1bJ1PNlvUI0DIa2KG8c/+g2K7i3K8tQZAEB+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33+sTJ+emzJq06ZNXuvXZ3vssUfOeuHChdnwzKaaPHnyRusCANQVu2+zbWHrN29V0PoAADQslVWr49Q3n6txEKTaolUr4wfjR0VlgXfEAwCgYTFeu+FLJQzSvn37iIjIZDIREfHRRx/ltf5LL72Us873GJr6rHv37usce+211zarxpgxY3LW22yzTXTq1KlGfQEAFEqfbdtu/KIa1W9X0PoAADQswye+u8WjYTZk/MK5MXziu3mtCQBAw2a8dsOXShhk9913z1m/8soreas9a9asePXVV7NBk+Li4thvv/3yVr++a9u2bfTo0SPn2Ob++a99/cCBA7N/3gAAdc2R7TsXtP4R7YViAQDYNCurVsfwSYUJbQyf9K7dQQAA2GTGazd8qYRBdtttt9hpp50iIiJJkhg7dmx8/PHHeak9fPjwWLnyyy0WM5lM7LffftG0adO81G4ojj322Jz1/fffv8n3Llu2LB5//PGvrAcAUJcc26FLdChtVpDaHUqbxeAOXQpSGwCAhueRmVNiZsWygtSeWbEsHp45pSC1AQBoeIzXbvhSCYNERBx55JGRJEl2R4lLLrmkxjVfffXVGD58eGQymUiSJCIEFdbnuOOOy1l/8sknMWrUqE269957741FixZl18XFxfHtb387r/0BAORT46JGMbRrj41fuAWGdu0RJQVO0AMA0HA8NXtaQev/c/b0gtYHAKDhMF674UstDHLxxRdHo0ZffuM8SZJ47LHH4tprr93ieuPGjYvjjz8+Kisrs8datWoV55xzTo17rcsymUzO5/TTT9/oPb17945DDz0059hFF12U82e3PgsWLIjLLrss59jpp58e7du33+y+AQBq09BuPaJ3q/z+46ZPq7Zxcbeeea0JAEDD9saCuQWuP6eg9QEAaDiM1274UguD7LrrrnHaaadldwdJkiQuueSSuPDCC2Pp0qWbXKe8vDyuuOKKOOigg2LWrFnZWplMJi688MJo0aJFAd+i/vrNb36Ts3733Xfj7LPPjtWr1z9XdOnSpXHSSSfFzJkzs8dKS0vjP//zPwvaJwBAPpQUNYo79zkkWhY3zku9VsWN487eA6O4KLW/TgMAUA99tGRBYesvXVjQ+gAANBzGazd8xWk+/Oqrr44XXnghJk2alA1x/M///E/cddddcdJJJ8WAAQNiyZIl2ZEvEREjR46MefPmxWeffRYvvvhivPjii7Fs2bKckTOZTCb69u0bv/jFL1J5r9dffz1ef/319Z579dVX1zl28803b7DWySefXJBAS9++feOMM86I22+/PXvsjjvuiE8//TR++ctfxkEHHRRNmjSJJUuWxJNPPhm/+tWv4r333supcdlll0XnzoVNjAEA5MteLVvHk/0GxaAxT8aiVSu3uE6r4sbxj36DonuLsjx2BwDA1qCiav2/iJUvK1avKmh9AAAajurx2pdOeC3vtY3XrhtSDYO0bt06nnjiiejfv38sWLAgGwhZuHBh/PnPf44///nPOdcnSRJHHnnkOsciIhsESZIkOnToEA888EAUF6fzeo8//nhceeWVm3z9ueeeu8FzRx55ZMF2N/njH/8YH3/8cbzyyivZYy+//HIcccQRkclkomXLlrFw4fp/m+CEE05YZ2QMAEBdN6B1h3j1wMHxg/GjYvzCzd+iu3ertnFX74GCIAAAbJHSokaxooCBkCaNUv12LwAA9czQbj3ivi8mbtH3SjfEeO26I/V9rXfbbbd46aWXYo899sju7lEdCqn+rGnN42teX32uR48e8eqrr8YOO+yQxuvUK82aNYunnnoqjj/++HXOVYdy1ue8886Le+65J4psiw4A1EPdW5TFmAMHx1V79t3kbRA7lDaLq/bsG2MOHCwIAgDAFtt9m20LW795q4LWBwCgYTFeu2GrE/8tdO/ePcaNGxfnnHNOlJSU5IQ8NvaJ+DK40KhRo/jhD38Yo0ePjp122inlN6o/ttlmm/j73/8eI0aMiP3333+D12UymTj00EPj+eefjz/+8Y9RUlJSi10CAORXSVGjuGTXXjH1sO/H/ft+M07eYZd1rtm7RVmcteMecf++34yph30/Ltm1l60NAQCokT7bti1w/XYFrQ8AQMNTPV67poEQ47Xrnkyy9tYbKfviiy9i2LBh8fe//z0+++yzjV7fvn37OOaYY+JnP/tZdO3atRY6bNimTp0aY8aMialTp8aKFSuiefPm0bVr1+jfv3+0b9++VnuZPn16dO7cOSIipk2bFp06darV5wMAW485Fcuj/T/vzDk2+4hTo11p05Q6gq2P/x8CsDV44IuJMeT1ZwpW//59vxknbt+tYPVha+HvpgBsjT5YXG68dooK8bPxOjdEcvvtt48//OEP8Yc//CGmTp0ar7zySkyfPj3m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fp3JZOKAAw7YpPtWrVq10V0+zj333Pjd734XM2bMiCRJ4sYbbxQGYatmnAsAAAAAAADA1qUojYeWl5dHRESSJBERsccee6z3uuqdQ6qtWLFio7WLiori2GOPzdaeMGFCTJ06tSbtAgAAAAAAAADUG6mEQRYsWJCzbtWq1Xqva968eTbUERGxdOnSTaq/995756zfeuutzeoPAAAAAAAAAKC+SiUM0rhx45z1hka/tGjRImc9ffr0Tarfrl27nPVnn322Gd0BAAAAAAAAANRfqYRBWrZsmbNevHjxeq8rKyvLWU+ZMmWT6i9fvjwi/jVmZkP1AQAAAAAAAAAamlTCIDvuuGNE/CusUV5evt7r9txzz5zrXn311U2q/8EHH0REZEfMNG3adMubBQAAAAAAAACoR1IJg+yxxx45648++mi91/Xo0SP7dZIk8fjjj29S/YceeigbIImIaNu27RZ0CQAAAAAAAABQ/6QSBtlpp51yRsBU7+SxtgMPPDBKSkqy608++STuvffer6x90003xccff5xzrFevXlveLAAAAAAAAABAPZJKGCTiy6BHkiSRJEm8/vrrUVFRsc41rVu3jsMPPzySJIlMJhNJksSPfvSjeOSRR9Zb8+abb45///d/z9kVpEOHDjk7jAAAAAAAAAAANGTFaT340EMPjUcffTQiIioqKuLFF1+Mww47bJ3rLrroonjiiSciIiKTycTixYvj+OOPj7322isOPPDAaN26dcydOzeeeeaZmDRpUk5wJJPJxAUXXFCr7wUAAAAAAAAAkKbUwiDHH398XHTRRdn1vffeu94wyKGHHhonnnhiPPDAA5HJZLJBj/feey/ef//97HVJkkREZHcFyWQysfvuu8eFF15Y2BcBAAAAAAAAAKhDUhsTs8MOO8SAAQOyo2Luv//+WLx48XqvvfXWW6Nv3745gY/qUEj1p/pYxJfBkPbt28eIESOiWbNmtfZOAAAAAAAAAABpSy0MEhHx0ksvRVVVVVRVVcXixYujRYsW672uRYsW8fTTT8eZZ54ZEbFOAGTNEEiSJHHQQQfF2LFjY4899qi1dwEAAAAAAAAAqAtSGxOzuVq0aBG33nprXHTRRXHffffFyJEjY9q0aTF37txo3rx5dOzYMQ488MAYMmRIDBw4MO12AQAAWMOwie/EsInvbPB81f/tBLmmrz3/YBT9X/h/Qy7u1jMu7tazxv0BAAAAQENSb8Ig1fbee+/Ye++949e//nXarQAAALCJFlWujM9XLN2se2ZULNukugAAAABArnoXBgEAAKD+aVnSOHZo0rwgdQEAAACAXMIgAAAAFJxxLgAAAABQe4RBAAAAALYSN/3pqbjplqfyXvfcs4+Mc390ZN7rAgAAAFtGGAQAAABgK7F4yfKYMbO8IHUBAACAukMYBAAAAGAr0WKbptGxQ9kGz1dVJTFr9oKcY9u13zaKijIbrQsAAADUHcIgAAAAAFuJc3/01eNc5s5bFHv2vCDn2PPP/DratmlZ6NYAAACAPCpKuwEAAAAAAAAAAPJHGAQAAAAAAAAAoAERBgEAAAAAAAAAaECEQQAAAAAAAAAAGhBhEAAAAAAAAACABkQYBAAAAAAAAACgAREGAQAAAAAAAABoQIRBAAAAAAAAAAAaEGEQAAAAAAAAAIAGRBgEAAAAAAAAAKABEQYBAAAAAAAAAGhAitNuYGtRVVUVb7zxRrz77rsxe/bsSJIk2rRpE927d4++fftGSUlJ2i0CAAAAAAAAAA2AMEiBLVmyJK655pq4+eabY/bs2eu9plWrVnH66afHZZddFu3atauVvrp06RKfffZZjWrcfvvtcfrpp+enIQAAAAAAAAAgL1IbE/Pf//3fsWjRorQeXytef/312GuvveJXv/rVBoMgERELFy6M66+/PvbYY4946qmnarFDAAAAAAAAAKChSS0M8u///u+xww47xNlnnx3jx49Pq42Cee211+KQQw6JqVOnrnOutLQ0mjZtus7x+fPnx9FHHx2PPvpobbQIAAAAAAAAADRAqY6JWbZsWfzlL3+Jv/zlL7HvvvvGj3/84zjppJOiSZMmabZVY3PmzInjjjsulixZkj1WXFwc559/fpx77rmxyy67RCaTialTp8Zf/vKXGDZsWCxdujQiIlatWhUnn3xyvPHGG7HbbrvVWs//9V//FW3atNmse/r371+gbgAAAAAAAACALZVqGCQiIkmSiIgYN25cnHnmmXHxxRfHaaedFj/60Y9i9913T7m7LXPFFVfEjBkzsuvS0tJ48MEH49vf/nbOdTvttFP86le/im9961sxaNCgKC8vj4iIJUuWxMUXXxyPP/54rfV88sknR5cuXWrteQAAAAAAAABAYaQ2JqZaJpOJTCYTEV8GQ8rLy+P666+P7t27x6GHHhp///vfY/Xq1Sl3uemmTJkSt956a86xK6+8cp0gyJr69u0b//M//5Nz7IknnohXX321ID0CAAAAAAAAAA1XamGQESNGxGGHHRYRX4ZAqkMhmUwmkiSJJEni+eefjyFDhkTnzp3j8ssvj+nTp6fV7iYbPnx4rFy5Mrveeeed4z/+4z82et/3vve9OOCAA3KOXXXVVXnvDwAAAAAAAABo2FILgwwePDieeuqp+Pjjj+M//uM/ok2bNtkQyNqhkJkzZ8Z//dd/xc477xyDBw+Of/7zn2m1vVEPP/xwzvqss86K4uJNm8Zz9tln56yffvrpWLZsWb5aAwAAAAAAAAC2AqmPienWrVtcc801MX369LjzzjtjwIAB64RCqoMhq1evjsceeyyOOuqo7H1z585N+xWy3nzzzZg6dWrOsZNOOmmT7//Od76TExxZvnx5PP3003nrDwAAAAAAAABo+FIPg1Rr3LhxnHLKKfHyyy/H22+/Heecc05ss802G9wtZPLkyfGzn/0sOnfuHD/4wQ/ilVdeSfsVYtSoUTnr7bbbLnbZZZdNvr9Zs2bRq1evnGPPPvtsPloDAAAAAAAAALYSdSYMsqYePXrEjTfeGF988UXceOON8bWvfW2Du4VUVFTEPffcEwcddFD07NkzbrrppliyZEkqfX/wwQc56/3333+za/Tr1y9nPWHChBr1BAAAAAAAAABsXepkGKRa8+bN45xzzok333wzRo8eHaecckqUlpZGkiQREevsFvLee+/F+eefH9tvv32ce+658fbbb9dqvx9++GHOumvXrptdY+171q5ZSFOmTIknn3wy7rzzzrjrrrviH//4R7zxxhtRWVlZaz0AAAAAAAAAADVTnHYDm6pfv37Rr1+/uO666+K2226LW265JT799NOI+DIUEhHZUMiSJUvilltuiVtuuSX69u0bP/7xj2PIkCHRuHHjgvb48ccf56x33HHHza7RuXPnnPXnn38eS5cujebNm9eot43Zd999Y968ees917Rp0+jfv3+cddZZMWTIkCgurjf/swEAAAAAAACArU6d3hlkfVq3bh0/+clP4uOPP45//vOfMXjw4GjUqNF6R8gkSRKvvfZanHbaabH99tvHpZdeGlOnTi1Yb+Xl5TnrDh06bHaNjh07brRuIWwoCBIRsXz58hg1alScfPLJseuuu8Zzzz1X8H4AAAAAAAAAgC1T78IgazrssMNixIgR8dlnn8V3vvOd7PiYiHVHyMyfPz/+8Ic/RLdu3eK73/1ufPTRR3ntZfny5bF69eqcY82aNdvsOk2bNl3n2JIlS7a4r3ybMmVKfPOb34yrr7467VYAAAAAAAAAgPWo1/M+Kisr4/7774+bb745Ro8enQ1/VI+NiYicr5MkidWrV8cDDzwQI0aMiHPPPTd+//vfrzeAsbmWLl26zrEmTZpsdp319bK+2vnQqFGj+PrXvx6DBg2KPn36xJ577hllZWVRUlIS8+fPjw8//DCee+65+POf/xwzZszI3ldVVRWXXnpptGnTJs4666wtfv706dO/8vyazwQAAAAAAAAANk29DINMmjQp/vSnP8Xtt9+eHW+y5piY6nVERElJSVRWVkZE5JxbtWpV/PGPf4xRo0bFyJEjt2iky5qWL1++zrHGjRtvdp3S0tJNql1Tl1xySRx77LGxww47rPd8hw4dokOHDnHwwQfHZZddFr/85S/jmmuuydl95ZxzzokDDjggdt999y3qoXPnzlt0HwAAAAAAAACwYfVmTEySJPHII4/EkUceGbvttlv84Q9/iLlz52bDCWsGPYqLi+O73/1uvPzyyzF//vy4+eabo3fv3tmRMWuOkHn//ffjmGOOiaqqqhr1t75dQFauXLnZdSoqKjapdk39+Mc/3mAQZG2NGzeOq666Km644Yac46tWrYrLLrss770BAAAAAAAAAFuuzodBZsyYEb/61a9ip512iuOPPz5GjhwZVVVV64Q6kiSJjh07xpVXXhlTp06Ne+65JwYMGBDNmzePs88+O15//fV4+eWXY9CgQesESN5444249957a9TnNttss86xFStWbHad9e0Csr7aaTj//PPj+OOPzzk2YsSImDVr1hbVmzZt2ld+xo4dm4+2AQAAAAAAAGCrUmfHxDz77LNx0003xWOPPRarVq3KGU+y9iiYAw88MBtUaNSo0QZrDhgwIJ544ol47LHH4pRTToklS5Zkz913333x/e9/f4v7bdq0aTRq1ChWr16dPbZs2bLNrlOXwyAREZdffnmMGDEiu06SJJ5++un4wQ9+sNm1OnXqlM/WAAAAAAAAAICoYzuDlJeXx7Bhw2L33XePww8/PB566KGorKzM2QWkeieQpk2bxg9/+MN4++2344UXXogTTzzxK4Mgazr66KPjmmuuydZNkiTGjx9f4/633XbbnPXMmTM3u8aMGTM2WjdNPXv2jB133DHnmB08AAAAAAAAAKDuqBNhkDFjxsTpp58enTp1ip/+9KfxySefZEe/rD0Kplu3bjFs2LD4/PPP409/+lP06NFji5555plnRklJSXY9d+7cGr/HbrvtlrOeOnXqZteYNm1aznr77bevUzuDRER07949Zz179uyUOgEAAAAAAAAA1pbamJilS5fG3XffHTfddFO88847EfGvsS/VY2CqjxUVFcWgQYPi/PPPjyOPPDIvzy8uLo4dd9wxJk2aFBERK1eurHHNPfbYI1599dXsurr25pg8efI6Neua1q1b56zLy8tT6gQAAAAAAAAAWFtqYZCOHTvG0qVLswGQiH+FQKqPlZWVxZlnnhk//vGPY+edd857D/necWPtHTO2ZHzKmDFjctZ77rlnjXoqhAULFuSsW7VqlU4jAAAAAAAAAMA6UguDLFmyJDsCplp1CKRXr15x3nnnxcknnxxNmjQpaB9rhlFqauDAgTnrWbNmxaeffhq77LLLJt2/bNmyeOutt3KOHXrooflqL28++eSTnHX79u1T6gQAAAAAAAAAWFtqYZA1JUkSJSUl8Z3vfCfOO++8+PrXv14rzz3nnHNi5syZeavXu3fv6Ny5c0ybNi177L777ovLLrtsk+4fMWJEVFZWZtdNmjSJww8/PG/95cOnn366ThikZ8+eKXUDAAAAAAAAAKwt1TBIkiTRsWPH+NGPfhQ/+tGPYrvttqvV5//oRz/Ke83BgwfHf//3f2fXf/nLX+LSSy+N4uKN/1HfcsstOevDDjssmjdvnvcea+I3v/nNOseOPPLIFDoBAAAAAAAAANanKK0HH3jggXHvvffGZ599Fv/5n/9Z60GQQhk6dGiUlJRk15MnT45rr712o/fde++98dJLL+Ucu/TSSzd6X/WonerP6aef/pXX12Qszr333ht//etfc44dfPDBsdNOO21xTQAAAAAAAAAgv1ILg7zwwgsxZMiQTdoxoz7Zeeed46yzzso5dsUVV8QTTzyxwXvGjh0b5513Xs6xQYMGFWRczosvvhhHHXXUOsGTjbn++uvj1FNPzQmTZDKZuPrqq/PdIgAAAAAAAABQA6mFQRqyK6+8Mjp06JBdr1ixIgYPHhxD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/1nkvBtuuEGdO3c2jb388suW5ydJ69ev108//WQae/PNNxUZGVnovPLly2v8+PGmsY8//lgnTpywPEcAAAAAAAAAAAAAAOAZmkEs9t1335m2R48erYiICJfm3n777abtH3/8Uenp6ValZvftt9+aths2bKiePXu6NPf6669XbGysfTsnJ0dz5syxND8AAAAAAAAAAAAAAOA5mkEstH79eu3fv980dt1117k8/5prrjE1jpw7d04//vijZfmdN2vWLNP2sGHDXJ5bpkwZDRw4sNB4AAAAAAAAAAAAAADAf2gGsdDChQtN21WqVFGDBg1cnl+6dGm1atXKNPbLL79YkZrdyZMntXHjRtPYZZdd5laMTp06mbbzP28AAAAAAAAAAAAAAOA/NINYaMuWLabt9u3bux2jY8eOpu2tW7d6lVN+zuJ16NDBrRj5c0xJSdHBgwe9ygsAAAAAAAAAAAAAAFiDZhALbdu2zbRdr149t2Pkn5M/prfyx4uPj1eFChXciuHseVmdJwAAAAAAAAAAAAAA8AzNIBbasWOHabtWrVpux6hZs6Zp+9ChQ0pLS/MqrwtZkWP58uVVpkwZ09j27du9ygsAAAAAAAAAAAAAAFiDZhALnTlzxrSdmJjodoyqVasWGdcbp0+fNm17kqPkmKeVOQIAAAAAAAAAAAAAAM9F+DuBUHHu3Dnl5uaaxkqXLu12nFKlSjmMpaamepxXUbE8yVFyzNPKHJ256qqrFB0dbUms66+/Xg888ECh+7z11lv68ssvLTneeb///nuhjx8/flxXX321pce8//77dcMNNxS6z5NPPqmFCxdadszKlStr9uzZhe6zadMm3X777ZYdU5LGjx+vXr16FbrPbbfdpj///NOyYzZr1kwfffRRofv88ssveuqppyw7piR9+OGHat68eaH7XHXVVTpx4oRlx+zZs6deeOGFQvf54osv9Pbbb1t2TEmaNWuWEhISCt3n0ksvtfSYnCPMOEd4jnOEGecI73CO+B/OEd7hHGHGOcJznCPMQukckdioQaH7cI7wHOcIM84RnmMdYcY5wjucI/6Hc4R3OEeYcY7wHOcIM84RnuMcYcY5wnPFdY7IzMz0ar4zNINYxNmtXGJiYtyO46wZxMrbxOSP5UmOkmOenuR48ODBQh8/cuSI/d/r1693O35BXDnB7tu3TytWrLDsmK7I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ZIi++eYb+1ipUqX0/vvvu33cYMG6FEWhFksGajG4UaeeY23qG6F5vRPAC8OGDdPkyZPt23PnztXo0aPdipGSkqLPPvvM5f0XLVqkjIwMp4+1bdvW4R5jwe7o0aPq1auXGjZsqEGDBunyyy9Xq1atlJCQIOnvv8o5ceKEfvnlF33xxRf6/vvvTfPr16/v1vf3QseOHdP69evt2w0aNNAll1zi+ZOBT1CHxWPNmjVas2aN08d+//13h7HCFo833XSTYmNjXToudRgaqFPPTJkyxbQdHR2tG264wfLjUGehw5taMwxD8+bN07x581SvXj1de+21uuyyy9SqVSvVqFFDYWFh9v127typhQsX6t1339Wff/7pEGvcuHFq166dNU8qwLA2RVGow+LB2hTeoE49w9oU7rDi/wHHjh2rPXv2qH///urfv7/atWunhg0b2ussPT1d69at06xZs/Thhx8qOTnZPjcsLExTp05VgwYNrHlCAYh1KVxBLRYP1qbwBnXqGdamPmIAMMnJyTESExMNSYYko3Tp0kZ6erpbMf766y/7fG+/xo0b59Hxnn32WQ+/A763fv16p881KirKqFixohEREVHg9+Piiy829u/f7/Gx33vvvaD5PpVk1GHxGDdunGXfo7/++svl41KHoYE6dV9WVpZRqVIl0/Gvu+46nxyLOgsd3tTaokWLCqwZm81mxMbGGhUqVDDCwsIKra8HH3zQrZz9XWvuYm2KolCHxYO1KbxBnbqPtSncZcX/A7Zs2dLhfR8WFmaUK1fOKFOmTIH1FRkZaXz++edu5+zvOnMX61K4glosHqxN4Q3q1H2sTX2H28QA+YSHh5supZeenq4ffvjBjxkVbcuWLaZtm82mIUOG+Ckbz2VlZenUqVPKyclxeMxms+nWW2/VqlWrVLNmTY+PMWPGDPu/w8LCNHLkSI9jwXeow9BGHYYG6tR9c+bM0cmTJ01jo0aN8smxqLPQ4ataMwxDKSkpOn36tPLy8pzuU7lyZc2cOVNvvPGGW7H9XWtWYW2K86jD0EYdhgbq1H2sTeEuX9VZXl6ezp49q7S0NKePN23aVL///rtHfxns7zqzCutSXIhaDG3UYmigTt3H2tR3aAYBnLj33nsVHh5u3540aZIfsyla/vuNXXvttWrWrJl/knFB3bp1NXbsWLVr104REYXfraps2bIaPny4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sWTJEre+b4U1gyxdutStPEqVKmXMmjXLreN7+rqdPHnSuOSSS7x6La6++mrjzJkzbuVrGAU3GRw8eNC49NJL3crhiSeecPv43rDy3FetWjWHWJ999plpn0B8nVasWGHUqlWryOM6c8MNNxgREREeP5eLL77Yo2a3gpoAkpKSjP79+7t8/Pj4eOOXX35xiL9ly5ZCG5jyfzVo0MCj5q/z9u7dawwZMsSj7+GQIUOKfD8UZzPIRx99ZFStWtXt+FWqVDFmzJjh1vetoPfB0aNHja5duxZ5TGfNIPv27XNpbmFfjz76qFvPwxO5ubkO66O7777bo1g0g5j95z//ccj52muv9Tjeb7/95hCvXr16DmtDVzir5e3bt3ucGwAAAIJL8VwvFAAAAACKyZYtW3Ty5EmP56enp+vee+/VXXfdZUk+L774ou655x5lZma6POett97Sk08+ad/OysrSoEGD9N5777kcIycnR6NGjdKsWbPcyjcUPP/887r++ut16NAht+du3rxZvXv31hdffOF1HnPmzNHll1/uVh7nzp3TNddco/nz53t9/KKkpKRo/fr1XsWYNWuW2rdvr4MHD3qdz549e9ShQwf9/vvvbs178cUX9dRTT3l9fH9ISkpyGCtXrpxpO9Bep6VLl6p79+7av3+/R/OXL1/ucOsDd2zdulUdO3bUzz//7HGM81JSUtSjRw/98MMPLs9JSkrSwIED9ccff9jH/vjjD3Xp0kV//vmny3F27dql7t27O30PFGXFihVq3769Zs6c6fZcSZo5c6Y6duyoXbt2eTTfKtnZ2frHP/6h2267TUeOHHF7/rFjxzRs2DA9//zzXuVx9OhRderUSUuXLnV77t69e9W5c2eP5ha31atXO6yPunfv7p9kQsxNN93kcCur2bNn69SpUx7Fmzx5ssPYrbfeKpvN5nasHj16OIzNmzfPo7wAAAAQfILjZqEAAAAA4KHatWvrkksuUZMmTVSjRg3FxsaqVKlSSk1N1eHDh/XHH39owYIFDh/Ivf/++2revLnuvvtuj4/93XffmZo6qlSpogEDBqh169aqVKmSUlJStGHDBn311Vc6duyYae6ECRM0aNAgtW/fXvfcc4/pF/cXXXSRBgwYoIYNG6pcuXI6ffq0li1bpq+//trUdJKXl6e77rpL3bt3d+se877UoEEDnT17VtLfH8Dlf94tW7YsMkaFChUKfOz555/X2LFjHcYjIiLUo0cPXX755apevbpycnJ04MABzZ07VytWrJBhGPZ9s7KydNNNNyk8PFzDhg1z8ZmZ/fHHH3r88ceVlZUlSSpVqpR69eqlrl27KjExURERETpw4IB+/PFH/fLLL6a5OTk5+sc//qHNmzcX6+tWtmxZtWvXThdffLEaNmyo+Ph4xcbGKisrS2fOnNGWLVu0aNEibd261TRv586duu6667RkyRJFRHj2a4aUlBT169fP3jhjs9nUqVMnXX755apVq5bKli2rEydO6LffftO3336rjIwM0/wJEyZo4MCB6tChg2dP3g/27duntLQ0h/HKlSsXOs+fr9PRo0c1ZMgQ0/e/ffv2uuKKK1S7dm3FxsbqyJEj2rJli2bMmFFkvPDwcLVu3VpNmzbVRRddpIoVKyouLk6GYSg5OVk7d+7UihUr9NtvvykvL88+LzU1Vddff73Wr1+vmjVrevRcJGnkyJFat26dfbtNmzbq16+f6tatq7Jly+ro0aNauHChvv/+e9Px09PTdfPNN2vdunU6efKkBgwYYP/QNzIyUj169FDPnj1VrVo1RUREaO/evZo1a5ZWrlxpOv6ePXv0+OOP67///a/LOS9evFj9+vVzqIGwsDB16dJFnTp1Ut26dVWuXDmdO3dOBw8e1JIlS/TLL78oNzfXvv/27dt15ZVXas2aNYqLi3M4ToUKFezn46ysLIf3U82aNQs9F0t/n+8LkpeXp0GDBmnu3LkOj1WrVk29evXSJZdcokqVKikmJkanT5/W+vXrNW/ePFMjkmEYGjt2rCpVquRRE2deXp6GDRumPXv22Mfq1aun/v3766KLLlKlSpV06tQp/fXXX/rmm28c5t966606cOCAw3irVq3UvXt3NWrUSOXKlVNkZKRSUlJ05swZbdu2TRs3btSaNWtMr4mvLVmyxGGsbdu2xXb8UFa+fHkNHjzY1EialZWlzz77TPfff79bsdLT0zV9+nTTWHh4uEaNGuVRbrVr11blypV14sQJ+9jixYv1wAMPeBQPAAAAQcbPVyYBAAAAEKQC9TYx48aNM5o3b268+eabxo4dO1yak5GRYbz99ttGXFyc6ZjR0dHGwYMHXYrh7DLc528NEx4ebjz//PPGuXPnnM5NSkpyern/K664wvjmm2/s25UrVza++uqrAnPYuXOn0ahRI4c4L774osfPwerbxFxo3Lhxll4KftmyZUZ4eLhDzM6dOxd6SfTly5cbF110kcO8cuXKGfv27SvyuM7erzExMfZ/jxgxwjh8+HCh8ytUqOAQ46WXXnLpeXv6/f/rr7+McuXKGffee6+xePFiIysry6Xj/fbbb0bbtm0djvnqq6+6NN8wHG8DcOH3q0OHDsbatWsLzbt169YOMfr06ePy8b1h1bnv9ddfd4gTFRVlpKamOhwvUF6nC+urRYsWxvLlywucW9D5rmHDhsaQIUOMmTNnGmfPnnUpj7179xo33HCDQz79+/d3+bnkvz3Ihbfuqlu3rvHTTz8VOHfNmjVGlSpVHI7/+eefGwMHDrRvX3755YX+3Pn4448dzlFhYWHGgQMHXHoOR44ccZrHLbfcUuS5ateuXUafPn0c5rpyKwtn73lXbtdVmLFjxzrErFGjhvHVV18ZOTk5Bc7Lzs42PvroI6Ns2bIOtVPYeeO8/O+DC1+PihUrGlOnTi3wVhx5eXlGRkaGffvXX391eA716tUzli1b5tL34PTp08Znn31mdO3a1XjsscdcmuONa6+91pRrbGysR7cdMQxuE+PMzz//7JB3q1at3I4zZcoUhzj9+vXzKrfLL7/cFK969epexQMAAEDwCI7VNAAAAICAE6jNIK5+uOjMhg0bHBpCHn/8cZfmOvtA/vwHfTNnzixyflZWltGsWTPTXJvNZlSqVMmQZFStWtWl5pZdu3aZPuSUZDRq1Mjj5xAszSB5eXlG48aNnX5YnJmZWeT8U6dOOXz/JRkDBgwocq6z9+v5r+eff96l/H/99VfDZrOZ5jZo0MCluZ5+/zMzM4309HSXjpHfuXPnjL59+5qOWbNmTSM7O9ul+QV9vwYMGFBgE8GFTp065fCheFhYmEvNO96y4tx34sQJIzEx0SFOz549HfYNxNfpsssuM5KSkjzKyZtz9DPPPONwjty2bZtLc/M3AZz/uvjii40jR44UOf+3335zqNGEhAT7v2+44QaXvq/jx493yOGFF15w6Tn069fPNC88PNz47LPPXJprGH+fJ2+55RaH469cubLQeVY3gyxfvtwICwszxbv00kvdem/88ccfDj+vXfnAvKD3QZUqVYzNmze79TwefvhhU4zIyEhj586dbsU4Ly0tzaN57qhTp44p344dO3oci2YQR3l5eUbdunUdcl+3bp1bcZy9R7/++muvchszZoxDzMKaVAEAABA6wgQAAAAAFlmzZo1atWrl9dc//vEPj3Pw5rYaLVq00IsvvmgamzRpksfxJOmxxx7T4MGDi9wvMjLS4fYmhmHo5MmTkqRPP/1UDRs2LDJO/fr1dcstt5jGduzYod27d7uRdfD54YcftH37dtNYrVq1NH36dEVFRRU5v0KFCpo9e7ZKlSpVZFxXDRkyRE899ZRL+3bu3FlDhw41je3atcunr1tUVJTD83VVTEyMPvnkE5UuXdo+dv62N56qU6eOpk2bppiYmCL3rVChgsaNG2cay8vL008//eTx8YvLsWPHdNVVV+no0aMOj912220OY4H2OsXHx2v69OlOby3i6nxPjR07Vu3atbNvG4bh1Tk6Ojpa06dPV2JiYpH7durUSf369TONHT9+XJLUuHFjTZw40aXb7/zzn/9UuXLlTGMX3gasIKtXr3bY76WXXtKNN95Y5NzzbDabPvjgA1188cWm8QkTJrgcwwrjx4833XanWrVqmjt3rlvvjZYtWzrcXmfevHnasGGDRzlNnDhRTZo0cWvOhbeWkaTu3bsXemucwlxYo76QlZWlffv2mcZq167t02OWNDabzWH9JUkff/yxyzF2796tpUuXmsYqV66sq666yqvcnL3WO3bs8ComAAAAggPNIAAAAAAsk5aWpg0bNnj95c/GheHDh8tms9m3jx8/7vEvzMuVK6cnnnjC5f0HDBig6Ohoh/HevXurV69eLse59tprHcbWrVvn8vxg9M477ziM/fvf/1aZMmVcjlG3bl09+uijpjHDMPTuu++6nU9YWJheeeUVt+YMHz7cYWzt2rVuH7u4JCQkqG/fvqaxZcuWeRxv3Lhxbn0YfP311ys8PNw0Fsjfrz179mjChAlq0aKFfv/9d4fH27Vrp+uuu87y41r9Oj300EOqXr26t2l5xGazacSIEaYxb57LiBEj1Lx5c5f3v+aaa5yOjx071uUP82NiYjRgwADT2IYNG2QYRqHzXn75ZdN2gwYN9NBDD7l0zAtFRkY6/FyaN2+eMjMz3Y7liT///FNz5841jb344osODTKuuPHGGx2aJL/77ju34/To0cPhNXFFSkqKabtixYpuxygu+/btc3iP+auOQ9moUaMUFmb+dfvnn3/ucn1NnjzZ4XUaMWKE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qJKTk5WVleVQK+PGjfND5iVLIL1O7t5WJb+XX37ZaSNI9erVdffdd2vatGn6/fffdeDAAZ09e1YZGRkOz2XRokVe5RCMrL5yhr8E4vPw9j0tSQ8//LD+/PNP3XTTTYqJiSl035ycHC1btkz33XefateurX/961/FcrsOZz/XvGkAcvZ9s6K5xFlOwXgFk6ZNm6pDhw6msZ07d+rXX391ur+zW8TceuutluXj7PvqzToJAAAAwYMrgwAAAAAIGY899pjDX1b27dtXH3/8sapWrepynOL4C2lYx9lfznrz4ZqzuVb9dW6g+OGHH/Tzzz+bxhISEvTpp5/qiiuucDkOteJbofQ6HT9+XC+99JJpLCIiQq+++qruvfdelz+UD4TnUtycXdVh69atuuiii/yQjeecPY/rrrtOX375pR+ysVbjxo01bdo0vfvuu/rhhx+0aNEiLVu2TNu3b5dhGE7npKen69///rdmz56tH3/80aeNqM5uh+PuFa8u5OzqW6mpqR7HKyyGp7fL87fRo0dr5cqVprGPP/5YXbp0MY1t3LhR69atM401atTIYT9vOHutrbjtFwAAAAIfVwYBAAAAEBLS0tL0/fffm8Zat26t2bNnu9UIIgXmXy+jYM4+KDp79qzH8ZzNrVChgsfxAtEXX3xh2g4PD9f333/vVoOB5N2HiShaKL1Os2fPVnp6umns5Zdf1oMPPujW1RkC4bkUN2e3EgnG70OoPI/CxMfH68Ybb9RHH32krVu36tSpU5ozZ44effRRNW/e3OmcHTt2qH///srKyvJZXs4aTQ4ePOhxPKt/7hYWI1ibQa6//nqHq2/MmDHDoeFl0qRJDnOtvCqI5Py15ip4AAAAJQPNIAAAAABCwtKlSx2uCvL4448rMjLSrTgHDhxQdna2lanBxypXruwwtnXrVo/jbdmyxWHM2YeYweynn34ybfft21ft27d3O86ePXusSglOhNLrlP+5lC9fXvfdd5/bcQLhuRS3KlWqOIzt27fPD5l4p3LlyrLZbKaxYHwe7ihfvrz69++vCRMmaOPGjdq+fbvuuusuh1ufbN682WlTgFUqVqyouLg405g3zSDOfiZu27bN43jnOfvZHaw/f2NjYzV06FDTWFpamr766iv7dlZWlj777DPTPhEREbr55pstzeXQoUMOY3Xr1rX0GAAAAAhMNIMAAAAACAkHDhxwGPPkEtu///67FemgGLVu3dphbM2aNR7HW716tWnbZrM5PUawyszM1PHjx01jntRKbm6uVq1aZVVayCfUXqf85+gOHTq43awnlcxzdIcOHRzGli5d6odMvBMTE6OWLVuaxnbs2KFjx475KaPi16hRI/33v//V1KlTHR775ptvfHrsFi1amLa3b9/ucSxnPxM3btzocbzz/vzzT4exNm3aeB3XX0aPHu0wNnnyZPu/Z8+erVOnTpke79evnxITEy3NI3+jTnR0dNDdZgoAAACeoRkEAAAAQEg4efKkw5gnt/aYPn26FenABc5uDZGbm+t2nNatWysmJsY09t1333kU69ixY/r1119NY40bNw6p28Tk/+BJ8qxW5s6d63C5e1gn1F6n/OdoT57LyZMntWjRIqtSCho9evRwOF/OmTOn2K5iZdW5WpJ69+7tMDZz5kyPYgWzG2+8Ua1atTKNWdFMUZh27dqZtvft26fk5GSPYl122WUOY/PmzZNhGB7FO2/OnDkuHStYdO7cWY0bNzaNLVu2TDt37pQkffzxxw5znDWQeGvDhg2m7ZYtW3rUjAcAAIDgQzMIAAAAgJCQ/77skvMGkcLs3r1bs2bNsiolFCE2NtZhzJMPrSMjI9WjRw/T2NGjR/Xdd9+5HevDDz9UTk6OaeyKK65wO04gs6JWJOn111+3Ih0UINRep/zPx5Pn8u677yojI8OqlIJGXFycunfvbho7ePCgPv3002I5vlXnakm6+uqrHcb+/e9/O5x3S4L8V2ZISkry6fE6duzoMOZpA0qbNm2c3nbGm2atXbt2OVz5Jzw8XF27dvU4ZiC49dZbHcY+/vhjHT58WD/++KNpvEqVKurfv7+lx8/IyNCOHTtMY86uNgQAAIDQRDMIAAAAgJBQtWpVh7H8v2QvTF5enm699VaP/9oZ7itfvrzD2J49ezyKdc899ziMPfzww0pPT3c5xr59+zRhwgTTmM1m07333utRToEqPj5epUuXNo25UyuSNHHiRC1evNjCrJBfqL1O+c/Ry5cvV1pamsvzN2/erJdeesnqtILGU0895TD28MMPe3zOdEdsbKzD1UE8Pe5ll13m0NiyZ88e/fOf//Q0vaB15MgR03blypV9erxevXopLMz8q+D8V8JyVXR0tG677TaH8X/9618er6Meeughh7FrrrlG1apV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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots(figsize=(11, 7), dpi=200)\n", + "\n", + "layer_labels = [(int(key.split(\":\")[0]), int(key.split(\":\")[1])) for key in bf_energies.keys()]\n", + "plot_labels = [str(item) for item in layer_labels]\n", + "\n", + "plt.errorbar(\n", + " plot_labels,\n", + " sim_physical_energy_diff,\n", + " yerr=sim_physical_uncertainties.values(),\n", + " ecolor=(20 / 255.0, 26 / 255.0, 94 / 255.0),\n", + " color=(20 / 255.0, 26 / 255.0, 94 / 255.0),\n", + " capsize=4,\n", + " elinewidth=1.5,\n", + " fmt=\"o\",\n", + " markersize=8,\n", + " markeredgewidth=1,\n", + " label=\"Physical\",\n", + ")\n", + "\n", + "plt.errorbar(\n", + " plot_labels,\n", + " sim_logical_energy_diff,\n", + " yerr=sim_logical_uncertainties.values(),\n", + " color=(0, 177 / 255.0, 152 / 255.0),\n", + " ecolor=(0, 177 / 255.0, 152 / 255.0),\n", + " capsize=4,\n", + " elinewidth=1.5,\n", + " fmt=\"o\",\n", + " markersize=8,\n", + " markeredgewidth=1,\n", + " label=\"Logical\",\n", + ")\n", + "\n", + "ax.set_xlabel(\"Hamiltonian Parameters (U, V)\", fontsize=18)\n", + "ax.set_ylabel(\"Energy above true ground state (in eV)\", fontsize=18)\n", + "ax.set_title(\"CUDA-Q AIM Circuits Simulation (lower is better)\", fontsize=20)\n", + "ax.legend(loc=\"upper right\", fontsize=18.5)\n", + "plt.xticks(fontsize=16)\n", + "plt.yticks(fontsize=16)\n", + "\n", + "ax.axhline(y=0, color=\"black\", linestyle=\"--\", linewidth=2)\n", + "plt.ylim(\n", + " top=max(sim_physical_energy_diff) + max(sim_physical_uncertainties.values()) + 0.2, bottom=-0.2\n", + ")\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running logical AIM on Infleqtion's hardware " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The entire workflow we've seen thus far can be seamlessly executed on real quantum hardware as well. CUDA-Q has integration with Infleqtion's gate-based neutral atom quantum computer, [Sqale](https://arxiv.org/html/2408.08288v2), allowing execution of CUDA-Q kernels on neutral-atom hardware via Infleqtion’s cross-platform Superstaq compiler API that performs low-level compilation and optimization under the hood. Indeed, the AIM research results seen in [our paper](https://arxiv.org/abs/2412.07670) were obtained via this complete end-to-end workflow.\n", + "\n", + "To do so, users can obtain a Superstaq API key from [superstaq.infleqtion.com](https://superstaq.infleqtion.com/) to gain access to Infleqtion's neutral-atom simulator, with [pre-registration](https://www.infleqtion.com/sqale-preregistration) open for access to Infleqtion’s neutral atom QPU." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As a tutorial, let us reproduce the workflow we've run so far but on Infleqtion's QPU. We begin with the same GPU-enhanced VQE to generate the AIM circuits:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "cudaq.reset_target()\n", + "\n", + "if cudaq.num_available_gpus() == 0:\n", + " cudaq.set_target(\"qpp-cpu\", option=\"fp64\")\n", + "else:\n", + " cudaq.set_target(\"nvidia\", option=\"fp64\")" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Computed optimal angles=[1.5846845738799267, 1.5707961678256028] for U=1, V=-9\n", + "Computed optimal angles=[4.588033710930825, 4.712388365176642] for U=1, V=-1\n", + "Computed optimal angles=[-1.588651490745171, 1.5707962742876598] for U=1, V=7\n", + "Computed optimal angles=[1.64012940802256, 1.5707963354922125] for U=5, V=-9\n", + "Computed optimal angles=[2.1293956916868737, 1.5707963294715355] for U=5, V=-1\n", + "Computed optimal angles=[-1.6598458659836037, 1.570796331040382] for U=5, V=7\n", + "Computed optimal angles=[1.695151467539617, 1.5707960973500679] for U=9, V=-9\n", + "Computed optimal angles=[2.4149519241823376, 1.5707928509325972] for U=9, V=-1\n", + "Computed optimal angles=[-1.7301462945564499, 1.570796044872433] for U=9, V=7\n", + "\n", + "Finished building optimized circuits!\n" + ] + } + ], + "source": [ + "device_circuit_dict = generate_circuit_set(\n", + " ignore_meas_id=True\n", + ") # Setting `ignore_meas_id=True` drops the noisy-identity gate from earlier" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And now, we change backends! Before selecting an Infleqtion machine in CUDA-Q, we must first set our Superstaq API key, like so:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "# os.environ['SUPERSTAQ_API_KEY'] = \"api_key\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we declare the type of execution we would like on Infleqtion's machine based on the keyword options specified:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "cudaq.reset_target()\n", + "\n", + "# Set the following to run on Infleqtion's Sqale QPU:\n", + "cudaq.set_target(\"infleqtion\", machine=\"cq_sqale_qpu\")\n", + "\n", + "# Set the following to run an ideal dry-run on Infleqtion's Sqale QPU:\n", + "# cudaq.set_target(\"infleqtion\", machine=\"cq_sqale_qpu\", method=\"dry-run\")\n", + "\n", + "# Set the following to run a device-realistic noisy simulation of Infleqtion's Sqale QPU:\n", + "# cudaq.set_target(\"infleqtion\", machine=\"cq_sqale_qpu\", method=\"noise-sim\")\n", + "\n", + "# Set the following to run a local, ideal emulation:\n", + "# cudaq.set_target(\"infleqtion\", emulate=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With that, we're all set! That simple change instructs our AIM circuits to execute on Infleqtion's QPU (or simulator). Due to the general queue wait time of running on hardware, we optionally recommend enabling the `run_async=True` flag to asynchronously sample the circuits. This will allow the cell to be executed and not wait synchronously until all the jobs are complete, allowing other classical code to be run in the meantime. When using `run_async`, an optional directory to store the job information can be specified with `folder_path` (this will be important to later retrieve the job results from the same directory)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Posting circuits associated with layer=('1:-9')\n", + "Posting circuits associated with layer=('1:-1')\n", + "Posting circuits associated with layer=('1:7')\n", + "Posting circuits associated with layer=('5:-9')\n", + "Posting circuits associated with layer=('5:-1')\n", + "Posting circuits associated with layer=('5:7')\n", + "Posting circuits associated with layer=('9:-9')\n", + "Posting circuits associated with layer=('9:-1')\n", + "Posting circuits associated with layer=('9:7')\n", + "\n", + "All circuits submitted for async sampling!\n" + ] + } + ], + "source": [ + "submit_aim_circuits(\n", + " device_circuit_dict, folder_path=\"hardware_aim_future_results\", shots_count=1000, run_async=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With the above cell execution, all the circuits will post to execute on QPU. We can then return at a later time to retrieve the job results with the cell below:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Retrieving all circuits counts associated with layer=('1:-9')\n", + "Retrieving all circuits counts associated with layer=('1:-1')\n", + "Retrieving all circuits counts associated with layer=('1:7')\n", + "Retrieving all circuits counts associated with layer=('5:-9')\n", + "Retrieving all circuits counts associated with layer=('5:-1')\n", + "Retrieving all circuits counts associated with layer=('5:7')\n", + "Retrieving all circuits counts associated with layer=('9:-9')\n", + "Retrieving all circuits counts associated with layer=('9:-1')\n", + "Retrieving all circuits counts associated with layer=('9:7')\n", + "\n", + "Obtained all circuit samples!\n" + ] + } + ], + "source": [ + "aim_device_data = _get_async_results(circuit_layers, folder_path=\"hardware_aim_future_results\")" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "physical_energies, physical_uncertainties = aim_physical_energies(\n", + " data_ordering, aim_device_data[\"physical\"]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "logical_energies, logical_uncertainties = aim_logical_energies(\n", + " data_ordering, aim_device_data[\"logical\"]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Layer=(1, -9) has brute-force energy of: -18.251736027394713\n", + "Physical circuit of layer=(1, -9) got an energy of: -17.626499999999997\n", + "Logical circuit of layer=(1, -9) got an energy of: -17.69666562801761\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(1, -1) has brute-force energy of: -2.265564437074638\n", + "Physical circuit of layer=(1, -1) got an energy of: -2.1415\n", + "Logical circuit of layer=(1, -1) got an energy of: -2.2032104443266585\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(1, 7) has brute-force energy of: -14.252231964940428\n", + "Physical circuit of layer=(1, 7) got an energy of: -12.9955\n", + "Logical circuit of layer=(1, 7) got an energy of: -13.76919450035401\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(5, -9) has brute-force energy of: -19.293350575766127\n", + "Physical circuit of layer=(5, -9) got an energy of: -18.331\n", + "Logical circuit of layer=(5, -9) got an energy of: -18.85730052910377\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(5, -1) has brute-force energy of: -3.608495283014149\n", + "Physical circuit of layer=(5, -1) got an energy of: -3.476\n", + "Logical circuit of layer=(5, -1) got an energy of: -3.5425689231532203\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(5, 7) has brute-force energy of: -15.305692796870582\n", + "Physical circuit of layer=(5, 7) got an energy of: -14.043500000000002\n", + "Logical circuit of layer=(5, 7) got an energy of: -14.795918428433312\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(9, -9) has brute-force energy of: -20.39007993367173\n", + "Physical circuit of layer=(9, -9) got an energy of: -19.4715\n", + "Logical circuit of layer=(9, -9) got an energy of: -19.96524696701215\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(9, -1) has brute-force energy of: -5.260398644698076\n", + "Physical circuit of layer=(9, -1) got an energy of: -4.973\n", + "Logical circuit of layer=(9, -1) got an energy of: -5.207315773582224\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n", + "Layer=(9, 7) has brute-force energy of: -16.429650912487233\n", + "Physical circuit of layer=(9, 7) got an energy of: -15.182\n", + "Logical circuit of layer=(9, 7) got an energy of: -16.241375689575516\n", + "------------------------------------------------------------------------\n", + "Logical circuit achieved the lower energy!\n", + "------------------------------------------------------------------------ \n", + "\n" + ] + } + ], + "source": [ + "physical_energy_diff, logical_energy_diff = _get_energy_diff(\n", + " bf_energies, physical_energies, logical_energies\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As before, we use the same metric of comparing against the true ground state energies; however, this time, both the physical and logical circuits are fully exposed to real hardware noise. Yet, we expect the use of logical qubits afforded to us by the `[[4,2,2]]` code to achieve energies closer to the true ground state than the bare physical circuits (up to a certain error threshold). And indeed they do! Visually, we can plot the energy deviations of both the physical and logical circuits from the cell above and observe that the logical circuits are able to outperform the physical circuits by obtaining much lower energies, demonstrating the power of error detection and the beginning possibilities of fault-tolerant quantum computation: " + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAACIMAAAVkCAYAAABNJ02+AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAB7CAAAewgFu0HU+AAEAAElEQVR4nOzdd3yN5/8/8NfJHrKQECKJGdTW2JGgqKqt9mxLKa0WbT9GUaVao0W1arQRmxpFbamYsYKYQURIgkRk73X//vBLvk7u+5ycmXMSr+fjcR6P5Dr3dV3vs+75vq9LJgiCACIiIiIiIiIiIiIiIiIiIiIqF0wMHQARERERERERERERERERERER6Q6TQYiIiIiIiIiIiIiIiIiIiIjKESaDEBEREREREREREREREREREZUjTAYhIiIiIiIiIiIiIiIiIiIiKkeYDEJERERERERERERERERERERUjjAZhIiIiIiIiIiIiIiIiIiIiKgcYTIIERERERERERERERERERERUTnCZBAiIiIiIiIiIiIiIiIiIiKicoTJIERERERERERERERERERERETlCJNBiIiIiIiIiIiIiIiIiIiIiMoRJoMQERERERERERERERERERERlSNMBiEiIiIiIiIiIiIiIiIiIiIqR5gMQkRERERERERERERERERERFSOMBmEiIiIiIiIiIiIiIiIiIiIqBxhMggRERERERERERERERERERFROcJkECIiIiIiIiIiIiIiIiIiIqJyhMkgREREREREREREREREREREROUIk0GIiIiIiIiIiIiIiIiIiIiIyhEmgxARERERERERERERERERERGVI0wGISIiIiJ6Q2VmZmLjxo348MMP0aRJE1StWhVWVlaQyWRyj759+8rVi4yMFC3j6elpkNdAys2bN0/0Wc2bN8/QYRGVS35+fqLfW1BQkKHDIiJ643h6eorWx5GRkYYOi5RYtGiR3OdVp04d5ObmlliPxyVUFryp+4hcF5Oxu3fvHszNzeW+o2fPnjV0WEQ6Z2boAIiISD0RERF4+PAhnjx5gpSUFGRkZMDCwgKOjo5wcnJClSpV0LRpU9jb2xs6VCKl0tLScPv2bURERODFixdIT0+HIAiwtbWFs7MzateujYYNG8LOzs7QoRKVS6tXr8a3336Lly9fGjoUIiIiIiJ6Q0VFRWHhwoVyZT/8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qBG++uorLF++HHPmzEF2drbcMmFhYejatSuCg4NRoUIFXb4MnZGa/qT451u4nLEngzRr1gzh4eFIS0srKvP399c6GaT4e+Th4QFBECSTgEg7165dE5X5+fmVfiBERERkFNQ9diIi0oWgoCCEhITIlY0bNw4mJpzVnoiISl+9evXg6+uLU6dOFZWtXbsWc+bMMdpzzkSq4t4VEZGB5eXloU+fPpKJIK6urjhz5gx++eUXjYYkk8lk6N27Ny5cuICAgABUrFhRFyGXOyYmJmjXrh1+//13PHr0CJ988onkSBXJycno3r27wtFbNLF7927RqCRVq1bFN998I1pWlRFEDE0QBAwZMgQPHz4UPVe5cmWcPn0as2fPLjER5HUWFhb4+uuvce7cOckpYW7duoUxY8ZoE7beREVF4cSJE6Ly5cuXi8oOHjyIFy9elEJUmrO1tcUHH3wgV3bw4EHExcVp3Obt27dFUwmNHj1a8jdI2ouPjxeVubm5GSASIiIiIiJ6U/38889y/5uZmeHDDz80UDRERETA+PHj5f5PTk7Gn3/+aaBoiHSHySBERAa2YMECXLhwQVTu5uaGs2fPom3btlr3IZPJMGrUKISGhqJDhw5at1eeOTs7448//sD+/fsls34TExPxwQcfIC8vTyf9SSV4DB8+HGPHjhVdDA8NDcXVq1d10q++/Pbbbzh+/Lio3NHRESdPntTqzsOWLVsiKCgITk5Ooud2796NzZs3a9y2vgQEBKCgoECurGXLlhg/fjzq1KkjV56bm4tNmzaVZngaGTt2rNz/eXl5Wr33xUcFkclkRpvcUx4kJyeLyqytrQ0QCRERERERvYkiIiLw77//ypX16NHDqKd/JSKi8m/AgAFwcHCQK1u5ciUEQTBQRES6wWQQIiIDevjwIX744QdRuampKXbv3o1atWrptD83NzcEBgZi6NChOm23PHr//fdx6NAhWFpaip4LCQnB6tWrte7j8ePH+O+//0Tlo0ePhru7Ozp16iR6zphHB4mPj8fMmTMln9uwYQMaNWqkdR8NGjTAxo0bJZ/78ssvkZqaqnUfuiIIAjZs2CAqHz16NABg1KhRoueM+fMt5OPjI0pk0TTu3NxcUSKJn58fatasqXF8pFxOTo6hQyAiIiIiojdYQECA6MLawIEDDRQNERHRK5aWlnj//fflyiIiInDmzBkD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8o8//lhvfSr6rezevVtvfapqx44dyMjIkCurWLGiaN7JgQMHiobsTUpKwj///KPvELVWfDqXxMREteLeuHEj8vLylLZZnt27dw+tWrXC0aNH1aqXnZ2NyZMn44cfftBTZKVv3bp1aNmyJU6ePKlR/ejoaIwYMQJfffWVaOotVT148ACtW7fGkSNH1KqXmZmJL774AlOmTNGo37Lk77//hre3N86ePatR/RcvXmD06NEYM2aM5BR5mlq1ahV69eqFZ8+eqVUvPDwcPj4+uHHjhs5iKY/i4uLw559/IjExUat2Hj9+jH79+uG7777TUWSvREZGwtvbGwcPHtSqnXPnzqFVq1YICQlRq15iYiKGDh2KZcuWadSvnZ0dvL295cqSk5Nx5coVldsIDAzU6LniTp48iYKCArmyTp06lTj9xMaNG3Hr1i2V+5GSnZ2NFStWwM/PD8+fP9eqrdcJgoAPP/wQ06dPR2ZmplZtHThwAE2aNJE8hlNFQkICvvjiCwwfPlyn68CyplGjRvjtt99E5cePH8fChQvVauvKlSuYPn26qLxnz5746quvVGrDWD7X06dPo3Hjxvjjjz9E+8fqePnyJR4/fqxxfZKXk5ODESNG4MMPP8TLly81auP06dN4++23FR4vayI9PR3du3fHzz//LFpvl2TDhg3o27evVt8zbR0+fFhU5ufnV/qBFBMdHY327dtjyZIlGv2eCwoKsG7dOrz99tu4e/euSnV8fHxEU+qdPn1arc+n+NTChfLy8nD69GmV25HaZ+jcubPK9YODg9GkSROsW7cO+fn5KtcrlJGRgfnz56NHjx5ISUlRu76U7OxsvP/++1i8eLFBv/PaevbsGXx8fODv76/x8W6h/Px8o3gvbt++jXbt2uHixYtq1UtJScGYMWPwzTffaNW/sWz/yxpjOHdTnK6OB4vr1KmTqOzQoUM67YOoNHGiayIiA3j27BmuX78uKvfz8yszczm+SWQyGfr27Yvly5fLlefn5+P06dPo37+/Wu0lJydLHnBIJX74+fnBw8ND7qReQUEBAgICMHPmTLX61Zfc3FzJExCenp5o37693vpt3749PD09ERkZKVd+/PhxFBQUlMp8oopIJfsMHToUFhYWcmUVKlRA//79sWnTJlH9IUOG6DVGbfXt2xdOTk5yFwj9/f0xePBgler7+/vL/V+/fn20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Lfrf1NQU4eHhWo+EoGtZWVmIiopCSkoKAMDW1hY1atTQy5335VVKSgqePn2KjIwMCIJQtD51dnbmMYOGEhMT8fLlS6SlpSE/P7/oONnZ2ZnbBy0Zw/a/LDLkuRtd2LJli2j6yn379qF3794GiohId5gMQkREREZp69atGDFihOgOnAoVKuC///5TONw5EZGhvKnJIERERERE6oqPj0etWrWQmppaVDZp0iSsWrXKgFEREdGbRhAENG3aFDdv3iwqa926NS5cuGDAqIh0h2lsREREZJSGDRuGJUuWiMrT0tLw3nvv4d69ewaIioiIiIiIiIi0VblyZXz55ZdyZX/99ZfCKUWIiIj04eDBg3KJIACwcOFCA0VDpHtMBiEiIiKjNW3aNNHJIeDVHUTdunVDdHS0AaIiIiIiIiIiIm1NmzYNVapUKfo/MzMTy5YtM2BERET0pime+NGtWzd06dLFQNEQ6R6TQYiIiMioLVu2DEOGDBGVP3nyBN27d0dCQoIBoiIiIiIiIiIibdjb2+Onn36SK1u5ciViYmIMFBEREb1J9uzZIzcdjLm5OVauXGnAiIh0j8kgREREZNRkMhkCAgLQuXNn0XN37txBz549kZGRYYDIiIiIiIiIiEgbo0aNQvv27Yv+z8zMxNy5cw0YERERvQny8vIwY8YMubKpU6fCy8vLQBER6YeZoQMgIiIiKomFhQX27t2L8ePHIykpSfT8unXrMGXKlNIPjIiIiIiIiIg0JpPJsG7dOuzYsaOozMzMDLm5uTA3NzdgZEREVJ49efIEQ4cOLfpfJpNh+vTpBoyISD9kgiAIhg6CiIiIiIiorBszZgwCAgLkyvz9/TFmzBjDBERERERERERERERvLE4TQ0RERERERERERERERERERFSOMBmEiIiIiIiIiIiIiIiIiIiIqBxhMggRERERERERERERERERERFROSITBEEwdBBEREREREREREREREREREREpBscGYSIiIiIiIiIiIiIiIiIiIioHGEyCBEREREREREREREREREREVE5YmboAIiMSVZWFm7evAkAcHZ2hpkZfyJERERERERERERERERERKQ/eXl5ePHiBQCgcePGsLKy0rpNXukmes3NmzfRqlUrQ4dBRERERERERERERERERERvoEuXLsHb21vrdjhNDBEREREREREREREREREREVE5wpFBiF7j7Oxc9PelS5fg6upqwGiIiIiIiIiIiIiIiIiIiKi8e/bsWdEMFq9fs9YGk0GIXmNm9n8/CVdXV7i5uRkwGiIiIiIiIiIiIiIiIiIiepO8fs1aG5wmhoiIiIiIiIiIiIiIiIiIiKgcYTIIERERERERERERERERERERUTnCZBAiIiIiIiIiIiIiIiIiIiKicoTJIERERERERERERERERERERETlCJNBiIiIiIiIiIiIiIiIiIiIiMoRJoMQERERERERERERERERERERlSNMBiEiIiIiIiIiIiIiIiIiIiIqR5gMQkRERERERERERERERERERFSOMBmEiIiIiIiIiIiIiIiIiIiIqBxhMggRERERERERERERERERERFROcJkECIiIiIiIiIiIiIiIiIiIqJyhMkgREREREREREREREREREREROUIk0GIiIiIiIiIiIiIiIiIiIiIyhEmgxARERERERERERERERERERGVI0wGISIiIiIiIiIiIiIiIiIiIipHmAxCREREREREREREREREREREVI4wGYSIiIiIiIiIiIiIiIiIiIioHGEyCBEREREREREREREREREREVE5wmQQIiIiIiIiIiIiIiIiIiIionKEySBERERERERERERERERERERE5QiTQYiIiIiIiIiIiIiIiIiIiIjKETNDB0BE8goKCpCWlobU1FTk5uaioKAA+fn5hg6LiIh0yNTUFKamprC0tISDgwOsrKwgk8kMHRYREREREREREREREZUTTAYhMgKCICA1NRWpqalIS0tDQUGBoUMiIiI9ysvLAwBkZGQgMTERFhYWcHBwgKOjI8zMuHtGRERERERERERERETa4dUGIgMTBAHPnj1DcnKy5PMymQympqalHBUREelTfn4+BEEo+j8nJwcvXrxAUlISPDw8YG5ubsDoiIiIiIiIiIiIiIiorGMyCJEBSSWCmJqaws7ODnZ2drCxsYGJiYkBIyQiIn3Jz89HamoqkpOTkZGRAQDIzc1FVFQUPDw8mAhIREREREREREREREQaYzIIkYFIJYK4urrCwcEBMpnMgJEREVFpMDU1haOjIxwdHZGTk4MnT54gNzcX2dnZiIqKgru7OxMCiYiIiIiIiIiIiIhII7zCQGQghXeDF6pevTocHR2ZCEJE9AaysLCAu7t70WggmZmZSExMNHBURERERERERERERERUVjEZhMhAUlNTi/52dXWFvb29AaMhIiJDs7CwgJubW9H/aWlpBoyGiIiIiIiIiIiIiIjKMiaDEBlAQUFB0UU+U1NTODg4GDgiIiIyBjY2NrCwsAAAZGRkID8/38ARERERERERERERERFRWcRkECIDSEtLQ0FBAQDAzs6OU8MQEVEROzu7or85OggREREREREREREREWmCySBEBvD6FDGvX/QjIiKqUKFC0d9MBiEiIiIiIiIiIiIiIk0wGYTIAHJzc4v+trGxMWAkRERkbKytrYv+fn17QUREREREREREREREpComgxAZQOEUMTKZDCYm/BkSEdH/kclkRdOHFW4viIiIiIiIiIiIiIiI1MGr0EQGkJ+fDwAwNTU1cCRERGSMCrcPhdsLIiIiIiIiIiIiIiIidTAZhIiIiIiIiIiIiIiIiIiIiKgcYTIIERERERERERERERERERERUTnCZBAiIiIiIiIiIiIiIiIiIiKicsTM0AEQ0Ztj9ZojWL32iM7bnTj+XUz85F2dt0tEREREREREREREREREVBYxGYSISk1qWiaePU/US7tERERERERERERERERERPQKk0GIqNTYVbCGa1Unhc8XFAiIjUuSK6vi4ggTE1mJ7RIRERERERERERERERER0StMBiGiUjPxE+XTucS/TEGDJp/JlQWd+B6VK9nrOzQiIiIiIiIiIiIiIiIionLDxNABEBEREREREREREREREREREZHuMBmEiIiIiIiIiIiIiIiIiIiIqBxhMggRERmdDRs2QCaTyT2CgoIMHVa5M2/ePNH7HBkZaeiwjA6/j0REREREREREREREVNaYGToAIiIyXjKZTOVlTUxMYG9vD0dHR7i4uKBly5Zo1aoVevTogSpVqugxSiIiIiIiIiIiIiIiIiJ6HUcGISIinSgoKEBSUhIiIyNx6dIlrF69GmPHjoW7uzuGDx+O27dvGzpEIiIiIiIiIiIiIiIiojcCk0GIiEivcnJysHXrVrRs2RLLly83dDhERERERERERERERERE5R6niSEiIrXY2dnBxEScS1hQUIDU1FSF9bKzs/Hll18iPj4eCxYs0GeIRERERERERERERERERG80jgxCRERquXHjBpKSkkSPlJQU5ObmIiwsDKtWrULdunUl6y9cuBBbt24t5aiJiIiIiIiIiIiIiIiI3hxMBiEiIp0xMzODl5cXJk2ahLt372Lq1KmSy33zzTfIzs4u5eiouHnz5kEQBLmHp6enocMiIiIiIiIiIiIiIiIiLTEZhIiI9MLU1BTLli3D+PHjRc9FR0djw4YNpR8UERERERERERERERER0RuAySBERKRXy5YtQ8WKFUXl//77rwGiISIiIiIiIiIiIiIiIir/mAxCRAaXk5OHfQcuYdbcLaLn+n3wI6ZM+xP7DlxCTk6eAaIjbVWoUAEjR44UlZ86dQr5+fkGiIiIiIiIiIiIiIiIiIiofDMzdABE9ObKzc3DH+uOYvXao3jxIllymbB7MQi7F4Ot20/DxcUBE8Z1x4Rx3WFuztVXWeLn54cVK1bIlaWmpiIuLg6urq5atR0WFoarV6/i6dOnyMnJQeXKleHq6ooOHTrAyclJq7aNRUpKCm7evIkHDx4gOTkZqampMDc3h42NDSpVqgRPT0/UqVMHLi4uhg61yP3793Hv3j3Ex8cjPj4eeXl5sLe3h6urKxo2bIi6devC1NRUqz6ysrKK+nnx4gVSUlIgk8lQsWJFVKpUCY0bN0bdunV19IqIiIiIiIiIiIiIiIjKDl5NJSKDCLsXjUlT1uLGzccq14mLS8b8hTvxz/6L+G3FeNT3ctNjhKRL7u7ukuXx8fEaJYPk5ORgzZo1WLlyJcLDwyWXMTU1RYcOHTB//nx07NhRpXbff/99HDx4UK4sNDQUTZo0UTvG1w0dOhTbt2+XKztz5gw6dOigsE52djY2bNiATZs24fz58xAEocR+PDw80K5dO/Tt2xc9e/aEra2t0uXnzZuH7777Tq7s0aNH8PT0LLEvKZcuXcLq1atx4sQJREdHK13W3t4enTt3Rt++ffHBBx/AxsamxPYFQcDZs2dx8OBBBAUFISQkBHl5ykcMqlKlCt577z1Mnz4dDRs2VOv1EBERERERERERERERlVWcJoaISt2lyw/wXp8FaiWCvO7Gzcd4r88CXLr8QMeRkb7Y29tLlqempqrd1t27d9GiRQt8/vnnChNBACA/Px+nTp2Cr68vJk+ejIKCghLbnjhxoqhs7dq1asf4uvj4eOzdu1eurGHDhkoTQc6cOYNGjRphwoQJOHfunEqJIADw+PFjbNu2DYMHD8bkyZO1ilsdt27dwnvvvYfWrVtjw4YNJSaCAK9GO/nnn38wZswYVKtWDc+fP1e6/NatW+Hu7o6OHTvip59+wsWLF0tMBAGA2NhY+Pv7o1GjRhgzZgwyMjJUfl1ERERERERERERERERlFZNBiKhUhd2LxpCRy5CamqlVO6mpmRgychnu3Y/RUWSkT8nJ0tMAOTg4qNXOlStX0LZtW9y+fVuter/99hvGjRtX4nI9evSAh4eHXNmWLVuQman59zUgIADZ2dlyZePHj1e4/OHDh9GtWzeliS6qUDWBRFu7d+9GmzZtcPjwYY3bSE5ORlZWltJlLl26pFKSiSKCICAgIADt27dHbGysxu0QERERERERERERERGVBZwmhohKTW5uHiZNWat1Ikih1NRMfPr5Ghw5MAfm5lydGbNHjx5JlleuXFnlNqKiojB16lS5xBJPT0907twZbm5usLW1RVxcHM6ePYtLly6JkiH++usv9O7dG3369FHYh4mJCT755BPMnDmzqCwpKQk7duzAmDFjVI71devWrZP738rKCqNGjZJc9sWLFxgxYoRkYkT16tXRvn171K5dG/b29jA1NUVKSgpevnyJO3fu4MaNG0hMTNQoRk39/vvvmDx5smTiiampKby9vdGyZUs4OzvD2toaSUlJiImJwZUrVxAWFqbSaC2KmJiYoFatWmjUqBFq1aoFe3t72NraIj09HXFxcQgNDZUcPeT69esYPnw4jh07BhMT5sQSEREREREREREREVH5xKunRFRq/lh3VOOpYRS5cfMx/lh3FJ992lOn7ZJunTx5UlTm6OgIZ2dnldv48ssv8fLlSwBA48aNsXTpUnTr1k1y2eDgYIwYMQIRERFy5dOmTUPv3r0hk8kU9vPRRx9h7ty5yM3NLSpbu3atRskgp06dwr179+TKBg4cCCcnJ8nlly1bhoSEBLmyOnXq4LffflP4WgsJgoDLly/jwIED+Ouvv9SOVV2nT5/GlClTRIkgNjY2+OKLLzB16lRUqlRJYf24uDjs2bMH/v7+uHTpkkp9mpqaolevXujfvz/ee+89pe0X9vHbb79h8eLFcgk2gYGBWLlyJb744guV+iUiIiIiIiIiIiIiIipreEssEZWKnJw8/LHuqF7a/mPdUeTm5pW8IBlESkoKNm/eLCr38/NTa2SGwkSQ9957D8HBwUqTI9q2bYtTp06hYsWKcuUPHz5EUFCQ0n5cXFzQv39/ubLg4GDcunVL5VgLrV27VlT2ySefKFx+165dcv87Ozvj7NmzJSaCAIBMJkOrVq3w/fff4/Hjx/jmm2/UjldVSUlJGDRokGjUDXd3d1y5cgULFy4sMVHDxcUFEyZMwMWLFxEYGAhHR0ely/fr1w8RERHYu3cvRo4cWWL7hX189913OHv2rOi78PPPP4viJyIiIiIiIiIiIiIiKi+YDEJEpeLw0auIi0sueUENxMUl49CRq3ppm7Q3efJkpKSkiMp79eqldlteXl7YuXMnbG1tS1zWzc0N8+fPF5Xv3bu3xLoTJ04UlUkldiiTkJCA3bt3y5U1bNgQHTp0kFw+JycHDx8+lCsbO3YsqlSpola/AGBmZoYGDRqoXU9Vq1atQmxsrFxZxYoVcfbsWY367dy5c4nJIL6+vnB3d1e7bQBo2bKlaLqeqKgoHDhwQKP2iIiIiIiIiIiIiIiIjB2TQYioVPwXdFOv7Z88pd/2SX3Z2dn45JNPsGnTJtFznp6eGDlypNptrly5UqVEkEIjRoyAlZWVXFlISEiJ9Xx9fdGwYUO5ss2bN8tNNVKSgIAAZGdny5WNGzdO4fLx8fGistq1a6vcX2nJyMjAihUrROVr1qxBjRo1DBCRavr37486derIlUlNX0RERERERERERERERFQeMBmEiEpF6I3IMt0+laygoAAJCQm4fPkyFi1ahDp16kiOpiGTybBs2TKYm5ur1X79+vVVmi7ldQ4ODmjWrJlc2Y0bN1SqO2HCBLn/ExMTsXPnTpX7Lj4ShZWVFUaNGqVw+QoVKojKHj16pHJ/peXo0aOixJUmTZpg4MCBBopIdb6+vnL/X7hwwUCREBERERERERERERER6ReTQYioVDyMeKbf9h8+12v79H9q1qwJmUwmepiamqJSpUpo1aoVZs6ciejoaMn6CxcuRP/+/dXut2vXrhrF+9Zbb8n9n5aWhpycnBLrjRo1CjY2NnJlxRM8FDl79izu3r0rVzZw4EBUrFhRYR17e3u4urrKla1Zswbh4eEq9VlagoKCRGXFE2eMVdWqVeX+v3PnjoEiISIiIiIiIiIiIiIi0i8mgxBRqcjOztNr+1nZuXptn7Rna2uLtWvXYsaMGRrVb9GihUb1nJycRGXJyckl1nNwcMDQoUPlyqSSPKRIjYgyfvz4Euv17t1b7v/ExES8/fbbWLhwIZ49029ClapOnz4tKuvUqVOpx5GYmIiAgABMmTIFnTt3Rq1ateDs7AwrKyvJZCWZTIaFCxfKtZGeno7cXK47iIiIiIiIiIiIiIio/GEyCBGVCktLM722b2Wp3pQjVHpsbW0xbtw4hIaGYty4cRq3U7lyZY37Ly4jI0OluhMnThSVSSV6vC4xMRG7du2SK2vQoAF8fHxK7O+bb74RxZucnIzZs2ejevXqaNOmDWbOnInDhw8jKSmp5BegBxEREXL/Ozg4wMvLq9T6v3PnDvr374+qVatizJgxWLlyJU6ePIlHjx4hPj4e2dnZarVnqPeRiIiIiIiIiIiIiIhIn/R7dZaI6P+rXcsVt+880V/7tauWvBDphJ2dHUxMxLmEJiYmsLOzg6OjI1xcXNCiRQu0atUK77zzDhwcHLTut0KFClq3UUgQBJWWa9myJby9vXH58uWiso0bN+LHH3+EpaWlZJ1NmzYhMzNTrkyVUUGAV1PwbN26FYMGDRIlNQiCgIsXL+LixYtYtGgRZDIZGjduDD8/P3Tt2hXdunWDhYWFSv1oKjc3FykpKXJlVatWhUwm02u/hebPn4/vv/8eeXm6G2lI1cQgIiIiIiIiIiIiIiKisoTJIERUKpo28dRrMkjTJp56a5vk3bhxA56enoYOo9RMnDhRLhkkISEBu3btwvDhwyWXX7dundz/VlZWGDVqlMr99e7dG+fPn8enn36KixcvKlxOEATcuHEDN27cwMqVK+Hk5ISRI0fim2++QbVq1VTuTx0JCQmiMkdHR730Vdy0adPw888/q7SsmZkZrKysYGpqKleelZUlmWRDRERERERERERERERU3nCaGCIqFZ39Guu1/U6++m2f3lxDhgwRJTwomirm/PnzuHXrllzZwIEDUbFiRbX6bNGiBS5cuIDjx49jxIgRKiVcJCYmYuXKlahduzZWrVqlVn/G7siRI5KJINbW1hg+fDjWrFmDixcvIjo6Gnl5ecjNzUVqaiqSkpLkHv/73/8MED0REREREREREREREVHp48ggRFQqenRvARcXB8TFJeu8bRcXB7z3bgudt0sEvEo4GD16NFasWFFUdvr0ady7dw9eXl5yy0oliag6RYyUd955B++88w4KCgpw/fp1nD59GmfPnsXZs2cRGxsrWScrKwufffYZYmNj8f3332vctxSppJakpCSd9iFl2rRporL+/ftj3bp1aiXapKen6zIsIiIiIiIiIiIiIiIio8WRQYioVFhYmGHCuO56aXvCuO4wN2duG+nPhAkTRGXFEz+Sk5Oxc+dOubIGDRrAx8dH6/5NTEzQokULfPHFF9i1axeeP3+OsLAw/Pzzz2jTpo1knQULFiA4OFjrvl9nbm4Oe3t7ubLY2Fi9TrVy+/Zt3LlzR66sbdu22Llzp9ojrkhNc0NERERERERERERERFQeMRmEiErNhHHd0aSxh07bbNrEExPHv6vTNomKq1+/Pjp16iRXtnHjRmRnZxf9v2nTJmRmZsoto82oICXx8vLCl19+ieDgYJw7dw61a9cWLbN06VKd91u8n6SkJNy/f1/n/RQKDAwUlX3zzTcwNTVVuy19xklERERERERERERERGRMmAxCRKXG3NwMv60YDzs7a520Z29vg99WjIeZmfoXhYnUNXHiRLn/4+PjsXfv3qL/161bJ/e8lZUVRo0aVSqxtWvXDkeOHIGFhYVc+YkTJ3TeV8eOHUVlJ0+e1Hk/hZ4+fSoqa9eundrtZGdn4/Lly7oIiYiIiIiIiIiIiIiIyOgxGYSISlV9Lzds3zRN64QQe3sbbNs4FV71qusoMiLl+vbti6pVq8qVFU4Vc/HiRdy4cUPuuQEDBqg9jYk26tSpgw4dOsiVpaSkICkpSaf9FB8hBQD++OMPnfbxOqmpXRwdHdVuZ+fOnXIjuRAREREREREREREREZVnTAYholLXyrsuDu//VuMpY5o09sChfbPRyruujiMjUszc3BwfffSRXFlQUBAePHhQlBTyuk8++aS0Qivi4uIiKsvJydFpH927dxf1ExoaKjdKii7Z2dmJyp49e6ZWG3l5eViyZImuQiIiIiIiIiIiIiIiIjJ6TAYhIoPwqlcdRw7MwZxZg+Di4qBSHRcXB8yZNQhHDszhiCBkEOPHj4eJyf9tOgVBwLJly7Bjxw655Ro0aAAfH5/SDg937tyR+9/c3BzOzs467cPKygpTpkwRlY8fPx4xMTE67QsAqlcX/9YPHDigVhvz5s3DzZs3dRUSERERERERERERERGR0WMyCBEZjLm5GT77tCeuX/oZ6/+YhAH92oqWaVDfDcOHdsT6Pybh+qWf8dmnPWFubmaAaIkAd3d39OzZU65szZo1SE9PlysbP3682m0fPHgQQ4cOxeXLlzWKbefOnaKpatq0aQOZTKZRe8pMmjQJrq6ucmXx8fHw8fHBvXv31G7v1KlTCqez8fPzE5UtWLBA5cSTn3/+GT/88IPaMREREREREREREREREZVlTAYhIoMzNzdDn16tsOC7YaLn9uz8BsuXfoQ+vVoxCYSMwsSJE5U+b2VlhVGjRqndbm5uLrZv345WrVrh7bffxo8//oj79++XWC81NRXff/89hg8fLnruww8/VDsOVTg4OGDHjh0wM5P/TT569AgtW7bEt99+i4SEBKVtvHz5EuvXr0fbtm3h5+enMBmkefPmqF+/vlzZ8+fP0bFjR5w6dUph++Hh4RgwYACmTZsGQRAAQJTAQkREREREREREREREVF7xyioREZEaunfvjpo1a+LRo0eSzw8YMAAVK1bUqo+QkBCEhIRgxowZqFy5Mpo3bw4vLy84OTnBwcEBOTk5ePHiBW7duoUzZ84gKytL1Iavry9Gjx6tVRzK+Pj4YPny5fjss8+Kki0AID09HQsWLMCiRYvQunVrtGjRAs7OzrC2tkZycjJiYmJw9epV3LlzB3l5eSX2I5PJsGDBAgwcOFCuPCIiAn5+fmjSpAl8fX1RrVo1FBQU4Pnz5zh//jyuXr0qF5ePjw98fX2xYMEC3b0JRERERERERERERERERorJIERERGowMTHB+PHjMWPGDMnnP/nkE532Fx8fj+PHj+P48eMq13n77bexc+dOvUwR87pJkybB2dkZY8aMQWZmptxz+fn5OH/+PM6fP691PwMGDMBnn32GX3/9VfTcjRs3RNPjFPfWW29h7969kvWJiIiIiIiIiIiIiIjKI04TQ0REpKYPP/wQFhYWovIGDRrAx8dHozYrV64MJycnreIyMzPD5MmTcfLkSbi4uGjVlqoGDRqE8+fPo1OnThq34ezsDBsbG6XLLF++HDNmzFA7waVPnz44d+4cKlWqpHF8REREREREREREREREZQ2TQYiIiNTk4uKCXr16icrHjRuncZsdOnTAixcvcOrUKcycORO+vr4lJkgUcnd3x/Tp03Hr1i38+uuvqFChgsZxaKJZs2b477//8N9//2HIkCGoXLlyiXUqVqyIQYMGYceOHYiOji4xecXExAQ//PADzp07h/fffx+mpqYKlzUzM8M777yDgwcP4p9//oGDg4Par4mIiIiIiIiIiIiIiKgskwmCIBg6CCJjER0djRo1agAAoqKi4Obmppd+Hjx4gLy8PJiZmaFu3bp66aMsin+ZggZNPpMru3vjV1SuZG+giIgUa9iwIe7evVv0v5WVFWJiYlCxYkWd9ZGfn4+IiAiEh4cjOjoaKSkpyMjIgI2NDezt7eHm5oamTZuiWrVqOutTFwoKChAaGoqIiAjEx8fj5cuXMDMzg52dHapXr44GDRqgdu3aMDHRPCc1JSUF586dw+PHj5GQkAATExM4OTmhTp068Pb2hr192V5vcDtBRERERERERERERPTm0Md1ajOtWyAiInrDnD9/Xi4RBAAGDBig00QQADA1NUXdunXLXDKAiYkJmjdvjubNm+utD3t7e/To0UNv7RMREREREREREREREZVlnCaGiIhITX/88YeobPz48QaIhIiIiIiIiIiIiIiIiEiMySBERERqiIuLw99//y1X1qhRI3Ts2NFAERERERERERERERERERHJYzIIERGRGpYsWYKsrCy5skmTJhkoGiIiIiIiIiIiIiIiIiIxJoMQERGp6Nq1a1ixYoVcmYuLC0aNGmWgiIiIiIiIiIiIiIiIiIjEmAxCRERUgufPn2PVqlXo2rUrcnNz5Z773//+BxsbGwNFRkRERERERERERERERCRmZugAiOjNsXrNEaxee0Th8wUFgqjM751vYWIiU9ruxPHvYuIn72odH1GhrVu34tNPPwUAZGdni6aFKdSwYcOi5YiIiIiIiIiIiIiIiIiMBZNBiKjUpKZl4tnzRLXqxMYlqdQukS7l5OQgOTlZ6TI2NjbYuHEjLC0tSykqIiIiIiIiIiIiIiIiItUYVTJIbGws7t+/j8jISERFRSE1NRXp6enIy8uDjY0NbG1t4eLiAg8PD9SsWRP169eHqampocMmIhXZVbCGa1UnvbRLVJpcXFywbds2tGzZ0tChEBEREREREREREREREYkYNBkkPDwchw8fRlBQEC5fvoyYmBi16ltaWqJp06Zo3bo1unfvjs6dO/MObSIjNvETTudCZZOpqSmcnJzQsGFD9OrVCx999BGcnHSf2ERERERERERERERERESkCzJBEITS7PDRo0fYuHEjtm7divDw8KJyTcOQyWRFf1tZWaFr164YNWoUevXqBXNzc63jpTdLdHQ0atSoAQCIioqCm5ubXvp58OAB8vLyYGZmhrp16+qlDyIiKru4nSAiIiIiIiIiIiIienPo4zp1qY0MsnfvXqxYsQJnzpwBIE7+eD2pQ12FbWVmZuLAgQM4cOAAnJycMHbsWHz++edFbxoRERERERERERERERERERFReWeiz8bz8vLwxx9/oG7duhg4cCDOnDkDQRAgCAJkMpnco7Bc3QcAyXYSEhLw888/o3bt2hg6dChu376tz5dKREREREREREREREREREREZBT0MjKIIAjYtGkTvvvuO0RGRoqSNgqXKeTs7IymTZuicePG8PDwgJubG1xdXWFjYwNra2uYmZkhMzMTmZmZSEhIQHR0NGJiYhAWFobQ0FDcv38feXl5Re293kdeXh527tyJXbt2YejQoZg3bx5q1aqlj5dNREREREREREREREREREREZHA6TwY5f/48Jk+ejNDQULkkEOD/EkCcnZ3Ro0cPdOrUCZ06dYK7u7tWfWZnZyM4OBgnT57EiRMncOHCBVHf+fn52LJlC3bu3ImpU6di9uzZsLGx0apfIiIiIiIiIiIiIiIiIiIiImMjE14fokNLo0aNwpYtWwCgaCqYwuYdHBwwdOhQfPDBB/D19YWJif5mqHn27Bn27NmDrVu3Ijg4GIB8QopMJoObmxs2b94MHx8fvcVBJUtOTsb58+cRHh6OlJQUWFpaonr16nj77bdRt27dUo8nOjoaNWrUAABERUXBzc1NL/08ePAAeXl5MDMzM8jrJCIi48btBBERERERERERERHRm0Mf16l1OjLI5s2bi5IugFeJF61bt8bEiRMxaNAgWFlZ6bI7hVxdXTFp0iRMmjQJt2/fxtq1a7FhwwakpqYWJahER0fj5MmTek0GEQQB4eHhuHTpEi5fvoxLly7h2rVryMrKEi1X2jw9PfH48WOt2vD398eYMWM0qhsaGor58+fjwIEDyM3NlVzmrbfewvTp0zF69Gi57xUREREREREREREREREREREppvNpYoBXyQ3du3fHjBkz0LFjR310obK33noLK1aswPz587Fq1SqsXLkSL1680Ft/aWlpWLRoES5fvozLly8jKSlJb32VVYsWLcKcOXOQl5endLnbt29j7NixCAgIwM6dO+Hs7FxKERIREREREREREREREREREZVdOp+rxcfHBxcuXMDhw4cNngjyOgcHB8yaNQuRkZH4/vvvYWdnp5d+4uPj8cMPP+D48eNMBJHw1VdfYebMmZKJIHZ2dpLTBwUFBcHX1xcvX74sjRCJiIiIiIiIiIiIiIiIiIjKNJ2ODPLvv//ivffe02WTOmdtbY1Zs2ZhwoQJiIiIMHQ4RmPBggWoVKmSWnXatm2r1vJbtmzB0qVL5co8PT0xa9YsDBw4EI6OjsjJycGlS5fw448/4uDBg0XL3b17F8OGDcORI0c4ZQwREREREREREREREREREZESOk0GMfZEkNdVqlRJ7eQHTdja2qJFixbw9vaGt7c3wsPD8e233+q9X3UNHz4cnp6eems/PT0dU6dOlStr3rw5jh49Kjf9i4WFBTp06IB///0Xs2bNwg8//FD03LFjx7B7924MHDhQb3ESERERERERERERERERERGVdTpNBiHAxsYGEyZMKEr+aNiwIUxNTYue37Bhg+GCM6AVK1YgLi6u6H8bGxvs2rVLLhGkuIULFyIkJARHjx4tKpszZw769+8vOZ0MERERERERERERERERERERMRlE51xcXLB69WpDh2FU8vLy8PPPP8uVTZs2DbVq1Sqx7qpVq1CvXj0IggDg1XQx+/fvR9++ffURKhERERERERERERERERERUZnH4RVI706fPo2XL18W/W9iYoJx48apVLdOnTro1KmTXNnevXt1Gh8REREREREREREREREREVF5opdkkHfffRe7du1Cbm6uPpqnMmbfvn1y/7dt2xY1atRQuf6QIUPk/j948CDy8/N1EhsREREREREREREREREREVF5o5dkkGPHjmHw4MGoVq0apk6dips3b+qjGyoj/vvvP7n/27dvr1b9du3ayf3/8uVLXL9+XduwiIiIiIiIiIiIiIiIiIiIyiW9ThPz8uVLrFixAs2aNUOrVq2wdu1apKam6rNLMjL5+fm4f/++XFnr1q3VaqNhw4awt7eXK7t7967WsREREREREREREREREREREZVHZvpsXCaTQRAEAMCVK1cQEhKCqVOnYsCAAfjwww/h6+urz+5JTZGRkbh79y5evHgBmUyGSpUqoUqVKmjSpAnMzc01ajMiIgI5OTlyZbVq1VKrDZlMBk9PT9y4caOoLCwsTKN4iIiIiIiIiIiIiIiIiIiIyju9JoMAry7kA4AgCBAEARn/j737Do+qWt8+fu8kEwghjRKCVINUpQuIdNQTBBFFUNQjRQSponKO8hNRRFQ8VlSkSVVUEAtKVQQEpRcFwSC9JoSSSnqy3z94MzKkJzPZSfh+rmuuk7322s96BjkEkjtrxcfrs88+02effabg4GANHjxYAwYMUNWqVV3dCnJw66236uLFi1ne8/LyUtu2bTV48GA9+OCD8vDI+2+ba3cFkaSaNWvmu78aNWo4hEEOHjyY7xoAAAAAAAAAAAAAAFwPXHJMzLRp09SyZUt7AES6EgrJeGWMHzlyROPHj1etWrV0zz336LvvvlNaWporWkIusguCSFJCQoLWrVunRx99VHXr1tX69evzXPfSpUsO1zabTRUqVMh3f9eGhSIjI/NdAwAAAAAAAAAAAACA64FLdgYZPny4hg8frv3792vOnDlatGiRzp8/L+mfUEgG0zSVmpqqVatWadWqVapcubL69++vQYMGqWHDhq5oD4Vw/Phx3XnnnXrjjTf03HPP5To/Li7O4bpcuXIFWtfLyyvHuigZ3j2yV+8e2Zv7xHx6tk4TPVunidPrAgAAAAAAAAAAAEBJ5NJjYm6++Wa9++67+t///qcffvhB8+bN0+rVq5WammoPhFx9jIwkRURE6J133tE777yjNm3a6IknntBDDz0kb29vV7Z6XXJ3d1e7du109913q2XLlmrYsKECAgJks9l06dIlhYaGav369Zo9e7bCwsLsz6Wnp+v5559XxYoVNXjw4BzXuHz5ssN12bJlC9TrtWGQa+vm1enTp3O8f/X7hPPFpCTrTGLB/tvlVhcAAAAAAAAAAAAAcIVLwyD2RTw8dP/99+v+++/XuXPntGDBAs2fP1+hoaGSst4tRJK2bdumbdu26emnn9aDDz6oQYMGqV27dkXRcqn33HPPqVevXqpWrVqW94OCghQUFKTOnTtr/PjxmjBhgt566y37fxtJGjZsmNq3b6/69etnu05CQoLDtaenZ4H6LVOmTI5186pGjRoFeg7O4WvzVLWy2Qe70k1TYUnxDmNVy5ST21V/PmRXFwAAAAAAAAAAAABwRZGEQa5WpUoVPffcc3ruuee0ZcsWzZ07V0uWLFFsbKwkx2BIRvAgLi5O8+bN07x581SvXj0NHjxY/fv3V2BgYFG3X2qMGDEiz3M9PT315ptvqkaNGho9erR9PDU1VePHj9fSpUuzffbanUCSkwu2g0NSUlKOdVEy5Hacy/mkBAWuWegw9kfnPqpcxiubJwAAAAAAAAAAAAAA13KzcvG2bdtq9uzZCg8P17x589SpUydJ/4RAMoIhhmHINE2ZpqmDBw/q+eefV40aNXTfffdp+fLlSk9Pt/JtXDdGjRql3r17O4x98803OnfuXLbPlC9f3uE6MTGxQGtfuxPItXXz6tSpUzm+tm/fXqC6AAAAAAAAAAAAAAAUF5aGQTJ4eXlpwIABWr9+vQ4dOqQXXnhB1atXtwdApMzBkJSUFP3www/q1auXqlevrhdeeEGHDh2y+J2Ufi+//LLDtWma+vHHH7Odf21oo6DHuzgrDFK9evUcX1WrVi1QXQAAAAAAAAAAAAAAiotiEQa5WnBwsCZPnqzjx49r9erVevDBB+Xp6ZllMCRjLDw8XG+++aYaNGigjh07auHChQUOHSBnTZo0Uc2aNR3GctpNIyAgwOE6OTlZly5dyve6YWFhOdYFgJJu/vz5DsFHwzC0YcMGq9sqdjZs2JDp12n+/PlWtwUAAAAAAAAAAFCsFLswSAbDMPSvf/1LX375pcLCwvTBBx+oRYsWOe4WYpqmfvvtNw0aNEhVq1bVsGHDtG3bNovfSenTqFEjh+uIiIhs59arVy/T2MmTJ/O95qlTp3KtC8D5rv2mu2EYOn78uNVtAQAAAAAAAAAAAMhBsQ2DXM3f31+jRo3Szp079ccff2j06NGqUKFCjruFxMTEaNasWWrXrp3F3Zc+FSpUcLiOjIzMdm5wcLA8PT0dxo4ePZqv9UzTzPTN5wYNGuSrBgAAAAAAAAAAAAAA14sSEQa5WuPGjTV16lSdPXtWS5YsUbdu3eTm5mYPgVz90+uS7GEROE9UVJTDtZ+fX7ZzPTw8VLduXYex/O7W8tdffykmJsZhrGHDhvmqAQAAAAAAAAAAAADA9aLEhUEy2Gw29enTRytXrtTJkyf16quvysvLy+q2rguHDh1yuA4MDMxxfteuXR2uf/vtt3ytd+38ChUqqFmzZvmqAQAAAAAAAAAAAADA9aLEhkEyHDlyRB999JFmzJihxMRESewG4kqHDx/OFAZp0qRJjs/06tXL4XrLli06depUntdcvHixw3WPHj3k4eGR5+cBAAAAAAAAAAAAALielMgwSHx8vObPn69OnTqpXr16mjJlis6cOeNwVAxc47XXXss01q1btxyf6dSpkypUqGC/Tk9P1+zZs/O03uHDh7Vu3TqHsfvuuy9PzwJASTJw4ED757GMV+fOna1uCwAAAAAAAAAAACVQiQqD/Pbbbxo8eLCCgoI0ePBg/frrr/ZvmEmSYRj2IEjGWMeOHS3rtyhkvOeM18CBA3OcX5hdU7788kstWLDAYaxz586qVatWjs95eHjomWeecRh75513dOzYsVzXHDVqlEPP9evXz7TTCAAAAAAAAAAAAAAA+EexP2sjLCxMCxYs0Pz58+3Hk1wdDrh6F5CM8apVq2rAgAF6/PHHddNNNxVtw8Xcxo0b9eabb+r//u//1KFDhzw/N3XqVP33v//N9Gv/v//9L0/PP/300/rggw90/vx5SVd2d+nTp49Wr16typUrZ/nMiy++qDVr1jiMTZo0Se7u7nnuGwAAAAAAAAAAAACQtekzV2v6rNVOrzt8aDcNfzLnEybgWsUyDJKSkqJly5Zp3rx5+vHHH5Wenp5tAES6EgLx8PBQjx49NHjwYHXv3l1ubtZterJz507t3Lkzy3tbtmzJNDZjxoxsaz366KPy8fFxWm+maWrVqlVatWqVgoOD1adPH7Vr107NmjVT9erV7b9upmnq0KFDWrdunaZNm6Y///wzU62XX35ZrVq1ytO65cuX19tvv60BAwbYx3bv3q3WrVtrwoQJ6t27t/z9/ZWcnKwdO3ZoypQpWr58uUONO++8U3379i3Eu0dxlZyepmXhx/Vd2PFM97ps/kFtAgLVLbCGegXVlqcbYSAAAAAAAAAAAADAGWLjEhQWHumSurBWsQqD/PHHH5o7d64+//xzXbp0SZIcjoC5WsZ4/fr19fjjj6t///6qUqVK0TacjeXLl+uVV17J8/zhw4dne69bt25ODYNc7ejRow47exiGofLly8tmsykqKkrp6enZPvv000/r5Zdfztd6/fv31++//6733nvPPnb8+HENHjxYgwcPlq+vr+Li4rJct379+vr8888z/T5AyZaSnqb3juzTu0f36lxS1p8Q9sdGan9spOaePKigMuX0THBjPVOnsWyEQq4r6enp2rNnj44dO6aIiAhFRUXJ399fgYGBuvHGG9W8eXOXhQAjIiK0fft2nT17VufPn5eXl5eqV6+uFi1alJjdp06ePKl9+/bpwoULunDhgpKSkuTj46PAwEA1bNhQDRo0kKenZ6HWSElJ0eHDhxUaGqrw8HDFxMTINE0FBASoQoUKatiwoW6++Wb+HAcAAAAAAAAAoBjxKe+lqkEB2d5PTzd1LiLKYaxKoL/c3HL+er9PeS9ntIdCsDwMEhkZqUWLFmnevHn6/fffJeV+DIy3t7f69u2rwYMHq127dkXab2lmmqZiY2NznFO5cmXNnDlT999/f4HWePfdd1WhQgVNnDhRaWlpDvdiYmKyfKZDhw766quvsj1OBiXT/phL6r9nvXZHX8jzM+FJ8Xr+r21afPaIFjbvopt9K7iwQxQHu3bt0nvvvac1a9bowoXsf69UrlxZISEhevbZZ9W8eXOnrP3dd9/pgw8+0MaNGzP9eZWhQYMGGjNmjJ544gl5eFz5lDpx4sRMgcBjx46pdu3aOa43f/58DRo0yGFs/fr16ty5c4H6Dw0N1Ycffqgff/xRhw8fznGul5eXOnbsqJ49e+rRRx+Vv79/ntbYvXu3vv/+e23YsEFbt25VUlJSjvMDAgJ05513auzYsWrTpk1e3woAAAAAAAAAAHCR4U/mfJzLhYsxathktMPYhrWvqlJFX1e3hkKy5CwV0zS1evVqPfTQQ7rhhhs0ZswY7dmzR6ZpyjRNGYZhf2XMN01Tbdq00axZsxQWFqa5c+cSBCmAZs2a6eOPP9aDDz6oGjVq5OkZm82mtm3b6pNPPtGJEycKHATJ8OKLL2rnzp2677777N88zUqjRo00Z84cbdiwodjs+gLn2HwpXLf/uixfQZCr7Y6+oNt/XabNl8Kd3BmKi4iICD366KNq1aqVFi1alGMQRJLOnz+vzz77TC1btlT//v1znZ+Tc+fOqVevXrr//vu1fv36bIMg0pXAxfDhw9WmTRudOHGiwGs608mTJ/XII4/o5ptv1scff5xrEESSEhIStGbNGo0aNUo33HCDdu/eneP8n3/+WfXq1VPLli31yiuv6Jdffsk1CCJdCYB+9dVXuu2229SjRw9dvHgxz+8LAAAAAAAAAAAAeVekO4McOXJEc+fO1cKFC3X27FlJue8CUqlSJT322GMaPHiwGjVqVJTtFtjEiRM1ceLEIlnr6l+/vPD399fw4cPtR9NcunRJoaGhOnXqlM6dO6fLly8rPT1dvr6+CggI0I033qiWLVuqbNmyTu27WbNm+vbbbxUVFaXNmzfr0KFDio2Nlaenp6pXr66WLVuqfv36Tl0TxcP+mEu6e+sqxaQmF6pOTGqy7t66Sls63KdGPtlvXYWS58iRIwoJCdGRI0fy/axpmvr000+1bds2rVmzJtfdOK4VHh6uLl26KDQ0NF/P7d69W23bttXmzZvz9Zyzbdq0SQ888IDOnz9f4BoJCQnZ7tSUYd++fTp06FCB15CklStX6tZbb9Xq1av58x4AAAAAAAAAAMDJXB4GiY+P15IlSzR37lz99ttvkrIPgGTcc3NzU0hIiAYPHqx7771XNpvN1W1etypUqKDbb7/dsvX9/f3VvXt3y9ZH0UpJT1P/PesLHQTJEJOarMd2r9PWDvfJ5ubulJqw1smTJ9WuXTudO3cu0z1PT0/deeedatSokSpXrqwLFy5o//79Wrt2rZKTHX9P/f3332rbtq127typatWq5Wnt+Ph43XHHHVkGQWw2mzp37qwmTZooMDBQUVFROnz4sNasWWMPToSFhalXr17q0aNHAd554S1btkx9+/ZVSkpKpnuGYahJkya67bbbFBgYKB8fH0VHRys8PFy7d+/Wn3/+meVz+VGrVi3dcsstqlu3rnx9feXj46OEhARdvHhRe/fu1datW5WQkODwzPHjx9W7d2/t2LFD5cqVK9T6AAAAAAAAAAAA+IfLwiC//vqr5s2bp6+++kqXL1+W9E8IJKsAiCTdeOONGjRokAYOHKjq1au7qjUAFnnvyL4CHw2Tnd3RF/TekX16rm4zp9ZF0UtPT9djjz2WKQhiGIaefPJJvf766woIyLwLzKVLlzRu3DjNnj3bYTw8PFz9+/fX2rVrM33eycoLL7ygAwcOZBofNGiQ3nzzTVWuXDnTvaSkJL333nuaNGmSEhIStHfvXvvOV0Xp4MGDeuyxxzIFOjw8PDRkyBC98MILOX5ejY6O1rJly7Rw4UL9/PPPeVrTMAzdcccd6tOnj+65555cQzcxMTGaO3euJk6cqOjoaPv4gQMH9MILL+j999/P07oAAAAAAAAAAADInZsritavX1+dOnXS/PnzFRcXJ9M0ZZqmDMOwf0MuY6xMmTJ6+OGHtXbtWh05ckQvvvgiQRCgFEpOT9N7R/e5pPZ7R/cpJT3NJbVRdD788ENt3LjRYcwwDM2dO1fTp0/PMggiXdnhaNasWZo1a1ame+vWrdO0adNyXXv37t368MMPM41PnTpVc+fOzTIIIkllypTRuHHjtGbNGvvOFhcuODfwlJu0tDQ98MADio2NdRgPCAjQunXr9PHHH+f6edXPz88enNm5c6duvPHGHOe3b99e+/fv108//aQnn3wyT7uv+Pr66umnn9aePXsyHd8ze/ZsRUZG5loDAAAAAAAAAAAAeeOSMMihQ4ckySEAcm0IpFmzZvrwww919uxZLVq0SF27dnVFKwCKiWXhxxWeFO+S2uFJ8fou/LhLaqNopKam6p133sk0/uKLL2rgwIF5qjFkyBA9//zzmcbffvttpaXlHBaaOnWq0tPTHcaefPJJPfXUU3lau0OHDpoxY0ae5jrbF198of379zuMlSlTRmvXrlWHDh3yXa9ly5aqVatWjnNuvfVWNWzYMN+1pSu7gC1ZskRubv/8FSQ+Pl7z588vUD0AAAAAAAAAAABk5pIwSIZrAyB+fn4aMWKEdu3apd27d2vkyJHy9/d3ZQsAionVEadcWn9NxGmX1odrfffddzp1yvH3SHBwsF544YV81Xn55ZczBRlOnDihZcuWZftMZGSklixZ4jDm7++vN954I19rP/bYY2rXrl2+niks0zQ1ZcqUTOOTJ09WixYtirSX/GjVqpW6dOniMLZ+/XqLugEAAAAAAAAAACh9XBoGMU1TktSlSxd99tlnCgsL00cffaTmzZu7clkAxdCuKNcenbEr6rxL68O1vvnmm0xjI0eOVNmyZfNVx8vLSyNHjsxT/Qxr165VYmKiw1i/fv2yPZYmJyNGjMj3M4Xx+++/Z9oVpHLlyho9enSR9lEQnTt3drjetm2bNY0AAAAAAAAAAACUQi4Lg9xwww0aP368Dh8+rJ9//lmPPPKIypQp46rlABRzB+OiXFv/crRL68O1Nm/e7HBtGIYeeeSRAtV67LHH7DtTZVf/almFEPr27Vugte+//37ZbLYCPVsQGzZsyDQ2aNCgEvH5NigoyOE6IiJCFy64NjQGAAAAAAAAAABwvfBwRdEVK1YoJCREbm4u3XgEQAmSlJ7m0vqJaakurQ/XCQ8P14kTJxzGgoODM4UF8iooKEjBwcE6cuSIfezYsWM6d+6cqlSpkmn+jh07HK4Nw1DLli0LtLaXl5caNWqkP/74o0DP59fGjRszjV17/EpRuHz5slasWKEdO3Zo7969OnLkiGJjYxUbG6uEhIQ814mMjFSlSpVc2CkAAAAAAAAAAMD1wSVhkLvvvtsVZQGUYGXc3JXowkBIWXeX/HGGInDy5MlMY82aNStUzRYtWjiEQSTp1KlTWYZBzp4963BdrVo1+fn5FXjtW265pcjCIEePHs001qZNmyJZW7ryazphwgQtXbpUly9fLnS9qKiowjcFAAAAAAAAAAAA1x0TAwBXq1/e37X1vQv+zXtYKzIyMtNYVqGN/Mjq+azWyWq8QoUKhVo7ICCgUM/nx8WLFx2uy5QpU2Trf/LJJ2rQoIEWLFjglCCIJMXHxzulDgAAAAAAAAAAwPWu2P4o/alTp7Rnzx5duHBBFy9etG8z/9JLL1ncGYCCaOlfSX/EXMx9YoHrV3ZZbbhWViENX1/fQtXMamePS5cuZTk3Ojra4drHx6dQaxe29/y4Ngzi7+9fJOt+8MEHGjNmTJ7muru7q2zZsvLwcPwrR3JycqYjZEzTdFqPAAAAAAAAAAAA17NiFQY5e/as3n33XX377bc6fvx4lnNyCoN8/vnnCg8Pt1936NBBrVq1cnabAAqgW2ANzT150GX1QwKru6w2SrcyZcooNTXVfp2cnFyoeoV9vrjbt2+fxo4dm2ncw8NDPXv2VJcuXdSyZUtVr15dVatWlc1my7LO/PnzNWjQIFe3CwAAAAAAAAAAcF0qFmGQtLQ0vfDCC/rggw+UnJyc7U8GG4aRY50zZ85o3Lhx9uuuXbvqp59+cmqvAAqmV1BtBZUpp/Ak5x8DEVSmnO4Lqu30uigaWR1rEhMTU6ia1+72IWV//Iu/v7/DMSeuWNtVKlasqDNnztivo6KiXL7muHHjHMIz0pXw5aJFi1SjRo0813HW0TIAAAAAAAAAAADIzM3qBi5cuKAuXbro7bffVlJSkkzTlGEYmV55MWzYMPv2/KZpav369Tp16pQr2weQR55u7nomuLFLaj8T3Fg2N3eX1IbrZRUGOXfuXKFqZvV8VutkNX7mzJlCHVdSlJ93Klas6HCdlJSU5bE7zhIVFaUff/zRYSw4OFgrV67MVxBEyv7YHgAAAAAAAAAAABSepWGQ5ORk3Xvvvfr1118dQiCmaTq88srHx0cPPvig/RnTNPXdd9+5qHsA+fVMncZq4VfJqTVb+lXSs3WaOLUmilbNmjUzjf3++++Fqrlnz55MY9mFFRo1auRwHRcXp0OHDjl1bVepU6dOprHt27e7bL2NGzdm2hXkqaeeUvny5fNd6++//3ZWWwAAAAAAAAAAALiGpWGQMWPGaOvWrQ4hkIoVK+rVV1/V7t27denSJTVt2jRfNR988EFJ/xwps3btWqf3DaBgbG7uWti8i3w9PJ1Sz8/DUwtbdJWHm+WbHKEQgoKCVKtWLYexo0ePFnh3kIiICB05csRh7MYbb1SVKlWynN+mTZtMY+vWrSvQ2gcOHCj0rib50bFjx0xj69evd9l6Z8+ezTR2++23F6jWr7/+Wth2AAAAAAAAAAAAkA3LvoN68OBBffLJJ/YQiCTdddddOnz4sMaPH69mzZrJ398/33U7d+7scFTML7/84sy2ARTSzb4VtOq2uwsdCPHz8NTK2+5WI5+sj/5AydKuXTuHa9M09cUXXxSo1meffZZpV6lr61+tQ4cOmcYWLlxYoLUXLFhQoOcKqkuXLpnG5s2bp+TkZJesl9XRLgX5XL1p0yYdP3688A0BAAAAAAAAAAAgS5aFQd544w2lpaVJurKLR8uWLbVixQp7kKOg3N3d1bx5c/s3AmNjY3Xy5MlC9wvAeW6vEKQtHe4r8JExLfwqaXOH+3R7hSAndwar9O7dO9PYtGnTlJSUlK86SUlJ+vjjj/NUP0OrVq0yHRWzZcsWrVy5Ml9rnzlzJsu1Xalp06Zq0sTxmKSIiAhNmzbNJev5+PhkGgsLC8t3nTfeeMMZ7QAAAAAAAAAAACAbloVBVqxY4bAryCeffCIPDw+n1G7ZsqXDdWhoqFPqAnCeRj4B2trhPr3ZsI2CypTL0zNBZcrpzYZttLXDfewIUsrcd999qlGjhsPY4cOHNWXKlHzVee211zIdEVO7dm3de++9OT735JNPZhobMWKEwsPD87RuamqqnnjiCcXFxeW9WSd5/vnnM4298MIL+uOPP5y+VrVq1TKN/fDDD/mq8cknn2jVqlXOagkAAAAAAAAAAABZsCQMsmfPHl28eFHSlV1B2rdvn+knmwujZs2aDtdnzpxxWm0AzmNzc9dzdZvp5F2PaMmtd+rRajdlmnOLT4AG12ygJbfeqZN3PaLn6jaTzc3dgm7hSu7u7ho7dmym8UmTJmnRokV5qrFgwQJNnjw50/jYsWPl7p7z75knnnhCderUcRg7ceKE7rzzTh06dCjHZ6Ojo/XQQw9p9erVeerT2R566KFMn0MTExN1xx13aMuWLfmut2fPHp04cSLLex07dpSbm+NfHaZNm6Y///wzT7UXL16skSNH5rsnAAAAAAAAAAAA5I8lYZBrv7F2xx13OLW+v7+/w3VMTIxT6wNwLpubu/reUEfv3XJ7pnvrbu+pT5p1Ut8b6hACKeVGjx6tjh07Ooylp6erf//+Gj16tKKiorJ8LjIyUiNHjtSgQYPsu01l6Nq1q0aMGJHr2uXKldMnn3wiwzAcxvfv368mTZromWee0datWxUfHy9JSk5OVmhoqKZMmaIGDRrom2++kSR5eHjkuguJs7m7u+urr77KdITLxYsX1alTJ40aNSrXUGRcXJw+//xzdevWTS1atNCxY8eynFepUiXdddddDmMJCQnq2rWrvv3222zrh4eHa+jQoXrkkUeUnJwsSapatWpe3h4AAAAAAAAAAAAKwDnnsuTT+fPnJUmmacowDNWuXdup9b29vSXJ/k29y5cvO7U+AFzPmjRpkml3iIJYtGiRevToYb92c3PTp59+qlatWikiIsI+np6ero8++kizZ8/WXXfdpUaNGqlixYq6ePGiDhw4oB9//NEeMLhaUFCQFi5cmOdeO3furLfeekv/+c9/HMYTExP1/vvv6/3335ckeXl5KSEhIcsar732muLj4/X99987jF8bMnG2evXq6dNPP1Xfvn2VkpJiH09JSdG0adP08ccfq1mzZmrTpo0CAwPl4+OjmJgYhYeHa8+ePdq3b5+SkpLytNarr76qn376Senp6fax8+fPq3fv3qpbt666du2qmjVrys3NTREREdq5c6c2b96stLQ0h36feuopjRo1ynm/CAAAAAAAAAAAALCzJAxy7U4d5cuXd2r96OhoSf+ETZxdHwCuZ7GxsU6pc3VoIUPNmjX122+/KSQkREePHnW4l5SUpOXLl2v58uW51q5bt67WrFmjatWq5aunsWPHys3NTf/5z38cwg5Xyy4I8vzzz+u5557ThAkTMt3LCCm6Uq9evfTTTz+pd+/eunTpksM90zS1Z88e7dmzp9DrtGrVSm+99VaWx/ocOnQo12N1brjhBq1atUobN24sdC8AAAAAAAAAAADImiXHxAQEBDhcZ4Q3nOXqnyiXpIoVKzq1PgDAdW666SZt3rxZ/fr1y/eOGoZh6NFHH9Vvv/2mG2+8sUDrP/PMM9q8ebOaNWuWp/k33HCDlixZoilTpkhSlsfZ+Pn5FaiX/OrUqZN27Nih3r17F3g3El9f30yfp6/17LPP6oMPPpDNZstX7Xbt2mn79u0KDg4uUG8AAAAAAAAAAADIG0vCIJUrV5b0z7b5p06dcmr9bdu2OVxXqlTJqfUBAK5VpUoVffHFF9qxY4ceffTRXEN9lSpV0r///W/t2rVLn332mf3zTEG1adNGu3bt0vr16zVixAi1aNFCQUFB8vDwUPny5dWgQQM9/PDDWrRokY4dO6a+ffvan712Vw4vL698hyYKIzg4WF9//bV27typQYMG5Wl3lPLly6tnz5765JNPdPbsWTVt2jTXZ0aPHq09e/bokUcekaenZ7bzDMNQ27ZttWjRIm3atCnfu7UAAAAAAAAAAAAg/wzTNM2iXnTnzp1q3bq1PQzSrVs3rVixIsu5zZs31969e+1HvqSlpeVYOykpSVWrVlV0dLT9mfDw8EJ/YxDXh9OnT6tGjRqSroSUqlev7pJ1Dh06pNTUVHl4eKhu3bouWaMkOp+UoMA1Cx3GIkL6q3IZL4s6QnGRnp6uXbt26fjx44qIiFBUVJT8/f0VGBioG2+8US1atJCbmyX5xkxuvvlmHThwwH5dr149HTx40MKOpAMHDujvv//W+fPndfHiRUmSj4+PgoKC1LBhQ9WrV08eHgU/OS4hIUFbtmzR4cOHdenSJZmmKV9fX9WpU0e33norocwC4PNEyTB95mpNn7Xa6XWHD+2m4U92c3pdAAAAAAAAAMivCxdj1LDJaIexv/Z+qEoVfS3qqHRyxfepC/6dn0Jo0aKF/P397YGNdevW6eLFi045zmXu3LmKioqyB00aNmxIEAQASjg3Nze1atVKrVq1srqVHEVGRio0NNRhrDj03KhRIzVq1Mhl9b28vNS1a1d17drVZWsAxVFsXILCwiNdUhcAAAAAAAAAgMKwJAzi5uamO++8U0uXLpUkJScn6+2339Ybb7xRqLphYWF65ZVXZBiGfVeQkJAQZ7QMAECu5s+fr/T0dIexW2+91aJuALiaT3kvVQ0KyPZ+erqpcxFRDmNVAv3l5mbkWhcAAAAAAAAAgMKwJAwiSUOHDtXSpUvtwY13331Xd999tzp27FigelFRUerdu7ciIiLsu4K4u7vrqaeecmbbAABkKSYmRu+//77DmGEY6tGjhzUNAXC54U/mfJxLVtsnblj7KtsnAgAAAAAAAABczs2qhe+880516dLFvoNHSkqK7rnnHn3zzTf5rrVhwwbdeuut2r59u8OuIA8//LBq1arlgu4BAKVRcnJygZ5LS0vT4MGDdfLkSYfxrl27qm7dus5oDQAAAAAAAAAAAMgzy8IgkvT+++/L29tb0pWfno6Li1Pfvn11xx136Msvv8z0TTVJSklJUXh4uLZt26a33npLbdu21R133KGjR4/KNE17raCgIP3vf/8r0vcDACjZRo8eraFDh+rw4cN5fiY8PFw9evSwH312tWeffdaZ7QEAAAAAAAAAAAB5YtkxMZLUuHFjff7557r//vvtu3mYpqkNGzZow4YN9nkZIQ/TNFW2bNlMdTKezfjYZrNp8eLFqlKlSpG8DwB58+6RvXr3yN5s76f///+vX63phqVy+////87Os3Wa6Nk6TQrdH5CUlKQFCxZo9uzZatu2rXr37q1WrVqpcePGCggIkGEYSktL0/nz57V161atXLlSn376qRITEzPVGjBggLp3727BuwAAAAAAAAAAAMD1ztIwiCT17NlTCxcu1NChQ5WQkOAQ6shKVuNXP+Pj46NFixapffv2rmsaQIHEpCTrTOLlfD0TlhSfp7qAs23ZskVbtmyxX7u5ucnLy0uXL+f+e7h58+b68MMPXdkeAAAAAAAAAAAAkC3LwyCS9Mgjj6hp06bq16+f9u/fL8Mw7AGPvDJNUw0aNNBXX32lm2++2UWdAigMX5unqpX1dkldwNXS09PzFATp2bOnvvjiC/sxaAAAAAAAAAAAAEBRKxZhEEm6+eabtXfvXi1dulT/+9//tGvXrkxzMo6RuVbdunU1fvx4/fvf/5abm1tRtAugADjOBcXdXXfdpW3btik0NDTfzzZu3Fgvvvii+vbtm+9AIwAAAAAAAAAAAOBMxSYMIl0Je/Tt21d9+/bViRMn9Msvv+i3337T6dOndfHiRUVGRsrLy0uVKlVSlSpV1KZNG911111q2LCh1a0DAEqBRx99VI8++qgOHjyoX3/9Vdu2bdOhQ4d04sQJXbp0SfHx8TIMQwEBAapQoYJq166tDh06qEuXLmrTpg0hEAAAAAAAAAAAABQLxSoMcrVatWqpf//+6t+/v9WtAACuM/Xr11f9+vU1ePBgq1sBAAAAAAAAAAAA8o0zVQAAAAAAAAAAAAAAAEoRwiAAAAAAAAAAAAAAAAClCGEQAAAAAAAAAAAAAACAUoQwCAAAAAAAAAAAAAAAQClCGAQAAAAAAAAAAAAAAKAUIQwCAAAAAAAAAAAAAABQihAGAQAAAAAAAAAAAAAAKEUIgwAAAAAAAAAAAAAAAJQihEEAAAAAAAAAAAAAAABKEcIgAAAAAAAAAAAAAAAApQhhEMAC7u7ukqS0tDSLOwEAFEcZnx8yPl8AAAAAAAAAAADkB2EQwAIZ39wzTZNACADAQVpamkzTlEQYBAAAAAAAAAAAFAxhEMACZcqUsX8cGxtrYScAgOLm8uXL9o89PT0t7AQAAAAAAAAAAJRUhEEAC/j5+dk/jo6OtrATAEBxExMTY//Y19fXwk4AAAAAAAAAAEBJRRgEsEDZsmXtP+0dHx+v5ORkizsCABQHaWlpiouLk3TliJhy5cpZ3BEAAAAAAAAAACiJPKxuALgeGYYhPz8/nT9/XpJ08uRJ1axZk+MAAOA6lp6ertOnT8s0TUmSj4+PDMOwuCsAAAAAVpo+c7Wmz1rt9LrDh3bT8Ce7Ob0uAAAAgOKDMAhgEX9/f0VFRSklJUUpKSk6fvy4qlevzk+BA8B1KC0tTadPn1Z8fLwkyc3NTQEBARZ3BQAAAMBqsXEJCguPdEldAAAAAKUbYRDAIh4eHqpVq5ZOnTqlpKQkpaWl6cSJE/L09JSPj4/Kly8vLy8vfiocAEqptLQ0Xb58WTExMYqLi7PvCOLm5qaaNWuqbNmyFncIAAAAwGo+5b1UNSj7oHh6uqlzEVEOY1UC/eXmlvPXk3zKezmjPQAAAADFGGEQwEI2m80eCElIuPITGcnJybp48aIuXrwo6cqRMu7u7la2CQBwsrS0NHv442oZQRAvL74wCwAAAEAa/mTOx7lcuBijhk1GO4xtWPuqKlX0dXVrAAAAAIo5wiCAxdzd3VWzZk1FRkYqLi7OfkRABtM0lZqaalF3AICi4O7uLh8fHwUEBLAjCAAAAAAAAAAAKDTCIEAx4ObmpooVK6pixYpKS0tTXFyc4uLilJKSovT0dKWlpVndIgDAidzd3eXu7i5PT0/5+vqqXLlyHAsGAAAAAAAAAACcptiGQc6cOaPo6GhFR0crJSWlULU6duzopK4A13N3d5efn5/8/PysbgUAAAAAAAAAAAAAUAIVmzBIWFiY5s2bpzVr1uj3339XXFycU+oahsERGwAAAAAAAAAAAAAA4LpheRgkPj5e48aN04wZM+xHYZimaXFXAAAAAAAAAAAAAAAAJZOlYZDz58+rU6dOOnjwoEMAxDAMp9QnVAIAAAAAAAAAAAAAAK43loVBUlNT1a1bN4WGhkpyDIAQ4gAAAAAAAAAAAAAAACgYy8IgM2fO1J49ezKFQMqWLavu3burdevWqlOnjvz8/GSz2axqEwAAAAAAAAAAAAAAoESxLAzy9ttv24MgGTuBDBkyRFOmTFFAQIBVbQEAAAAAAAAAAAAAAJRoblYsevDgQZ04cULSlSCIYRgaN26cZs6cSRAEAAAAAAAAAAAAAACgECwJg+zevdvh+sYbb9SkSZOsaAUAAAAAAAAAAAAAAKBUsSQMcv78efvHhmHogQcekIeHZSfWAAAAAAAAAAAAAAAAlBqWhEHi4uIkXTkiRpIaNmxoRRsAAAAAAAAAAAAAAACljiVhED8/P4fr8uXLW9EGAAAAAAAAAAAAAABAqWNJGKRu3bqSrhwRI0kXLlywog0AAAAAAAAAAAAAAIBSx5IwyG233SabzWa/3rdvnxVtAAAAAAAAAAAAAAAAlDqWhEF8fX3Vs2dPmaYp0zS1YsUKmaZpRSsAAAAAAAAAAAAAAACliiVhEEmaMGGC3N3dZRiGTp06pXnz5lnVCgAAAAAAAAAAAAAAQKlhWRikadOmGjdunH1HkGeffVZ//vmnVe0AAAAAAAAAAAAAAACUCpaFQSTp1Vdf1SOPPCLTNBUTE6MuXbpoxYoVVrYEAAAAAAAAAAAAAABQolkaBpGkTz/9VOPGjZNhGLp48aLuvfdede3aVV988YXOnj1rdXsAAAAAAAAAAAAAAAAliodVCwcHBztc22w2paSkyDRN/fLLL/rll18kSWXLllWFChVks9kKtI5hGDpy5Eih+wUAAAAAAAAAAAAAACgJLAuDHD9+XIZhyDRNGYZhH88Yy5CQkKAzZ84UeJ2rawMAAAAAAAAAAAAAAJR2loVBMmQV1nBWgOPqUAkAAAAAAAAAAAAAAMD1wNIwCGENAAAAAAAAAAAAAAAA57IsDDJgwACrlgYAAAAAAAAAAAAAACi1LAuDzJs3z6qlAQAAAAAAAAAAAAAASi03qxsAAAAAAAAAAAAAAACA8xAGAQAAAAAAAAAAAAAAKEUIgwAAAAAAAAAAAAAAAJQihEEAAAAAAAAAAAAAAABKEcIgAAAAAAAAAAAAAAAApQhhEAAAAAAAAAAAAAAAgFKEMAgAAAAAAAAAAAAAAEAp4uHMYidPnsxyvGbNmnme6wpZrQ8AAAAAAAAAAAAAAFAaOTUMUrt2bRmG4TBmGIZSU1PzNNcVslsfAAAAAAAAAAAAAACgNHJqGESSTNN0yVwAAAAAAAAAAAAAAADkzulhkKt3+8gt7OHqnUEImwAAAAAAAAAAAAAAgOsNO4MAAAAAAAAAAAAAAACUIk4Ng8ybN88lcwEAAAAAAAAAAAAAAJA3Tg2DDBgwwCVzAQAAAAAAAAAAAAAAkDduVjcAAAAAAAAAAAAAAAAA5yEMAgAAAAAAAAAAAAAAUIoQBgEAAAAAAAAAAAAAAChFCIMAAAAAAAAAAAAAAACUIoRBAAAAAAAAAAAAAAAAShHCIAAAAAAAAAAAAAAAAKUIYRAAAAAAAAAAAAAAAIBSxKlhkIcfflgnTpxwZkmXME1Tc+fO1SeffGJ1KwAAAAAAAAAAAAAAAE7l1DDI4sWL1aBBA40dO1YRERHOLO0033//vZo2baohQ4bo7NmzVrcDAAAAAAAAAAAAAADgVE4/JiY5OVnvv/++ateurVGjRhWLnULS0tK0aNEiNWnSRPfff7/+/PNPq1sCAAAAAAAAAAAAAABwCaeHQTIkJiZq+vTpuummm9SrVy+tXLlSpmm6arksnTx5UhMmTFDNmjXVv39//fnnnzJNU4ZhSJL9fwEAAAAAAAAAAAAAAEoLp4ZB1qxZo7p16zoELtLS0rR8+XL17NlT1atX1+jRo/XLL7+4LBhy+vRpTZ06VR07dlRwcLBef/11hYWFOfTk5uamUaNGacyYMS7pAQAAAAAAAAAAAAAAwCoezix21113ad++fXrnnXf0xhtvKDY21h7AME1TYWFh+vjjj/Xxxx/L19dX7du3V+fOnXXrrbeqSZMmCggIyNd6aWlpCg0N1R9//KFNmzZp/fr1OnTokP1+RuDEMAyZpinTNNWxY0d98MEHatKkifPeOAAAAAAAAAAAAAAAQDHh1DCIJNlsNo0bN05Dhw7V66+/ro8//liJiYkOoRBJio6O1sqVK7Vy5Ur7s0FBQapVq5aqVaumoKAgeXt7y8vLS+7u7kpMTFRCQoIuXbqk06dP6/Tp0zp27JhSUlLsz1+924hhGA4hkBYtWmjy5Mnq1q2bs98yAAAAAAAAAAAAAABAseH0MEiGChUq6O2339bYsWP1/vvva/bs2YqKirKHQiRlOiomLCxM4eHheaqf1TEzWdXu2LGjxo4dq549exbkbQAAAAAAAAAAAAAAAJQobq5eoGrVqnrzzTd16tQpffjhh2revLl9tw7pnx08Ml6S7Pdzel373NW7gPj4+Gjw4MHasWOHNmzYQBAEAAAAAAAAAAAAAABcN1y2M8i1vL29NXLkSI0cOVIHDhzQl19+qRUrVuj333/P8niX3Fy7M0iFChUUEhKi++67T/fee6/KlCnj9PcAAAAAAAAAAAAAAABQ3BVZGORqjRo10qRJkzRp0iSdO3dOGzdu1I4dO7Rz5079/fffCgsLy/IYmAw+Pj668cYb1aRJE7Vq1Uq33Xabbr311jyFSAAAAAAAAAAAAAAAAEozS8IgV6tSpYr69u2rvn372sdSUlJ05swZxcTEKD4+XmlpafLy8pK3t7cCAwMVEBBgYccAAAAAAAAAAAAAAADFl+VhkKzYbDbVrl3b6jYAAAAAAAAAoNhJTk7VqjW7tXLNrkz37u87RS2a11HXzo11d0gLeXoWyy8BAwAAAHAx/iUAAAAAAAAAACVASkqqZsxeo+mz1uj8+egs54QePKPQg2f0+ZcbFRjop2FDQjRsSIhsNr4UDAAAAFxP+BcAJEknTpzQ/v37dfLkSUVFRcnNzU0BAQG64YYb1Lp1a1WuXNnqFgEAAAAAAIDrVujB0xo5Zpb27juR52ciIqI16bUl+u77bZo2daga1K/uwg4BAAAAFCeEQVzINE0dPnxY27dv144dO7R9+3bt2bNHiYmJmeYVtYiICH333Xdau3at1q9frwsXLuQ4/+abb9awYcM0cOBAlS9fPt/rTZw4Ua+88kpB25UkderUSRs2bChUDQAAAAAAAKCk2b7jkPo99o5iYxMK9PzefSfUvddkffnpWLVuVdfJ3QEAAAAojgiDOFlcXJzeeOMN7dixQzt27FBUVJTVLTk4f/68Hn74YW3YsEFpaWl5fm7//v0aPXq0pkyZonnz5umuu+5yYZcAAAAlF+e3AwAAwJlCD54uVBAkQ2xsgvo99o5WfT9B9etVc1J3AAAAAIorvvrsZBcuXNDrr79udRvZunjxon7++ecc55QrV042m03R0ZnPHT1z5oxCQkI0c+ZMDRkyxFVtAgAAlDic3w4AAABnS0lJ1cgxswodBMkQG5ugEU/N1OofXuLvoAAAAEApx9/4oY4dO6p79+7q0qWLGjVqZD8G5vLly9q0aZM+/PBDrVy50j7fNE0NGzZM1apVU/fu3Qu0Zo8ePXTPPffk65kbbrihQGsBAAC4Gue3AwAAwBVmzF6Tr79j5sXefSc0Y/YajR7Rw6l1AQAAABQvhEFczNvbWy1atFCrVq3UqlUrHT58WBMmTLC6LZUrV05PPvmkhg0bpnr16mU5x9vbW926dVO3bt00b948DRkyxH60THp6ukaNGqUDBw6obNmy+V7/1ltv1bBhwwr1HgAAAIoDzm8HAACAKyQnX9l5zhVmzF7DDnUAAABAKedmdQOlTbly5TRs2DDNmTNHe/fuVXR0tDZu3Kh33nlH/fr1U/Xq1v7Ep81m08iRI3XkyBG9++672QZBrjVo0CD973//cxg7duyYlixZ4oo2AQAASgRnn99+8O8zTuoMAAAAJd2qNbsVEZH18YOFFRERrZWrd7ukNgAAAIDigTCIkwUGBmr69Ol6/PHH1bhxY7m7u1vdkoM6deroo48+UlBQUL6fHTNmjIKDgx3Gli1b5qzWAAAAShRXnd+ekpLqlHoAAAAo2dZt2OfS+ut/cW19AAAAlGzJyala9sN2jX95UaZ79/edojFj52jZD9uVnMzXM4srwiDIM3d3d/Xq1cth7I8//rCoGwAAAGu58vx2AAAA4I+9x0t0fQAAAJRMKSmp+vDjFWrW+lk9MWyavvl2a6Y5oQfP6PMvN+qJYdPUvM2z+vDjFfyQWzFEGAT5UqdOHYfr8PBwizoBAACwjqvPb+cfTgAAADhyNMy19Y/wdT0AAAA4Cj14Wt16TtKk15bo/Pm8HVkYERGtSa8tUbeekxR68LSLO0R+EAZBviQnJztcG4ZhUScAAADW4fx2AAAAuFpSkmsDwolJKS6tDwAAgJJl+45D6t5rcoF3Q96774S695qs7TsOObkzFBRhEOTL4cOHHa6DgoIs6gQAAMA6nN8OAAAAVytTxsOl9cuWsbm0PgAAAEqO0IOn1e+xdxQbm1CoOrGxCer32Ds6+PcZJ3WGwnDtvyhQqqSlpem7775zGGvZsmWB68XGxmrPnj0KDw9XXFycAgICVLFiRd18882qWLFiIbsFAABwHc5vBwAAgKvVCa6q/QdOuq5+HX7ICwAAAFJKSqpGjplV6CBIhtjYBI14aqZW//CSbDbiCFbiVx959v333+vs2bMOYz179ixQralTp2ry5MlKS0vLdM8wDDVs2FC9evXSU089xe4jAACg2OH8dgAAALha0ya1XRoGadqktstqAwAAoOSYMXtNgY+Gyc7efSc0Y/YajR7Rw6l1kT8cE4M8SUxM1PPPP+8wVrlyZfXu3btA9aKiorIMgkiSaZo6cOCA3njjDdWuXVsvvvhitnMBAACswPntAAAAcLWunRu7tH6XTq6tDwAAgOIvOTlVM2avcUntGbPXKCXFtV9HRc4IgyBPxo0bp0OHDjmMTZw4UV5eXi5dNykpSa+99po6d+6s6Ohol64FAACQV5zfDgAAAFe7O6SFAgP9XFI7MNBP3bu1cEltAAAAlByr1uxWRIRrvgcbERGtlat3u6Q28oYwCHL1zTffaOrUqQ5jt99+u4YNG5bvWsHBwRo1apSWLFmiAwcOKDIyUikpKbp06ZL27dunmTNnqmPHjpme+/XXX9W7d2+lpBTup2RPnz6d4ysszLVbvgMAgNKhTnBV19bn/HYAAIDrnqenh4YNCXFJ7WFDQji/HQAAAFq3YZ9L66//xbX1kTP+xo8c/fHHHxowYIDDmJ+fnz799FO5ueU9S9S2bVtt2LBBnTp1yvJ+QECAAgICdMstt2jo0KH68ccf1b9/f507d84+Z926dXr11Vc1adKkgr0ZSTVq1CjwswAAABk4vx0AAABFYdiQEH33/TannuHetEltDR/azWn1AAAAUHL9sfd4ia6PnLEzCLJ1/Phxde/eXXFxcfYxd3d3LVq0SMHBwfmqFRISkm0QJCv/+te/tHnzZlWuXNlh/N1333UIiAAAAFiB89sBAABQFGw2D02bOlQ+Ps45qtnXt5ymTR0qDw93p9QDAABAyXbkqGtPTThyJNyl9ZEzwiDIUkREhP71r3/p7NmzDuOzZs1Sjx49iqSH4OBgzZkzx2Hs8uXLWrBgQYFrnjp1KsfX9u3bC9s2AAC4DnB+OwAAAIpKg/rV9eWnYwsdCPH1LacvFj6r+vWqOakzAAAAlHRJSakurZ+YlOLS+shZsQuDXLhwQZ9++qkGDx6sFi1aqGbNmvL29pa7u7s8PDjVpihER0crJCREhw4dchh/66239PjjjxdpLz179lTz5s0dxtasWVPgetWrV8/xVbVq1cK2DAAArgOc3w4AAICi1LpVXa36foKaNK5VoOebNK6llcteVOtWdZ3cGQAAAEqyMmVc+3XIsmVsLq2PnBWbMMjZs2c1atQo1apVSwMHDtT8+fP1+++/6/Tp00pISJBpmjJNM8cajz76qHx9fe2v//u//yui7kuP+Ph49ejRQ7///rvD+Pjx4/Wf//zHkp569uzpcM3uHQAAoDgYNiSkwF+Mzw7ntwMAACA79etV0+ofXtJL4x/M8y51gYF+emn8g1r9w0vsCAIAAIBM6gS79gfl69QJcml95KxYhEFWrVqlJk2aaPr06Q7BD8Mw7K+8GDlypOLi4uyv+fPnKz093cXdlx7Jycm6//779dtvvzmMjx49WpMnT7aoK6lRo0YO13FxcUpISLCoGwAAgCs4vx0AAABFzWbz0OgRPfT79nf1yYyReuD+tpnmNGxQXY8+3FGfzBip37e/q9EjerDzHAAAALLUtEntEl0fObM8DDJv3jz17NlTly5dcgiASMrTbiBXu/3229WmTRv78xEREfrpp59c0ndpk5aWpn79+unHH390GB8wYICmTp1qUVdXVKhQIdNYZGSkBZ0AAAA44vx2AAAAWMFm81Cvnq01+ZVHMt37Zsnzev/twerVszUhEAAAAOSoa+fGLq3fpZNr6yNnloZBNm7cqGHDhik9Pd0eAjFNU+XLl9e9996rMWPGqEqVKvmq+fDDD9tDJZK0evVqV7ReqpimqYEDB+rbb791GO/Tp4/mzJmT551ZXCUqKirTmJ9f3rbCBAAAcDXObwcAAAAAAABQEt0d0iLPRxDmV2Cgn7p3a+GS2sgby8IgqampGjRokFJSUuwhEA8PD02ePFnnzp3Td999p/fee09BQfk7R+iBBx5w2Flk7dq1rmi/VBk5cqQ+++wzh7G7775bixYtkru79duUHzp0yOG6XLly8vb2tqgbAACAzDi/HQAAAAAAAEBJ4+npoWFDQlxSe9iQEHaqs5hlv/pz587VsWPH7EEQT09P/fDDD7rrrrsKVbdatWqqW7euPUBw4MABxcfHq1y5cs5ou9R5/vnnNX36dIexTp066euvv5anp6dFXTlauXKlw3WTJk0s6gQAACB7Gee3DxsSopWrd2vVmt36+tstDnMaNqiuFs2D1aVTY3Xv1oJ/DAEAAAAAAACw1LAhIfru+23au++E02o2bVJbw4d2c1o9FIxlO4N88sknkmQ/0mXSpEmFDoJkaNmypUzTtF+HhoY6pW5p8/rrr+t///ufw1irVq30ww8/yMurcOfeO8svv/yi3377zWGsWzf+4AAAAMUX57cDAAAAAAAAKClsNg9NmzpUPj7O+f6wr285TZs6VB4e1p9Acb2zJAwSGRmp3bt3249zqVy5ssaMGeO0+rfccovD9bXHjJQmhmE4vAYOHJin56ZNm6bx48c7jDVu3FirV6+Wj4+PU3u8OpiTH+fOncv0fmw2mx599FEndAUAAAAAAAAAAAAAaFC/ur78dGyhAyG+vuX0xcJnORa7mLAkDLJ161alp6dLuhJm6N69u8qUKeO0+hUrVnS4joyMdFrt0uCzzz7T6NGjHcbq1q2rn376SRUqVHD6el26dNHHH3+shISEPD/z+++/q0OHDjp+/LjD+NChQ3XTTTc5uUMAAAAAAAAAAAAAuH61blVXq76foCaNaxXo+SaNa2nlshfVulVdJ3eGgrJkb+pz585J+ueImFatWjm1vr+/vyTZdx6JjY11av3c7Ny5Uzt37szy3pYtWzKNzZgxI9tajz76qFN36ti8ebMGDRrksFuHYRjq27evvv322wLVvPfee3XDDTdke//48eMaOXKknnvuOd1zzz3q1q2bmjVrpgYNGqhs2bL2eZcuXdJvv/2mRYsWaenSpUpLS3Oo07RpU73++usF6hEAAAAAAAAAAAAAkL369app9Q8vacbsNZoxe40iIqJzfSYw0E/DhoRo2JAQjsYuZiz5r3H+/HmH60qVKjm1fsauI9ldu9ry5cv1yiuv5Hn+8OHDs73XrVs3p4ZB/v77b6WmpjqMmaZZqJBFgwYNcgyDZLh8+bIWL16sxYsX28fKli2r8uXLKzY2VklJSdk+27BhQ61atUq+vr4F7hMAAAAAAAAAAAAAkD2bzUOjR/TQsCEhWrl6t1at2a2vv3Xc8KBhg+pq0TxYXTo1VvduLQiBFFOW/Fdxc3M8nebacEJhXbx4UdI/O4+44ugTOEdiYqISExOzve/m5qbhw4frrbfekpdX4c6oAgAAAAAAAAAAAADkzmbzUK+erdXu9gaZwiDfLHlelSryQ/zFnSVhkMDAQIfrS5cuObX+oUOHHK4rVqzo1PrIn+nTp2v16tXatGmT9u/fr+Tk5FyfqVWrlvr27asRI0boxhtvLIIuAQAAAAAAAAAAAAAoHSwJg1SuXFmSZBiGJGnv3r1Orf/LL7/IMAyZpinpSrCgKE2cOFETJ04skrUy3mNeDRw4UAMHDnRNM9m4++67dffdd0uSUlJSFBoaquPHj+vs2bOKiYlRYmKiypUrp4CAAAUGBurWW29VUFBQkfYIAAAAAAAAAAAAAEBpYUkYpEWLFvYgiGmaWr9+vdNq79u3T3/88Ye9vo+Pj5o3b+60+igcm82mxo0bq3Hjxla3AgAAAAAAAAAAAABAqeRmxaKBgYFq1qyZ/frIkSNat26dU2q/8sor9o8Nw1D79u3l5mbJ2wQAAAAAAAAAAAAAAChylqUk7r33XpmmaT/O5emnn1Zqamqhas6aNUvffPONwxEx//73v53RLgAAAAAAAAAAAAAAQIlgWRhkzJgx8vf3t1/v379f/fr1U1paWoHqTZ06VaNHj7YfDyNJwcHBeuihhwrbKgAAAAAAAAAAAAAAQIlhWRjE399fzz33nMPuIN9++61at26tX3/9NU81TNPUmjVr1LlzZz377LNKSUmxjxuGoUmTJjmEQwAAAAAAAAAAAAAAAEo7DysXf+6557Rp0yatWrXKHgjZs2ePOnXqpLp16+r2229XeHi4/cgXSXrhhRd06dIlnThxQps3b1ZcXJykfwIgkmQYhh5//HE9/PDDlrwvAAAAAAAAAAAAAAAAq1gaBnFzc9PixYvVqVMn7dmzxx7mME1Tf//9tw4dOuQw3zRNvfnmmw7XGa5+tmPHjpo2bVoRvAMAAAAAAAAAAAAAAIDixbJjYjKUL19ev/76q/r3728PdxiGYd8p5OrAhyT7WMZOINfOHTRokH766Sd5enpa8XYAAAAAAAAAAAAAAAAsZXkYRJK8vLw0f/58ffnll2rUqFGWYY+sXtI/4ZCbbrpJixYt0pw5c2Sz2Sx+RwAAAAAAAAAAAAAAANYoFmGQDA8++KD27dunH374QQMGDFCtWrUcdgK59hUQEKC+ffvqs88+019//aWHH37Y6rcAAAAAAAAAAAAAAABgKQ+rG8hKjx491KNHD0lSWFiYTp8+rYsXLyoyMlJeXl6qVKmSqlSpoptuusm+QwgAAAAAAAAAAAAAAACKaRjkalWrVlXVqlWtbgMAAAAAAAAAAAAAAKBEKFbHxAAAAAAAAAAAAAAAAKBwCIMAAAAAAAAAAAAAAACUIpYdE/P444/bP65Zs6YmTpzotNoTJ07UyZMnJUmGYWjOnDlOqw0AAAAAAAAAAAAAAFCcWRYGmT9/vgzDkCQ1bdrUqWGQZcuWae/evTJNkzAIAAAAAAAAAAAAAAC4rlh+TIxpmiWqLgAAAAAAAAAAAAAAQHFmeRjEVTJ2HQEAAAAAAAAAAAAAALielNowCAAAAAAAAAAAAAAAwPWoVIZBUlNT7R/bbDYLOwEAAAAAAAAAAAAAAChapTIMcunSJfvH5cuXt7ATAAAAAAAAAAAAAACAolXqwiDnz59XWFiY/bpChQoWdgMAAAAAAAAAAAAAAFC0Sl0Y5L333rN/bBiGGjZsaGE3AAAAAAAAAAAAAAAARcvDVYU3btyY57lxcXH5mn+1tLQ0xcXF6ejRo1q5cqXWrl0rwzBkmqYMw9Ctt95aoLoAAAAAAAAAAAAAAAAlkcvCIJ07d5ZhGDnOMU1TknTkyBF16dLFKetmhEAy9OvXzyl1AQAAAAAAAAAAAAAASgKXhUEyZAQ+CjsnrzKCIIZh6JFHHlHdunWdVhsAAAAAAAAAAAAAAKC4c3kYJLvdQa4OgOS2g0h+ZNS94447NG3aNKfVBQAAAAAAAAAAAAAAKAlcGgbJ644fztoZxM/PT23bttXjjz+uBx54wKkhEwAAAAAAAAAAAAAAgJLAZWGQ9evXZ3vPNE117dpVhmHINE3ddNNNmj17doHW8fDwkI+PjwICAlSjRo2CtgsAAAAAAAAAAAAAAFAquCwM0qlTpzzNMwxD5cuXz/N8AAAAAAAAAAAAAAAAZM+lx8TkxlnHwwAAAAAAAAAAAAAAAOAKy8IgL7/8sv3joKAgq9oAAAAAAAAAAAAAAAAoVYpFGAQAAAAAAAAAAAAAAADO4WZ1AwAAAAAAAAAAAAAAAHAewiAAAAAAAAAAAAAAAAClCGEQAAAAAAAAAAAAAACAUoQwCAAAAAAAAAAAAAAAQCniYXUDWTl8+LC2bt2qkydPKioqStHR0UpJSSlQLcMwNGfOHCd3CAAAAAAAAAAAAAAAUDwVmzBIWFiYpk2bpjlz5igiIsIpNU3TJAwCAAAAAAAAAAAAAACuK8UiDDJjxgw9++yzSkpKkmmaTqlpGIZT6gAAAAAAAAAAAAAAAJQklodBRo4cqRkzZthDIIUNcTgrTAIAAAAAAAAAAAAAAFASWRoG+fjjjzV9+nRJ/4RAMo52CQwM1KVLl5Sammofq1mzphISEhQZGamUlBR7nasDJN7e3qpUqVLRvhEAAAAAAAAAAAAAAIBiws2qhS9evKjnnntOhmHIMAyZpikfHx999NFHioyMVFhYmBo1auTwzLFjxxQeHq6kpCSdPHlSixcvVu/eveXm5ibTNGWappKTk/XEE0/o2LFj9hcAAAAAAAAAAAAAAMD1wrIwyAcffKD4+HhJV3YD8fPz06ZNmzRixAj5+vrm+nz16tXVt29fLV26VKGhoerQoYMkKTU1VS+99JIGDhzoyvYBAAAAAAAAAAAAAACKJcuOifn888/tO4IYhqHJkyercePGBapVp04dbdiwQUOHDtWcOXMkSZ9++qlq1qypSZMmObNtAAAAAICFps9cremzVju97vCh3TT8yW5OrwsAAAAAAABYwZIwyLlz53TkyBEZhiFJ8vPz09ChQwtV0zAMzZo1SydOnNDatWtlmqamTJmivn37FjhkAgAAAAAoXmLjEhQWHumSugAAAAAAAEBpYUkYZOfOnfaPDcNQSEiIbDZboesahqEPP/xQjRs3VlpamtLS0vTWW29p4cKFha4NAAAAALCeT3kvVQ0KyPZ+erqpcxFRDmNVAv3l5mbkWhcAAACQ2I0OAACUDpaEQSIiIhyumzVrlqfnEhMTVbZs2Rzn1K9fXx06dND69eslSd9++61SUlKcEjYBAAAAAFhr+JM5fwH9wsUYNWwy2mFsw9pXVamir6tbAwAAQCnBbnQAAKA0sCQMEhl55S9RpmnKMAzdcMMNWc67NsCRlJSUaxhEku666y57GCQ+Pl47duzQ7bffXsiuAQAAAAAAAABAacdudAAAoDSwJAySlJTkcF2+fPks5/n6+so0Tfv1+fPn5efnl2v9atWqOVwfPHiQMAgAAAAAAAAAAMgVu9EBAIDSwJIwiI+Pj8N1QkLWW6NdO+/UqVO66aabcq2fsXuIYVxJ4V64cKEgbQIAAAAAAACAZabPXK3ps1Znez893cw01vnOCbnuTjB8aM7f6AYAAABQ8lkSBqlYsaLDdWxsbJbzqlev7nC9b98+denSJdf64eHhkv45hiY1NbWAnQIAAAAAAACANWLjEhQWHpmvZ649uiK7ugAAAABKN0vCIPXq1ZP0z84dp0+fznJe48aNHeZt3LhRTz31VK71f/31V4frgIDsz/YDAAAAAAAAgOLIp7yXqgY5/2ubPuW9nF4TAAAAQPFiSRikQYMG9oCHJB04cCDLeS1btrR/bJqmli9frnPnzqlKlSrZ1j558qSWLVsmwzBkmle2SaxZs6aTOgcAAAAAAACAojH8SY5zAQAAAFAwblYs6u3trcaNG8s0TZmmqT179mQ5r2XLlqpVq5b9OiUlRYMHD1ZaWlqW8+Pi4tSvXz8lJyfbx9zc3NS+fXvnvgEAAAAAAAAAAAAAAIBiypIwiCR17tzZ/vGJEyd05MiRLOf9+9//lmma9p0+Vq1apdtuu03ffvutzp8/r7S0NJ07d06ffvqpWrZsqW3bttnnGoahkJAQ+fr6FtG7AgAAAAAAAAAAAAAAsJZlYZAePXpIkv24mFWrVmU577///a/9WJiMkMeuXbvUp08fBQUFydPTUzfccIMGDhyoQ4cO2Y+GyZj/4osvuvidAAAAAAAAAAAAAAAAFB+WhUG6du2qSpUq2Y+K+eSTT7Kc5+vrqxkzZsjN7UqrGeGRjOeufhmGYb9vGIbGjx+v2267rWjeEAAAAAAAAAAAAAAAQDHgYdXC7u7umjZtmg4cOGAfi42NlY+PT6a5vXr10oIFCzR48GAlJSXZAx9ZydgZZNy4cXrllVec3zgAAAAAAAAAAAAAAEAxZlkYRJL69u2b57mPPPKIbrvtNj3//PNavny5kpKSspzXvn17vfLKK+rSpYuz2gQAAAAAAAAAAAAAACgxLA2D5FdwcLC++uorxcfHa+PGjTp16pQuXLggb29vVa1aVR06dFBQUJDVbQIAAAAAAAAAAAAAAFimRIVBMpQrV07dunWzug0AAAAAAAAAAAAAAIBix83qBgAAAAAAAAAAAAAAAOA8hEEAAAAAAAAAAAAAAABKEcuOidm4caP94/Lly6tFixZOq717927FxcXZrzt27Oi02gAAAAAAAAAAAAAAAMWZZWGQzp07yzAMSVLTpk21e/dup9UePHiw9u7dK0kyDEOpqalOqw0AAAAAAAAAAAAAAFCcWRYGkSTTNEtkbQAAAAAAAAAAAAAAgOLKzcrFDcOw7w7iitoAAAAAAAAAAAAAAADXG0vDIBI7eAAAAAAAAAAAAAAAADiT5WEQV7g6YOLmVirfIgAAAAAAAAAAAAAAQJZKZVLi8uXL9o/LlStnYScAAAAAAAAAAAAAAABFq9SFQVJTU3Xq1Cn7ta+vr4XdAAAAAAAAAAAAAAAAFK1SFwZZuXKlkpOTJUmGYSg4ONjijgAAAAAAAAAAAAAAAIqOh9UNOEt0dLRWrlypsWPHyjAMmaYpwzDUpEkTq1sDAAAAAAAAAAAAAAAoMi4Lg+RnR44DBw4UeAePtLQ0xcXFKSoqSpLsIZAM99xzT4HqAgAAAAAAAAAAAAAAlEQuC4McP37cvkNHdjLuJScn6/jx405ZNyMIYhiGbrrpJv3rX/9ySl0AAAAAAAAAAAAAAICSwOXHxFy9S8fVrg6JZDenoEzTlLe3txYuXOj02gAAAAAAAAAAAAAAAMWZmyuLm6aZ7Suv8/L7MgxD3bt31/bt29WmTRtXvj0AAAAAAAAAAAAAAIBix2U7gwwYMCDH+wsWLLAfI1OhQgX17NmzQOt4eHjIx8dHAQEBaty4sW677TYFBQUVqBYAAAAAAAAAAAAAAEBJ57IwyLx583K8v2DBAvvHNWvWzHU+AAAAAAAAAAAAAAAAcufSY2LywjAMq1sAAAAAAAAAAAAAAAAoNVy2M0huatasaQ+C3HDDDVa1AQAAAAAAAAAAAAAAUKpYFgY5fvy4VUsDAAAAAAAAAAAAAACUWpYfEwMAAAAAAAAAAAAAAADnsWxnEAAAAKAkmz5ztabPWp3t/fR0M9NY5zsnyM3NyLHu8KHdNPzJboXuDwAAAAAAAABw/SrRYZCUlBQdOXJE0dHRqly5smrVqiV3d3er2wIAAMB1IDYuQWHhkfl65lxEVJ7qAgAAAAAAAABQGCUyDHL48GFNmDBBy5cvV3x8vH3c399fDz30kCZOnKjAwEALOwQAAEBp51PeS1WDAlxSFwAAAAAAAACAwrAsDHLmzBn17dvXfl2mTBmtWrVKZcuWzfG5H3/8UX369NHly5dlmo5bb0dGRmrmzJlaunSpvv32W7Vr184lvQMAAADDn+Q4FwAAAAAAAABA8eRm1cJLly7V1q1btW3bNm3btk3VqlXLNQhy/PhxPfjgg4qLi5NpmjIMI9PLNE1duHBB9957rw4ePFhE7wYAAAAAAAAAAAAAAKB4sCwMsmLFCkmy7+4xaNCgXJ/573//q5iYGHvwI+P5jJck+3hkZKSefPJJV7QOAAAAAAAAAAAAAABQbFlyTIxpmtq+fbt9Jw9vb2917Ngxx2f+/vtvffPNNw4hEB8fHw0ePFgNGjRQWFiY5s+frxMnTtjrbtq0SStXrlT37t2L4m0BAAAAAAAAAAAAAABYzpIwyJEjRxx2+Gjbtq1sNluOzyxatMh+NIxpmgoICNCWLVtUr149+5xnnnlGd955p3bt2mUfW7BgAWEQAAAAAAAAAAAAAABw3bDkmJhjx445XN9yyy25PrN06VJ7EMQwDP3f//2fQxBEknx9fTVv3jxJss9dsWKF0tPTndc8AAAAAAAAAAAAAABAMWZJGOTUqVOSrhz1Ikk33XRTjvMjIiL0119/2a89PDw0ePDgLOfefPPNateunb12QkKCQkNDndE2AAAAAAAAAAAAAABAsWdJGCQmJsbh2tfXN8f5GzdutH9sGIbat2+vgICAbOd36NDB4Xr//v0F6BIAAAAAAAAAAAAAAKDksSQMEh8f73Bdrly5HOdv27ZN0j87iYSEhOQ4/9qdRi5cuJDfFgEAAAAAAAAAAAAAAEokS8Ig7u7uDteJiYk5zt+6davDdfv27XOcn7HTiGEYkqTY2Nj8tggAAAAAAAAAAAAAAFAiWRIGufZYmHPnzmU7NzExUTt27LAHOzw9PdWqVasc66empjpcp6enF7BTAAAAAAAAAAAAAACAksWSMEjlypUl/bNzx4EDB7Kdu27dOiUnJ9vnN2vWTDabLcf6UVFRkv45Vsbb27uwLQMAAAAAAAAAAAAAAJQIloRBGjdubP/YNE39+OOP2c5dsmSJfZ4kdezYMdf61+40UqFChYK0CQAAAAAAAAAAAAAAUOJYEgapW7eu/Pz87NenTp3SwoULM807ffq0lixZYt9BRJLuvPPOXOv/8ccfDte1a9cueLMAAAAAAAAAAAAAAAAliCVhEDc3Nz3wwAMyTVOGYcg0TY0cOVKfffaZ0tPTJUlHjx5V7969lZiYaH+uUqVKuuOOO3Ktv2fPHocAyU033eT8NwEAAAAAAAAAAAAAAFAMWRIGkaQRI0bIze3K8oZh6PLlyxowYIB8fX1VrVo11atXT7t27bKHRQzD0JAhQ+zPZOfgwYM6fvy4/bpatWqqUqWKK98KAAAAAAAAAAAAAABAsWFZGKRFixYaPHiwTNOUJHvoIz4+XmFhYUpPT7ffk67sCvKf//wn17rffPON/WPDMNS2bVvnNw8AAAAAAAAAAAAAAFBMWRYGkaQPP/xQXbt2dQiEXPsyTVNlypTRF198IX9//1xrfv755/bnJKlLly6ufAsAAAAAAAAAAAAAAADFiqVhEE9PT61atUovv/yyvL29ZZpmplfz5s21YcMGde3aNdd6a9eu1f79+x3GevTo4ar2AQAAAAAAAAAAAAAAih0Pqxuw2Wx6+eWXNW7cOP388886evSoYmNjVbFiRbVp00ZNmzbNc60///xTvXr1sl8HBQWpRo0armgbAAAAAAAAAAAAAACgWLI8DJKhTJky6t69e6FqPP3003r66aed0xAAAAAAAAAAAAAAAEAJZOkxMQAAAAAAAAAAAAAAAHAuwiAAAAAAAAAAAAAAAAClCGEQAAAAAAAAAAAAAACAUoQwCAAAAAAAAAAAAAAAQClCGAQAAAAAAAAAAAAAAKAUIQwCAAAAAAAAAAAAAABQihAGAQAAAAAAAAAAAAAAKEUIgwAAAAAAAAAAAAAAAJQihEEAAAAAAAAAAAAAAABKEcIgAAAAAAAAAAAAAAAApYiH1Q2geDl16pS2bdumEydOKCEhQeXLl1dwcLDatm2rypUrO3296Ohobd68WYcPH1ZMTIzKlCmjatWq6dZbb1XdunWdvh4AAAAAAAAAAAAAAKUdYRAXMk1Thw8f1vbt27Vjxw5t375de/bsUWJiYqZ5Vlu2bJmmTJmirVu3Znnfzc1Nd9xxh1588UV17Nix0Ov98ccfmjRpkn744QelpKRkOefmm2/Wf/7zHw0YMECGYRR6TQAAAAAAAAAAAAAArgeEQZwsLi5Ob7zxhnbs2KEdO3YoKirK6pZydPnyZQ0aNEhfffVVjvPS09P1008/6aefftJTTz2ld955Rx4eBfvt88Ybb+ill15SampqjvP279+vQYMGacGCBVqyZIlLdiYBAAAAAAAAAAAAAKC0IQziZBcuXNDrr79udRt5kpCQoO7du2vjxo2Z7hmGIV9fX0VHR2e698EHH+jcuXP64osv8r1jx3//+1+9/fbbWd7z8fHR5cuXlZ6e7jC+YcMGderUSZs2bVLFihXztR4AAAAAAAAAAAAAANcbN6sbgHWeeuqpTEGQ9u3ba/Xq1bp8+bKioqIUExOjxYsX65ZbbnGYt3jxYr3xxhv5Wm/RokWZgiC1a9fW7NmzFRkZqZiYGCUkJGjTpk3q0aOHw7y//vpLjzzySLE4UgcAAAAAAAAAAAAAgOKMMIiLeXt7q0OHDnr22Wf1xRdf6NVXX7W6JUnSjh079MknnziMDRw4UBs2bFBISIi8vLwkXdmt48EHH9TWrVt11113OcyfNGmSTp8+naf1Ll++rGeffdZhrHnz5tq+fbueeOIJ+fv7S5I8PT3Vvn17LV++XC+88ILD/B9//FFff/11ft4mAAAAAAAAAAAAAADXHcIgTlauXDkNGzZMc+bM0d69exUdHa2NGzfqnXfeUb9+/VS9enWrW5QkjR8/3uG6cePGmjVrltzd3bOc7+3trcWLFysoKMg+lpSUlOdwy9SpUxUREWG/LleunJYuXarKlStn+8xrr72mkJAQh7GXXnop0zEyAAAAAAAAAAAAAADgH4RBnCwwMFDTp0/X448/rsaNG2cbrrDSnj179NNPPzmMvf/++7LZbDk+FxAQoMmTJzuMzZ07V+fPn8/xudTUVL377rsOY2PHjlVwcHCuvX700UcyDMN+/ddff+n777/P9TkAAAAAAAAAAAAAAK5XHlY3gKL37bffOlzXrVtXXbt2zdOz/fr10zPPPKPY2FhJV4Iey5cv16BBg7J9ZuPGjbp48aL92s3NTUOGDMnTejfddJO6dOmidevWOfR/33335el5AAAAAAAAAAAAAEDWps9cremzVmd7Pz3dzDTW+c4JcnMzspj9j+FDu2n4k90K3R8KjjDIdWjZsmUO1w8++GCen/X29lbPnj31+eefO9TLKQxy7Xpt27ZVjRo18rxmv379HMIgK1asUFpaWrHcdQUAAAAAAAAAAAAASorYuASFhUfm65lzEVF5qgtrEQa5zly4cEF79+51GGvXrl2+atx+++0OYZCrgxpZufZ+Qda72sWLF/X777+rZcuW+aoDAAAAAAAAAAAAAPiHT3kvVQ0KcEldWMupYZCNGzc6s5zTdOzY0eoWio2//vor01ibNm3yVeO2225zuI6NjdXp06dVvXr1THPT0tL0999/F2q9Ro0aydfXVzExMfaxv/76izAIAAAAAAAAAAAAABTC8Cc5zqW0cmoYpHPnzjKMnM8GKmqGYSg1NdXqNoqN0NBQh2s/Pz9VqFAhXzWCg4OzrJtVGOTo0aNKTk7O9fmcGIah2rVrO+xocu37AAAAAAAAAAAAAAAAV7i5oqhpmsXqhX9cu0tHzZo1810jICBA3t7eDmMHDx7M03oFXbNGjRp5Wg8AAAAAAAAAAAAAgOudS8IghmEUixcyu3TpksN1UFBQgepUrVrV4ToyMjJP69lstnzvRJKf9QAAAAAAAAAAAAAAuN459ZgYSYXeiePqEEdeauV3/vUuLi7O4bpcuXIFquPl5ZVjXavWAwAAAAAAAAAAAADgeufUMMj69esL9Nz27ds1ceJEJSYmSroS6vD09FTnzp3VsmVLNWjQQH5+fvL29tbly5cVHR2t0NBQ7dq1Sxs2bFBycrI9FFKuXDm9/PLLat26tdPeV2ly+fJlh+uyZcsWqM614Yxr61q1Xm5Onz6d4/2wsLAC1QUAAAAAAAAAAAAAoLhwahikU6dO+X5m5syZGj9+vNLS0mSapvz9/TVhwgQNHDhQAQEBuT4fFRWl+fPn69VXX1VkZKTi4+M1fvx4ffTRRxo6dGhB3kaplpCQ4HDt6elZoDplypTJsa5V6+WmRo0aBXoOAAAAAAAAAAAAAICSws3KxRcuXKgRI0YoNTVVpmmqbdu2Cg0N1TPPPJOnIIgk+fv76+mnn1ZoaKjatWsnSUpNTdXw4cO1YMECV7ZfIl27M0dycnKB6iQlJeVY16r1AAAAAAAAAAAAAAC43jl1Z5D8OH78uEaMGCHTNGUYhlq2bKm1a9dmOg4krypXrqwff/xRnTp10q5du2SapkaOHKmOHTvqxhtvdHL3JVf58uUdrjOO5smva3fmuLauVevl5tSpUzneDwsL44ghAAAAAAAAAAAAAECJZlkY5LXXXlN8fLwkyd3dXXPnzi1wECSDl5eX5s6dq+bNmys9PV0JCQmaPHmy5syZ44yWS4VrQxQZ/w3yq6BhkIIe7+KsMEj16tUL9BwAAAAAAAAAAAAAACWFJcfEJCUl6csvv5RhGDIMQx07dtQtt9zilNq33HKLOnfuLNM0ZZqmFi9enOmIkevZtcfvhIeHF6jOtc9ld6zPtePJycm6dOlSvtcLCwvL03oAAAAAAAAAAAAAAFzvLAmDbN++XZcvX7Zfh4SEOLX+v/71L/vHCQkJ2rZtm1Prl2T16tVzuD558mS+a0RGRiouLi7HujmNF2TNa493yW49AAAAAAAAAAAAAACud5aEQUJDQyVJpmlKcv7RHdWqVctyPUgNGjRwuI6Ojs73Th3Hjh3LtW6G4OBgeXp6OowdPXo0X+uZpqnjx4/naT0AAAAAAAAAAAAAAK53loRBrg0fpKamOrV+enq6JMkwjCzXu541atQo01h+d07ZunWrw3X58uWzDfR4eHiobt26hVrvr7/+UkxMjMNYw4YN81UDAAAAAAAAAAAAAIDrhSVhEJvN5nB97REghZVRL2PnkWt3prieVapUSY0bN3YY++233/JV49r5Xbt2tQdvstK1a1enrlehQgU1a9YsXzUAAAAAAAAAAAAAALheWBIGueGGGyT9s3PHypUrnVr/2npVq1Z1av2SrlevXg7XS5YsyfOz8fHxWr58eY71cltvy5Yt+QoALV682OG6R48e8vDwyPPzAAAAAAAAAAAAAABcTywJg1x9bIhpmtq6dWu+jw7JzrZt27R582aHnSrq1avnlNqlxf333+9wfejQIa1bty5Pz3755ZcOR7Z4eHjonnvuyfGZTp06qUKFCvbr9PR0zZ49O0/rHT58OFNv9913X56eBQAAAAAAAAAAAADgemRJGKRly5aqWbOmpCu7g6Snp2vQoEG6dOlSoepeunRJgwYNsh8PI0k1atRQy5YtC1W3ODMMw+E1cODAXJ9p0aKF7rjjDoexp59+WikpKTk+FxUVpfHjxzuMDRw4UIGBgTk+5+HhoWeeecZh7J133tGxY8dy7XXUqFEO/z3r16+f604kAAAAAAAAAAAAAABczywJg0jSv//9b4dv8oeGhqpTp076+++/C1Tv0KFD6ty5s0JDQ2UYhkzTlGEY6t+/v7NaLlVee+01h+t9+/Zp6NChSktLy3L+5cuX9dBDDyk8PNw+VqZMGb300kt5Wu/pp59W5cqV7dfx8fHq06ePzp8/n+0zL774otasWeMwNmnSJLm7u+dpTQAAAADXj+TkVC37YbvGv7wo0737+07RmLFztOyH7UpOTrWgOwAAAAAAAKBoeVi18AsvvKDPPvtMp06dsh/psn//fjVp0kSjR4/WE088ofr16+da5++//9bs2bP10UcfKTk52T5uGIZq1KihcePGuew9ZGfnzp3auXNnlve2bNmSaWzGjBnZ1nr00Ufl4+PjtN4ytGnTRoMGDdK8efPsY/Pnz9fhw4c1YcIEdezYUWXLllVcXJxWrVqlSZMm6c8//3SoMX78eNWoUSNP65UvX15vv/22BgwYYB/bvXu3WrdurQkTJqh3797y9/dXcnKyduzYoSlTpmj58uUONe6880717du3EO8aAAAAQGmTkpKqGbPXaPqsNTp/PjrLOaEHzyj04Bl9/uVGBQb6adiQEA0bEiKbzbJ/EgMAAAAAAAAuZZhXb89RxH7++Wf16NHDfjxJRisZ4ZAGDRqoZcuWql+/vvz8/OTt7a3Lly8rOjpaBw8e1K5duxQaGprpWdM0VaZMGa1YsUJdu3Yt8vc1ceJEvfLKK06pdezYMdWuXTvb+xm/VhkGDBig+fPn56l2fHy8/vWvf+m3337Lsq6vr6+io7P+YmqfPn20ePFiubnlb3OZZ599Vu+9916W93x9fRUXF6f09PRM9+rXr69NmzY57C7iCqdPn7YHXE6dOqXq1au7dD0AAAAABRd68LRGjpmlvftO5PvZJo1radrUoWpQn7/zAwAAIH8uXIxRwyajHcb+2vuhKlX0tagjAABQ0rni+9SW/hjUHXfcoaVLl6pPnz5KSUmxBxsygh1//fWXPeyRlatzLFc/6+npqaVLl1oSBClJypUrp9WrV2vAgAH65ptvHO6ZppltEGTkyJF677338h0EkaR3331XFSpU0MSJEzMdSRMTE5PlMx06dNBXX33l8iAIAAAAgJJj+45D6vfYO4qNTSjQ83v3nVD3XpP15adj1bpVXSd3BwAAAAAAAFgr/9/Nd7J77rlH69evV7169Rx298h4maaZ7evqedKVAEP9+vW1YcMG9ejRw8q3VWKUL19eX3/9tb755hu1bt0623mGYeiOO+7Qhg0b9NFHH8lmsxV4zRdffFE7d+7UfffdJw+P7PNIjRo10pw5c7RhwwZVqVKlwOsBAAAAKF1CD54uVBAkQ2xsgvo99o4O/n3GSZ0BAAAAAAAAxYOlx8RcLSkpSW+++aZmzJih8PBwSZmPQMlKRvtVqlTRiBEj9Nxzz6lMmTIu7bU0O3nypLZu3aqTJ08qMTFR3t7eCg4OVtu2bRUYGOj09aKiorR582YdOnRIsbGx8vT0VPXq1e3HAxU1jokBAAAAireUlFR16zmpQEfDZKdJ41pa/cNLstks3TwTAAAAJQTHxAAAAGcrdcfEXK1MmTJ66aWX9MILL+i7777TqlWrtHXrVoWGhiqrvIphGGrQoIFuu+023X333bnuMoG8qVmzpmrWrFlk6/n7+6t79+5Fth4AAACAkm3G7DVODYJIV46MmTF7jUaPYIdJAAAAAAAAlA7FLj3h4eGhPn36qE+fPpKkhIQEnT9/XlFRUYqNjZWPj4/8/f1VuXJleXl5WdwtAAAAAKCoJCenasbsNS6pPWP2Gg0bEsLuIAAAAAAAACgViv1Xuby8vIp8twoAAAAAQPGzas1uRUREu6R2RES0Vq7erV49W7ukPgAAAAAAAFCU3KxuAAAAAACAvFi3YZ9L66//xbX1AQAAAAAAgKJCGAQAAAAAUCL8sfd4ia4PAAAAAAAAFBXCIAAAAACAEuHI0TDX1j8S7tL6AAAAAAAAQFEhDAIAAAAAKBGSklJdWj8xKcWl9QEAAAAAAICiQhgEAAAAAFAilCnj4dL6ZcvYXFofAAAAAAAAKCqu/UpaPiUmJmrLli3atWuXDh48qOjoaEVHRyslpeA/nWUYhn7++WcndgkAAAAAsEKd4Kraf+Ck6+rXCXJZbQAAAAAAAKAoFYswyMmTJzVlyhR98cUXiomJcVpd0zRlGIbT6gEAAAAArNO0SW2XhkGaNqntstoAAAAAAABAUbL8mJg5c+aocePGmjlzpqKjo2WapsMLAAAAAABJ6tq5sUvrd+nk2voAAAAAAABAUbF0Z5CZM2dqxIgR9tBHVrt4EAgBAAAAAEjS3SEtFBjop4iIaKfXDgz0U/duLZxeFwAAAAAAALCCZWGQ0NBQjRo1SpJjCCQj/OHt7a3atWvLz89PNpvNkh4BAAAAAMWHp6eHhg0J0aTXlji99rAhIbLZisVJqgAAAAAAAEChWfaVrvHjxystLc0eBDFNU15eXho9erT+/e9/6+abb85ypxAAAAAAwPVr2JAQfff9Nu3dd8JpNZs2qa3hQ7s5rR4AAAAAAABgNUvCIPHx8VqxYoVDEOTGG2/UTz/9pODgYCtaAgAAAACUADabh6ZNHaruvSYrNjah0PV8fctp2tSh8vBwd0J3AAAAAAAAQPHgZsWiv/76q5KTkyVdCYJ4eHho2bJlBEEAAAAAALlqUL+6vvx0rHx8vApVx9e3nL5Y+Kzq16vmpM4AAAAAAACA4sGSMMipU6fsHxuGoR49euiWW26xohUAAAAAQAnUulVdrfp+gpo0rlWg55s0rqWVy15U61Z1ndwZAAAAAAAAYD1LwiAXLlyQdGVXEEnq0qWLFW0AAAAAAEqw+vWqafUPL+ml8Q8qMNAvT88EBvrppfEPavUPL7EjCAAAAAAAAEotDysWdXd3PIu5atWqVrQBAAAAACjhbDYPjR7RQ8OGhGjl6t1atWa3vv52i8Ochg2qq0XzYHXp1FjdVjczkQABAABJREFUu7WQzWbJP4UBAAAAAACAImPJV8CCgoIcrhMTE61oAwAAAABQSthsHurVs7Xa3d4gUxjkmyXPq1JFX4s6AwAAAAAAAIqeJcfENG/eXJJkGIYk6cyZM1a0AQAAAAAAAAAAAAAAUOpYEga5+eabVbt2bfv1+vXrrWgDAAAAAAAAAAAgT5KTU7Xsh+0a//KiTPfu7ztFY8bO0bIftis5OdWC7gAAABxZdlDy6NGjNXbsWEnShg0bdPToUQUHB1vVDgAAAAAAAAAAQCYpKamaMXuNps9ao/Pno7OcE3rwjEIPntHnX25UYKCfhg0J0bAhIbLZLPs2DAAAuM5ZsjOIJI0cOVL169eXYRhKTU3VmDFjrGoFAAAAAAAAAAAgk9CDp9Wt5yRNem1JtkGQa0VERGvSa0vUreckhR487eIOAQAAsmZZGMTT01Nff/21fHx8JEkrV67UiBEjlJ6eblVLAAAAAAAAAAAAkqTtOw6pe6/J2rvvRIGe37vvhLr3mqztOw45uTMAAIDcWRYGkaRGjRrpp59+UsWKFWWapmbOnKn27dtr06ZNVrYFAAAAAAAAAACuY6EHT6vfY+8oNjahUHViYxPU77F3dPDvM07qDAAAIG8sO6xu4cKF9o/HjBmj1157TYmJidq6das6d+6sunXrqmPHjqpbt64qVKggm81W4LX69+/vjJYBAAAAAAAAAEApl5KSqpFjZhU6CJIhNjZBI56aqdU/vCSbzbJvywAAgOuMZX/rGDhwoAzDcBgzDEOmaco0Tf399986dMg5W6cRBgEAAAAAAAAAAHkxY/aaAh8Nk529+05oxuw1Gj2ih1PrAgAAZMfSY2Ik2cMfGS/DMOyva+8V5AUAAAAAAAAAAJAXycmpmjF7jUtqz5i9RikpqS6pDQAAcC3LwyBXhz+y2imkMC8AAAAAAAAAAIC8WrVmtyIiol1SOyIiWitX73ZJbQAAgGtZGgZxxs4f7AoCAAAAAAAAAACcYd2GfS6tv/4X19YHAADI4GHVwseOHbNqaQAAAAAAAAAAgEz+2Hu8RNcHAADIYFkYpFatWlYtDQAAAAAAAAAAkMmRo2GurX8k3KX1AQAAMlh6TAwAAAAAAAAAAEBxkZSU6tL6iUkpLq0PAACQgTAIAAAAAAAAAACApDJlXLuhetkyNpfWBwAAyEAYBAAAAAAAAAAAQFKd4KqurV8nyKX1AQAAMhAGAQAAAAAAAAAAkNS0Se0SXR8AACADYRAAAAAAAAAAAABJXTs3dmn9Lp1cWx8AACADYRAAAAAAAAAAAABJd4e0UGCgn0tqBwb6qXu3Fi6pDQAAcC3CIAAAAAAAAAAAAJI8PT00bEiIS2oPGxIim83DJbUBAACuZdnfOoKDg4tkHcMwdOTIkSJZCwAAAAAAAAAAlGzDhoTou++3ae++E06r2bRJbQ0f2s1p9QAAAHJjWRjk+PHjMgxDpmm6dB3DMFxaHwAAAAAAAAAAlB42m4emTR2q7r0mKzY2odD1fH3LadrUofLwcHdCdwAAAHlj+TExhmG47AUAAAAAAAAAAJBfDepX15efjpWPj1eh6vj6ltMXC59V/XrVnNQZAABA3lgaBjFNs1Cv3GoCAAAAAAAAAAAUROtWdbXq+wlq0rhWgZ5v0riWVi57Ua1b1XVyZwAAALmz7JiYAQMGFPjZlJQUXbx4UYcPH9aRI0ckyX7kjJeXlx544AG5u7PdGgAAAAAAAAAAKLj69app9Q8vacbsNZoxe40iIqJzfSYw0E/DhoRo2JAQ2WyWfRsGAABc5yz7W8i8efOcUufs2bOaOXOmPvzwQ0VFRSkxMVEnTpzQt99+qwoVKjhlDQAAAAAAAAAAcH2y2Tw0ekQPDRsSopWrd2vVmt36+tstDnMaNqiuFs2D1aVTY3Xv1oIQCAAAsJxhlpLzVE6ePKk+ffpo586dMgxDjRo10qZNm+Tv7291ayhBTp8+rRo1akiSTp06perVq1vcEQAAAID8uHAxRg2bjHYY+2vvh6pU0deijgAAAFDa8HdOAADgbK74PrVboSsUEzVr1tSPP/6o+vXryzRNHThwQA8//LDVbQEAAAAAAAAAAAAAABSpUhMGkSR/f399/PHHkiTT/H/s3Xd4VGX+/vH7JJmEJCQhlBB6rwILCdIEFVBpKliw4UpbFNaygIX9qruCrq4NUHcVRAQFCyqLoCBNEEV6B+lSDCAhgfTezu8PfhmZFEiZyZkM79d1zbXzPOecz/mcWWXZzJ3nMbVq1SotWLDA4q4AAAAAAAAAAAAAAAAqjkeFQSSpd+/e6tSpk6SLgZDXX3/d4o4AAAAAAAAAAAAAAAAqjseFQSSpX79+9vd79uzRb7/9ZmE3AAAAAAAAAAAAAAAAFccjwyDNmzd3GG/dutWiTgAAAAAAAAAAAAAAACqWR4ZBgoODJUmGYUiSTp06ZWU7AAAAAAAAAAAAAAAAFcYjwyDx8fEO48zMTIs6AQAAAAAAAAAAAAAAqFgeGQbZtm2bJMk0TUlSaGiole0AAAAAAAAAAAAAAABUGI8Lg8THx2vhwoX2LWIkqU6dOhZ2BAAAAAAAAAAAAAAAUHE8KgySm5ur4cOHKyEhwT5nGIZ69uxpXVMAAAAAAAAAAAAAAAAVyGPCIGvWrFG3bt20bNkyGYYh0zRlGIa6d++uGjVqWN0eAAAAAAAAAAAAAABAhfCx6sYvvvhiua7Pzs5WUlKSjh8/ru3btysmJkaS7CGQfFOmTCnXfQAAAAAAAAAAAAAAACoTy8IgkydPdghtlIdpmvb3l9YcM2aM+vTp45R7AAAAAAAAAAAAAAAAVAaWhUHyXRrkKKtLAyD59UaOHKkZM2aUuzYAAAAAAAAAAAAAAEBl4mV1A4ZhlPtlmqb91aZNG3399df68MMPnbbyCAAAAAAAAAAAAAAAQGVh6cog5VkVxMfHR8HBwapWrZpatWqlyMhIDRgwQN27d3dihwAAAAAAAAAAAAAAAJWLZWGQvLw8q24NAAAAAAAAAAAAAADgsSzfJgYAAAAAAAAAAAAAAADOQxgEAAAAAAAAAAAAAADAgxAGAQAAAAAAAAAAAAAA8CCEQQAAAAAAAAAAAAAAADwIYRAAAAAAAAAAAAAAAAAPQhgEAAAAAAAAAAAAAADAg/hY3UBRYmNjtW7dOm3YsEE7d+7U+fPnFRcXp+TkZAUFBal69eqqWbOmIiMj1aNHD/Xu3Vs1a9a0um0AAAAAAAAAAAAAAADLuVUYZOfOnZo2bZoWLlyo7Oxs+7xpmvb36enpiomJkWEY2rBhg9555x3ZbDbde++9Gj9+vDp16mRF6wAAAAAAAAAAAAAAAG7BLbaJyc7O1pNPPqkuXbro888/V1ZWlkzTtL8Mwyj0uvR4VlaWPvnkE1177bV6+umnHYIkAAAAAAAAAAAAAAAAVxPLwyAJCQnq0aOH3nrrLeXl5RUZ/pDkEP6QVGQ4JC8vT9OmTdN1112nxMREKx8LAAAAAAAAAAAAAADAEpZuE5OVlaXBgwdrx44dkmQPfkh/bA3j5eWlBg0aKDQ0VIGBgUpNTVVCQoKioqKUl5dnv+7S0MiOHTs0ePBgrVq1Sr6+vhX8VAAAAAAAAAAAAAAAANaxNAzyzDPPaP369YVCIKGhobr//vt1zz33KDIyUoGBgYWuTUtL044dO/Tll1/q888/V1xcnMMqIevXr9ekSZM0ffr0inwkAAAAAAAAAAAAAAAAS1m2Tczx48c1Y8aMQtvAjB07VsePH9d///tfXX/99UUGQSQpICBAvXr10n/+8x8dP35cf/3rX+3H8gMhM2bM0IkTJyrkeQAAAAAAAAAAAAAAANyBZWGQ1157TdnZ2ZIuBkG8vb31/vvv67333lNISEipagUHB+u///2vPvjgA4dVRrKzs/X66687tW8AAAAAAAAAAAAAAAB3ZlkYZNmyZfYVPAzD0FNPPaUxY8aUq+aoUaP09NNP22uapqlvv/3WSR0DAAAAAAAAAAAAAAC4P0vCIL/88ot+//13+7hGjRqaPHmyU2pPnjxZNWvWtI/Pnj2rX375xSm1AQAAAAAAAAAAAAAA3J0lYZAjR47Y3xuGoTvuuEN+fn5Oqe3n56c77rhDpmkWeT8AAAAAAAAAAAAAAABPZkkYJDY2VpLsgY0//elPTq3fsWNHh3FMTIxT6wMAAAAAAAAAAAAAALgrS8IgcXFxDuOwsDCn1s/fJsYwDElSQkKCU+sDAAAAAAAAAAAAAAC4K0vCICEhIQ7jguGQ8soPf+SvPBIcHOzU+gAAAAAAAAAAAAAAAO7KkjBIrVq1JP2xcsfBgwedWr9gvfz7AQAAAAAAAAAAAAAAeDpLwiBNmjSxvzdNU4sWLXJa7fx6+UETSWrcuLHT6gMAAAAAAAAAAAAAALgzS8IgERERCg0NtY9Pnz6td955xym133vvPUVFRdnHoaGh6ty5s1NqAwAAAAAAAAAAAAAAuDtLwiBeXl665ZZbZJqmDMOQaZp69tlntW7dunLVXb9+vf7+97/baxqGoVtuucVhlRAAAAAAAAAAAAAAAABPZkkYRJKeeuope0jDMAylpaVp0KBBeu+998pUb+bMmRowYIBSU1Ptc4Zh6KmnnnJKvwAAAAAAAAAAAAAAAJWBZWGQyMhIDR06VKZpSroY3EhPT9fjjz+ujh07aubMmYqJiblsjdjYWM2YMUOdOnXSo48+qrS0NIdVQYYOHaqIiIiKeBwAAAAAAAAAAAAAAAC34GPlzd99913t3LlTx44dkyR7kGPv3r169NFH9eijj6p+/fpq06aNqlWrpsDAQKWmpiohIUEHDx7U6dOnJckhUJL/n82bN9d///tfax4MAAAAAAAAAAAAAADAIpaGQWrUqKEVK1aoZ8+eio6OlmEY9kBIfsDj1KlT9tDHpfKP58sPgpimqTp16mjFihWqUaOG6x8CAAAAAAAAAAAAAADAjVi2TUy+pk2bas+ePerfv7/DCh+XvvLDIZe+ijtn4MCB2r17t5o0aWLxkwEAAAAAAAAAAAAAAFQ8y8MgklSrVi199913+uSTTxQREeEQ+pAKh0MuXQUk/9W5c2d99tlnWrp0qWrVqmXl4wAAAAAAAAAAAAAAAFjG0m1iCnrggQf0wAMPaMuWLVq9erU2bNignTt36sKFC8rLy7Of5+XlpZo1ayoiIkI9evRQv379dO2111rYOQAAAAAAAAAAAAAAgHtwqzBIvq5du6pr164Oc0lJSUpOTlZQUJCCg4Mt6gwAAAAAAAAAAAAAAMC9WRIGycjIUExMjMNc/fr15eVV/K41wcHBhEAAAAAAAAAAAAAAAACuwJIwyIIFCzR69Gj7ODw8XKdPn7aiFQAAAAAAAAAAAAAAAI9S/FIcLnTu3DmZpinTNCVJQ4cOlWEYVrQCAAAAAAAAAAAAAADgUSwJg+Tm5kqSPQDSsmVLK9oAAAAAAAAAAAAAAADwOJaEQYKCgiTJvjJIWFiYFW0AAAAAAAAAAAAAAAB4HEvCII0aNXIYx8fHW9EGAAAAAAAAAAAAAACAx7EkDNKpUydJf2wTc+zYMSvaAAAAAAAAAAAAAAAA8DiWhEEaNGig9u3bS7q4Vczy5cutaAMAAAAAAAAAAAAAAMDjWBIGkaSxY8fKNE1J0i+//KIVK1ZY1QoAAAAAAAAAAAAAAIDHsCwMMmbMGF1zzTUyDEOmaerRRx/VuXPnrGoHAAAAAAAAAAAAAADAI1gWBvHx8dGiRYsUGhoqSTpx4oT69OmjvXv3WtUSAAAAAAAAAAAAAABApWdZGESSWrRooQ0bNqhFixaSpIMHD+raa6/VmDFjtGnTJvs2MgAAAAAAAAAAAAAAACgZH6tuPGrUKPv7jh076vjx48rLy1N2drbmzJmjOXPmyN/fXx06dFBYWJiCg4Pl41P6dg3D0IcffujM1gEAAAAAAAAAAAAAANyWZWGQjz76SIZhFJo3DMO+IkhaWpq2bNlS5nuYpkkYBAAAAAAAAAAAAAAAXFUsC4Pkyw9+XBoMKRgSKct2MUUFTQAAAAAAAAAAAAAAADyd5WGQkoQ2CHYAAAAAAAAAAAAAAACUjGVhkIYNGxLyAAAAAAAAAAAAAAAAcDLLwiAnT5606tYAAAAAAAAAAAAAAAAey8vqBgAAAAAAAAAAAAAAAOA8hEEAAAAAAAAAAAAAAAA8CGEQAAAAAAAAAAAAAAAAD0IYBAAAAAAAAAAAAAAAwIP4WN3A1SIvL087duzQvn37FBMTI9M0VaNGDbVt21Zdu3aVzWazukUAAAAAAAAAAAAAAOABCIO4WEpKit544w3NnDlTMTExRZ4TEhKiESNG6LnnnlOtWrVc3pNhGE6t16hRI508efKy54wYMUIff/xxue4zfPhwffTRR+WqAQAAAKBym/H+Cs2YtaLY43l5ZqG5G2/6h7y8Lv//g8Y93F/jHulf7v4AAAAAAAAAd0AYxIW2b9+uu+66S1FRUZc9LzExUW+//bbmz5+vTz/9VP37V64fQPr48I8RAAAAgIqRnJKus9HxpbrmXExCieoCAAAAAAAAnsKyb/GvFJAoD29vbwUHBysoKMhl97iSLVu26KabblJKSkqhY35+fvLy8lJ6uuMPG+Pi4nTbbbfpf//7n26//faKarXcKlt4BQAAAEDlFVTVX3XCQ11SFwAAAAAAAPAUloVBGjdu7PTtSgoyDEPVqlVTu3bt1KVLF11//fUaOHCgvLy8XHrf2NhY3XHHHQ5BEB8fHz322GMaN26cmjdvLsMwFBUVpQ8//FDTpk1TamqqJCknJ0fDhg3Tjh071LJlS5f0N2PGjDJf+80332j58uUOcyNGjCh1nYceekjdu3cv1TWtWrUq9X0AAAAAeJZxj7CdCwAAAAAAAHAllu7vYZqF93J2dv24uDitX79e69ev19SpU1WvXj399a9/1cSJE+Xr6+uS+06ePFlnz561j/38/LRw4ULdeuutDuc1atRIL774ogYNGqQBAwYoPv7iUscpKSmaOHGili5d6pL+xo4dW+ZrZ86c6TBu166dOnfuXOo6vXv3LlOIBAAAAAAAAAAAAAAAXJ5rl8i4AsMwKuQlXQyGmKap06dP67nnnlPXrl118OBBpz/TyZMnNXv2bIe5KVOmFAqCXKpr16569913HeaWLVumTZs2Ob2/8ti1a5f27NnjMEegAwAAAAAAAAAAAAAA92JpGCQ/oHHp60rHy3rupeEQ0zS1Z88e9ezZU0ePHnXqM02fPl1ZWVn2cZMmTfTkk09e8br7779fPXv2dJh77bXXnNpbeX300UcOYx8fHz344IPWNAMAAAAAAAAAAAAAAIpk2TYxc+fOtb8/efKkXn/9dWVkZEi6GOzw8vJSRESEOnXqpCZNmigkJER+fn5KSkrShQsXtHfvXm3btk3nzp2TJPsKIN27d9df/vIX5ebmKj4+XtHR0dq8ebO2b9+urKwsh0BIfHy8br/9dm3btk1Vq1Z1ynMtXrzYYTx69Gj5+JTsY3744Yf1888/28erVq1SWlqaAgICnNJbeWRnZ+uzzz5zmBswYIBq165tUUcAAAAAAAAAAAAAAKAoloVBhg8fLunidihPPPGEMjIyZJqmgoKCNGnSJI0YMUJ169a9bA3TNLV69Wq98cYbWrNmjQzD0KZNm1SjRg19/vnnDiGKmJgYvfXWW5o6dapycnLsgZAjR47o7bff1nPPPVfuZ9q1a5eioqIc5u69994SX3/XXXdp1KhRysnJkSSlp6dr1apVGjJkSLl7K6+lS5fq/PnzDnMjR460qBsAAAAAAAAAAAAAAFAcS7eJWbVqle68804lJyfLNE316NFDhw4d0rPPPnvFIIh0cTWQW265RatXr9acOXPsK3AsXbpUt99+uz1UIUlhYWF65ZVX9MMPPyg4ONh+vWmaevvtt5Wenl7u51m7dq3DuHbt2mrevHmJrw8ICFDHjh0d5tasWVPuvpyh4BYxNWvW1K233mpNMwAAAAAAAAAAAAAAoFiWhUHOnz+vBx98UNnZ2TIMQ507d9aqVatUp06dMtUbMWKE5s+fL9M0ZZqmfvjhB7300kuFzuvRo4c+/vhjmaZpn7tw4YK+//77Mj9LvgMHDjiMu3TpUuoa3bp1cxgfPHiwXD05Q2xsrJYvX+4w98ADD8hms1nUEQAAAAAAAAAAAAAAKI5lYZDXX3/dvu2Il5eXZs+e7bCtS1ncc889uuuuuyRd3ELmjTfeUGxsbKHzbr/9dvXu3dshEPLTTz+V696SdOjQIYdx06ZNS12j4DUFa1rhk08+UXZ2tsOcM7aIuXDhgtauXasFCxZo7ty5WrJkiTZs2KCkpKRy1wYAAAAAAAAAAAAA4GplSRgkLy9Pc+bMkWEYMgxDPXv2VIcOHZxS+/HHH5d0cQuYzMxMzZ8/v8jzxo4daz9PkjZv3lzuex85csRh3LBhw1LXaNCggcP4zJkzSk1NLVdf5fXxxx87jP/0pz8V2s6mtJ588knVqlVLffv21f33369Ro0ZpyJAh6tmzp6pXr67IyEi9+uqrSkxMLNd9AAAAAAAAAAAAAAC42lgSBtm+fbvi4uLs4379+jmtds+ePeXv728fF7f9y/XXX29/b5qmoqOjy33v+Ph4h3F4eHipaxS1TU7BuhVp165d2rNnj8OcM1YFiYuLc1iZ5VK5ubnauXOn/u///k8NGjTQf/7zn3LfDwAAAAAAAAAAAACAq4UlYZD9+/dLkj0MUL9+fafV9vLysgcqTNO036ug2rVrq3r16vZxeQMX6enpys3NdZgry7Y3lwZZ8qWkpJS5r/L66KOPHMY2m03Dhg2rsPsnJyfriSee0F133aWsrKwKuy8AAAAAAAAAAAAAAJWVjxU3PX/+vGMTPs5tw9vbu9h7Xap69er2EEhSUlK57lnUVi5VqlQpdZ2iwiBWbROTnZ2tzz77zGFu0KBBqlmzZplrtmvXTgMHDlT37t3Vrl07hYWFKSAgQImJiYqKitL69es1d+5c7d692+G6RYsWacyYMYW2rCmt06dPX/b42bNny1UfAAAAAAAAAAAAAACrWRIGKej33393ar1Lt3wxDKPY8/z8/OzvbTZbue6Znp5eaM7X17fUdS7t6XK1K8LSpUsLhWnKukXMrbfeqieeeEIRERFFHq9Ro4Zq1KihTp066YknntBnn32msWPHKjk52X7OvHnzdMMNN2jUqFFl6kGSGjRoUOZrAQAAAAAAAAAAAACoDCzZJiZ/G5f8oMYPP/zgtNo7d+50CBCEh4cXe+6lq4EEBgaW675FrQJSlm1NMjMzS1S7IhTcIiYsLEwDBw4sU62777672CBIUR544AF9//33hVZKeeGFF5SRkVGmHgAAAAAAAAAAAAAAuBpYEgZp1KiR/b1pmvr+++916tQpp9SePXu2/b1hGA73ulReXp5iYmLs48uFRkqiatWqhebKElooahWQomq7WmxsrJYvX+4w9+CDDzp9S5/L6dKli1577TWHudOnT+ubb74pc81Tp05d9rV169bytg0AAAAAAAAAAAAAgKUs2Same/fuCg0NVUJCgqSLK2iMHTtWS5cuvey2LleyceNGzZo1S4ZhyDRNGYZR7EoWR44cUWZmpgzDkGEYaty4cZnvK0n+/v7y9vZWbm6ufS4tLa3UddwlDPLJJ58oOzvbYW7EiBEV3sfYsWP18ssv69y5c/a5lStX6p577ilTvfr16zurNRRhxvsrNGPWCqfXHfdwf417pL/T6wIAAAAAAAAAAACAJ7IkDOLj46MhQ4Zo7ty59uDGihUr9MADD+ijjz6Sn59fqWv+/PPPuv3222Wapn3Oy8tLd999d5Hnb9q0yWHcvn37Ut+zoGrVqunChQv2cXR0dKlrnD17tsi6Fe3jjz92GEdGRjrlMyotm82mfv36ad68efY5Vu9wX8kp6TobHe+SugAAAAAAAAAAAACAkrEkDCJJkydP1pdffqm0tDR7IOTLL7/Utm3b9O9//1tDhgyRzWa7Yp0TJ07ojTfe0AcffKDc3FyHVUHGjRtX7DYx+VuN5J/bvXv3cj9Ty5YtHUImUVFRpa5RcLucunXrVvjKILt27dKePXsc5qxYFSRf27ZtHcaXbu8D9xJU1V91wkOLPZ6XZ+pcTILDXO2wavLyuvyKQEFV/Z3RHgAAAAAAAAAAAABcFSwLgzRo0EAvv/yyxo8fb9+qxTRNHT9+XPfdd59CQ0PVt29fRUREqHHjxgoJCZGvr6+Sk5N14cIF7du3T5s3b7avEpEf6sjXqFEjvfzyy0Xe+8KFC1q5cqX9nr6+vrrhhhvK/UytW7d2CIMcP3681DVOnDhRqGZF++ijjxzGvr6+euCBByq8j3zVq1d3GMfHO3/lCTjHuEcuv53L+QtJatPhcYe5dd+/pJo1gl3dGgAAAAAAAAAAAABcNSwLg0jSE088oXPnzunf//63PRAiXQx2xMXFaeHChVq4cOFla+RvC3PptQ0aNNCaNWsUFBRU5DUzZ85URkaGfdy3b99izy2NgitYlGU7k82bNzuM27RpU66eSis7O1ufffaZw9ztt99eKJBRkRISEhzGISEh1jQCAAAAAAAAAAAAAEAl4GV1Ay+//LKmTZsmPz8/h2BH/qodV3oVDJF0795dP/74o5o0aVLsPR999FGdPXvW/vriiy+c8ix9+vRxGJ87d06//vpria9PS0vT7t27Heb69u3rjNZKbOnSpTp//rzDnJVbxEjS0aNHHcZhYWEWdQIAAAAAAAAAAAAAgPuzPAwiSePHj9fu3bvVr18/SSoU9LjcK//csLAwTZs2TT///LMaN2582ftVq1ZNtWvXtr8CAwOd8hwRERFq0KCBw1xpgiaLFi1Sdna2fVylShXdcsstTumtpApuEVOnTh3171/8th+ulpeXp5UrVzrMdejQwaJuAAAAAAAAAAAAAABwf24RBpGkli1bavny5Tpy5IieeeYZde7cWT4+PpddFaR27doaMmSIPv/8c0VFRWn8+PH2VUKsMmTIEIfxhx9+qJycnBJdO2vWLIfxzTff7LSgSknExsZq+fLlDnMPPvigvL29K6yHgubPn6+oqCiHOSvDKQAAAAAAAAAAAAAAuDsfqxsoqFmzZnr11VclSZmZmTp06JAuXLig+Ph4ZWZmKiQkRKGhoWrQoEGhVTjcwYQJEzRz5kz7Ch8nTpzQ1KlTNWnSpMtet2DBAq1fv95h7krXSCoUfhk+fHih1T1K6pNPPnFYmURyzhYx+au8lNahQ4c0YcIEh7nQ0FANHjy43D0BAAAAAAAAAAAAAOCp3C4Mcik/Pz/96U9/srqNUmnSpIlGjx6tmTNn2ucmT56sdu3aadCgQUVes3XrVj366KMOcwMGDNB1113n0l4L+vjjjx3GXbp0Udu2bctdt3379nr22Wc1dOhQ2Wy2El2zdu1aDRs2TPHx8Q7zzz77rKpVq1bungAAAAAAAAAAAAAA8FRus02MJ5kyZYrCw8Pt44yMDA0ZMkQTJkzQ0aNHZZqmJCkqKkovvPCC+vTpo7i4OPv5gYGBmjZtWoX2vGvXLu3Zs8dhzhmrgkjS/v37NWzYMIWHh2v06NH67LPPdODAAWVlZTmcFx0dra+++koDBw5U3759FR0d7XD85ptv1t/+9jen9AQAAAAAAAAAAAAAgKdy65VBKquwsDAtWrRIN998s1JTUyVJOTk5euutt/TWW2/Jz89PXl5eSk9PL3Stt7e35s+fr9atW1dozwW3lqlSpYruv/9+p94jLi5Oc+bM0Zw5c+xz/v7+CggIUHJycqFwyKV69eqlRYsWlXhlEQAAAAAAAAAAAAAArlasDOIi3bt319q1a1W/fv1CxzIzM4sMgoSGhmrJkiW64447KqJFu+zsbH322WcOc4MHD66Q7VjS09N14cKFYoMgvr6+mjJlin744QdVrVrV5f0AAAAAAAAAAAAAAFDZEQZxoS5duujAgQN6/vnnVatWrWLPCw4O1uOPP65Dhw5p0KBBFdjhRcuWLdP58+cd5py1RYwkff7553r44YfVtm1beXt7l+iaVq1aacqUKYqKitI///nPEl8HAAAAAAAAAAAAAMDVjm1iXCwoKEgvvfSSJk+erB07dmjv3r2KjY2VaZqqUaOG2rZtq65du8rX17dM9U3TLHePQ4YMcUqd4tx333267777JF1cCeTgwYOKiorS77//rpSUFGVmZqpq1aoKDQ1VnTp1dO2116p69eou6wcAAAAAAAAAAAAAAE9GGKSCeHt7q0uXLurSpYvVrVjK399fERERioiIsLoVAAAAAAAAAAAAAAA8EtvEAAAAAAAAAAAAAAAAeBDCIAAAAAAAAAAAAAAAAB6EMAgAAAAAAAAAAAAAAIAHIQwCAAAAAAAAAAAAAADgQQiDAAAAAAAAAAAAAAAAeBDCIAAAAAAAAAAAAAAAAB6EMAgAAAAAAAAAAAAAAIAHIQwCAAAAAAAAAAAAAADgQQiDAAAAAAAAAAAAAAAAeBDCIAAAAAAAAAAAAAAAAB6EMAgAAAAAAAAAAAAAAIAHIQwCAAAAAAAAAAAAAADgQQiDAAAAAAAAAAAAAAAAeBAfqxsoTnZ2tg4ePKjz58/rwoULSk9PlyQ99NBDFncGAAAAAAAAAAAAAADgvtwqDJKRkaHZs2fr66+/1ubNm5WRkVHonMuFQdasWaPExET7uEOHDmrevLlLegUAAAAAAAAAAAAAAHBHbhMGmTFjhiZPnqzz589LkkzTLHSOYRiXrbFu3Tq98sor9vFtt92mxYsXO7VPAAAAAAAAAAAAAAAAd2Z5GCQ9PV2jR4/WF198YQ+AGIZRKPhRVDikoCeeeEJTp05VZmamTNPU8uXLdf78edWsWdMlvQMAAAAAAAAAAM8y4/0VmjFrRbHH8/IKf19x403/kJfX5X+hddzD/TXukf7l7g8AAKAkLA2DmKap+++/X99++61M07QHQAoGP660Iki+WrVq6a677tKnn34qScrJydHixYv1l7/8xbmNAwAAAAAAAAAAj5Sckq6z0fGluuZcTEKJ6gIAAFQUS8MgkydP1jfffGNfCcQ0Tfn6+urPf/6zBg0apCZNmuiBBx7QoUOHSlxz6NCh+vTTT+0BktWrVxMGAQAAAAAAAAAAJRJU1V91wkNdUhcAAKCiGGZJ9l9xgTNnzqh58+bKysqSdHE1kA4dOujrr79WkyZN7Od16tRJe/futa8ckpube9m6mZmZql69ujIyMmSapmrWrKmYmBiXPgs8x+nTp9WgQQNJ0qlTp1S/fn2LO6pcph3bq2nH9hZ7PC8vT+fOJTjM1a5dTV5eXpetO7FZB01s1sEZLQIAAAAAAAAAAACAW3HF99SWrQzy6quvKjMz074iSPPmzbV+/XoFBQWVq66fn586duyoTZs2SZIuXLigs2fPqk6dOs5oG8BlJGVn6UxG6uVPCvVzGJ7NuvLSiEnZWeVpCwAAAAAAAAAAAACuKpaFQb7++mt7EMQwDM2ePbvcQZB8kZGR9jCIJB06dIgwCFABgm2+qlclsNjjObm5Oped4TBX21ZFPt7eV6wLAAAAAAAAAAAAACgZS8IgBw8e1O+//y7DMCRJERERuv76651Wv2nTpg7jqKgop9UGULwrbedyKDpGbbZ+7TC3rtMAtQ4Pc3VrAAAAAAAAAAAAAHDV8LLipgcOHLC/NwxDN998s1PrV6tWzWGclJTk1PoAAAAAAAAAAAAAAADuypKVQWJjYyXJvkVMixYtnFo/f7uZ/JVHUlJSnFofAAAAAAAAAAB4pmnH9mrasb1Or3ullZUBAACcyZIwSHx8vMM4JCTEqfXzwx/5YZMqVao4tT4AAAAAAAAAAPBMSdlZOpOR6pK6AAAAFcWSMEhwcLDDODk52an181ceyVejRg2n1gcAAAAAAAAAAJ4p2OarelUCiz2eZ5o6m5nmMFfHL0Be/3+18svVBQAAqCiWhEHCwsIk/bGNy9mzZ51af8eOHQ7jmjVrOrU+AAAAAAAAAADwTFfaziU2M11hK+c5zO258W7V8vN3dWsAAAAl5mXFTevVq+cw3rZtm9Nq5+bmat26dfagiSR16MAefAAAAAAAAAAAAAAA4OpgSRjk2muvVWDgxSXWTNPU6tWrlZKS4pTaX3zxhc6dO2cfN2nSRPXr13dKbQAAAAAAAAAAAAAAAHdnSRjEZrPpxhtvlGmakqTU1FTNmDGj3HWTkpL0wgsvyDAMmaYpwzB00003lbsuAAAAAAAAAAAAAABAZWFJGESShg8fLkn24MaUKVN04MCBMtfLzs7Wgw8+qGPHjjnMP/bYY+XqEwAAAAAAAAAAAAAAoDKxLAxy9913q1OnTpIuBkLS0tLUt29fbdu2rdS1jh07puuvv17Lli1zWBXk1ltvVbt27ZzdOgAAAAAAAAAAAAAAgNvysfLmb731lvr27avc3FwZhqFz586pR48eGj58uP7yl7+oc+fOxV577tw5/fTTT1q0aJH+97//KTc31x4CkaSgoCBNnTq1oh4FAAAAAAAAAJxq2rG9mnZsr9PrTmzWQRObdXB6XQAAAADuw9IwSK9evfTf//5XY8eOlWEYMgxDubm5mjt3rubOnSubzSZJMk3Tfk3dunUVHx+vrKws+1z+8UtXBZk7d66aN29esQ8EAAAAAAAAAE6SlJ2lMxmpLqkLAAAAwLNZGgaRpIcfflhxcXF6/vnn7UGO/HDHpYEP6WLoIzo6ulCN/NVATNOUj4+P3nnnHd1xxx2ubx4AAAAAAAAAXCTY5qt6VQKLPZ5nmjqbmeYwV8cvQF7//+ell6sLAAAAwLNZHgaRpL///e+69tpr9eCDD+rcuXP2cEdpmKapmjVrasGCBerTp48LugQAAAAAAACAinOl7VxiM9MVtnKew9yeG+9WLT9/V7cGAAAAwM15Wd1Avr59++ro0aN67bXXVKdOHZmmaX8V5dLjwcHBmjx5so4dO0YQBAAAAAAAAAAAAAAAXNXcYmWQfFWrVtXTTz+t8ePHa+PGjfrxxx+1YcMGnT59WhcuXFB8fLz8/f1Vs2ZN1a5dW127dtXNN9+sG264QQEBAVa3DwAAAAAAAAAAAAAAYDm3CoPks9lsuuGGG3TDDTdY3QoAAAAAAAAAAAAAAECl4pZhEAAAAMDdTTu2V9OO7XV63SvtCw8AAAAAAAAAwJUQBgEAAADKICk7S2cyUl1SFwAAAAAAAACA8iAMAgAAAJRBsM1X9aoEFns8zzR1NjPNYa6OX4C8DOOKdQEAAAAAAAAAKA/CIAAAAEAZXGk7l9jMdIWtnOcwt+fGu1XLz9/VrQEAAAAAAAAArnJeVjcAAAAAAAAAAAAAAAAA5yEMAgAAAAAAAAAAAAAA4EEs2yamT58+FXIfwzC0Zs2aCrkXgKJlZeVo+cqd+nr5VlXbsV/e5zKknDzJx0sjm5xQl4gW6nNjew3oFyFfX3avAgAAAAAAAAAAAIDysOxb13Xr1skwDJfewzRNl98DQPGys3M084OVmjFrpWJjEyUV+EMnO0+/HjmrX4+c1WcLflJYWIjGjumnsWP6yWYjFAIAAAAAAAAAAAAAZVGpt4kxTbPIFwDrHTp8Wv1ve1EvvvylPQhyJTExiXrx5S/V/7YXdejwaRd3CAAAAAAAAAAAAACeyfIwSHGBjiu9pItbwBR8XVoTgDW2bjuqgYP/pb37fivT9Xv3/aaBg/+lrduOOrkzAAAAAAAAAAAAAPB8lu3DcP3115d5C5fs7GxduHBBUVFRSk9Pl3QxGGKapvz9/dWlSxdntgqgFA4dPq37/jxVycnp5aqTnJyu+/48Vcu/+YdataznpO4AAAAAAAAAAAAAwPNZFgZZt25duWvk5ORo69atev/997VgwQLl5OQoIyNDdevW1Zw5c+Tn51f+RgGUWHZ2jh7926xyB0HyJSen669PvK8V3/5TNptlf1wBAAAAAAAAAAAAQKVi+TYx5eHj46MePXro448/1saNG9WkSROZpqkFCxZowIABysrKsrpF4Koy84OVZd4apjh79/2mmR+sdGpNAAAAAAAAAAAAAPBklToMcqnIyEitXbtWderUkWma+vHHH/Xwww9b3RZw1cjKynFZaGPmByuVnZ3jktoAAAAAAAAAAAAA4Gk8JgwiSQ0bNtR//vMfSZJpmpo/f75WrVplcVfA1WH5yp2KiUl0Se2YmER9t2KnS2oDAAAAAAAAAAAAgKfxqDCIJN15551q1aqVDMOQaZp65ZVXrG4JuCqsXbfPpfV/+NG19QEAAAAAAAAAAADAU3hcGESSBg4cKNM0JUk///yzzp07Z3FHgOfbs/dkpa4PAAAAAAAAAAAAAJ7CI8MgrVu3tr83TVObN2+2sBvg6nDs+FnX1j8W7dL6AAAAAAAAAAAAAOApPDIMEhoaKkkyDEOSdOLECSvbAa4KmZk5Lq2fkZnt0voAAAAAAAAAAAAA4Ck8MgySnJzsME5LS7OoE+Dq4efn49L6VfxsLq0PAAAAAAAAAAAAAJ7CI8Mgu3fvlnRxixhJqlatmnXNAFeJZk3ruLZ+s3CX1gcAAAAAAAAAAAAAT+FxYZD09HQtXLjQvkWMJIWFhVnYEXB1+FOHxpW6PgAAAAAAAAAAAAB4Co8Lg/ztb3/T2bNnHea6d+9uUTfA1aPPje1dWr/3Da6tDwAAAAAAAAAAAACewmPCIL/++quGDBmiDz/8UIZhyDRNGYahDh06qF69ela3B3i8Af0iFBYW4pLaYWEhGtg/wiW1AQAAAAAAAAAAAMDT+Fh143nz5pXr+uzsbCUlJen48ePaunWrtm/fLkn2EEi+5557rlz3AVAyvr4+Gjumn158+Uun1x47pp9sNsv+uAIAAIAbmXZsr6Yd2+v0uhObddDEZh2cXhcAAAAAAACwgmXfro4YMcIhtFEepmna3+fXNAxDt956q+6++26n3APAlY0d00+Lv9mivft+c1rNP3VorHEP93daPQAAAFRuSdlZOpOR6pK6AAAAAAAAgKew/FftLw1ylFXBUIlpmurbt6++/NL5KxQAKJ7N5qN3335YAwf/S8nJ6eWuFxwcoHffflg+Pt5O6A4AAACeINjmq3pVAos9nmeaOpuZ5jBXxy9AXlf4ZYRgm69T+gMAAAAAAADcgeVhEGevDhIaGqoXXnhBjz/+uNNqAyi51q3qa8H8J3Xfn6eWKxASHBygz+dNVKuW9ZzYHQAAACq7K23nEpuZrrCVjtuS7rnxbtXy83d1awAAAAAAAIDbsDQM4oxVQby9vdWyZUtFRkZqwIABuvPOO+Xn5+eE7gCUVZdrW2j5N//QX594v0xbxnRo30jvvfMIQRAAAAAAAAAAAAAAKAPLwiAnTpwo1/U2m03BwcGqWrWqkzoC4EytWtbTim//qZkfrNTMD1YqJibxiteEhYVo7Jh+Gjumn2w2yxcuAgAAAAAAAAAAAIBKybJvWxs1amTVrQFUEJvNR4//dZDGjumn71bs1NfLt2nJjv3yPpcu5eRJPl5q2TRcXSKaq/cN7TWwfwQhEAAAAAAAAAAAAAAoJ751BeByNpuPBt/WRa2ubayPtzr+sTOnyx1qHR5mUWcAAAAAAAAAAAAA4Hm8rG4AAAAAAAAAAAAAAAAAzmPJyiAHDhzQwoUL7WPDMDRp0iT5+vpa0Q4AAAAAAAAAAAAAAIDHsCQM8sMPP2jy5MkyDEOS1K1bN/3jH/+wohUAAAAAAAAAAAAAAACPYsk2MYmJiZIk0zQlSQMGDLCiDQAAAAAAAAAAAAAAAI9jSRjEx8dxQZL69etb0QYAAAAAAAAAAAAAAIDHsSQMUr16dYexv7+/FW0AAAAAAAAAAAAAAAB4HEvCIC1atJAkGYYhSYqJibGiDQAAAAAAAAAAAAAAAI9jSRikS5cu8vPzs4+3bdtmRRsAAAAAAAAAAAAAAAAex5IwiL+/vwYMGCDTNGWappYvX6709HQrWgEAAAAAAAAAAAAAAPAoloRBJOnpp5+WYRgyDENxcXF6/fXXrWoFAAAAAAAAAAAAAADAY1gWBunevbvGjRsn0zQlSS+//LIWLVpkVTsAAAAAAAAAAAAAAAAewbIwiCS99dZbuu2222SapnJycnTvvfdq0qRJSktLs7ItAAAAAAAAAAAAAACASsvSMIiPj4++/vprPfvss/L29lZubq7efPNN1a1bV4888ogWLFigI0eOKCEhQXl5eVa2CgAAAAAAAAAAAAAAUCn4WHVjb2/vQnOGYcg0TSUlJWn27NmaPXt2ue9jGIZycnLKXQcAAAAAAAAAAAAAAKAysCwMYppmoTnDMGQYRrHHAQAAAAAAAAAAAAAAcHmWhUEk2YMfpT1WUgRKAAAAAAAAAAAAAADA1cbSMAhhDQAAAAAAAAAAAAAAAOeyLAzywgsvWHVrABUsKy9XS6JPasHJo4WO3fHLWvWIDlf/sAYaHN5Yvl7eFnQIAAAAAAAAAAAAAJ6DMAgAl8nOy9X0Y/s07fhenctML/KcQ2mJOhSVqDlRhxXuF6AJTdtrQrP2shEKAQAAAAAAAAAAAIAy8bK6AQCeaX9SnLqtX6xJB7cUGwQpKDozTZMOblG39Yu1PynOxR0CAAAAAAAAAAAAgGciDALA6TbGRavHz0u0M/F8ma7fmXhePX5eoo1x0U7uDAAAAAAAAAAAAAA8H2EQAE61PylOAzYvV1JOVrnqJOVkacDm5TqQHO+kzgAAAAAAAAAAAADg6kAYBIDTZOfl6qFdP5Q7CJIvKSdLf965Vtl5uU6pBwAAAAAAAAAAAABXAx+rbtynTx/7++bNm2vWrFlOq/3www/r119/lSQZhqE1a9Y4rTaA4k0/tq/MW8MUZ2fieU0/tk/PtOjo1LoAAAAAAAAAAAAA4KksC4OsW7dOhmFIkhISEpxae9u2bdq7d69M07TfA4BrZeXlavrxfS6pPf34Pk1o1l42L2+X1AcAAAAAAAAAAAAAT2L5NjGmaVrdAgAnWBJ9UtGZaS6pHZ2ZpsXRJ11SGwAAAAAAAAAAAAA8jeVhEACeYUXMKZfWXxlz2qX1AQAAAAAAAAAAAMBTEAYB4BQ7Es67uH6sS+sDAAAAAAAAAAAAgKfwyDBIdna2/b2vr6+FnQBXj8MpCa6tn5ro0voAAAAAAAAAAAAA4Ck8MgwSG/vHCgJBQUEWdgJcPTLzcl1aPyM3x6X1AQAAAAAAAAAAAMBTeFwYJCoqyiEMUrNmTQu7Aa4efl7eLq1fxdvHpfUBAAAAAAAAAAAAwFN4XBhkypQp9veGYeiaa66xsBvg6tGqajXX1g8McWl9AAAAAAAAAAAAAPAULvtV+3nz5pX43Li4uFKdf6nc3FylpKTo+PHjWrlypQ4fPizDMGSapgzDULdu3cpUF0DpRFarqT1JF1xYv5bLagMAAAAAAAAAAACAJ3FZGGTEiBEyDOOy55imKUk6deqURo4cWe575tfLv6+Xl5fuu+++ctcFcGX9wxpoTtRhl9XvF1bfZbUBAAAAAAAAAAAAwJO4LAySLz+gUd5zSuLS8IlhGHr88cdVr149p9QGcHmDwxsr3C9A0ZlpTq8d7hegIeGNnV4XAAAAAAAAAAAAADyRl6tvYBhGka+SnFPal3QxWGKapoYPH67XXnvN1Y8H4P/z9fLWhKbtXVJ7QtP2snl5u6Q2AAAAAAAAAAAAAHgal64MUtIVP5yxMoiPj4/atm2rHj16aNSoUercuXO5awIonQnN2uuL349pZ+J5p9WMDKmpic06OK0eAAAAAAAAAAAAAHg6l4VBTpw4Uewx0zTVtGlTGYYh0zTVtm1bLVu2rEz38fHxUVBQkIKCggqtOAKgYtm8vDWvU2/1+HmJknKyyl0vxMdX8yL6yMfL5YsYAQAAAAAAAAAAAIDHcFkYpFGjRiU6zzAM+fr6lvh8AO7tmuDqWt5tgAZsXl6uQEiIj6++6zZAbYNCndgdAAAAAAAAAAAAAHg+S3/d3jRNp2wRA8C99Kgerk29higipGaZro8IqamNvYaoR/VwJ3cGAAAAAAAAAAAAAJ7PZSuDXMncuXPt76tXr25VGwBcpG1QqDb3GqLpx/Zp+vF9is5Mu+I14X4BmtC0vSY0ay+bl3cFdAkAAAAAAAAAAAAAnseyMMjw4cOtujWACmLz8tYzLTpqQrP2Whx9Ul/8dlT/i/3N4Zw2ASHqUbOO+oXV15DwxoRAAAAAAAAAAAAAAKCcLAuDALh62Ly8NbRuM7X3CioUBlnUro9ah4dZ1BkAAAAAAAAAAAAAeB4vqxsAAAAAAAAAAAAAAACA8xAGAQAAAAAAAAAAAAAA8CBsE1NB8vLytGPHDu3bt08xMTEyTVM1atRQ27Zt1bVrV9lsNqtbtERiYqI2btyoX3/9VUlJSfLz81O9evXUuXNntWjRwur2AAAAAAAAALeTlZerJdEntfjsyULHem/8Vl1Dw9Q/rIEGhzeWr5d3xTcIAAAAwHKEQVwsJSVFb7zxhmbOnKmYmJgizwkJCdGIESP03HPPqVatWhXSV+PGjfXbb7+Vq8bcuXM1YsSIMl27Z88evfjii/r222+VnZ1d5DnXXHONnnrqKQ0fPlyGYZSjUwAAAAAAAKDyy87L1fRj+zTt+F6dy0wv8pz9yfHanxyvOVGHFe4XoAlN22tCs/ayEQoBAAAAripsE+NC27dv1zXXXKMXX3yx2CCIdHF1jLffflutW7fWihUrKrBDa/z73/9W586dtWjRomKDIJK0f/9+jRw5Un369FFsbGwFdggAAAAAAAC4l/1Jceq2frEmHdxSbBCkoOjMNE06uEXd1i/W/qQ4F3cIAAAAwJ0QBnGRLVu2qHfv3oqKiip0zM/PT/7+/oXm4+LidNttt+mbb76piBYt8fTTT+vZZ59VTk5OoWNBQUHy8ir8j+S6det0ww036MKFCxXRIgAAAAAAAOBWNsZFq8fPS7Qz8XyZrt+ZeF49fl6ijXHRTu4MAAAAgLtimxgXiI2N1R133KGUlBT7nI+Pjx577DGNGzdOzZs3l2EYioqK0ocffqhp06YpNTVVkpSTk6Nhw4Zpx44datmyZYX1/K9//Us1atQo1TXdu3cv1fmffvqp3nzzTYe5xo0b67nnntPdd9+tatWqKSsrS1u3btWrr76qZcuW2c87ePCgHnjgAa1YsYItYwAAAAAAAHDV2J8UpwGblyspJ6tcdZJysjRg83Jt6jVEbYNCndQdAAAAAHdFGMQFJk+erLNnz9rHfn5+WrhwoW699VaH8xo1aqQXX3xRgwYN0oABAxQfHy9JSklJ0cSJE7V06dIK63nYsGFq3Lixy+qnpqZq4sSJDnOdOnXSypUrVatWLfucr6+vevbsqaVLl+q5557TK6+8Yj+2atUq/e9//9Pdd9/tsj4BAAAAAAAAd5Gdl6uHdv1Q7iBIvqScLP1551pt7jVENi9vp9QEAAAA4J7YJsbJTp48qdmzZzvMTZkypVAQ5FJdu3bVu+++6zC3bNkybdq0ySU9WuHtt99WTEyMfRwQEKCFCxc6BEEKevnll9WvXz+HuX/+85/Ky8tzWZ8AAAAAAACAu5h+bF+Zt4Ypzs7E85p+bJ9TawIAAABwP4RBnGz69OnKyvojqd+kSRM9+eSTV7zu/vvvV8+ePR3mXnvtNaf3Z4WcnBxNmzbNYe7JJ59U06ZNr3jtf//7X4dtYQ4ePKhvvvnG6T0CAAAAAAAA7iQrL1fTj7smtDH9+D5l5+W6pDYAAAAA98A2MU62ePFih/Ho0aPl41Oyj/nhhx/Wzz//bB+vWrVKaWlpCggIcGaLFe6nn37ShQsX7GMvLy+NGTOmRNc2b95cvXv31tq1a+1zX3/9tYYMGeLsNgEAAAAAAAC3sST6pKIz01xSOzozTYujT2po3WYuqQ8AQFnMeH+FZsxa4fS64x7ur3GP9Hd6XQBwd4RBnGjXrl2KiopymLv33ntLfP1dd92lUaNGKScnR5KUnp6uVatWVfrgw5IlSxzG3bt3V4MGDUp8/X333ecQBlm2bJlyc3Pl7c2+pgAAAAAAAPBMK2JOubT+ypjThEEAAG4lOSVdZ6PjXVIXAK5GhEGc6NLAgiTVrl1bzZs3L/H1AQEB6tixo7Zv326fW7NmTaUPgxT8XK677rpSXd+jRw+H8YULF7R7925FRkaWuzcAAAAAAADAHe1IOO/i+rEurQ8AQGkFVfVXnfDQYo/n5Zk6F5PgMFc7rJq8vIwr1gWAqxFhECc6cOCAw7hLly6lrtGtWzeHMMjBgwfL3ZeVcnNzdeTIEYe5rl27lqpG27ZtFRwcrKSkJPvcwYMHCYMAAAAAAADAYx1OSXBt/dREl9YHAKC0xj1y+e1czl9IUpsOjzvMrfv+JdWsEezq1gCgUiIM4kSHDh1yGDdt2rTUNQpeU7CmK508eVIHDx5UbGysDMNQjRo1VLt2bXXo0EE2m61MNY8fP66srCyHudJ+LoZhqHHjxtq7d699riI/FwAAAAAAAKCiZeblurR+Rm6OS+sDAAAAsBZhECcquAJGw4YNS12jQYMGDuMzZ84oNTVVgYGB5ertSjp37qwLFy4Ueczf31/du3fX6NGjdc8998jHp+T/2BT8TKSyfy6XhkEOHz5c6hoAAAAAAABAZeHn5a0MFwZCqnjzo2EAAADAk/E3fieKj493GIeHh5e6Rp06dYqs6+owSHFBEElKT0/X2rVrtXbtWj333HOaM2eOevfuXaK6cXFxDmObzabq1auXur+Cn0vBzxruYcb7KzRj1opij2dV8ZImtXKYG3LXq/LNyLts3XEPX35pOAAAAAAAAE/Tqmo17Ukq/md25a4fGOKy2gAAAACs55ZhkFOnTmndunXatWuXzp8/rwsXLig9PV2GYWjNmjVWt1ek9PR05eY6JvUDAgJKXcff37/QXEpKSpn7craTJ0/qpptu0r///W8988wzVzy/YO9l+Uykwp+LO30m+ENySrrORhcf1MmrWviPnNjzifJKufyypMkp6eXuDQAAAAAAoDKJrFbTpWGQyGq1XFYbAAAAgPXcKgyycOFCvfrqq9q1a1ehY6ZpyjCMy17//PPPO2wlcu+992rYsGFO77MoqampheaqVKlS6jpFhUGKqu0M3t7euu666zRgwABFRkaqTZs2Cg0Nlc1mU1xcnA4dOqQffvhBH3zwgc6ePWu/Li8vT5MmTVKNGjU0evToy96jYO9l+Uykwp9LWT+T06dPX/b4pc+J0guq6q864aHFHs8J8FZcgbnaYdXkU/XyS54GVS387wUAAO4qKy9XS6JPavHZk4WO9d74rbqGhql/WAMNDm8sXy/vim8QAAAAlUL/sAaaE+W6rZL7hdV3WW0AAAAA1nOLMMiZM2d09913a+vWrZIuBj8udaUQSL527drplVdesZ9//PjxCguDpKcXXrnA19e31HX8/PxKVLu8nnnmGQ0ePFj16tUr8nh4eLjCw8N144036rnnntM//vEPvfHGGw7/3YwdO1Y9e/ZUq1atiqxRVO9l+Uykwp9LWT+TBg0alOk6lMy4Ry6/nUtsZrrCVs5zmPvx+3+plh9hDwBA5Zedl6vpx/Zp2vG9OpdZ9N9V9ifHa39yvOZEHVa4X4AmNG2vCc3ay0YoBAAAAAUMDm+scL8ARWemOb12uF+AhoQ3dnpdAAAAAO7Dy+oGtm/froiICG3dutUeNDAMw+FVUvfcc48aN24s6WKg5ODBg9q5c6cr2i6kqBUvsrKySl0nMzOzRLXL669//WuxQZCCfH199dprr+mdd95xmM/JydFzzz132WsL9l6Wz0Qq/Lm44jMBAAAoq/1Jceq2frEmHdxSbBCkoOjMNE06uEXd1i/W/qSCa2cBAADgaufr5a0JTdu7pPaEpgSSAQAAAE9naRjkzJkzuv322xUbG2vfBsY0TZmmqZCQEHXo0EEBAQElrufl5aVhw4Y5rF6xdOlSV7ReSNWqVQvNZWRklLpOUSteFFXbCo899pjuvPNOh7lFixbp3LlzxV5TsPeyfCZS4c+lrJ/JqVOnLvvKX50GAACgpDbGRavHz0u0M/F8ma7fmXhePX5eoo1x0U7uDAAAAJXdhGbtFRFS06k1I0NqamKzDk6tCQAAAMD9WBoGefDBBxUdHW1fAcQ0Td18881av369Lly4oF27dql58+alqjl06FBJf2wt8/333zu976L4+/vL29sxTZ+WVvolHN05DCJJL7zwgsPYNE2tWrWq2PML9l7W7V2cFQapX7/+ZV916tQpU10AAHB12p8UpwGblyspp2yrn+VLysnSgM3LdSA53kmdAQAAwBPYvLw1r1NvBfuUbevlgkJ8fDUvoo98vCxfMBoAAACAi1n2t/7Vq1frxx9/tIdAJGny5MlauXKlrrvuulJtD3OpDh062L/QN01TW7duVW5urtP6vpxq1ao5jKOjS//bnWfPnr1iXSt16NBBDRs2dJi73GoaoaGhDuOsrCzFxZV+GfSCn0vBugAAABUtOy9XD+36odxBkHxJOVn68861ys6rmL+7AgAAoHK4Jri6lncbUO5ASIiPr77rNkBtg/i5GgAAAHA1sCwMMm3aNEmybw/z4IMP6p///KdTakdGRtoDJtnZ2Tp69KhT6l5Jy5YtHcZRUVGlrnHq1CmHcd26dd1qZRBJatu2rcM4Jiam2HMLfiaScz6XouoCAABUpOnH9pV5a5ji7Ew8r+nH9jm1JgAAACq/HtXDtanXkDJvGRMRUlMbew1Rj+rhTu4MAAAAgLuyJAySmZlpXxVEkvz8/PTGG284rX7Hjh0dxocPH3Za7ctp3bq1w/j48eOlrnHixInL1nQH1atXdxjHxxe/nHnTpk3l6+v4Wwul/VxM09TJkycd5tzxcwEAAFePrLxcTT/umtDG9OP7WB0EAAAAhbQNCtXmXkP0WpuuCvcLKNE14X4Beq1NV23uNYQVQQAAAICrjCVhkC1btigjI0OSZBiGBgwYoLCwMKfVDw93TLhfbuUKZyq4Ysbltk8pzubNmx3Gbdq0KVdPrpCQkOAwDgkJKfZcHx8ftWjRwmFuy5YtpbrfwYMHlZSU5DDnjp8LAAC4eiyJPqnozDSX1I7OTNPi6JMuqQ0AAIDKzeblrWdadFTUzQ/oy843aVi95oXOaRcUqtENW+vLzjcp6uYH9EyLjrJ5eVvQLQAAAAArWRIGKbjlR48ePZxav1q1apJkX3kkOTnZqfWL06dPH4fxuXPn9Ouvv5b4+rS0NO3evdthrm/fvs5ozakKbrtzpSBPwc9lw4YNpbpfwfOrV69eaPUXAACAirQi5tSVTyqHlTGnXVofAAAAlZvNy1tD6zbT9HaFf666tsdtmt3xBg2t24wQCAAAAHAVsyQMEhsbK+ni9h+SVKdOHafWL7gtSf4qJK4WERGhBg0aOMx98cUXJb5+0aJFys7Oto+rVKmiW265xWn9OcOvv/5aKAzSoUOHy14zePBgh/GmTZsKBYIup+BnOGjQIPn4+JT4egAAAGfbkXDexfVjXVofAAAAAAAAAODZLAmDZGZmOowLhjfKKy4uTtIfYZP8lUIqwpAhQxzGH374oXJyckp07axZsxzGN998swIDA53VmlO8/PLLheb69+9/2WtuuOEGVa9e3T7Oy8vTBx98UKL7/frrr1q7dq3DXMHPGAAAoKIdTklwbf3URJfWBwAAAAAAAAB4NkvCIDVr1nQYx8fHO7X+6dOOy2rXqFHDqfUvZ8KECbLZbPbxiRMnNHXq1Ctet2DBAq1fv95hbtKkSVe8zjAMh9eIESMue35+QKYsFixYoI8//thh7sYbb1SjRo0ue52Pj48mTJjgMDd16lSdOHHiivd87LHHHHpu1apVoZVGAAAAKlpmXq5L62fklixMDAAAAAAAAABAUSwJg4SFhUm6GGSQpMOHDzu1fsFQhbO3obmcJk2aaPTo0Q5zkydP1rJly4q9ZuvWrXr00Ucd5gYMGKDrrrvO6f399NNPGjhwYKHP6ErefvttPfTQQw7BDMMw9Prrr5fo+vHjx6tWrVr2cVpamu6++277lkFFef7557Vy5UqHuRdffFHe3ux1CgAArOXn4r3Xq3izJR4AAAAAAAAAoOwsCYO0atXKYbxhwwan1T537pw2bdpkD5r4+Pjo2muvdVr9kpgyZYrCw8Pt44yMDA0ZMkQTJkzQ0aNH7YGKqKgovfDCC+rTp499axtJCgwM1LRp01zSm2maWr58ua6//no1a9ZMkyZN0jfffKOoqCjl5eU5nHfkyBHNnDlT7du31/jx45Wdne1Q64UXXijxZ1u1alW9+eabDnM7d+5Uly5dNGfOHCUkJEiSsrKytGHDBt12222FtqS56aabNHTo0DI8NQAAgHO1qlrNtfUDQ1xaHwAAAAAAAADg2Sz5lcOWLVuqUaNGioqKkmma2rp1q44cOaKWLVuWu/b06dOVlZVl3zbl2muvlb+/vxO6LrmwsDAtWrRIN998s1JTUyVJOTk5euutt/TWW2/Jz89PXl5eSk9PL3Stt7e35s+fr9atW7u8z+PHjzus7GEYhqpWrSqbzaaEhASHcEhB48eP1wsvvFCq+z300EPavXu3pk+fbp87efKkRo8erdGjRys4OFgpKSlF3rdVq1b67LPP7CEfAAAAK0VWq6k9SRdcWL/WlU8CAAAAAAAAAKAYlqwMIkn9+/eXaZr2L/efeeaZctfctGmTpk+fLsMw7KtvDB48uNx1y6J79+5au3at6tevX+hYZmZmkUGQ0NBQLVmyRHfccUdFtFiIaZpKTk5WXFxcsUGQWrVqadGiRQ6BjtKYNm2aXnrppSK3eklKSiryvr169dKPP/7osM0MAACAlfqHNXBp/X5hhf8OCQAAAAAAAABASVkWBpk4caI9EGCapr799ltNnTq1zPW2bdumO++802Erk5CQEI0dO7bcvZZVly5ddODAAT3//POXDTIEBwfr8ccf16FDhzRo0CCX9tSxY0e99957uueee9SgQcm+xLDZbOrevbtmz56t3377rdxhleeff17bt2/XkCFD5ONT/OI0bdu21Ycffqh169apdu3a5bonAACAMw0Ob6xwvwCX1A73C9CQ8MYuqQ0AAAAAAAAAuDpYsk2MJLVo0ULDhw/XnDlz7Ct5PPPMM/rtt9/073//W4GBgSWqEx8fr7fffluvvfaaMjMz7bUMw9ATTzyhoKAgFz/J5QUFBemll17S5MmTtWPHDu3du1exsbEyTVM1atRQ27Zt1bVrV/n6+papfv4KKCVVrVo1jRs3TuPGjZMkxcXF6dChQzp16pTOnTun1NRU5eXlKTg4WKGhoWrSpIkiIyNVpUqVMvVXnI4dO+rrr79WQkKCNm7cqKNHjyo5OVm+vr6qX7++IiMj1apVK6feEwAAwFl8vbw1oWl7TTq4xem1JzRtL5tX4VXUAAAAAAAAAAAoKcvCIJL0+uuv68cff9Tx48ftIY53331X8+fP17333qsePXooJSXFIfCwevVqXbhwQb/99pt++ukn/fTTT0pLS3PYcsYwDHXt2lXPP/+8VY9WiLe3t7p06aIuXbpY3YqD6tWrq0ePHpbdv1q1aho4cKBl9wcAACirCc3a64vfj2ln4nmn1YwMqamJzTo4rR4AAAAAAAAA4OpkaRikevXqWrZsmbp3766EhAR7ICQxMVEffPCBPvjgA4fzTdNU//79C81JsgdBTNNUeHi4vvrqq8tuQQIAAACUh83LW/M69VaPn5coKSer3PVCfHw1L6KPfLws28kRAAAAAAAAAOAhLP9Jc8uWLbV+/Xq1bt3avrpHfigk/3WpS+cvPT//WPv27bVp0ybVq1fPiscBAADAVeSa4Opa3m2Agn3KtuVfvhAfX33XbYDaBoU6qTMAAAAAAAAAwNXM8jCIJLVt21bbtm3T2LFjZbPZHEIeV3pJF0Mg3t7eGjNmjDZu3KhGjRpZ/EQAAAC4WvSoHq5NvYYoIqRmma6PCKmpjb2GqEf1cCd3BgAAAAAAAAC4WrlFGESSAgIC9N577+n48eOaOHGiGjZsWGgVkKJetWrV0l/+8hcdOnRI77//vgIDA61+FAAAAFxl2gaFanOvIXqtTVeF+wWU6JpwvwC91qarNvcawoogAAAAAAAAAACn8rG6gYLq1q2rN998U2+++aaioqK0YcMGnT59WhcuXFB8fLz8/f1Vs2ZN1a5dW127dlWHDh2sbhkAAACQzctbz7ToqAnN2mtx9EktOXtSn5751eGcdkGh6hpaW/3C6mtIeGPZvLwt6hYAAAAAAAAA4MncLgxyqYYNG6phw4ZWtwEAAACUmM3LW0PrNtONNeoWCoOs7XGbavn5W9QZAAAAAAAAAOBq4TbbxAAAAAAAAAAAAAAAAKD8CIMAAAAAAAAAAAAAAAB4EMvCIHl5eVbdGgAAAAAAAAAAAAAAwGNZFgZp0KCBnn32WR05csSqFgAAAAAAHiIrL1df/X5M43/ZWOhY743favTudfrq92PKysu1oDsAAAAAAACgYlkWBjl79qxee+01tWnTRtddd53mzJmjlJQUq9oBAAAAAFRC2Xm5ev3objVc/anu2f69Pjvza6Fz9ifHa07UYd2z/Xs1Wv2ZXj+6W9mEQgAAAAAAAODBLAuD5DNNU5s3b9aYMWMUHh6uESNG6Mcff7S6LQAAAACAm9ufFKdu6xdr0sEtOpeZXqJrojPTNOngFnVbv1j7k+Jc3CEAAAAAAABgDcvDIIZhyDRNmaaptLQ0zZ8/X3369FGzZs30r3/9S6dOnbK6RQAAAACAm9kYF60ePy/RzsTzZbp+Z+J59fh5iTbGRTu5MwAAAAAAAMB6loVBWrVqZQ+BGIZhf+XPnThxQi+88IKaNGmiW265RQsWLFBmZqZV7QIAAAAA3MT+pDgN2LxcSTlZ5aqTlJOlAZuX60ByvJM6AwAAAAAAANyDZWGQgwcPauPGjfrLX/6i4ODgYoMheXl5WrNmjYYNG6Y6dero0Ucf1bZt26xqGwAAAABgoey8XD2064dyB0HyJeVk6c871yo7L9cp9QAAAAAAAAB3YOk2Md26ddOsWbN09uxZzZ8/XzfddJM9BCKp0GohCQkJmjlzprp166b27dtr+vTpio2NtfIRAAAAAAAVaPqxfWXeGqY4OxPPa/qxfU6tCQAAAAAAAFjJ0jBIvipVqmjYsGFatWqVTpw4oSlTpqhp06aX3UZm//79euqpp1S/fn3dcccd+uabb5Sby29yAQAAAICnysrL1fTjrgltTD++j9VBAAAAcFlZebn66vdjGv/LxkLHem/8VqN3r9NXvx9TFn+vBAAAbsAtwiCXatCggf7xj3/o6NGj+vHHHzVixAgFBgYWGwzJzs7WN998ozvuuEP169fXM888owMHDlj9GAAAAAAAJ1sSfVLRmWkuqR2dmabF0SddUhsAAACVW3Zerl4/ulsNV3+qe7Z/r8/O/FronP3J8ZoTdVj3bP9ejVZ/pteP7iZsDAAALOV2YZBL9erVS3PmzFF0dLTmzp2rG264QZKK3Ubm3Llzmjp1qtq3b6+uXbtq1qxZSkpKsvIRAAAAAABOsiLmlEvrr4w57dL6AAAAqHz2J8Wp2/rFmnRwi85lppfomujMNE06uEXd1i/W/qQ4F3cIAABQNLcOg+QLCAjQ8OHD9cMPP+jXX3/VP/7xDzVs2PCy28hs375d48aNU926da1uHwAAAADgBDsSzru4fqxL6wMAAKBy2RgXrR4/L9HOxLL9PXRn4nn1+HmJNsZFO7kzAACAK6sUYZBLNWnSRFOmTNGJEye0Zs0aDRs2TP7+/oWCIdLFFUTS00uW1AUAAAAAuLfDKQmurZ+a6NL6AAAAqDz2J8VpwOblSsrJKledpJwsDdi8XAeS453UGQAAQMlUujDIpXr37q358+crOjpas2bNUo8ePexbyAAAAAAAPEumi/dcz8jNcWl9AAAAVA7Zebl6aNcP5Q6C5EvKydKfd65Vtov/PgsAAHApH6sbcIaqVavqz3/+s/z9/RUfH6+DBw/aVwcBAAAAAHgGPy9vZbjwB+hVvD3i/yIDAACgnKYf21fmrWGKszPxvKYf26dnWnR0al0AAMpr2rG9mnZsr9PrTmzWQRObdXB6XZRcpf9J1+bNm/XRRx/piy++UFJSktXtAAAAAABcpFXVatqTdMF19QNDXFYbAAAAlUNWXq6mH9/nktrTj+/ThGbtZfPydkl9AADKIik7S2cyUl1SF9aqlGGQs2fPat68efr44491+PBhSXLYHoZVQQAAAADA80RWq+nSMEhktVouqw0AAIDKYUn0SUVnprmkdnRmmhZHn9TQus1cUh8AgLIItvmqXpXAYo/nmabOFvjfxjp+AfK6wnfywTZfp/SHsqs0YZDs7GwtXrxYc+fO1erVq5WXl1dsACR/vn379ho5cmSF9woAAAAAcL7+YQ00J+qwy+r3C6vvstoAAACoHFbEnHJp/ZUxpwmDAADcypW2c4nNTFfYynkOc3tuvFu1/Pxd3RrKye3DIDt27NDcuXO1YMECxcfHS/oj7FFUACQ0NFT333+/Ro0apYiIiIpvGAAAAADgEoPDGyvcL8Alv6kZ7hegIeGNnV4XAAAAlcuOhPMurh/r0voAAAD53DIMEhMTo08++UQfffSR9u/fL6n4bWBM05SXl5duvvlmjRo1SkOGDJGvL0vOAAAAAICn8fXy1oSm7TXp4Ban157QlL3bAQAAIB1OSXBt/dREl9YHAADI5zZhkNzcXH377beaO3euVqxYoZycnCtuA9OsWTONHDlSDz30kOrXZzlfAAAAAPB0E5q11xe/H9POROf9xmZkSM3LLocKAACAq0dmXq5L62fk5ri0PgAAQD7LwyB79uzRRx99pM8++0znz1/8Yd7ltoEJDAzU0KFDNXLkSPXq1aviGwZQrGnH9mrasb3FHs+7JOCV70/rFsrrkn/Xi3KlvcoAAABw9bB5eWtep97q8fMSJeVklbteiI+v5kX0kY+XlxO6AwAAQGXn5+WtDBcGQqp4W/61DAAAuEpY9reOd955Rx999JH27Nkj6fLbwEhSz549NXLkSN1zzz0KDAys2GYBlEhSdpbOZKSW6pqzJdjvPSm7/D/kBwAAgOe4Jri6lncboAGbl5crEBLi46vvug1Q26BQJ3YHAACAyqxV1Wrak3TBdfUDQ1xWGwAA4FKWhUHGjx8vwzAuuwpIvXr19NBDD2nkyJFq3ry5JX0CKLlgm6/qVXF+WCvY5uv0mgAAAKjcelQP16ZeQ/TnnWvLtGVMREhNzY/oQxAEAAAADiKr1XRpGCSyWi2X1QYAALiU5euR5YdA8gMgfn5+uv322zVy5Ejdcsst8mKpXqDSYDsXAAAAVKS2QaHa3GuIph/bp+nH9ym6BKvOhfsFaELT9prQrL1sXt4V0CUAAAAqk/5hDTQn6rDL6vcLq++y2gAAAJeyPAySHwLp1KmTRo4cqWHDhik0lN/MAgAAAABcmc3LW8+06KgJzdprcfRJLTl7Up+e+dXhnHZBoeoaWlv9wuprSHhjQiAAAAAo1uDwxgr3CyhR0Li0wv0CNCS8sdPrAgAAFMXSMEj16tU1bNgwjRo1Sh06sJoAAAAAAKBsbF7eGlq3mW6sUbdQGGRtj9tUy8/fos4AAABQmfh6eWtC0/aadHCL02tPaMrqdAAAoOJYFgZZuHChbrvtNtlsNqtaAAAAAAAAAAAAcDChWXt98fsx7Uw877SakSE12WIbAABUKC+rbnznnXcSBAEAAAAAAAAAAG7F5uWteZ16K9jH1yn1Qnx8NS+ij3y8LPtKBgAAXIX4mwcAAAAAAAAAAMAlrgmuruXdBpQ7EBLi46vvug1Q26BQJ3UGAABQMoRBAAAAAAAAAAAACuhRPVybeg1RREjNMl0fEVJTG3sNUY/q4U7uDAAA4Mp8rG7gSrKzsxUfH6+4uDglJycrKChI1atXV/Xq1eXj4/btAwAAAAAAAACASqptUKg29xqi6cf2afrxfYrOTLviNeF+AZrQtL0mNGsvm5d3BXQJAABQmNulKUzT1JIlS7R69Wpt2LBB+/fvV15eXqHzvLy81K5dO/Xo0UO33HKLbr/9dhmGYUHHAAAAAAAAAADAU9m8vPVMi46a0Ky9Fkef1JKzJ/XpmV8dzmkXFKquobXVL6y+hoQ3JgQCAAAs5zZhkLy8PL399tt65513FBUVJeliMKQ4ubm52rNnj/bu3auZM2eqUaNGGj9+vB577DF5ebH7DQAAAAAAAAAAcB6bl7eG1m2mG2vULRQGWdvjNtXy87eoMwAAgMLcIjVx4sQJXXfddXrqqaf022+/yTRNexDEMIxiX5Ls5548eVITJkxQz549deLECSsfBwAAAAAAAAAAAAAAwDKWh0H27dunyMhIbd26VaZpFgp85Ic9inoVde7mzZvVuXNn/fLLL1Y/GgAAAAAAAAAAAAAAQIWzdJuYU6dOacCAAUpISCi02ock+fn5qUOHDmrbtq1CQ0MVGBio1NRUJSQk6MCBA9q7d68yMjIkySEQEh8fr4EDB2rjxo2qX7++Zc8HAAAAAAAAAAAAAABQ0SwNg4wbN06///67PQQiXQyC3HjjjfrrX/+qwYMHy2azFXt9dna2vvnmG82YMUNr1651CIScOXNGY8eO1dKlSyviUQAAAAAAAAAAAAAAANyCZdvE/Pjjj/ruu+8cVgMJCgrSggULtHbtWt19992XDYJIks1m01133aXvv/9eX375pYKDgyXJHghZvny5fvrpJ5c/CwAAAAAAAAAAAAAAgLuwLAzy9ttv29+bpqnQ0FCtWbNG99xzT5nq3X333Vq7dq2qVatW7H0AAAAAAAAAAAAAAAA8nSVhkJycHK1Zs8a+godhGJo2bZoiIyPLVbdTp06aPn26vaZpmvr++++Vk5PjpM4BAAAAAAAAAAAAAADcmyVhkK1btyo5Odk+bt68uYYPH+6U2g899JBatGhhH6ekpGjr1q1OqQ0AAAAAAAAAAAAAAODuLAmDnDp1yv7eMAzdcccdTq1/5513yjRN+zgqKsqp9QEAAAAAAAAAAAAAANyVJWGQmJgYSbIHNi5dycMZmjdv7jCOjY11an0AAAAAAAAAAAAAAAB3ZUkYJDU11WEcHBzs1Pr59QzDKPJ+AAAAAAAAAAAAAAAAnsqSMEiNGjUcxtHR0U6tf+7cOUl/rDxS8H4AAAAAAAAAAAAAAACeypIwSFhYmKQ/Vu7YunWrU+tv27bNYVyrVi2n1gcAAAAAAAAAAAAAAHBXloRB2rZta39vmqa+/fZbJScnO6V2cnKyvvnmG3vQRJKuueYap9QGAAAAAAAAAAAAAABwd5aEQVq0aKHGjRvbx8nJyXrmmWecUvv//u//lJiYaB83btxYLVq0cEptAAAAAAAAAAAAAAAAd2dJGESSbr/9dpmmKcMwZJqmZs2apVdffbVcNd98802999579pqGYej22293UscAAAAAAAAAAAAAAADuz7IwyKRJkxQQECBJ9vDGc889p3vuuUdnz54tVa3o6Gjdd999mjRpksO8v7+/01YcAQAAAAAAAAAAAAAAqAwsC4PUqVNHEydOlGmakv4IhPzvf/9T06ZNde+99+qrr77SiRMnirz+xIkT+uqrr3TvvfeqadOm+uqrrxxWGjEMQ08++aTq1KlTkY8FAAAAAAAAAAAAAABgKR8rbz558mTt3r1bS5culWEY9iBHZmamFi5cqIULF0qSfH19FRISosDAQKWmpioxMVFZWVn2OpcGSvL/89Zbb9XkyZMr/JkAAAAAAAAAAAAAAACsZNnKIJLk5eWlL774Qn379nUIdOSHQvJfmZmZiomJ0YkTJxQTE6PMzEyH4/nXSBeDIX379tUXX3xhnwMAAAAAAAAAAAAAALhaWBoGkSR/f3+tWrVKr7zyinx8fAqFQkryki6GQHx8fPTaa69p5cqVqlKlipWPBQAAAAAAAAAAAAAAYAnLwyDSxeDH3//+dx05ckQTJ05USEiIw8of+S9JRc6HhIToqaee0tGjR/X000+zIggAAAAAAAAAAAAAALhq+VjdwKUaNWqkN998Uy+//LK2bdumDRs2aOfOnTp//rzi4+OVnJysoKAghYaGqlatWoqIiFCPHj3UpUsX+fr6Wt0+AAAAAAAAAAAAAACA5dwqDJLPz89PPXv2VM+ePa1uBQAAAAAAAAAAAAAAoFJxi21iAAAAAAAAAAAAAAAA4ByEQQAAAAAAAAAAAAAAADwIYRAAAAAAAAAAAAAAAAAPQhgEAAAAAAAAAAAAAADAg/hY3UBxcnNztXv3bu3YsUMxMTFKSEhQcnKygoKCVK1aNYWFhSkyMlIdO3aUt7e31e0CAAAAAAAAgFNNO7ZX047tLfZ4nmkWmvvTuoXyMozL1p3YrIMmNutQ7v4AAAAAuC+3C4N89913mjlzptasWaOMjIwrnl+lShX17dtX48aN04ABAyqgQwAAAAAAAABwvaTsLJ3JSC3VNWcz00pUFwAAAIBnc5swyE8//aSHH35YR48elSSZRaTai5Kenq5ly5Zp2bJlatGihWbNmqXrr7/ela0CAAAAAAAAgMsF23xVr0qgS+oCAAAA8GxuEQaZMGGC/vOf/ygvL88+Z1xhKcNL5QdHjhw5oj59+uiJJ57QtGnTnN4nAAAAAAAAAFQUtnMBAAAAUFaWhkFM09TIkSM1f/58maZZKABSktVBDMNwuC4vL09vv/224uLiNHfu3FKFSgAAAAAAAAAAAAAAACo7S8Mgzz//vObNm1co0GGapurXr6/BgwcrIiJCrVu3VkhIiAIDA5WamqrExEQdPnxYO3bs0JIlS3T69Gn79YZhyDRNzZ8/X/Xq1dPLL79s1eMBAAAAAAAAAAAAAABUOMvCIPv27dMbb7xRKATSsmVLTZ06VQMHDrzsqh7du3fXiBEj9M477+i7777T008/rUOHDtmDJaZp6s0339R9992n9u3bV8QjAQAAAAAAAAAAAAAAWM7Lqhu/+OKLysnJkfTHdjD333+/9u3bp0GDBpV4exfDMDRo0CDt3btXDz74oMPWMjk5OXrppZec3zwAAAAAAAAAAAAAAICbsiQMkpqaqu+++86+godhGLr11lv16aefymazlammj4+P5s2bp9tvv91e0zRNLVu2TKmpqU5+AgAAAAAAAAAAAAAAAPdkSRhkw4YNSk9Pt4+rVKmi999/3ym1Z86cKX9/f/s4IyNDGzZscEptAAAAAAAAAAAAAAAAd2dJGOT06dP294ZhaODAgQoPD3dK7fDwcA0aNMhhu5hL7wcAAAAAAAAAAAAAAODJLAmDxMTESJI9sNGrVy+n1u/Zs2eR9wMAAAAAAAAAAAAAAPB0loRBfH19HcbOWhWkYD3DMCRJNpvNqfUBAAAAAAAAAAAAAADclSVhkDp16jiMU1JSnFo/v17+yiN169Z1an0AAAAAAAAAAAAAAAB3ZUkYpGPHjpL+WLnj2LFjTq1fsN6f/vQnp9YHAAAAAAAAAAAAAABwV5aEQdq0aaMmTZpIurh6x6JFi5xa/+uvv7YHTRo2bKi2bds6tT4AAAAAAAAAAAAAAIC7siQMIkmPPfaYfRuXI0eO6JNPPnFK3U8//VSHDh2SdHHlkccee8wpdQEAAAAAAAAAAAAAACoDS8Mgbdu2lWEYMk1Tf/vb37Rz585y1dy1a5eeeOIJ+6ogbdq00RNPPOGMdgEAAAAAAAAAAAAAACoFy8IgNptNS5YsUa1atSRJ8fHx6tOnT5lXCPn000/Vp08fJSQkyDRNhYWFafHixbLZbM5sGwAAAAAAAAAAAAAAwK1ZFgaRpGbNmmnz5s3q2LGjJCkpKUnDhw9X165dNXv2bMXGxl72+vPnz2v27Nnq1q2bHnroISUmJso0TXXs2FGbN29W8+bNK+ApAAAAAAAAAAAAAAAA3IePM4uNGjWqTNe1bdtWhw4dUmZmpkzT1LZt27R9+3Y98sgjqlevnlq1aqWQkBAFBgYqNTVViYmJOnLkiE6fPi1JMk3TXsvf31/XXHONpkyZIkkyDEMffvhh+R8OAAAAAAAAAAAAAACgEnBqGOSjjz6SYRjlqmEYhkzTtAc8Tp8+rTNnzhQ679IASP51kpSRkaHPPvvMfg5hEAAAAAAAAAAAAAAAcDVxahgkX8GgRklcGiIpGCgpqt7lQidluT8AAAAAAAAAAAAAAIAncEkYpLyrg5S3Xv75hEIAAAAAAAAAAAAAAMDVxqlhkIYNGzo9CAIAAAAAAAAAAAAAAICSc2oY5OTJk84sBwAAALitacf2atqxvcUezytilbo/rVsoryuEpyc266CJzTqUuz8AAAAAAAAAwNXLJdvEAAAAAJ4uKTtLZzJSS3XN2cy0EtUFAAAAAAAAAKA8CIMAAAAAZRBs81W9KoEuqQsAAAAAAAAAQHkQBgEAAADKgO1cAAAAAAAAAADuysvqBgAAAAAAAAAAAAAAAOA8rAwCAAAAAAAAAAAAwC1lZeVo+cqd+m7ljkLH7hj6qiI6NVOfG9trQL8I+fry1ScA5ONPRAAAAAAAAAAAAABuJTs7RzM/WKkZs1YqNjaxyHMOHT6jQ4fP6LMFPyksLERjx/TT2DH9ZLPxFSgAsE0MAAAAAAAAAAAAALdx6PBp9b/tRb348pfFBkEKiolJ1Isvf6n+t72oQ4dPu7hDAHB/bh+LS0tLU2JiorKzs8tco2HDhk7sCAAAAAAAAAAAAIArbN12VPf9eaqSk9PLdP3efb9p4OB/acH8J9Xl2hZO7g4AKg+3CoMkJibq888/188//6zNmzfr1KlTysnJKVdNwzDKXQMAAAAAAAAAAACAax06fLpcQZB8ycnpuu/PU7X8m3+oVct6TuoOACoXtwiDpKSk6Nlnn9VHH32k1NRUSZJpmhZ3BQAAAAAAAAAAAKAiZGfn6NG/zSp3ECRfcnK6/vrE+1rx7T9ls7nFV6IAUKG8rG5g3759ioyM1LvvvquUlBR7CMQwjHK/AAAAAAAAAAAAALi/mR+s1N59vzm15t59v2nmByudWhMAKgtLwyBnzpzRLbfcoqNHj8o0TXuIwzTNEr0KutJxAAAAAAAAAAAAAO4lKyvHZaGNmR+sVHZ2jktqA4A7s3RNpHvvvVfnzp2zr+JhmqYaNGigoUOHqkWLFnrllVd0+vRpe1Bkzpw5Sk9PV1xcnI4fP65Nmzbp4MGDkmSvERISoueff141a9a07LkAAAAAAAAAAAAAlMzylTsVE5PoktoxMYn6bsVODb6ti0vqA4C7siwMsnr1am3cuNG+EohhGBo1apTeffdd+fn5SZJmzpyp06dP268ZPnx4oTq//PKLpk6dqnnz5skwDCUmJmrq1KlatmyZOnXqVGHPAwAAAAAAAAAAAKD01q7b59L6P/y4jzAIgKuOZdvEvPXWW/b3hmHo5ptv1uzZs+1BkJJq166d5s6dq5UrV6pGjRoyDEPR0dHq27evDh065OSuAQAAAAAAAAAAADjTnr0nK3V9AHBHloRBcnNz9eOPP9pXBZGk6dOnl6vmTTfdpBUrVigoKEiGYSghIUF33XWXcnLYAwwAAAAAAAAAAABwV8eOn3Vt/WPRLq0PAO7IkjDIzp07lZaWZh9HRkaqTZs25a4bERGhl19+2b7tzKFDhzRr1qxy1wUAAAAAAAAAAADgGpmZrv3l7ozMbJfWBwB3ZEkY5NixY/b3hmGoZ8+eJbquJKt8jBs3TnXr1pUkmaap9957r2xNAgAAAAAAAAAAAHA5Pz8fl9av4mdzaX0AcEeWhEHi4+Mlyb5FTOvWrYs8zzAMh3FGRsYVa3t5eWnw4MH22gcPHlRUVFR52gUAAAAAAAAAAADgIs2a1nFt/WbhLq0PAO7IkjBIQkKCwzgkJKTI8wIDA+2hDklKTU0tUf127do5jHfv3l2q/gAAAAAAAAAAAABUjD91aFyp6wOAO7IkDOLr6+sw9vEpeumnoKAgh/Hp06dLVL9WrVoO499++60U3QEAAAAAAAAAAACoKH1ubO/S+r1vcG19AHBHrt2AqxjBwcEO4+Tk5CLPCw0NdRifPHlSkZGRV6yfnp4u6Y9tZoqrX5Hy8vK0Y8cO7du3TzExMTJNUzVq1FDbtm3VtWtX2WzW7lX222+/af/+/YqKilJCQoK8vLwUGhqqunXrqkuXLoUCNgAAAAAAAAAAAIAzDOgXobCwEMXEJDq9dlhYiAb2j3B6XQBwd5aEQRo2bCjpj7BGfHx8kee1adPG4bxNmzbprrvuumL9AwcOSJJM05RhGPL39y93z2WVkpKiN954QzNnzlRMTEyR54SEhGjEiBF67rnnKix0ERMTo8WLF+v777/XDz/8oPPnz1/2/GuuuUZjx47ViBEjVLVq1VLfb/LkyZoyZUpZ25Uk3XDDDVq3bl25agAAAAAAAAAAAMC9+Pr6aOyYfnrx5S+dXnvsmH6y2Sz5ShQALGXJNjGtW7d2GB8+fLjI89q3/2PJJtM0tXTp0hLV//rrr+0BEkmqWbNmGbosv+3bt+uaa67Riy++WGwQRJISExP19ttvq3Xr1lqxYoVLe4qNjdVNN92kunXr6pFHHtFXX311xSCIJO3fv1+PP/64WrdurdWrV7u0RwAAAAAAAAAAAFxdxo7ppw7tGzm15p86NNa4h/s7tSYAVBaWhEEaNWrksAVM/koeBfXq1cth+5SjR49qwYIFl609Y8YMHTlyxGGuY8eOZW+2jLZs2aLevXsrKiqq0DE/P78iVyuJi4vTbbfdpm+++cZlfV24cEFr1qxRbm5usecEBAQoJCSkyGNnzpxRv3799MEHH7iqRQAAAAAAAAAAAFxlbDYfvfv2wwoKcs6K/8HBAXr37Yfl4+PtlHoAUNlYtiZSr1697KGH7du3KzMzU35+fg7nVK9eXbfccouWLVsmwzBkmqYeeeQR+fv7a/DgwYVqzpw5U3/7298cVgUJDw93WGGkIsTGxuqOO+5QSkqKfc7Hx0ePPfaYxo0bp+bNm8swDEVFRenDDz/UtGnTlJqaKknKycnRsGHDtGPHDrVs2bJC+r3++us1cOBA9e7dW23btrVvA5Oamqr169frP//5j7777jv7+aZpauzYsapXr54GDhxYpnsOGjRIt956a6muqVu3bpnuBQAAAAAAAAAAAPc27dheTfttr/IebydN3yWl55S9mL+P0h+7Rn1P/qCJ3h00sVkH5zUKAJWEZWGQvn372sMgmZmZ+umnn3TzzTcXOm/8+PFatmyZJMkwDCUnJ+vOO+/UNddco169eql69eo6f/68vv/+ex0/flymadqDI4Zh6PHHH6/Q55KkyZMn6+zZs/axn5+fFi5cWCj80KhRI7344osaNGiQBgwYoPj4eElSSkqKJk6cWOJtccoiICBAjzzyiMaOHVts6CQwMFD9+/dX//79NXfuXI0ZM8a+okheXp4ee+wxHThwQFWqVCn1/Tt37qyxY8eW6xkAAAAAAAAAAADgGZKys3QmI1VqYJP30+0UNPeofE6llrpOToNAJY9sodw6NikjVUnZWS7oFgDcn2VhkDvvvFPjx4+3jxcsWFBkGKRv374aOnSovvrqKxmGYQ96/PLLL9q/f7/9PNM0Jcm+KohhGGrVqpWeeOIJ1z5IASdPntTs2bMd5qZMmXLZVTC6du2qd999Vw888IB9btmyZdq0aZO6d+/u1P5sNpseffRRPf/88woPDy/xdSNHjlR8fLyefPJJ+9yJEyf05Zdf6qGHHnJqjwAAAAAAwL3MeH+FZsxa4fS64x7ur3GPsIc7AAAApGCbr+pVCbw4aBIo8581lLsqSrmrfpMSSxDoCPGV9y2N5HtLQwX6eDnUBYCrkWVhkHr16qlHjx7asGGDJOnLL7/UW2+9paCgoELnzp49W1FRUdqyZYs9ECL9EQCR5LA1jGmaql27thYtWqSAgAAXP4mj6dOnKyvrj/9BatKkiUOAojj333+/3nvvPf3888/2uddee02LFy92an/NmjXTf//73zJd+7e//U3vvvuujh8/bp9bsmQJYRAAAAAAADxcckq6zkbHu6QuAAAAIEkTmxWxnctAKTs7R9+t2Kmvl2/Tkh375X0uXcrJk3y81LJpuLpENFfvG9prYP8I2WyWffUJAG7H0j8R169fX6LzgoKCtGrVKk2YMEFz584ttApIvvz566+/XvPnz1eDBg2c23AJFAxvjB49Wj4+JfuYH374YYcwyKpVq5SWllbhgZbieHt7a/DgwZo+fbp9bs+ePRZ2BAAAAAAAKkJQVX/VCQ8t9nhenqlzMQkOc7XDqsnLyyj6gkvqAgAAAJdjs/lo8G1d1Oraxvp4q+N3bnO63KHW4WEWdQYA7q3SxOOCgoI0e/ZsjR8/Xl988YVWr16tU6dO6fz58woMDFSdOnXUq1cv3XPPPerTp48lPe7atUtRUVEOc/fee2+Jr7/rrrs0atQo5eTkSJLS09O1atUqDRkyxJltlkuzZs0cxtHR0RZ1AgAAAAAAKsq4Ry6/ncv5C0lq0+Fxh7l137+kmjWCXd0aAAAAAAAoQqUJg+Rr166d2rVrp5deesnqVgpZu3atw7h27dpq3rx5ia8PCAhQx44dtX37dvvcmjVr3CoMcukWOFLh1VkAAAAAAAAAAAAAAIC1vKxuwJMcOHDAYdylS5dS1+jWrZvD+ODBg+Xqydl+/fVXh3F4eLhFnQAAAAAAAAAAAAAAgKJUupVB3NmhQ4ccxk2bNi11jYLXFKxppdzcXC1evNhhLjIyssz1kpOTtWvXLkVHRyslJUWhoaGqUaOGrrnmGtWoUaOc3QIAAAAAAAAAAAAAcHUiDOJER44ccRg3bNiw1DUaNGjgMD5z5v+xd9/xVdTZ/8ffNxUIJNQQegkoIL2LUhVpsgIK6tpQFkWx4frVXQui2F1QXAuiWBBcEQVRFNCVooD0Kr2FHgg1jfT5/eEvd5nctHszN3Pvzev5eOQhM3c+53MmyRk/Sc6dOaaUlBRFRESUKDcrfPfddzp+/Lhp3+DBgz2KNWXKFL344ovKzs52ec3hcKh58+a64YYb9PDDD3P3EQAAAABOk/dv1eT9Wwt8PccwXPa1Wfa1gop4xOVjsa31WGzrEucHAAAAAAAA+AKaQSx07tw507YnTQy1atXKN67dzSBpaWl68sknTftq1KihYcOGeRTv/PnzBb5mGIZ27NihHTt2aPLkyXr88cf1/PPPKzg42KO5AAAAAASOxMwMHUtLcWvMifTUYsUFAAAAAAAAAgXNIBa5ePGiy10uKlSo4Hac8uXLu+xLTk72OC+r/OMf/9DevXtN+yZMmJBvvlZKT0/XSy+9pOXLl2vBggWKiory6nwAAAAAfFtkaJjqlLO+WT4yNMzymAAAAAAAAIBdaAaxSEqK6zvTypUr53ac/Jor8otdmubOnaspU6aY9nXr1k1jxoxxO1bjxo01cOBA9ejRQy1btlStWrVUsWJFJSUl6dixY1q1apVmzZqlX3/91TRuxYoVGjZsmBYtWqTQ0FCPz+Xo0aOFvn7ixAmPYwMAAADwPh7nAgAAAAAAABSNZhCLXLx40WVfWJj77ywLDw8vVuzSsmXLFt11112mfVFRUfr8888VFBRU7DhXXnmlli1bpp49e+b7epUqVVSlShW1bNlS9957r3766SfdeeedOnnypPOYJUuWaOLEiXrhhRc8OxlJ9erV83gsAAAAAAAAAAAAAAD+oPh/zUeh8rsLSEaG+8+cTk9PL1bs0hAXF6eBAweaHlMTHBysWbNmqXHjxm7F6tevX4GNIPm57rrrtGrVKtWoUcO0f/LkyaYGEQAAAAAAAAAAAAAAYEYziEUqVqzosi8tLc3tOPndBSS/2N526tQpXXfddTp+/Lhp/7Rp0zRo0KBSyaFx48aaPn26aV9KSoo+++wzj2MeOXKk0I+1a9eWNG0AAAAAAAAAAAAAAGzFY2IsUr58eQUHBys7O9u5LzU11e04vtAMcuHCBfXr10979+417X/jjTd0zz33lGougwcPVrt27bRp0ybnvsWLF+uJJ57wKF7dunWtSg0AAAAAAAAAAAAAAJ/EnUEsVLlyZdN2fHy82zFOnDhRZFxvSk1N1aBBg7R582bT/qefflqPP/54qeVxqcGDB5u2uXsHAAAAAAAAAAAAAAAFoxnEQpdddplp+/Dhw27HOHLkiGm7du3apXZnkIyMDA0dOlQrV6407X/ooYf04osvlkoO+WnRooVpOzk5Od87qAAAAAAAAAAAAAAAAJpBLNWsWTPT9oEDB9yOcfDgwUJjekt2drZuueUW/fTTT6b9d911l6ZMmVIqORSkatWqLvvOnTtnQyYAAAAAAAAAAAAAAPg+mkEslPcOFp48zmT16tWm7ebNm5cop+IwDEMjR47UvHnzTPtvuukmTZ8+XQ6Hw+s5FOb8+fMu+6Kioko/EQAAAAAAAAAAAAAA/ADNIBbq06ePafvkyZPat29fscenpqZq8+bNpn3XXHONFakVauzYsZo5c6Zp34ABAzRr1iwFBwd7ff6i7N2717RdoUIFRURE2JQNAAAAAAAAAAAAAAC+jWYQC7Vv31716tUz7Zs9e3axx8+dO1eZmZnO7XLlyum6666zLL/8PPnkk3r//fdN+3r27KlvvvlGYWFhXp27uH788UfTduvWrW3KBAAAAAAAAAAAAAAA30cziMWGDBli2p4+fbqysrKKNXbatGmm7b59+3r1Dhgvv/yyXn/9ddO+Tp066fvvv1f58uW9Nq87li9frpUrV5r29e/f36ZsAAAAAAAAAAAAAADwfTSDWGzcuHEKDQ11bh88eFCTJk0qctyXX36p3377zbTvySefLHKcw+EwfYwcObJYeb777rt6+umnTftatWqlRYsWqVKlSsWKUVyGYXg07uTJky7nExoaqttuu82CrAAAAAAAAAAAAAAACEw0g1isUaNGGjVqlGnfhAkT9MMPPxQ4Zu3atRo7dqxp34ABA3TVVVd5JceZM2fqoYceMu1r2rSpfv75Z1WtWtXy+Xr37q333ntPFy9eLPaYzZs3q3v37oqLizPtv/fee9WkSROLMwQAAAAAAAAAAAAAIHCE2DXxv//9b911112KjIy0KwWvef755/Xtt98qPj5ekpSWlqYhQ4bowQcf1AMPPKAmTZrI4XDo8OHDmj59uiZNmqSUlBTn+IiICE2ePNkrua1atUp333236W4dDodDw4cP17x58zyK+Ze//EW1a9cu8PW4uDiNHTtWTzzxhK6//nr1799fbdu2VbNmzVSuXDnncWfPntXKlSs1a9Ysff3118rOzjbFadOmjV5++WWPcgQAAAAAAAAAAAAAoKywrRnkkUce0VNPPaVbb71VY8aMUfv27e1KxXLR0dGaO3eu+vbt62zyyMrK0ltvvaW33npL4eHhCgoKyvdOGcHBwfr888/VrFkzr+S2Z88eZWVlmfYZhlGiJotmzZoV2gySKyUlRbNnz9bs2bOd+8qVK6eKFSsqKSlJ6enpBY5t3ry5Fi5cGJDNQwAAAAAAAAAAAAAAWMnWx8SkpqZq+vTp6tSpk7p06aLPPvtMaWlpdqZkmSuvvFJLlixR3bp1XV5LT0/PtxGkSpUqmj9/voYOHVoaKfqEtLQ0nT59usBGkKCgII0dO1YbNmxQrVq1Sjk7AAAAAAAAAAAAAAD8j63NINKfd6UwDEPr1q3TPffcozp16uixxx7T7t277U6txDp37qwdO3bomWeeUY0aNQo8LjIyUg899JB27dqlQYMGlWKGpeP999/Xww8/rHbt2iksLKxYYxo0aKDHH39c+/bt0zvvvKPy5ct7OUsAAAAAAAAAAAAAAAKDbY+JyeVwOCT9rynk3LlzmjJliqZMmaJevXrpgQce0JAhQxQcHGxzpp6pVKmSJk6cqAkTJmjDhg3aunWrEhISZBiGqlWrphYtWqhLly7FbpLIyzAMt44fOXKkRo4c6dFcnhowYIAGDBggScrMzNSuXbsUFxen48ePKzExUWlpaapQoYKqVKmi6OhodezYUTExMaWaIwAAAAAAAAAAAAAAgcK2ZpC5c+dq6tSp+vnnn2UYhrMpRPpfg8OyZcu0bNky1axZU6NHj9bo0aPzfeyKPwgODlbnzp3VuXNnu1OxVWhoqFq1aqVWrVrZnQoAAAAAAAAAAAAAAAHJtsfEDBkyRIsWLdKePXv097//XdWqVXPeHcThcMjhcDi34+Pj9eKLL6pRo0YaMmSIFi9ebFfaAAAAAAAAAAAAAAAAPs22ZpBcsbGxeuONN3T06FHNmDFD3bp1c2kKyW0Myc7O1vfff6+BAwc6x50+fdruUwAAAAAAAAAAAAAAAPAZtjeD5AoLC9Ptt9+uFStWaMuWLRozZowqVqxY4N1CDh48qH/84x+qV6+e7rjjDq1cudLuUwAAAAAAAAAAAAAAALCdzzSDXKpVq1Z67733dPz4cb333ntq06ZNgXcLSU9P1xdffKEePXqodevWev/995WcnGz3KQAAAAAAAAAAAAAAANjCJ5tBckVERGjMmDHatGmTVq1apdtvv13h4eEyDEOSXO4W8scff+jBBx9U7dq1df/992vLli02nwEAAAAAAAAAAAAAAEDp8ulmkEt17dpVM2bM0NGjR/X6668rNjbW5W4hkmQYhpKTkzVt2jS1b99e3bp108yZM5WRkWHzGQAAAAAAAAAAAAAAAHif3zSD5Kpataoef/xx7dmzR4sXL9aQIUMUHByc7yNkDMPQmjVrdNddd6l27dp68skndfjwYbtPAQAAAAAAAAAAAAAAwGv8rhnkUn379tXcuXN16NAh3Xjjjc7Hx0iuj5A5e/as/vWvfyk2Nla33HKLdu/ebWPmAAAAAAAAAAAAAAAA3uHXzSCZmZmaNWuWRowYoblz5zqbPy6V924h2dnZmjNnjlq1aqVHHnlEFy9etCl7AAAAAAAAAAAAAAB8T0ZOtuYc369H/1jl8lrvVd9r1OZlmnN8vzJysm3IDsURYncCnjhw4IA++OADffLJJzpz5owkmR4Tk7stSaGhocrMzJQk02tZWVl65513tGTJEv3888+KiYmx4UwAAAAAAAAAAAAAAPANmTnZenP/Nk0+sFUn0/O/scL2pHPannROHx/erZjwChrXuJXGxbZSaFBwKWeLwvjNnUEMw9D8+fPVv39/XXbZZfrXv/6l06dPO5s+Lm30CAkJ0S233KIVK1bo7Nmzmjp1qtq3b+98ZMyldwrZvn27/vKXvygnJ8fO0wMAAAAAAAAAAAAAwDbbE8+q62/f6smdawpsBMkrPj1VT+5co66/favtiWe9nCHc4fPNICdOnNALL7ygBg0aaNiwYfr555+Vk5Pj0tRhGIZq1aql559/XocPH9YXX3yhbt26KSIiQvfee6/Wr1+vFStWaMCAAS4NJBs2bNCXX35p52kCAAAAAAAAAAAAAGCLVWfj1W3FfG28cNqj8RsvnFa3FfO16my8xZnBUz7bDPLLL7/opptuUsOGDfX888/r6NGj+d7ZwzAMde/eXbNnz9ahQ4f07LPPqmbNmvnG7Natm3744QfNnz9fFStWNL02e/bs0jgtAAAAAAAAAAAAAAB8xvbEsxqweqESszJKFCcxK0MDVi/UjqRzFmWGkvCpZpBz585p8uTJuvzyy3Xddddp3rx5yszMNDWA5DaBlC9fXqNHj9aWLVu0fPlyDR8+XMHBxXsG0eDBg/XGG2844xqGoY0bN3r57AAAAAAAAAAAAAAA8B2ZOdm6c9PSEjeC5ErMytAdG5coMyfbknjwXIjdCUjS6tWrNXXqVM2ZM0dpaWnOx7hI/3uUS+6+Jk2a6IEHHtDdd9+tqKgoj+e855579NBDDykrK0uSdPq0Z7e7AQAAAAAACHST92/V5P1bC3w9JydHZ1/uYNrXet23Cgoq/H1Ij8W21mOxrS3JEQAAAADgvjf3b/P40TAF2XjhtN7cv01PNG1raVy4x7ZmkJSUFM2aNUvvv/++tm7985cJuQ0fuQ0gufuCgoI0YMAAPfjgg+rfv78l84eEhKh+/fo6cOCAJCkjw5pOJwAAAAAAgECTmJmhY2kphR9UJdy0eSLjYrHiAgAAAADskZGTrTcPbPNK7DcPbNO42FYKDSre0z1gPduaQWrVqqWUlJRC7wJSpUoV3XPPPXrggQfUqFEjy3OoWLGi5TEBAAAAAAACTWRomOqUiyjw9azsbJ3MTDPtqxlaTiFFPNI3MjTMkvwAAAAAAO6bHx+n+PRUr8SOT0/Vt/FxGl471ivxUTTbmkGSk5PlcDhc7gIiSW3bttXYsWN12223qVy5cl7N49JmFAAAAAAAALgq6nEuu+JPqfnaeaZ9y9oNULOYaG+nBgAAAADw0KJTR7waf/GpozSD2Mi2ZpBLGYah0NBQ3XjjjRo7dqyuuuqqUpl3zJgxio+PL5W5AAAAAAAAAAAAAADwFRvOn/Zy/ASvxkfhbG0GMQxDtWrV0n333af77rtPNWvWLNX577vvvlKdDwAAAAAAAAAAAAAAX7A7+bx346dc8Gp8FM62ZpDu3btr7NixGjZsmEJCfOIGJQAAAAAAAAAAAAAAlAnpOdlejZ+WneXV+CicbV0Yy5cvt2tqAAAAAAAAAAAAAADKtPCgYKV5sSGkXDA3hbBTkN0JAAAAAAAAAAAAAACA0nV5xcrejR8R5dX4KBzNIAAAAAAAAAAAAAAAlDEdKlf3cvwaXo2PwtEMAgAAAAAAAAAAAABAGdM/up5X4/eLruvV+CgczSAAAAAAAAAAAAAAAJQxN8Q0VEx4Ba/EjgmvoCExDb0SG8VDMwgAAAAAAAAAAAAAAGVMWFCwxjVu5ZXY4xq3UmhQsFdio3hC7Jo4ONh7X/igoCBFRkYqKipKVatWVcuWLdW5c2f16NFDLVu29Nq8AAAAAAAAAAAAAAD4i3GxrTT7+H5tvHDaspgdoqrrsdjWlsWDZ2y7M4hhGF77yM7O1rlz5xQXF6eNGzfq888/10MPPaQ2bdqoW7du+vLLL+06bQAAAAAAAAAAAAAAfEJoULBmtOutyJAwS+JFhYRpRvs+CgniISV2s+3OIJLkcDhKZR7DMJz/Xr16tdasWaMZM2bo008/VXR0dKnkAAAAAAAAAAAAfN/k/Vs1ef/WAl/PueRvDrnaLPtaQUX8zeOx2Na8SxoA4JOuiKyqhV0HaMDqhUrMyvA4TlRImH7sOkAtKlWxMDt4ytZmECOfBVPeBpH8jsnvWHeOMwxDixYtUufOnbVmzRrVrFnTnbQBAAAAAAAAAECASszM0LG0FLfGnEhPLVZcAAB8VbeqMfq9+xDdsXGJR4+MaR9VXZ+370MjiA+xrRnkueeec/77woUL+uCDD5SWlibpf40d1apVU7t27dSoUSNFRUUpPDxciYmJOnPmjLZu3apdu3YpKytLDofD2fDRtWtXXXfddcrKytK5c+cUHx+vNWvW6NixY5L+1xhiGIYOHz6soUOHavny5QoNDS3N0wcAAAAAAAAAAD4oMjRMdcpFeCUuAAC+rEWlKlrdfYje3L9Nbx7YpvhiNDvGhFfQuMatNC62lUKDgkshSxSX7c0gGzZs0JAhQ5SWlibDMBQcHKy7775bo0aNUpcuXQqNkZiYqC+//FJvv/22duzYIYfDodWrV6tDhw566623FHTJc4i2bNmi1157TV9++aWzecQwDK1Zs0bTpk3T2LFjvXq+AAAAAAAAAADA9/E4FwBAWRYaFKwnmrbVuNhW+jY+TvNPxGnWsX2mY1pWqqIuVWqqX3RdDYlpSBOIjwoq+hDv2bhxo3r37q3jx4/LMAxddtllWr9+vaZNm1ZkI4gkRUZG6t5779XmzZs1fvx45x1F3n33Xd15552mY9u0aaMvvvhCs2fPVljYn923uQ0hb7zxhrKzs60/QQAAAAAAAAAAAAAA/ExoULCG147Vmy27uby2pNtgfdS2p4bXjqURxIfZ1gySmpqqG2+8UcnJyTIMQ02aNNHy5cvVpk0bt2OFhIRowoQJmjRpkgzDkGEY+s9//qN///vfLscOHz5cb7/9trNxRJKOHDmiJUuWlOh8AAAAAAAAAAAAAAAAfIFtzSCTJ0/WoUOHJP15h46PPvpINWvWLFHMcePGqXfv3pIkwzA0fvx4JSUluRw3evRodejQwdQQ8uuvv5ZobgAAAAAAAAAAAAAAAF9gWzPI1KlT5XA45HA41LFjR/Xo0cOSuI8//rikPxtMEhMT9cUXX+R73NixY53HSdKqVassmR8AAAAAAAAAAAAAAMBOtjSDbNmyRcePH3duDxo0yLLY1157rcLDw53bCxcuzPe4Pn36OP9tGIaOHj1qWQ4AAAAAAAAAAAAAAAB2saUZZNu2bZLkfExLgwYNLIsdGhqqmJgYZ/zcufKqX7++oqKinNvnzp2zLAcAAAAAAAAAAAAAAAC72NIMEh8fb9ouX768pfEvjXfy5MkCj6tWrZrz3xcuXLA0BwAAAAAAAAAAAAAAADvY0gySnZ1t2s7bHFJSl8bLO9elKlSo4Px3cHCwpTkAAAAAAAAAAAAAAADYIcSOSXMf4+JwOCRJq1at0sMPP2xJ7N27d+v8+fPO2DVr1izw2KSkJOe/IyIiLJkfAIDS9v4Hi/T+tEWWx73/3v66/77+lscFAAAAAAAAAACAd9nSDFK3bl3nvw3D0I8//qizZ8+qatWqJY792WefOf/tcDhMc+WVkJDg/HeNGjVKPDcAAHZISr6oE/HnvBIXAAAAKExGRpYWLt6oeQvXqvKG7Qo+mSZl5UghQbq70UF1bt9UfXq10oB+7RUWZsuvoQAAAAAAKJNs+Sn8qquuUkREhFJTUyVJKSkpGjdunKmRwxO7d+/Wm2++KYfDIcMw5HA4dN111+V77IEDB5SamiqHwyGHw6GGDRuWaG4AAOxSqWJ51YqpUuDrOTmGTp46b9pXM7qygoIcRcYFAAAA8pOZmaWpHy7W+9MWKyHhgqQ8v2TKzNG+PSe0b88JffHlr4qOjtKY0f00ZnQ/hYbSFAIAAAAAgLfZ8tN3uXLlNGDAAH399dfOxo2ZM2eqZs2aev311z2KuXfvXl133XVKT093PiJGkoYPH57v8WvXrjVtt2jRwqN5AQCw2/33Ff44l9NnEtW89UOmfcv+O1HVq0V6OzUAAAAEoF27j2rsI9O0dduhYo85deqCXnjpK3373Rq9O+VeNbu84Du5AgAAAACAkguya+IJEyYoJOTPXpTchpBJkyape/fuWrduXbHjpKSk6PXXX1f79u115MgR011Bbr75ZjVv3jzfcd9//72kPx9TI0lXXnllCc8IAAAAAAAgsK1dt1cDb3jRrUaQS23ddkgDb3hRa9fttTgzAAAAAABwKdvuy9miRQs9/vjjevXVV52PajEMQytXrlTXrl3VokULDRw4UO3bt1fDhg0VFRWlsLAwJSUl6cyZM9q2bZtWr16tBQsWKDU11dkAkqtKlSp688038507OTlZCxYscM4ZFBSkXr16ldKZAwAAAAAA+J9du4/qljsmKSnpYoniJCVd1C13TNLC757V5ZfVsSg7AAAAAABwKVsf0vrSSy/p6NGjmjlzpqkhxDAMbd++XTt27CgyRu6dPXIbQQzDUOXKlbVo0SLVrFkz3zGffPKJkpKSnNvdu3dXtWrVLDgjAAB8R0ZGlhYu3qgfF29weW3o8FfVvl2s+vRqpQH92issjOe2AwAAoGCZmVka+8i0EjeC5EpKuqgHHv5Ai74fr9BQ1qIAAAAAAFjN1p+2HQ6HPv30U1WtWlX//ve/nfty5TZ6FBXj0uMbN26s2bNnq0OHDgWO6devn37//Xfndu3atT1JHwAAn5SZmaWpHy7W+9MWKyHhQr7H7Np9TLt2H9MXX/6q6OgojRndT2NG9+MX8QAAAMjX1A8Xe/xomIJs3XZIUz9crIceGGRpXAAAAAAAIAXZnkBQkN566y0tWbJEl19+ufPOIJKcdwsp7CP3+NDQUD388MPaunVroY0gknTZZZepS5cuzo969eqVxqkCAOB1u3YfVf/BL+iFl74qsBEkr1OnLuiFl75S/8EvaNfuo17OEAAAAP4mI+PPZmNvmPrhYmVmZnklNgAAAAAAZZntzSC5evbsqR07dui///2vRowYoWrVqjkbPQr6CAoKUtu2bfXKK6/o6NGjeuutt1ShQgW7TwUAAFusXbdXA2940eN3bG7ddkgDb3hRa9fttTgzAAAA+LOFizfq1KniNRq769SpC/px0UavxAYAAAAAoCzzuXvB9+nTR3369JEkHThwQNu2bdOZM2d07tw5paenKyoqSlWqVFG9evXUoUMHmj8AANCfdwS55Y5JJX6Ge1LSRd1yxyQt/O5ZXX5ZHYuyAwAAgD9bsmybV+MvXb5NNwzu7NU5AAAAAAAoa3yuGeRSjRs3VuPGje1OAwAAn5aZmaWxj0wrcSNIrqSki3rg4Q+06PvxCg316aUCAAAASsGWrXF+HR8AAAAAgLLIlr/w7NixQ19//bVz2+Fw6Mknn1RYWJgd6QAA4NemfrjY40fDFGTrtkOa+uFiPfTAIEvjAgAAwP/sP3DCu/H3x3s1PgAAAAAAZZEtzSBLly7VhAkT5HA4JEldu3bVs88+a0cqAAD4tYyMLE39cLFXYk/9cLHGjO7H3UEAAADKuPT0LK/GT0vP9Gp8AAAAAADKoiA7Jr1w4YIkyTAMSdKAAQPsSAMAAL+3cPFGnTp1wSuxT526oB8XbfRKbAAAAPiP8HDvNgeXCw/1anwAAAAAAMoiW5pBQkLMv0SoW7euHWkAAOD3lizb5tX4S5d7Nz4AAAB8X2zjWt6NHxvj1fgAAAAAAJRFtjSDVK1a1bRdvnx5O9IAAMDvbdka59fxAQAA4PvatG7o1/EBAAAAACiLbGkGadq0qSTJ4XBIkk6dOmVHGgAA+L39B054N/7+eK/GBwAAgO/r06uVV+P37und+AAAAAAAlEW2NIN07txZ4eHhzu1169bZkQYAAH4vPT3Lq/HT0jO9Gh8AAAC+b0C/9oqOjvJK7OjoKA3s394rsQEAAAAAKMtsaQYpX768BgwYIMMwZBiGFi5cqIsXL9qRCgAAfi08PMSr8cuFh3o1PgAAAHxfWFiIxozu55XYY0b3U2iod9e0AAAAAACURbY0g0jS//3f/8nhcMjhcOjs2bN6/fXX7UoFAAC/Fdu4lnfjx8Z4NT4AAAD8w5jR/dS6VQNLY7Zp3VD339vf0pgAAAAAAOBPtjWDXHnllbr//vtlGIYk6aWXXtLcuXPtSgcAAL/UpnVDv44PAAAA/xAaGqJ3p9yrSpXKWxIvMrKC3p1yr0JCgi2JBwAAAAAAzGxrBpGkt956S4MHD5ZhGMrKytLNN9+sJ598UqmpqXamBQCA3+jTq5VX4/fu6d34AAAA8B/NLq+rLz//e4kbQiIjK+g/Mx7T5ZfVsSgzAAAAAACQl63NICEhIZo3b56eeuopBQcHKzs7W//6179Uu3Zt3Xffffryyy+1Z88enT9/Xjk5OXamCgCATxrQr72io6O8Ejs6OkoD+7f3SmwAAAD4p86dmmrhd896/MiY1q0a6Mf5z6hzp6YWZwYAAAAAAC4VYtfEwcGutwF1OBwyDEOJiYn66KOP9NFHH5V4HofDoaysrBLHAQDAF4WFhWjM6H564aWvLI89ZnQ/hYbatlQAAACAj7r8sjpa9P14Tf1wsaZ+uFinTl0ockx0dJTGjO7HGhMAAAAAgFJi20/fhmG47HM4HHI4HAW+DgAAXI0Z3U/ffrdGW7cdsixmm9YNdf+9/S2LBwAAgMASGhqihx4YpDGj++nHRRs1b+E6zd+wXcEnL0pZOVJIkC5rHKPO7Zuod89WGti/PU0gAAAAAACUIlt/Cs9t/HD3teKioQQAUBaEhobo3Sn3auANLyop6WKJ40VGVtC7U+5VSIjrXbwAAACAS4WGhuiGwZ11eaeG+myt+ddMH3ceqmYx0TZlBgAAAABA2RZk5+SGYXj1AwCAsqLZ5XX15ed/V6VK5UsUJzKygv4z4zFdflkdizIDAAAAAAAAAABAabPtziDPPfecXVMDABCQOndqqoXfPasHHv7Ao0fGtG7VQO+9fR+NIAAAAAAAAAAAAH6OZhAAAALI5ZfV0aLvx2vqh4s19cPFOnXqQpFjoqOjNGZ0P40Z3Y/nuAMAAAAAAAAAAAQA/uIDAECACQ0N0UMPDNKY0f3046KNWrh4o76Z97vpmObN6qp9u8bq3bOVBvZvTxMIAAAAAAAAAABAAOEvPwAABKjQ0BDdMLizrurWzKUZZO5XT6p6tUibMgMAAAAAAAAAAIA3BdmdAAAAAAAAAAAAAAAAAKxDMwgAAAAAAAAAAAAAAEAA4TExAAD4ufc/WKT3py0q8PWcHMNlX69rn1VQkKPQuPff21/339e/xPkBAAAAAAAAAACgdPlsM0hmZqZ27typ06dP68yZM7p48aIk6c4777Q5MwAAfEtS8kWdiD/n1piTp84XKy4AAAAAAAAAAAD8j081g6Slpemjjz7SvHnztHr1aqWlpbkcU1gzyC+//KILFy44t1u3bq0mTZp4JVcAAHxFpYrlVSumilfiAgAAAAAAAAAAwP/4TDPI+++/rwkTJuj06dOSJMNwvaW9w1H47eyXLVuml19+2bk9ePBgffvtt5bmCQCAr7n/Ph7nAgAAAAAAAAAAgP+xvRnk4sWLGjVqlGbPnu1sAHE4HC6NH/k1h+T18MMPa9KkSUpPT5dhGFq4cKFOnz6t6tWreyV3AAB8weT9WzV5/1bL4z4W21qPxba2PC4AAAAAAAAAAAC8y9ZmEMMwdOutt+r777+XYRjOBpC8jR9F3REkV40aNXTjjTdq1qxZkqSsrCx9++23+tvf/mZt4gAA+JDEzAwdS0vxSlwAAAAAAAAAAAD4nyA7J58wYYK+++47SX82fBiGodDQUI0aNUpz587Vpk2b1Lx5c7diDh8+3BlPkn7++WdrkwYAwMdEhoapTrmIAj9qhVdwGVMrvEKhY+qUi1BkaJgNZwMAAAAAAAAAAICSsu3OIMeOHdPrr79uuhtI69atNW/ePDVq1Mh5XFiYe3+I6tevn8qXL6+0tDQZhqGlS5damjcAAL6mqMe5JKRfVPTiGaZ9W3rdpBrh5b2dGgAAAAAAAAAAAGxg251BXn31VaWnp0v6sxGkSZMm+u2330yNIJ4IDw9X27ZtnY+aOXPmjE6cOFHifAEAAAAAAGCWkZOtOcf36+kDG11eG/rHEo3avExzju9XRk62DdkBAAAAAFB22XZnkHnz5jkfDeNwOPTRRx+pUqVKlsTu0KGDfv/9d+f2rl27VKtWLUtiAwAAAAAAlHWZOdl6c/82TT6wVSfTL+Z7zK7UC9p1+II+PrxbMeEVNK5xK42LbaXQoOBSzhYAAAAAgLLHlmaQnTt36vjx485HxLRv3149evSwLH7jxo1N24cPH7YsNgAA/iIjJ1vz4+P07Yk4l9d6r/peXapEq390Pd0Q01Bh/EIeAAAAxbQ98azu3LRUGy+cLvaY+PRUPblzjWYf368Z7XrrisiqXswQAAAAAADY0gyyY8cO578dDof69u1rafzKlSubthMTEy2NDwCALyvOuzS3J53T9qRzvEsTAAAAbll1Nl4DVi9UYlaGR+M3Xjitbivma2HXAepWNcbi7AAAAAAAQK4gOyZNSEiQJBmGIUlq2rSppfFzHzeTe+eR5ORkS+MDAOCrtieeVdffvtWTO9cU2AiSV+67NLv+9q22J571coYAAADwV9sTz5aoESRXYlaGBqxeqB1J5yzKDAAAAAAA5GVLM8i5c+Yf9qOioiyNn9v8kdtsUq5cOUvjAwDgi1adjVe3FfPdul33pXLfpbnqbLzFmQEAAMDfZeZk685NS0vcCJIrMStDd2xcosycbEviAQAAAAAAM1uaQSIjI03bSUlJlsbPvfNIrmrVqlkaHwAAX8O7NAEAAOBNb+7f5nHTcUE2XjitN/dvszQmAAAAAAD4ky3NINHR0ZL+9xiXEydOWBp/w4YNpu3q1atbGh8AAF/CuzQBAADgTRk52XrzgHeaNt48sI11JwAAAAAAXmBLM0idOnVM2+vWrbMsdnZ2tpYtW+ZsNJGk1q1bWxYfAABfw7s0AQAA4E3z4+MUn57qldjx6an6Nj7OK7EBAAAQGDJysjXn+H49fWCjy2tD/1iiUZuXac7x/cqgyRgATGxpBunUqZMiIiIkSYZh6Oeff1ZycrIlsWfPnq2TJ086txs1aqS6detaEhsAAF/DuzQBAADgbYtOHfFq/MWnjno1PgAAAPxTZk62Xt+7WfV/nqUR6/+ruacPuRyzK/WCPj68WyPW/1cNfv5Cr+/dzO80AeD/s6UZJDQ0VL169ZJhGJKklJQUvf/++yWOm5iYqOeee04Oh0OGYcjhcOjaa68tcVwAAHwV79IEAACAt204b+1d6FzjJ3g1PgAAAPzP9sSz6vrbt3py5xqdTL9YrDHx6al6cucadf3tW21PPOvlDAHA99nSDCJJd911lyQ5Gzeef/557dixw+N4mZmZuv3227V//37T/gcffLBEeQIA4Mt4lyYAAAC8bXfyee/GT7ng1fgAAADwL6vOxqvbivkePxp744XT6rZivladjbc4MwDwL7Y1g9x0001q166dpD8bQlJTU3XNNddo3bp1bsfav3+/evTooR9++MF0V5Drr79eLVu2tDp1AAB8Bu/SBAAAgLele/k222nZWV6NDwAAAP+xPfGsBqxeqMSsjBLFSczK0IDVC7Uj6ZxFmQGA/7GtGUSS3nrrLQUHB0v6syHk5MmT6tatm/72t79p9erVysoq+JcBJ0+e1Jw5c3TrrbeqefPmWrt2rfOxM5JUqVIlTZo0yevnAACAnXiXJgAAALwtPCjYq/HLBYd4NT4AAAD8Q2ZOtu7ctLTEjSC5ErMydMfGJcr0cnMzAPgqW5tBunfvrnfeecfZxOFwOJSdna1PPvlEV111lSpWrKidO3eamjxq166t8uXLq3bt2rrlllv01VdfKSsry3k3kNz/fvLJJ2rSpIldpwYAQKngXZoAAADwtssrVvZu/Igor8YHAACAfxjxxdcePxqmIBsvnNaIL762NCYA+Atbm0Ek6d5779XLL78sh8MhSc6GDsMwlJGRoYyM/3X/GYah+Ph4paenO4/Jbf7IHRcSEqJ3331XQ4cOteuUAAAoNbxLEwAAAN7WoXJ1L8ev4dX4AAAA8H0ZOdn6pUKiV2L/UiGRu4MAKJNsbwaRpH/84x9avHixoqOjTc0dxf2Q/mwUqV69uhYtWqQxY8bYfEYAAJQO3qUJAAAAb+sfXc+r8ftF1/VqfAAAAPi++fFxSgrJ8UrspJAcfRsf55XYAODLfKIZRJKuueYa7d27V6+99ppq1apluvNHfi59PTIyUhMmTND+/fvVp0+fUs4cAAD78C5NAAAAeNsNMQ0VE17BK7FjwitoSExDr8QGAACA/1h06ohX4y8+ddSr8QHAF/nUvd8rVqyo//u//9Ojjz6qVatWafny5Vq5cqWOHj2qM2fO6Ny5cypfvryqV6+umjVrqkuXLurbt6969uypChW880sJAAB8Wf/oevr48G6vxeddmgAAAAgLCta4xq305M41lsce17iVQr386EMAAAD4vg3nT3s5foJX4wOAL/KpZpBcoaGh6tmzp3r27Gl3KgAA+LTcd2nGp6daHpt3aQIAACDXuNhWmn18vzZesO6X9B2iquux2NaWxQMAAID/2p183rvxUy54NT4A+CKfeUwMAABwX+67NL2Bd2kCAAAgV2hQsGa0663IkDBL4kWFhGlG+z4KCeJXUwAAAJDSc7K9Gj8tO8ur8QHAF/ETNwAAfm5cbCu1j6puaUzepQkAAIC8roisqoVdB5S4ISQqJEw/dh2gFpWqWJQZAAAA/F24l9+UVi7YJx+WAABeRTMIAAB+jndpAgAAoLR0qxqj37sP8bgZuX1Uda3qPkTdqsZYnBkAAAD82eUVK3s3fkSUV+MDgC/irzwAAAQA3qUJAACA0tKiUhWt7j5ErzXvopjwCsUaExNeQa8176LV3Yew1gQAAICLDpWtvfOxa/waXo0PAL6IZhAAAAIE79IEAABAaQkNCtYTTdvqcN+/6quO1+rGGg1cjmleIUqj6jfTVx2v1eG+f9UTTdsq1Mu3/wYAAIB/6h9dz6vx+0XX9Wp8APBFPCALAIAAkvsuzTf3b9ObB7YpPj21yDEx4RU0rnErjYttxS/nAQAA4JbQoGANrx2rVkGV9E3CIdNrc1v2UbOYaJsyAwAAgD+5IaahYsIrFOv3me6KCa+gITENLY8LAL7Op5pBVq1apcWLF2vDhg3avXu3Lly4oAsXLigrK8vjmA6Ho0TjAQDwN7nv0hwX20rfxsdp/ok4zTq2z3RMy0pV1KVKTfWLrqshMQ1pAgEAAAAAAABgm7CgYI1r3EpP7lxjeexxjXkTHICyySeaQebOnatnn31Wu3btcu4zDMPGjAAA8H+579LsVa22SzPIkm6DVSO8vE2ZAQAAAAAAAIDZuNhWmn18vzZeOG1ZzA5R1fVYbGvL4gGAPwmyc/Ls7GzdeeedGj58uHbt2iXDMJwfDoejxB8AAAAAAAAAAAAAfF9oULBmtOutyJAwS+JFhYRpRvs+Cgmy9c+hAGAbW69+f/vb3zRz5sx8G0AubQzx9AMAAAAAAAAAAACAf7gisqoWdh1Q4oaQqJAw/dh1gFpUqmJRZgDgf2x7TMyiRYv02Wefme7gkdvAUaNGDXXo0EGxsbGKiopSaGioXWkCAODzJu/fqsn7txb4ek4+DZJtln2toCLuovVYbGtuoQgAAAAAAACgVHWrGqPfuw/RHRuXePTImPZR1fV5+z40ggAo82xrBpkwYYLz37lNIC1bttTrr7+u6667TkHcsgkAgGJJzMzQsbQUt8acSE8tVlwAAAAAAAAAKG0tKlXR6u5D9Ob+bXrzwDbFF+P3mTHhFTSucSuNi22l0KDgUsgSAHybLc0gp06d0rp165yPg3E4HOrXr5++/fZbhYVZ8xwwAADKisjQMNUpF+GVuAAAAAAAAABgh9CgYD3RtK3GxbbSt/Fxmn8iTrOO7TMd07JSFXWpUlP9outqSExDmkAA4BK2NIOsXLnS2QQiSVFRUfr8889pBAEAwAM8zgUAAAAAAABAoAoNCtbw2rHqVa22SzPIkm6DVSO8vE2ZAYBvs+VZLCdPnnT+2+FwaNiwYapWrZodqQAAAAAAAAAAAAAAAAQUW+4McvbsWUly3h2kS5cudqRRqnJycrRhwwZt27ZNp06dkmEYqlatmlq0aKEuXbooNDTU7hQlSUeOHNGaNWt06NAhXbx4URUrVlTjxo115ZVXqkaNGpbPd+HCBa1atUr79u1TYmKiwsPDVadOHXXs2FFNmza1fD4AAAAAAAAAAAAAAAKdLc0gFSpUMG1XrVrVjjRKRXJyst544w1NnTpVp06dyveYqKgojRw5Uk8//bRXGi6KY/78+Xr11Ve1evXqfF8PCgrSNddco2eeeUY9evQo8XxbtmzRCy+8oO+//16ZmZn5HnPFFVfo8ccf11133eV8pBAAAAAAAAAAAAAAACicLY+JadCggWn7/PnzdqThdevXr9cVV1yhF154ocBGEOnPu2NMmTJFzZo106JFi0oxQyklJUUjRozQkCFDCmwEkf68s8nPP/+snj176pFHHlFWVpbHc77yyivq2LGj5s6dW2AjiCRt375dd999t/r06aOEhASP5wMAAAAAAAAAAAAAoCyxpRmkU6dOkuS828OBAwfsSMOr1qxZo969e+vw4cMur4WHh6t8+fIu+8+ePavBgwfru+++K40UdfHiRQ0cOFBz5sxxec3hcCgqKirfcW+//bZuv/12GYbh9pz/93//p6eeeirfZpJKlSopKMj1W3LZsmXq2bOnzpw54/Z8AAAAAAAAAAAAAACUNbY0g9StW1ddu3Z1NhMsXrzYjjS8JiEhQUOHDlVycrJzX0hIiB599FHt3r1bqampSklJUVxcnJ599llFREQ4j8vKytJtt92mPXv2eD3Phx9+WL/++qtp39VXX61FixYpJSVF58+fV2JiombPnq2WLVuajps9e7ZeeeUVt+abNWuW/vWvf5n2NWzYUB9++KHOnTunxMREXbx4Ub/99psGDRpkOm7nzp3661//6lEDCgAAAAAAAAAAAAAAZYktzSDSn3eIkCTDMLRp0yYtX77crlQsN2HCBJ04ccK5HR4ernnz5unNN9/UZZddpqCgIDkcDjVo0EAvvPCCfvnlF1WpUsV5fHJysh577DGv5rhu3Tp99NFHpn0jR47UsmXL1K9fP+edSypVqqQRI0Zo9erV6tu3r+n4F154QUePHi3WfCkpKS7n1K5dO61du1Z/+9vfVLlyZUlSWFiYrr76ai1YsEBPPfWU6fiffvpJ33zzjTunCQAAAAAAAAAAAABAmWNbM8jQoUN1/fXXO7fvv/9+nT9/3q50LBMXF+fSZPH888+bzjWvLl266N133zXt++GHH/T77797JUdJevrpp03brVq10rRp0xQcHJzv8REREZo9e7ZiYmKc+9LT0zVx4sRizTdlyhSdOnXKuV2hQgV9/fXXqlGjRoFjXnrpJfXr18+0b/z48crJySnWnAAAAAAAAAAAAAAAlEW2NYNI0syZM3XFFVfIMAzt3r1bAwYM0LFjx+xMqcTefPNNZWRkOLcbNWqkv//970WOu/XWW3X11Veb9r322muW5ydJmzZt0s8//2za99Zbbyk0NLTQcVWqVNGLL75o2vfxxx8rISGh0HFZWVmaPHmyad/f//53NW7cuMhc33nnHTkcDuf2zp079d133xU5DgAAAAAAAAAAAACAssrWZpDIyEgtX75cV199tQzD0Nq1a9W6dWtNnDjR9JgVf/Ltt9+atkeNGqWQkJBijb333ntN2z/99JNSU1OtSs1p3rx5pu2mTZuqT58+xRp7yy23qFKlSs7trKwsLViwoNAxv/76q86cOePcDgoK0ujRo4s1X5MmTdS7d2/Tvrz5AwAAAAAAAAAAAACA/ylel4IX3HPPPc5/N2jQQOvXr1d6errOnTunCRMmaMKECYqNjVXTpk1VtWrVIu9aURCHw6Hp06dblXahNm3apMOHD5v23XzzzcUef+ONN+qee+5RVlaWJOnixYv66aefNGTIECvT1Pz5803bI0aMKPbYiIgIDR48WF988YUp3t13313s+a688krVq1ev2HPecsstWrJkiXP7hx9+UHZ2doGPtAEAAAAAAAAAAAAAoCyzrRnk008/NT3+I5fD4ZBhGJKkffv2af/+/R7PYRhGqTaDXNqwIEk1a9ZUkyZNij2+QoUKatu2rdavX+/c98svv1jaDHL69Glt3brVtO+qq65yK0a3bt1MzSB5zzuvvK97Mt+lzpw5o82bN6tDhw5uxQEAAAAAAAAAAAAAoCyw9TEx0p8NG7nNH7kcDofzI/d1dz/ssGPHDtN2586d3Y7RtWtX0/bOnTtLlFNe+cXr0qWLWzHy5piUlKSjR4/me2x2drb27NlTovlatGihyMhI0z6rPy8AAAAAAAAAAAAAAAQK25tBcps+inrd3Q877Nq1y7TduHFjt2PkHZM3ZknljRcVFaWqVau6FSO/8yoozwMHDigjI6PI8YVxOBxq2LBhseYDAAAAAAAAAAAAAKCss+0xMfXr17etacNb8t4Bo379+m7HqFevnmn72LFjSklJUURERIlyy2VFjlWqVFFERIRSUlKc+3bv3q1rr722yPk8nbNevXqmx9vs3r3b7RgAAAAAAAAAAAAAAJQFtjWDxMXF2TW115w7d860HRMT43aMWrVq5RvXqmaQs2fPmrY9yVH6M899+/Y5t/Oee0HzhYaGun0nktz5LlXQfFb6y1/+ovDwcEti3XLLLXrkkUcKPWbKlCn68ssvLZkv1++//17o66dOndINN9xg6ZwPP/ywbr311kKPefrpp7Vk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+v3zn/+0MGN4E7Xn36i9wEVt2ovaClzUVulhzYlLUXv+jdoLXNSmvaitwGVVbaWlpWnJkiX69NNP9frrr+vVV1/VRx99pFWrVikzM9PirP0Ta05citrzb9Re4KI27VWma8sA4DMee+wxQ5IhyYiKijJOnz7t1vgzZ84YixYtMiZOnGgMHjzYiImJccbL/fjkk0+8k7wfyMzMND788EOjZcuWLp+XSz9atGhhTJ061cjKynJ7jilTpjjjhIWFGXv27PHCmcBq1J535eTkGHv27DFmzpxpPPLII8aVV15plCtXzuVzVBLUXmCiNs0++eSTQv//5cnHc889V+ic1FZg8qS2GjRoUOLvN3+qt5JgzYmCUHvexZoTnqI2zVhzwiol/Xlu/fr1xg033JDvtTz3IzIy0hg7dqxx7NgxL52F72LNiYJQe97FmhOeojbNWHOWHppBAB+xc+dOIzQ01HkhevHFF4s1btasWcatt95qxMbG+vUvG7xt586dRvPmzd36H0eHDh2MgwcPujVPenq66ZdC119/vXdOCJah9rwjKSnJeOqpp4y+ffsalStXLtbnqCSovcBDbbryxg9JEydOLHROaivweFpbgfxHLyux5kRBqD3vYM2JkqI2XbHmhBU8rS3D+PP74d577zUcDkexv8cqVqxofPHFF148I9/CmhMFofa8gzUnSoradMWas/TQDAL4iJtuusl5AYqMjDTOnz9frHE9e/YMiF82eNOKFSsKXKQFBQUZVapUMYKDg/N9vXbt2sbevXvdmu/tt982xVixYoWXzgxWoPa84+DBg24v1kqK2gss1KYrb/yQtG7duiLnpbYCi6e1Fch/9LIKa04UhtrzDtacKClq0xVrTljB09pKSUkp9Ge6SpUqGRUqVCjw9XfffdfLZ2Y/1pwoDLXnHaw5UVLUpivWnKXHYRiGIQC22rRpkzp06KDccnziiSf02muvFWtsr169tHz58mLP9cknn2jkyJGepOmXjh8/rvbt2+vkyZOm/cOGDdPDDz+sq666SiEhIcrJydHGjRs1bdo0TZ8+XTk5Oc5jW7RoobVr1yoiIqJYc6ampqpBgwY6ffq0pD+/RkuXLrXupGAZas974uLi1KhRI7fGlHRJQu0FDmozf7t37y7R9/Q///lPnT9/3rndsmVLbdu2rchx1FbgKEltNWzYUIcOHXJuv/jii6pWrZpb8/fu3VuXX365W2P8BWtOFIba8x7WnCgJajN/rDlRUiWprTvuuEMzZ8407WvcuLGeffZZ/eUvf1HVqlUlSSdOnNA333yjF1980bT+CgoK0k8//aRrrrnGorPxLaw5URhqz3tYc6IkqM38seYsRXZ0oAAwu/HGG51daMHBwcbhw4eLPfbSrsCgoCCjefPmxp133mn8+9//NlavXu3SGeer7zzxlkGDBpnO3+FwGNOmTSt0zI8//ujy3LUJEya4Ne9TTz1lGv/rr7+W5DTgJdSe9+TtmI+IiDC6d+9uPPbYY8Z//vMfY+LEiZZ3zBsGtRcoqE3rbdy40eXc//WvfxV7PLUVGEpSW3nfAe3uLaYDHWtOFIba8x7WnCgJatN6rDlhGJ7X1pw5c1y+f6677jojJSWlwDGnT5822rdvbxrTtGlTIzMz06rT8SmsOVEYas97WHOiJKhN67HmdA/NIIDNDhw4YAQFBTkvOoMGDXJr/Isvvmi89tprxtKlS43ExESX18vqH70MwzA2bNjgcv5PPPFEscZ+8MEHpnGVKlUyTp06Vey59+/fb3qG27Bhwzw9DXgJteddJ0+eNMaMGWNMnz7d2Lp1q5GVlWV6Pb/bwFmB2vN/1KZ3PPzww6bzDgkJMeLj44s9ntryfyWtLf7oVTDWnCgMteddrDnhKWrTO1hzoiS11aZNG9P3T+PGjY3k5OQix504ccKoUqWKaez7779fktPwSaw5URhqz7tYc8JT1KZ3sOZ0D80ggM3GjRtnumjNmzfP0vhl9Y9ehmEY9913n+ncq1ataqSmphZ7fMuWLU3jn3nmGbfm79Onj3NsUFAQvxzyMdSevbz1Q5JhUHv+jtq0XkZGhlG9enXTeQ8ePNjtONSWfytpbfFHr4Kx5kRhqD17seZEQahN67HmhGF4Xlu///67y/V69uzZxZ73jTfeMI2tV6+ekZOT4+FZ+CbWnCgMtWcv1pwoCLVpPdac7gsSANtkZWXp888/d25XqlRJAwYMsDGjwLJkyRLT9q233qry5csXe/w999xj2v7666/dmn/48OHOf+fk5GjGjBlujYf3UHuBjdrzX9SmdyxYsMD5HMxcI0eOdDsOteW/qC3vYs2JglB7gY3a81/Upnew5kRJaivveqpy5coaOnRosee+++67FRT0vz91HDlyRGvXri32eH/AmhMFofYCG7Xnv6hN72DN6T6aQQAbLVq0yHTRGjRokMLDw23MKHCcO3dOe/fuNe3r3r27WzGuvvpq0/auXbu0c+fOYo8fOnSo6X+4M2fOdGt+eA+1F9ioPf9FbXrHp59+atquXr26Bg8e7HYcast/UVvew5oThaH2Ahu157+oTe9gzYmS1NaaNWtM2127dlVoaGix565WrZqaNWtm2jd37txij/d1rDlRGGovsFF7/ova9A7WnO6jGQSw0VdffWXa7t+/v02ZBJ6TJ0+67GvSpIlbMZo2beqy77///W+xx9esWVPt27d3bu/du1cbN250Kwd4B7UX2Kg9/0VtWi8hIUELFy407fvrX//q1g+Quagt/0VteQ9rThSG2gts1J7/ojatx5oTUslqK++ayt31lOS6pnJnPeXrWHOiMNReYKP2/Be1aT3WnJ6hGQSwiWEYWrx4sWlfr1697EkmAJ09e9ZlX1RUlFsxIiMjXfZt377drRh5v6Z5/0eF0kftlQ3Unv+hNr1j5syZyszMNO3z5NaJuagt/0NteRdrThSE2isbqD3/Q216B2tOlLS28q6p3F1P5Tdm165dysnJcTuOL2LNiYJQe2UDted/qE3vYM3pGZpBAJts3rxZp06dcm7Xr19fDRo0sDGjwJLf7bbS09PdipHf8e7cPlGSevToYdpetGiRW+NhPWqvbKD2/A+16R2fffaZabtNmzZq166dx/GoLf/jzdqKi4vTwoULNWPGDH3++ef68ccftWHDBpcfzAMZa04UhNorG6g9/0NtegdrTpS0tvKuqdxdT0lSWlqaaTs1NVWHDh1yO44vYs2JglB7ZQO153+oTe9gzemZELsTAMqq1atXm7bbtGljUyaBqWrVqi77EhIS3IqR3/G7d+92K0ber+v69euVlZWlkBAuv3ah9soGas//UJvW27Rpk7Zs2WLaV5JueYna8kfeqq2OHTvqzJkz+b5Wvnx5XXnllRo1apRGjBgR0N8frDlREGqvbKD2/A+1aT3WnJBKXlt511TurqcKGrN79241atTI7Vi+hjUnCkLtlQ3Unv+hNq3HmtNz3BkEsMn69etN261atbIpk8BUq1YthYWFmfZt2LDBrRj5PR8sv9syFqZ+/fqqXLmyczstLU3btm1zKwasRe2VDdSe/6E2rffpp5+atkNDQ3XbbbeVKCa15X+8VVsF/cFL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as//UJslw5oTBbHr57msrCyNHDlSx44dc+675pprdNtttxVrvK/Uljex5gxs1F7ZQO35H2qzZFhzWotmEMAmDofD5cKybNkye5IJYPHx8VqyZEmxj//ll1/Uu3dvnTx50rmvb9++uuOOOzyaP+//TAYOHOhRHFiH2isbqD3/Q216btOmTS6d7HfffbdX5qK2/E9Ja+vXX3/VwIED9dtvv7k175QpU3TnnXea/mjmcDj0+uuvuxXHX7DmRF7UXtlA7fkfatNzrDlRGKt+nvvuu++UlJRUrGPj4+M1bNgwffPNN8595cuX19SpU92e11+w5kRe1F7ZQO35H2rTc6w5rRd49zoB/MiIESP0ySefOLd//PFHjRo1yq0YSUlJmjVrVrGPX7p0qdLS0vJ9rWPHji7PMvN38fHxuuaaa9S0aVMNGTJE1157rdq2bavo6GhJf76rJyEhQb/88ov+85//6PvvvzeNj42Ndevze6mTJ09q06ZNzu0mTZqoXbt2np8MLEPtlY7169dr/fr1+b72+++/u+wrbGF62223qVKlSsWal9rzX9SmZz799FPTdnh4uG699VbL56G2/FdJasswDC1cuFALFy5U48aNddNNN+mqq65S27ZtVbduXQUFBTmP27t3r5YsWaJ3331Xf/zxh0us5557Tp06dbLmpHwMa07kh9orHaw54S5q0zOsOVEUK36eGz9+vA4cOKBBgwZp0KBB6tSpk5o2beqsrdTUVG3cuFHz58/XtGnTlJiY6BwbFBSkGTNmqEmTJtackA9izYn8UHulgzUn3EVteoY1pxcYAGyTlZVlxMTEGJIMSUaFChWM1NRUt2IcPHjQOb6kH88995xH8z3//PMefga8b9OmTfmea1hYmFGtWjUjJCSkwM9H8+bNjcOHD3s89/vvv+83n6eyhtorHc8995xln6ODBw8We15qz39Rm+7LyMgwqlevbpr/5ptv9spc1Jb/KkltLV26tMAacTgcRqVKlYyqVasaQUFBhdbTo48+6lbOdteWu1hzIj/UXulgzQl3UZvuY82J4rDi57k2bdq4fK8HBQUZlStXNiIiIgqsqdDQUOOLL75wO2e7a8tdrDmRH2qvdLDmhLuoTfex5vQOHhMD2Cg4ONh0W77U1FT98MMPNmZUtB07dpi2HQ6Hhg0bZlM2nsvIyNCZM2eUlZXl8prD4dA9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RnJystufr8K+D3JycoxZs2YZXbt2LfD/GZde2/OaO3eucf311xuhoaHF+vzUrl3bGD58uDFz5kzjwoULbp+LJ26//XZTDi1atPAoToMGDUr0/53CuHMt8RVt27Yt9rW8uJo1a+YSc8mSJW7HufPOO00xmjRpUqK8AAAAYA/fXxUDAAAACCi+2gwye/Zso3r16h7/8apChQrGO++843bOhf1B/t133zXCw8OLncMjjzxi5OTkmOKnp6cb999/f7FjhISEGDNmzLDsHKwaV1rNIF9++aVRp04dt2NfccUVxvLly936vBXWDPLrr7+6lUf58uWN+fPnuzW/p1+306dPG+3atSvR1+KGG24wzp0751a+hlFwk8HRo0eNK6+80q0cnnrqKbfnLwkrr321a9d2iTVr1izTMb74dVq9erVRv379IufNz6233mqEhIR4fC7Nmzf3qNmtoCaACxcuGIMGDSr2/FFRUcYvv/ziEn/Hjh2FNjDl/WjSpIlHzV+54uLijGHDhnn0ORw2bFiR3w+l2Qzy4YcfGrVq1XI7fs2aNY05c+a49Xkr6PsgPj7e6NGjR5Fz5tcMcujQoWKNLezjySefdOs8PJGdne2yPnrggQc8ikUziNm///1vl5xvuukmj+OtXLnSJV7jxo1d1obFkV8t79692+PcAAAAYI/Sue8nAAAAAPi4HTt26PTp0x6PT01N1YMPPqj777/fknxefvlljR07Vunp6cUeM2XKFD399NPO7YyMDA0ZMkTvv/9+sWNkZWVp5MiRmj9/vlv5BoKJEyfqlltu0bFjx9weu337dvXt21f/+c9/SpzHggULdO2117qVx8WLF3XjjTdq0aJFJZ6/KElJSdq0aVOJYsyfP1+dO3fW0aNHS5zPgQMH1KVLF/3+++9ujXv55Zf1zDPPlHh+O1y4cMFlX+XKlU3bvvZ1+vXXX9WrVy8dPnzYo/GrVq1yefSBO3bu3KmuXbvqv//9r8cxciUlJal379764Ycfij3mwoULGjx4sDZv3uzct3nzZnXv3l1//PFHsePs27dPvXr1yvd7oCirV69W586dNXfuXLfHStLcuXPVtWtX7du3z6PxVsnMzNTf/vY3jR49WidOnHB7/MmTJzVixAhNnDixRHnEx8erW7du+vXXX90eGxcXp6uvvtqjsaVt3bp1LuujXr162ZNMgLnttttcHmX13Xff6cyZMx7F++STT1z23XPPPXI4HG7H6t27t8u+hQsXepQXAAAA7OMfD/0EAAAAgFLWoEEDtWvXTi1atFDdunVVqVIllS9fXsnJyTp+/Lg2b96sxYsXu/xBburUqWrVqpUeeOABj+f+9ttvTU0dNWvW1PXXX6/27durevXqSkpK0pYtW/TVV1/p5MmTprGvvvqqhgwZos6dO2vs2LGmX9w3a9ZM119/vZo2barKlSvr7NmzWrFihb7++mtT00lOTo7uv/9+9erVy61nzHtTkyZNdP78eUl//gEu73m3adOmyBhVq1Yt8LWJEydq/PjxLvtDQkLUu3dvXXvttapTp46ysrJ05MgR/fjjj1q9erUMw3Aem5GRodtuu03BwcEaMWJEMc/MbPPmzfrnP/+pjIwMSVL58uV1zTXXqEePHoqJiVFISIiOHDmin376Sb/88otpbFZWlv72t79p+/btpfp1q1ixojp16qTmzZuradOmioqKUqVKlZSRkaFz585px44dWrp0qXbu3Gkat3fvXt18881avny5QkI8+/VEUlKSBgwY4GyccTgc6tatm6699lrVr19fFStWVEJCglauXKl58+YpLS3NNP7VV1/V4MGD1aVLF89O3gaHDh1SSkqKy/4aNWoUOs7Or1N8fLyGDRtm+vx37txZ1113nRo0aKBKlSrpxIkT2rFjh+bMmVNkvODgYLVv315XXHGFmjVrpmrVqikyMlKGYSgxMVF79+7V6tWrtXLlSuXk5DjHJScn65ZbbtGmTZtUr149j85Fku68805t3LjRud2hQwcNGDBAjRo1UsWKFRUfH68lS5bo+++/N82fmpqqu+66Sxs3btTp06d1/fXXO//oGxoaqt69e6tPnz6qXbu2QkJCFBcXp/nz52vNmjWm+Q8cOKB//vOfeu+994qd87JlyzRgwACXGggKClL37t3VrVs3NWrUSJUrV9bFixd19OhRLV++XL/88ouys7Odx+/evVsDBw7U+vXrFRkZ6TJP1apVndfjjIwMl++nevXqFXotlv683hckJydHQ4YM0Y8//ujyWu3atXXNNdeoXbt2ql69usqVK6ezZ89q06ZNWrhwoakRyTAMjR8/XtWrV/eoiTMnJ0cjRozQgQMHnPsaN26sQYMGqVmzZqpevbrOnDmjgwcP6ptvvnEZf8899+jIkSMu+9u2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pdatW1vOWRgMHjzYFJszZ46OHz+uH3/80RCvWLGiunbt6tH+L126pAMHDhhijmYbAgAAQOFGMQgAAACA21pISIgplvM/2XOTlZWlwYMHW/62M1wXGBhoiv3++++Wcj3//POm2MiRI3XhwgWncyQlJWny5MmGmM1m0wsvvGBpTIWVv7+/SpcubYi5cq1I0qxZs7R27VoPjgo5FbfjlPMevWnTJmVkZDjdfs+ePZo0aZKnh1VkjBkzxhQbOXKk5XumK/z8/Eyzg1jtt2XLlqbClt9//12vvPKK1eEVWX/99Zdhu3z58nnaX/v27VWihPG/kHPOhOUsHx8fDRs2zBR/9dVXLf8d9fLLL5tijz76qCpVqmQpX2ExYMAA0/Xz2Wefac6cOab36qmnnnI4E487Nm/ebCq2evDBBz3aBwAAAPIexSAAAAAAbmutWrUyxSZMmODUFOhZWVl6+umntW7durwYGm6ifv36plhMTIylXF26dDF9y/rw4cPq27evU984T0tL00MPPWQqHunevbvuvfdeS2MqzCIiIgzba9eudfq9X7Fihf7v//4vL4aFHIrTccp5jz5//rzeeustp9oePnxYPXr00OXLl/NiaEVCmzZtTEuspKWlqVOnTtq3b5+lnJcuXdInn3xyy9ljSpQoobp16xpiK1euVFZWlqV+J0yYIJvNZohNnz5d48aNs7zMyO7duzVgwAClpaVZam/FK6+8or1791pqGxcXZyrEaNiwoSeGdVPlypUzzQjhzkwew4cPNy01EhcXp2effdblc+Ptt9/WsmXLTPGRI0e6lKdt27ay2WyGn7lz57qUw9OCg4NNs30cP35cEydONL3W0Swi7sp5jH19fdWuXTuP9wMAAIC8RTEIAAAAgNtaSEiI6cHpoUOH9OCDDyopKemm7RISEtSpUyfNmjVLklSyZEnTt/GRN+677z7TNPOTJk3S3LlzdfHiRZdy2Ww2zZ49W15eXob40qVL1bFjRx06dOimbbdu3aqIiAjt3LnTEA8ICNAHH3zg0jiKiscff9wU6927t5YsWXLTNhcvXtTbb7+thx56KPv45Dx+8KzidJweffRR06wE7777rt54441cC7a+/PJLPfDAA9kzURSGfSkoc+fONc2ScPDgQd1///2aNGmSU8uM2O12bdq0SSNGjFD16tX1zDPPODXLR4sWLQzbCQkJ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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots(figsize=(11, 7), dpi=200)\n", + "\n", + "plt.errorbar(\n", + " plot_labels,\n", + " physical_energy_diff,\n", + " yerr=physical_uncertainties.values(),\n", + " ecolor=(20 / 255.0, 26 / 255.0, 94 / 255.0),\n", + " color=(20 / 255.0, 26 / 255.0, 94 / 255.0),\n", + " capsize=4,\n", + " elinewidth=1.5,\n", + " fmt=\"o\",\n", + " markersize=8,\n", + " markeredgewidth=1,\n", + " label=\"Physical\",\n", + ")\n", + "plt.errorbar(\n", + " plot_labels,\n", + " logical_energy_diff,\n", + " yerr=logical_uncertainties.values(),\n", + " color=(0, 177 / 255.0, 152 / 255.0),\n", + " ecolor=(0, 177 / 255.0, 152 / 255.0),\n", + " capsize=4,\n", + " elinewidth=1.5,\n", + " fmt=\"o\",\n", + " markersize=8,\n", + " markeredgewidth=1,\n", + " label=\"Logical\",\n", + ")\n", + "\n", + "ax.set_xlabel(\"Hamiltonian Parameters (U, V)\", fontsize=18)\n", + "ax.set_ylabel(\"Energy above true ground state (in eV)\", fontsize=18)\n", + "ax.set_title(\"CUDA-Q AIM Infleqtion Hardware Execution (lower is better)\", fontsize=20)\n", + "ax.legend(loc=\"upper left\", fontsize=18.5)\n", + "plt.xticks(fontsize=16)\n", + "plt.yticks(fontsize=16)\n", + "\n", + "ax.axhline(y=0, color=\"black\", linestyle=\"--\", linewidth=2)\n", + "plt.ylim(top=max(physical_energy_diff) + max(physical_uncertainties.values()) + 0.2, bottom=-0.2)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.16" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/sphinx/targets/cpp/infleqtion.cpp b/docs/sphinx/targets/cpp/infleqtion.cpp new file mode 100644 index 00000000000..4a61b71571b --- /dev/null +++ b/docs/sphinx/targets/cpp/infleqtion.cpp @@ -0,0 +1,65 @@ +// Compile and run with: +// ``` +// nvq++ --target infleqtion infleqtion.cpp -o out.x && ./out.x +// ``` +// This will submit the job to the Infleqtion's ideal simulator, +// cq_sqale_simulator (default). Alternatively, we can enable hardware noise +// model simulation by specifying `noise-sim` to the flag `--infleqtion-method`, +// e.g., +// ``` +// nvq++ --target infleqtion --infleqtion-machine cq_sqale_qpu +// --infleqtion-method noise-sim infleqtion.cpp -o out.x && ./out.x +// ``` +// where "noise-sim" instructs Superstaq to perform a noisy emulation of the +// QPU. An ideal dry-run execution on the QPU may be performed by passing +// `dry-run` to the `--infleqtion-method` flag, e.g., +// ``` +// nvq++ --target infleqtion --infleqtion-machine cq_sqale_qpu +// --infleqtion-method dry-run infleqtion.cpp -o out.x && ./out.x +// ``` +// Note: If targeting ideal cloud simulation, `--infleqtion-machine +// cq_sqale_simulator` is optional since it is the default configuration if not +// provided. + +#include +#include + +// Define a simple quantum kernel to execute on Infleqtion backends. +struct ghz { + // Maximally entangled state between 5 qubits. + auto operator()() __qpu__ { + cudaq::qvector q(5); + h(q[0]); + for (int i = 0; i < 4; i++) { + x(q[i], q[i + 1]); + } + auto result = mz(q); + } +}; + +int main() { + // Submit to infleqtion asynchronously (e.g., continue executing + // code in the file until the job has been returned). + auto future = cudaq::sample_async(ghz{}); + // ... classical code to execute in the meantime ... + + // Can write the future to file: + { + std::ofstream out("saveMe.json"); + out << future; + } + + // Then come back and read it in later. + cudaq::async_result readIn; + std::ifstream in("saveMe.json"); + in >> readIn; + + // Get the results of the read in future. + auto async_counts = readIn.get(); + async_counts.dump(); + + // OR: Submit to infleqtion synchronously (e.g., wait for the job + // result to be returned before proceeding). + auto counts = cudaq::sample(ghz{}); + counts.dump(); +} diff --git a/docs/sphinx/targets/python/infleqtion.py b/docs/sphinx/targets/python/infleqtion.py new file mode 100644 index 00000000000..20cb330d1df --- /dev/null +++ b/docs/sphinx/targets/python/infleqtion.py @@ -0,0 +1,54 @@ +import cudaq + +# You only have to set the target once! No need to redefine it +# for every execution call on your kernel. +# To use different targets in the same file, you must update +# it via another call to `cudaq.set_target()` +cudaq.set_target("infleqtion") + + +# Create the kernel we'd like to execute on Infleqtion. +@cudaq.kernel +def kernel(): + qvector = cudaq.qvector(2) + h(qvector[0]) + x.ctrl(qvector[0], qvector[1]) + mz(qvector) + + +# Note: All measurements must be terminal when performing the sampling. + +# Execute on Infleqtion and print out the results. + +# Option A (recommended): +# By using the asynchronous `cudaq.sample_async`, the remaining +# classical code will be executed while the job is being handled +# by the Superstaq API. This is ideal when submitting via a queue +# over the cloud. +async_results = cudaq.sample_async(kernel) +# ... more classical code to run ... + +# We can either retrieve the results later in the program with +# ``` +# async_counts = async_results.get() +# ``` +# or we can also write the job reference (`async_results`) to +# a file and load it later or from a different process. +file = open("future.txt", "w") +file.write(str(async_results)) +file.close() + +# We can later read the file content and retrieve the job +# information and results. +same_file = open("future.txt", "r") +retrieved_async_results = cudaq.AsyncSampleResult(str(same_file.read())) + +counts = retrieved_async_results.get() +print(counts) + +# Option B: +# By using the synchronous `cudaq.sample`, the execution of +# any remaining classical code in the file will occur only +# after the job has been returned from Superstaq. +counts = cudaq.sample(kernel) +print(counts) diff --git a/docs/sphinx/using/applications.rst b/docs/sphinx/using/applications.rst index 572187128f5..b3119decf4c 100644 --- a/docs/sphinx/using/applications.rst +++ b/docs/sphinx/using/applications.rst @@ -22,7 +22,8 @@ Applications that give an in depth view of CUDA-Q and its applications in Python /applications/python/quantum_teleportation.ipynb /applications/python/quantum_volume.ipynb /applications/python/readout_error_mitigation.ipynb + /applications/python/vqe.ipynb /applications/python/vqe_advanced.ipynb /applications/python/hadamard_test.ipynb - /applications/python/vqe.ipynb - \ No newline at end of file + /applications/python/logical_aim_sqale.ipynb + diff --git a/docs/sphinx/using/backends/backends.rst b/docs/sphinx/using/backends/backends.rst index e5a8c9b087b..9ee2bbbbbf7 100644 --- a/docs/sphinx/using/backends/backends.rst +++ b/docs/sphinx/using/backends/backends.rst @@ -12,11 +12,12 @@ CUDA-Q Backends **The following is a comprehensive list of the available targets in CUDA-Q:** +* :ref:`anyon ` * :ref:`braket ` * :ref:`density-matrix-cpu ` * :ref:`fermioniq ` +* :ref:`infleqtion ` * :ref:`ionq ` -* :ref:`anyon ` * :ref:`iqm ` * :ref:`nvidia ` * :ref:`nvidia-fp64 ` @@ -35,6 +36,6 @@ CUDA-Q Backends * :ref:`tensornet-mps ` .. deprecated:: 0.8 - The `nvidia-fp64`, `nvidia-mgpu`, `nvidia-mqpu`, and `nvidia-mqpu-fp64` targets can be + The `nvidia-fp64`, `nvidia-mgpu`, `nvidia-mqpu`, and `nvidia-mqpu-fp64` targets can be enabled as extensions of the unified `nvidia` target (see `nvidia` :ref:`target documentation `). These target names might be removed in a future release. \ No newline at end of file diff --git a/docs/sphinx/using/backends/hardware.rst b/docs/sphinx/using/backends/hardware.rst index 366e8371738..33f4907cecf 100644 --- a/docs/sphinx/using/backends/hardware.rst +++ b/docs/sphinx/using/backends/hardware.rst @@ -112,6 +112,128 @@ To see a complete example for using Amazon Braket backends, take a look at our : The ``cudaq.observe`` API is not yet supported on the `braket` target. +Infleqtion +================================== + +.. _infleqtion-backend: + +Infleqtion is a quantum hardware provider of gate-based neutral atom quantum computers. Their backends may be +accessed via `Superstaq `__, Infleqtion’s cross-platform software API +that performs low-level compilation and cross-layer optimization. To get started users can create a Superstaq +account by following `these instructions `__. + +For access to Infleqtion's neutral atom quantum computer, Sqale, +`pre-registration `__ is now open. + +Setting Credentials +````````````````````````` + +Programmers of CUDA-Q may access Infleqtion backends from either C++ or Python. Generate +an API key from your `Superstaq account `__ and export +it as an environment variable: + +.. code:: bash + + export SUPERSTAQ_API_KEY="superstaq_api_key" + +Submission from C++ +````````````````````````` + +To target quantum kernel code for execution on Infleqtion's backends, +pass the flag ``--target infleqtion`` to the ``nvq++`` compiler. + +.. code:: bash + + nvq++ --target infleqtion src.cpp + +This will take the API key and handle all authentication with, and submission to, Infleqtion's QPU +(or simulator). By default, quantum kernel code will be submitted to Infleqtion's Sqale +simulator. + +To execute your kernels on a QPU, pass the ``--infleqtion-machine`` flag to the ``nvq++`` compiler +to specify which machine to submit quantum kernels to: + +.. code:: bash + + nvq++ --target infleqtion --infleqtion-machine cq_sqale_qpu src.cpp ... + +where ``cq_sqale_qpu`` is an example of a physical QPU. + +To run an ideal dry-run execution on the QPU, additionally pass ``dry-run`` with the ``--infleqtion-method`` +flag to the ``nvq++`` compiler: + +.. code:: bash + + nvq++ --target infleqtion --infleqtion-machine cq_sqale_qpu --infleqtion-method dry-run src.cpp ... + +To noisily simulate the QPU instead, pass ``noise-sim`` to the ``--infleqtion-method`` flag like so: + +.. code:: bash + + nvq++ --target infleqtion --infleqtion-machine cq_sqale_qpu --infleqtion-method noise-sim src.cpp ... + +Alternatively, to emulate the Infleqtion machine locally, without submitting through the cloud, +you can also pass the ``--emulate`` flag to ``nvq++``. This will emit any target +specific compiler diagnostics, before running a noise free emulation. + +.. code:: bash + + nvq++ --emulate --target infleqtion src.cpp + +To see a complete example for using Infleqtion's backends, take a look at our :doc:`C++ examples <../examples/examples>`. + +Submission from Python +````````````````````````` + +The target to which quantum kernels are submitted +can be controlled with the ``cudaq::set_target()`` function. + +.. code:: python + + cudaq.set_target("infleqtion") + +By default, quantum kernel code will be submitted to Infleqtion's Sqale +simulator. + +To specify which Infleqtion QPU to use, set the :code:`machine` parameter. + +.. code:: python + + cudaq.set_target("infleqtion", machine="cq_sqale_qpu") + +where ``cq_sqale_qpu`` is an example of a physical QPU. + +To run an ideal dry-run execution of the QPU, additionally set the ``method`` flag to ``"dry-run"``. + +.. code:: python + + cudaq.set_target("infleqtion", machine="cq_sqale_qpu", method="dry-run") + +To noisily simulate the QPU instead, set the ``method`` flag to ``"noise-sim"``. + +.. code:: python + + cudaq.set_target("infleqtion", machine="cq_sqale_qpu", method="noise-sim") + +Alternatively, to emulate the Infleqtion machine locally, without submitting through the cloud, +you can also set the ``emulate`` flag to ``True``. This will emit any target +specific compiler diagnostics, before running a noise free emulation. + +.. code:: python + + cudaq.set_target("infleqtion", emulate=True) + +The number of shots for a kernel execution can be set through +the ``shots_count`` argument to ``cudaq.sample`` or ``cudaq.observe``. By default, +the ``shots_count`` is set to 1000. + +.. code:: python + + cudaq.sample(kernel, shots_count=100) + +To see a complete example for using Infleqtion's backends, take a look at our :doc:`Python examples <../examples/examples>`. +Moreover, for an end-to-end application workflow example executed on the Infleqtion QPU, take a look at the +:doc:`Anderson Impurity Model ground state solver <../applications>` notebook. IonQ ================================== diff --git a/docs/sphinx/using/examples/hardware_providers.rst b/docs/sphinx/using/examples/hardware_providers.rst index 96fbc559f22..6abbb8dea71 100644 --- a/docs/sphinx/using/examples/hardware_providers.rst +++ b/docs/sphinx/using/examples/hardware_providers.rst @@ -2,7 +2,7 @@ Using Quantum Hardware Providers ----------------------------------- CUDA-Q contains support for using a set of hardware providers (Amazon Braket, -IonQ, IQM, OQC, ORCA Computing, Quantinuum and QuEra Computing). +Infleqtion, IonQ, IQM, OQC, ORCA Computing, Quantinuum, and QuEra Computing). For more information about executing quantum kernels on different hardware backends, please take a look at :doc:`hardware <../backends/hardware>`. @@ -21,6 +21,21 @@ The following code illustrates how to run kernels on Amazon Braket's backends. .. literalinclude:: ../../targets/cpp/braket.cpp :language: cpp +Infleqtion +================================== + +The following code illustrates how to run kernels on Infleqtion's backends. + +.. tab:: Python + + .. literalinclude:: ../../targets/python/infleqtion.py + :language: python + +.. tab:: C++ + + .. literalinclude:: ../../targets/cpp/infleqtion.cpp + :language: cpp + IonQ ================================== From a1f765c06e8b7e915a2349033795705e4ce49e27 Mon Sep 17 00:00:00 2001 From: Thien Nguyen <58006629+1tnguyen@users.noreply.github.com> Date: Thu, 12 Dec 2024 03:04:59 +1100 Subject: [PATCH 22/44] Fix a typo in the docs: mgpu -> mqpu (#2463) Signed-off-by: Thien Nguyen --- docs/sphinx/using/backends/platform.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx/using/backends/platform.rst b/docs/sphinx/using/backends/platform.rst index ad395ebc558..da019f3f656 100644 --- a/docs/sphinx/using/backends/platform.rst +++ b/docs/sphinx/using/backends/platform.rst @@ -96,7 +96,7 @@ QPU via the :code:`cudaq::get_state_async` (C++) or :code:`cudaq.get_state_async ./a.out .. deprecated:: 0.8 - The :code:`nvidia-mqpu` and :code:`nvidia-mqpu-fp64` targets, which are equivalent to the multi-QPU options `mgpu,fp32` and `mgpu,fp64`, respectively, of the :code:`nvidia` target, are deprecated and will be removed in a future release. + The :code:`nvidia-mqpu` and :code:`nvidia-mqpu-fp64` targets, which are equivalent to the multi-QPU options `mqpu,fp32` and `mqpu,fp64`, respectively, of the :code:`nvidia` target, are deprecated and will be removed in a future release. Parallel distribution mode ^^^^^^^^^^^^^^^^^^^^^^^^^^ From ade221c3418190a3ef6111512e2836f17badf3de Mon Sep 17 00:00:00 2001 From: Luca Mondada <72734770+lmondada@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:07:03 +0000 Subject: [PATCH 23/44] Document C++ API for get_state, sample and observe (#2140) Signed-off-by: Bettina Heim Co-authored-by: Eric Schweitz Co-authored-by: Bettina Heim --- docs/sphinx/api/languages/cpp_api.rst | 17 +++++++++++++++++ runtime/cudaq/algorithms/observe.h | 19 +++++++++++++------ runtime/cudaq/algorithms/sample.h | 2 ++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/docs/sphinx/api/languages/cpp_api.rst b/docs/sphinx/api/languages/cpp_api.rst index b16e570f532..34487dbefb8 100644 --- a/docs/sphinx/api/languages/cpp_api.rst +++ b/docs/sphinx/api/languages/cpp_api.rst @@ -35,6 +35,14 @@ Common .. doxygenclass:: cudaq::observe_result :members: +.. doxygenstruct:: cudaq::observe_options + :members: + +.. doxygenfunction:: cudaq::observe(const observe_options &options, QuantumKernel &&kernel, spin_op H, Args &&...args) +.. doxygenfunction:: cudaq::observe(std::size_t shots, QuantumKernel &&kernel, spin_op H, Args &&...args) +.. doxygenfunction:: cudaq::observe(QuantumKernel &&kernel, spin_op H, Args &&...args) +.. doxygenfunction:: cudaq::observe(QuantumKernel &&kernel, const SpinOpContainer &termList, Args &&...args) + .. doxygenclass:: cudaq::ExecutionContext :members: @@ -53,6 +61,13 @@ Common .. doxygenclass:: cudaq::sample_result :members: +.. doxygenstruct:: cudaq::sample_options + :members: + +.. doxygenfunction:: cudaq::sample(const sample_options &options, QuantumKernel &&kernel, Args &&...args) +.. doxygenfunction:: cudaq::sample(std::size_t shots, QuantumKernel &&kernel, Args &&...args) +.. doxygenfunction:: cudaq::sample(QuantumKernel &&kernel, Args&&... args) + .. doxygenclass:: cudaq::SimulationState .. doxygenstruct:: cudaq::SimulationState::Tensor @@ -89,6 +104,8 @@ Common .. doxygenfunction:: cudaq::draw(QuantumKernel &&kernel, Args&&... args) +.. doxygenfunction:: cudaq::get_state(QuantumKernel &&kernel, Args&&... args) + .. doxygenclass:: cudaq::Resources .. doxygentypedef:: cudaq::complex_matrix::value_type diff --git a/runtime/cudaq/algorithms/observe.h b/runtime/cudaq/algorithms/observe.h index 6e5abe392b4..407a528013a 100644 --- a/runtime/cudaq/algorithms/observe.h +++ b/runtime/cudaq/algorithms/observe.h @@ -54,13 +54,12 @@ concept ObserveCallValid = HasVoidReturnType>; #endif -/// Observe options to provide as an argument to the `observe()`, +/// @brief Observe options to provide as an argument to the `observe()`, /// `async_observe()` functions. -/// -/// \p shots is the number of shots to run for the given kernel. The default of -/// -1 means direct calculations for simulation backends. \p noise is the noise -/// model to use for the observe operation. -/// \p num_trajectories is the optional number of trajectories to be used when +/// @param shots number of shots to run for the given kernel, or -1 if not +/// applicable. +/// @param noise noise model to use for the sample operation +/// @param num_trajectories is the optional number of trajectories to be used when /// computing the expectation values in the presence of noise. This parameter is /// only applied to simulation backends that support noisy /// simulation of trajectories. @@ -216,6 +215,7 @@ inline auto distributeComputations( } // namespace details +/// \overload /// \brief Compute the expected value of `H` with respect to `kernel(Args...)`. #if CUDAQ_USE_STD20 template @@ -283,6 +283,9 @@ std::vector observe(QuantumKernel &&kernel, return results; } +// Doxygen: ignore overloads with `DistributionType`s, preferring the simpler +// ones +/// @cond /// @brief Compute the expected value of `H` with respect to `kernel(Args...)`. /// Distribute the work `amongst` available QPUs on the platform in parallel. /// This distribution can occur on multi-GPU multi-node platforms, multi-GPU @@ -404,7 +407,9 @@ observe_result observe(QuantumKernel &&kernel, spin_op H, Args &&...args) { return observe(shots, std::forward(kernel), H, std::forward(args)...); } +/// \endcond +/// \overload /// \brief Compute the expected value of `H` with respect to `kernel(Args...)`. /// Specify the number of shots. #if CUDAQ_USE_STD20 @@ -581,6 +586,7 @@ auto observe_async(QuantumKernel &&kernel, spin_op &H, Args &&...args) { std::forward(args)...); } +/// @overload /// @brief Run the standard observe functionality over a set of `N` /// argument packs. For a kernel with signature `void(Args...)`, this /// function takes as input a set of `vector...`, a vector for @@ -623,6 +629,7 @@ std::vector observe(QuantumKernel &&kernel, spin_op H, numQpus, platform, functor, params); } +/// @overload /// @brief Run the standard observe functionality over a set of N /// argument packs. For a kernel with signature `void(Args...)`, this /// function takes as input a set of `vector...`, a vector for diff --git a/runtime/cudaq/algorithms/sample.h b/runtime/cudaq/algorithms/sample.h index 41d8d104053..9d2de93f866 100644 --- a/runtime/cudaq/algorithms/sample.h +++ b/runtime/cudaq/algorithms/sample.h @@ -186,6 +186,7 @@ struct sample_options { cudaq::noise_model noise; }; +/// @overload /// @brief Sample the given quantum kernel expression and return the /// mapping of observed bit strings to corresponding number of /// times observed. @@ -226,6 +227,7 @@ sample_result sample(QuantumKernel &&kernel, Args &&...args) { .value(); } +/// @overload /// @brief Sample the given quantum kernel expression and return the /// mapping of observed bit strings to corresponding number of /// times observed. Specify the number of shots. From 8262655bba27b3d0cb56076352dad4ad5893985c Mon Sep 17 00:00:00 2001 From: Monica VanDieren <150082832+mmvandieren@users.noreply.github.com> Date: Wed, 11 Dec 2024 08:08:46 -0800 Subject: [PATCH 24/44] copy edits. Plus fixed deprecated backend cudaq.set_target("nvidia-mqpu") (#2455) Signed-off-by: Monica VanDieren <150082832+mmvandieren@users.noreply.github.com> Signed-off-by: Eric Schweitz Co-authored-by: Eric Schweitz --- .../applications/python/hadamard_test.ipynb | 36 +++++++------- docs/sphinx/applications/python/krylov.ipynb | 48 +++++++++++-------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/docs/sphinx/applications/python/hadamard_test.ipynb b/docs/sphinx/applications/python/hadamard_test.ipynb index 8199b74d6bd..a643f59ba38 100644 --- a/docs/sphinx/applications/python/hadamard_test.ipynb +++ b/docs/sphinx/applications/python/hadamard_test.ipynb @@ -6,14 +6,14 @@ "source": [ "# Using the Hadamard Test to Determine Quantum Krylov Subspace Decomposition Matrix Elements\n", "\n", - "The Hadamard test, is a quantum algorithm for estimating the expectation values and is a useful subroutine for a number of quantum applications ranging from estimation of molecular ground state energies to quantum semidefinite programming. This tutorial will briefly introduce the Hadamard test, and demonstrate how it can be implemented in CUDA-Q and then parallelized for a Quantum Krylov Subspace Diagonalization application.\n", + "The Hadamard test is a quantum algorithm for estimating expectation values and is a useful subroutine for a number of quantum applications ranging from estimation of molecular ground state energies to quantum semidefinite programming. This tutorial will briefly introduce the Hadamard test, demonstrate how it can be implemented in CUDA-Q, and then parallelized for a Quantum Krylov Subspace Diagonalization application.\n", "\n", "The Hadamard test is performed using a register with an ancilla qubit in the $\\ket{0}$ state and a prepared quantum state $\\ket{\\psi}$, the following circuit can be used to extract the expectation value from measurement of the ancilla.\n", "\n", "\n", "![Htest](./images/htest.png)\n", "\n", - "The key insight is to note that $$P(0) = \\frac{1}{2} \\left[ I + Re \\bra{\\psi} O \\ket{\\phi} \\right]$$ and $$P(1) = \\frac{1}{2} \\left[ I - Re \\bra{\\psi} O \\ket{\\phi} \\right]$$ so their difference is equal to $$P(0)-P(1) = Re \\bra{\\psi} O \\ket{\\phi}$$\n", + "The key insight is that $$P(0) = \\frac{1}{2} \\left[ I + Re \\bra{\\psi} O \\ket{\\phi} \\right]$$ and $$P(1) = \\frac{1}{2} \\left[ I - Re \\bra{\\psi} O \\ket{\\phi} \\right]$$ so their difference is equal to $$P(0)-P(1) = Re \\bra{\\psi} O \\ket{\\phi}.$$\n", "\n", "\n", "More details and a short derivation can be found [here](https://en.wikipedia.org/wiki/Hadamard_test)." @@ -26,18 +26,18 @@ "What if you want to perform the Hadamard test to compute an expectation value like $\\bra{\\psi} O \\ket{\\phi}$, where $\\ket{\\psi}$ and $\\ket{\\phi}$ are different states and $O$ is a Pauli Operator? This is a common subroutine for the QKSD, where matrix elements are determined by computing expectation values between different states.\n", "\n", "Defining $O$ as \n", - "$$O = X_1X_2$$\n", + "$$O = X_1X_2,$$\n", "\n", "and given the fact that\n", - "$$\\ket{\\psi} = U_{\\psi}\\ket{0} \\qquad \\ket{\\phi} = U_{\\phi}\\ket{0}$$\n", + "$$\\ket{\\psi} = U_{\\psi}\\ket{0} \\qquad \\ket{\\phi} = U_{\\phi}\\ket{0},$$\n", "\n", - "We can combine the state preparation steps into the operator resulting in\n", - "$$\\bra{\\psi}O\\ket{\\phi} = \\bra{0}U_\\psi^\\dagger O U_\\phi\\ket{0}$$\n", - "Which corresponds to the following circuit\n", + "we can combine the state preparation steps into the operator resulting in\n", + "$$\\bra{\\psi}O\\ket{\\phi} = \\bra{0}U_\\psi^\\dagger O U_\\phi\\ket{0},$$\n", + "which corresponds to the following circuit.\n", "![Htest2](./images/htestfactored.png)\n", "\n", - "By preparing this circuit, and repeatedly measuring the ancilla qubit, allows for estimation of the expectation value as $$P(0)-P(1) = Re \\bra{\\psi} O \\ket{\\phi}$$\n", - "\n", + "By preparing this circuit, and repeatedly measuring the ancilla qubit, we estimate the expectation value as $$P(0)-P(1) = Re \\bra{\\psi} O \\ket{\\phi}.$$\n", + "\, "\n", "The following sections demonstrate how this can be performed in CUDA-Q." ] @@ -53,7 +53,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Before performing the Hadamard test, lets determine the exact expectation value by performing the matrix multiplications explicitly. The code below builds two CUDA-Q kernels corresponding to $\\ket{\\psi} = \\frac{1}{\\sqrt{2}}\\begin{pmatrix}1 \\\\ 0 \\\\ 1 \\\\ 0\\end{pmatrix}$ and $\\ket{\\phi} = \\begin{pmatrix}0 \\\\ 1 \\\\ 0 \\\\ 0\\end{pmatrix}$" + "Before performing the Hadamard test, let's determine the exact expectation value by performing the matrix multiplications explicitly. The code below builds two CUDA-Q kernels corresponding to $\\ket{\\psi} = \\frac{1}{\\sqrt{2}}\\begin{pmatrix}1 \\\\ 0 \\\\ 1 \\\\ 0\\end{pmatrix}$ and $\\ket{\\phi} = \\begin{pmatrix}0 \\\\ 1 \\\\ 0 \\\\ 0\\end{pmatrix}.$" ] }, { @@ -87,7 +87,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The state vectors can be accessed using the `get_state` command and printed as numpy arrays" + "The state vectors can be accessed using the `get_state` command and printed as numpy arrays:" ] }, { @@ -118,7 +118,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The Hamiltonian operator ($O$ in the derivation above) is defined as a CUDA-Q spin operator and converted to a matrix withe the `to_matrix`. The following line of code performs the explicit matrix multiplications to produce the exact expectation value." + "The Hamiltonian operator ($O$ in the derivation above) is defined as a CUDA-Q spin operator and converted to a matrix with `to_matrix`. The following line of code performs the explicit matrix multiplications to produce the exact expectation value." ] }, { @@ -208,7 +208,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The CUDA-Q Sample method computes 100000 sample ancilla measurements and uses them to estimate the expectation value. The standard error is provided as well. Try increasing the sample size and note the convergence of the expectation value and the standard error towards the numerical result." + "The CUDA-Q `sample` method computes 100000 sample ancilla measurements, and from them we can estimate the expectation value. The standard error is provided as well. Try increasing the sample size and note the convergence of the expectation value and the standard error towards the numerical result." ] }, { @@ -249,9 +249,9 @@ "\n", "![Htest3](./images/QKSD.png)\n", "\n", - "This method can be easily parallelized and multiple QPUs, if available could compute the matrix elements asynchronously. The CUDA-Q `mqpu` backend allows you to simulate a computation across multiple simulated QPUs. The code below demonstrates how.\n", + "This method can be easily parallelized, and multiple QPUs, if available, could compute the matrix elements asynchronously. The CUDA-Q `mqpu` backend allows you to simulate a computation across multiple simulated QPUs. The code below demonstrates how.\n", "\n", - "First, the Hadamard test circuit is defined, but this time the $\\ket{\\psi}$ and $\\ket{\\phi}$ states contain parameterized rotations so that multiple states can be quickly generated for the sake of example." + "First, the Hadamard test circuit is defined, but this time the $\\ket{\\psi}$ and $\\ket{\\phi}$ states contain parameterized rotations so that multiple states can be quickly generated, for the sake of example." ] }, { @@ -302,7 +302,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -314,7 +314,7 @@ } ], "source": [ - "cudaq.set_target(\"nvidia-mqpu\")\n", + "cudaq.set_target(\"nvidia\", option=\"mqpu\")\n", "\n", "target = cudaq.get_target()\n", "qpu_count = target.num_qpus()\n", @@ -354,7 +354,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The four matrix elements are shown below and now be classically processed to produce the eigenvalues." + "The four matrix elements are shown below and can be classically processed to produce the eigenvalues." ] }, { diff --git a/docs/sphinx/applications/python/krylov.ipynb b/docs/sphinx/applications/python/krylov.ipynb index 1954212c027..183a13d6919 100644 --- a/docs/sphinx/applications/python/krylov.ipynb +++ b/docs/sphinx/applications/python/krylov.ipynb @@ -7,39 +7,45 @@ "source": [ "# Multi-reference Quantum Krylov Algorithm - $H_2$ Molecule\n", "\n", - "The multireference selected quantum Krylov (MRSQK) algorithm is defined in [this paper](https://arxiv.org/pdf/1911.05163) and was developed as a low-cost alternative to quantum phase estimation. This tutorial will demonstrate how this algorithm can be implemented in CUDA-Q and accelerated using multiple GPUs. The CUDA-Q Hadamard test tutorial might provide helpful background information for understanding this tutorial.\n", + "The multireference selected quantum Krylov (MRSQK) algorithm is defined in [this paper](https://arxiv.org/pdf/1911.05163) and was developed as a low-cost alternative to quantum phase estimation. This tutorial will demonstrate how this algorithm can be implemented in CUDA-Q and accelerated using multiple GPUs. The [CUDA-Q Hadamard test tutorial](https://nvidia.github.io/cuda-quantum/latest/applications/python/hadamard_test.html) might provide helpful background information for understanding this tutorial.\n", "\n", "The algorithm works by preparing an initial state, and then defining this state in a smaller subspace constructed with a basis that corresponds to Trotter steps of the initial state. This subspace can be diagonalized to produce an approximate energy for the system without variational optimization of any parameters.\n", "\n", "In the example below, the initial guess is the ground state of the diagonalized Hamiltonian for demonstration purposes. In practice one could use a number of heuristics to prepare the ground state such as Hartree Fock or CISD. A very promising ground state preparation method which can leverage quantum computers is the linear combination of unitaries (LCU). LCU would allow for the state preparation to occur completely on the quantum computer and avoid storing an inputting the exponentially large state vector.\n", "\n", "\n", - "Regardless of the method used for state preparation, the procedure begins by selecting a $d$ dimensional basis of reference states ${\\Phi_0 \\cdots \\Phi_d}$ where each is a linear combination of slater determinants. \n", + "Regardless of the method used for state preparation, the procedure begins by selecting a $d$-dimensional basis of reference states ${\\Phi_0 \\cdots \\Phi_d},$ where each is a linear combination of Slater determinants: \n", "\n", - "$$ \\ket{\\Phi_I} = \\sum_{\\mu} d_{\\mu I}\\ket{\\phi_{\\mu}} $$\n", + "$$ \\ket{\\Phi_I} = \\sum_{\\mu} d_{\\mu I}\\ket{\\phi_{\\mu}}. $$\n", "\n", "\n", - "From this, a non-orthogonal Krylov Space $\\mathcal{K} = \\{\\psi_{0} \\cdots \\psi_{N}\\}$ is constructed by applying a family of $s$ unitary operators on each of the $d$ reference states resulting in $d*s = N$ elements in the Krylov space where, \n", - "$$ \\ket{\\psi_{\\alpha}} \\equiv \\ket{\\psi_I^{(n)}} = \\hat{U}_n\\ket{\\Phi_I} $$\n", + "From this, a non-orthogonal Krylov Space $\\mathcal{K} = \\{\\psi_{0} \\cdots \\psi_{N}\\}$ is constructed by applying a family of $s$ unitary operators on each of the $d$ reference states resulting in $d*s = N$ elements in the Krylov space where \n", + "$$ \\ket{\\psi_{\\alpha}} \\equiv \\ket{\\psi_I^{(n)}} = \\hat{U}_n\\ket{\\Phi_I}, $$\n", "\n", "Therefore, the general quantum state that we originally set out to describe is\n", "\n", - "$$ \\ket{\\Psi} = \\sum_{\\alpha} c_{\\alpha}\\ket{\\psi_{\\alpha}} = \\sum_{I=0}^d \\sum_{n=0}^s c_I^{(n)}\\hat{U}_n\\ket{\\Phi_I} $$\n", + "$$ \\ket{\\Psi} = \\sum_{\\alpha} c_{\\alpha}\\ket{\\psi_{\\alpha}} = \\sum_{I=0}^d \\sum_{n=0}^s c_I^{(n)}\\hat{U}_n\\ket{\\Phi_I}. $$\n", "\n", "The energy of this state can be obtained by solving the generalized eigenvalue problem\n", - "$$ \\boldsymbol{Hc}=\\boldsymbol{Sc}E $$\n", + "$$ \\boldsymbol{Hc}=\\boldsymbol{Sc}E, $$\n", "\n", - "Where the elements of the overlap and Hamiltonian matrix are\n", + "where the elements of the overlap are\n", "\n", "$$S_{\\alpha \\beta} = \\braket{\\psi_{\\alpha}|\\psi_{\\beta}} = \\braket{\\Phi_I|\\hat{U}_m^{\\dagger}\\hat{U}_n|\\Phi_J}$$ \n", "\n", - "$$H_{\\alpha \\beta} = \\braket{\\psi_{\\alpha}|\\hat{H}|\\psi_{\\beta}} = \\braket{\\Phi_I|\\hat{U}_m^{\\dagger}\\hat{H}\\hat{U}_n|\\Phi_J}$$\n", + "and Hamiltonian matrix is\n", "\n", - "These matrix elements are computed using a quantum computer and the Hadamard test with a circuit shown below for the case of the overlap matrix elements (The Hamiltonian matrix elements circuit would include controlled application of the Hamiltonian in the circuit). Once the matrices are constructed, the diagonalization is performed classically to produce an estimate for the ground state in question.\n", + "$$H_{\\alpha \\beta} = \\braket{\\psi_{\\alpha}|\\hat{H}|\\psi_{\\beta}} = \\braket{\\Phi_I|\\hat{U}_m^{\\dagger}\\hat{H}\\hat{U}_n|\\Phi_J}.$$\n", + "\n", + "The matrix elements for $S$ are computed with the Hadamard test with a circuit shown below for the case of the overlap matrix elements. \n", "\n", "![Htest](./images/krylovcircuit.png)\n", "\n", - "The $2\\sigma_+$ term refers to measurement of the expectation value of this circuit with the $X+iY$ operator. \n" + "The $2\\sigma_+$ term refers to measurement of the expectation value of this circuit with the $X+iY$ operator.\n", + "\n", + "The Hamiltonian matrix elements are computed with a circuit that includes controlled application of the Hamiltonian. Once the $H$ and $S$ matrices are constructed, the diagonalization is performed classically to produce an estimate for the ground state in question.\n", + "\n", + "\n" ] }, { @@ -140,7 +146,7 @@ " return result\n", "\n", "\n", - "#Build the lists of coefficients and Pauli Words from H2 Hamiltonian\n", + "# Build the lists of coefficients and Pauli Words from the H2 Hamiltonian\n", "coefficient = termCoefficients(hamiltonian)\n", "pauli_string = termWords(hamiltonian)\n", "\n", @@ -153,7 +159,7 @@ "id": "74b4c079-8837-47ae-a484-52ec5f4a160e", "metadata": {}, "source": [ - "In the case of this example, the unitary operators that build the Krylov subspace are first-order Trotter operations at different time steps. The performance here could potentially be improved by increasing the size of the time step, using a higher order Trotter approximation, or using other sorts of approximations. The CUDA-Q kernels below define the unitary operations that construct the $\\psi$ basis. Each receives the target qubits, the time step, and components of the Hamiltonian." + "In this example, the unitary operators that build the Krylov subspace are first-order Trotter operations at different time steps. The performance here could potentially be improved by increasing the size of the time step, using a higher order Trotter approximation, or using other sorts of approximations. The CUDA-Q kernels below define the unitary operations that construct the $\\psi$ basis. Each receives the target qubits, the time step, and components of the Hamiltonian." ] }, { @@ -300,11 +306,11 @@ "\n", "The cell below computes the overlap matrix. This can be done in serial or in parallel, depending on the `multi_gpu` specification. First, an operator is built to apply the identity to the overlap matrix circuit when `apply_pauli` is called. Next, the `wf_overlap` array is constructed which will hold the matrix elements. \n", "\n", - "Next, a pair of nested loops, iterate over the time steps defined by the dimension of the subspace. Each m,n combination corresponds to computation of an off-diagonal matrix element of the overlap matrix $S$ using the Hadamard test. This is accomplished by calling the CUDA-Q `observe` function with the X and Y operators, along with the time steps, the components of the Hamiltonian matrix, and the initial state vector `vec`.\n", + "Next, a pair of nested loops iterate over the time steps defined by the dimension of the subspace. Each m,n combination corresponds to computation of an off-diagonal matrix element of the overlap matrix $S$ using the Hadamard test. This is accomplished by calling the CUDA-Q `observe` function with the X and Y operators, along with the time steps, the components of the Hamiltonian matrix, and the initial state vector `vec`.\n", "\n", "The observe function broadcasts over the two provided operators $X$ and $Y$ and returns a list of results. The `expectation` function returns the expectation values which are summed and stored in the matrix.\n", "\n", - "The multi-gpu case completes the same steps, expect for `observe_async` is used. This allows for the $X$ and $Y$ observables to be evaluated at the same time on two different simulated QPUs. In this case, the results are stored in lists corresponding to the real an imaginary parts, and then accessed later with the `get` command to build $S$\n" + "The multi-gpu case completes the same steps, except the `observe_async` command is used. This allows for the $X$ and $Y$ observables to be evaluated at the same time on two different simulated QPUs. In this case, the results are stored in lists corresponding to the real and imaginary parts. These are then accessed later with the `get` command to build $S$.\n" ] }, { @@ -541,19 +547,19 @@ "id": "1917bd58-4ce3-4e3e-9ccf-096577c0da14", "metadata": {}, "source": [ - "### Determining the ground state energy of the Subspace\n", + "### Determining the ground state energy of the subspace\n", "\n", "The final step is to solve the generalized eigenvaulue problem with the overlap and Hamiltonian matrices constructed using the quantum computer. The procedure begins by diagonalizing $S$ with the transform $$S = U\\Sigma U^{\\dagger}$$\n", "\n", - "The eigenvectors $v$ and eigenvalues $s$ are used to construct a new matrix $X'$\n", + "The eigenvectors $v$ and eigenvalues $s$ are used to construct a new matrix $X':$\n", "\n", - "$$ X' = S ^{\\frac{-1}{2}} = \\sum_k v_{ki} \\frac{1}{\\sqrt{s_k}} v_{kj}$$\n", + "$$ X' = S ^{\\frac{-1}{2}} = \\sum_k v_{ki} \\frac{1}{\\sqrt{s_k}} v_{kj}.$$\n", "\n", - "The $X'$ matrix diagonalizes $H$\n", + "The matrix $X'$ diagonalizes $H:$\n", "\n", - "$$ X'^{\\dagger}HX' = ES^{\\frac{1}{2}}C$$\n", + "$$ X'^{\\dagger}HX' = ES^{\\frac{1}{2}}C.$$\n", "\n", - "Using the eigenvectors of $H'$, ($^{\\frac{1}{2}}C$), the original eigenvectors to the problem can be found by left multiplying by $S^{\\frac{-1}{2}}C$" + "Using the eigenvectors of $H'$, ($^{\\frac{1}{2}}C$), the original eigenvectors to the problem can be found by left multiplying by $S^{\\frac{-1}{2}}C.$" ] }, { From c56261bc7975aadb1e309b6ab8bcf3eaa4b663e9 Mon Sep 17 00:00:00 2001 From: Ben Howe <141149032+bmhowe23@users.noreply.github.com> Date: Wed, 11 Dec 2024 08:51:19 -0800 Subject: [PATCH 25/44] Make remote-mqpu auto-launcher aware of CUDAQ_DYNLIBS env var (#2385) Signed-off-by: Ben Howe Signed-off-by: Bettina Heim Co-authored-by: Bettina Heim --- .../cudaq/platform/mqpu/helpers/MQPUUtils.cpp | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/runtime/cudaq/platform/mqpu/helpers/MQPUUtils.cpp b/runtime/cudaq/platform/mqpu/helpers/MQPUUtils.cpp index bf36c2e1b7d..bbf32ddd227 100644 --- a/runtime/cudaq/platform/mqpu/helpers/MQPUUtils.cpp +++ b/runtime/cudaq/platform/mqpu/helpers/MQPUUtils.cpp @@ -14,7 +14,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -79,6 +81,44 @@ cudaq::AutoLaunchRestServerProcess::AutoLaunchRestServerProcess( if (!serverApp) throw std::runtime_error("Unable to find CUDA-Q REST server to launch."); + // If the CUDAQ_DYNLIBS env var is set (typically from the Python + // environment), add these to the LD_LIBRARY_PATH. + std::string libPaths; + std::optional> Env; + if (auto *ch = getenv("CUDAQ_DYNLIBS")) { + Env = llvm::SmallVector(); + libPaths = ch; + if (libPaths.size() > 0) + libPaths += ":"; + + std::set libDirs; + while (libPaths.size() > 0) { + auto next_delim = libPaths.find(":"); + if (next_delim < libPaths.size()) { + std::filesystem::path lib(libPaths.substr(0, next_delim)); + libPaths = libPaths.substr(next_delim + 1); + auto libDir = lib.remove_filename().string(); + if (libDir.size() > 0) + libDirs.insert(libDir); + } + } + + std::string dynLibs = std::accumulate( + std::begin(libDirs), std::end(libDirs), std::string{}, + [](const std::string &a, const std::string &b) { return a + b + ':'; }); + dynLibs.pop_back(); + if (auto *p = getenv("LD_LIBRARY_PATH")) { + std::string envLibs = p; + if (envLibs.size() > 0) + dynLibs += ":" + envLibs; + } + for (char **env = environ; *env != nullptr; ++env) { + if (!std::string(*env).starts_with("LD_LIBRARY_PATH=")) + Env->push_back(*env); + } + Env->push_back("LD_LIBRARY_PATH=" + dynLibs); + } + constexpr std::size_t PORT_MAX_RETRIES = 10; for (std::size_t j = 0; j < PORT_MAX_RETRIES; j++) { /// Step 1: Look up a port @@ -98,9 +138,8 @@ cudaq::AutoLaunchRestServerProcess::AutoLaunchRestServerProcess( llvm::StringRef argv[] = {serverApp.get(), "--port", port.value()}; std::string errorMsg; bool executionFailed = false; - auto processInfo = - llvm::sys::ExecuteNoWait(serverApp.get(), argv, std::nullopt, {}, 0, - &errorMsg, &executionFailed); + auto processInfo = llvm::sys::ExecuteNoWait(serverApp.get(), argv, Env, {}, + 0, &errorMsg, &executionFailed); if (executionFailed) throw std::runtime_error("Failed to launch " + serverExeName + " at port " + port.value() + ": " + errorMsg); From 1a3ffcbf992f984828154080f6dc38792146dd4d Mon Sep 17 00:00:00 2001 From: Monica VanDieren <150082832+mmvandieren@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:04:28 -0800 Subject: [PATCH 26/44] copy edits of DC_QAOA and fixed broken link (#2467) DCO Remediation Commit for Monica VanDieren <150082832+mmvandieren@users.noreply.github.com> I, Monica VanDieren <150082832+mmvandieren@users.noreply.github.com>, hereby add my Signed-off-by to this commit: ab2cb59ee0daee5c17aea6a80df3b8b5d468a14c I, Monica VanDieren <150082832+mmvandieren@users.noreply.github.com>, hereby add my Signed-off-by to this commit: c2d03110bed58bc711bde948731340d9bfa25f74 Signed-off-by: Monica VanDieren <150082832+mmvandieren@users.noreply.github.com> --- .../digitized_counterdiabatic_qaoa.ipynb | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/sphinx/applications/python/digitized_counterdiabatic_qaoa.ipynb b/docs/sphinx/applications/python/digitized_counterdiabatic_qaoa.ipynb index 35dcb5778a6..0ac3df0ca5c 100644 --- a/docs/sphinx/applications/python/digitized_counterdiabatic_qaoa.ipynb +++ b/docs/sphinx/applications/python/digitized_counterdiabatic_qaoa.ipynb @@ -8,7 +8,7 @@ "\n", "Drugs often work by binding to an active site of a protein, inhibiting or activating its function for some therapeutic purpose. Finding new candidate drugs is extremely difficult. The study of molecular docking helps guide this search and involves the prediction of how strongly a certain ligand (drug) will bind to its target (usually a protein). \n", "\n", - "One of the primary challenges to molecular docking arises from the many geometric degrees of freedom present in proteins and ligands, making it difficult to predict the optimal orientation and assess if the drug is a good candidate or not. One solution is to formulate the problem as a mathematical optimization problem where the optimal solution corresponds to the most likely ligand-protein configuration. This optimization problem can be solved on a quantum computer using methods like the Quantum Approximate Optimization Algorithm (QAOA). This tutorial demonstrates how this [paper](https://arxiv.org/pdf/2308.04098) used digitized-counteradiabatic (DC) QAOA to study molecular docking. This tutorial assumes you have an understanding of QAOA, if not, please see the CUDA-Q MaxCut tutorial found [here](https://nvidia.github.io/cuda-quantum/latest/examples/python/tutorials/qaoa.html)\n", + "One of the primary challenges to molecular docking arises from the many geometric degrees of freedom present in proteins and ligands, making it difficult to predict the optimal orientation and assess if the drug is a good candidate or not. One solution is to formulate the problem as a mathematical optimization problem where the optimal solution corresponds to the most likely ligand-protein configuration. This optimization problem can be solved on a quantum computer using methods like the Quantum Approximate Optimization Algorithm (QAOA). This tutorial demonstrates how this [paper](https://arxiv.org/pdf/2308.04098) used digitized-counteradiabatic (DC) QAOA to study molecular docking. This tutorial assumes you have an understanding of QAOA, if not, please see the CUDA-Q MaxCut tutorial found [here](https://nvidia.github.io/cuda-quantum/latest/applications/python/qaoa.html).\n", "\n", "The next section provides more detail on the problem setup followed by CUDA-Q implementations below." ] @@ -25,10 +25,10 @@ "\n", "\n", "There are 6 key steps:\n", - "1. The experimental protein and ligand structures are determined and used to select pharmacores, or an important chemical group that will govern the chemical interactions,\n", - "2. T wo labeled distance graphs (LAGs) of size $N$ and $M$ represent the protein and the ligand, respectively. Each node corresponds to a pharmacore and each edge weight corresponds to the distance between pharmacores.\n", + "1. The experimental protein and ligand structures are determined and used to select pharmacores, or an important chemical group that will govern the chemical interactions.\n", + "2. Two labeled distance graphs (LAGs) of size $N$ and $M$ represent the protein and the ligand, respectively. Each node corresponds to a pharmacore and each edge weight corresponds to the distance between pharmacores.\n", "3. A $M*N$ node binding interaction graph (BIG) is created from the LAGs. Each node in the BIG graph corresponds to a pair of pharmacores, one from the ligand and the other from the protein. The existence of edges between nodes in the BIG graph are determined from the LAGs and correspond to interactions that can feesibly coexist. Therefore, cliques in the graph correspond to mutually possible interactions. \n", - "4. The problem is mapped to a QAOA circuit and corresponding Hamiltonian, and the ground state solution is determined.\n", + "4. The problem is mapped to a QAOA circuit and corresponding Hamiltonian. From there, the ground state solution is determined.\n", "5. The ground state will produce the maximum weighted clique which corresponds to the best (most strongly bound) orientation of the ligand and protein.\n", "6. The predicted docking structure is interpreted from the QAOA result and is used for further analysis.\n" ] @@ -44,7 +44,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -52,19 +52,20 @@ "from cudaq import spin\n", "import numpy as np\n", "\n", - "# cudaq.set_target('nvidia')" + "cudaq.set_target('nvidia')\n", + "# cudaq.set_target('qpp-cpu') # Uncomment this line if no GPUs are available" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The block below defines two of the BIG data sets from the paper. The first is a smaller example, but it can be swapped with the commented out example below at your discretion. The weights are specified for each node based on the nature of the ligand and protein pharmacores represented by the node" + "The block below defines two of the BIG data sets from the paper. The first is a smaller example, but it can be swapped with the commented out example below at your discretion. The weights are specified for each node based on the nature of the ligand and protein pharmacores represented by the node." ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -77,7 +78,7 @@ } ], "source": [ - "# The two graphs input from the paper\n", + "# The two graph inputs from the paper\n", "\n", "# BIG 1\n", "\n", @@ -113,12 +114,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next, the Hamiltonian is constructed. \n", + "Next, the Hamiltonian is constructed: \n", "\n", "$$H = \\frac{1}{2}\\sum_{i \\in V}w_i(\\sigma^z_i - 1) + \\frac{P}{4} \\sum_{(i,j) \\notin E, i \\neq j} (\\sigma^z_i -1)(\\sigma^z_j - 1) $$\n", "\n", "\n", - "The first term concerns the vertices and the weights of the given pharmacores. The second term is a penalty term that penalizes edges of the graph with no interactions. The penalty $P$ is set by the user and is defined as 6 in the cell above. The function below returns the Hamiltonina as a CUDA-Q `spin_op` object.\n", + "The first term concerns the vertices and the weights of the given pharmacores. The second term is a penalty term that penalizes edges of the graph with no interactions. The penalty $P$ is set by the user and is defined as 6 in the cell above. The function below returns the Hamiltonian as a CUDA-Q `spin_op` object.\n", "\n" ] }, @@ -208,7 +209,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The kernel below defines a DC-QAOA circuit. What makes the approach \"DC\" is the inclusion of additional counteradiabatic terms to better drive the optimization to the ground state. These terms are digitized and applied as additional operations following each QAOA layer. The increase in parameters is hopefully offset by requiring fewer layers. In this example, the DC terms are additional parameterized $Y$ operations applied to each qubit. These can be commented out to run conventional QAOA." + "The kernel below defines a DC-QAOA circuit. What makes the approach \"DC\" is the inclusion of additional counteradiabatic terms to better drive the optimization to the ground state. These terms are digitized and applied as additional operations following each QAOA layer. The increase in parameters is hopefully offset by requiring fewer layers. In this example, the DC terms are the additional parameterized $Y$ operations applied to each qubit. These can be commented out to run conventional QAOA." ] }, { @@ -246,7 +247,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The classical optimizer for the QAOA procedure can be specified as one of the build in CUDA-Q optimizers, in this case Nelder Mead. The parameter count is defined for DC-QAOA, but can be swapped with the commented line below for conventional QAOA." + "The classical optimizer for the QAOA procedure can be specified as one of the built-in CUDA-Q optimizers, in this case Nelder Mead. The parameter count is defined for DC-QAOA, but can be swapped with the commented line below for conventional QAOA." ] }, { From 5785e44256b757263879580c82cb84adc85bcf5a Mon Sep 17 00:00:00 2001 From: Pradnya Khalate <148914294+khalatepradnya@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:08:39 -0800 Subject: [PATCH 27/44] Fixes for Python notebooks (#2472) Follow-up to PR# 2455 and PR# 2467 * Fix for invalid notebook - hadamard_test.ipynb * Remove explicit setting of target (default target is nvidia if GPU(s) present) - digitized_counterdiabatic_qaoa.ipynb Addresses CI failures in the image validation step. Signed-off-by: Pradnya Khalate --- .../python/digitized_counterdiabatic_qaoa.ipynb | 9 +++------ docs/sphinx/applications/python/hadamard_test.ipynb | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/sphinx/applications/python/digitized_counterdiabatic_qaoa.ipynb b/docs/sphinx/applications/python/digitized_counterdiabatic_qaoa.ipynb index 0ac3df0ca5c..a3043b7a1be 100644 --- a/docs/sphinx/applications/python/digitized_counterdiabatic_qaoa.ipynb +++ b/docs/sphinx/applications/python/digitized_counterdiabatic_qaoa.ipynb @@ -44,16 +44,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import cudaq\n", "from cudaq import spin\n", - "import numpy as np\n", - "\n", - "cudaq.set_target('nvidia')\n", - "# cudaq.set_target('qpp-cpu') # Uncomment this line if no GPUs are available" + "import numpy as np\n" ] }, { @@ -65,7 +62,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [ { diff --git a/docs/sphinx/applications/python/hadamard_test.ipynb b/docs/sphinx/applications/python/hadamard_test.ipynb index a643f59ba38..1d1f781d595 100644 --- a/docs/sphinx/applications/python/hadamard_test.ipynb +++ b/docs/sphinx/applications/python/hadamard_test.ipynb @@ -37,7 +37,7 @@ "![Htest2](./images/htestfactored.png)\n", "\n", "By preparing this circuit, and repeatedly measuring the ancilla qubit, we estimate the expectation value as $$P(0)-P(1) = Re \\bra{\\psi} O \\ket{\\phi}.$$\n", - "\, + "\n", "\n", "The following sections demonstrate how this can be performed in CUDA-Q." ] From e790a98fde7790dfac2a30bb9308c34c8c8bb22f Mon Sep 17 00:00:00 2001 From: Thien Nguyen <58006629+1tnguyen@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:38:51 +1100 Subject: [PATCH 28/44] Fix uninitialized memory issue for the result buffer in kron (#2475) Signed-off-by: Thien Nguyen --- runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cpp b/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cpp index 3f4d09bf76f..5f4b2f48017 100644 --- a/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cpp +++ b/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cpp @@ -216,7 +216,8 @@ class CuStateVecCircuitSimulator void *newDeviceStateVector; HANDLE_CUDA_ERROR(cudaMalloc((void **)&newDeviceStateVector, stateDimension * sizeof(CudaDataType))); - + HANDLE_CUDA_ERROR(cudaMemset(newDeviceStateVector, 0, + stateDimension * sizeof(CudaDataType))); // Place the state data on device. Could be that // we just need the zero state, or the user could have provided one void *otherState; @@ -283,6 +284,8 @@ class CuStateVecCircuitSimulator void *newDeviceStateVector; HANDLE_CUDA_ERROR(cudaMalloc((void **)&newDeviceStateVector, stateDimension * sizeof(CudaDataType))); + HANDLE_CUDA_ERROR(cudaMemset(newDeviceStateVector, 0, + stateDimension * sizeof(CudaDataType))); constexpr int32_t threads_per_block = 256; uint32_t n_blocks = (stateDimension + threads_per_block - 1) / threads_per_block; From aae02de7da304fd1d83b121aad860d3baa28f6ca Mon Sep 17 00:00:00 2001 From: Bettina Heim Date: Thu, 12 Dec 2024 11:23:39 +0100 Subject: [PATCH 29/44] Updating docs version index (#2470) Signed-off-by: Bettina Heim --- .github/workflows/docker_images.yml | 2 +- .../main_divisive_clustering.py | 2 +- .../applications/python/vqe_advanced.ipynb | 2 +- .../examples/python/building_kernels.ipynb | 2 +- .../examples/python/building_kernels.py | 4 ++- .../examples/python/executing_kernels.ipynb | 2 +- docs/sphinx/releases.rst | 31 ++++++++++++++++--- 7 files changed, 35 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docker_images.yml b/.github/workflows/docker_images.yml index 5b4717c5709..0e62b2a3029 100644 --- a/.github/workflows/docker_images.yml +++ b/.github/workflows/docker_images.yml @@ -694,7 +694,7 @@ jobs: fi image_tag=`docker inspect $cudaq_image --format='{{json .Config.Labels}}' | jq -r '."org.opencontainers.image.version"'` - docs_version="CUDA_QUANTUM_VERSION=${image_tag%-base}" + docs_version="CUDA_QUANTUM_VERSION=$(echo $image_tag | sed -re 's/^(cu[0-9]+-)?(.*)-base$/\2/')" docker image rm $cudaq_image docker image prune --force diff --git a/docs/sphinx/applications/python/divisive_clustering_src/main_divisive_clustering.py b/docs/sphinx/applications/python/divisive_clustering_src/main_divisive_clustering.py index 6ada0c0cde8..4065d93755c 100644 --- a/docs/sphinx/applications/python/divisive_clustering_src/main_divisive_clustering.py +++ b/docs/sphinx/applications/python/divisive_clustering_src/main_divisive_clustering.py @@ -27,7 +27,7 @@ type=str, choices=["qpp-cpu", "nvidia", "nvidia-mgpu"], help= - "Quantum simulator backend. Default is qpp-cpu. See https://nvidia.github.io/cuda-quantum/0.6.0/using/simulators.html for more options.", + "Quantum simulator backend. Default is qpp-cpu. See https://nvidia.github.io/cuda-quantum for more options.", ) argparser.add_argument( "-d", diff --git a/docs/sphinx/applications/python/vqe_advanced.ipynb b/docs/sphinx/applications/python/vqe_advanced.ipynb index 188df3af45b..3135b938298 100644 --- a/docs/sphinx/applications/python/vqe_advanced.ipynb +++ b/docs/sphinx/applications/python/vqe_advanced.ipynb @@ -331,7 +331,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[\u001b[38;2;255;000;000mwarning\u001b[0m] Target \u001b[38;2;000;000;255mnvidia-mqpu\u001b[0m: \u001b[38;2;000;000;255mThis target is deprecating. Please use the 'nvidia' target with option 'mqpu,fp32' or 'mqpu' (fp32 is the default precision option) by adding the command line option '--target-option mqpu,fp32' or passing it as cudaq.set_target('nvidia', option='mqpu,fp32') in Python. Please refer to CUDA-Q \u001b]8;;https://nvidia.github.io/cuda-quantum/latest/using/backends/platform.html#nvidia-mqpu-platform\u001b\\documentation\u001b]8;;\u001b\\ for more information.\u001b[0m\n" + "[\u001b[38;2;255;000;000mwarning\u001b[0m] Target \u001b[38;2;000;000;255mnvidia-mqpu\u001b[0m: \u001b[38;2;000;000;255mThis target is deprecating. Please use the 'nvidia' target with option 'mqpu,fp32' or 'mqpu' (fp32 is the default precision option) by adding the command line option '--target-option mqpu,fp32' or passing it as cudaq.set_target('nvidia', option='mqpu,fp32') in Python. Please refer to CUDA-Q \u001b]8;;https://nvidia.github.io/cuda-quantum/latest/using/backends/platform\u001b\\documentation\u001b]8;;\u001b\\ for more information.\u001b[0m\n" ] } ], diff --git a/docs/sphinx/examples/python/building_kernels.ipynb b/docs/sphinx/examples/python/building_kernels.ipynb index 366b9f626f3..b847b4936e4 100644 --- a/docs/sphinx/examples/python/building_kernels.ipynb +++ b/docs/sphinx/examples/python/building_kernels.ipynb @@ -145,7 +145,7 @@ "### Applying Gates\n", "\n", "\n", - "After a kernel is constructed, gates can be applied to start building out a quantum circuit. All the predefined gates in CUDA-Q can be found [here](https://nvidia.github.io/cuda-quantum/latest/api/default_ops.html#unitary-operations-on-qubits).\n", + "After a kernel is constructed, gates can be applied to start building out a quantum circuit. All the predefined gates in CUDA-Q can be found [here](https://nvidia.github.io/cuda-quantum/latest/api/default_ops).\n", "\n", "\n", "Gates can be applied to all qubits in a register:" diff --git a/docs/sphinx/examples/python/building_kernels.py b/docs/sphinx/examples/python/building_kernels.py index 2228d1045be..35583fbb9be 100644 --- a/docs/sphinx/examples/python/building_kernels.py +++ b/docs/sphinx/examples/python/building_kernels.py @@ -100,7 +100,9 @@ def kernel(state: cudaq.State): # ### Applying Gates # # -# After a kernel is constructed, gates can be applied to start building out a quantum circuit. All the predefined gates in CUDA-Q can be found [here](https://nvidia.github.io/cuda-quantum/latest/api/default_ops.html#unitary-operations-on-qubits). +# After a kernel is constructed, gates can be applied to start building out a quantum circuit. +# All the predefined gates in CUDA-Q can be found here: +# https://nvidia.github.io/cuda-quantum/api/default_ops. # # # Gates can be applied to all qubits in a register: diff --git a/docs/sphinx/examples/python/executing_kernels.ipynb b/docs/sphinx/examples/python/executing_kernels.ipynb index 66a7afee3bd..0d3250c8c10 100644 --- a/docs/sphinx/examples/python/executing_kernels.ipynb +++ b/docs/sphinx/examples/python/executing_kernels.ipynb @@ -78,7 +78,7 @@ "source": [ "Note that there is a subtle difference between how `sample` is executed with the target device set to a simulator or with the target device set to a QPU. In simulation mode, the quantum state is built once and then sampled $s$ times where $s$ equals the `shots_count`. In hardware execution mode, the quantum state collapses upon measurement and hence needs to be rebuilt over and over again.\n", "\n", - "There are a number of helpful tools that can be found in the API [here](https://nvidia.github.io/cuda-quantum/latest/api/languages/python_api.html#cudaq.SampleResult) to process the `Sample_Result` object produced by `sample`." + "There are a number of helpful tools that can be found in the [API docs](https://nvidia.github.io/cuda-quantum/latest/api/languages/python_api) to process the `Sample_Result` object produced by `sample`." ] }, { diff --git a/docs/sphinx/releases.rst b/docs/sphinx/releases.rst index 6a8a3534625..de3e12d78b1 100644 --- a/docs/sphinx/releases.rst +++ b/docs/sphinx/releases.rst @@ -4,12 +4,35 @@ CUDA-Q Releases **latest** -The latest version of CUDA-Q is on the main branch of our `GitHub repository `__ and is also available as a Docker image. More information about installing the nightly builds can be found :doc:`here ` +The latest version of CUDA-Q is on the main branch of our `GitHub repository `__ +and is also available as a Docker image. More information about installing the nightly builds can be found +:doc:`here ` - `Docker image (nightly builds) `__ - `Documentation `__ - `Examples `__ +**0.9.0** + +We are very excited to share a new toolset added for modeling and manipulating the dynamics of physical systems. +The new API allows to define and execute a time evolution under arbitrary operators. For more information, take +a look at the `docs `__. +The 0.9.0 release furthermore includes a range of contribution to add new backends to CUDA-Q, including backends +from `Anyon Technologies `__, +`Ferimioniq `__, and +`QuEra Computing `__, +as well as updates to existing backends from `ORCA `__ +and `OQC `__. +We hope you enjoy the new features - also check out our new notebooks and examples to dive into CUDA-Q. + +- `Docker image `__ +- `Python wheel `__ +- `C++ installer `__ +- `Documentation `__ +- `Examples `__ + +The full change log can be found `here `__. + **0.8.0** The 0.8.0 release adds a range of changes to improve the ease of use and performance with CUDA-Q. @@ -17,7 +40,7 @@ The changes listed below highlight some of what we think will be the most useful to know about. While the listed changes do not capture all of the great contributions, we would like to extend many thanks for every contribution, in particular those from external contributors. -- `Docker image `__ +- `Docker image `__ - `Python wheel `__ - `C++ installer `__ - `Documentation `__ @@ -30,7 +53,7 @@ The full change log can be found `here `__. +`here `__. It furthermore adds a range of bug fixes and changes the Python wheel installation instructions. - `Docker image `__ @@ -46,7 +69,7 @@ The full change log can be found `here `, giving you access to our most powerful GPU-accelerated simulators even if you don't have an NVIDIA GPU. With 0.7.0, we have furthermore greatly increased expressiveness of the Python and C++ language frontends. -Check out our `documentation `__ +Check out our `documentation `__ to get started with the new Python syntax support we have added, and `follow our blog `__ to learn more about the new setup and its performance benefits. From 09f5e1f0d89f96c9827f3fb4f731adea01e6c21a Mon Sep 17 00:00:00 2001 From: Eric Schweitz Date: Thu, 12 Dec 2024 10:07:10 -0800 Subject: [PATCH 30/44] Convert static variable to a constexpr. (#2471) Make this static data structure constant for efficiency and to eliminate an initialization ctor. Signed-off-by: Eric Schweitz --- lib/Optimizer/CodeGen/ConvertToQIRProfile.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Optimizer/CodeGen/ConvertToQIRProfile.cpp b/lib/Optimizer/CodeGen/ConvertToQIRProfile.cpp index 2ae90d302b0..58526dc6929 100644 --- a/lib/Optimizer/CodeGen/ConvertToQIRProfile.cpp +++ b/lib/Optimizer/CodeGen/ConvertToQIRProfile.cpp @@ -516,7 +516,7 @@ namespace { /// trivial pass only does this preparation work. It performs no analysis and /// does not rewrite function body's, etc. -static const std::vector measurementFunctionNames{ +static constexpr std::array measurementFunctionNames{ cudaq::opt::QIRMeasureBody, cudaq::opt::QIRMeasure, cudaq::opt::QIRMeasureToRegister}; @@ -564,7 +564,7 @@ struct QIRProfilePreparationPass func.getFunctionType().getParams(), module); // Apply irreversible attribute to measurement functions - for (auto &funcName : measurementFunctionNames) { + for (auto *funcName : measurementFunctionNames) { Operation *op = SymbolTable::lookupSymbolIn(module, funcName); auto funcOp = llvm::dyn_cast_if_present(op); if (funcOp) { From 95134fdaeb8f94fc60833182e8af966daeb25c40 Mon Sep 17 00:00:00 2001 From: Pradnya Khalate <148914294+khalatepradnya@users.noreply.github.com> Date: Thu, 12 Dec 2024 11:35:55 -0800 Subject: [PATCH 31/44] Enable nightly integration tests for `infleqtion` target (#2469) Signed-off-by: Pradnya Khalate --- .github/workflows/integration_tests.yml | 46 +++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 55d329b93f2..5c5bc28300b 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -17,6 +17,7 @@ on: options: - nightly - anyon + - infleqtion - ionq - iqm - oqc @@ -650,6 +651,51 @@ jobs: fi shell: bash + - name: Submit to Infleqtion test server + if: (success() || failure()) && (inputs.target == 'infleqtion' || github.event_name == 'schedule' || inputs.target == 'nightly') + run: | + echo "### Submit to Infleqtion server" >> $GITHUB_STEP_SUMMARY + export SUPERSTAQ_API_KEY='${{ secrets.SUPERSTAQ_API_KEY }}' + set +e # Allow script to keep going through errors + test_err_sum=0 + cpp_tests="docs/sphinx/targets/cpp/infleqtion.cpp" + for filename in $cpp_tests; do + [ -e "$filename" ] || echo "::error::Couldn't find file ($filename)" + nvq++ --target infleqtion $filename + test_status=$? + if [ $test_status -eq 0 ]; then + ./a.out + test_status=$? + if [ $test_status -eq 0 ]; then + echo ":white_check_mark: Successfully ran test: $filename" >> $GITHUB_STEP_SUMMARY + else + echo ":x: Test failed (failed to execute): $filename" >> $GITHUB_STEP_SUMMARY + test_err_sum=$((test_err_sum+1)) + fi + else + echo ":x: Test failed (failed to compile): $filename" >> $GITHUB_STEP_SUMMARY + test_err_sum=$((test_err_sum+1)) + fi + done + python_tests="docs/sphinx/targets/python/infleqtion.py" + for filename in $python_tests; do + [ -e "$filename" ] || echo "::error::Couldn't find file ($filename)" + python3 $filename 1> /dev/null + test_status=$? + if [ $test_status -eq 0 ]; then + echo ":white_check_mark: Successfully ran test: $filename" >> $GITHUB_STEP_SUMMARY + else + echo ":x: Test failed (failed to execute): $filename" >> $GITHUB_STEP_SUMMARY + test_err_sum=$((test_err_sum+1)) + fi + done + set -e # Re-enable exit code error checking + if [ ! $test_err_sum -eq 0 ]; then + echo "::error::${test_err_sum} tests failed. See step summary for a list of failures" + exit 1 + fi + shell: bash + - name: Submit to ${{ inputs.target }} # The full set of tests used by this step is currently only supported on # Quantinuum. The other supported tests are tested by the step above. From 105c05a15c5533cf2aadb0ede263f110f44426a0 Mon Sep 17 00:00:00 2001 From: Pradnya Khalate <148914294+khalatepradnya@users.noreply.github.com> Date: Thu, 12 Dec 2024 13:03:46 -0800 Subject: [PATCH 32/44] Enable nightly integration tests for `anyon` target (#2308) Signed-off-by: Pradnya Khalate --- .github/workflows/integration_tests.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 5c5bc28300b..5619f5baea0 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -336,11 +336,7 @@ jobs: fi - name: Setup anyon account - # This step is currently bypassed during nightly runs due to - # maintenance. Restore the if check to the original value when - # maintenance is complete. - #if: github.event_name == 'schedule' || inputs.target == 'nightly' || inputs.target == 'anyon' - if: inputs.target == 'anyon' + if: github.event_name == 'schedule' || inputs.target == 'nightly' || inputs.target == 'anyon' run: | curl -X POST --user "${{ secrets.ANYON_USERNAME }}:${{ secrets.ANYON_PASSWORD }}" -H "Content-Type: application/json" https://api.anyon.cloud:5000/login > credentials.json id_token=`cat credentials.json | jq -r '."id_token"'` @@ -349,11 +345,7 @@ jobs: echo "refresh: $refresh_token" >> ~/.anyon_config - name: QIR syntax check (Anyon) - # This step is currently bypassed during nightly runs due to - # maintenance. Restore the if check to the original value when - # maintenance is complete. - #if: github.event_name == 'schedule' || inputs.target == 'nightly' || inputs.target == 'anyon' - if: inputs.target == 'anyon' + if: github.event_name == 'schedule' || inputs.target == 'nightly' || inputs.target == 'anyon' run: | echo "### QIR syntax check (Anyon)" >> $GITHUB_STEP_SUMMARY export CUDAQ_LOG_LEVEL="info" From 2cf6b04d068aa40071a36f169795bebe28c61f3e Mon Sep 17 00:00:00 2001 From: Eric Schweitz Date: Thu, 12 Dec 2024 17:14:17 -0800 Subject: [PATCH 33/44] [core] Move canonical patterns out of tablegen. (#2473) * [core] Move canonical patterns out of tablegen. Fix #476. Eliminate the file Canonical.td and move/rewrite the patterns from that file. Signed-off-by: Eric Schweitz * Fix comments. Signed-off-by: Eric Schweitz * Move OpRewritePatterns to the new file. Signed-off-by: Eric Schweitz --------- Signed-off-by: Eric Schweitz --- .../Optimizer/Dialect/Quake/CMakeLists.txt | 4 - .../Optimizer/Dialect/Quake/Canonical.td | 67 --- lib/Optimizer/Dialect/Quake/CMakeLists.txt | 1 - .../Dialect/Quake/CanonicalPatterns.inc | 489 ++++++++++++++++++ lib/Optimizer/Dialect/Quake/QuakeOps.cpp | 410 +-------------- 5 files changed, 490 insertions(+), 481 deletions(-) delete mode 100644 include/cudaq/Optimizer/Dialect/Quake/Canonical.td create mode 100644 lib/Optimizer/Dialect/Quake/CanonicalPatterns.inc diff --git a/include/cudaq/Optimizer/Dialect/Quake/CMakeLists.txt b/include/cudaq/Optimizer/Dialect/Quake/CMakeLists.txt index d038abd0409..6dca96dc833 100644 --- a/include/cudaq/Optimizer/Dialect/Quake/CMakeLists.txt +++ b/include/cudaq/Optimizer/Dialect/Quake/CMakeLists.txt @@ -9,7 +9,3 @@ add_cudaq_dialect(Quake quake) add_cudaq_interface(QuakeInterfaces) add_cudaq_dialect_doc(QuakeDialect quake) - -set(LLVM_TARGET_DEFINITIONS Canonical.td) -mlir_tablegen(Canonical.inc -gen-rewriters) -add_public_tablegen_target(CanonicalIncGen) diff --git a/include/cudaq/Optimizer/Dialect/Quake/Canonical.td b/include/cudaq/Optimizer/Dialect/Quake/Canonical.td deleted file mode 100644 index d7aec89e6f6..00000000000 --- a/include/cudaq/Optimizer/Dialect/Quake/Canonical.td +++ /dev/null @@ -1,67 +0,0 @@ -/********************************************************** -*- tablegen -*- *** - * 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. * - ******************************************************************************/ - -#ifndef NVQPP_OPTIMIZER_DIALECT_QUAKE_CANONICAL -#define NVQPP_OPTIMIZER_DIALECT_QUAKE_CANONICAL - -include "mlir/IR/OpBase.td" -include "mlir/IR/PatternBase.td" -include "mlir/Dialect/Arith/IR/ArithOps.td" -include "cudaq/Optimizer/Dialect/Quake/QuakeOps.td" - -def KnownSizePred : Constraint< - CPred<"$0.getType().isa() && " - "$0.getType().cast().hasSpecifiedSize()">>; - -def UnknownSizePred : Constraint< - CPred<"$0.getType().isa() && " - "!$0.getType().cast().hasSpecifiedSize()">>; - -def createConstantOp : NativeCodeCall< - "$_builder.create($_loc, $0.getType()," - " $_builder.getIntegerAttr($0.getType()," - " $1.getType().cast().getSize()))">; - -// %4 = quake.veq_size %3 : (!quake.veq<10>) -> 164 -// ──────────────────────────────────────────────── -// %4 = constant 10 : i64 -def ForwardConstantVeqSizePattern : Pat< - (quake_VeqSizeOp:$res $veq), (createConstantOp $res, $veq), - [(KnownSizePred $veq)]>; - -def SizeIsPresentPred : Constraint(" - " $0[0].getDefiningOp())">>; - -def createAllocaOp : NativeCodeCall< - "quake::createConstantAlloca($_builder, $_loc, $0, $1)">; - -// %2 = constant 10 : i32 -// %3 = quake.alloca !quake.veq[%2 : i32] -// ─────────────────────────────────────────── -// %3 = quake.alloca !quake.veq<10> -def FuseConstantToAllocaPattern : Pat< - (quake_AllocaOp:$alloca $optSize), (createAllocaOp $alloca, $optSize), - [(SizeIsPresentPred $optSize)]>; - -def createExtractRefOp : NativeCodeCall< - "$_builder.create($_loc, $0," - " cast($1[0].getDefiningOp()).getValue()." - " cast().getInt())">; - -// %2 = constant 10 : i32 -// %3 = quake.extract_ref %1[%2] : (!quake.veq, i32) -> !quake.ref -// ─────────────────────────────────────────── -// %3 = quake.extract_ref %1[10] : (!quake.veq) -> !quake.ref -def FuseConstantToExtractRefPattern : Pat< - (quake_ExtractRefOp $veq, $index, $rawIndex), - (createExtractRefOp $veq, $index), - [(SizeIsPresentPred $index)]>; - -#endif diff --git a/lib/Optimizer/Dialect/Quake/CMakeLists.txt b/lib/Optimizer/Dialect/Quake/CMakeLists.txt index 87733716c41..bc55e40d525 100644 --- a/lib/Optimizer/Dialect/Quake/CMakeLists.txt +++ b/lib/Optimizer/Dialect/Quake/CMakeLists.txt @@ -16,7 +16,6 @@ add_cudaq_dialect_library(QuakeDialect QuakeDialectIncGen QuakeOpsIncGen QuakeTypesIncGen - CanonicalIncGen LINK_LIBS CCDialect diff --git a/lib/Optimizer/Dialect/Quake/CanonicalPatterns.inc b/lib/Optimizer/Dialect/Quake/CanonicalPatterns.inc new file mode 100644 index 00000000000..de1eada9be9 --- /dev/null +++ b/lib/Optimizer/Dialect/Quake/CanonicalPatterns.inc @@ -0,0 +1,489 @@ +/****************************************************************-*- C++ -*-**** + * 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. * + ******************************************************************************/ + +// These canonicalization patterns are used by the canonicalize pass and not +// shared for other uses. Generally speaking, these patterns should be trivial +// peephole optimizations that reduce the size and complexity of the input IR. + +// This file must be included after a `using namespace mlir;` as it uses bare +// identifiers from that namespace. + +namespace { + +// %4 = quake.veq_size %3 : (!quake.veq<10>) -> 164 +// ──────────────────────────────────────────────── +// %4 = constant 10 : i64 +struct ForwardConstantVeqSizePattern + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::VeqSizeOp veqSize, + PatternRewriter &rewriter) const override { + auto veqTy = dyn_cast(veqSize.getVeq().getType()); + if (!veqTy) + return failure(); + if (!veqTy.hasSpecifiedSize()) + return failure(); + auto resTy = veqSize.getType(); + rewriter.replaceOpWithNewOp(veqSize, veqTy.getSize(), + resTy); + return success(); + } +}; + +// %2 = constant 10 : i32 +// %3 = quake.alloca !quake.veq[%2 : i32] +// ───────────────────────────────────────── +// %3 = quake.alloca !quake.veq<10> +struct FuseConstantToAllocaPattern : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::AllocaOp alloc, + PatternRewriter &rewriter) const override { + auto size = alloc.getSize(); + if (!size) + return failure(); + auto intCon = cudaq::opt::factory::getIntIfConstant(size); + if (!intCon) + return failure(); + auto veqTy = dyn_cast(alloc.getType()); + if (!veqTy) + return failure(); + if (veqTy.hasSpecifiedSize()) + return failure(); + auto loc = alloc.getLoc(); + auto resTy = alloc.getType(); + auto newAlloc = rewriter.create( + loc, static_cast(*intCon)); + rewriter.replaceOpWithNewOp(alloc, resTy, newAlloc); + return success(); + } +}; + +// %2 = constant 10 : i32 +// %3 = quake.extract_ref %1[%2] : (!quake.veq, i32) -> !quake.ref +// ────────────────────────────────────────────────────────────────── +// %3 = quake.extract_ref %1[10] : (!quake.veq) -> !quake.ref +struct FuseConstantToExtractRefPattern + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::ExtractRefOp extract, + PatternRewriter &rewriter) const override { + auto index = extract.getIndex(); + if (!index) + return failure(); + auto intCon = cudaq::opt::factory::getIntIfConstant(index); + if (!intCon) + return failure(); + rewriter.replaceOpWithNewOp( + extract, extract.getVeq(), static_cast(*intCon)); + return success(); + } +}; + +// %4 = quake.concat %2, %3 : (!quake.ref, !quake.ref) -> !quake.veq<2> +// %7 = quake.extract_ref %4[0] : (!quake.veq<2>) -> !quake.ref +// ─────────────────────────────────────────── +// replace all use with %2 +struct ForwardConcatExtractPattern + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::ExtractRefOp extract, + PatternRewriter &rewriter) const override { + auto veq = extract.getVeq(); + auto concatOp = veq.getDefiningOp(); + if (concatOp && extract.hasConstantIndex()) { + // Don't run this canonicalization if any of the operands + // to concat are of type veq. + auto concatQubits = concatOp.getQbits(); + for (auto qOp : concatQubits) + if (isa(qOp.getType())) + return failure(); + + // concat only has ref type operands. + auto index = extract.getConstantIndex(); + if (index < concatQubits.size()) { + auto qOpValue = concatQubits[index]; + if (isa(qOpValue.getType())) { + rewriter.replaceOp(extract, {qOpValue}); + return success(); + } + } + } + return failure(); + } +}; + +// %2 = quake.concat %1 : (!quake.ref) -> !quake.veq<1> +// %3 = quake.extract_ref %2[0] : (!quake.veq<1>) -> !quake.ref +// quake.* %3 ... +// ─────────────────────────────────────────── +// quake.* %1 ... +struct ForwardConcatExtractSingleton + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::ExtractRefOp extract, + PatternRewriter &rewriter) const override { + if (auto concat = extract.getVeq().getDefiningOp()) + if (concat.getType().getSize() == 1 && extract.hasConstantIndex() && + extract.getConstantIndex() == 0) { + assert(concat.getQbits().size() == 1 && concat.getQbits()[0]); + extract.getResult().replaceUsesWithIf( + concat.getQbits()[0], [&](OpOperand &use) { + if (Operation *user = use.getOwner()) + return isQuakeOperation(user); + return false; + }); + return success(); + } + return failure(); + } +}; + +// %7 = quake.concat %4 : (!quake.veq<2>) -> !quake.veq<2> +// ─────────────────────────────────────────── +// removed +struct ConcatNoOpPattern : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::ConcatOp concat, + PatternRewriter &rewriter) const override { + // Remove concat veq -> veq + // or + // concat ref -> ref + auto qubitsToConcat = concat.getQbits(); + if (qubitsToConcat.size() > 1) + return failure(); + + // We only want to handle veq -> veq here. + if (isa(qubitsToConcat.front().getType())) { + return failure(); + } + + // Do not handle anything where we don't know the sizes. + auto retTy = concat.getResult().getType(); + if (auto veqTy = dyn_cast(retTy)) + if (!veqTy.hasSpecifiedSize()) + // This could be a folded quake.relax_size op. + return failure(); + + rewriter.replaceOp(concat, qubitsToConcat); + return success(); + } +}; + +// %8 = quake.concat %4, %5, %6 : (!quake.ref, !quake.veq<4>, +// !quake.veq<2>) -> !quake.veq +// ─────────────────────────────────────────────────────────── +// %.8 = quake.concat %4, %5, %6 : (!quake.ref, !quake.veq<4>, +// !quake.veq<2>) -> !quake.veq<7> +// %8 = quake.relax_size %.8 : (!quake.veq<7>) -> !quake.veq +struct ConcatSizePattern : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::ConcatOp concat, + PatternRewriter &rewriter) const override { + if (concat.getType().hasSpecifiedSize()) + return failure(); + + // Walk the arguments and sum them, if possible. + std::size_t sum = 0; + for (auto opnd : concat.getQbits()) { + if (auto veqTy = dyn_cast(opnd.getType())) { + if (!veqTy.hasSpecifiedSize()) + return failure(); + sum += veqTy.getSize(); + continue; + } + assert(isa(opnd.getType())); + sum++; + } + + // Leans into the relax_size canonicalization pattern. + auto *ctx = rewriter.getContext(); + auto loc = concat.getLoc(); + auto newTy = quake::VeqType::get(ctx, sum); + Value newOp = + rewriter.create(loc, newTy, concat.getQbits()); + auto noSizeTy = quake::VeqType::getUnsized(ctx); + rewriter.replaceOpWithNewOp(concat, noSizeTy, newOp); + return success(); + } +}; + +// %7 = quake.make_struq %5, %6 : (!quake.veq, !quake.veq) -> +// !quake.struq, !quake.veq> +// %8 = quake.get_member %7[1] : (!quake.struq, +// !quake.veq>) -> !quake.veq +// ─────────────────────────────────────────────────────────── +// replace uses of %8 with %6 +struct BypassMakeStruq : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::GetMemberOp getMem, + PatternRewriter &rewriter) const override { + auto makeStruq = getMem.getStruq().getDefiningOp(); + if (!makeStruq) + return failure(); + auto toStrTy = cast(getMem.getStruq().getType()); + std::uint32_t idx = getMem.getIndex(); + Value from = makeStruq.getOperand(idx); + auto toTy = toStrTy.getMembers()[idx]; + if (from.getType() != toTy) + rewriter.replaceOpWithNewOp(getMem, toTy, from); + else + rewriter.replaceOp(getMem, from); + return success(); + } +}; + +// %22 = quake.init_state %1, %2 : (!quake.veq, T) -> !quake.veq +// ──────────────────────────────────────────────────────────────────── +// %.22 = quake.init_state %1, %2 : (!quake.veq, T) -> !quake.veq +// %22 = quake.relax_size %.22 : (!quake.veq) -> !quake.veq +struct ForwardAllocaTypePattern + : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::InitializeStateOp initState, + PatternRewriter &rewriter) const override { + if (auto isTy = dyn_cast(initState.getType())) + if (!isTy.hasSpecifiedSize()) { + auto targ = initState.getTargets(); + if (auto targTy = dyn_cast(targ.getType())) + if (targTy.hasSpecifiedSize()) { + auto newInit = rewriter.create( + initState.getLoc(), targTy, targ, initState.getState()); + rewriter.replaceOpWithNewOp(initState, isTy, + newInit); + return success(); + } + } + + // Remove any intervening cast to !cc.ptr> ops. + if (auto stateCast = + initState.getState().getDefiningOp()) + if (auto ptrTy = dyn_cast(stateCast.getType())) { + auto eleTy = ptrTy.getElementType(); + if (auto arrTy = dyn_cast(eleTy)) + if (arrTy.isUnknownSize()) { + rewriter.replaceOpWithNewOp( + initState, initState.getTargets().getType(), + initState.getTargets(), stateCast.getValue()); + return success(); + } + } + return failure(); + } +}; + +// %3 = quake.subveq %0, 4, 10 : (!quake.veq<12>, i64, i64) -> !quake.veq +// ────────────────────────────────────────────────────────────────────────── +// %.3 = quake.subveq %0, 4, 10 : (!quake.veq<12>, i64, i64) -> !quake.veq<7> +// %3 = quake.relax_size %.3 : (!quake.veq<7>) -> !quake.veq +struct FixUnspecifiedSubveqPattern : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::SubVeqOp subveq, + PatternRewriter &rewriter) const override { + auto veqTy = dyn_cast(subveq.getType()); + if (veqTy && veqTy.hasSpecifiedSize()) + return failure(); + if (!(subveq.hasConstantLowerBound() && subveq.hasConstantUpperBound())) + return failure(); + auto *ctx = rewriter.getContext(); + std::size_t size = + subveq.getConstantUpperBound() - subveq.getConstantLowerBound() + 1u; + auto szVecTy = quake::VeqType::get(ctx, size); + auto loc = subveq.getLoc(); + auto subv = rewriter.create( + loc, szVecTy, subveq.getVeq(), subveq.getLower(), subveq.getUpper(), + subveq.getRawLower(), subveq.getRawUpper()); + rewriter.replaceOpWithNewOp(subveq, veqTy, subv); + return success(); + } +}; + +// %1 = constant 4 : i64 +// %2 = constant 10 : i64 +// %3 = quake.subveq %0, %1, %2 : (!quake.veq<12>, i64, i64) -> !quake.veq +// ────────────────────────────────────────────────────────────────────────── +// %3 = quake.subveq %0, 4, 10 : (!quake.veq<12>, i64, i64) -> !quake.veq<7> +struct FuseConstantToSubveqPattern : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::SubVeqOp subveq, + PatternRewriter &rewriter) const override { + if (subveq.hasConstantLowerBound() && subveq.hasConstantUpperBound()) + return failure(); + bool regen = false; + std::int64_t lo = subveq.getConstantLowerBound(); + Value loVal = subveq.getLower(); + if (!subveq.hasConstantLowerBound()) + if (auto olo = cudaq::opt::factory::getIntIfConstant(subveq.getLower())) { + regen = true; + loVal = nullptr; + lo = *olo; + } + + std::int64_t hi = subveq.getConstantUpperBound(); + Value hiVal = subveq.getUpper(); + if (!subveq.hasConstantUpperBound()) + if (auto ohi = cudaq::opt::factory::getIntIfConstant(subveq.getUpper())) { + regen = true; + hiVal = nullptr; + hi = *ohi; + } + + if (!regen) + return failure(); + rewriter.replaceOpWithNewOp( + subveq, subveq.getType(), subveq.getVeq(), loVal, hiVal, lo, hi); + return success(); + } +}; + +// Replace subveq operations that extract the entire original register with the +// original register. +struct RemoveSubVeqNoOpPattern : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::SubVeqOp subVeqOp, + PatternRewriter &rewriter) const override { + auto origVeq = subVeqOp.getVeq(); + // The original veq size must be known + auto veqType = dyn_cast(origVeq.getType()); + if (!veqType.hasSpecifiedSize()) + return failure(); + if (!(subVeqOp.hasConstantLowerBound() && subVeqOp.hasConstantUpperBound())) + return failure(); + + // If the subveq is the whole register, than the start value must be 0. + if (subVeqOp.getConstantLowerBound() != 0) + return failure(); + + // If the sizes are equal, then replace + if (veqType.getSize() != subVeqOp.getConstantUpperBound() + 1) + return failure(); + + // this subveq is the whole original register, hence a no-op + rewriter.replaceOp(subVeqOp, origVeq); + return success(); + } +}; + +// %11 = quake.init_state %_, %_ : (!quake.veq<2>, T1) -> !quake.veq +// %12 = quake.veq_size %11 : (!quake.veq) -> i64 +// ──────────────────────────────────────────────────────────────────── +// %11 = quake.init_state %_, %_ : (!quake.veq<2>, T1) -> !quake.veq +// %12 = constant 2 : i64 +struct FoldInitStateSizePattern : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::VeqSizeOp veqSize, + PatternRewriter &rewriter) const override { + Value veq = veqSize.getVeq(); + if (auto initState = veq.getDefiningOp()) + if (auto veqTy = + dyn_cast(initState.getTargets().getType())) + if (veqTy.hasSpecifiedSize()) { + std::size_t numQubits = veqTy.getSize(); + rewriter.replaceOpWithNewOp(veqSize, numQubits, + veqSize.getType()); + return success(); + } + return failure(); + } +}; + +// If there is no operation that modifies the wire after it gets unwrapped and +// before it is wrapped, then the wrap operation is a nop and can be +// eliminated. +struct KillDeadWrapPattern : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::WrapOp wrap, + PatternRewriter &rewriter) const override { + if (auto unwrap = wrap.getWireValue().getDefiningOp()) + rewriter.eraseOp(wrap); + return success(); + } +}; + +template +struct MergeRotationPattern : public OpRewritePattern { + using Base = OpRewritePattern; + using Base::Base; + + LogicalResult matchAndRewrite(OP rotate, + PatternRewriter &rewriter) const override { + auto wireTy = quake::WireType::get(rewriter.getContext()); + if (rotate.getTarget(0).getType() != wireTy || + !rotate.getControls().empty()) + return failure(); + assert(!rotate.getNegatedQubitControls()); + auto input = rotate.getTarget(0).template getDefiningOp(); + if (!input || !input.getControls().empty()) + return failure(); + assert(!input.getNegatedQubitControls()); + + // At this point, we have + // %input = quake.rotate %angle1, %wire + // %rotate = quake.rotate %angle2, %input + // Replace those ops with + // %new = quake.rotate (%angle1 + %angle2), %wire + auto loc = rotate.getLoc(); + auto angle1 = input.getParameter(0); + auto angle2 = rotate.getParameter(0); + if (angle1.getType() != angle2.getType()) + return failure(); + auto adjAttr = rotate.getIsAdjAttr(); + auto newAngle = [&]() -> Value { + if (input.isAdj() == rotate.isAdj()) + return rewriter.create(loc, angle1, angle2); + // One is adjoint, so it should be subtracted from the other. + if (input.isAdj()) + return rewriter.create(loc, angle2, angle1); + adjAttr = input.getIsAdjAttr(); + return rewriter.create(loc, angle1, angle2); + }(); + rewriter.replaceOpWithNewOp(rotate, rotate.getResultTypes(), adjAttr, + ValueRange{newAngle}, ValueRange{}, + ValueRange{input.getTarget(0)}, + rotate.getNegatedQubitControlsAttr()); + return success(); + } +}; + +// Forward the argument to a relax_size to the users for all users that are +// quake operations. All quake ops that take a sized veq argument are +// polymorphic on all veq types. If the op is not a quake op, then maintain +// strong typing. +struct ForwardRelaxedSizePattern : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(quake::RelaxSizeOp relax, + PatternRewriter &rewriter) const override { + auto inpVec = relax.getInputVec(); + bool replaced = false; + rewriter.replaceOpWithIf(relax, inpVec, [&](OpOperand &use) { + bool res = false; + if (Operation *user = use.getOwner()) + res = isQuakeOperation(user) && !isa(user); + replaced = replaced || res; + return res; + }); + // return success if and only if at least one use was replaced. + return success(replaced); + }; +}; + +} // namespace diff --git a/lib/Optimizer/Dialect/Quake/QuakeOps.cpp b/lib/Optimizer/Dialect/Quake/QuakeOps.cpp index 1f2c3bd06d8..d2da75d99b3 100644 --- a/lib/Optimizer/Dialect/Quake/QuakeOps.cpp +++ b/lib/Optimizer/Dialect/Quake/QuakeOps.cpp @@ -23,9 +23,7 @@ using namespace mlir; -namespace { -#include "cudaq/Optimizer/Dialect/Quake/Canonical.inc" -} // namespace +#include "CanonicalPatterns.inc" static LogicalResult verifyWireResultsAreLinear(Operation *op) { for (Value v : op->getOpResults()) @@ -311,73 +309,6 @@ LogicalResult quake::BorrowWireOp::verify() { // Concat //===----------------------------------------------------------------------===// -namespace { -// %7 = quake.concat %4 : (!quake.veq<2>) -> !quake.veq<2> -// ─────────────────────────────────────────── -// removed -struct ConcatNoOpPattern : public OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(quake::ConcatOp concat, - PatternRewriter &rewriter) const override { - // Remove concat veq -> veq - // or - // concat ref -> ref - auto qubitsToConcat = concat.getQbits(); - if (qubitsToConcat.size() > 1) - return failure(); - - // We only want to handle veq -> veq here. - if (isa(qubitsToConcat.front().getType())) { - return failure(); - } - - // Do not handle anything where we don't know the sizes. - auto retTy = concat.getResult().getType(); - if (auto veqTy = dyn_cast(retTy)) - if (!veqTy.hasSpecifiedSize()) - // This could be a folded quake.relax_size op. - return failure(); - - rewriter.replaceOp(concat, qubitsToConcat); - return success(); - } -}; - -struct ConcatSizePattern : public OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(quake::ConcatOp concat, - PatternRewriter &rewriter) const override { - if (concat.getType().hasSpecifiedSize()) - return failure(); - - // Walk the arguments and sum them, if possible. - std::size_t sum = 0; - for (auto opnd : concat.getQbits()) { - if (auto veqTy = dyn_cast(opnd.getType())) { - if (!veqTy.hasSpecifiedSize()) - return failure(); - sum += veqTy.getSize(); - continue; - } - assert(isa(opnd.getType())); - sum++; - } - - // Leans into the relax_size canonicalization pattern. - auto *ctx = rewriter.getContext(); - auto loc = concat.getLoc(); - auto newTy = quake::VeqType::get(ctx, sum); - Value newOp = - rewriter.create(loc, newTy, concat.getQbits()); - auto noSizeTy = quake::VeqType::getUnsized(ctx); - rewriter.replaceOpWithNewOp(concat, noSizeTy, newOp); - return success(); - }; -}; -} // namespace - void quake::ConcatOp::getCanonicalizationPatterns(RewritePatternSet &patterns, MLIRContext *context) { patterns.add(context); @@ -418,69 +349,6 @@ void printRawIndex(OpAsmPrinter &printer, OP refOp, Value index, printer << rawIndex.getValue(); } -namespace { -// %4 = quake.concat %2, %3 : (!quake.ref, !quake.ref) -> !quake.veq<2> -// %7 = quake.extract_ref %4[0] : (!quake.veq<2>) -> !quake.ref -// ─────────────────────────────────────────── -// replace all use with %2 -struct ForwardConcatExtractPattern - : public OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(quake::ExtractRefOp extract, - PatternRewriter &rewriter) const override { - auto veq = extract.getVeq(); - auto concatOp = veq.getDefiningOp(); - if (concatOp && extract.hasConstantIndex()) { - // Don't run this canonicalization if any of the operands - // to concat are of type veq. - auto concatQubits = concatOp.getQbits(); - for (auto qOp : concatQubits) - if (isa(qOp.getType())) - return failure(); - - // concat only has ref type operands. - auto index = extract.getConstantIndex(); - if (index < concatQubits.size()) { - auto qOpValue = concatQubits[index]; - if (isa(qOpValue.getType())) { - rewriter.replaceOp(extract, {qOpValue}); - return success(); - } - } - } - return failure(); - } -}; - -// %2 = quake.concat %1 : (!quake.ref) -> !quake.veq<1> -// %3 = quake.extract_ref %2[0] : (!quake.veq<1>) -> !quake.ref -// quake.* %3 ... -// ─────────────────────────────────────────── -// quake.* %1 ... -struct ForwardConcatExtractSingleton - : public OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(quake::ExtractRefOp extract, - PatternRewriter &rewriter) const override { - if (auto concat = extract.getVeq().getDefiningOp()) - if (concat.getType().getSize() == 1 && extract.hasConstantIndex() && - extract.getConstantIndex() == 0) { - assert(concat.getQbits().size() == 1 && concat.getQbits()[0]); - extract.getResult().replaceUsesWithIf( - concat.getQbits()[0], [&](OpOperand &use) { - if (Operation *user = use.getOwner()) - return isQuakeOperation(user); - return false; - }); - return success(); - } - return failure(); - } -}; -} // namespace - void quake::ExtractRefOp::getCanonicalizationPatterns( RewritePatternSet &patterns, MLIRContext *context) { patterns.add { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(quake::GetMemberOp getMem, - PatternRewriter &rewriter) const override { - if (auto makeStruq = - getMem.getStruq().getDefiningOp()) { - auto toStrTy = cast(getMem.getStruq().getType()); - std::uint32_t idx = getMem.getIndex(); - Value from = makeStruq.getOperand(idx); - auto toTy = toStrTy.getMembers()[idx]; - if (from.getType() != toTy) { - rewriter.replaceOpWithNewOp(getMem, toTy, from); - } else { - rewriter.replaceOp(getMem, from); - } - return success(); - } - return failure(); - } -}; -} // namespace - void quake::GetMemberOp::getCanonicalizationPatterns( RewritePatternSet &patterns, MLIRContext *context) { patterns.add(context); @@ -575,48 +419,6 @@ LogicalResult quake::InitializeStateOp::verify() { return success(); } -namespace { -// %22 = quake.init_state %1, %2 : (!quake.veq, T) -> !quake.veq -// ──────────────────────────────────────────────────────────────────── -// %22' = quake.init_state %1, %2 : (!quake.veq, T) -> !quake.veq -// %22 = quake.relax_size %22' : (!quake.veq) -> !quake.veq -struct ForwardAllocaTypePattern - : public OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(quake::InitializeStateOp initState, - PatternRewriter &rewriter) const override { - if (auto isTy = dyn_cast(initState.getType())) - if (!isTy.hasSpecifiedSize()) { - auto targ = initState.getTargets(); - if (auto targTy = dyn_cast(targ.getType())) - if (targTy.hasSpecifiedSize()) { - auto newInit = rewriter.create( - initState.getLoc(), targTy, targ, initState.getState()); - rewriter.replaceOpWithNewOp(initState, isTy, - newInit); - return success(); - } - } - - // Remove any intervening cast to !cc.ptr> ops. - if (auto stateCast = - initState.getState().getDefiningOp()) - if (auto ptrTy = dyn_cast(stateCast.getType())) { - auto eleTy = ptrTy.getElementType(); - if (auto arrTy = dyn_cast(eleTy)) - if (arrTy.isUnknownSize()) { - rewriter.replaceOpWithNewOp( - initState, initState.getTargets().getType(), - initState.getTargets(), stateCast.getValue()); - return success(); - } - } - return failure(); - } -}; -} // namespace - void quake::InitializeStateOp::getCanonicalizationPatterns( RewritePatternSet &patterns, MLIRContext *context) { patterns.add(context); @@ -652,30 +454,6 @@ LogicalResult quake::RelaxSizeOp::verify() { return success(); } -namespace { -// Forward the argument to a relax_size to the users for all users that are -// quake operations. All quake ops that take a sized veq argument are -// polymorphic on all veq types. If the op is not a quake op, then maintain -// strong typing. -struct ForwardRelaxedSizePattern : public RewritePattern { - ForwardRelaxedSizePattern(MLIRContext *context) - : RewritePattern("quake.relax_size", 1, context, {}) {} - - LogicalResult matchAndRewrite(Operation *op, - PatternRewriter &rewriter) const override { - auto relax = cast(op); - auto inpVec = relax.getInputVec(); - Value result = relax.getResult(); - result.replaceUsesWithIf(inpVec, [&](OpOperand &use) { - if (Operation *user = use.getOwner()) - return isQuakeOperation(user) && !isa(user); - return false; - }); - return success(); - }; -}; -} // namespace - void quake::RelaxSizeOp::getCanonicalizationPatterns( RewritePatternSet &patterns, MLIRContext *context) { patterns.add(context); @@ -709,103 +487,6 @@ LogicalResult quake::SubVeqOp::verify() { return success(); } -namespace { -// %3 = quake.subveq %0, 4, 10 : (!quake.veq<12>, i64, i64) -> !quake.veq -// ───────────────────────────────────────────────────────────────────────────── -// %new3 = quake.subveq %0, 4, 10 : (!quake.veq<12>, i64, i64) -> !quake.veq<7> -// %3 = quake.relax_size %new3 : (!quake.veq<7>) -> !quake.veq -struct FixUnspecifiedSubveqPattern : public OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(quake::SubVeqOp subveq, - PatternRewriter &rewriter) const override { - auto veqTy = dyn_cast(subveq.getType()); - if (veqTy && veqTy.hasSpecifiedSize()) - return failure(); - if (!(subveq.hasConstantLowerBound() && subveq.hasConstantUpperBound())) - return failure(); - auto *ctx = rewriter.getContext(); - std::size_t size = - subveq.getConstantUpperBound() - subveq.getConstantLowerBound() + 1u; - auto szVecTy = quake::VeqType::get(ctx, size); - auto loc = subveq.getLoc(); - auto subv = rewriter.create( - loc, szVecTy, subveq.getVeq(), subveq.getLower(), subveq.getUpper(), - subveq.getRawLower(), subveq.getRawUpper()); - rewriter.replaceOpWithNewOp(subveq, veqTy, subv); - return success(); - } -}; - -// %1 = constant 4 : i64 -// %2 = constant 10 : i64 -// %3 = quake.subveq %0, %1, %2 : (!quake.veq<12>, i64, i64) -> !quake.veq -// ───────────────────────────────────────────────────────────────────────────── -// %3 = quake.subveq %0, 4, 10 : (!quake.veq<12>, i64, i64) -> !quake.veq<7> -struct FuseConstantToSubveqPattern : public OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(quake::SubVeqOp subveq, - PatternRewriter &rewriter) const override { - if (subveq.hasConstantLowerBound() && subveq.hasConstantUpperBound()) - return failure(); - bool regen = false; - std::int64_t lo = subveq.getConstantLowerBound(); - Value loVal = subveq.getLower(); - if (!subveq.hasConstantLowerBound()) - if (auto olo = cudaq::opt::factory::getIntIfConstant(subveq.getLower())) { - regen = true; - loVal = nullptr; - lo = *olo; - } - - std::int64_t hi = subveq.getConstantUpperBound(); - Value hiVal = subveq.getUpper(); - if (!subveq.hasConstantUpperBound()) - if (auto ohi = cudaq::opt::factory::getIntIfConstant(subveq.getUpper())) { - regen = true; - hiVal = nullptr; - hi = *ohi; - } - - if (!regen) - return failure(); - rewriter.replaceOpWithNewOp( - subveq, subveq.getType(), subveq.getVeq(), loVal, hiVal, lo, hi); - return success(); - } -}; - -// Replace subveq operations that extract the entire original register with the -// original register. -struct RemoveSubVeqNoOpPattern : public OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(quake::SubVeqOp subVeqOp, - PatternRewriter &rewriter) const override { - auto origVeq = subVeqOp.getVeq(); - // The original veq size must be known - auto veqType = dyn_cast(origVeq.getType()); - if (!veqType.hasSpecifiedSize()) - return failure(); - if (!(subVeqOp.hasConstantLowerBound() && subVeqOp.hasConstantUpperBound())) - return failure(); - - // If the subveq is the whole register, than the start value must be 0. - if (subVeqOp.getConstantLowerBound() != 0) - return failure(); - - // If the sizes are equal, then replace - if (veqType.getSize() != subVeqOp.getConstantUpperBound() + 1) - return failure(); - - // this subveq is the whole original register, hence a no-op - rewriter.replaceOp(subVeqOp, origVeq); - return success(); - } -}; -} // namespace - void quake::SubVeqOp::getCanonicalizationPatterns(RewritePatternSet &patterns, MLIRContext *context) { patterns.add { - using OpRewritePattern::OpRewritePattern; - - // %11 = quake.init_state %_, %_ : (!quake.veq<2>, T1) -> !quake.veq - // %12 = quake.veq_size %11 : (!quake.veq) -> i64 - // ──────────────────────────────────────────────────────────────────── - // %11 = quake.init_state %_, %_ : (!quake.veq<2>, T1) -> !quake.veq - // %12 = constant 2 : i64 - LogicalResult matchAndRewrite(quake::VeqSizeOp veqSize, - PatternRewriter &rewriter) const override { - Value veq = veqSize.getVeq(); - if (auto initState = veq.getDefiningOp()) - if (auto veqTy = - dyn_cast(initState.getTargets().getType())) - if (veqTy.hasSpecifiedSize()) { - std::size_t numQubits = veqTy.getSize(); - rewriter.replaceOpWithNewOp(veqSize, numQubits, - veqSize.getType()); - return success(); - } - return failure(); - } -}; -} // namespace - void quake::VeqSizeOp::getCanonicalizationPatterns(RewritePatternSet &patterns, MLIRContext *context) { patterns.add( @@ -852,22 +507,6 @@ void quake::VeqSizeOp::getCanonicalizationPatterns(RewritePatternSet &patterns, // WrapOp //===----------------------------------------------------------------------===// -namespace { -// If there is no operation that modifies the wire after it gets unwrapped and -// before it is wrapped, then the wrap operation is a nop and can be -// eliminated. -struct KillDeadWrapPattern : public OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - LogicalResult matchAndRewrite(quake::WrapOp wrap, - PatternRewriter &rewriter) const override { - if (auto unwrap = wrap.getWireValue().getDefiningOp()) - rewriter.eraseOp(wrap); - return success(); - } -}; -} // namespace - void quake::WrapOp::getCanonicalizationPatterns(RewritePatternSet &patterns, MLIRContext *context) { patterns.add(context); @@ -1040,53 +679,6 @@ void quake::RxOp::getOperatorMatrix(Matrix &matrix) { -1i * std::sin(theta / 2.), std::cos(theta / 2.)}); } -namespace { -template -struct MergeRotationPattern : public OpRewritePattern { - using Base = OpRewritePattern; - using Base::Base; - - LogicalResult matchAndRewrite(OP rotate, - PatternRewriter &rewriter) const override { - auto wireTy = quake::WireType::get(rewriter.getContext()); - if (rotate.getTarget(0).getType() != wireTy || - !rotate.getControls().empty()) - return failure(); - assert(!rotate.getNegatedQubitControls()); - auto input = rotate.getTarget(0).template getDefiningOp(); - if (!input || !input.getControls().empty()) - return failure(); - assert(!input.getNegatedQubitControls()); - - // At this point, we have - // %input = quake.rotate %angle1, %wire - // %rotate = quake.rotate %angle2, %input - // Replace those ops with - // %new = quake.rotate (%angle1 + %angle2), %wire - auto loc = rotate.getLoc(); - auto angle1 = input.getParameter(0); - auto angle2 = rotate.getParameter(0); - if (angle1.getType() != angle2.getType()) - return failure(); - auto adjAttr = rotate.getIsAdjAttr(); - auto newAngle = [&]() -> Value { - if (input.isAdj() == rotate.isAdj()) - return rewriter.create(loc, angle1, angle2); - // One is adjoint, so it should be subtracted from the other. - if (input.isAdj()) - return rewriter.create(loc, angle2, angle1); - adjAttr = input.getIsAdjAttr(); - return rewriter.create(loc, angle1, angle2); - }(); - rewriter.replaceOpWithNewOp(rotate, rotate.getResultTypes(), adjAttr, - ValueRange{newAngle}, ValueRange{}, - ValueRange{input.getTarget(0)}, - rotate.getNegatedQubitControlsAttr()); - return success(); - } -}; -} // namespace - void quake::RxOp::getCanonicalizationPatterns(RewritePatternSet &patterns, MLIRContext *context) { patterns.add>(context); From 912816f03413495f49d4f6fd969b507efee8c579 Mon Sep 17 00:00:00 2001 From: Eric Schweitz Date: Thu, 12 Dec 2024 22:19:55 -0800 Subject: [PATCH 34/44] [test] Fix targettests for braket backend. (#2474) Signed-off-by: Eric Schweitz --- targettests/execution/angled_gate.cpp | 2 +- targettests/execution/bug_qubit.cpp | 2 +- targettests/execution/callable_kernel_arg.cpp | 2 +- targettests/execution/cudaq_observe.cpp | 2 +- .../execution/custom_operation_adj.cpp | 2 +- .../execution/custom_operation_basic.cpp | 2 +- targettests/execution/graph_coloring-1.cpp | 2 +- targettests/execution/graph_coloring.cpp | 2 +- targettests/execution/if_jit.cpp | 2 +- targettests/execution/int8_t.cpp | 2 +- targettests/execution/int8_t_free_func.cpp | 2 +- targettests/execution/load_value.cpp | 2 +- targettests/execution/state_preparation.cpp | 2 +- .../execution/state_preparation_vector.cpp | 37 +++---------------- .../state_preparation_vector_sizes.cpp | 2 +- targettests/execution/swap_gate.cpp | 2 +- targettests/execution/variable_size_qreg.cpp | 2 +- 17 files changed, 22 insertions(+), 47 deletions(-) diff --git a/targettests/execution/angled_gate.cpp b/targettests/execution/angled_gate.cpp index d3ac5c7e92b..90d97d04e37 100644 --- a/targettests/execution/angled_gate.cpp +++ b/targettests/execution/angled_gate.cpp @@ -6,7 +6,7 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi #include diff --git a/targettests/execution/bug_qubit.cpp b/targettests/execution/bug_qubit.cpp index d33409b9c81..c987e0025c8 100644 --- a/targettests/execution/bug_qubit.cpp +++ b/targettests/execution/bug_qubit.cpp @@ -15,7 +15,7 @@ // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t // RUN: cudaq-quake %cpp_std %s | cudaq-opt --promote-qubit-allocation | FileCheck --check-prefixes=MLIR %s diff --git a/targettests/execution/callable_kernel_arg.cpp b/targettests/execution/callable_kernel_arg.cpp index 7ffa47ebc98..7e142f7f989 100644 --- a/targettests/execution/callable_kernel_arg.cpp +++ b/targettests/execution/callable_kernel_arg.cpp @@ -13,7 +13,7 @@ // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t // clang-format on diff --git a/targettests/execution/cudaq_observe.cpp b/targettests/execution/cudaq_observe.cpp index 4db6b8c644a..24642b5c6d8 100644 --- a/targettests/execution/cudaq_observe.cpp +++ b/targettests/execution/cudaq_observe.cpp @@ -15,7 +15,7 @@ // RUN: nvq++ --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ --target braket --emulate %s -o %t && %t | FileCheck %s; fi // clang-format on #include diff --git a/targettests/execution/custom_operation_adj.cpp b/targettests/execution/custom_operation_adj.cpp index 16dfefc2182..73bf181e86a 100644 --- a/targettests/execution/custom_operation_adj.cpp +++ b/targettests/execution/custom_operation_adj.cpp @@ -14,7 +14,7 @@ // RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // clang-format on #include diff --git a/targettests/execution/custom_operation_basic.cpp b/targettests/execution/custom_operation_basic.cpp index 9ba007c8b79..a660fa6985a 100644 --- a/targettests/execution/custom_operation_basic.cpp +++ b/targettests/execution/custom_operation_basic.cpp @@ -14,7 +14,7 @@ // RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // clang-format on #include diff --git a/targettests/execution/graph_coloring-1.cpp b/targettests/execution/graph_coloring-1.cpp index 3257c9a9d3f..34db685e329 100644 --- a/targettests/execution/graph_coloring-1.cpp +++ b/targettests/execution/graph_coloring-1.cpp @@ -10,7 +10,7 @@ // clang-format off // RUN: nvq++ %s -o %t --target infleqtion --emulate && %t | FileCheck %s // RUN: nvq++ %s -o %t --target quantinuum --emulate && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ %s -o %t --target braket --emulate && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %s -o %t --target braket --emulate && %t | FileCheck %s; fi // clang-format on #include diff --git a/targettests/execution/graph_coloring.cpp b/targettests/execution/graph_coloring.cpp index fd11d40f425..ae40f54f3ff 100644 --- a/targettests/execution/graph_coloring.cpp +++ b/targettests/execution/graph_coloring.cpp @@ -10,7 +10,7 @@ // clang-format off // RUN: nvq++ %s -o %t --target infleqtion --emulate && %t | FileCheck %s // RUN: nvq++ %s -o %t --target quantinuum --emulate && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ %s -o %t --target braket --emulate && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %s -o %t --target braket --emulate && %t | FileCheck %s; fi // clang-format on #include diff --git a/targettests/execution/if_jit.cpp b/targettests/execution/if_jit.cpp index ee820f3cdbd..464d35c8d7a 100644 --- a/targettests/execution/if_jit.cpp +++ b/targettests/execution/if_jit.cpp @@ -15,7 +15,7 @@ // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t // clang-format on diff --git a/targettests/execution/int8_t.cpp b/targettests/execution/int8_t.cpp index 2ff858d5ebf..90303b539a0 100644 --- a/targettests/execution/int8_t.cpp +++ b/targettests/execution/int8_t.cpp @@ -13,7 +13,7 @@ // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t // clang-format on diff --git a/targettests/execution/int8_t_free_func.cpp b/targettests/execution/int8_t_free_func.cpp index 5df3f812498..d6c12515ac2 100644 --- a/targettests/execution/int8_t_free_func.cpp +++ b/targettests/execution/int8_t_free_func.cpp @@ -13,7 +13,7 @@ // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t // clang-format on diff --git a/targettests/execution/load_value.cpp b/targettests/execution/load_value.cpp index f4296931d4c..cd31d04b62e 100644 --- a/targettests/execution/load_value.cpp +++ b/targettests/execution/load_value.cpp @@ -14,7 +14,7 @@ // RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t // clang-format on diff --git a/targettests/execution/state_preparation.cpp b/targettests/execution/state_preparation.cpp index c1e74889f12..adb4e9c0026 100644 --- a/targettests/execution/state_preparation.cpp +++ b/targettests/execution/state_preparation.cpp @@ -17,7 +17,7 @@ // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi #include #include diff --git a/targettests/execution/state_preparation_vector.cpp b/targettests/execution/state_preparation_vector.cpp index d7e896f2a2e..138ac1ae505 100644 --- a/targettests/execution/state_preparation_vector.cpp +++ b/targettests/execution/state_preparation_vector.cpp @@ -11,7 +11,7 @@ // RUN: nvq++ %cpp_std %s -o %t && %t | FileCheck %s // Quantum emulators -// RUN: nvq++ %cpp_std -target braket -emulate %s -o %t && %t | FileCheck %s +// RUN: if %braket_avail; then nvq++ %cpp_std -target braket -emulate %s -o %t && %t | FileCheck %s ; fi // RUN: nvq++ %cpp_std -target quantinuum -emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std -target ionq -emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std -target oqc -emulate %s -o %t && %t | FileCheck %s @@ -35,17 +35,10 @@ __qpu__ void test_complex_constant_array() { cudaq::qvector v(std::vector({M_SQRT1_2, M_SQRT1_2, 0., 0.})); } -#ifdef CUDAQ_SIMULATION_SCALAR_FP32 __qpu__ void test_complex_constant_array_floating_point() { cudaq::qvector v( - std::vector>({M_SQRT1_2, M_SQRT1_2, 0., 0.})); + std::vector>({M_SQRT1_2, M_SQRT1_2, 0., 0.})); } -#else -__qpu__ void test_complex_constant_array_floating_point() { - cudaq::qvector v( - std::vector>({M_SQRT1_2, M_SQRT1_2, 0., 0.})); -} -#endif __qpu__ void test_complex_constant_array2() { cudaq::qvector v1( @@ -63,45 +56,27 @@ __qpu__ void test_complex_array_param(std::vector inState) { cudaq::qvector q1 = inState; } -#ifdef CUDAQ_SIMULATION_SCALAR_FP32 -__qpu__ void test_complex_array_param_floating_point( - std::vector> inState) { - cudaq::qvector q1 = inState; -} -#else __qpu__ void test_complex_array_param_floating_point( - std::vector> inState) { + std::vector> inState) { cudaq::qvector q1 = inState; } -#endif __qpu__ void test_real_constant_array() { cudaq::qvector v({M_SQRT1_2, M_SQRT1_2, 0., 0.}); } -#ifdef CUDAQ_SIMULATION_SCALAR_FP32 -__qpu__ void test_real_constant_array_floating_point() { - cudaq::qvector v(std::vector({M_SQRT1_2, M_SQRT1_2, 0., 0.})); -} -#else __qpu__ void test_real_constant_array_floating_point() { - cudaq::qvector v(std::vector({M_SQRT1_2, M_SQRT1_2, 0., 0.})); + cudaq::qvector v(std::vector({M_SQRT1_2, M_SQRT1_2, 0., 0.})); } -#endif __qpu__ void test_real_array_param(std::vector inState) { cudaq::qvector q1 = inState; } -#ifdef CUDAQ_SIMULATION_SCALAR_FP32 -__qpu__ void test_real_array_param_floating_point(std::vector inState) { - cudaq::qvector q1 = inState; -} -#else -__qpu__ void test_real_array_param_floating_point(std::vector inState) { +__qpu__ void +test_real_array_param_floating_point(std::vector inState) { cudaq::qvector q1 = inState; } -#endif void printCounts(cudaq::sample_result &result) { std::vector values{}; diff --git a/targettests/execution/state_preparation_vector_sizes.cpp b/targettests/execution/state_preparation_vector_sizes.cpp index 7fbc7214159..54ff3276c54 100644 --- a/targettests/execution/state_preparation_vector_sizes.cpp +++ b/targettests/execution/state_preparation_vector_sizes.cpp @@ -16,7 +16,7 @@ // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target iqm --iqm-machine Apollo --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi #include #include diff --git a/targettests/execution/swap_gate.cpp b/targettests/execution/swap_gate.cpp index 4cbab8facf3..615dbcd364a 100644 --- a/targettests/execution/swap_gate.cpp +++ b/targettests/execution/swap_gate.cpp @@ -13,7 +13,7 @@ // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t && %t | FileCheck %s #include "cudaq.h" diff --git a/targettests/execution/variable_size_qreg.cpp b/targettests/execution/variable_size_qreg.cpp index dfabe164987..538da46b826 100644 --- a/targettests/execution/variable_size_qreg.cpp +++ b/targettests/execution/variable_size_qreg.cpp @@ -13,7 +13,7 @@ // RUN: nvq++ %cpp_std --target iqm --iqm-machine Adonis --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target oqc --emulate %s -o %t && %t | FileCheck %s // RUN: nvq++ %cpp_std --target quantinuum --emulate %s -o %t && %t | FileCheck %s -// RUN: if $braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi +// RUN: if %braket_avail; then nvq++ %cpp_std --target braket --emulate %s -o %t && %t | FileCheck %s; fi // RUN: nvq++ -std=c++17 --enable-mlir %s -o %t // clang-format on From fa63aa2c5a36355974e1f381b7acf59c3bfc7e97 Mon Sep 17 00:00:00 2001 From: Eric Schweitz Date: Thu, 12 Dec 2024 23:50:24 -0800 Subject: [PATCH 35/44] Explicit registration of codegen dialect. (#2468) * Explicit registration of codegen dialect. Modify the cudaq-opt tool so that it explicit registers the codegen dialect. This change enables easier experimentation and has no effect on the existing compiler flow. Signed-off-by: Eric Schweitz * Update comment. Signed-off-by: Eric Schweitz --------- Signed-off-by: Eric Schweitz --- {lib => include/cudaq}/Optimizer/CodeGen/CodeGenDialect.h | 0 include/cudaq/Optimizer/InitAllDialects.h | 2 +- lib/Optimizer/CodeGen/CodeGenDialect.cpp | 2 +- lib/Optimizer/CodeGen/CodeGenTypes.cpp | 2 +- lib/Optimizer/CodeGen/ConvertCCToLLVM.cpp | 2 +- lib/Optimizer/CodeGen/PassDetails.h | 2 +- tools/cudaq-opt/cudaq-opt.cpp | 2 ++ 7 files changed, 7 insertions(+), 5 deletions(-) rename {lib => include/cudaq}/Optimizer/CodeGen/CodeGenDialect.h (100%) diff --git a/lib/Optimizer/CodeGen/CodeGenDialect.h b/include/cudaq/Optimizer/CodeGen/CodeGenDialect.h similarity index 100% rename from lib/Optimizer/CodeGen/CodeGenDialect.h rename to include/cudaq/Optimizer/CodeGen/CodeGenDialect.h diff --git a/include/cudaq/Optimizer/InitAllDialects.h b/include/cudaq/Optimizer/InitAllDialects.h index 0748b5866a9..abe474fe928 100644 --- a/include/cudaq/Optimizer/InitAllDialects.h +++ b/include/cudaq/Optimizer/InitAllDialects.h @@ -33,7 +33,7 @@ inline void registerAllDialects(mlir::DialectRegistry ®istry) { mlir::math::MathDialect, mlir::memref::MemRefDialect, - // NVQ++ dialects + // CUDA-Q dialects cudaq::cc::CCDialect, quake::QuakeDialect >(); diff --git a/lib/Optimizer/CodeGen/CodeGenDialect.cpp b/lib/Optimizer/CodeGen/CodeGenDialect.cpp index 93204693a6b..76665aea3ce 100644 --- a/lib/Optimizer/CodeGen/CodeGenDialect.cpp +++ b/lib/Optimizer/CodeGen/CodeGenDialect.cpp @@ -6,7 +6,7 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -#include "CodeGenDialect.h" +#include "cudaq/Optimizer/CodeGen/CodeGenDialect.h" #include "CodeGenOps.h" #include "mlir/IR/DialectImplementation.h" diff --git a/lib/Optimizer/CodeGen/CodeGenTypes.cpp b/lib/Optimizer/CodeGen/CodeGenTypes.cpp index fe2ab430582..bc871513bf7 100644 --- a/lib/Optimizer/CodeGen/CodeGenTypes.cpp +++ b/lib/Optimizer/CodeGen/CodeGenTypes.cpp @@ -7,7 +7,7 @@ ******************************************************************************/ #include "CodeGenTypes.h" -#include "CodeGenDialect.h" +#include "cudaq/Optimizer/CodeGen/CodeGenDialect.h" #include "llvm/ADT/TypeSwitch.h" #include "mlir/IR/Builders.h" #include "mlir/IR/DialectImplementation.h" diff --git a/lib/Optimizer/CodeGen/ConvertCCToLLVM.cpp b/lib/Optimizer/CodeGen/ConvertCCToLLVM.cpp index 7ad2618a4f3..1b8ed8264bc 100644 --- a/lib/Optimizer/CodeGen/ConvertCCToLLVM.cpp +++ b/lib/Optimizer/CodeGen/ConvertCCToLLVM.cpp @@ -6,9 +6,9 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -#include "CodeGenDialect.h" #include "cudaq/Optimizer/Builder/Intrinsics.h" #include "cudaq/Optimizer/CodeGen/CCToLLVM.h" +#include "cudaq/Optimizer/CodeGen/CodeGenDialect.h" #include "cudaq/Optimizer/CodeGen/Passes.h" #include "cudaq/Optimizer/Dialect/CC/CCOps.h" #include "cudaq/Optimizer/Dialect/CC/CCTypes.h" diff --git a/lib/Optimizer/CodeGen/PassDetails.h b/lib/Optimizer/CodeGen/PassDetails.h index 719c03b3911..7cfe50b2d40 100644 --- a/lib/Optimizer/CodeGen/PassDetails.h +++ b/lib/Optimizer/CodeGen/PassDetails.h @@ -8,7 +8,7 @@ #pragma once -#include "CodeGenDialect.h" +#include "cudaq/Optimizer/CodeGen/CodeGenDialect.h" #include "cudaq/Optimizer/Dialect/CC/CCDialect.h" #include "cudaq/Optimizer/Dialect/Quake/QuakeDialect.h" #include "mlir/Dialect/Func/IR/FuncOps.h" diff --git a/tools/cudaq-opt/cudaq-opt.cpp b/tools/cudaq-opt/cudaq-opt.cpp index 3fb349cf2f4..0ebd2a1146d 100644 --- a/tools/cudaq-opt/cudaq-opt.cpp +++ b/tools/cudaq-opt/cudaq-opt.cpp @@ -6,6 +6,7 @@ * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ +#include "cudaq/Optimizer/CodeGen/CodeGenDialect.h" #include "cudaq/Optimizer/CodeGen/Passes.h" #include "cudaq/Optimizer/Dialect/CC/CCDialect.h" #include "cudaq/Optimizer/Dialect/Common/InlinerInterface.h" @@ -75,6 +76,7 @@ int main(int argc, char **argv) { mlir::DialectRegistry registry; cudaq::registerAllDialects(registry); + registry.insert(); registerInlinerExtension(registry); return mlir::asMainReturnCode( mlir::MlirOptMain(argc, argv, "nvq++ optimizer\n", registry)); From f0fc5f934d0f364fef59c0740235e829d04466ac Mon Sep 17 00:00:00 2001 From: Eric Schweitz Date: Fri, 13 Dec 2024 11:02:05 -0800 Subject: [PATCH 36/44] Add a yapf style config file. (#2477) This makes the default "google" style and will help prevent people from accidentally using the yapf default style. Fix #2428 Signed-off-by: Eric Schweitz --- .style.yapf | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .style.yapf diff --git a/.style.yapf b/.style.yapf new file mode 100644 index 00000000000..0e9640c29a2 --- /dev/null +++ b/.style.yapf @@ -0,0 +1,2 @@ +[style] +based_on_style = google From de6699d7c480b96bad35b074e22924d09324ebee Mon Sep 17 00:00:00 2001 From: Thien Nguyen <58006629+1tnguyen@users.noreply.github.com> Date: Sat, 14 Dec 2024 07:38:19 +1100 Subject: [PATCH 37/44] Always clear `threadToQpuId` when we clear `platformQPUs` (#2478) * Always clear threadToQpuId when we clear platformQPUs Signed-off-by: Thien Nguyen * Clear threadToQpuId for DefaultQuantumPlatform for consistency Signed-off-by: Thien Nguyen --------- Signed-off-by: Thien Nguyen --- runtime/cudaq/platform/default/DefaultQuantumPlatform.cpp | 2 ++ runtime/cudaq/platform/mqpu/MultiQPUPlatform.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/runtime/cudaq/platform/default/DefaultQuantumPlatform.cpp b/runtime/cudaq/platform/default/DefaultQuantumPlatform.cpp index df8a89e6f45..b24637c2ceb 100644 --- a/runtime/cudaq/platform/default/DefaultQuantumPlatform.cpp +++ b/runtime/cudaq/platform/default/DefaultQuantumPlatform.cpp @@ -82,6 +82,7 @@ class DefaultQuantumPlatform : public cudaq::quantum_platform { /// specified by that variable. void setTargetBackend(const std::string &backend) override { platformQPUs.clear(); + threadToQpuId.clear(); platformQPUs.emplace_back(std::make_unique()); cudaq::info("Backend string is {}", backend); @@ -121,6 +122,7 @@ class DefaultQuantumPlatform : public cudaq::quantum_platform { auto qpuName = config.BackendConfig->PlatformQpu; cudaq::info("Default platform QPU subtype name: {}", qpuName); platformQPUs.clear(); + threadToQpuId.clear(); platformQPUs.emplace_back(cudaq::registry::get(qpuName)); if (platformQPUs.front() == nullptr) throw std::runtime_error( diff --git a/runtime/cudaq/platform/mqpu/MultiQPUPlatform.cpp b/runtime/cudaq/platform/mqpu/MultiQPUPlatform.cpp index bdde97a464f..198a214c162 100644 --- a/runtime/cudaq/platform/mqpu/MultiQPUPlatform.cpp +++ b/runtime/cudaq/platform/mqpu/MultiQPUPlatform.cpp @@ -31,6 +31,7 @@ class MultiQPUQuantumPlatform : public cudaq::quantum_platform { // Make sure that we clean up the client QPUs first before cleaning up the // remote servers. platformQPUs.clear(); + threadToQpuId.clear(); platformNumQPUs = 0; m_remoteServers.clear(); } @@ -154,6 +155,7 @@ class MultiQPUQuantumPlatform : public cudaq::quantum_platform { qpuSubType)); if (qpuSubType == "NvcfSimulatorQPU") { platformQPUs.clear(); + threadToQpuId.clear(); auto simName = getOpt(description, "backend"); if (simName.empty()) simName = "custatevec-fp32"; @@ -199,6 +201,7 @@ class MultiQPUQuantumPlatform : public cudaq::quantum_platform { } else if (qpuSubType == "orca") { auto urls = cudaq::split(getOpt(description, "url"), ','); platformQPUs.clear(); + threadToQpuId.clear(); for (std::size_t qId = 0; qId < urls.size(); ++qId) { // Populate the information and add the QPUs platformQPUs.emplace_back(cudaq::registry::get("orca")); @@ -244,6 +247,7 @@ class MultiQPUQuantumPlatform : public cudaq::quantum_platform { "receiving {}, expecting {}.", sims.size(), urls.size())); platformQPUs.clear(); + threadToQpuId.clear(); for (std::size_t qId = 0; qId < urls.size(); ++qId) { const auto simName = sims.size() == 1 ? sims.front() : sims[qId]; // Populate the information and add the QPUs From ec5f15eac687e2873e3fb0bf2869d4244f98f60f Mon Sep 17 00:00:00 2001 From: Eric Schweitz Date: Fri, 13 Dec 2024 14:09:37 -0800 Subject: [PATCH 38/44] Change the constant used for dynamic sized quake.veq type. (#2476) * Change the constant used for dynamic sized quake.veq type. Signed-off-by: Eric Schweitz * Fix a bug in the implementation of constantSize(). Workaround a bad test. This test was never testing the c_if calls apparently. Signed-off-by: Eric Schweitz * clang-format Signed-off-by: Eric Schweitz --------- Signed-off-by: Eric Schweitz --- include/cudaq/Optimizer/Dialect/Quake/QuakeTypes.td | 7 +++++-- lib/Optimizer/Dialect/Quake/QuakeTypes.cpp | 4 ++-- python/cudaq/kernel/ast_bridge.py | 2 +- python/cudaq/kernel/kernel_builder.py | 8 ++++---- python/cudaq/kernel/quake_value.py | 2 +- python/runtime/mlir/py_register_dialects.cpp | 3 ++- runtime/cudaq/builder/QuakeValue.cpp | 3 ++- unittests/Optimizer/QuakeSynthTester.cpp | 5 ++++- 8 files changed, 21 insertions(+), 13 deletions(-) diff --git a/include/cudaq/Optimizer/Dialect/Quake/QuakeTypes.td b/include/cudaq/Optimizer/Dialect/Quake/QuakeTypes.td index 7dd31469fdb..0aac6fa46c1 100644 --- a/include/cudaq/Optimizer/Dialect/Quake/QuakeTypes.td +++ b/include/cudaq/Optimizer/Dialect/Quake/QuakeTypes.td @@ -153,9 +153,12 @@ def VeqType : QuakeType<"Veq", "veq"> { let hasCustomAssemblyFormat = 1; let extraClassDeclaration = [{ - bool hasSpecifiedSize() const { return getSize(); } + static constexpr std::size_t kDynamicSize = + std::numeric_limits::max(); + + bool hasSpecifiedSize() const { return getSize() != kDynamicSize; } static VeqType getUnsized(mlir::MLIRContext *ctx) { - return VeqType::get(ctx, 0); + return VeqType::get(ctx, kDynamicSize); } }]; } diff --git a/lib/Optimizer/Dialect/Quake/QuakeTypes.cpp b/lib/Optimizer/Dialect/Quake/QuakeTypes.cpp index b536a59710a..d124e32c3c3 100644 --- a/lib/Optimizer/Dialect/Quake/QuakeTypes.cpp +++ b/lib/Optimizer/Dialect/Quake/QuakeTypes.cpp @@ -39,9 +39,9 @@ void quake::VeqType::print(AsmPrinter &os) const { Type quake::VeqType::parse(AsmParser &parser) { if (parser.parseLess()) return {}; - std::size_t size = 0; + std::size_t size = kDynamicSize; if (succeeded(parser.parseOptionalQuestion())) - size = 0; + size = kDynamicSize; else if (parser.parseInteger(size)) return {}; if (parser.parseGreater()) diff --git a/python/cudaq/kernel/ast_bridge.py b/python/cudaq/kernel/ast_bridge.py index d76a802d725..7783822bd39 100644 --- a/python/cudaq/kernel/ast_bridge.py +++ b/python/cudaq/kernel/ast_bridge.py @@ -3168,7 +3168,7 @@ def bodyBuilder(iterVar): # we currently handle `veq` and `stdvec` types if quake.VeqType.isinstance(iterable.type): size = quake.VeqType.getSize(iterable.type) - if size: + if quake.VeqType.hasSpecifiedSize(iterable.type): totalSize = self.getConstantInt(size) else: totalSize = quake.VeqSizeOp(self.getIntegerType(64), diff --git a/python/cudaq/kernel/kernel_builder.py b/python/cudaq/kernel/kernel_builder.py index 192aad8929b..59c091cba24 100644 --- a/python/cudaq/kernel/kernel_builder.py +++ b/python/cudaq/kernel/kernel_builder.py @@ -675,8 +675,8 @@ def __applyControlOrAdjoint(self, target, isAdjoint, controls, *args): if (quake.VeqType.isinstance(inTy) and quake.VeqType.isinstance(argTy)): - if quake.VeqType.getSize( - inTy) and not quake.VeqType.getSize(argTy): + if quake.VeqType.hasSpecifiedSize( + inTy) and not quake.VeqType.hasSpecifiedSize(argTy): value = quake.RelaxSizeOp(argTy, value).result mlirValues.append(value) @@ -1029,8 +1029,8 @@ def reset(self, target): return # target is a VeqType - size = quake.VeqType.getSize(target.mlirValue.type) - if size: + if quake.VeqType.hasSpecifiedSize(target.mlirValue.type): + size = quake.VeqType.getSize(target.mlirValue.type) for i in range(size): extracted = quake.ExtractRefOp(quake.RefType.get(self.ctx), target.mlirValue, i).result diff --git a/python/cudaq/kernel/quake_value.py b/python/cudaq/kernel/quake_value.py index 3c55a9170cd..41689cbfada 100644 --- a/python/cudaq/kernel/quake_value.py +++ b/python/cudaq/kernel/quake_value.py @@ -67,7 +67,7 @@ def size(self): if quake.VeqType.isinstance(type): size = quake.VeqType.getSize(type) - if size: + if quake.VeqType.hasSpecifiedSize(type): return size return QuakeValue( quake.VeqSizeOp(self.intType, self.mlirValue).result, diff --git a/python/runtime/mlir/py_register_dialects.cpp b/python/runtime/mlir/py_register_dialects.cpp index 9c0c4f2985e..cda4f2a30ad 100644 --- a/python/runtime/mlir/py_register_dialects.cpp +++ b/python/runtime/mlir/py_register_dialects.cpp @@ -74,7 +74,8 @@ void registerQuakeDialectAndTypes(py::module &m) { [](py::object cls, MlirContext ctx, std::size_t size) { return wrap(quake::VeqType::get(unwrap(ctx), size)); }, - py::arg("cls"), py::arg("context"), py::arg("size") = 0) + py::arg("cls"), py::arg("context"), + py::arg("size") = std::numeric_limits::max()) .def_staticmethod( "hasSpecifiedSize", [](MlirType type) { diff --git a/runtime/cudaq/builder/QuakeValue.cpp b/runtime/cudaq/builder/QuakeValue.cpp index ea2261651ec..9a714d1ac85 100644 --- a/runtime/cudaq/builder/QuakeValue.cpp +++ b/runtime/cudaq/builder/QuakeValue.cpp @@ -190,7 +190,8 @@ QuakeValue QuakeValue::size() { std::optional QuakeValue::constantSize() { if (auto qvecTy = dyn_cast(getValue().getType())) - return qvecTy.getSize(); + if (qvecTy.hasSpecifiedSize()) + return qvecTy.getSize(); return std::nullopt; } diff --git a/unittests/Optimizer/QuakeSynthTester.cpp b/unittests/Optimizer/QuakeSynthTester.cpp index 5c2e324cab2..ceb31841837 100644 --- a/unittests/Optimizer/QuakeSynthTester.cpp +++ b/unittests/Optimizer/QuakeSynthTester.cpp @@ -283,7 +283,10 @@ TEST(QuakeSynthTests, checkVectorOfInt) { kernel.h(aq); kernel.z(aq); kernel.h(q); - for (std::size_t i = 0; i < *q.constantSize(); ++i) { + // FIXME: This test never really tested the c_if in this loop. The call to + // constantSize just returned 0. + std::size_t unrollBy = q.constantSize().has_value() ? *q.constantSize() : 0; + for (std::size_t i = 0; i < unrollBy; ++i) { kernel.c_if(hiddenBits[i], [&]() { kernel.x(aq, q[i]); }); } kernel.h(q); From 2bcee444b38fb7f1474008e8c956ff5840fa8ab8 Mon Sep 17 00:00:00 2001 From: Eric Schweitz Date: Fri, 13 Dec 2024 16:52:52 -0800 Subject: [PATCH 39/44] [core] Move the QIR peephole patterns out of tablegen. (#2480) Moves all the peephole patterns to explicit patterns and out of tablegen. Fix #476. Signed-off-by: Eric Schweitz --- .../cudaq/Optimizer/CodeGen/CMakeLists.txt | 4 - include/cudaq/Optimizer/CodeGen/Peephole.h | 9 +- include/cudaq/Optimizer/CodeGen/Peephole.td | 177 ------------- lib/Optimizer/CodeGen/CMakeLists.txt | 1 - lib/Optimizer/CodeGen/ConvertToQIR.cpp | 2 + lib/Optimizer/CodeGen/ConvertToQIRProfile.cpp | 2 + lib/Optimizer/CodeGen/PeepholePatterns.inc | 238 ++++++++++++++++++ lib/Optimizer/CodeGen/VerifyQIRProfile.cpp | 2 +- 8 files changed, 245 insertions(+), 190 deletions(-) delete mode 100644 include/cudaq/Optimizer/CodeGen/Peephole.td create mode 100644 lib/Optimizer/CodeGen/PeepholePatterns.inc diff --git a/include/cudaq/Optimizer/CodeGen/CMakeLists.txt b/include/cudaq/Optimizer/CodeGen/CMakeLists.txt index 5c2c15f8d71..c0140aefa42 100644 --- a/include/cudaq/Optimizer/CodeGen/CMakeLists.txt +++ b/include/cudaq/Optimizer/CodeGen/CMakeLists.txt @@ -12,7 +12,3 @@ add_cudaq_dialect_doc(CodeGenDialect codegen) set(LLVM_TARGET_DEFINITIONS Passes.td) mlir_tablegen(Passes.h.inc -gen-pass-decls -name OptCodeGen) add_public_tablegen_target(OptCodeGenPassIncGen) - -set(LLVM_TARGET_DEFINITIONS Peephole.td) -mlir_tablegen(Peephole.inc -gen-rewriters) -add_public_tablegen_target(OptPeepholeIncGen) diff --git a/include/cudaq/Optimizer/CodeGen/Peephole.h b/include/cudaq/Optimizer/CodeGen/Peephole.h index 4fdca9bd02d..f5eae54c4bf 100644 --- a/include/cudaq/Optimizer/CodeGen/Peephole.h +++ b/include/cudaq/Optimizer/CodeGen/Peephole.h @@ -38,9 +38,8 @@ inline bool isIntToPtrOp(mlir::Value operand) { static constexpr char resultIndexName[] = "result.index"; inline mlir::Value createMeasureCall(mlir::PatternRewriter &builder, - mlir::Location loc, mlir::OpResult result, + mlir::Location loc, mlir::LLVM::CallOp op, mlir::ValueRange args) { - auto op = cast(result.getDefiningOp()); auto ptrTy = cudaq::opt::getResultType(builder.getContext()); if (auto intAttr = dyn_cast_or_null(op->getAttr(resultIndexName))) { @@ -57,7 +56,7 @@ inline mlir::Value createMeasureCall(mlir::PatternRewriter &builder, inline mlir::Value createReadResultCall(mlir::PatternRewriter &builder, mlir::Location loc, - mlir::OpResult result) { + mlir::Value result) { auto i1Ty = mlir::IntegerType::get(builder.getContext(), 1); return builder .create(loc, mlir::TypeRange{i1Ty}, @@ -65,7 +64,3 @@ inline mlir::Value createReadResultCall(mlir::PatternRewriter &builder, mlir::ArrayRef{result}) .getResult(); } - -namespace { -#include "cudaq/Optimizer/CodeGen/Peephole.inc" -} diff --git a/include/cudaq/Optimizer/CodeGen/Peephole.td b/include/cudaq/Optimizer/CodeGen/Peephole.td deleted file mode 100644 index 32f32b5d211..00000000000 --- a/include/cudaq/Optimizer/CodeGen/Peephole.td +++ /dev/null @@ -1,177 +0,0 @@ -/********************************************************** -*- tablegen -*- *** - * 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. * - ******************************************************************************/ - -#ifndef NVQPP_OPTIMIZER_CODEGEN_PEEPHOLE -#define NVQPP_OPTIMIZER_CODEGEN_PEEPHOLE - -include "cudaq/Optimizer/Dialect/Quake/QuakeOps.td" -include "mlir/Dialect/LLVMIR/LLVMOps.td" -include "mlir/IR/OpBase.td" -include "mlir/IR/PatternBase.td" - -//===----------------------------------------------------------------------===// - -def InvokeOnXWithOneControl : Constraint>; - -def CreateCallCnot : NativeCodeCall< - "[&]() -> std::size_t {" - " $_builder.create($_loc," - " mlir::TypeRange{}, cudaq::opt::QIRCnot, $0.drop_front(2));" - " return 0; }()">; - -// %1 = address_of @__quantum__qis__x__ctl -// %2 = call @invokewithControlBits %1, %ctrl, %targ -// ───────────────────────────────────────────────── -// %2 = call __quantum__qis__cnot %ctrl, %targ -def XCtrlOneTargetToCNot : Pat< - (LLVM_CallOp $callee, $args, $_, $_), (CreateCallCnot $args), - [(InvokeOnXWithOneControl $callee, $args)]>; - -//===----------------------------------------------------------------------===// - -def NeedsRenaming : Constraint>; - -def CreateAddressOf : NativeCodeCall< - "$_builder.create($_loc, $0.getType()," - " $1.getValue().str() + \"__body\")">; - -// %4 = address_of @__quantum__cis__* -// ──────────────────────────────────────── -// %4 = address_of @__quantum__cis__*__body -def AddrOfCisToBase : Pat< - (LLVM_AddressOfOp:$addr $global), (CreateAddressOf $addr, $global), - [(NeedsRenaming $global)]>; - -//===----------------------------------------------------------------------===// - -// Apply special rule for `mz`. See below. -def FuncNotMeasure : Constraint>; - -def CreateCallOp : NativeCodeCall< - "[&]() -> std::size_t {" - " $_builder.create($_loc, mlir::TypeRange{}," - " mlir::FlatSymbolRefAttr::get($_builder.getContext()," - " $0.getValue().str() + \"__body\"), $1, $2, $3);" - " return 0; }()">; - -// %4 = call @__quantum__cis__* -// ────────────────────────────────── -// %4 = call @__quantum__cis__*__body -def CalleeConv : Pat< - (LLVM_CallOp $callee, $args, $fm, $bw), - (CreateCallOp $callee, $args, $fm, $bw), - [(NeedsRenaming $callee), (FuncNotMeasure:$callee)]>; - -//===----------------------------------------------------------------------===// - -def IsArrayGetElementPtrId : Constraint>; - -def EraseArrayGEPOp : NativeCodeCall< - "$_builder.create($_loc," - " cudaq::opt::getQubitType($_builder.getContext()))">; - -def EraseDeadArrayGEP : Pat< - (LLVM_CallOp:$call $callee, $_, $_, $_), (EraseArrayGEPOp), - [(IsArrayGetElementPtrId $callee), (HasNoUseOf:$call)]>; - -//===----------------------------------------------------------------------===// - -def IsaAllocateCall : Constraint>; - -def EraseArrayAllocateOp : NativeCodeCall< - "$_builder.create($_loc," - " cudaq::opt::getArrayType($_builder.getContext()))">; - -// Replace the call with a dead op to DCE. -// -// %0 = call @allocate ... : ... -> T* -// ─────────────────────────────────── -// %0 = undef : T* -def EraseArrayAlloc : Pat< - (LLVM_CallOp $callee, $_, $_, $_), (EraseArrayAllocateOp), - [(IsaAllocateCall $callee)]>; - -//===----------------------------------------------------------------------===// - -def IsaReleaseCall : Constraint>; - -def EraseArrayReleaseOp : NativeCodeCall<"static_cast(0)">; - -// Remove the release calls. This removes both array allocations as well as -// qubit singletons. -// -// call @release %5 : (!Qubit) -> () -// ───────────────────────────────── -def EraseArrayRelease : Pat< - (LLVM_CallOp $callee, $_, $_, $_), (EraseArrayReleaseOp), - [(IsaReleaseCall $callee)]>; - -//===----------------------------------------------------------------------===// - -def IsaMeasureCall : Constraint>; - -def IsaIntToPtrOperand : Constraint>; - -def CreateMeasureCall : NativeCodeCall< - "createMeasureCall($_builder, $_loc, $0, $1)">; - -// %result = call @__quantum__qis__mz(%qbit) : (!Qubit) -> i1 -// ────────────────────────────────────────────────────────────── -// call @__quantum__qis__mz_body(%qbit, %result) : (Q*, R*) -> () -def MeasureCallConv : Pat< - (LLVM_CallOp:$call $callee, $args, $_, $_), - (CreateMeasureCall $call, $args), - [(IsaMeasureCall:$callee), (IsaIntToPtrOperand $args)]>; - -//===----------------------------------------------------------------------===// - -def IsaMeasureToRegisterCall : Constraint>; - -// %result = call @__quantum__qis__mz__to__register(%qbit, i8) : (!Qubit) -> i1 -// ──────────────────────────────────────────────────────────────────────────── -// call @__quantum__qis__mz_body(%qbit, %result) : (Q*, R*) -> () -def MeasureToRegisterCallConv : Pat< - (LLVM_CallOp:$call $callee, $args, $_, $_), - (CreateMeasureCall $call, $args), - [(IsaMeasureToRegisterCall:$callee), (IsaIntToPtrOperand $args)]>; - -//===----------------------------------------------------------------------===// - -def HasI1PtrType : Constraint>; - -def HasResultType : Constraint>; - -def IsaIntAttr : Constraint()">>; - -def CreateReadResultCall : NativeCodeCall< - "createReadResultCall($_builder, $_loc, $0)">; - -// %1 = llvm.constant 1 -// %2 = llvm.inttoptr %1 : i64 -> Result* -// %3 = llvm.bitcast %2 : Result* -> i1* -// %4 = llvm.load %3 -// ───────────────────────────────────── -// %4 = call @read_result %2 -def LoadMeasureResult : Pat< - (LLVM_LoadOp:$load (LLVM_BitcastOp:$bitcast (LLVM_IntToPtrOp:$cast - (LLVM_ConstantOp $attr))), $_, $_, $_, $_, $_, $_), - (CreateReadResultCall $cast), - [(HasI1PtrType:$bitcast), (HasResultType:$cast), (IsaIntAttr:$attr)]>; - -#endif diff --git a/lib/Optimizer/CodeGen/CMakeLists.txt b/lib/Optimizer/CodeGen/CMakeLists.txt index 5c056e0e11d..3739855b317 100644 --- a/lib/Optimizer/CodeGen/CMakeLists.txt +++ b/lib/Optimizer/CodeGen/CMakeLists.txt @@ -37,7 +37,6 @@ add_cudaq_library(OptCodeGen CodeGenOpsIncGen CodeGenTypesIncGen OptCodeGenPassIncGen - OptPeepholeIncGen OptTransformsPassIncGen QuakeDialect diff --git a/lib/Optimizer/CodeGen/ConvertToQIR.cpp b/lib/Optimizer/CodeGen/ConvertToQIR.cpp index 738ee66ea1a..1eaba931b3a 100644 --- a/lib/Optimizer/CodeGen/ConvertToQIR.cpp +++ b/lib/Optimizer/CodeGen/ConvertToQIR.cpp @@ -45,6 +45,8 @@ namespace cudaq::opt { using namespace mlir; +#include "PeepholePatterns.inc" + /// Greedy pass to match subgraphs in the IR and replace them with codegen ops. /// This step makes converting a DAG of nodes in the conversion step simpler. static LogicalResult fuseSubgraphPatterns(MLIRContext *ctx, ModuleOp module) { diff --git a/lib/Optimizer/CodeGen/ConvertToQIRProfile.cpp b/lib/Optimizer/CodeGen/ConvertToQIRProfile.cpp index 58526dc6929..f3aad7c60cd 100644 --- a/lib/Optimizer/CodeGen/ConvertToQIRProfile.cpp +++ b/lib/Optimizer/CodeGen/ConvertToQIRProfile.cpp @@ -32,6 +32,8 @@ using namespace mlir; +#include "PeepholePatterns.inc" + /// For a call to `__quantum__rt__qubit_allocate_array`, get the number of /// qubits allocated. static std::size_t getNumQubits(LLVM::CallOp callOp) { diff --git a/lib/Optimizer/CodeGen/PeepholePatterns.inc b/lib/Optimizer/CodeGen/PeepholePatterns.inc new file mode 100644 index 00000000000..ad6cc64fe88 --- /dev/null +++ b/lib/Optimizer/CodeGen/PeepholePatterns.inc @@ -0,0 +1,238 @@ +/****************************************************************-*- C++ -*-**** + * 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. * + ******************************************************************************/ + +namespace { + +//===----------------------------------------------------------------------===// + +// %1 = address_of @__quantum__qis__x__ctl +// %2 = call @invokewithControlBits %1, %ctrl, %targ +// ───────────────────────────────────────────────── +// %2 = call __quantum__qis__cnot %ctrl, %targ +struct XCtrlOneTargetToCNot : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(LLVM::CallOp call, + PatternRewriter &rewriter) const override { + auto callee = call.getCallee(); + if (!callee) + return failure(); + auto args = call.getOperands(); + if (!callToInvokeWithXCtrlOneTarget(*callee, args)) + return failure(); + auto *ctx = rewriter.getContext(); + auto funcSymbol = FlatSymbolRefAttr::get(ctx, cudaq::opt::QIRCnot); + rewriter.replaceOpWithNewOp( + call, TypeRange{}, funcSymbol, args.drop_front(2), + call.getFastmathFlagsAttr(), call.getBranchWeightsAttr()); + return success(); + } +}; + +//===----------------------------------------------------------------------===// + +// %4 = address_of @__quantum__cis__* +// ──────────────────────────────────────── +// %4 = address_of @__quantum__cis__*__body +struct AddrOfCisToBase : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(LLVM::AddressOfOp addr, + PatternRewriter &rewriter) const override { + auto global = addr.getGlobalName(); + if (!needsToBeRenamed(global)) + return failure(); + rewriter.replaceOpWithNewOp(addr, addr.getType(), + global.str() + "__body"); + return success(); + } +}; + +//===----------------------------------------------------------------------===// + +// This rule does not apply to measurements. +// +// %4 = call @__quantum__cis__* +// ────────────────────────────────── +// %4 = call @__quantum__cis__*__body +struct CalleeConv : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(LLVM::CallOp call, + PatternRewriter &rewriter) const override { + auto callee = call.getCallee(); + if (!callee) + return failure(); + if (!needsToBeRenamed(*callee) || + callee->startswith(cudaq::opt::QIRMeasure)) + return failure(); + auto *ctx = rewriter.getContext(); + auto symbol = FlatSymbolRefAttr::get(ctx, callee->str() + "__body"); + rewriter.replaceOpWithNewOp( + call, TypeRange{}, symbol, call.getOperands(), + call.getFastmathFlagsAttr(), call.getBranchWeightsAttr()); + return success(); + } +}; + +//===----------------------------------------------------------------------===// + +// Manually erase dead calls to QIRArrayGetElementPtr1d. +struct EraseDeadArrayGEP : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(LLVM::CallOp call, + PatternRewriter &rewriter) const override { + auto callee = call.getCallee(); + if (!callee) + return failure(); + if (*callee != cudaq::opt::QIRArrayGetElementPtr1d) + return failure(); + if (!call->use_empty()) + return failure(); + rewriter.eraseOp(call); + return success(); + } +}; + +//===----------------------------------------------------------------------===// + +// Replace the call with a dead op to DCE. +// +// %0 = call @allocate ... : ... -> T* +// ─────────────────────────────────── +// %0 = undef : T* +struct EraseArrayAlloc : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(LLVM::CallOp call, + PatternRewriter &rewriter) const override { + auto callee = call.getCallee(); + if (!callee) + return failure(); + if (*callee != cudaq::opt::QIRArrayQubitAllocateArray) + return failure(); + auto *ctx = rewriter.getContext(); + rewriter.replaceOpWithNewOp(call, + cudaq::opt::getArrayType(ctx)); + return success(); + } +}; + +//===----------------------------------------------------------------------===// + +// Remove the release calls. This removes both array allocations as well as +// qubit singletons. +// +// call @release %5 : (!Qubit) -> () +// ───────────────────────────────── +// +struct EraseArrayRelease : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(LLVM::CallOp call, + PatternRewriter &rewriter) const override { + auto callee = call.getCallee(); + if (!callee) + return failure(); + if (*callee != cudaq::opt::QIRArrayQubitReleaseArray && + *callee != cudaq::opt::QIRArrayQubitReleaseQubit) + return failure(); + rewriter.eraseOp(call); + return success(); + } +}; + +//===----------------------------------------------------------------------===// + +// %result = call @__quantum__qis__mz(%qbit) : (!Qubit) -> i1 +// ────────────────────────────────────────────────────────────── +// call @__quantum__qis__mz_body(%qbit, %result) : (Q*, R*) -> () +struct MeasureCallConv : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(LLVM::CallOp call, + PatternRewriter &rewriter) const override { + auto callee = call.getCallee(); + if (!callee) + return failure(); + auto args = call.getOperands(); + if (*callee != cudaq::opt::QIRMeasure) + return failure(); + auto inttoptr = args[0].getDefiningOp(); + if (!inttoptr) + return failure(); + rewriter.replaceOp(call, + createMeasureCall(rewriter, call.getLoc(), call, args)); + return success(); + } +}; + +//===----------------------------------------------------------------------===// + +// %result = call @__quantum__qis__mz__to__register(%qbit, i8) : (!Qubit) -> i1 +// ──────────────────────────────────────────────────────────────────────────── +// call @__quantum__qis__mz_body(%qbit, %result) : (Q*, R*) -> () +struct MeasureToRegisterCallConv : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(LLVM::CallOp call, + PatternRewriter &rewriter) const override { + auto callee = call.getCallee(); + if (!callee) + return failure(); + auto args = call.getOperands(); + if (*callee != cudaq::opt::QIRMeasureToRegister) + return failure(); + auto inttoptr = args[0].getDefiningOp(); + if (!inttoptr) + return failure(); + rewriter.replaceOp(call, + createMeasureCall(rewriter, call.getLoc(), call, args)); + return success(); + } +}; + +//===----------------------------------------------------------------------===// + +// %1 = llvm.constant 1 +// %2 = llvm.inttoptr %1 : i64 -> Result* +// %3 = llvm.bitcast %2 : Result* -> i1* +// %4 = llvm.load %3 +// ───────────────────────────────────── +// %4 = call @read_result %2 +struct LoadMeasureResult : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(LLVM::LoadOp load, + PatternRewriter &rewriter) const override { + auto *ctx = rewriter.getContext(); + auto bitcast = load.getAddr().getDefiningOp(); + if (!bitcast) + return failure(); + auto inttoptr = bitcast.getArg().getDefiningOp(); + if (!inttoptr) + return failure(); + auto conint = inttoptr.getArg().getDefiningOp(); + if (!conint) + return failure(); + if (bitcast.getType() != + cudaq::opt::factory::getPointerType(IntegerType::get(ctx, 1))) + return failure(); + if (inttoptr.getType() != cudaq::opt::getResultType(ctx)) + return failure(); + if (!isa(conint.getValue())) + return failure(); + + rewriter.replaceOp(load, createReadResultCall(rewriter, load.getLoc(), + inttoptr.getResult())); + return success(); + } +}; + +} // namespace diff --git a/lib/Optimizer/CodeGen/VerifyQIRProfile.cpp b/lib/Optimizer/CodeGen/VerifyQIRProfile.cpp index 02ccc932fa8..6adbe833d21 100644 --- a/lib/Optimizer/CodeGen/VerifyQIRProfile.cpp +++ b/lib/Optimizer/CodeGen/VerifyQIRProfile.cpp @@ -9,7 +9,7 @@ #include "PassDetails.h" #include "cudaq/Optimizer/Builder/Intrinsics.h" #include "cudaq/Optimizer/CodeGen/Passes.h" -#include "cudaq/Optimizer/CodeGen/Peephole.h" +#include "cudaq/Optimizer/CodeGen/QIRFunctionNames.h" #include "cudaq/Optimizer/Dialect/Quake/QuakeOps.h" #include "cudaq/Todo.h" #include "nlohmann/json.hpp" From 519f9d42e46cc63154a02b2a53b9c20900c95a40 Mon Sep 17 00:00:00 2001 From: Thien Nguyen <58006629+1tnguyen@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:04:41 +1100 Subject: [PATCH 40/44] Extend the lifetime the constructed LD_LIBRARY_PATH stringref (#2482) Signed-off-by: Thien Nguyen --- runtime/cudaq/platform/mqpu/helpers/MQPUUtils.cpp | 4 +++- runtime/cudaq/platform/mqpu/helpers/MQPUUtils.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/runtime/cudaq/platform/mqpu/helpers/MQPUUtils.cpp b/runtime/cudaq/platform/mqpu/helpers/MQPUUtils.cpp index bbf32ddd227..fe84926d4e1 100644 --- a/runtime/cudaq/platform/mqpu/helpers/MQPUUtils.cpp +++ b/runtime/cudaq/platform/mqpu/helpers/MQPUUtils.cpp @@ -116,7 +116,9 @@ cudaq::AutoLaunchRestServerProcess::AutoLaunchRestServerProcess( if (!std::string(*env).starts_with("LD_LIBRARY_PATH=")) Env->push_back(*env); } - Env->push_back("LD_LIBRARY_PATH=" + dynLibs); + // Cache the string as a member var to keep the pointer alive. + m_ldLibPathEnv = "LD_LIBRARY_PATH=" + dynLibs; + Env->push_back(m_ldLibPathEnv); } constexpr std::size_t PORT_MAX_RETRIES = 10; diff --git a/runtime/cudaq/platform/mqpu/helpers/MQPUUtils.h b/runtime/cudaq/platform/mqpu/helpers/MQPUUtils.h index 974107fd4c8..f759923d7e5 100644 --- a/runtime/cudaq/platform/mqpu/helpers/MQPUUtils.h +++ b/runtime/cudaq/platform/mqpu/helpers/MQPUUtils.h @@ -25,6 +25,7 @@ struct AutoLaunchRestServerProcess { private: int m_pid; std::string m_url; + std::string m_ldLibPathEnv; }; // Helper to retrieve the number of GPU. From c9ecfe2e23edf125787373df26d7eecab433270c Mon Sep 17 00:00:00 2001 From: Bettina Heim Date: Tue, 17 Dec 2024 13:35:17 +0100 Subject: [PATCH 41/44] Fixing wheel validation in publishing (#2479) The wheel validation as written is not configured properly to run in our publishing pipeline. - The applications folder should not be copied. Notebooks are validated by a separate script, and currently only during QA for notebooks that explicitly require GPU targets. While it may be nice to also run notebook validation during publishing at some point, copying the applications folder won't achieve that. - Running target tests requires credentials, and doesn't require the GPU runner. Hence, also running target tests should be set up separately. - I currently configured the validation script to silently skip nvqc snippets if NVQC_API_KEY is not set. I did set this key during publishing to validate nvqc snippets. - A couple of other adjustments are needed, like installing matplotlib and fixing the torch download url. Signed-off-by: Bettina Heim --- .github/workflows/publishing.yml | 14 +++++-- scripts/validate_pycudaq.sh | 72 +++++++++++++++++++++++++------- 2 files changed, 67 insertions(+), 19 deletions(-) diff --git a/.github/workflows/publishing.yml b/.github/workflows/publishing.yml index cfdb5068b21..675a0f96da6 100644 --- a/.github/workflows/publishing.yml +++ b/.github/workflows/publishing.yml @@ -1090,6 +1090,11 @@ jobs: cuda_version: ['11.8', '12.4'] fail-fast: false + # Must have environment to access environment secreats + environment: + name: ghcr-deployment + url: ${{ vars.deployment_url }} + container: image: ubuntu:22.04 options: --user root @@ -1129,13 +1134,16 @@ jobs: rm -rf ${cudaq_metapackage} && readme=README.md # Setup files for validate_pycudaq.sh script + # Important: Notebooks are *not* validated by validate_pycudaq.sh. cp $GITHUB_WORKSPACE/scripts/validate_pycudaq.sh . cp -r $GITHUB_WORKSPACE/docs/sphinx/examples/python /tmp/examples/ - cp -r $GITHUB_WORKSPACE/docs/sphinx/applications/python /tmp/applications/ - cp -r $GITHUB_WORKSPACE/docs/sphinx/targets/python /tmp/targets/ cp -r $GITHUB_WORKSPACE/docs/sphinx/snippets/python /tmp/snippets/ cp -r $GITHUB_WORKSPACE/python/tests /tmp/tests/ cp $GITHUB_WORKSPACE/$readme /tmp/README.md + # Target tests should not be run here either, since that requires credentials but doesn't require a GPU runner. + + # The NVQC API key is needed to validate NVQC related snippets and examples. + export NVQC_API_KEY="${{ secrets.NVQC_PROD_SERVICE_KEY }}" # Run the script w/ -q to run a shortened test set +e # Allow script to keep going through errors (needed for skipped tests) @@ -1158,7 +1166,7 @@ jobs: create_release: name: CUDA-Q Release needs: [assets, cudaq_images, cudaq_installers, cudaq_wheels, cudaq_metapackages] - if: needs.assets.outputs.release_title && inputs.github_commit == '' && inputs.assets_from_run == '' && inputs.nvidia_mgpu_commit == '' + if: needs.assets.outputs.release_title && inputs.github_commit == '' && inputs.nvidia_mgpu_commit == '' runs-on: ubuntu-latest environment: diff --git a/scripts/validate_pycudaq.sh b/scripts/validate_pycudaq.sh index 63bf95d05f3..f6eb70b00fe 100644 --- a/scripts/validate_pycudaq.sh +++ b/scripts/validate_pycudaq.sh @@ -25,13 +25,16 @@ # COPY ${package_folder} ${package_folder} # COPY scripts/validate_pycudaq.sh validate_pycudaq.sh # COPY docs/sphinx/examples/python /tmp/examples/ -# COPY docs/sphinx/applications/python /tmp/applications/ -# COPY docs/sphinx/targets/python /tmp/targets/ # COPY docs/sphinx/snippets/python /tmp/snippets/ # COPY python/tests /tmp/tests/ # COPY python/README.md.in /tmp/README.md # RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates vim wget openssh-client +# Note: To run the target tests, make sure to set all necessary API keys: +# COPY docs/sphinx/targets/python /tmp/targets/ +# ENV NVQC_API_KEY=... +# ENV ... + __optind__=$OPTIND OPTIND=1 python_version=3.11 @@ -123,18 +126,6 @@ if [ ! $? -eq 0 ]; then status_sum=$((status_sum+1)) fi -# Run torch integrator tests. -# This is an optional integrator, which requires torch and torchdiffeq. -# Install torch separately to match the cuda version. -# Torch if installed as part of torchdiffeq's dependencies, may default to the latest cuda version. -python3 -m pip install torch --index-url https://download.pytorch.org/whl/cu$(echo ${cuda_version//.}) -python3 -m pip install torchdiffeq -python3 -m pytest -v "$root_folder/tests/operator/integrators" -if [ ! $? -eq 0 ]; then - echo -e "\e[01;31mPython tests failed.\e[0m" >&2 - status_sum=$((status_sum+1)) -fi - # If this is a quick test, we return here. if $quick_test; then if [ ! $status_sum -eq 0 ]; then @@ -169,19 +160,42 @@ for parallelTest in "$root_folder/tests/parallel"/*.py; do fi done +# Run torch integrator tests. +# This is an optional integrator, which requires torch and torchdiffeq. +# Install torch separately to match the cuda version. +# Torch if installed as part of torchdiffeq's dependencies, may default to the latest cuda version. +python3 -m pip install torch --index-url https://download.pytorch.org/whl/cu$(echo $cuda_version | cut -d '.' -f-2 | tr -d .) +python3 -m pip install torchdiffeq +python3 -m pytest -v "$root_folder/tests/operator/integrators" +if [ ! $? -eq 0 ]; then + echo -e "\e[01;31mPython tests failed.\e[0m" >&2 + status_sum=$((status_sum+1)) +fi + # Run snippets in docs -for ex in `find "$root_folder/snippets" -name '*.py'`; do +# Some snippets generate plots +python3 -m pip install --user matplotlib +for ex in `find "$root_folder/snippets" -name '*.py' -not -path '*/nvqc/*'`; do python3 "$ex" if [ ! $? -eq 0 ]; then echo -e "\e[01;31mFailed to execute $ex.\e[0m" >&2 status_sum=$((status_sum+1)) fi done +if [ -n "${NVQC_API_KEY}" ]; then + for ex in `find "$root_folder/snippets" -name '*.py' -path '*/nvqc/*'`; do + python3 "$ex" + if [ ! $? -eq 0 ]; then + echo -e "\e[01;31mFailed to execute $ex.\e[0m" >&2 + status_sum=$((status_sum+1)) + fi + done +fi # Run examples # Some examples generate plots python3 -m pip install --user matplotlib -for ex in `find "$root_folder/examples" "$root_folder/applications" "$root_folder/targets" -name '*.py'`; do +for ex in `find "$root_folder/examples" -name '*.py'`; do skip_example=false explicit_targets=`cat $ex | grep -Po '^\s*cudaq.set_target\("\K.*(?=")'` for t in $explicit_targets; do @@ -190,6 +204,9 @@ for ex in `find "$root_folder/examples" "$root_folder/applications" "$root_folde # to submit a (paid) job to Amazon Braket (includes QuEra). echo -e "\e[01;31mWarning: Explicitly set target braket or quera in $ex; skipping validation due to paid submission.\e[0m" >&2 skip_example=true + elif [ "$t" == "nvqc" ] && [ -z "${NVQC_API_KEY}" ]; then + echo -e "\e[01;31mWarning: Explicitly set target nvqc in $ex; skipping validation due to missing API key.\e[0m" >&2 + skip_example=true fi done if ! $skip_example; then @@ -201,6 +218,29 @@ for ex in `find "$root_folder/examples" "$root_folder/applications" "$root_folde fi done +# Run target tests if target folder exists. +if [ -d "$root_folder/targets" ]; then + for ex in `find "$root_folder/targets" -name '*.py'`; do + skip_example=false + explicit_targets=`cat $ex | grep -Po '^\s*cudaq.set_target\("\K.*(?=")'` + for t in $explicit_targets; do + if [ "$t" == "quera" ] || [ "$t" == "braket" ] ; then + # Skipped because GitHub does not have the necessary authentication token + # to submit a (paid) job to Amazon Braket (includes QuEra). + echo -e "\e[01;31mWarning: Explicitly set target braket or quera in $ex; skipping validation due to paid submission.\e[0m" >&2 + skip_example=true + fi + done + if ! $skip_example; then + python3 "$ex" + if [ ! $? -eq 0 ]; then + echo -e "\e[01;31mFailed to execute $ex.\e[0m" >&2 + status_sum=$((status_sum+1)) + fi + fi + done +fi + # Run remote-mqpu platform test # Use cudaq-qpud.py wrapper script to automatically find dependencies for the Python wheel configuration. # Note that a derivative of this code is in From d40cb5b2b40d9a99cb6fc12c15459392b653f55a Mon Sep 17 00:00:00 2001 From: Monica VanDieren <150082832+mmvandieren@users.noreply.github.com> Date: Tue, 17 Dec 2024 07:30:13 -0800 Subject: [PATCH 42/44] copy edits and removal of error message in Deutsch's algorithm (#2445) * copy edits and removal of error message in Deutsch's algorithm * Update docs/sphinx/applications/python/deutschs_algorithm.ipynb Co-authored-by: Eric Schweitz Signed-off-by: Monica VanDieren <150082832+mmvandieren@users.noreply.github.com> * caught a spelling error * copy edit to deutschs_algorithm.ipynb and sign-off added DCO Remediation Commit for Monica VanDieren <150082832+mmvandieren@users.noreply.github.com> I, Monica VanDieren <150082832+mmvandieren@users.noreply.github.com>, hereby add my Signed-off-by to this commit: ab2cb59ee0daee5c17aea6a80df3b8b5d468a14c I, Monica VanDieren <150082832+mmvandieren@users.noreply.github.com>, hereby add my Signed-off-by to this commit: c2d03110bed58bc711bde948731340d9bfa25f74 Signed-off-by: Monica VanDieren <150082832+mmvandieren@users.noreply.github.com> --------- Signed-off-by: Monica VanDieren <150082832+mmvandieren@users.noreply.github.com> Co-authored-by: Eric Schweitz Co-authored-by: Bettina Heim --- .../python/deutschs_algorithm.ipynb | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/docs/sphinx/applications/python/deutschs_algorithm.ipynb b/docs/sphinx/applications/python/deutschs_algorithm.ipynb index feaacacd9da..b1e281a0af7 100644 --- a/docs/sphinx/applications/python/deutschs_algorithm.ipynb +++ b/docs/sphinx/applications/python/deutschs_algorithm.ipynb @@ -13,17 +13,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We have a function which takes in a bit and outputs a bit. This can be represented as $f: \\{0,1\\} \\longrightarrow \\{0,1\\}$. \n", + "Deutsch's Algorithm is a concise demonstration of the differences in computational complexity between classical and quantum algorithms for certain problems. For Desutch's algorithm, we begin with a function which takes in a bit and outputs a bit. This can be represented as $f: \\{0,1\\} \\longrightarrow \\{0,1\\}$. \n", + "The function $f$ has the property that it either constant or balanced. The goal of Deutsch's Algorithm is to determine whether our given function is constant or whether it is balanced. \n", "\n", - "The function $f$ has a property; either it is constant or balanced. \n", + "A constant function is \"A balanced function is a function such that the outputs are the same regardless of the inputs, i.e., if $f(0) = 0$ then $f(1) = 1$ or if $f(0) = 1$ then $f(1) = 0$.\n\", the outputs are the same regardless of the inputs, i.e., in the case of $f: \\{0,1\\} \\longrightarrow \\{0,1\\}$, there are are two ways in which this can occur: $f(0) = f(1) = 0$ or $f(0) = f(1) = 1$.\n", "\n", - "If constant, the outputs are the same regardless of the inputs, i.e., $f(0) = f(1) = 0$ or $f(0) = f(1) = 1$.\n", - "\n", - "If balanced, the ouputs are balanced across their possibilities, i.e, if $f(0) = 0$ then $f(1) = 1$ or if $f(0) = 1$ then $f(1) = 0$.\n", - "\n", - "The question we would like to answer is if the function is constant or balanced. \n", + "A balanced function is defined such that the ouputs are balanced across their possibilities, i.e., if $f(0) = 0$ then $f(1) = 1$ or if $f(0) = 1$ then $f(1) = 0$.\n", " \n", - "Classically, if we are given a function $f$, we can solve to find its property via the code below: \n" + "Classically, if we are given a function $f: \\{0,1\\} \\longrightarrow \\{0,1\\}$, we can determine if it is constant or balanced by evaluating the function at $0$ and at $1$. This is carried out in the code below: \n" ] }, { @@ -96,11 +93,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If you step through the `if` statements above, one can see that we require 2 calls to the function to determine its property. That is, we have to query $f$ twice.\n", + "If you step through the `if` statements above, you may notice that we require 2 calls to the function to determine its property. That is, we have to query $f$ twice.\n", "\n", - "The claim is that Deutsch's algorithm can solve for this property with 1 function evalulation, demonstrating quantum advantage. \n", + "The claim is that Deutsch's Algorithm can determine if a given function is constant or balanced with just 1 function evalulation, demonstrating quantum advantage. \n", "\n", - "Below we first go through the math and then the implementation in CUDA Quantum. \n", + "Below we first outline Deutsch's Algorithm and work through the math to verify that it does as promised. Then, we provide the implementation in CUDA-Q. \n", "\n" ] }, @@ -130,7 +127,7 @@ "\n", "\n", "\n", - "Suppose we have $f(x): \\{0,1\\} \\longrightarrow \\{0,1\\}$. We can compute this function on a quantum computer using oracles which we treat as black box functions that yield the output with an appropriate sequence of logic gates. \n", + "Suppose we have $f(x): \\{0,1\\} \\longrightarrow \\{0,1\\}$. We can compute this function on a quantum computer using oracles which we treat as black box functions that yield the output with an appropriate sequence of logical gates. \n", "\n", "Above you see an oracle represented as $U_f$ which allows us to transform the state $\\ket{x}\\ket{y}$ into: \n", "\n", @@ -140,7 +137,7 @@ "\\end{aligned}\n", "$$\n", "\n", - "If $y = 0$, then $U_f\\ket{x}\\ket{y} = U_f\\ket{x}\\ket{0} = \\ket{x}\\ket{0 \\oplus f(x)} = \\ket{x}\\ket{f(x)}$ since $f(x)$ can either be $0/1$ and $0 \\oplus 0 = 0$ and $0 \\oplus 1 = 1$.\n", + "If $y = 0$, then $U_f\\ket{x}\\ket{y} = U_f\\ket{x}\\ket{0} = \\ket{x}\\ket{0 \\oplus f(x)} = \\ket{x}\\ket{f(x)}$, since $f(x)$ can either be $0$ or $1$ and $0 \\oplus 0 = 0$ and $0 \\oplus 1 = 1$.\n", "\n", "This is remarkable because by setting $\\ket{y} = \\ket{0}$, we can extract the value of $f(x)$ by measuring the value of the second qubit. \n", " \n", @@ -213,7 +210,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Deutschs' Algorithm: \n", + "## Deutsch's Algorithm: \n", "\n", "Our aim is to find out if $f: \\{0,1\\} \\longrightarrow \\{0,1\\}$ is a constant or a balanced function? If constant, $f(0) = f(1)$, and if balanced, $f(0) \\neq f(1)$.\n", "\n", @@ -296,18 +293,9 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "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" - ] - } - ], + "outputs": [], "source": [ "# Import the CUDA-Q package and set the target to run on NVIDIA GPUs.\n", "\n", @@ -391,6 +379,17 @@ "elif np.array(result)[0] == '1':\n", " print('f(x) is a balanced function')" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This algorithm can be generalized to determine if a $n$-bit function $f:{0,1}^n\\longrightarrow {0,1}$ is constant or a balanced with only $\\frac{n}{2}$ function evaluations, for $n$ even. A function if balanced if half of the inputs map to $0$ and half map to $1$. \n", + "\n", + "Here we must assume that the function that we are given is either constant or balanced since there are $n$-bit functions that are neither constant, nor balanced. For instance the $2$-bit function $f(b_0,b_1) = \\max(b_0,b_1)$ is neither balanced, nor constant.\n", + "\n", + "A hint on how you might approach this problem is to first solve the problem for $n=2$ and see if you can then use that approach to handle $n$-bit functions for larger values of $n$." + ] } ], "metadata": { From 2284d54115c79da634bcbca4ce3410e7f3e40475 Mon Sep 17 00:00:00 2001 From: Bettina Heim Date: Wed, 18 Dec 2024 20:50:39 +0100 Subject: [PATCH 43/44] Adding 0.9.1 to the list of releases (#2483) Signed-off-by: Bettina Heim --- docs/sphinx/releases.rst | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/releases.rst b/docs/sphinx/releases.rst index de3e12d78b1..8e455ff9dd4 100644 --- a/docs/sphinx/releases.rst +++ b/docs/sphinx/releases.rst @@ -12,6 +12,27 @@ and is also available as a Docker image. More information about installing the n - `Documentation `__ - `Examples `__ +**0.9.1** + +This release adds support for using +`Amazon Braket `__ and +`Infeqtion's Superstaq `__ as backends. + +Starting with this release, all C++ quantum kernels will be processed by the `nvq++` compiler regardless of whether +they run on a simulator or on a quantum hardware backend. This change is largely non-breaking, but language constructs +that are not officially supported within quantum kernels will now lead to a compilation error whereas previously they +could be used when executing on a simulator only. The previous behavior can be forced by passing the `--library-mode` +flag to the compiler. Please note that if you do so, however, the code will never be executable outside of a simulator +and may not be supported even on simulators. + +- `Docker image `__ +- `Python wheel `__ +- `C++ installer `__ +- `Documentation `__ +- `Examples `__ + +The full change log can be found `here `__. + **0.9.0** We are very excited to share a new toolset added for modeling and manipulating the dynamics of physical systems. @@ -21,12 +42,12 @@ The 0.9.0 release furthermore includes a range of contribution to add new backen from `Anyon Technologies `__, `Ferimioniq `__, and `QuEra Computing `__, -as well as updates to existing backends from `ORCA `__ +as well as updates to existing backends from `ORCA `__ and `OQC `__. We hope you enjoy the new features - also check out our new notebooks and examples to dive into CUDA-Q. -- `Docker image `__ -- `Python wheel `__ +- `Docker image `__ +- `Python wheel `__ - `C++ installer `__ - `Documentation `__ - `Examples `__ From 1a4f99ef78964a096a09b82fdecc796e10350806 Mon Sep 17 00:00:00 2001 From: Bruno Schmitt <7152025+boschmitt@users.noreply.github.com> Date: Wed, 18 Dec 2024 23:33:09 +0100 Subject: [PATCH 44/44] [nvq++] Removes MLIR's `memref` dialect. (#2438) * [nvq++] Removes MLIR's `scf` dialect We don't use this dialect and its presence is a historical artifact. This change triggered the removal of two tests: * `test/Quake/ghz.qke` * `test/Quake/iqft.qke` Both tests are a reminder of a past when we had to write quantum kernels directly in MLIR because of a lack of frontend. Both no longer test aything useful. The commit modifies `test/Quake/canonical-2.qke`, which was only testing the canonicalization of `cc.scope` operations. The new form is removes the clutter, making the test more precise. `test/Translate/ghz.qke` had to be modified because it uses MLIR's `affined.for` and its conversion to LLVMDialect requires `scf.for`. Signed-off-by: boschmitt <7152025+boschmitt@users.noreply.github.com> * [nvq++] Remove MLIR's `affine` dialect. We don't use this dialect. The removal triggered the removel of one test: * `test/Quake/ccnot.qke` The test was not testing anything useful, only kernel inlining, which is already covered in other tests. Furthermore, the test is misleading because, contrary to what the kernel name might indicate, it is not implementing a `ccnot`. Signed-off-by: boschmitt <7152025+boschmitt@users.noreply.github.com> * [nvq++] Removes MLIR's `memref` dialect. We don't use this dialect. Most changes were to substitute `memref` ops with `cc` ops in tests. Signed-off-by: boschmitt <7152025+boschmitt@users.noreply.github.com> --------- Signed-off-by: boschmitt <7152025+boschmitt@users.noreply.github.com> Signed-off-by: Eric Schweitz Co-authored-by: Eric Schweitz --- include/cudaq/Optimizer/InitAllDialects.h | 2 - test/Quake/lambda_kernel_exec.qke | 12 +- test/Quake/loop.qke | 132 +++++++++++----------- test/Quake/memtoreg-2.qke | 78 ++++++------- test/Quake/observeAnsatz.qke | 6 +- test/Translate/alloca_no_operand.qke | 18 +-- tools/cudaq-quake/CMakeLists.txt | 2 - 7 files changed, 120 insertions(+), 130 deletions(-) diff --git a/include/cudaq/Optimizer/InitAllDialects.h b/include/cudaq/Optimizer/InitAllDialects.h index abe474fe928..54b1ac29f79 100644 --- a/include/cudaq/Optimizer/InitAllDialects.h +++ b/include/cudaq/Optimizer/InitAllDialects.h @@ -16,7 +16,6 @@ #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/LLVMIR/LLVMDialect.h" #include "mlir/Dialect/Math/IR/Math.h" -#include "mlir/Dialect/MemRef/IR/MemRef.h" namespace cudaq { @@ -31,7 +30,6 @@ inline void registerAllDialects(mlir::DialectRegistry ®istry) { mlir::func::FuncDialect, mlir::LLVM::LLVMDialect, mlir::math::MathDialect, - mlir::memref::MemRefDialect, // CUDA-Q dialects cudaq::cc::CCDialect, diff --git a/test/Quake/lambda_kernel_exec.qke b/test/Quake/lambda_kernel_exec.qke index aedb9564b5f..f4076f57cbf 100644 --- a/test/Quake/lambda_kernel_exec.qke +++ b/test/Quake/lambda_kernel_exec.qke @@ -27,10 +27,10 @@ module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__lambda.main.canHa %4 = arith.extsi %c0_i32_0 : i32 to i64 %5 = quake.extract_ref %1[%4] : (!quake.veq, i64) -> !quake.ref %16 = quake.mz %5 name "b" : (!quake.ref) -> !quake.measure - %alloca = memref.alloca() : memref + %alloca = cc.alloca i1 %6 = quake.discriminate %16 : (!quake.measure) -> i1 - memref.store %6, %alloca[] : memref - %7 = memref.load %alloca[] : memref + cc.store %6, %alloca : !cc.ptr + %7 = cc.load %alloca : !cc.ptr cc.if(%7) { cc.scope { %c1_i32_1 = arith.constant 1 : i32 @@ -67,9 +67,9 @@ module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__lambda.main.canHa %5 = quake.extract_ref %1[%4] : (!quake.veq,i64) -> !quake.ref %16 = quake.mz %5 name "b" : (!quake.ref) -> !quake.measure %6 = quake.discriminate %16 : (!quake.measure) -> i1 - %alloca = memref.alloca() : memref - memref.store %6, %alloca[] : memref - %7 = memref.load %alloca[] : memref + %alloca = cc.alloca i1 + cc.store %6, %alloca : !cc.ptr + %7 = cc.load %alloca : !cc.ptr cc.if(%7) { cc.scope { %c1_i32_1 = arith.constant 1 : i32 diff --git a/test/Quake/loop.qke b/test/Quake/loop.qke index 3c144eb9ead..2982c517c76 100644 --- a/test/Quake/loop.qke +++ b/test/Quake/loop.qke @@ -9,17 +9,17 @@ // RUN: cudaq-opt %s | cudaq-opt | FileCheck %s func.func @test_old_for() { - %1 = memref.alloc() : memref + %1 = cc.alloca i32 %zero = arith.constant 0 : i32 - memref.store %zero, %1[] : memref + cc.store %zero, %1 : !cc.ptr cc.loop while { - %3 = memref.load %1[] : memref + %3 = cc.load %1 : !cc.ptr %ten = arith.constant 10 : i32 %8 = arith.cmpi slt, %3, %ten : i32 cc.condition %8 } do { ^bb0: - %13 = memref.load %1[] : memref + %13 = cc.load %1 : !cc.ptr %five = arith.constant 5 : i32 %18 = arith.cmpi slt, %13, %five : i32 cf.cond_br %18, ^bb1, ^bb2 @@ -29,25 +29,25 @@ func.func @test_old_for() { cc.continue } step { %4 = arith.constant 12 : i32 - %5 = memref.load %1[] : memref + %5 = cc.load %1 : !cc.ptr %6 = arith.addi %4, %5 : i32 - memref.store %6, %1[] : memref + cc.store %6, %1 : !cc.ptr cc.continue } func.return } // CHECK-LABEL: func.func @test_old_for() { -// CHECK: %[[VAL_0:.*]] = memref.alloc() : memref +// CHECK: %[[VAL_0:.*]] = cc.alloca i32 // CHECK: %[[VAL_1:.*]] = arith.constant 0 : i32 -// CHECK: memref.store %[[VAL_1]], %[[VAL_0]][] : memref +// CHECK: cc.store %[[VAL_1]], %[[VAL_0]] : !cc.ptr // CHECK: cc.loop while { -// CHECK: %[[VAL_2:.*]] = memref.load %[[VAL_0]][] : memref +// CHECK: %[[VAL_2:.*]] = cc.load %[[VAL_0]] : !cc.ptr // CHECK: %[[VAL_3:.*]] = arith.constant 10 : i32 // CHECK: %[[VAL_4:.*]] = arith.cmpi slt, %[[VAL_2]], %[[VAL_3]] : i32 // CHECK: cc.condition %[[VAL_4]] // CHECK: } do { -// CHECK: %[[VAL_5:.*]] = memref.load %[[VAL_0]][] : memref +// CHECK: %[[VAL_5:.*]] = cc.load %[[VAL_0]] : !cc.ptr // CHECK: %[[VAL_6:.*]] = arith.constant 5 : i32 // CHECK: %[[VAL_7:.*]] = arith.cmpi slt, %[[VAL_5]], %[[VAL_6]] : i32 // CHECK: cf.cond_br %[[VAL_7]], ^bb1, ^bb2 @@ -57,26 +57,26 @@ func.func @test_old_for() { // CHECK: cc.continue // CHECK: } step { // CHECK: %[[VAL_8:.*]] = arith.constant 12 : i32 -// CHECK: %[[VAL_9:.*]] = memref.load %[[VAL_0]][] : memref +// CHECK: %[[VAL_9:.*]] = cc.load %[[VAL_0]] : !cc.ptr // CHECK: %[[VAL_10:.*]] = arith.addi %[[VAL_8]], %[[VAL_9]] : i32 -// CHECK: memref.store %[[VAL_10]], %[[VAL_0]][] : memref +// CHECK: cc.store %[[VAL_10]], %[[VAL_0]] : !cc.ptr // CHECK: } // CHECK: return // CHECK: } func.func @test_scoped_for() { cc.scope { - %1 = memref.alloc() : memref + %1 = cc.alloca i32 %zero = arith.constant 0 : i32 - memref.store %zero, %1[] : memref + cc.store %zero, %1 : !cc.ptr cc.loop while { - %3 = memref.load %1[] : memref + %3 = cc.load %1 : !cc.ptr %ten = arith.constant 10 : i32 %8 = arith.cmpi slt, %3, %ten : i32 cc.condition %8 } do { ^bb0: - %13 = memref.load %1[] : memref + %13 = cc.load %1 : !cc.ptr %five = arith.constant 5 : i32 %18 = arith.cmpi slt, %13, %five : i32 cf.cond_br %18, ^bb1, ^bb2 @@ -86,9 +86,9 @@ func.func @test_scoped_for() { cc.continue } step { %4 = arith.constant 12 : i32 - %5 = memref.load %1[] : memref + %5 = cc.load %1 : !cc.ptr %6 = arith.addi %4, %5 : i32 - memref.store %6, %1[] : memref + cc.store %6, %1 : !cc.ptr cc.continue } cc.continue @@ -98,16 +98,16 @@ func.func @test_scoped_for() { // CHECK-LABEL: func.func @test_scoped_for() { // CHECK: cc.scope { -// CHECK: %[[VAL_0:.*]] = memref.alloc() : memref +// CHECK: %[[VAL_0:.*]] = cc.alloca i32 // CHECK: %[[VAL_1:.*]] = arith.constant 0 : i32 -// CHECK: memref.store %[[VAL_1]], %[[VAL_0]][] : memref +// CHECK: cc.store %[[VAL_1]], %[[VAL_0]] : !cc.ptr // CHECK: cc.loop while { -// CHECK: %[[VAL_2:.*]] = memref.load %[[VAL_0]][] : memref +// CHECK: %[[VAL_2:.*]] = cc.load %[[VAL_0]] : !cc.ptr // CHECK: %[[VAL_3:.*]] = arith.constant 10 : i32 // CHECK: %[[VAL_4:.*]] = arith.cmpi slt, %[[VAL_2]], %[[VAL_3]] : i32 // CHECK: cc.condition %[[VAL_4]] // CHECK: } do { -// CHECK: %[[VAL_5:.*]] = memref.load %[[VAL_0]][] : memref +// CHECK: %[[VAL_5:.*]] = cc.load %[[VAL_0]] : !cc.ptr // CHECK: %[[VAL_6:.*]] = arith.constant 5 : i32 // CHECK: %[[VAL_7:.*]] = arith.cmpi slt, %[[VAL_5]], %[[VAL_6]] : i32 // CHECK: cf.cond_br %[[VAL_7]], ^bb1, ^bb2 @@ -117,9 +117,9 @@ func.func @test_scoped_for() { // CHECK: cc.continue // CHECK: } step { // CHECK: %[[VAL_8:.*]] = arith.constant 12 : i32 -// CHECK: %[[VAL_9:.*]] = memref.load %[[VAL_0]][] : memref +// CHECK: %[[VAL_9:.*]] = cc.load %[[VAL_0]] : !cc.ptr // CHECK: %[[VAL_10:.*]] = arith.addi %[[VAL_8]], %[[VAL_9]] : i32 -// CHECK: memref.store %[[VAL_10]], %[[VAL_0]][] : memref +// CHECK: cc.store %[[VAL_10]], %[[VAL_0]] : !cc.ptr // CHECK: } // CHECK: } // CHECK: return @@ -127,17 +127,17 @@ func.func @test_scoped_for() { func.func @test_scoped_for_with_args() { cc.scope { - %1 = memref.alloc() : memref + %1 = cc.alloca i32 %zero = arith.constant 0 : i32 - memref.store %zero, %1[] : memref + cc.store %zero, %1 : !cc.ptr %z2 = cc.loop while ((%xtra = %zero) -> i32) { - %3 = memref.load %1[] : memref + %3 = cc.load %1 : !cc.ptr %ten = arith.constant 10 : i32 %8 = arith.cmpi slt, %3, %ten : i32 cc.condition %8 (%xtra : i32) } do { ^bb0(%x2 : i32): - %13 = memref.load %1[] : memref + %13 = cc.load %1 : !cc.ptr %five = arith.constant 5 : i32 %18 = arith.cmpi slt, %13, %five : i32 cf.cond_br %18, ^bb1, ^bb2 @@ -149,9 +149,9 @@ func.func @test_scoped_for_with_args() { ^bb4 (%x3 : i32): %4 = arith.constant 12 : i32 %16 = arith.addi %x3, %4 : i32 - %5 = memref.load %1[] : memref + %5 = cc.load %1 : !cc.ptr %6 = arith.addi %16, %5 : i32 - memref.store %6, %1[] : memref + cc.store %6, %1 : !cc.ptr cc.continue %x3 : i32 } } @@ -162,17 +162,17 @@ func.func private @getI32() -> i32 // CHECK-LABEL: func.func @test_scoped_for_with_args() { // CHECK: cc.scope { -// CHECK: %[[VAL_0:.*]] = memref.alloc() : memref +// CHECK: %[[VAL_0:.*]] = cc.alloca i32 // CHECK: %[[VAL_1:.*]] = arith.constant 0 : i32 -// CHECK: memref.store %[[VAL_1]], %[[VAL_0]][] : memref +// CHECK: cc.store %[[VAL_1]], %[[VAL_0]] : !cc.ptr // CHECK: %[[VAL_2:.*]] = cc.loop while ((%[[VAL_3:.*]] = %[[VAL_1]]) -> (i32)) { -// CHECK: %[[VAL_4:.*]] = memref.load %[[VAL_0]][] : memref +// CHECK: %[[VAL_4:.*]] = cc.load %[[VAL_0]] : !cc.ptr // CHECK: %[[VAL_5:.*]] = arith.constant 10 : i32 // CHECK: %[[VAL_6:.*]] = arith.cmpi slt, %[[VAL_4]], %[[VAL_5]] : i32 // CHECK: cc.condition %[[VAL_6]](%[[VAL_3]] : i32) // CHECK: } do { // CHECK: ^bb0(%[[VAL_7:.*]]: i32): -// CHECK: %[[VAL_8:.*]] = memref.load %[[VAL_0]][] : memref +// CHECK: %[[VAL_8:.*]] = cc.load %[[VAL_0]] : !cc.ptr // CHECK: %[[VAL_9:.*]] = arith.constant 5 : i32 // CHECK: %[[VAL_10:.*]] = arith.cmpi slt, %[[VAL_8]], %[[VAL_9]] : i32 // CHECK: cf.cond_br %[[VAL_10]], ^bb1, ^bb2 @@ -184,9 +184,9 @@ func.func private @getI32() -> i32 // CHECK: ^bb0(%[[VAL_11:.*]]: i32): // CHECK: %[[VAL_12:.*]] = arith.constant 12 : i32 // CHECK: %[[VAL_13:.*]] = arith.addi %[[VAL_11]], %[[VAL_12]] : i32 -// CHECK: %[[VAL_14:.*]] = memref.load %[[VAL_0]][] : memref +// CHECK: %[[VAL_14:.*]] = cc.load %[[VAL_0]] : !cc.ptr // CHECK: %[[VAL_15:.*]] = arith.addi %[[VAL_13]], %[[VAL_14]] : i32 -// CHECK: memref.store %[[VAL_15]], %[[VAL_0]][] : memref +// CHECK: cc.store %[[VAL_15]], %[[VAL_0]] : !cc.ptr // CHECK: cc.continue %[[VAL_11]] : i32 // CHECK: } // CHECK: } @@ -196,13 +196,13 @@ func.func private @getI32() -> i32 // CHECK-LABEL: func.func private @getI32() -> i32 func.func @test_do_while() { - %1 = memref.alloc() : memref + %1 = cc.alloca i32 %zero = arith.constant 0 : i32 - memref.store %zero, %1[] : memref + cc.store %zero, %1 : !cc.ptr cc.loop do { ^bb0: %8 = func.call @getI32() : () -> i32 - memref.store %8, %1[] : memref + cc.store %8, %1 : !cc.ptr cc.continue } while { %3 = arith.constant 1 : i1 @@ -212,12 +212,12 @@ func.func @test_do_while() { } // CHECK-LABEL: func.func @test_do_while() { -// CHECK: %[[VAL_0:.*]] = memref.alloc() : memref +// CHECK: %[[VAL_0:.*]] = cc.alloca i32 // CHECK: %[[VAL_1:.*]] = arith.constant 0 : i32 -// CHECK: memref.store %[[VAL_1]], %[[VAL_0]][] : memref +// CHECK: cc.store %[[VAL_1]], %[[VAL_0]] : !cc.ptr // CHECK: cc.loop do { // CHECK: %[[VAL_2:.*]] = func.call @getI32() : () -> i32 -// CHECK: memref.store %[[VAL_2]], %[[VAL_0]][] : memref +// CHECK: cc.store %[[VAL_2]], %[[VAL_0]] : !cc.ptr // CHECK: } while { // CHECK: %[[VAL_3:.*]] = arith.constant true // CHECK: cc.condition %[[VAL_3]] @@ -226,12 +226,12 @@ func.func @test_do_while() { // CHECK: } func.func @test_do_while_with_args() { - %1 = memref.alloc() : memref + %1 = cc.alloca i32 %zero = arith.constant 0 : i32 - memref.store %zero, %1[] : memref + cc.store %zero, %1 : !cc.ptr cc.loop do ((%i = %zero) -> i32) { %8 = func.call @getI32() : () -> i32 - memref.store %i, %1[] : memref + cc.store %i, %1 : !cc.ptr cc.continue %i : i32 } while { ^bb9(%arg0 : i32): @@ -245,12 +245,12 @@ func.func @test_do_while_with_args() { } // CHECK-LABEL: func.func @test_do_while_with_args() { -// CHECK: %[[VAL_0:.*]] = memref.alloc() : memref +// CHECK: %[[VAL_0:.*]] = cc.alloca i32 // CHECK: %[[VAL_1:.*]] = arith.constant 0 : i32 -// CHECK: memref.store %[[VAL_1]], %[[VAL_0]][] : memref +// CHECK: cc.store %[[VAL_1]], %[[VAL_0]] : !cc.ptr // CHECK: %[[VAL_2:.*]] = cc.loop do ((%[[VAL_3:.*]] = %[[VAL_1]]) -> (i32)) { // CHECK: %[[VAL_4:.*]] = func.call @getI32() : () -> i32 -// CHECK: memref.store %[[VAL_3]], %[[VAL_0]][] : memref +// CHECK: cc.store %[[VAL_3]], %[[VAL_0]] : !cc.ptr // CHECK: cc.continue %[[VAL_3]] : i32 // CHECK: } while { // CHECK: ^bb0(%[[VAL_5:.*]]: i32): @@ -264,40 +264,40 @@ func.func @test_do_while_with_args() { // CHECK: } func.func @test_while() { - %1 = memref.alloc() : memref + %1 = cc.alloca i32 %zero = arith.constant 0 : i32 - memref.store %zero, %1[] : memref + cc.store %zero, %1 : !cc.ptr cc.loop while { %3 = arith.constant 1 : i1 cc.condition %3 } do { ^bb0: %8 = func.call @getI32() : () -> i32 - memref.store %8, %1[] : memref + cc.store %8, %1 : !cc.ptr cc.continue } func.return } // CHECK-LABEL: func.func @test_while() { -// CHECK: %[[VAL_0:.*]] = memref.alloc() : memref +// CHECK: %[[VAL_0:.*]] = cc.alloca i32 // CHECK: %[[VAL_1:.*]] = arith.constant 0 : i32 -// CHECK: memref.store %[[VAL_1]], %[[VAL_0]][] : memref +// CHECK: cc.store %[[VAL_1]], %[[VAL_0]] : !cc.ptr // CHECK: cc.loop while { // CHECK: %[[VAL_2:.*]] = arith.constant true // CHECK: cc.condition %[[VAL_2]] // CHECK: } do { // CHECK: %[[VAL_3:.*]] = func.call @getI32() : () -> i32 -// CHECK: memref.store %[[VAL_3]], %[[VAL_0]][] : memref +// CHECK: cc.store %[[VAL_3]], %[[VAL_0]] : !cc.ptr // CHECK: cc.continue // CHECK: } // CHECK: return // CHECK: } func.func @test_if_else(%c : i1) { - %1 = memref.alloc() : memref + %1 = cc.alloca i32 %zero = arith.constant 0 : i32 - memref.store %zero, %1[] : memref + cc.store %zero, %1 : !cc.ptr cc.if (%c) { ^bb0: %3 = arith.constant 1 : i1 @@ -314,9 +314,9 @@ func.func @test_if_else(%c : i1) { // CHECK-LABEL: func.func @test_if_else( // CHECK-SAME: %[[VAL_0:.*]]: i1) { -// CHECK: %[[VAL_1:.*]] = memref.alloc() : memref +// CHECK: %[[VAL_1:.*]] = cc.alloca i32 // CHECK: %[[VAL_2:.*]] = arith.constant 0 : i32 -// CHECK: memref.store %[[VAL_2]], %[[VAL_1]][] : memref +// CHECK: cc.store %[[VAL_2]], %[[VAL_1]] : !cc.ptr // CHECK: cc.if(%[[VAL_0]]) { // CHECK: %[[VAL_3:.*]] = arith.constant true // CHECK: } else { @@ -329,9 +329,9 @@ func.func @test_if_else(%c : i1) { // CHECK: } func.func @test_if(%c : i1) { - %1 = memref.alloc() : memref + %1 = cc.alloca i32 %zero = arith.constant 0 : i32 - memref.store %zero, %1[] : memref + cc.store %zero, %1 : !cc.ptr cc.if (%c) { ^bb1: %8 = func.call @getI32() : () -> i32 @@ -344,9 +344,9 @@ func.func @test_if(%c : i1) { // CHECK-LABEL: func.func @test_if( // CHECK-SAME: %[[VAL_0:.*]]: i1) { -// CHECK: %[[VAL_1:.*]] = memref.alloc() : memref +// CHECK: %[[VAL_1:.*]] = cc.alloca i32 // CHECK: %[[VAL_2:.*]] = arith.constant 0 : i32 -// CHECK: memref.store %[[VAL_2]], %[[VAL_1]][] : memref +// CHECK: cc.store %[[VAL_2]], %[[VAL_1]] : !cc.ptr // CHECK: cc.if(%[[VAL_0]]) { // CHECK: %[[VAL_3:.*]] = func.call @getI32() : () -> i32 // CHECK: cf.br ^bb1 @@ -357,9 +357,9 @@ func.func @test_if(%c : i1) { // CHECK: } func.func @test_if_else_thread(%c : i1) -> i32 { - %1 = memref.alloc() : memref + %1 = cc.alloca i32 %zero = arith.constant 0 : i32 - memref.store %zero, %1[] : memref + cc.store %zero, %1 : !cc.ptr %2 = cc.if (%c) -> i32 { %3 = arith.constant 1 : i32 cc.continue %3 : i32 @@ -372,9 +372,9 @@ func.func @test_if_else_thread(%c : i1) -> i32 { // CHECK-LABEL: func.func @test_if_else_thread( // CHECK-SAME: %[[VAL_0:.*]]: i1) -> i32 { -// CHECK: %[[VAL_1:.*]] = memref.alloc() : memref +// CHECK: %[[VAL_1:.*]] = cc.alloca i32 // CHECK: %[[VAL_2:.*]] = arith.constant 0 : i32 -// CHECK: memref.store %[[VAL_2]], %[[VAL_1]][] : memref +// CHECK: cc.store %[[VAL_2]], %[[VAL_1]] : !cc.ptr // CHECK: %[[VAL_3:.*]] = cc.if(%[[VAL_0]]) -> i32 { // CHECK: %[[VAL_4:.*]] = arith.constant 1 : i32 // CHECK: cc.continue %[[VAL_4]] : i32 diff --git a/test/Quake/memtoreg-2.qke b/test/Quake/memtoreg-2.qke index 2878c43c6d4..c1e0bd1c505 100644 --- a/test/Quake/memtoreg-2.qke +++ b/test/Quake/memtoreg-2.qke @@ -754,20 +754,19 @@ func.func @simple_loop() { %c1_i64 = arith.constant 1 : i64 %c42_i64 = arith.constant 42 : i64 %q0 = quake.alloca !quake.ref - // memtoreg does not promote memref types. - %alloca = memref.alloca() : memref - memref.store %c0_i64, %alloca[] : memref + %alloca = cc.alloca i64 + cc.store %c0_i64, %alloca : !cc.ptr cc.loop while { - %1 = memref.load %alloca[] : memref + %1 = cc.load %alloca : !cc.ptr %2 = arith.cmpi ult, %1, %c42_i64 : i64 cc.condition %2 } do { quake.x %q0 : (!quake.ref) -> () cc.continue } step { - %1 = memref.load %alloca[] : memref + %1 = cc.load %alloca : !cc.ptr %2 = arith.addi %1, %c1_i64 : i64 - memref.store %2, %alloca[] : memref + cc.store %2, %alloca : !cc.ptr } quake.z %q0 : (!quake.ref) -> () quake.dealloc %q0 : !quake.ref @@ -779,24 +778,21 @@ func.func @simple_loop() { // CHECK: %[[VAL_1:.*]] = arith.constant 1 : i64 // CHECK: %[[VAL_2:.*]] = arith.constant 42 : i64 // CHECK: %[[VAL_3:.*]] = quake.null_wire -// CHECK: %[[VAL_4:.*]] = memref.alloca() : memref -// CHECK: memref.store %[[VAL_0]], %[[VAL_4]][] : memref -// CHECK: %[[VAL_5:.*]] = cc.loop while ((%[[VAL_6:.*]] = %[[VAL_3]]) -> (!quake.wire)) { -// CHECK: %[[VAL_7:.*]] = memref.load %[[VAL_4]][] : memref +// CHECK: %[[VAL_4:.*]] = cc.undef i64 +// CHECK: %[[VAL_5:.*]]:2 = cc.loop while ((%[[VAL_6:.*]] = %[[VAL_3]], %[[VAL_7:.*]] = %[[VAL_0]]) -> (!quake.wire, i64)) { // CHECK: %[[VAL_8:.*]] = arith.cmpi ult, %[[VAL_7]], %[[VAL_2]] : i64 -// CHECK: cc.condition %[[VAL_8]](%[[VAL_6]] : !quake.wire) +// CHECK: cc.condition %[[VAL_8]](%[[VAL_6]], %[[VAL_7]] : !quake.wire, i64) // CHECK: } do { -// CHECK: ^bb0(%[[VAL_9:.*]]: !quake.wire): -// CHECK: %[[VAL_10:.*]] = quake.x %[[VAL_9]] : (!quake.wire) -> !quake.wire -// CHECK: cc.continue %[[VAL_10]] : !quake.wire +// CHECK: ^bb0(%[[VAL_9:.*]]: !quake.wire, %[[VAL_10:.*]]: i64): +// CHECK: %[[VAL_11:.*]] = quake.x %[[VAL_9]] : (!quake.wire) -> !quake.wire +// CHECK: cc.continue %[[VAL_11]], %[[VAL_10]] : !quake.wire, i64 // CHECK: } step { -// CHECK: ^bb0(%[[VAL_11:.*]]: !quake.wire): -// CHECK: %[[VAL_12:.*]] = memref.load %[[VAL_4]][] : memref -// CHECK: %[[VAL_13:.*]] = arith.addi %[[VAL_12]], %[[VAL_1]] : i64 -// CHECK: memref.store %[[VAL_13]], %[[VAL_4]][] : memref -// CHECK: cc.continue %[[VAL_11]] : !quake.wire +// CHECK: ^bb0(%[[VAL_12:.*]]: !quake.wire, %[[VAL_13:.*]]: i64): +// CHECK: %[[VAL_14:.*]] = arith.addi %[[VAL_13]], %[[VAL_1]] : i64 +// CHECK: cc.continue %[[VAL_12]], %[[VAL_14]] : !quake.wire, i64 // CHECK: } -// CHECK: %[[VAL_14:.*]] = quake.z %[[VAL_15:.*]] : (!quake.wire) -> !quake.wire +// CHECK: %[[VAL_15:.*]] = quake.z %[[VAL_16:.*]]#0 : (!quake.wire) -> !quake.wire +// CHECK: quake.sink %[[VAL_15]] : !quake.wire // CHECK: return // CHECK: } @@ -806,51 +802,49 @@ func.func @floop_with_vector_and_qextract() { %c2_i64 = arith.constant 2 : i64 %veq = quake.alloca !quake.veq<2> %q0 = quake.extract_ref %veq[%c0_i64] : (!quake.veq<2> ,i64) -> !quake.ref - %alloca = memref.alloca() : memref - memref.store %c0_i64, %alloca[] : memref + %alloca = cc.alloca i64 + cc.store %c0_i64, %alloca : !cc.ptr cc.loop while { - %3 = memref.load %alloca[] : memref + %3 = cc.load %alloca : !cc.ptr %4 = arith.cmpi ult, %3, %c2_i64 : i64 cc.condition %4 } do { - %3 = memref.load %alloca[] : memref + %3 = cc.load %alloca : !cc.ptr %4 = quake.extract_ref %veq[%3] : (!quake.veq<2> ,i64) -> !quake.ref cc.continue } step { - %3 = memref.load %alloca[] : memref + %3 = cc.load %alloca : !cc.ptr %4 = arith.addi %3, %c1_i64 : i64 - memref.store %4, %alloca[] : memref + cc.store %4, %alloca : !cc.ptr } %2 = quake.mz %veq : (!quake.veq<2>) -> !cc.stdvec quake.dealloc %veq : !quake.veq<2> return } - // CHECK-LABEL: func.func @floop_with_vector_and_qextract() { // CHECK: %[[VAL_0:.*]] = arith.constant 0 : i64 // CHECK: %[[VAL_1:.*]] = arith.constant 1 : i64 // CHECK: %[[VAL_2:.*]] = arith.constant 2 : i64 // CHECK: %[[VAL_3:.*]] = quake.alloca !quake.veq<2> -// CHECK: %[[VAL_4:.*]] = quake.extract_ref %[[VAL_3]][%[[VAL_0]]] : (!quake.veq<2>, i64) -> !quake.ref +// CHECK: %[[VAL_4:.*]] = quake.extract_ref %[[VAL_3]]{{\[}}%[[VAL_0]]] : (!quake.veq<2>, i64) -> !quake.ref // CHECK: %[[VAL_5:.*]] = quake.unwrap %[[VAL_4]] : (!quake.ref) -> !quake.wire -// CHECK: %[[VAL_6:.*]] = memref.alloca() : memref -// CHECK: memref.store %[[VAL_0]], %[[VAL_6]][] : memref -// CHECK: cc.loop while { -// CHECK: %[[VAL_7:.*]] = memref.load %[[VAL_6]][] : memref -// CHECK: %[[VAL_8:.*]] = arith.cmpi ult, %[[VAL_7]], %[[VAL_2]] : i64 -// CHECK: cc.condition %[[VAL_8]] +// CHECK: %[[VAL_6:.*]] = cc.undef i64 +// CHECK: %[[VAL_7:.*]] = cc.loop while ((%[[VAL_8:.*]] = %[[VAL_0]]) -> (i64)) { +// CHECK: %[[VAL_9:.*]] = arith.cmpi ult, %[[VAL_8]], %[[VAL_2]] : i64 +// CHECK: cc.condition %[[VAL_9]](%[[VAL_8]] : i64) // CHECK: } do { -// CHECK: %[[VAL_9:.*]] = memref.load %[[VAL_6]][] : memref -// CHECK: %[[VAL_10:.*]] = quake.extract_ref %[[VAL_3]][%[[VAL_9]]] : (!quake.veq<2>, i64) -> !quake.ref -// CHECK: %[[VAL_11:.*]] = quake.unwrap %[[VAL_10]] : (!quake.ref) -> !quake.wire -// CHECK: cc.continue +// CHECK: ^bb0(%[[VAL_10:.*]]: i64): +// CHECK: %[[VAL_11:.*]] = quake.extract_ref %[[VAL_3]]{{\[}}%[[VAL_10]]] : (!quake.veq<2>, i64) -> !quake.ref +// CHECK: %[[VAL_12:.*]] = quake.unwrap %[[VAL_11]] : (!quake.ref) -> !quake.wire +// CHECK: cc.continue %[[VAL_10]] : i64 // CHECK: } step { -// CHECK: %[[VAL_12:.*]] = memref.load %[[VAL_6]][] : memref -// CHECK: %[[VAL_13:.*]] = arith.addi %[[VAL_12]], %[[VAL_1]] : i64 -// CHECK: memref.store %[[VAL_13]], %[[VAL_6]][] : memref +// CHECK: ^bb0(%[[VAL_13:.*]]: i64): +// CHECK: %[[VAL_14:.*]] = arith.addi %[[VAL_13]], %[[VAL_1]] : i64 +// CHECK: cc.continue %[[VAL_14]] : i64 // CHECK: } -// CHECK: quake.mz %[[VAL_3]] : (!quake.veq<2>) -> !cc.stdvec +// CHECK: %[[VAL_15:.*]] = quake.mz %[[VAL_3]] : (!quake.veq<2>) -> !cc.stdvec +// CHECK: quake.dealloc %[[VAL_3]] : !quake.veq<2> // CHECK: return // CHECK: } diff --git a/test/Quake/observeAnsatz.qke b/test/Quake/observeAnsatz.qke index ea7eefa157e..3c08197d809 100644 --- a/test/Quake/observeAnsatz.qke +++ b/test/Quake/observeAnsatz.qke @@ -11,12 +11,12 @@ func.func @__nvqpp__mlirgen__ansatz(%arg0: f64) { %c0_i64 = arith.constant 0 : i64 %c1_i64 = arith.constant 1 : i64 - %0 = memref.alloca() : memref - memref.store %arg0, %0[] : memref + %0 = cc.alloca f64 + cc.store %arg0, %0 : !cc.ptr %1 = quake.alloca !quake.veq<2> %2 = quake.extract_ref %1[%c0_i64] : (!quake.veq<2>,i64) -> !quake.ref quake.x %2 : (!quake.ref) -> () - %3 = memref.load %0[] : memref + %3 = cc.load %0 : !cc.ptr %4 = quake.extract_ref %1[%c1_i64] : (!quake.veq<2>,i64) -> !quake.ref quake.ry (%3) %4 : (f64, !quake.ref) -> () %5 = quake.extract_ref %1[%c1_i64] : (!quake.veq<2>,i64) -> !quake.ref diff --git a/test/Translate/alloca_no_operand.qke b/test/Translate/alloca_no_operand.qke index a321371a410..af2feccaf5d 100644 --- a/test/Translate/alloca_no_operand.qke +++ b/test/Translate/alloca_no_operand.qke @@ -10,7 +10,7 @@ func.func @adder_n4() { %0 = quake.alloca !quake.veq<4> - %1 = memref.alloc() : memref<4xi1> + %1 = cc.alloca !cc.array %c0 = arith.constant 0 : index %2 = quake.extract_ref %0[%c0] : (!quake.veq<4>, index) -> !quake.ref quake.x %2 : (!quake.ref) -> () @@ -44,20 +44,20 @@ func.func @adder_n4() { quake.h %4 : (!quake.ref) -> () %6 = quake.mz %2 : (!quake.ref) -> !quake.measure %61 = quake.discriminate %6 : (!quake.measure) -> i1 - %c0_0 = arith.constant 0 : index - memref.store %61, %1[%c0_0] : memref<4xi1> + %a = cc.compute_ptr %1[0] : (!cc.ptr>) -> !cc.ptr + cc.store %61, %a : !cc.ptr %7 = quake.mz %3 : (!quake.ref) -> !quake.measure %71 = quake.discriminate %7 : (!quake.measure) -> i1 - %c1_1 = arith.constant 1 : index - memref.store %71, %1[%c1_1] : memref<4xi1> + %b = cc.compute_ptr %1[1] : (!cc.ptr>) -> !cc.ptr + cc.store %71, %b : !cc.ptr %8 = quake.mz %5 : (!quake.ref) -> !quake.measure %81 = quake.discriminate %8 : (!quake.measure) -> i1 - %c2_2 = arith.constant 2 : index - memref.store %81, %1[%c2_2] : memref<4xi1> + %c = cc.compute_ptr %1[2] : (!cc.ptr>) -> !cc.ptr + cc.store %81, %c : !cc.ptr %9 = quake.mz %4 : (!quake.ref) -> !quake.measure %91 = quake.discriminate %9 : (!quake.measure) -> i1 - %c3_3 = arith.constant 3 : index - memref.store %91, %1[%c3_3] : memref<4xi1> + %d = cc.compute_ptr %1[3] : (!cc.ptr>) -> !cc.ptr + cc.store %91, %d : !cc.ptr return } diff --git a/tools/cudaq-quake/CMakeLists.txt b/tools/cudaq-quake/CMakeLists.txt index 277df39c221..52e3d927507 100644 --- a/tools/cudaq-quake/CMakeLists.txt +++ b/tools/cudaq-quake/CMakeLists.txt @@ -25,8 +25,6 @@ target_link_libraries(cudaq-quake MLIRLLVMCommonConversion MLIRLLVMToLLVMIRTranslation - MLIRMemRefDialect - clangCodeGen clangFrontendTool clangFrontend